Skip to main content

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:

ToolMinimumRequired forInstall
Node.js22+Running npm run setup and repo toolingnodejs.org
GitanyCloning and contributinggit-scm.com
OrbStack or Docker Desktopcurrent stableFull-stack and indexer developmentorbstack.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:

ToolRequired forInstall
FoundryContract development and testscurl -L https://foundry.paradigm.xyz | bash && foundryup
cloudflaredHTTPS tunnel for mobile-device testingbrew install cloudflared

Docker, Foundry, and cloudflared warnings do not block setup unless you are working in the matching workflow.

Operating system support

OSSupport levelNotes
macOSNativeStandard local path. Full stack uses the Docker-backed indexer to avoid native Envio/Rust issues.
LinuxNativeGood path for native indexer work, provided Docker and generated indexer dependencies are ready.
WindowsWSL2 or dev containerNative 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:

  1. Dependency check -- verifies Node.js 22+ and Git, installs Bun when the selected profile allows it, and warns about missing workflow-specific tools.
  2. Install packages -- runs bun install for host setup, or bun install --frozen-lockfile for isolated/cloud setup when dependencies are not already present.
  3. Environment posture -- leaves host/cloud env materialization to the documented env:sync path, while isolated setup can write a non-secret baseline .env for web checks.
  4. 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

ProfileCommandUse forSide effects
Hostnpm run setup or bun run setup:hostFirst-clone local machine setupCan install Bun and Foundry, checks Docker, runs bun install, prints env-sync next steps
Isolatednpm run setup -- --profile isolated or bun run setup:isolatedWorktrees, containers, and agent-owned checkoutsCan install Bun, uses frozen lockfile install, can create non-secret baseline .env, avoids host/service/browser/sibling-worktree effects
Cloudbun run setup:cloudPre-baked remote runtimes with injected secretsExpects Bun in the image, uses frozen lockfile install, never writes .env

Role profiles

RoleRequired local stateFirst command
Frontend QANode.js, Git, Bun from setup, root .env, ports 3001/3002/3003/3004 availablebun run dev:doctor -- --profile web
Full-stack / indexerFrontend QA plus Docker, packages/indexer/generated, and generated dependenciesbun run dev:doctor -- --profile full
ContractsFrontend QA plus Foundrybun run dev:doctor -- --profile contracts
Upload-capable QAFrontend QA plus VITE_API_BASE_URL and PINATA_JWT_OP_REF=op://... or PINATA_JWT in root .envbun run dev:doctor -- --profile upload
Production-backed local reviewFrontend QA plus production Arbitrum/indexer/agent env overlaybun run dev:prod:health
Production local indexer mirrorProduction-backed review plus Docker and packages/indexer/generatedbun 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.

VariableRole
APP_ENVEnvironment selector (development, test, staging, production)
GG_WORKSPACE_PROFILENon-secret setup posture (host, isolated, cloud)
GG_SETUP_INSTALLSetup install policy (auto, always, skip)
GG_SETUP_ENV_MODESetup env materialization policy (auto, baseline, skip)
VITE_CHAIN_IDSets the target chain at build time (42161 for Arbitrum, 11155111 for Sepolia)
VITE_DEV_CHAIN_MODEOptional dev override. bun run dev sets arbitrum_fork; keep blank in shared env files.
VITE_LOCAL_FORK_RPC_URLOptional local fork RPC. The Green Goods dev stack sets http://127.0.0.1:3009.
VITE_ENABLE_ANVIL_WALLETSOptional local-development marker for disposable Anvil-funded wallets.
VITE_PASSKEY_RP_IDOptional passkey relying-party override. Leave blank for localhost development fallback; set localhost for explicit local passkey QA.
VITE_API_BASE_URLBrowser API origin. Upload-capable flows call the agent for short-lived Pinata signed upload URLs.
PINATA_JWTServer/script Pinata JWT used by the agent upload signer and maintenance scripts. Not embedded in browser bundles.
VITE_PINATA_GATEWAY_URLPublic Pinata gateway URL for reading IPFS media.
SEPOLIA_RPC_URLRPC 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:

PresentationURL
PWAhttps://localhost:3001/?presentation=pwa
Editorial websitehttps://localhost:3001/?presentation=website

For fork-mode transaction testing, use wallet auth with a disposable Anvil account:

  1. Start the stack with bun run dev.
  2. Open a dedicated dev browser profile.
  3. Add a custom wallet network named Green Goods Local Arbitrum Fork.
  4. Set RPC URL http://127.0.0.1:3009, chain id 42161, and currency symbol ETH.
  5. 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.
  6. 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 processPackageDefault port
clientpackages/client3001
adminpackages/admin3002
docsdocs3003
storybookpackages/shared3004

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 processPackageDefault port
clientpackages/client3001
adminpackages/admin3002
docsdocs3003
indexerpackages/indexer (Docker / GraphQL)3006
agentpackages/agent3005
tunnelcloudflared HTTPS tunnel--
browserlocal URL opener--
storybookpackages/shared3004
anvil-arbitrumpackages/contracts3009

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.

ModeCommandChain targetIndexerAgent/APIWrites
Fully local defaultbun run devArbitrum fork on http://127.0.0.1:3009 with chain id 42161Local Docker-backed Envio/Hasura on 3006-3008Local agent on 3005Wallet writes stay in local Anvil state
Hosted production-backedbun run dev:prodArbitrum One 42161Hosted production indexerhttps://agent.greengoods.appWallet-confirmed writes are real Arbitrum transactions
Local live-indexer mirrorbun run dev:prod:mirrorArbitrum One 42161Local Docker-backed Envio/Hasura indexing live Arbitrumhttps://agent.greengoods.appWallet-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:

  1. Use a dedicated QA wallet with only the funds you intend to risk.
  2. Select Arbitrum One in the wallet.
  3. Start bun run dev:prod or bun run dev:prod:mirror.
  4. Navigate to the production action and stop at wallet confirmation if you only need reachability proof.
  5. Broadcast only when the test intentionally mutates production state.
  6. 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.

Alternative: ADB port forwarding

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. 1

    Read project rules

    Start with `AGENTS.md`, `CLAUDE.md`, and `.claude/context/*` for conventions.

  2. 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. 3

    Check role readiness

    Run `bun run dev:doctor -- --profile web` before debugging setup. It does not mutate files or start services.

  4. 4

    Start services

    Run `bun run dev` for the fork-backed repo-native local stack.

  5. 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. 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. 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 test

bun 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

SymptomCauseFix
forge: command not foundFoundry not installedcurl -L https://foundry.paradigm.xyz | bash && foundryup
Indexer fails to startDocker not runningStart Docker or OrbStack, then retry bun run dev or the narrow workbench target dev launch green-goods:indexer-graphql
VITE_CHAIN_ID is undefinedMissing .envRun bun run env:sync, or copy .env.template to .env and fill values manually
Pinata forbidden or upload failsMissing signer or Pinata server credentialConfirm 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 respondVite failed to read envRun 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 portsWeb doctor failedFix the doctor failures first; smoke only probes services after the web profile is ready.
bun run dev:prod:smoke fails production-agent-healthHosted agent /health is not returning OKCheck 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 lagHosted indexer or local mirror is reachable but behind Arbitrum head beyond the smoke thresholdWait 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 agentWrong command or stale process was already runningRun 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 localhostRP ID mismatchLeave VITE_PASSKEY_RP_ID blank for fallback behavior, or set VITE_PASSKEY_RP_ID=localhost for local passkey QA
Port 3001/3002 in useAnother dev server runningRun 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 hangsStale dependency stateDelete node_modules, then bun install
No QR code on landing pagecloudflared not installedbrew install cloudflared, then restart bun run dev
Tunnel URL not appearingClient not ready yetThe 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_ID at build time. There is no runtime chain switching.
  • Deployment changes go through bun deploy:* wrappers, never raw forge commands.
  • 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