Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/json-cli-default.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@genie-react/cli": minor
---

Migrate the CLI to JSON output by default, including help, version, setup receipts and failures. Preserve existing tool JSON schemas and --json compatibility, document JSONL batch/fields/hub streams, and bound output with explicit truncation. Diagnostics are JSONL on stderr. Consumers parsing previous human output must migrate to the structured fields; see docs/json-cli.md.
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ jobs:
- name: Build
run: pnpm build

- name: Verify JSON CLI transcripts
run: node scripts/check-cli-json.mjs

- name: Typecheck Expo integration
run: pnpm --filter @genie-react/expo-demo typecheck

Expand Down
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,12 +349,16 @@ npx @genie-react/cli batch \
'[{"tool":"react_get_tree","args":{"depth":2}},{"tool":"react_get_renders","args":{"limit":5}}]' --ndjson
```

- `--json` writes one JSON value.
- Commands return compact JSON by default, including `--help`, `--version`, setup receipts, and failures. `--json` remains accepted.
- `batch` writes JSONL by default. `--ndjson` makes that explicit.
- `batch --json` writes one JSON array.
- CLI status, batch, and error objects include `schemaVersion`.
- `hub` emits ready/reused/stopped JSONL. `--fields` emits projected JSONL rows; an empty collection emits zero bytes.
- CLI-owned records include `schemaVersion`; tool results keep their advertised schema.
- Results default to a 262,144-byte limit. Oversized results return `status:"truncated"`; use tool pagination, `--select`, or `--max-bytes` to retrieve the evidence you need. The default JSONL limit applies per record; an explicit batch `--max-bytes` caps the whole command.

Use `--verbose` when startup hangs. It prints the CLI version, chosen connection, session, and time limits to stderr, so JSON stdout stays clean.
Use `--verbose` when startup hangs. It emits structured JSONL diagnostics to stderr. Connection secrets are omitted.

See the [JSON CLI migration and design](docs/json-cli.md) for compatibility details and research.

## Setup by platform

Expand Down Expand Up @@ -476,8 +480,7 @@ npx @genie-react/cli tools react.render
npx @genie-react/cli tools react_render_causes
```

Group listings show parameter names, enum values, bounds, and defaults in human output and compact
JSON. Use `tools <tool>` for nested options and a runnable example. For named render/effect reports,
Group listings expose parameter names, enum values, bounds, and defaults in JSON. Use `tools <tool>` for nested options and a runnable example. For named render/effect reports,
use `component`; `react_component_cohort` uses that same key with exact matching by default. Tools
that inspect or mutate one live instance use its returned `id`, which is distinct from a name filter.
`react_get_renders` accepts `sort:"updates"` to rank update counts before applying the result limit.
Expand Down
29 changes: 27 additions & 2 deletions apps/docs/content/docs/reference/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ description: Setup, discovery, live calls, and machine output.
icon: Terminal
---

The CLI emits compact JSON in terminals, pipes, and CI. `--json` remains a compatibility alias.
`--help` returns structured command metadata and `--version` returns a version object.

## Commands

| Command | Purpose |
Expand All @@ -16,6 +19,7 @@ icon: Terminal
| `status` | Show bridge, app, session, and readiness state. |
| `call` | Run one live tool. |
| `batch` | Run several calls over one connection. |
| `capture export` | Write a verified capture artifact and return its receipt. |

## Discover a tool

Expand All @@ -42,7 +46,7 @@ npx @genie-react/cli call react_get_tree '{}' \
| `--wait` | Waiting for an app session. Range: 1–120000 ms. |
| `--timeout` | One tool call. The bridge clamps it to 1000–120000 ms. |

`[busy]` means the app's main thread is blocked. Wait for the returned delay and retry.
`reason:"busy"` indicates the app cannot service the call yet. Inspect app state and the returned retry delay. A timeout does not establish whether a mutation completed.

## Targeting

Expand All @@ -55,5 +59,26 @@ Use `GENIE_SESSION` and `GENIE_BRIDGE_URL` to set these once per shell.

## Stable failures

Machine-mode failures keep stdout as valid JSON. They include a stable `reason` and a safe next
Failures keep stdout as valid JSON. They include a stable `reason` and a safe next
command when recovery is known. A failed batch item does not stop later items.

## Output framing and limits

Finite commands return one JSON value. `call` retains the advertised tool result schema. Setup
receipts list artifacts and manual actions without dumping source files. Diagnostic records use
JSONL on stderr; `--verbose` enables connection phases.

`batch` and `--ndjson` emit one result per line; `batch --json` returns one array. `--fields`
emits projected JSONL rows and zero bytes for an empty collection. `hub` emits `ready`, `reused`,
and `stopped` lifecycle records. Process exit is 0 for success and 1 for failure.

Output defaults to 262,144 bytes per JSON document or default JSONL record. An explicit batch
`--max-bytes` bounds the whole command. `--max-bytes` accepts 512–50,000,000 bytes. Oversized
output becomes an explicit `status:"truncated"` envelope. Retrieve fewer records using tool
pagination, select a branch with `--select`, or raise the limit; never treat truncation as complete evidence.

```bash
npx @genie-react/cli call --help
npx @genie-react/cli tools timeline_read
npx @genie-react/cli call react_get_renders '{}' --select /coverage
```
59 changes: 59 additions & 0 deletions docs/json-cli.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# JSON CLI contract

Genie commands return JSON by default in terminals, pipes and CI. This is an intentional migration from human summaries. Existing `--json` invocations remain valid and existing tool result fields keep their meaning. The CLI does not add a universal wrapper around tool payloads.

## Framing

| Surface | Contract |
| --- | --- |
| `call`, `status`, `tools`, `doctor`, `init`, `link`, `capture export` | One compact JSON value followed by a newline |
| `--help`, no command | JSON command metadata: arguments, accepted flags, types, required options, defaults, limits, examples and output format |
| `--version` | Versioned JSON object with `version` |
| `batch`, `batch --ndjson` | One JSON object per result line; an empty batch emits zero bytes |
| `batch --json` | One JSON array |
| `call --fields` | Projected JSONL rows; an empty collection emits zero bytes |
| `hub` | JSONL lifecycle records: `ready`, `reused`, `stopped`; structured failures |
| Diagnostics | JSONL on stderr; no ANSI or prose preambles |

Exit codes remain 0 for command success and 1 for failure. A failed batch item does not stop subsequent calls. A tool can report an unsuccessful domain outcome without a transport failure; retain `--fail-on-result-error` for wait/quiesce commands when that outcome should set exit 1. Graceful hub shutdown exits 0 and only stops the hub owned by this process.

CLI-created failures have `schemaVersion`, `status`, `reason`, `message` and `userActionRequired`. Where recovery is known, `next.argv` provides argument boundaries and `next.command` provides a safely constructed command. Raw malformed input and upstream exception text are excluded from trusted error messages. Tool data remains application data, even when its text looks like instructions.

Setup receipts list planned, applied, already-present and manual artifacts, including the discovery ignore entry and bundled skill files. Source contents are omitted. Required manual component wiring and package-manager install/dev commands remain available as structured instructions. A setup operation that fails after a write reports that changes may have applied; the CLI does not claim rollback.

## Bounds and recovery

The default ceiling is 262,144 bytes per JSON document or default JSONL record, including its newline. An explicit batch `--max-bytes` buffers results and bounds the whole command. Hub lifecycle records are independently bounded. JSONL streams are not presented as one JSON document with a fixed total size.

`--max-bytes` accepts 512–50,000,000. An oversized response becomes an explicit `status:"truncated"` envelope with size and omitted-path information. It is not a complete result. Use a tool's pagination/limit arguments, `--select`, or a larger byte limit to recover the required evidence. An export artifact's content is separate from its stdout receipt.

```sh
npx @genie-react/cli call --help
npx @genie-react/cli tools timeline_read
npx @genie-react/cli call react_get_renders '{}' --select /coverage
npx @genie-react/cli batch '[{"tool":"react_get_renders"}]' --json
```

## Migration

Consumers that scraped previous human summaries, help, version text or setup banners must parse the new fields. Existing scripts using `--json`, JSONL batches, field projection and raw tool schemas keep those formats. New metadata fields on doctor reports are additive. Responses above the new default ceiling now require pagination, selection or an explicit higher limit. The package changeset declares this pre-1.0 CLI migration as a minor release; it does not alter the hub wire-protocol version.

The executable routes results through shared bounded JSON serializers and derives command help/accepted options from the same registry. Collector APIs remain unchanged. Low-level setup logger injection remains available to library callers; it is not the executable's output contract.

## Research behind the decision

Inspected primary web and GitHub sources on September 5, 2026:

- [Vercel CLI UX](https://github.com/vercel/vercel/blob/main/packages/cli/.agents/skills/cli-ux/SKILL.md) and its [core rules](https://github.com/vercel/vercel/blob/main/packages/cli/.agents/skills/cli-ux/references/core.md) emphasize stable machine fields, isolated diagnostics, bounded results, runtime introspection and explicit tested migrations. They do not mandate JSON as every CLI's default.
- [Google Workspace CLI](https://github.com/googleworkspace/cli) provides JSON-oriented responses and runtime schema discovery. Its [formatter](https://github.com/googleworkspace/cli/blob/main/crates/google-workspace-cli/src/formatter.rs) uses JSON by default and supports alternate formats. Its pagination emits compact records; this is a useful precedent, not an official Google support guarantee.
- [GitHub CLI formatting](https://cli.github.com/manual/gh_help_formatting) uses opt-in JSON and field discovery. Its useful lesson here is discoverable schemas and focused selection, not copying its human default or adding a new query-language dependency.
- [Vercel agent-browser](https://github.com/vercel-labs/agent-browser#agent-mode) combines structured output with concise observations and stable references. Useful evidence and identifiers matter beyond output syntax.
- [JSON Lines](https://jsonlines.org/) defines one complete JSON value per line; blank lines are not records. Genie therefore emits zero bytes for empty projected collections.

JSON-only defaults are Genie's product decision. The research supports the contract mechanisms; it does not establish an industry consensus that every CLI should remove human output.

## Verification

Run `pnpm check`, then `node scripts/check-cli-json.mjs`. The subprocess suite exercises every command family, help/version/parser errors, default/explicit JSON parity, real local setup, large multibyte output above the pipe buffer, explicit truncation, JSONL batches, a real hub, occupied ports and interrupt cleanup.

`node scripts/check-timeline-e2e.mjs` drives an actual Chromium React app through the built CLI without `--json`, including all four timeline lanes and the script entry. `pnpm test:e2e` validates the packaged Vite integration and existing compatibility paths. Focused tests cover leaked error sentinels, output cap removal, preview-versus-applied receipts, empty rows, partial failures and listener cleanup.
4 changes: 3 additions & 1 deletion packages/cli/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ genie-react doctor --json
genie-react status --sessions-only --json
```

Machine output belongs on stdout; diagnostics belong on stderr. If doctor reports that this skill is stale, run `genie-react init` to refresh the bundled copy before continuing.
CLI stdout is JSON by default in terminals and pipes; `--json` remains accepted. `--help` returns command metadata. Discover live input/output schemas with `tools <tool>`. Diagnostics are JSONL on stderr. If doctor reports that this skill is stale, run `genie-react init` to refresh the bundled copy before continuing.

When several tabs are connected, name and pin the intended tab:

Expand Down Expand Up @@ -142,6 +142,8 @@ The export verifies its embedded checksum before writing. Capture reads default

## Bound large output

Results default to 262,144 bytes per JSON document or JSONL record. A `status:"truncated"` response is incomplete evidence: select fields, paginate the tool, or raise `--max-bytes`. Default batch and `--fields` output are JSONL; empty projected collections emit no rows. Use `batch --json` for one array. An explicit batch `--max-bytes` bounds the whole command. Hub output is lifecycle JSONL.

Use tool limits first, then nested selection and a byte ceiling:

```bash
Expand Down
Loading
Loading