diff --git a/docs/docs.json b/docs/docs.json index 0b357367..54262e3f 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -84,7 +84,8 @@ "group": "Reference", "pages": [ "v3/documentation/reference/platform", - "v3/documentation/reference/sdk" + "v3/documentation/reference/sdk", + "v3/documentation/reference/cli" ] } ] diff --git a/docs/snippets/cli-commands.mdx b/docs/snippets/cli-commands.mdx new file mode 100644 index 00000000..4eb9d2d3 --- /dev/null +++ b/docs/snippets/cli-commands.mdx @@ -0,0 +1,547 @@ +{/* + GENERATED by honcho-cli/scripts/generate_cli_docs.py — do not edit. + Re-generate with: uv run --package honcho-cli python honcho-cli/scripts/generate_cli_docs.py + Source of truth: honcho-cli/src/honcho_cli/commands/ +*/} + +## honcho conclusion + +List, search, create, and delete peer conclusions (Honcho's memory atoms). + + + +Create a conclusion. + +```bash +honcho conclusion create +``` + + + + Observer peer ID. + + + Observed peer ID. + + + Session context. Short alias: `-s`. + + + +Delete a conclusion. + +```bash +honcho conclusion delete +``` + + + + Observer peer ID. + + + Observed peer ID. + + + Skip confirmation. Short alias: `-y`. + + + +List conclusions. + +```bash +honcho conclusion list +``` + + + Observer peer ID. + + + Observed peer ID. + + + Max results. + + + +Semantic search over conclusions. + +```bash +honcho conclusion search +``` + + + + Observer peer ID. + + + Observed peer ID. + + + Max results. + + + + +## honcho config + +Inspect CLI configuration. + +```bash +honcho config +``` + + +## honcho doctor + +Verify config and connectivity. Scope with -w / -p to check workspace, peer, and queue health. + +```bash +honcho doctor +``` + + +## honcho help + +Show help message. + +```bash +honcho help +``` + + +## honcho init + +Set API key and server URL in ~/.honcho/config.json. + +Press Enter to keep the current value or type a replacement. +Workspace / peer / session scoping is per-command via -w / -p / -s +or HONCHO_* env vars — never persisted. + +```bash +honcho init +``` + + + API key (admin JWT). + + + Honcho API URL (e.g. https://api.honcho.dev, http://localhost:8000). + + +## honcho message + +List, create, and get messages within a session. + + + +Create a message in a session. + +```bash +honcho message create +``` + + + + Peer ID of the message sender. Short alias: `-p`. + + + JSON metadata to associate with the message. + + + Session ID. Short alias: `-s`. + + + +Get a single message by ID. + +```bash +honcho message get +``` + + + + Session ID. Short alias: `-s`. + + + +List messages in a session. Scoped to a peer with -p. + +```bash +honcho message list [] +``` + + + + Number of recent messages. + + + Show oldest first (default is newest first). + + + Show only IDs, peer, token count, and created_at (no content). + + + Filter by peer ID. Short alias: `-p`. + + + + +## honcho peer + +List, create, chat with, search, and manage peers and their representations. + + + +Get raw peer card content. + +```bash +honcho peer card [] +``` + + + + Target peer for relationship card. + + + +Query the dialectic about a peer. + +```bash +honcho peer chat +``` + + + + Target peer for perspective. + + + Reasoning level: minimal, low, medium, high, max. Short alias: `-r`. + + + +Create or get a peer. + +```bash +honcho peer create +``` + + + + Whether Honcho will form a representation of this peer. Negate with `--no-observe-me`. + + + JSON metadata to associate with the peer. + + + +Get metadata for a peer. + +```bash +honcho peer get-metadata [] +``` + + + + +Inspect a peer: card, session count, recent conclusions. + +```bash +honcho peer inspect [] +``` + + + + +List all peers in the workspace. + +```bash +honcho peer list +``` + + + +Get the formatted representation for a peer. + +```bash +honcho peer representation [] +``` + + + + Target peer to get representation about. + + + Semantic search query to filter conclusions. + + + Maximum number of conclusions to include. + + + +Search a peer's messages. + +```bash +honcho peer search +``` + + + + Max results. + + + +Set metadata for a peer. + +```bash +honcho peer set-metadata +``` + + + + Peer ID (uses default if omitted). Short alias: `-p`. + + + + +## honcho session + +List, inspect, create, delete, and manage conversation sessions and their peers. + + + +Add peers to a session. + +```bash +honcho session add-peers +``` + + + + + +Get session context (what an agent would see). + +```bash +honcho session context [] +``` + + + + Token budget. + + + Include summary. Negate with `--no-summary`. + + + +Create or get a session. + +```bash +honcho session create +``` + + + + Comma-separated peer IDs to add to the session. + + + JSON metadata to associate with the session. + + + +Delete a session and all its data. Destructive — requires --yes or interactive confirm. + +```bash +honcho session delete [] +``` + + + + Skip confirmation. Short alias: `-y`. + + + +Get metadata for a session. + +```bash +honcho session get-metadata [] +``` + + + + +Inspect a session: peers, message count, summaries, config. + +```bash +honcho session inspect [] +``` + + + + +List sessions in the workspace. + +```bash +honcho session list +``` + + + Filter by peer. Short alias: `-p`. + + + +List peers in a session. + +```bash +honcho session peers [] +``` + + + + +Remove peers from a session. + +```bash +honcho session remove-peers +``` + + + + + +Get the representation of a peer within a session. + +```bash +honcho session representation [] +``` + + + + + Target peer (what peer_id knows about target). + + + Semantic search query to filter conclusions. + + + Maximum number of conclusions to include. + + + +Search messages in a session. + +```bash +honcho session search [] +``` + + + + + Max results. + + + +Set metadata for a session. + +```bash +honcho session set-metadata [] +``` + + + + JSON metadata to set (e.g. '\{"key": "value"\}'). Short alias: `-d`. + + + +Get session summaries (short + long). + +```bash +honcho session summaries [] +``` + + + + + +## honcho workspace + +List, create, inspect, delete, and search workspaces. + + + +Create or get a workspace. + +```bash +honcho workspace create +``` + + + + JSON metadata to associate with the workspace. + + + +Delete a workspace. Use --dry-run first to see what will be deleted. + +Requires --yes to skip confirmation, or will prompt interactively. +If sessions exist, requires --cascade to delete them first. + +```bash +honcho workspace delete +``` + + + + Skip confirmation prompt (for scripted/agent use). Short alias: `-y`. + + + Delete all sessions before deleting the workspace. + + + Show what would be deleted without deleting. + + + +Inspect a workspace: peers, sessions, config. + +```bash +honcho workspace inspect [] +``` + + + + +List all accessible workspaces. + +```bash +honcho workspace list +``` + + + +Get queue processing status. + +```bash +honcho workspace queue-status +``` + + + Filter by observer peer. + + + Filter by sender peer. + + + +Search messages across workspace. + +```bash +honcho workspace search +``` + + + + Max results. + + + diff --git a/docs/v3/documentation/reference/cli.mdx b/docs/v3/documentation/reference/cli.mdx new file mode 100644 index 00000000..7b89bfeb --- /dev/null +++ b/docs/v3/documentation/reference/cli.mdx @@ -0,0 +1,220 @@ +--- +title: 'CLI Reference' +description: 'Command-line interface for Honcho — inspect workspaces, peers, sessions, and memory from your terminal' +icon: 'terminal' +--- + +import CliCommands from "/snippets/cli-commands.mdx"; + +## Install + + +```bash uv (recommended) +uv tool install honcho-cli +``` + +```bash uvx (ephemeral) +uvx honcho-cli +``` + + +## Quick Start + +```bash +honcho init # confirm/set apiKey + Honcho URL in ~/.honcho/config.json +honcho doctor # verify your config + connectivity +honcho # show banner + command list +``` + +## Configuration + +The CLI resolves config in this order: **flag → env var → config file → default**. + +| Value | File key | Env var | Flag | Persisted? | +|-------------|-------------------|------------------------|------------------------|------------| +| API key | `apiKey` | `HONCHO_API_KEY` | — | Yes | +| API URL | `environmentUrl` | `HONCHO_BASE_URL` | — | Yes | +| Workspace | — | `HONCHO_WORKSPACE_ID` | `-w` / `--workspace` | No | +| Peer | — | `HONCHO_PEER_ID` | `-p` / `--peer` | No | +| Session | — | `HONCHO_SESSION_ID` | `-s` / `--session` | No | +| JSON output | — | `HONCHO_JSON` | `--json` | No | + +### Persisted config + +The CLI shares `~/.honcho/config.json` with sibling Honcho tools. It owns only +`apiKey` and `environmentUrl` at the top level — everything else (`hosts`, +`sessions`, etc.) is written by other tools and left untouched on save. + +```json +{ + "apiKey": "hch-v3-...", + "environmentUrl": "https://api.honcho.dev", + "hosts": { "claude_code": { "...": "..." } } +} +``` + +Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-s` +flags or `HONCHO_*` env vars. **Not** persisted as CLI defaults. This is +deliberate: every invocation is explicit about what it operates on. + + +### Runtime overrides + +Workspace, peer, and session scoping are **per-command only** — pass flags or +`HONCHO_*` env vars on every invocation. + +```bash +# Per-command flags +honcho peer card -w prod -p user + +# Or export once per shell +export HONCHO_WORKSPACE_ID=prod +export HONCHO_PEER_ID=user +honcho peer card + +# One-off against a different server +HONCHO_BASE_URL=http://localhost:8000 honcho workspace list + +# CI/CD — env vars only, no config file needed +export HONCHO_API_KEY=hch-v3-xxx +export HONCHO_BASE_URL=https://api.honcho.dev +honcho workspace list +``` + +## Output & exit codes + +Every command adapts its output to the context: + +- **TTY** — human-readable tables via Rich. +- **Piped or redirected** — JSON automatically (detected via `isatty`). +- **`--json` flag / `HONCHO_JSON=1`** — force JSON regardless of terminal. + +Collection commands emit JSON arrays; single-resource commands emit JSON objects. Errors are always structured: + +```json +{ + "error": { + "code": "PEER_NOT_FOUND", + "message": "Peer 'abc' not found in workspace 'my-ws'", + "details": {"workspace_id": "my-ws", "peer_id": "abc"} + } +} +``` + +| Exit code | Meaning | +|-----------|---------| +| `0` | Success | +| `1` | Client error (bad input, resource not found) | +| `2` | Server error | +| `3` | Auth error (missing or invalid API key) | + +CI pipelines and agent runtimes can branch on these without parsing stderr. + +## Command reference + + + +## Workflows + +### Inspect an unfamiliar workspace + +When you pick up a workspace and need to orient — start broad, narrow to the peer and session you care about. + + + + ```bash + honcho workspace inspect --json + honcho peer list --json + ``` + + + ```bash + honcho peer inspect --json + honcho peer card --json + ``` + + + ```bash + honcho conclusion list --observer --json + honcho conclusion search "topic" --observer --json + ``` + + + ```bash + honcho session inspect --json + honcho message list --last 20 --json + honcho session context --json + honcho session summaries --json + ``` + + + + + `honcho session context` shows exactly what an agent would receive at inference time — check it before `honcho peer chat` if a response surprises you. + + +### A peer isn't learning + +If new messages aren't producing new conclusions, work down the diagnostic ladder. + +```bash +# Is observation enabled for this peer? +honcho peer inspect --json | jq '.configuration' + +# Is the deriver actually processing? +honcho workspace queue-status --json + +# Do any conclusions exist at all? Any for the expected topic? +honcho conclusion list --observer --json +honcho conclusion search "expected topic" --observer --json +``` + +### Session context looks wrong + +When an agent's responses don't reflect what you expect it to know. + +```bash +honcho session context --json +honcho session summaries --json +honcho message list --last 50 --json +``` + +### Dialectic returns bad answers + +When `honcho peer chat` or the dialectic API is hallucinating or missing context. + +```bash +# What does the peer card actually say? +honcho peer card --json + +# Any conclusions for this topic? +honcho conclusion search "topic" --observer --json + +# Reproduce the query against the CLI +honcho peer chat "what do you know about X?" --json +``` + +## Scripting & automation + +Pipe commands into `jq` for inline transforms, or set `HONCHO_*` env vars for a CI/CD environment with no config file: + +```bash +# Pipe to jq +honcho peer list --json | jq '.[].id' +honcho workspace inspect --json | jq '.peers' + +# Machine-parseable health check — exit code for CI, details for logs +honcho doctor --json + +# CI/CD — env vars only, no ~/.honcho/config.json +export HONCHO_API_KEY=hch-v3-xxx +export HONCHO_BASE_URL=https://api.honcho.dev +honcho workspace list +``` + +Non-interactive onboarding: + +```bash +# Pre-seed via flags / env vars; init still prompts for anything missing +HONCHO_API_KEY=hch-v3-xxx honcho init --base-url https://api.honcho.dev +``` diff --git a/honcho-cli/scripts/generate_cli_docs.py b/honcho-cli/scripts/generate_cli_docs.py new file mode 100644 index 00000000..8fa4e223 --- /dev/null +++ b/honcho-cli/scripts/generate_cli_docs.py @@ -0,0 +1,261 @@ +"""Generate ``docs/snippets/cli-commands.mdx`` from the Typer app. + +Walks the ``honcho`` Typer app and emits a Mintlify snippet using native +Mintlify components: ```` / ```` for subcommand +grouping and ```` for each argument and option. The output is a +single snippet included by ``docs/v3/documentation/reference/cli.mdx``. + +Usage:: + + uv run --package honcho-cli python honcho-cli/scripts/generate_cli_docs.py + + # Or as a drift check (non-zero exit if the committed snippet is stale): + uv run --package honcho-cli python honcho-cli/scripts/generate_cli_docs.py --check +""" + +from __future__ import annotations + +import sys +from argparse import ArgumentParser +from pathlib import Path + +import click +import typer.main + +from honcho_cli.main import app + +REPO_ROOT = Path(__file__).resolve().parents[2] +OUTPUT = REPO_ROOT / "docs" / "snippets" / "cli-commands.mdx" + +HEADER = """{/* + GENERATED by honcho-cli/scripts/generate_cli_docs.py — do not edit. + Re-generate with: uv run --package honcho-cli python honcho-cli/scripts/generate_cli_docs.py + Source of truth: honcho-cli/src/honcho_cli/commands/ +*/} + +""" + +# Documented once in cli.mdx's Configuration table. Skip at the per-command +# level so each Accordion only shows options specific to that subcommand. +GLOBAL_OPTIONS: set[tuple[str, str]] = { + ("--workspace", "Override workspace ID"), + ("--peer", "Override peer ID"), + ("--session", "Override session ID"), + ("--json", "Force JSON output"), +} + + +def _escape_mdx(text: str) -> str: + """Escape MDX-sensitive characters in prose so Mintlify's parser doesn't + mistake ``{...}`` for a JSX expression or ```` for a JSX tag.""" + return ( + text.replace("\\", "\\\\") + .replace("{", "\\{") + .replace("}", "\\}") + .replace("<", "\\<") + ) + + +def _attr(value: str) -> str: + """Escape a string for use inside a JSX double-quoted attribute value.""" + return value.replace("\\", "\\\\").replace('"', "'") + + +def _long_opt(param: click.Option) -> str | None: + return next((o for o in param.opts if o.startswith("--")), None) + + +def _short_opt(param: click.Option) -> str | None: + return next( + (o for o in param.opts if o.startswith("-") and not o.startswith("--")), + None, + ) + + +def _is_global(param: click.Parameter) -> bool: + if not isinstance(param, click.Option) or not param.help: + return False + return (_long_opt(param), param.help) in GLOBAL_OPTIONS + + +def _param_type(param: click.Parameter) -> str: + if isinstance(param, click.Option) and param.is_flag: + return "boolean" + if isinstance(param.type, click.Choice): + return "string" + name = getattr(param.type, "name", "") + if name in ("integer", "int"): + return "number" + if name in ("float", "decimal"): + return "number" + if name == "boolean": + return "boolean" + return "string" + + +def _param_path(param: click.Parameter) -> str: + if isinstance(param, click.Argument): + return param.name or "" + return _long_opt(param) or (param.opts[0] if param.opts else "") + + +def _param_required(param: click.Parameter) -> bool: + if isinstance(param, click.Argument): + return param.required + if isinstance(param, click.Option): + return bool(param.required) + return False + + +def _default_attr(param: click.Parameter) -> str | None: + default = param.default + if default is None or default is False or callable(default): + return None + if isinstance(default, (list, tuple)) and not default: + return None + if default is True: + return "true" + return _attr(str(default)) + + +def _ensure_period(text: str) -> str: + return text if text.endswith((".", "?", "!", ":")) else text + "." + + +def _param_body(param: click.Parameter) -> str: + parts: list[str] = [] + if isinstance(param, click.Option): + if param.help: + parts.append(_ensure_period(_escape_mdx(param.help.strip()))) + short = _short_opt(param) + if short: + parts.append(f"Short alias: `{short}`.") + if param.secondary_opts: + neg = " / ".join(f"`{o}`" for o in param.secondary_opts) + parts.append(f"Negate with {neg}.") + if isinstance(param.type, click.Choice): + choices = ", ".join(f"`{c}`" for c in param.type.choices) + parts.append(f"One of: {choices}.") + return " ".join(parts) + + +def _render_param(param: click.Parameter) -> list[str]: + props = [ + f'path="{_attr(_param_path(param))}"', + f'type="{_param_type(param)}"', + ] + if _param_required(param): + props.append("required") + default_attr = _default_attr(param) + if default_attr is not None: + props.append(f'default="{default_attr}"') + body = _param_body(param).strip() + open_tag = f"" + if body: + return [open_tag, f" {body}", ""] + return [open_tag.replace(">", " />")] + + +def _params_of( + cmd: click.Command, *, strip_globals: bool +) -> list[click.Parameter]: + args = [p for p in cmd.params if isinstance(p, click.Argument)] + opts = [ + p + for p in cmd.params + if isinstance(p, click.Option) + and not p.hidden + and not (strip_globals and _is_global(p)) + ] + return args + opts + + +def _invocation_line(cmd: click.Command, path: list[str]) -> str: + args = [p for p in cmd.params if isinstance(p, click.Argument)] + parts = [" ".join(path)] + for a in args: + placeholder = f"<{a.name}>" + if not a.required: + placeholder = f"[{placeholder}]" + parts.append(placeholder) + return " ".join(parts) + + +def _render_accordion(cmd: click.Command, path: list[str]) -> list[str]: + lines = [f''] + if cmd.help: + lines.append(_escape_mdx(cmd.help.strip())) + lines.append("") + lines.append("```bash") + lines.append(_invocation_line(cmd, path)) + lines.append("```") + lines.append("") + for p in _params_of(cmd, strip_globals=True): + lines.extend(_render_param(p)) + lines.append("") + return lines + + +def _render_top(cmd: click.Command, path: list[str]) -> list[str]: + lines = [f"## {' '.join(path)}", ""] + if cmd.help: + lines.append(_escape_mdx(cmd.help.strip())) + lines.append("") + + if isinstance(cmd, click.Group) and cmd.commands: + lines.append("") + for sub_name in sorted(cmd.commands): + lines.extend( + _render_accordion(cmd.commands[sub_name], path + [sub_name]) + ) + lines.append("") + lines.append("") + return lines + + lines.append("```bash") + lines.append(_invocation_line(cmd, path)) + lines.append("```") + lines.append("") + for p in _params_of(cmd, strip_globals=True): + lines.extend(_render_param(p)) + lines.append("") + return lines + + +def build() -> str: + root: click.Command = typer.main.get_command(app) + if not isinstance(root, click.Group): + raise SystemExit("Expected root command to be a Group") + + body: list[str] = [] + for name in sorted(root.commands): + body.extend(_render_top(root.commands[name], ["honcho", name])) + return HEADER + "\n".join(body) + "\n" + + +def main() -> int: + parser = ArgumentParser() + parser.add_argument( + "--check", + action="store_true", + help="Exit non-zero if the committed snippet differs from generated output.", + ) + ns = parser.parse_args() + generated = build() + if ns.check: + current = OUTPUT.read_text() if OUTPUT.exists() else "" + if current != generated: + print( + f"::error::{OUTPUT} is stale. Re-run without --check to regenerate.", + file=sys.stderr, + ) + return 1 + return 0 + OUTPUT.parent.mkdir(parents=True, exist_ok=True) + OUTPUT.write_text(generated) + print(f"Wrote {OUTPUT}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())