Getting Started
What this helps you do
Use this page to prepare a local Green Goods checkout, choose the right development profile, start the services you need, and verify that the web stack responds before you begin package work.
Before you start
Before cloning, install the tools the setup script cannot reliably provide:
| Tool | Minimum | Required for | Install |
|---|---|---|---|
| Node.js | 22+ | Running npm run setup and repo tooling | nodejs.org |
| Git | any | Cloning and contributing | git-scm.com |
| OrbStack or Docker Desktop | current stable | Full-stack and indexer development | orbstack.dev / docker.com |
Node includes npm, which is enough to run the first setup command. npm run setup installs Bun automatically if it is missing, installs workspace dependencies, and prints the env-bootstrap commands to run next.
Additional tools are workflow-specific:
| Tool | Required for | Install |
|---|---|---|
| Foundry | Contract development and tests | curl -L https://foundry.paradigm.xyz | bash && foundryup |
| cloudflared | HTTPS tunnel for mobile-device testing | brew install cloudflared |
Docker, Foundry, and cloudflared warnings do not block setup unless you are working in the matching workflow.
Operating system support
| OS | Support level | Notes |
|---|---|---|
| macOS | Native | Standard local path. Full stack uses the Docker-backed indexer to avoid native Envio/Rust issues. |
| Linux | Native | Good path for native indexer work, provided Docker and generated indexer dependencies are ready. |
| Windows | WSL2 or dev container | Native PowerShell is not a supported target yet because repo scripts use POSIX shell tools such as sh, bash, lsof, xargs, pkill, and shell env syntax. |
Use WSL2 for Windows contributors until a dedicated native Windows script pass lands.
Running Development Setup
git clone https://github.com/greenpill-dev-guild/green-goods.git
cd green-goods
npm run setup # first-clone bridge: deps and workspace install
bun run env:sync # materializes .env from .env.template via 1Password (op inject)
bun run dev:doctor -- --profile web # non-mutating web readiness check
bun run dev # starts the full repo-native local stack
bun run dev:smoke:web # confirms browser surfaces respond locally
For isolated worktrees, containers, or agent-owned checkouts, use the same npm bridge with the isolated profile:
npm run setup -- --profile isolated
The isolated profile requires Node.js 22+ and Git first. It can bootstrap Bun when network access is available, then uses bun install --frozen-lockfile after Bun is present. It avoids host-only setup such as Docker checks, Foundry installation, service starts/stops, browser launches, and sibling-worktree cleanup.
When you intentionally need local browser apps against live production
infrastructure, use bun run dev:prod instead of bun run dev. That mode is
documented below because wallet-confirmed writes are real Arbitrum
transactions.
bun run env:sync requires the 1Password CLI signed in with desktop app integration on. If you are not on the team and do not have vault access, copy .env.template to .env and fill local values directly (see Required Secrets & Variables).
npm run setup is the only npm entrypoint in the normal workflow. It exists because a fresh machine may not have Bun installed yet. After setup, use bun run ... for package operations and repo scripts.
What npm run setup does
The setup script (scripts/dev/setup.js) runs through four steps:
- Dependency check -- verifies Node.js 22+ and Git, installs Bun when the selected profile allows it, and warns about missing workflow-specific tools.
- Install packages -- runs
bun installfor host setup, orbun install --frozen-lockfilefor isolated/cloud setup when dependencies are not already present. - Environment posture -- leaves host/cloud env materialization to the documented
env:syncpath, while isolated setup can write a non-secret baseline.envfor web checks. - Next steps -- prints the doctor, stack, smoke, test, and cleanup commands for the selected profile.
Environment files are materialized separately by bun run env:sync (which runs op inject against .env.template). See Required Secrets & Variables below.
Workspace setup profiles
| Profile | Command | Use for | Side effects |
|---|---|---|---|
| Host | npm run setup or bun run setup:host | First-clone local machine setup | Can install Bun and Foundry, checks Docker, runs bun install, prints env-sync next steps |
| Isolated | npm run setup -- --profile isolated or bun run setup:isolated | Worktrees, containers, and agent-owned checkouts | Can install Bun, uses frozen lockfile install, can create non-secret baseline .env, avoids host/service/browser/sibling-worktree effects |
| Cloud | bun run setup:cloud | Pre-baked remote runtimes with injected secrets | Expects Bun in the image, uses frozen lockfile install, never writes .env |
Role profiles
| Role | Required local state | First command |
|---|---|---|
| Frontend QA | Node.js, Git, Bun from setup, root .env, ports 3001/3002/3003/3004 available | bun run dev:doctor -- --profile web |
| Full-stack / indexer | Frontend QA plus Docker, packages/indexer/generated, and generated dependencies | bun run dev:doctor -- --profile full |
| Contracts | Frontend QA plus Foundry | bun run dev:doctor -- --profile contracts |
| Upload-capable QA | Frontend QA plus VITE_API_BASE_URL and PINATA_JWT_OP_REF=op://... or PINATA_JWT in root .env | bun run dev:doctor -- --profile upload |
| Production-backed local review | Frontend QA plus production Arbitrum/indexer/agent env overlay | bun run dev:prod:health |
| Production local indexer mirror | Production-backed review plus Docker and packages/indexer/generated | bun run dev:prod:mirror:health |
Run bun run dev:doctor -- --profile web|full|contracts|upload|prod|prod-mirror any time you need a non-mutating check of the current machine. It reports missing tools, Docker state, Pinata readiness, passkey RP ID posture, indexer generated state, stale environment keys, and port conflicts without printing secret values. Add --json for machine-readable output in CI, containers, or agent workflows.
Required Secrets & Variables
Green Goods uses one root .env plus the committed .env.schema (full key contract) and .env.template (team-shared file with 1Password refs for shared secrets and plain values for non-secrets). Do not create package-level env files.
The standard flow:
bun run env:sync # materialize .env from .env.template via op inject (Touch ID once)
bun run env:check # verify .env satisfies .env.schema
For details on regenerating the template (e.g. after a new key lands in .env.schema), and for first-time setup without 1Password access, see Environment management.
| Variable | Role |
|---|---|
APP_ENV | Environment selector (development, test, staging, production) |
GG_WORKSPACE_PROFILE | Non-secret setup posture (host, isolated, cloud) |
GG_SETUP_INSTALL | Setup install policy (auto, always, skip) |
GG_SETUP_ENV_MODE | Setup env materialization policy (auto, baseline, skip) |
VITE_CHAIN_ID | Sets the target chain at build time (42161 for Arbitrum, 11155111 for Sepolia) |
VITE_DEV_CHAIN_MODE | Optional dev override. bun run dev sets arbitrum_fork; keep blank in shared env files. |
VITE_LOCAL_FORK_RPC_URL | Optional local fork RPC. The Green Goods dev stack sets http://127.0.0.1:3009. |
VITE_ENABLE_ANVIL_WALLETS | Optional local-development marker for disposable Anvil-funded wallets. |
VITE_PASSKEY_RP_ID | Optional passkey relying-party override. Leave blank for localhost development fallback; set localhost for explicit local passkey QA. |
VITE_API_BASE_URL | Browser API origin. Upload-capable flows call the agent for short-lived Pinata signed upload URLs. |
PINATA_JWT | Server/script Pinata JWT used by the agent upload signer and maintenance scripts. Not embedded in browser bundles. |
VITE_PINATA_GATEWAY_URL | Public Pinata gateway URL for reading IPFS media. |
SEPOLIA_RPC_URL | RPC endpoint for Sepolia fork tests |
Variables prefixed with VITE_ are embedded into frontend bundles at build time.
For personal local-only credentials, put direct values in root .env. For shared Green Goods credentials (upload-capable QA, deploys, CI), edit .env.template to use 1Password URIs in the op://Vault/Item/field form, sign in to the op CLI, and run bun run env:sync.
Running services
Repo-native local stack
Prefer the repo-native stack for day-to-day single-repo development:
bun run dev
The repo-native PM2 stack starts the Arbitrum fork on port 3009 by default,
then starts the client/admin surfaces with VITE_DEV_CHAIN_MODE=arbitrum_fork,
VITE_CHAIN_ID=42161, and VITE_LOCAL_FORK_RPC_URL=http://127.0.0.1:3009.
It streams logs in the foreground, opens the review URLs, and cleans up on
Ctrl-C.
green-goods:client opens both review presentations:
| Presentation | URL |
|---|---|
| PWA | https://localhost:3001/?presentation=pwa |
| Editorial website | https://localhost:3001/?presentation=website |
For fork-mode transaction testing, use wallet auth with a disposable Anvil account:
- Start the stack with
bun run dev. - Open a dedicated dev browser profile.
- Add a custom wallet network named
Green Goods Local Arbitrum Fork. - Set RPC URL
http://127.0.0.1:3009, chain id42161, and currency symbolETH. - Import one Anvil-funded account from
packages/contracts/.generated/runtime/arbitrum-fork.json. The launcher keeps Anvil startup output quiet and redacts the fork endpoint in that file so provider credentials do not appear in logs. - Connect that wallet in the app and sign transactions normally.
The fork uses the real Arbitrum deployment artifact, but every write is mined
only in local Anvil state. Restarting the fork resets local chain state.
Passkey and smart-account writes are intentionally blocked in fork mode until
local account-abstraction infrastructure exists. ?mockAuth=operator only
changes UI auth state; it does not sign transactions and cannot replace the
wallet step.
bun run dev:web is still available as a repo-native PM2 fallback:
| PM2 process | Package | Default port |
|---|---|---|
client | packages/client | 3001 |
admin | packages/admin | 3002 |
docs | docs | 3003 |
storybook | packages/shared | 3004 |
Use this focused PM2 path when you only want the browser surfaces. After those
surfaces are up, run
bun run dev:smoke:web in another terminal. It reruns the web doctor, then
checks that the client, admin, docs, and Storybook respond on their local ports.
Full stack
The repo-native full local stack starts with:
bun run dev
That includes the fork-backed app surfaces, agent, and Docker-backed indexer.
For cross-repo orchestration, dev launch green-goods calls this same native
repo command under workbench ownership.
The Docker-backed indexer is local infrastructure, but it mirrors the configured
live networks into local Postgres/Hasura. Set ENVIO_API_TOKEN in the root
.env when you need reliable catch-up and a passing bun run dev:smoke:full
indexer-lag proof; without it, HyperSync can return 429 Too Many Requests and
the smoke should fail rather than claim the local mirror is current.
| PM2 process | Package | Default port |
|---|---|---|
client | packages/client | 3001 |
admin | packages/admin | 3002 |
docs | docs | 3003 |
indexer | packages/indexer (Docker / GraphQL) | 3006 |
agent | packages/agent | 3005 |
tunnel | cloudflared HTTPS tunnel | -- |
browser | local URL opener | -- |
storybook | packages/shared | 3004 |
anvil-arbitrum | packages/contracts | 3009 |
Stop everything with bun run dev:stop. To remove only disposable artifacts in
the current checkout, use bun run dev:clean:dry first, then bun run dev:clean.
The cleanup script does not stop shared services, remove dependencies, touch env
files, or inspect sibling worktrees.
Production-backed local stack
Use this only when you intentionally want local browser surfaces to read from production infrastructure instead of the local Arbitrum fork.
| Mode | Command | Chain target | Indexer | Agent/API | Writes |
|---|---|---|---|---|---|
| Fully local default | bun run dev | Arbitrum fork on http://127.0.0.1:3009 with chain id 42161 | Local Docker-backed Envio/Hasura on 3006-3008 | Local agent on 3005 | Wallet writes stay in local Anvil state |
| Hosted production-backed | bun run dev:prod | Arbitrum One 42161 | Hosted production indexer | https://agent.greengoods.app | Wallet-confirmed writes are real Arbitrum transactions |
| Local live-indexer mirror | bun run dev:prod:mirror | Arbitrum One 42161 | Local Docker-backed Envio/Hasura indexing live Arbitrum | https://agent.greengoods.app | Wallet-confirmed writes are real Arbitrum transactions |
Hosted production-backed mode:
bun run dev:prod:health
bun run dev:prod
bun run dev:prod starts only the local client, admin, docs, Storybook, and
browser helper. It does not start local Anvil, the local indexer, the local
agent, or a tunnel. After ports are ready, it automatically runs a read-only
production smoke.
Local live-indexer mirror mode:
bun run dev:prod:mirror:health
bun run dev:prod:mirror
This keeps the browser apps pointed at Arbitrum One while running the local
Docker-backed Postgres/Hasura/Envio stack on ports 3006-3008.
The production smoke proves the local surfaces respond, RPC chain id is
42161, a deployed Arbitrum contract has bytecode, production agent /health
returns status: "ok", indexer GraphQL returns Arbitrum chain metadata, and
the indexer is within the configured lag threshold of Arbitrum head. Repeat it
on demand:
bun run dev:prod:smoke
bun run dev:prod:smoke -- --mode mirror
bun run dev:prod:smoke -- --max-indexer-lag-blocks 5000
The smoke never submits transactions. A real live-write proof is manual:
- Use a dedicated QA wallet with only the funds you intend to risk.
- Select Arbitrum One in the wallet.
- Start
bun run dev:prodorbun run dev:prod:mirror. - Navigate to the production action and stop at wallet confirmation if you only need reachability proof.
- Broadcast only when the test intentionally mutates production state.
- Record the route, expected contract/action, wallet network, and transaction hash if a broadcast is intentionally submitted.
The production agent has two health endpoints. /health is the Fly machine
health contract used by the smoke. /ready is stricter and can return 503 while
optional AI/voice model readiness is still loading, even when the agent,
webhook, and routine API are usable.
Individual packages
You can also run packages directly without PM2:
bun run dev:client # Gardener PWA
bun run dev:admin # Operator dashboard
bun run dev:docs # Docusaurus site
bun run dev:indexer # Envio indexer (Docker Compose)
bun run dev:contracts # Anvil local chain
bun run dev:agent # Agent bot
Mobile device testing
Green Goods is a mobile-first PWA, so testing on a real phone is essential. When you run bun run dev, a cloudflared tunnel starts automatically alongside the other services. It creates a temporary public HTTPS URL (e.g. https://random-words.trycloudflare.com) that points to your local client on port 3001.
Once the tunnel is ready, the landing page QR code automatically switches to the tunnel URL. Open https://localhost:3001 on your laptop and scan the QR code with your phone — you'll get the full PWA experience including service worker, install prompts, and passkey auth.
# Tunnel starts automatically with bun run dev.
# To run it standalone (e.g. for the admin dashboard):
bun run dev:tunnel # default: port 3001
bun run dev:tunnel -- --port 3002 # admin on port 3002
If cloudflared is not installed, the tunnel service exits silently and the QR code falls back to window.location.origin. Install it with brew install cloudflared.
If you prefer a wired connection, plug in your Android device with USB debugging enabled and run:
adb reverse tcp:3001 tcp:3001
Then navigate to https://localhost:3001 on Android Chrome. Since localhost is a secure context, the service worker registers without a trusted CA certificate.
How to know it worked
- 1
Read project rules
Start with `AGENTS.md`, `CLAUDE.md`, and `.claude/context/*` for conventions.
- 2
Configure env bootstrap
Keep the generated root `.env` for baseline web dev. Add direct personal values only when needed; use 1Password refs for shared team, deploy, upload, or CI secrets. Never add package-level env files.
- 3
Check role readiness
Run `bun run dev:doctor -- --profile web` before debugging setup. It does not mutate files or start services.
- 4
Start services
Run `bun run dev` for the fork-backed repo-native local stack.
- 5
Smoke the web stack
Run `bun run dev:smoke:web` once client/admin/docs/Storybook are starting. It verifies local HTTP/HTTPS reachability.
- 6
Use production deliberately
Run `bun run dev:prod` only when you want local apps against production Arbitrum, indexer, and agent APIs. The automatic smoke is read-only; wallet-confirmed writes are real.
- 7
Run the quality gate
Run `bun run format:check`, `bun run lint`, `bun run test`, and `bun run build` before pushing changes.
Development workflow
The standard edit-test-build-lint cycle:
# 1. Run tests for the package you changed (always `bun run test`, never `bun test`)
cd packages/shared && bun run test
# 2. Check formatting and lint the workspace
bun run format:check
bun run lint
# 3. Build (respects dependency order: contracts -> shared -> indexer -> client/admin)
bun run build
# 4. Format before committing
bun run format
bun test vs bun run testbun test invokes Bun's built-in test runner, which ignores your Vitest config. Always use bun run test to run the package.json script with proper environment setup.
If something goes wrong
| Symptom | Cause | Fix |
|---|---|---|
forge: command not found | Foundry not installed | curl -L https://foundry.paradigm.xyz | bash && foundryup |
| Indexer fails to start | Docker not running | Start Docker or OrbStack, then retry bun run dev or the narrow workbench target dev launch green-goods:indexer-graphql |
VITE_CHAIN_ID is undefined | Missing .env | Run bun run env:sync, or copy .env.template to .env and fill values manually |
Pinata forbidden or upload fails | Missing signer or Pinata server credential | Confirm VITE_API_BASE_URL points at the agent and add PINATA_JWT to root .env (or set it via 1Password ref in .env.template and re-run bun run env:sync), then rerun bun run dev:doctor -- --profile upload |
Client PM2 process is online but localhost:3001 does not respond | Vite failed to read env | Run bun run dev:doctor -- --profile web and bun run env:check. For 1Password-resolved env, ensure op CLI is signed in via desktop integration, then bun run env:sync. |
bun run dev:smoke:web fails before checking ports | Web doctor failed | Fix the doctor failures first; smoke only probes services after the web profile is ready. |
bun run dev:prod:smoke fails production-agent-health | Hosted agent /health is not returning OK | Check https://agent.greengoods.app/health. Do not use /ready as the production health contract unless voice-model readiness is the thing being tested. |
bun run dev:prod:smoke fails indexer lag | Hosted indexer or local mirror is reachable but behind Arbitrum head beyond the smoke threshold | Wait for catch-up and rerun, or pass --max-indexer-lag-blocks <blocks> when intentionally debugging a stale mirror. |
bun run dev:prod starts local Anvil or local agent | Wrong command or stale process was already running | Run dev status green-goods; production-backed mode should leave 3005-3009 free except mirror mode uses 3006-3008. Stop only owned PM2/workbench processes. |
| Passkey registration fails on localhost | RP ID mismatch | Leave VITE_PASSKEY_RP_ID blank for fallback behavior, or set VITE_PASSKEY_RP_ID=localhost for local passkey QA |
| Port 3001/3002 in use | Another dev server running | Run dev status green-goods. If the surface is owned, reuse it or stop it with dev stop green-goods:<surface>. If the PID is unknown, inspect it and do not kill it from an agent without explicit direction. |
bun install hangs | Stale dependency state | Delete node_modules, then bun install |
| No QR code on landing page | cloudflared not installed | brew install cloudflared, then restart bun run dev |
| Tunnel URL not appearing | Client not ready yet | The tunnel waits up to 60s for port 3001 — check PM2 logs with bunx pm2 logs tunnel |
Non-negotiables
- All React hooks live in
packages/shared-- client and admin only have components and views. - The target chain derives from
VITE_CHAIN_IDat build time. There is no runtime chain switching. - Deployment changes go through
bun deploy:*wrappers, never rawforgecommands. - User-facing strings must be added to all three translation files (
en.json,es.json,pt.json).
Next page
Next best action
Keep momentum by moving to the next high-value step in this journey.
Architecture