Priority: medium · Converges the existing `outputSchema` metadata with runtime enforcement, and gives agents a stable contract.
Problem
`src/cli/json-output.ts` exposes a boolean `--json` flag plus a single envelope:
```ts
interface JsonEnvelope {
success: boolean;
command: string;
data?: unknown;
error?: string;
}
```
There is no way to select which fields appear inside `data`, and no on-the-fly filtering. Consequences for agents:
- Agents must serialize the entire `data` payload to find one value.
- Adding a new field to `data` is potentially breaking — consumers may pin to the current shape and choke on additions.
- The `outputSchema` field already declared on every `AgentCommandMeta` is documentation-only; nothing enforces it at runtime.
`gh`'s pattern in `pkg/cmdutil/json_flags.go::AddJSONFlags(cmd, &exporter, fields)`:
- Per-command allowlist of field names is declared in code.
- `--json field1,field2` validates against the allowlist; unknown fields error with a sorted suggestion list.
- `--jq ` runs the result through jq.
- `--template ` formats the result.
- Shell completion for `--json ` returns the allowlist.
Current behavior
```bash
$ allagents plugin list --json
{
"success": true,
"command": "plugin list",
"data": {
"plugins": [...], # full shape, all fields, always
"total": 12
}
}
Field selection is not supported:
$ allagents plugin list --json=name
error: found 1 error
--json=name plugin list
^ Unknown arguments
No --jq / --template:
$ allagents plugin list --json --jq '.data.plugins[].name'
error: --jq is not a known option
```
Expected behavior
```bash
(1) Boolean form still works (backward compatible)
$ allagents plugin list --json
{ "success": true, "command": "plugin list", "data": { "plugins": [...], "total": 12 } }
(2) Field allowlist
$ allagents plugin list --json=name,source
{
"success": true,
"command": "plugin list",
"data": { "plugins": [{"name": "...", "source": "..."}, ...] }
}
(3) Unknown field fails fast with sorted suggestion list
$ allagents plugin list --json=nope
Error: Unknown JSON field: "nope"
Available fields:
clients
install
name
skills
source
exit 2
(4) --jq pipes the envelope through jq
$ allagents plugin list --json --jq '.data.plugins | map(.name)'
["foo", "bar", "baz"]
(5) --template uses ES6-style or Handlebars-style template (pick one)
$ allagents plugin list --json --template '{{range .data.plugins}}{{.name}}\n{{end}}'
foo
bar
baz
(6) --agent-help advertises the allowlist
$ allagents --agent-help "plugin list" | jq .json_fields
["clients", "install", "name", "skills", "source"]
```
Verification gate (must pass before closing)
```bash
set -euo pipefail
bun run build
WS=$(mktemp -d)
cd "$WS"
allagents workspace init --client claude
allagents skills add brainstorming --from anthropics/superpowers
(1) Boolean --json still returns the full envelope (backward compat)
allagents --json skills list | jq -e '.command == "skills list" and (.data.skills | type == "array")'
(2) Field selection returns only the requested fields
allagents --json=name,plugin skills list | jq -e '.data.skills[0] | keys == ["name", "plugin"]'
(3) Unknown field exits non-zero with a sorted suggestion list
OUT=$(allagents --json=bogus skills list 2>&1 || true)
echo "$OUT" | grep -q 'Unknown JSON field'
echo "$OUT" | grep -q 'Available fields'
(4) --jq filter
allagents --json --jq '.data.skills | map(.name)' skills list | jq -e 'type == "array"'
(5) --jq without --json is rejected
! allagents --jq '.data' skills list
(6) Allowlist is discoverable via --agent-help
allagents --agent-help "skills list" | jq -e '.json_fields | type == "array" and length >= 1'
cd / && rm -rf "$WS"
```
All six checks must pass.
Implementation notes
- Add `jsonFields: readonly string[]` to `AgentCommandMeta` in `src/cli/help.ts`. Populate every existing meta in `src/cli/metadata/*.ts` from its existing `outputSchema`.
- `src/cli/json-output.ts`: rework `extractJsonFlag` to accept `--json` (boolean), `--json=field1,field2` (or `--json field1,field2`), `--jq `, `--template `. Mutually-exclusive rules:
- `--jq` requires `--json`.
- `--template` requires `--json`.
- `--jq` and `--template` mutually exclusive.
- Field validation happens at the point of envelope construction: each command knows its meta, the meta knows its `jsonFields` allowlist. Reject unknowns with a sorted Available-fields list and exit 2.
- For `--jq` use a small embedded jq (e.g., `node-jq`, `jq-wasm`). Bun's runtime supports both.
- For `--template`: pick a template engine. Simplest is Handlebars or Mustache; closest to `gh`'s Go templates would be `gomplate`-style but that's overkill. Document the choice in the meta.
- Update `src/cli/agent-help.ts::formatForAgent` to emit a `json_fields` key when the meta defines one.
- Backward compat: every existing `--json` invocation that returns the full envelope must continue to do so when no field list is given.
Refs
- Reference impl: `cli/cli` `pkg/cmdutil/json_flags.go` (`AddJSONFlags`, `checkJSONFlags`).
- Companion wiki page: `concepts/allagents-vs-gh-skill.md` § "--json output contract".
Problem
`src/cli/json-output.ts` exposes a boolean `--json` flag plus a single envelope:
```ts
interface JsonEnvelope {
success: boolean;
command: string;
data?: unknown;
error?: string;
}
```
There is no way to select which fields appear inside `data`, and no on-the-fly filtering. Consequences for agents:
`gh`'s pattern in `pkg/cmdutil/json_flags.go::AddJSONFlags(cmd, &exporter, fields)`:
Current behavior
```bash
$ allagents plugin list --json
{
"success": true,
"command": "plugin list",
"data": {
"plugins": [...], # full shape, all fields, always
"total": 12
}
}
Field selection is not supported:
$ allagents plugin list --json=name
error: found 1 error
--json=name plugin list
^ Unknown arguments
No --jq / --template:
$ allagents plugin list --json --jq '.data.plugins[].name'
error: --jq is not a known option
```
Expected behavior
```bash
(1) Boolean form still works (backward compatible)
$ allagents plugin list --json
{ "success": true, "command": "plugin list", "data": { "plugins": [...], "total": 12 } }
(2) Field allowlist
$ allagents plugin list --json=name,source
{
"success": true,
"command": "plugin list",
"data": { "plugins": [{"name": "...", "source": "..."}, ...] }
}
(3) Unknown field fails fast with sorted suggestion list
$ allagents plugin list --json=nope
Error: Unknown JSON field: "nope"
Available fields:
clients
install
name
skills
source
exit 2
(4) --jq pipes the envelope through jq
$ allagents plugin list --json --jq '.data.plugins | map(.name)'
["foo", "bar", "baz"]
(5) --template uses ES6-style or Handlebars-style template (pick one)
$ allagents plugin list --json --template '{{range .data.plugins}}{{.name}}\n{{end}}'
foo
bar
baz
(6) --agent-help advertises the allowlist
$ allagents --agent-help "plugin list" | jq .json_fields
["clients", "install", "name", "skills", "source"]
```
Verification gate (must pass before closing)
```bash
set -euo pipefail
bun run build
WS=$(mktemp -d)
cd "$WS"
allagents workspace init --client claude
allagents skills add brainstorming --from anthropics/superpowers
(1) Boolean --json still returns the full envelope (backward compat)
allagents --json skills list | jq -e '.command == "skills list" and (.data.skills | type == "array")'
(2) Field selection returns only the requested fields
allagents --json=name,plugin skills list | jq -e '.data.skills[0] | keys == ["name", "plugin"]'
(3) Unknown field exits non-zero with a sorted suggestion list
OUT=$(allagents --json=bogus skills list 2>&1 || true)
echo "$OUT" | grep -q 'Unknown JSON field'
echo "$OUT" | grep -q 'Available fields'
(4) --jq filter
allagents --json --jq '.data.skills | map(.name)' skills list | jq -e 'type == "array"'
(5) --jq without --json is rejected
! allagents --jq '.data' skills list
(6) Allowlist is discoverable via --agent-help
allagents --agent-help "skills list" | jq -e '.json_fields | type == "array" and length >= 1'
cd / && rm -rf "$WS"
```
All six checks must pass.
Implementation notes
Refs