honcho-cli 0.1.2__tar.gz → 0.1.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/.gitignore +3 -3
  2. honcho_cli-0.1.4/CHANGELOG.md +58 -0
  3. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/PKG-INFO +39 -7
  4. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/README.md +37 -5
  5. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/pyproject.toml +5 -1
  6. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/scripts/generate_cli_docs.py +1 -1
  7. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/__init__.py +1 -1
  8. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/_help.py +18 -8
  9. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/commands/message.py +7 -4
  10. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/commands/session.py +247 -6
  11. honcho_cli-0.1.4/src/honcho_cli/commands/stack.py +400 -0
  12. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/common.py +9 -0
  13. honcho_cli-0.1.4/src/honcho_cli/local/__init__.py +12 -0
  14. honcho_cli-0.1.4/src/honcho_cli/local/docker.py +436 -0
  15. honcho_cli-0.1.4/src/honcho_cli/local/env.py +183 -0
  16. honcho_cli-0.1.4/src/honcho_cli/local/health.py +53 -0
  17. honcho_cli-0.1.4/src/honcho_cli/local/profile.py +157 -0
  18. honcho_cli-0.1.4/src/honcho_cli/local/setup.py +531 -0
  19. honcho_cli-0.1.4/src/honcho_cli/local/templates/__init__.py +1 -0
  20. honcho_cli-0.1.4/src/honcho_cli/local/templates/docker-compose.yml +100 -0
  21. honcho_cli-0.1.4/src/honcho_cli/local/templates/init.sql +1 -0
  22. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/main.py +6 -0
  23. honcho_cli-0.1.4/src/honcho_cli/output.py +235 -0
  24. honcho_cli-0.1.4/src/honcho_cli/update_check.py +67 -0
  25. honcho_cli-0.1.4/tests/test_commands.py +480 -0
  26. honcho_cli-0.1.4/tests/test_local.py +127 -0
  27. honcho_cli-0.1.4/tests/test_output.py +139 -0
  28. honcho_cli-0.1.4/tests/test_setup.py +94 -0
  29. honcho_cli-0.1.4/tests/test_start.py +159 -0
  30. honcho_cli-0.1.2/CHANGELOG.md +0 -29
  31. honcho_cli-0.1.2/src/honcho_cli/output.py +0 -104
  32. honcho_cli-0.1.2/tests/test_commands.py +0 -195
  33. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/branding.py +0 -0
  34. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/commands/__init__.py +0 -0
  35. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/commands/conclusion.py +0 -0
  36. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/commands/config_cmd.py +0 -0
  37. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/commands/peer.py +0 -0
  38. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/commands/setup.py +0 -0
  39. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/commands/workspace.py +0 -0
  40. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/config.py +0 -0
  41. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/oauth.py +0 -0
  42. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/src/honcho_cli/validation.py +0 -0
  43. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/tests/__init__.py +0 -0
  44. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/tests/conftest.py +0 -0
  45. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/tests/test_common.py +0 -0
  46. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/tests/test_config.py +0 -0
  47. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/tests/test_oauth.py +0 -0
  48. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/tests/test_validation.py +0 -0
  49. {honcho_cli-0.1.2 → honcho_cli-0.1.4}/uv.lock +0 -0
@@ -6,8 +6,8 @@ api/docker-compose.yml
6
6
  *.db
7
7
  data
8
8
  redis-data
9
- docker-compose.yml
10
- compose.yml
9
+ /docker-compose.yml
10
+ /compose.yml
11
11
 
12
12
 
13
13
 
@@ -195,4 +195,4 @@ lancedb_data/
195
195
  grafana-data/
196
196
 
197
197
  # Claude Code addon stuff
198
- .omc
198
+ .omc
@@ -0,0 +1,58 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
+ and this project adheres to [Semantic Versioning](http://semver.org/).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.1.4] - 2026-08-26
11
+
12
+ ### Added
13
+
14
+ - A TTY notice when a newer `honcho-cli` is on PyPI (`uv tool upgrade honcho-cli`). Skipped in JSON mode; disable with `HONCHO_NO_UPDATE_CHECK`
15
+
16
+ ### Fixed
17
+
18
+ - `--setup` for openai-compatible writes `EMBEDDING_MODEL_CONFIG__OVERRIDES__BASE_URL` into the profile `.env` alongside `LLM_OPENAI_BASE_URL` (#1068)
19
+ - `--setup` API key prompts echo `*` per character so a paste is visibly received instead of a blank getpass field
20
+
21
+ ## [0.1.3] - 2026-08-25
22
+
23
+ ### Added
24
+
25
+ - `honcho start`, `honcho stop`, and `honcho status` — run a personal Honcho stack in Docker (API, deriver, Postgres, Redis). Profiles live under `~/.honcho/profiles/`. First start pins `ghcr.io/plastic-labs/honcho:latest` by digest and copies the image `config.toml`. Optional `--setup basic` / `--setup advanced` wizard writes LLM overrides to `.env` (#1029)
26
+ - `honcho session view` — session transcript table (`--last N`, `--page N --size M`, `--all`, `--reverse`, `--ids`, peer filter via `-p`). Content is shown verbatim, timestamps are normalized to UTC, and the command is read-only: unlike the other session commands it never get-or-creates the session (#1006)
27
+
28
+ ### Fixed
29
+
30
+ - `honcho message list --last N` no longer stops at the first page of 50 — it walks pages to fill the requested window (#1006)
31
+
32
+ ## [0.1.2] - 2026-07-20
33
+
34
+ ### Added
35
+
36
+ - Device-code OAuth login for managed Honcho servers. `honcho init` now offers browser-based login (RFC 8628 device authorization grant) when the host advertises the device grant in its OAuth authorization-server metadata; tokens are persisted to `~/.honcho/config.json` and auto-refreshed (#891)
37
+ - `HONCHO_CONFIG_DIR` environment variable for pointing the CLI at an alternate config directory (#891)
38
+
39
+ ### Changed
40
+
41
+ - An OAuth grant now records the host it was minted against and is ignored — neither used nor refreshed — when `base_url` points elsewhere, so a staging grant is never sent to production. A live OAuth token takes precedence over a stored `apiKey`, and a dead grant degrades to the saved key with a warning instead of aborting. Device login no longer deletes the shared `apiKey`, which sibling tools read from the same config file (#891)
42
+
43
+ ## [0.1.1] - 2026-06-15
44
+
45
+ ### Fixed
46
+
47
+ - Declare `click` as an explicit dependency. The CLI imported `click` directly but relied on it being pulled in transitively, so installs without it on the path could fail at runtime (#787)
48
+
49
+ ## [0.1.0] - 2026-04-20
50
+
51
+ ### Added
52
+
53
+ - Initial release of `honcho-cli` — a terminal for inspecting and managing a Honcho deployment (#424)
54
+ - `workspace`, `peer`, `session`, `message`, `conclusion`, and `config` command groups for managing resources against any Honcho server
55
+ - `init` onboarding flow that prompts for and persists connection settings, with flag/env-var pre-seeding for non-interactive use
56
+ - Per-command flags, environment variables, and a config file for pointing the CLI at different servers (local, self-hosted, or hosted)
57
+ - Rich terminal output and an agent-usage mode for scripting against the CLI
58
+ - Documentation and an agent skill for the CLI (#589)
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: honcho-cli
3
- Version: 0.1.2
3
+ Version: 0.1.4
4
4
  Summary: A terminal for Honcho — memory that reasons.
5
5
  Project-URL: Homepage, https://github.com/plastic-labs/honcho
6
6
  Project-URL: Repository, https://github.com/plastic-labs/honcho
@@ -47,22 +47,50 @@ uv tool install honcho-cli
47
47
  ## Quick Start
48
48
 
49
49
  ```bash
50
- honcho init # confirm/set apiKey + Honcho URL in ~/.honcho/config.json
51
- honcho doctor # verify your config + connectivity
52
- honcho # show banner + command list
50
+ honcho init # confirm/set apiKey + Honcho URL in ~/.honcho/config.json
51
+ honcho start --setup basic # local stack: LLM provider key + Docker
52
+ honcho doctor # verify config + connectivity
53
+ honcho # show banner + command list
53
54
  ```
54
55
 
55
- `honcho init` reads `apiKey` and `environmentUrl` from the top-level of `~/.honcho/config.json` (the same file other Honcho tools — plugins, host integrations — share). If both are present, it confirms them with you; if either is missing (or you decline), it prompts for the missing value(s) and writes them back. Host-specific entries under `hosts` are left untouched.
56
+ `honcho init` writes `apiKey` and `environmentUrl` to the top-level of `~/.honcho/config.json` (the same file other Honcho tools — plugins, host integrations — share) so the CLI can call a Honcho server. If both are present, it confirms them with you; if either is missing (or you decline), it prompts and writes them back. Host-specific entries under `hosts` are left untouched. It does **not** set the LLM provider key the local deriver needs — that is `honcho start --setup` (or `LLM_*_API_KEY` in the environment).
56
57
 
57
58
  Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-s` flags or `HONCHO_*` env vars — not persisted as CLI defaults.
58
59
 
60
+ ### Local stack
61
+
62
+ `honcho start --setup basic` runs a personal Honcho server on your machine (API, deriver, Postgres, Redis) via Docker. The wizard writes the LLM provider key into the profile `.env` — `honcho init` cannot do this; its `apiKey` is for calling a Honcho server, not for deriver/dialectic inference. You can also pass `LLM_OPENAI_API_KEY`, `LLM_ANTHROPIC_API_KEY`, or `LLM_GEMINI_API_KEY` in the environment and skip `--setup`. Stack files live under `~/.honcho/profiles/local/` and are not committed to a project.
63
+
64
+ On first start, the CLI pulls `ghcr.io/plastic-labs/honcho:latest` and **pins that digest** in `profile.json`, then copies the image's `config.toml.example` to `config.toml` in the same directory. `honcho start` never overwrites `config.toml` after that — including when you re-pin the image. Delete the file yourself if you want a fresh copy from a new image.
65
+
66
+ Pass `--setup basic` or `--setup advanced` for an interactive wizard that writes curated LLM/feature overrides into the profile `.env` (env wins over `config.toml`). TTY only; re-runnable. `basic` asks provider + chat model; `advanced` also covers embeddings, deriver/dialectic models, dreams, and snappy deriver flush. Everything else stays in `config.toml`.
67
+
68
+ `honcho start` does **not** change `environmentUrl` in `~/.honcho/config.json` (that file is shared with plugins). To talk to the local stack for one command:
69
+
70
+ ```bash
71
+ HONCHO_BASE_URL=http://127.0.0.1:8000 honcho workspace list
72
+ ```
73
+
74
+ To make local the default, run `honcho init --base-url http://127.0.0.1:8000`.
75
+
76
+ ```bash
77
+ honcho start --setup basic
78
+ honcho start --setup advanced
79
+ honcho status
80
+ honcho stop # keep data
81
+ honcho stop --wipe # also delete volumes
82
+ ```
83
+
59
84
  ## Commands
60
85
 
61
86
  ### Onboarding
62
87
 
63
88
  | Command | Description |
64
89
  |---------|-------------|
65
- | `honcho init` | Confirm/set `apiKey` + `environmentUrl` in `~/.honcho/config.json` |
90
+ | `honcho init` | Confirm/set `apiKey` + `environmentUrl` in `~/.honcho/config.json`. |
91
+ | `honcho start` | Start a local Honcho stack (API, deriver, Postgres, Redis). Requires Docker and a cloud LLM key. `--setup basic` / `--setup advanced` runs an interactive config wizard (TTY only). Does not change `environmentUrl`. |
92
+ | `honcho stop` | Stop the local stack. `--wipe` also deletes volumes. |
93
+ | `honcho status` | Show every local stack (or `--profile` for one). |
66
94
  | `honcho doctor` | Health check: config, connectivity, workspace, peer, queue |
67
95
 
68
96
  ### Workspaces
@@ -96,6 +124,7 @@ Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-
96
124
  | `honcho session list` | List sessions in the workspace (filter with `--peer/-p`) |
97
125
  | `honcho session create <id>` | Create or get a session (optionally `--peers` to add peers, `--metadata`) |
98
126
  | `honcho session inspect <id>` | Peers, message count, summaries, config |
127
+ | `honcho session view <id>` | Transcript table (`--last N`, `--page N --size M`, `--all`, `--reverse`, `--ids`, `-p`) |
99
128
  | `honcho session context <id>` | What an agent would see |
100
129
  | `honcho session summaries <id>` | Short + long summaries |
101
130
  | `honcho session peers <id>` / `add-peers` / `remove-peers` | Peer management |
@@ -181,6 +210,9 @@ Precedence (highest first): **flag → env var → config file → default**.
181
210
  | `HONCHO_PEER_ID` | `-p` / `--peer` | Peer scope |
182
211
  | `HONCHO_SESSION_ID` | `-s` / `--session` | Session scope |
183
212
  | `HONCHO_JSON` | `--json` | Force JSON output (`1` / `true`) |
213
+ | `HONCHO_NO_UPDATE_CHECK` | — | Disable the once-a-day upgrade notice (`1` / `true`) |
214
+ | `HONCHO_PROFILE` | `--profile` (start/stop/status) | Local stack profile (default: `local`) |
215
+ | `LLM_OPENAI_API_KEY` | — | Provider key for `honcho start` (also `LLM_ANTHROPIC_API_KEY`, `LLM_GEMINI_API_KEY`) |
184
216
 
185
217
  ```bash
186
218
  # Per-command flags
@@ -22,22 +22,50 @@ uv tool install honcho-cli
22
22
  ## Quick Start
23
23
 
24
24
  ```bash
25
- honcho init # confirm/set apiKey + Honcho URL in ~/.honcho/config.json
26
- honcho doctor # verify your config + connectivity
27
- honcho # show banner + command list
25
+ honcho init # confirm/set apiKey + Honcho URL in ~/.honcho/config.json
26
+ honcho start --setup basic # local stack: LLM provider key + Docker
27
+ honcho doctor # verify config + connectivity
28
+ honcho # show banner + command list
28
29
  ```
29
30
 
30
- `honcho init` reads `apiKey` and `environmentUrl` from the top-level of `~/.honcho/config.json` (the same file other Honcho tools — plugins, host integrations — share). If both are present, it confirms them with you; if either is missing (or you decline), it prompts for the missing value(s) and writes them back. Host-specific entries under `hosts` are left untouched.
31
+ `honcho init` writes `apiKey` and `environmentUrl` to the top-level of `~/.honcho/config.json` (the same file other Honcho tools — plugins, host integrations — share) so the CLI can call a Honcho server. If both are present, it confirms them with you; if either is missing (or you decline), it prompts and writes them back. Host-specific entries under `hosts` are left untouched. It does **not** set the LLM provider key the local deriver needs — that is `honcho start --setup` (or `LLM_*_API_KEY` in the environment).
31
32
 
32
33
  Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-s` flags or `HONCHO_*` env vars — not persisted as CLI defaults.
33
34
 
35
+ ### Local stack
36
+
37
+ `honcho start --setup basic` runs a personal Honcho server on your machine (API, deriver, Postgres, Redis) via Docker. The wizard writes the LLM provider key into the profile `.env` — `honcho init` cannot do this; its `apiKey` is for calling a Honcho server, not for deriver/dialectic inference. You can also pass `LLM_OPENAI_API_KEY`, `LLM_ANTHROPIC_API_KEY`, or `LLM_GEMINI_API_KEY` in the environment and skip `--setup`. Stack files live under `~/.honcho/profiles/local/` and are not committed to a project.
38
+
39
+ On first start, the CLI pulls `ghcr.io/plastic-labs/honcho:latest` and **pins that digest** in `profile.json`, then copies the image's `config.toml.example` to `config.toml` in the same directory. `honcho start` never overwrites `config.toml` after that — including when you re-pin the image. Delete the file yourself if you want a fresh copy from a new image.
40
+
41
+ Pass `--setup basic` or `--setup advanced` for an interactive wizard that writes curated LLM/feature overrides into the profile `.env` (env wins over `config.toml`). TTY only; re-runnable. `basic` asks provider + chat model; `advanced` also covers embeddings, deriver/dialectic models, dreams, and snappy deriver flush. Everything else stays in `config.toml`.
42
+
43
+ `honcho start` does **not** change `environmentUrl` in `~/.honcho/config.json` (that file is shared with plugins). To talk to the local stack for one command:
44
+
45
+ ```bash
46
+ HONCHO_BASE_URL=http://127.0.0.1:8000 honcho workspace list
47
+ ```
48
+
49
+ To make local the default, run `honcho init --base-url http://127.0.0.1:8000`.
50
+
51
+ ```bash
52
+ honcho start --setup basic
53
+ honcho start --setup advanced
54
+ honcho status
55
+ honcho stop # keep data
56
+ honcho stop --wipe # also delete volumes
57
+ ```
58
+
34
59
  ## Commands
35
60
 
36
61
  ### Onboarding
37
62
 
38
63
  | Command | Description |
39
64
  |---------|-------------|
40
- | `honcho init` | Confirm/set `apiKey` + `environmentUrl` in `~/.honcho/config.json` |
65
+ | `honcho init` | Confirm/set `apiKey` + `environmentUrl` in `~/.honcho/config.json`. |
66
+ | `honcho start` | Start a local Honcho stack (API, deriver, Postgres, Redis). Requires Docker and a cloud LLM key. `--setup basic` / `--setup advanced` runs an interactive config wizard (TTY only). Does not change `environmentUrl`. |
67
+ | `honcho stop` | Stop the local stack. `--wipe` also deletes volumes. |
68
+ | `honcho status` | Show every local stack (or `--profile` for one). |
41
69
  | `honcho doctor` | Health check: config, connectivity, workspace, peer, queue |
42
70
 
43
71
  ### Workspaces
@@ -71,6 +99,7 @@ Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-
71
99
  | `honcho session list` | List sessions in the workspace (filter with `--peer/-p`) |
72
100
  | `honcho session create <id>` | Create or get a session (optionally `--peers` to add peers, `--metadata`) |
73
101
  | `honcho session inspect <id>` | Peers, message count, summaries, config |
102
+ | `honcho session view <id>` | Transcript table (`--last N`, `--page N --size M`, `--all`, `--reverse`, `--ids`, `-p`) |
74
103
  | `honcho session context <id>` | What an agent would see |
75
104
  | `honcho session summaries <id>` | Short + long summaries |
76
105
  | `honcho session peers <id>` / `add-peers` / `remove-peers` | Peer management |
@@ -156,6 +185,9 @@ Precedence (highest first): **flag → env var → config file → default**.
156
185
  | `HONCHO_PEER_ID` | `-p` / `--peer` | Peer scope |
157
186
  | `HONCHO_SESSION_ID` | `-s` / `--session` | Session scope |
158
187
  | `HONCHO_JSON` | `--json` | Force JSON output (`1` / `true`) |
188
+ | `HONCHO_NO_UPDATE_CHECK` | — | Disable the once-a-day upgrade notice (`1` / `true`) |
189
+ | `HONCHO_PROFILE` | `--profile` (start/stop/status) | Local stack profile (default: `local`) |
190
+ | `LLM_OPENAI_API_KEY` | — | Provider key for `honcho start` (also `LLM_ANTHROPIC_API_KEY`, `LLM_GEMINI_API_KEY`) |
159
191
 
160
192
  ```bash
161
193
  # Per-command flags
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "honcho-cli"
3
- version = "0.1.2"
3
+ version = "0.1.4"
4
4
  description = "A terminal for Honcho — memory that reasons."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -38,6 +38,10 @@ build-backend = "hatchling.build"
38
38
  [tool.hatch.build.targets.wheel]
39
39
  packages = ["src/honcho_cli"]
40
40
 
41
+ [tool.hatch.build.targets.wheel.force-include]
42
+ "src/honcho_cli/local/templates/docker-compose.yml" = "honcho_cli/local/templates/docker-compose.yml"
43
+ "src/honcho_cli/local/templates/init.sql" = "honcho_cli/local/templates/init.sql"
44
+
41
45
  [tool.pytest.ini_options]
42
46
  testpaths = ["tests"]
43
47
 
@@ -230,7 +230,7 @@ def build() -> str:
230
230
  body: list[str] = []
231
231
  for name in sorted(root.commands):
232
232
  body.extend(_render_top(root.commands[name], ["honcho", name]))
233
- return HEADER + "\n".join(body) + "\n"
233
+ return HEADER + "\n".join(body).rstrip("\n") + "\n"
234
234
 
235
235
 
236
236
  def main() -> int:
@@ -1,3 +1,3 @@
1
1
  """Honcho CLI — a terminal for Honcho."""
2
2
 
3
- __version__ = "0.1.2"
3
+ __version__ = "0.1.4"
@@ -24,6 +24,7 @@ from typer.core import TyperGroup
24
24
  from honcho_cli import __version__
25
25
  from honcho_cli.branding import BANNER, BRAND
26
26
  from honcho_cli.output import use_json
27
+ from honcho_cli.update_check import maybe_print_update_nag
27
28
 
28
29
 
29
30
  # Theme Typer's rich help renderer. Module-level side effect limited to
@@ -59,15 +60,21 @@ def _welcome_panel(title: str, rows: list[tuple[str, str]]) -> Panel:
59
60
 
60
61
 
61
62
  def print_welcome(console: Console) -> None:
62
- """Render the curated 3-panel welcome (banner + getting started / memory / commands)."""
63
+ """Render the curated welcome (banner + getting started / local stack / commands / memory)."""
63
64
  if use_json():
64
65
  return
65
66
  console.print(f"[bold {BRAND}]{BANNER}[/bold {BRAND}]")
66
67
  console.print(f" [dim]v{__version__}[/dim]\n", highlight=False)
67
68
 
68
69
  start_rows = [
69
- ("honcho init", "configure API key and server URL"),
70
- ("honcho doctor", "verify connection and workspace health"),
70
+ ("honcho init", "configure API key and server URL"),
71
+ ("honcho start [--setup basic]", "run a local Honcho stack (Docker)"),
72
+ ("honcho doctor", "verify connection and workspace health"),
73
+ ]
74
+ stack_rows = [
75
+ ("honcho start / status / stop", "lifecycle for the local Docker stack"),
76
+ ("honcho start --setup basic", "interactive LLM + feature wizard"),
77
+ ("HONCHO_BASE_URL=http://127.0.0.1:8000", "prefix any command — CLI stays on api.honcho.dev until you set this"),
71
78
  ]
72
79
  cmd_rows = [
73
80
  ("[dim]pattern[/dim]", r"[dim]honcho <command> \[args] \[-w workspace] \[-p peer] \[-s session][/dim]"),
@@ -76,7 +83,7 @@ def print_welcome(console: Console) -> None:
76
83
  ("workspace", "list · create · search · delete · inspect · queue-status"),
77
84
  ("peer", "list · create · search · inspect · card · chat"),
78
85
  ("", "get-metadata · set-metadata · representation"),
79
- ("session", "list · create · search · delete · inspect · add-peers"),
86
+ ("session", "list · create · search · delete · inspect · view · add-peers"),
80
87
  ("", "context · get-metadata · set-metadata · peers"),
81
88
  ("", "remove-peers · representation · summaries"),
82
89
  ("message", "list · create · get"),
@@ -84,14 +91,15 @@ def print_welcome(console: Console) -> None:
84
91
  ("config", "inspect current configuration"),
85
92
  ]
86
93
  memory_rows = [
87
- ("honcho peer chat \"...\" -p <peer> -w <workspace>","query the Dialectic about a peer"),
88
- ("honcho peer inspect -p <peer> -w <workspace>","dashboard: peer card + recent conclusions + configuration"),
94
+ ("honcho peer chat \"...\" -p <peer> -w <workspace>", "query the Dialectic about a peer"),
95
+ ("honcho peer inspect -p <peer> -w <workspace>", "dashboard: peer card + recent conclusions + configuration"),
89
96
  ("honcho peer representation -p <peer> -w <workspace>", "global peer representation"),
90
97
  ("honcho peer representation -p <peer> -w <workspace> -s <session>", "session-scoped peer representation"),
91
98
  ("honcho peer card -p <peer> -w <workspace>", "synthesized identity: traits, preferences, instructions"),
92
- ("honcho conclusion list -p <peer> -w <workspace>", "browse peer conclusions"),
99
+ ("honcho conclusion list -p <peer> -w <workspace>", "browse peer conclusions"),
100
+ ("honcho session view / context -s <session>", "transcript, or what an agent would see"),
101
+ ("honcho workspace queue-status", "is the deriver processing?"),
93
102
  ]
94
-
95
103
  option_rows = [
96
104
  ("-w / --workspace", "scope to a workspace"),
97
105
  ("-p / --peer", "scope to a peer"),
@@ -101,10 +109,12 @@ def print_welcome(console: Console) -> None:
101
109
  ]
102
110
 
103
111
  console.print(_welcome_panel("getting started", start_rows))
112
+ console.print(_welcome_panel("local stack", stack_rows))
104
113
  console.print(_welcome_panel("commands", cmd_rows))
105
114
  console.print(_welcome_panel("memory", memory_rows))
106
115
  console.print(_welcome_panel("options", option_rows))
107
116
  console.print()
117
+ maybe_print_update_nag()
108
118
 
109
119
 
110
120
  class HonchoTyperGroup(TyperGroup):
@@ -10,7 +10,7 @@ import typer
10
10
 
11
11
  from honcho.api_types import MessageCreateParams
12
12
 
13
- from honcho_cli.commands.session import _get_session_id
13
+ from honcho_cli.commands.session import _fetch_recent_messages, _get_session_id
14
14
  from honcho_cli.commands.workspace import _handle_error
15
15
  from honcho_cli.output import print_error, print_result, status
16
16
  from honcho_cli.validation import validate_resource_id
@@ -37,15 +37,18 @@ def list_messages(
37
37
 
38
38
  handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer, session=session)
39
39
  sid = _get_session_id(session_id)
40
+ if last < 1:
41
+ print_error("INVALID_FLAGS", "--last must be >= 1", {"last": last})
42
+ raise typer.Exit(1)
40
43
  client, config = get_client()
41
44
  sess = client.session(sid)
42
45
 
43
46
  try:
44
47
  filters = {"peer_id": config.peer_id} if config.peer_id else None
45
- # Fetch newest-first so [:last] always gives the most recent N messages,
46
- # then flip to oldest-at-top / newest-at-bottom for readable display.
48
+ # Fetch newest-first so we always get the most recent N messages, then
49
+ # flip to oldest-at-top / newest-at-bottom for readable display.
47
50
  # --reverse keeps the raw server order (oldest first, descending in table).
48
- msgs = sess.messages(filters=filters, reverse=True).items[:last]
51
+ msgs, _ = _fetch_recent_messages(sess, filters, last)
49
52
  if not reverse:
50
53
  msgs = list(reversed(msgs))
51
54
 
@@ -1,22 +1,29 @@
1
- """Session commands: list, inspect, context, summaries, peers, search, representation, metadata."""
1
+ """Session commands: list, inspect, view, context, summaries, peers, search, representation, metadata."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  import json
6
+ import shlex
6
7
  from typing import List, Optional
7
8
 
8
9
  import typer
9
10
 
10
- from honcho import HonchoError
11
+ from honcho import HonchoError, Session
11
12
 
12
13
  from honcho_cli.commands.workspace import _config_to_dict, _handle_error, _raw_list
13
- from honcho_cli.output import print_error, print_result, status, use_json
14
+ from honcho_cli.output import print_error, print_result, print_transcript, status, use_json
14
15
  from honcho_cli.validation import validate_resource_id
15
16
 
16
17
  from honcho_cli._help import HonchoTyperGroup
17
- from honcho_cli.common import add_common_options, get_client, get_resolved_config, handle_cmd_flags
18
-
19
- app = typer.Typer(cls=HonchoTyperGroup, help="List, inspect, create, delete, and manage conversation sessions and their peers.")
18
+ from honcho_cli.common import (
19
+ add_common_options,
20
+ get_client,
21
+ get_flag_overrides,
22
+ get_resolved_config,
23
+ handle_cmd_flags,
24
+ )
25
+
26
+ app = typer.Typer(cls=HonchoTyperGroup, help="List, inspect, view, create, delete, and manage conversation sessions and their peers.")
20
27
  add_common_options(app)
21
28
 
22
29
 
@@ -135,6 +142,240 @@ def inspect(
135
142
  _handle_error(e, "session", sid)
136
143
 
137
144
 
145
+ # Server-side ceiling on page size (fastapi-pagination's default ``Params``
146
+ # declares ``size`` as ``Query(50, ge=1, le=100)``).
147
+ MAX_PAGE_SIZE = 100
148
+ DEFAULT_PAGE_SIZE = 50
149
+
150
+
151
+ def _fetch_recent_messages(sess, filters: dict | None, last: int) -> tuple[list, int | None]:
152
+ """Fetch the ``last`` most recent messages, newest first.
153
+
154
+ Walks as many newest-first server pages as it takes to fill the window.
155
+ Returns the messages plus the session's total message count (if reported).
156
+ """
157
+ page = sess.messages(
158
+ filters=filters,
159
+ reverse=True,
160
+ size=min(max(last, 1), MAX_PAGE_SIZE),
161
+ )
162
+ total = page.total
163
+ msgs = list(page.items)
164
+ while len(msgs) < last and page.has_next_page():
165
+ page = page.get_next_page()
166
+ if page is None:
167
+ break
168
+ msgs.extend(page.items)
169
+ return msgs[:last], total
170
+
171
+
172
+ def _next_page_command(
173
+ session_id: str,
174
+ next_page: int,
175
+ size: int,
176
+ *,
177
+ reverse: bool,
178
+ show_ids: bool,
179
+ workspace: str | None,
180
+ peer: str | None,
181
+ ) -> str:
182
+ """Continuation command for the next page, carrying this invocation's scope.
183
+
184
+ Scoping flags are echoed only when passed as flags; anything resolved from
185
+ the environment or config file resolves the same way on the next run. IDs
186
+ are shell-quoted — they may contain spaces and metacharacters, and this
187
+ string is meant to be pasted into a shell.
188
+ """
189
+ parts = [
190
+ "honcho",
191
+ "session",
192
+ "view",
193
+ session_id,
194
+ "--page",
195
+ str(next_page),
196
+ "--size",
197
+ str(size),
198
+ ]
199
+ if reverse:
200
+ parts.append("--reverse")
201
+ if show_ids:
202
+ parts.append("--ids")
203
+ if workspace:
204
+ parts += ["-w", workspace]
205
+ if peer:
206
+ parts += ["-p", peer]
207
+ return shlex.join(parts)
208
+
209
+
210
+ def _fetch_all_messages(sess, filters: dict | None) -> tuple[list, int | None]:
211
+ """Fetch every message in the session, oldest first."""
212
+ page = sess.messages(filters=filters, reverse=False, size=MAX_PAGE_SIZE)
213
+ total = page.total
214
+ msgs = list(page.items)
215
+ while page.has_next_page():
216
+ page = page.get_next_page()
217
+ if page is None:
218
+ break
219
+ msgs.extend(page.items)
220
+ return msgs, total
221
+
222
+
223
+ @app.command()
224
+ def view(
225
+ session_id: Optional[str] = typer.Argument(None, help="Session ID (uses default if omitted)"),
226
+ last: Optional[int] = typer.Option(
227
+ None,
228
+ "--last",
229
+ help=f"Show only the N most recent messages (default when no --page/--all: {DEFAULT_PAGE_SIZE})",
230
+ ),
231
+ page_number: Optional[int] = typer.Option(
232
+ None,
233
+ "--page",
234
+ help="1-indexed page of the full transcript. Use for page 2+.",
235
+ ),
236
+ size: Optional[int] = typer.Option(
237
+ None,
238
+ "--size",
239
+ help=f"Messages per page; requires --page (1-{MAX_PAGE_SIZE}, default: {DEFAULT_PAGE_SIZE})",
240
+ ),
241
+ all_messages: bool = typer.Option(False, "--all", help="Show the full transcript (every page)"),
242
+ reverse: bool = typer.Option(
243
+ False,
244
+ "--reverse",
245
+ help="Newest first (default is chronological: oldest at top)",
246
+ ),
247
+ show_ids: bool = typer.Option(False, "--ids", help="Include message IDs in the transcript"),
248
+ workspace: Optional[str] = typer.Option(None, "--workspace", "-w", help="Override workspace ID"),
249
+ peer: Optional[str] = typer.Option(None, "--peer", "-p", help="Filter by peer ID"),
250
+ session: Optional[str] = typer.Option(None, "--session", "-s", help="Override session ID"),
251
+ json_output: bool = typer.Option(False, "--json", help="Force JSON output"),
252
+ ) -> None:
253
+ """View a session transcript as a chat log.
254
+
255
+ Modes (pick one):
256
+
257
+ - default / --last N: tail of the conversation (most recent N)
258
+ - --page N [--size M]: page through the full transcript
259
+ - --all: every message
260
+
261
+ Paging follows the requested order: --page 1 starts at the oldest message,
262
+ or the newest with --reverse.
263
+
264
+ Human mode prints a row-delimited table. JSON mode emits the message list
265
+ (same shape as message list).
266
+ """
267
+ handle_cmd_flags(json_output=json_output, workspace=workspace, peer=peer, session=session)
268
+ sid = _get_session_id(session_id)
269
+
270
+ # Validate every flag before touching the network.
271
+ modes = sum([
272
+ last is not None,
273
+ page_number is not None,
274
+ all_messages,
275
+ ])
276
+ if modes > 1:
277
+ print_error(
278
+ "INVALID_FLAGS",
279
+ "--last, --page, and --all are mutually exclusive",
280
+ {"last": last, "page": page_number, "all": all_messages},
281
+ )
282
+ raise typer.Exit(1)
283
+
284
+ if page_number is not None and page_number < 1:
285
+ print_error("INVALID_FLAGS", "--page must be >= 1", {"page": page_number})
286
+ raise typer.Exit(1)
287
+ if size is not None and page_number is None:
288
+ print_error("INVALID_FLAGS", "--size only applies with --page", {"size": size})
289
+ raise typer.Exit(1)
290
+ if size is not None and not 1 <= size <= MAX_PAGE_SIZE:
291
+ print_error(
292
+ "INVALID_FLAGS",
293
+ f"--size must be between 1 and {MAX_PAGE_SIZE}",
294
+ {"size": size},
295
+ )
296
+ raise typer.Exit(1)
297
+ if last is not None and last < 1:
298
+ print_error("INVALID_FLAGS", "--last must be >= 1", {"last": last})
299
+ raise typer.Exit(1)
300
+
301
+ # Default: tail of conversation (most recent 50).
302
+ mode = "page" if page_number is not None else ("all" if all_messages else "last")
303
+ tail = last if last is not None else DEFAULT_PAGE_SIZE
304
+ page_size = size if size is not None else DEFAULT_PAGE_SIZE
305
+
306
+ client, config = get_client()
307
+ # Read-only: client.session() is a get-or-create POST, so build the Session directly.
308
+ sess = Session(sid, client)
309
+
310
+ try:
311
+ filters = {"peer_id": config.peer_id} if config.peer_id else None
312
+ page_meta: int | None = None
313
+ pages_meta: int | None = None
314
+
315
+ if mode == "page":
316
+ # Page in the order the caller asked for.
317
+ result_page = sess.messages(
318
+ filters=filters,
319
+ page=page_number,
320
+ size=page_size,
321
+ reverse=reverse,
322
+ )
323
+ msgs = list(result_page.items)
324
+ total = result_page.total
325
+ page_meta = result_page.page if result_page.page is not None else page_number
326
+ pages_meta = result_page.pages
327
+ elif mode == "all":
328
+ msgs, total = _fetch_all_messages(sess, filters)
329
+ if reverse:
330
+ msgs = list(reversed(msgs))
331
+ else:
332
+ # Tail window: fetched newest-first, flipped to chronological unless --reverse.
333
+ msgs, total = _fetch_recent_messages(sess, filters, tail)
334
+ if not reverse:
335
+ msgs = list(reversed(msgs))
336
+
337
+ items = [
338
+ {
339
+ "id": m.id,
340
+ "peer_id": m.peer_id,
341
+ "content": m.content,
342
+ "token_count": m.token_count,
343
+ "metadata": m.metadata,
344
+ "created_at": str(m.created_at),
345
+ }
346
+ for m in msgs
347
+ ]
348
+ except Exception as e:
349
+ _handle_error(e, "session", sid)
350
+ raise # unreachable: _handle_error always exits
351
+
352
+ next_page_hint = None
353
+ if page_meta is not None and pages_meta is not None and page_meta < pages_meta:
354
+ # Effective overrides, not the command-level params: -w/-p also parse at
355
+ # group and top level.
356
+ overrides = get_flag_overrides()
357
+ next_page_hint = _next_page_command(
358
+ sid,
359
+ page_meta + 1,
360
+ page_size,
361
+ reverse=reverse,
362
+ show_ids=show_ids,
363
+ workspace=overrides["workspace"],
364
+ peer=overrides["peer"],
365
+ )
366
+
367
+ # Rendered outside the try: output failures aren't session API errors.
368
+ print_transcript(
369
+ items,
370
+ session_id=sid,
371
+ total=total,
372
+ page=page_meta,
373
+ pages=pages_meta,
374
+ show_ids=show_ids,
375
+ next_page_hint=next_page_hint,
376
+ )
377
+
378
+
138
379
  @app.command()
139
380
  def context(
140
381
  session_id: Optional[str] = typer.Argument(None, help="Session ID (uses default if omitted)"),