cinna-cli 0.2.2__tar.gz → 0.2.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.
Files changed (48) hide show
  1. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/PKG-INFO +25 -1
  2. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/README.md +24 -0
  3. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/docs/README.md +31 -1
  4. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/pyproject.toml +1 -1
  5. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/account.py +30 -3
  6. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/bootstrap.py +1 -0
  7. cinna_cli-0.2.3/src/cinna/chat.py +476 -0
  8. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/client.py +166 -22
  9. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/context.py +27 -0
  10. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/main.py +368 -57
  11. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/templates/ACCOUNT_CLAUDE.md.template +14 -1
  12. cinna_cli-0.2.3/src/cinna/templates/CHAT_TESTING.md +56 -0
  13. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/templates/CLAUDE.md.template +4 -1
  14. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/test_account.py +32 -0
  15. cinna_cli-0.2.3/tests/test_chat.py +406 -0
  16. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/test_context.py +7 -0
  17. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/uv.lock +1 -1
  18. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/.github/workflows/publish.yml +0 -0
  19. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/.gitignore +0 -0
  20. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/LICENSE.md +0 -0
  21. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/docs/interface.md +0 -0
  22. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/docs/mutagen_capabilities.md +0 -0
  23. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/__init__.py +0 -0
  24. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/auth.py +0 -0
  25. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/config.py +0 -0
  26. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/console.py +0 -0
  27. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/doctor.py +0 -0
  28. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/errors.py +0 -0
  29. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/logging.py +0 -0
  30. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/mcp_proxy.py +0 -0
  31. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/mutagen_runtime.py +0 -0
  32. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/sync.py +0 -0
  33. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/sync_session.py +0 -0
  34. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/sync_ssh_shim.py +0 -0
  35. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/sync_tui.py +0 -0
  36. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/src/cinna/templates/__init__.py +0 -0
  37. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/__init__.py +0 -0
  38. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/conftest.py +0 -0
  39. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/test_auth.py +0 -0
  40. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/test_bootstrap.py +0 -0
  41. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/test_client.py +0 -0
  42. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/test_config.py +0 -0
  43. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/test_doctor.py +0 -0
  44. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/test_main.py +0 -0
  45. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/test_mutagen_runtime.py +0 -0
  46. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/test_sync.py +0 -0
  47. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/test_sync_session.py +0 -0
  48. {cinna_cli-0.2.2 → cinna_cli-0.2.3}/tests/test_sync_ssh_shim.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cinna-cli
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Local development CLI for Cinna Core agents
5
5
  Project-URL: Homepage, https://github.com/opencinna/cinna-cli
6
6
  Project-URL: Repository, https://github.com/opencinna/cinna-cli
@@ -255,6 +255,30 @@ cinna api PATCH agents/3fa85f64-5717-4562-b3fc-2c963f66afa6 --json '{"descriptio
255
255
  cinna api POST tasks --data @task.json
256
256
  ```
257
257
 
258
+ ### `cinna chat [--agent <ref>] [--resume <session_id>] [--file PATH ...] [MESSAGE...]`
259
+
260
+ Talk to an agent through a **real platform session** — the same conversation pipeline production uses (permission checks, agent-env calls, the model/SDK the platform selects), not a local mock. Built for a local coding agent to test the agent it is building: it can prepare a prompt, attach files, and read the reply back as structured data.
261
+
262
+ Run it from the account workspace (or any synced agent folder under it). The reply is observed by **polling** the backend rather than reading a live stream, so it is robust to streaming/transport quirks.
263
+
264
+ - `--agent <name|slug|id>` picks the agent; omit it inside a synced agent workspace to infer it. `--resume <session_id>` continues an existing conversation instead of opening a new one (default mode for a new session is `conversation`; `--mode building` opens a building session).
265
+ - The message is the positional argument; if omitted it is read from stdin, or you are prompted for it interactively in a TTY.
266
+ - `--file PATH` (repeatable) uploads a local file and attaches it to the message.
267
+ - Output is **NDJSON** by default — one JSON event per line (`session`, `upload`, `message`, `status`, `done`), trivially parseable by another agent. `--pretty` switches to a human-readable transcript.
268
+ - Each `message` carries the agent's reasoning/tool trace under **`events`** — an ordered list of the `thinking` blocks, `tool` calls (with their full `tool_input` payload) and tool results behind the reply, so you see *what the agent did*, not just its final `content`. Pass `--no-events` to drop the trace and keep only the final text.
269
+ - Files the agent attaches to its replies are downloaded under `./cinna-chat-files/<session_id>/` (override with `--download-dir`, or skip with `--no-download` to just report the file ids). Downloads are bounded by the api-proxy's 8 MiB response cap.
270
+ - `--interval` / `--timeout` tune the poll cadence and the maximum wait for a turn. Ctrl-C interrupts the agent's turn and exits.
271
+
272
+ ```bash
273
+ cinna chat --agent crm-agent "Summarize today's leads"
274
+ cinna chat --agent crm-agent --file report.csv "Validate this export"
275
+ cinna chat --resume 3fa85f64-5717-4562-b3fc-2c963f66afa6 "Now break it down by region"
276
+ echo "ping" | cinna chat --agent crm-agent # message from stdin
277
+ cinna chat --agent crm-agent "hi" | jq -c 'select(.event=="message")'
278
+ ```
279
+
280
+ The session id is printed in the first `session` event — capture it to drive a multi-turn conversation with `--resume`.
281
+
258
282
  ### `cinna dev`
259
283
 
260
284
  Start a foreground dev session — creates / resumes the Mutagen sync session for this workspace and attaches the terminal to a two-tab TUI (status + raw Mutagen details). Ctrl-C terminates the session; sync does not outlive the TUI. To observe sync from another terminal without affecting it, use `cinna sync status`.
@@ -218,6 +218,30 @@ cinna api PATCH agents/3fa85f64-5717-4562-b3fc-2c963f66afa6 --json '{"descriptio
218
218
  cinna api POST tasks --data @task.json
219
219
  ```
220
220
 
221
+ ### `cinna chat [--agent <ref>] [--resume <session_id>] [--file PATH ...] [MESSAGE...]`
222
+
223
+ Talk to an agent through a **real platform session** — the same conversation pipeline production uses (permission checks, agent-env calls, the model/SDK the platform selects), not a local mock. Built for a local coding agent to test the agent it is building: it can prepare a prompt, attach files, and read the reply back as structured data.
224
+
225
+ Run it from the account workspace (or any synced agent folder under it). The reply is observed by **polling** the backend rather than reading a live stream, so it is robust to streaming/transport quirks.
226
+
227
+ - `--agent <name|slug|id>` picks the agent; omit it inside a synced agent workspace to infer it. `--resume <session_id>` continues an existing conversation instead of opening a new one (default mode for a new session is `conversation`; `--mode building` opens a building session).
228
+ - The message is the positional argument; if omitted it is read from stdin, or you are prompted for it interactively in a TTY.
229
+ - `--file PATH` (repeatable) uploads a local file and attaches it to the message.
230
+ - Output is **NDJSON** by default — one JSON event per line (`session`, `upload`, `message`, `status`, `done`), trivially parseable by another agent. `--pretty` switches to a human-readable transcript.
231
+ - Each `message` carries the agent's reasoning/tool trace under **`events`** — an ordered list of the `thinking` blocks, `tool` calls (with their full `tool_input` payload) and tool results behind the reply, so you see *what the agent did*, not just its final `content`. Pass `--no-events` to drop the trace and keep only the final text.
232
+ - Files the agent attaches to its replies are downloaded under `./cinna-chat-files/<session_id>/` (override with `--download-dir`, or skip with `--no-download` to just report the file ids). Downloads are bounded by the api-proxy's 8 MiB response cap.
233
+ - `--interval` / `--timeout` tune the poll cadence and the maximum wait for a turn. Ctrl-C interrupts the agent's turn and exits.
234
+
235
+ ```bash
236
+ cinna chat --agent crm-agent "Summarize today's leads"
237
+ cinna chat --agent crm-agent --file report.csv "Validate this export"
238
+ cinna chat --resume 3fa85f64-5717-4562-b3fc-2c963f66afa6 "Now break it down by region"
239
+ echo "ping" | cinna chat --agent crm-agent # message from stdin
240
+ cinna chat --agent crm-agent "hi" | jq -c 'select(.event=="message")'
241
+ ```
242
+
243
+ The session id is printed in the first `session` event — capture it to drive a multi-turn conversation with `--resume`.
244
+
221
245
  ### `cinna dev`
222
246
 
223
247
  Start a foreground dev session — creates / resumes the Mutagen sync session for this workspace and attaches the terminal to a two-tab TUI (status + raw Mutagen details). Ctrl-C terminates the session; sync does not outlive the TUI. To observe sync from another terminal without affecting it, use `cinna sync status`.
@@ -199,6 +199,7 @@ main.py (CLI commands — Click)
199
199
  ├── bootstrap.py — setup orchestration
200
200
  ├── account.py — account workspace; `cinna login` (device auth), `cinna account`, `cinna agent`
201
201
  ├── doctor.py — `cinna doctor`: reconcile registry ↔ Mutagen, repair stale state, refresh tokens
202
+ ├── chat.py — `cinna chat`: session-backed conversation testing (poll + NDJSON) over the api-proxy
202
203
  ├── config.py — .cinna/config.json: load/save/find
203
204
  ├── auth.py — JWT storage, Authorization headers
204
205
  ├── client.py — PlatformClient: HTTP + SSE stream_exec
@@ -356,6 +357,31 @@ To run an actual remote shell snippet (pipes, redirects, `&&`), pass it explicit
356
357
 
357
358
  ---
358
359
 
360
+ ## Remote Chat (`cinna chat`)
361
+
362
+ `cinna chat` lets a local coding agent **test the agent it is building** by driving a real platform conversation session — exercising the production path (permission checks, agent-env calls, the model/SDK the platform selects) rather than a local mock. It lives in `chat.py` and runs entirely through the **account workspace's api-proxy** (`AccountClient`), so it needs an account workspace (`.cinna/account.json`) — found by walking up from the cwd, exactly like the other account verbs, so it works from a synced `agents/<slug>/` folder too.
363
+
364
+ ### Why polling, not streaming
365
+
366
+ The platform's send-message route (`POST /sessions/{id}/messages/stream`) returns a **JSON ack immediately** and runs the agent turn asynchronously; the live events go out over a Socket.IO room *and* are persisted onto each message's `message_metadata.streaming_events`. The api-proxy is a buffered JSON hatch (it rejects `text/event-stream`), so `cinna chat` never reads the stream. Instead it:
367
+
368
+ 1. Creates the session (`POST /sessions/`, mode `conversation` by default) — or resumes the one passed to `--resume`.
369
+ 2. Uploads each `--file` and collects the returned file ids.
370
+ 3. Records the current message count as a cursor, then sends the message (`file_ids` carry the attachments).
371
+ 4. **Polls** `GET /sessions/{id}/messages?offset=<cursor>` (messages are ordered ascending by `sequence_number`, so `offset` is the cursor) and `GET …/messages/streaming-status` (`{is_streaming}`) until the turn settles — `is_streaming` false with no message flagged `streaming_in_progress`. A start-grace window covers env wake / queueing before the turn begins; an overall `--timeout` bounds the wait.
372
+
373
+ Each finalized message is emitted as one NDJSON line (`session` / `upload` / `message` / `status` / `done`); the in-progress assistant message is held back (its content is still growing) and emitted once final. Every `message` also carries the agent's reasoning/tool trace under **`events`** — the normalized `streaming_events` (thinking blocks, `tool` calls with their full `tool_input` payloads, tool results), with the bookkeeping/`attachment` entries stripped (attachments are surfaced separately). The final coalesced text stays in `content`; the trace shows *how* the agent got there. `--no-events` drops the trace; `--pretty` swaps NDJSON for a Rich transcript. Ctrl-C calls `POST …/messages/interrupt` and exits 130.
374
+
375
+ ### Attachments
376
+
377
+ Agents attach workspace files to replies via `<cinna_attach>` tags; the backend materializes them and both injects an `attachment` streaming event (`metadata.file_id` / `filename` / `mime_type` / `size`) and lists them under the message's `files[]` with `source == "agent_attachment"`. `chat.py` collects attachments from the streaming events (preferred) with the `files[]` list as a replay fallback, dedups by file id, and downloads each via the proxy (`GET /files/{id}/download`) into `./cinna-chat-files/<session_id>/`. Because the proxy buffers the response, downloads are bounded by its **8 MiB** response cap; larger files surface a clear `PlatformError` instead of a partial write.
378
+
379
+ ### File upload — the one dedicated route
380
+
381
+ The api-proxy is JSON-only and cannot carry a multipart body, and neither the account token nor a per-agent token may call `/files/upload` directly. So uploading a local attachment uses a dedicated account-CLI route, **`POST /api/v1/cli/account/files/upload`** (multipart, account-token auth), added alongside the other `/cli/account/*` routes; it creates a `File` owned by the account user and returns `FileUploadPublic` whose `id` goes into the message's `file_ids`. This is the only part of `cinna chat` that does not ride the api-proxy.
382
+
383
+ ---
384
+
359
385
  ## Bootstrap Flow
360
386
 
361
387
  ```
@@ -439,8 +465,12 @@ When a CLI token expires (or is revoked) the normal remedy is `cinna set-token <
439
465
  | POST | `/api/v1/cli/account/login/start` | None | Begin a `cinna login` device-authorization request |
440
466
  | POST | `/api/v1/cli/account/login/poll` | None | Poll a `cinna login` request — always HTTP 200 + `status` |
441
467
  | POST | `/api/v1/cli/account/agents/{id}/mint` | Account token | Mint a per-agent CLI token (`cinna agent sync`, `cinna doctor` re-mint) |
468
+ | POST | `/api/v1/cli/account/api-proxy` | Account token | Buffered JSON escape hatch — `cinna api`, and the transport for every `cinna chat` session/message call |
469
+ | POST | `/api/v1/cli/account/files/upload` | Account token | Multipart upload for `cinna chat --file` (the proxy can't carry multipart) |
470
+
471
+ `cinna chat` reaches the conversation API **through** the api-proxy (so these are inner routes, not CLI routes): `POST /sessions/`, `GET /sessions/{id}`, `GET /sessions/{id}/messages`, `POST /sessions/{id}/messages/stream`, `GET /sessions/{id}/messages/streaming-status`, `POST /sessions/{id}/messages/interrupt`, and `GET /files/{id}/download`.
442
472
 
443
- The account-workspace surface adds the broader `/api/v1/cli/account/*` route group (login, agents, credentials, connect, schedules, status, api-proxy); only the routes the sync / login / doctor paths use are listed here.
473
+ The account-workspace surface adds the broader `/api/v1/cli/account/*` route group (login, agents, credentials, connect, schedules, status, api-proxy, files/upload); only the routes the sync / login / doctor / chat paths use are listed here.
444
474
 
445
475
  Endpoints that were part of the old Docker-replica model (`build-context`, `workspace` POST, `workspace/manifest`, `credentials`) have been removed from the backend and from this CLI.
446
476
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cinna-cli"
3
- version = "0.2.2"
3
+ version = "0.2.3"
4
4
  description = "Local development CLI for Cinna Core agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -732,6 +732,12 @@ def _write_account_claude_md(account_root: Path, config: AccountConfig) -> None:
732
732
  )
733
733
  (account_root / "CLAUDE.md").write_text(claude_md)
734
734
 
735
+ # Companion testing guide (loaded by the orchestrator only when it tests an
736
+ # agent via `cinna chat`). Bundled + overwritten, like the orchestrator guide.
737
+ from cinna.context import write_chat_testing_guide
738
+
739
+ write_chat_testing_guide(account_root)
740
+
735
741
 
736
742
  def _install_context_package(
737
743
  account_cfg: AccountConfig, account_root: Path, *, replace: bool = False
@@ -864,9 +870,10 @@ def run_account_refresh_context() -> None:
864
870
 
865
871
  The old context tree is only removed after a successful download, so a
866
872
  failed refresh leaves the existing context intact (warn-don't-die). The
867
- orchestrator `CLAUDE.md` is re-rendered from the bundled template too, so a
868
- CLI upgrade's new commands / guidance reach existing account workspaces
869
- without a full re-setup.
873
+ orchestrator `CLAUDE.md` is re-rendered from the bundled template too — and
874
+ so is every synced agent's per-agent `CLAUDE.md` under `agents/<slug>/` — so
875
+ a CLI upgrade's new commands / guidance reach existing account workspaces
876
+ (orchestrator and child agents alike) without a full re-setup.
870
877
  """
871
878
  account_root = find_account_root()
872
879
  account_cfg = load_account_config(account_root)
@@ -888,6 +895,26 @@ def run_account_refresh_context() -> None:
888
895
  # account workspaces (auto-generated infra — safe to overwrite).
889
896
  _write_account_mcp_config(account_root)
890
897
 
898
+ # Re-render the per-agent CLAUDE.md for every synced child workspace from
899
+ # the same bundled template — offline (the local-dev guide is a pure
900
+ # function of the template + the agent's config), so one bad workspace never
901
+ # aborts the rest. BUILDING_AGENT.md is left untouched (it mirrors the
902
+ # platform's building prompt and is refreshed on sync, not from a template).
903
+ from cinna.context import regenerate_claude_md
904
+
905
+ refreshed = 0
906
+ for child, child_cfg in list_child_workspaces(account_root):
907
+ try:
908
+ regenerate_claude_md(child_cfg, child)
909
+ refreshed += 1
910
+ except Exception as exc: # one unreadable/locked workspace mustn't abort
911
+ console.warn(f"Could not regenerate CLAUDE.md for {child.name}: {exc}")
912
+ if refreshed:
913
+ console.status(
914
+ f"Regenerated CLAUDE.md for {refreshed} synced agent "
915
+ f"workspace{'s' if refreshed != 1 else ''}"
916
+ )
917
+
891
918
 
892
919
  def run_account_agents(show_all: bool = False) -> None:
893
920
  """List accessible agents — called by `cinna account agents`.
@@ -243,6 +243,7 @@ def provision_workspace(
243
243
  # `cinna agent unsync`; user workspace files are preserved.
244
244
  GENERATED_WORKSPACE_FILES = [
245
245
  "CLAUDE.md",
246
+ "CHAT_TESTING.md",
246
247
  "BUILDING_AGENT.md",
247
248
  ".mcp.json",
248
249
  "opencode.json",