CLI spec

The normative contract: every command, flag, exit code, and the groot.json schema. Edit on GitHub

groot CLI specification

Status: normative contract. init shipped in v0.2; add and doctor in v0.3; init --preset in v0.4. Changes to this document are semver-relevant — stability.md defines exactly which surfaces are covered and the change rules (binding since v1.0.0).

Invocation forms

FormNotes
bun create groot [dir] [flags]bun resolves to the create-groot package; a leading bare destination (no subcommand) is routed to init
bunx create-groot@latest init [dir] [flags]explicit
groot init [dir] [flags]standalone compiled binary (curl/PowerShell install)

The npm package requires Bun ≥ 1.2 on the invoking machine. The compiled binary embeds its own runtime, but scaffolded workspaces still require Bun — groot checks and reports this in preflight.

groot init [dir]

Plant a new workspace. Interactive by default; fully scriptable with flags.

Flags

FlagValuesDefaultNotes
--name <name>stringdir basenameWorkspace/root package name
--web <choice>next | sveltekit | tanstack-start | astro | react-router | nuxt | vite | nonepromptWeb app in apps/web
--mobile <choice>expo | react-native | nonepromptMobile app in apps/mobile
--desktop <choice>tauri | electron | nonepromptDesktop app in apps/desktop (v1.1)
--api <choice>elysia | hono | fastify | nonepromptAPI app in apps/api
--backend <choice>convex | supabase | nonepromptBackend in packages/backend
--preset <path>groot.json file or workspace dirSelections source for slots not fixed by flags — see Presets
--yes, -yoffAccept defaults for all unanswered prompts
--dry-runoffPrint the resolved plan; write nothing
--jsonoffWith --dry-run: machine-readable plan on stdout
--no-installinstall onSkip root bun install
--no-gitgit onSkip git init + initial commit
--githuboffAfter the initial commit: create + push a GitHub repository via gh (see GitHub publishing)
--publicoffWith --github: make the created repository public (private otherwise)
--dir-conflict <policy>error | merge | incrementerrorNon-empty target directory policy
--keep-failedoffDon't delete the target dir if a generator fails
--verboseoffStream generator output instead of spinners
--version, -v / --help, -hStandard

Defaults (--yes with no selection flags)

--web next --mobile none --api none --backend convex — a Next.js app wired to a Convex backend: groot's flagship pairing.

GitHub publishing

--github runs after verify's initial commit: Bun.which("gh")gh auth statusgh repo create <name> --private|--public --source=. --remote=origin --push (every flag verified against the published gh sources and the 2.96.0 binary; non-interactive gh repo create requires exactly one visibility flag, and --push hard-errors on a repo with zero commits).

Presets

--preset <path> reads an existing groot.json — a file, or a workspace directory containing one — as the selections source: groot init my-app --preset ../flagship replicates the shape of the workspace that manifest describes.

Interactive flow

Prompts (clack) run only for slots not already fixed by flags — mixing flags and prompts is supported. Prompt order: web → mobile → api → backend → confirm plan. Ctrl-C at any prompt exits with code 130 and writes nothing.

Output contract

Exit codes

CodeMeaning
0Success
1Unexpected internal error
2Invalid flags/arguments
3Preflight failure (bun missing, dir conflict under error policy, offline)
4Generator failure (upstream create-* command failed)
5Stitch/verify failure
130Cancelled at a prompt

groot add <scaffold> (v0.3)

Grow an existing groot workspace: groot add expo, groot add hono, groot add sveltekit --path apps/marketing.

Runs the same grow → stitch → verify stages as init for just the new scaffold — no trunk is re-planted. Because every stitch operation is idempotent over the existing scaffolds, cross-cutting wiring lands automatically: adding convex links the existing web/mobile apps to the backend, and adding a frontend next to an existing backend wires the new app the same way. The stitch stage persists the updated groot.json.

Flags

FlagValuesDefaultNotes
<scaffold>next | sveltekit | tanstack-start | astro | react-router | nuxt | vite | expo | react-native | tauri | electron | elysia | hono | fastify | convex | supabaserequiredPositional: the scaffold to grow
--path <dir>workspace-relativethe framework's slot pathFresh directory (absent or empty), a direct child of apps/ (web/mobile/api) or packages/ (backend), not claimed by groot.json
--no-installinstall onSkip the root bun install after growing
--keep-failedoffKeep the new scaffold directory if its generator fails
--dry-runoffPrint the would-be scaffold and the groot.json change; write nothing
--jsonoffWith --dry-run: the would-be groot.json (manifest schema) on stdout
--verboseoffStream generator output instead of progress lines

Rules

Exit codes

Same table as init: 2 invalid arguments and occupancy violations, 4 generator failure, 5 stitch/verify failure.

groot doctor (v0.3)

Verify workspace health: workspace globs valid, single lockfile, no port collisions, per-scaffold checks from each adapter (e.g. convex/_generated in sync), bun version, turbo tasks resolvable. Exits 0 (healthy) or 5 (problems found, each with a suggested fix). --json emits structured results.

groot.json manifest

Written to the workspace root by init, updated by add. The manifest is groot's memory — never required by the apps themselves at runtime.

{
  "$schema": "https://raw.githubusercontent.com/bloxy-studios/groot/main/schemas/groot.schema.json",
  "version": 1,                      // manifest schema version
  "createdWith": "create-groot@0.2.0",
  "conventions": {
    "packagesNamespace": "@repo"
  },
  "scaffolds": [
    {
      "slot": "web",
      "framework": "next",
      "path": "apps/web",
      "generator": "create-next-app@16",   // pinned major actually used
      "port": 3000
    },
    {
      "slot": "backend",
      "framework": "convex",
      "path": "packages/backend",
      "generator": null,                   // groot wrote these files directly
      "port": null
    }
  ]
}

Non-interactive contract (CI & agents)

groot treats scriptability as a first-class feature:

  1. Any run where every slot is fixed by flags (or a --preset) and --yes is present must complete with zero prompts, or exit non-zero — it must never hang waiting for input.
  2. --dry-run --json (on init and add) is stable, versioned output (the manifest schema) — safe for agents to parse; doctor --json emits its structured results the same way.
  3. Progress output is plain and line-based everywhere — groot has no spinners to garble CI logs. --verbose additionally streams full generator output.
  4. Anything interactive that cannot be avoided (today: Convex login) is never run by groot — it is printed as a next step instead.

Recipes for CI systems and agents live in ci.md.