honcho-cli 0.1.2__tar.gz → 0.1.3__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.
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/.gitignore +3 -3
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/CHANGELOG.md +18 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/PKG-INFO +34 -2
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/README.md +32 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/pyproject.toml +5 -1
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/scripts/generate_cli_docs.py +1 -1
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/__init__.py +1 -1
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/_help.py +2 -1
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/commands/message.py +7 -4
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/commands/session.py +247 -6
- honcho_cli-0.1.3/src/honcho_cli/commands/stack.py +400 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/common.py +9 -0
- honcho_cli-0.1.3/src/honcho_cli/local/__init__.py +12 -0
- honcho_cli-0.1.3/src/honcho_cli/local/docker.py +436 -0
- honcho_cli-0.1.3/src/honcho_cli/local/env.py +183 -0
- honcho_cli-0.1.3/src/honcho_cli/local/health.py +53 -0
- honcho_cli-0.1.3/src/honcho_cli/local/profile.py +157 -0
- honcho_cli-0.1.3/src/honcho_cli/local/setup.py +469 -0
- honcho_cli-0.1.3/src/honcho_cli/local/templates/__init__.py +1 -0
- honcho_cli-0.1.3/src/honcho_cli/local/templates/docker-compose.yml +100 -0
- honcho_cli-0.1.3/src/honcho_cli/local/templates/init.sql +1 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/main.py +4 -0
- honcho_cli-0.1.3/src/honcho_cli/output.py +235 -0
- honcho_cli-0.1.3/tests/test_commands.py +480 -0
- honcho_cli-0.1.3/tests/test_local.py +127 -0
- honcho_cli-0.1.3/tests/test_output.py +139 -0
- honcho_cli-0.1.3/tests/test_setup.py +66 -0
- honcho_cli-0.1.3/tests/test_start.py +159 -0
- honcho_cli-0.1.2/src/honcho_cli/output.py +0 -104
- honcho_cli-0.1.2/tests/test_commands.py +0 -195
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/branding.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/commands/__init__.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/commands/conclusion.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/commands/config_cmd.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/commands/peer.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/commands/setup.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/commands/workspace.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/config.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/oauth.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/src/honcho_cli/validation.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/tests/__init__.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/tests/conftest.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/tests/test_common.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/tests/test_config.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/tests/test_oauth.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/tests/test_validation.py +0 -0
- {honcho_cli-0.1.2 → honcho_cli-0.1.3}/uv.lock +0 -0
|
@@ -5,11 +5,29 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.3] - 2026-08-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- `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)
|
|
15
|
+
- `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)
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- `honcho message list --last N` no longer stops at the first page of 50 — it walks pages to fill the requested window (#1006)
|
|
20
|
+
|
|
8
21
|
## [0.1.2] - 2026-07-20
|
|
9
22
|
|
|
10
23
|
### Added
|
|
11
24
|
|
|
12
25
|
- 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)
|
|
26
|
+
- `HONCHO_CONFIG_DIR` environment variable for pointing the CLI at an alternate config directory (#891)
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- 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)
|
|
13
31
|
|
|
14
32
|
## [0.1.1] - 2026-06-15
|
|
15
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: honcho-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
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
|
|
@@ -48,6 +48,7 @@ uv tool install honcho-cli
|
|
|
48
48
|
|
|
49
49
|
```bash
|
|
50
50
|
honcho init # confirm/set apiKey + Honcho URL in ~/.honcho/config.json
|
|
51
|
+
honcho start # optional: local API + deriver + Postgres + Redis (Docker)
|
|
51
52
|
honcho doctor # verify your config + connectivity
|
|
52
53
|
honcho # show banner + command list
|
|
53
54
|
```
|
|
@@ -56,6 +57,31 @@ honcho # show banner + command list
|
|
|
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` runs a personal Honcho server on your machine (API, deriver, Postgres, Redis) via Docker. Inference is cloud-side: set `LLM_OPENAI_API_KEY`, `LLM_ANTHROPIC_API_KEY`, or `LLM_GEMINI_API_KEY` (env overrides `config.toml`). 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
|
+
LLM_OPENAI_API_KEY=sk-... honcho start
|
|
78
|
+
honcho start --setup basic
|
|
79
|
+
honcho start --setup advanced
|
|
80
|
+
honcho status
|
|
81
|
+
honcho stop # keep data
|
|
82
|
+
honcho stop --wipe # also delete volumes
|
|
83
|
+
```
|
|
84
|
+
|
|
59
85
|
## Commands
|
|
60
86
|
|
|
61
87
|
### Onboarding
|
|
@@ -63,6 +89,9 @@ Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-
|
|
|
63
89
|
| Command | Description |
|
|
64
90
|
|---------|-------------|
|
|
65
91
|
| `honcho init` | Confirm/set `apiKey` + `environmentUrl` in `~/.honcho/config.json` |
|
|
92
|
+
| `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`. |
|
|
93
|
+
| `honcho stop` | Stop the local stack. `--wipe` also deletes volumes. |
|
|
94
|
+
| `honcho status` | Show every local stack (or `--profile` for one). |
|
|
66
95
|
| `honcho doctor` | Health check: config, connectivity, workspace, peer, queue |
|
|
67
96
|
|
|
68
97
|
### Workspaces
|
|
@@ -96,6 +125,7 @@ Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-
|
|
|
96
125
|
| `honcho session list` | List sessions in the workspace (filter with `--peer/-p`) |
|
|
97
126
|
| `honcho session create <id>` | Create or get a session (optionally `--peers` to add peers, `--metadata`) |
|
|
98
127
|
| `honcho session inspect <id>` | Peers, message count, summaries, config |
|
|
128
|
+
| `honcho session view <id>` | Transcript table (`--last N`, `--page N --size M`, `--all`, `--reverse`, `--ids`, `-p`) |
|
|
99
129
|
| `honcho session context <id>` | What an agent would see |
|
|
100
130
|
| `honcho session summaries <id>` | Short + long summaries |
|
|
101
131
|
| `honcho session peers <id>` / `add-peers` / `remove-peers` | Peer management |
|
|
@@ -181,6 +211,8 @@ Precedence (highest first): **flag → env var → config file → default**.
|
|
|
181
211
|
| `HONCHO_PEER_ID` | `-p` / `--peer` | Peer scope |
|
|
182
212
|
| `HONCHO_SESSION_ID` | `-s` / `--session` | Session scope |
|
|
183
213
|
| `HONCHO_JSON` | `--json` | Force JSON output (`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
|
|
@@ -23,6 +23,7 @@ uv tool install honcho-cli
|
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
honcho init # confirm/set apiKey + Honcho URL in ~/.honcho/config.json
|
|
26
|
+
honcho start # optional: local API + deriver + Postgres + Redis (Docker)
|
|
26
27
|
honcho doctor # verify your config + connectivity
|
|
27
28
|
honcho # show banner + command list
|
|
28
29
|
```
|
|
@@ -31,6 +32,31 @@ honcho # show banner + command list
|
|
|
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` runs a personal Honcho server on your machine (API, deriver, Postgres, Redis) via Docker. Inference is cloud-side: set `LLM_OPENAI_API_KEY`, `LLM_ANTHROPIC_API_KEY`, or `LLM_GEMINI_API_KEY` (env overrides `config.toml`). 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
|
+
LLM_OPENAI_API_KEY=sk-... honcho start
|
|
53
|
+
honcho start --setup basic
|
|
54
|
+
honcho start --setup advanced
|
|
55
|
+
honcho status
|
|
56
|
+
honcho stop # keep data
|
|
57
|
+
honcho stop --wipe # also delete volumes
|
|
58
|
+
```
|
|
59
|
+
|
|
34
60
|
## Commands
|
|
35
61
|
|
|
36
62
|
### Onboarding
|
|
@@ -38,6 +64,9 @@ Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-
|
|
|
38
64
|
| Command | Description |
|
|
39
65
|
|---------|-------------|
|
|
40
66
|
| `honcho init` | Confirm/set `apiKey` + `environmentUrl` in `~/.honcho/config.json` |
|
|
67
|
+
| `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`. |
|
|
68
|
+
| `honcho stop` | Stop the local stack. `--wipe` also deletes volumes. |
|
|
69
|
+
| `honcho status` | Show every local stack (or `--profile` for one). |
|
|
41
70
|
| `honcho doctor` | Health check: config, connectivity, workspace, peer, queue |
|
|
42
71
|
|
|
43
72
|
### Workspaces
|
|
@@ -71,6 +100,7 @@ Per-command scoping (workspace / peer / session) is handled via `-w` / `-p` / `-
|
|
|
71
100
|
| `honcho session list` | List sessions in the workspace (filter with `--peer/-p`) |
|
|
72
101
|
| `honcho session create <id>` | Create or get a session (optionally `--peers` to add peers, `--metadata`) |
|
|
73
102
|
| `honcho session inspect <id>` | Peers, message count, summaries, config |
|
|
103
|
+
| `honcho session view <id>` | Transcript table (`--last N`, `--page N --size M`, `--all`, `--reverse`, `--ids`, `-p`) |
|
|
74
104
|
| `honcho session context <id>` | What an agent would see |
|
|
75
105
|
| `honcho session summaries <id>` | Short + long summaries |
|
|
76
106
|
| `honcho session peers <id>` / `add-peers` / `remove-peers` | Peer management |
|
|
@@ -156,6 +186,8 @@ Precedence (highest first): **flag → env var → config file → default**.
|
|
|
156
186
|
| `HONCHO_PEER_ID` | `-p` / `--peer` | Peer scope |
|
|
157
187
|
| `HONCHO_SESSION_ID` | `-s` / `--session` | Session scope |
|
|
158
188
|
| `HONCHO_JSON` | `--json` | Force JSON output (`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.
|
|
3
|
+
version = "0.1.3"
|
|
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:
|
|
@@ -67,6 +67,7 @@ def print_welcome(console: Console) -> None:
|
|
|
67
67
|
|
|
68
68
|
start_rows = [
|
|
69
69
|
("honcho init", "configure API key and server URL"),
|
|
70
|
+
("honcho start", "run a local Honcho stack (Docker)"),
|
|
70
71
|
("honcho doctor", "verify connection and workspace health"),
|
|
71
72
|
]
|
|
72
73
|
cmd_rows = [
|
|
@@ -76,7 +77,7 @@ def print_welcome(console: Console) -> None:
|
|
|
76
77
|
("workspace", "list · create · search · delete · inspect · queue-status"),
|
|
77
78
|
("peer", "list · create · search · inspect · card · chat"),
|
|
78
79
|
("", "get-metadata · set-metadata · representation"),
|
|
79
|
-
("session", "list · create · search · delete · inspect · add-peers"),
|
|
80
|
+
("session", "list · create · search · delete · inspect · view · add-peers"),
|
|
80
81
|
("", "context · get-metadata · set-metadata · peers"),
|
|
81
82
|
("", "remove-peers · representation · summaries"),
|
|
82
83
|
("message", "list · create · get"),
|
|
@@ -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
|
|
46
|
-
#
|
|
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
|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
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)"),
|