cinna-cli 0.3.0__tar.gz → 0.4.0__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 (103) hide show
  1. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/PKG-INFO +22 -5
  2. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/README.md +21 -4
  3. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/README.md +11 -5
  4. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/account_workspace/account_workspace.md +85 -7
  5. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/account_workspace/account_workspace_acceptance.md +110 -3
  6. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/account_workspace/account_workspace_tech.md +92 -12
  7. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/bootstrap_onboarding/bootstrap_onboarding.md +44 -6
  8. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/bootstrap_onboarding/bootstrap_onboarding_acceptance.md +45 -1
  9. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/bootstrap_onboarding/bootstrap_onboarding_tech.md +97 -6
  10. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/pyproject.toml +1 -1
  11. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/account.py +228 -30
  12. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/bootstrap.py +1 -2
  13. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/chat.py +1 -1
  14. cinna_cli-0.4.0/src/cinna/cli_version.py +111 -0
  15. cinna_cli-0.4.0/src/cinna/console.py +187 -0
  16. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/doctor.py +46 -3
  17. cinna_cli-0.4.0/src/cinna/errors.py +212 -0
  18. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/local_import.py +1 -1
  19. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/main.py +163 -25
  20. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/mutagen_runtime.py +40 -9
  21. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/sync_session.py +2 -1
  22. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/sync_tui.py +6 -9
  23. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/conftest.py +10 -0
  24. cinna_cli-0.4.0/tests/test_cli_version.py +55 -0
  25. cinna_cli-0.4.0/tests/test_onboarding.py +800 -0
  26. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/uv.lock +1 -1
  27. cinna_cli-0.3.0/src/cinna/console.py +0 -39
  28. cinna_cli-0.3.0/src/cinna/errors.py +0 -66
  29. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/.claude/commands/cinna-cli.feature.doc.md +0 -0
  30. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/.github/workflows/publish.yml +0 -0
  31. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/.gitignore +0 -0
  32. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/LICENSE.md +0 -0
  33. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/agent_api/agent_api.md +0 -0
  34. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/agent_api/agent_api_acceptance.md +0 -0
  35. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/agent_api/agent_api_tech.md +0 -0
  36. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/agent_management/agent_management.md +0 -0
  37. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/agent_management/agent_management_acceptance.md +0 -0
  38. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/agent_management/agent_management_tech.md +0 -0
  39. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/agent_schedules/agent_schedules.md +0 -0
  40. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/agent_schedules/agent_schedules_acceptance.md +0 -0
  41. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/agent_schedules/agent_schedules_tech.md +0 -0
  42. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/doctor/doctor.md +0 -0
  43. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/doctor/doctor_acceptance.md +0 -0
  44. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/doctor/doctor_tech.md +0 -0
  45. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/git_versioning/git_versioning.md +0 -0
  46. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/git_versioning/git_versioning_acceptance.md +0 -0
  47. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/git_versioning/git_versioning_tech.md +0 -0
  48. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/improvement_requests/improvement_requests.md +0 -0
  49. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/improvement_requests/improvement_requests_acceptance.md +0 -0
  50. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/improvement_requests/improvement_requests_tech.md +0 -0
  51. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/live_sync/live_sync.md +0 -0
  52. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/live_sync/live_sync_acceptance.md +0 -0
  53. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/live_sync/live_sync_tech.md +0 -0
  54. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/local_agent_import/local_agent_import.md +0 -0
  55. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/local_agent_import/local_agent_import_acceptance.md +0 -0
  56. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/local_agent_import/local_agent_import_tech.md +0 -0
  57. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/mcp_integration/mcp_integration.md +0 -0
  58. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/mcp_integration/mcp_integration_acceptance.md +0 -0
  59. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/mcp_integration/mcp_integration_tech.md +0 -0
  60. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/remote_chat/remote_chat.md +0 -0
  61. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/remote_chat/remote_chat_acceptance.md +0 -0
  62. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/remote_chat/remote_chat_tech.md +0 -0
  63. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/remote_exec/remote_exec.md +0 -0
  64. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/remote_exec/remote_exec_acceptance.md +0 -0
  65. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/features/remote_exec/remote_exec_tech.md +0 -0
  66. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/interface.md +0 -0
  67. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/docs/mutagen_capabilities.md +0 -0
  68. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/scripts/check_docs_references.py +0 -0
  69. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/__init__.py +0 -0
  70. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/auth.py +0 -0
  71. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/client.py +0 -0
  72. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/config.py +0 -0
  73. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/context.py +0 -0
  74. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/git_versioning.py +0 -0
  75. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/improve.py +0 -0
  76. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/kit_contract.py +0 -0
  77. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/logging.py +0 -0
  78. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/mcp_proxy.py +0 -0
  79. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/sync.py +0 -0
  80. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/sync_ssh_shim.py +0 -0
  81. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/templates/ACCOUNT_CLAUDE.md.template +0 -0
  82. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/templates/CHAT_TESTING.md +0 -0
  83. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/templates/CLAUDE.md.template +0 -0
  84. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/templates/GIT_VERSIONING.md +0 -0
  85. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/src/cinna/templates/__init__.py +0 -0
  86. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/__init__.py +0 -0
  87. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_account.py +0 -0
  88. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_auth.py +0 -0
  89. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_bootstrap.py +0 -0
  90. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_chat.py +0 -0
  91. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_client.py +0 -0
  92. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_config.py +0 -0
  93. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_context.py +0 -0
  94. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_doctor.py +0 -0
  95. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_git_versioning.py +0 -0
  96. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_improve.py +0 -0
  97. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_kit_contract.py +0 -0
  98. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_local_import.py +0 -0
  99. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_main.py +0 -0
  100. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_mutagen_runtime.py +0 -0
  101. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_sync.py +0 -0
  102. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_sync_session.py +0 -0
  103. {cinna_cli-0.3.0 → cinna_cli-0.4.0}/tests/test_sync_ssh_shim.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cinna-cli
3
- Version: 0.3.0
3
+ Version: 0.4.0
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
@@ -63,7 +63,11 @@ Your Editor / Claude Code
63
63
  ## Prerequisites
64
64
 
65
65
  - **Python 3.10+**
66
- - **[Mutagen](https://mutagen.io)** (version pinned by the platform — `cinna setup` checks and prompts to install)
66
+ - **[Mutagen](https://mutagen.io)** (version pinned by the platform — `cinna setup` checks and prompts to install; set `CINNA_MUTAGEN_BIN=/abs/path/mutagen` to use a specific binary instead of the one on PATH)
67
+
68
+ ### Cinna Desktop
69
+
70
+ [Cinna Desktop](https://github.com/opencinna/cinna-desktop) installs its **own pinned copy** of cinna-cli (with `uv` and Mutagen alongside, inside its data folder) and drives it as a child process to create and keep alive an account workspace under `<AgentsHome>/Cloud/<host>/` right after you sign in — no `cinna login`, no token to paste. That private copy is not on your `PATH` unless you opt in from the desktop's settings; installing cinna-cli yourself (`uv tool install cinna-cli`) for terminal use is fine and both operate on the same workspace. The version the platform pins is reported by `cinna account status` / `cinna doctor`.
67
71
 
68
72
  ## Getting Started
69
73
 
@@ -135,7 +139,7 @@ cinna login app.example.com --dir my-cinna # always into a named sub
135
139
  # ✓ Account workspace ready.
136
140
  ```
137
141
 
138
- Use it when `cinna account status` or `cinna doctor` reports the account token has expired. Because the per-agent tokens minted from an account (`cinna agent sync`) can only be re-minted while the account token is valid, the flow is: `cinna login` (refresh the account), then `cinna doctor` (re-mint the dependent sub-agent tokens). If the platform doesn't expose the device-login endpoints yet, `cinna login` says so and points you at the `cinna account setup` paste fallback.
142
+ Use it when `cinna account status` or `cinna doctor` reports the account token has expired. Because the per-agent tokens minted from an account (`cinna agent sync`) can only be re-minted while the account token is valid, the flow is: `cinna login` (refresh the account), then `cinna doctor` (re-mint the dependent sub-agent tokens). If the platform doesn't expose the device-login endpoints yet, `cinna login` says so and points you at the `cinna account set-token` paste fallback.
139
143
 
140
144
  ### `cinna account setup <token_or_url>`
141
145
 
@@ -145,7 +149,7 @@ Initialize an **account workspace** — a multi-agent root from which you can di
145
149
  curl -sL https://your-platform.com/api/cli-setup/account/TOKEN | python3 -
146
150
  ```
147
151
 
148
- Accepts the same input forms as `cinna setup` (full curl command, URL, or bare token — bare tokens need `CINNA_PLATFORM_URL`). Creates `my-cinna/` (override with `--dir`) containing:
152
+ Accepts the same input forms as `cinna setup` (full curl command, URL, or bare token — bare tokens need `CINNA_PLATFORM_URL`). Creates a folder named after the platform domain (override with `--dir`: an absolute path is used as is with parents created, a relative one lands under the current directory; an existing account workspace there is refused before the token is spent) containing:
149
153
 
150
154
  ```
151
155
  my-cinna/
@@ -159,6 +163,17 @@ Setup also downloads the **context package** into `context/` — curated platfor
159
163
 
160
164
  The account token is only used for the account-level endpoints (listing agents, minting per-agent tokens, the context package). Per-agent work always runs on each child workspace's own token. Revoking the account session in Settings disconnects every agent synced from it.
161
165
 
166
+ `account setup`, `account set-token` and `account status` take `--no-input` (never prompt: defaults, or fail with code `needs_input`; also `CINNA_NO_INPUT=1`, accepted before or after the subcommand) and `--json` (one JSON object per line on stdout — progress `{"step","total","status","message"}` lines, then a final `{"result":"ok"|"error",…}`; implies `--no-input`). Exit codes are stable for every command: `0` ok, `10` setup token invalid/expired/used, `11` token for another account, `12` platform unreachable or 5xx, `1` anything else (the JSON `code` says what), `2` usage.
167
+
168
+ ### `cinna account set-token <token_or_url>`
169
+
170
+ Refresh the **account** token in place from a fresh account setup token — the account counterpart of `cinna set-token`, and the paste alternative to `cinna login`. Run inside the account workspace; it re-exchanges under the stored machine name and rewrites only `account_token` (plus a refreshed platform/frontend URL) in `.cinna/account.json`. The active user workspace, machine name, `context/` and every synced child under `agents/` are untouched; run `cinna doctor` afterwards to re-mint expired child tokens. A token for a different account is refused (exit `11`) and nothing is written. Bare tokens reuse the stored platform URL.
171
+
172
+ ```bash
173
+ cd my-cinna/
174
+ cinna account set-token 'curl -sL https://your-platform.com/api/cli-setup/account/TOKEN | python3 -'
175
+ ```
176
+
162
177
  ### `cinna account agents`
163
178
 
164
179
  List the agents your account can access (run from inside the account workspace). For each agent: display name + ID, building rights (`✓ can build`, `view-only`, or `foreign install` — installed bundles are publisher-managed and can't be synced), whether a remote environment is active, and whether a local workspace already exists under `agents/`.
@@ -167,7 +182,9 @@ List the agents your account can access (run from inside the account workspace).
167
182
 
168
183
  One-shot summary of the account workspace: platform/frontend URLs, machine name, synced-agent count, and an account-token probe (`valid token` / `expired token` / `no connection`) — the account-level counterpart of `cinna status`.
169
184
 
170
- Also reports the workspace's **context package version** against the platform's current one — the orchestrator guides ship in that tree, so a workspace set up before a guide existed silently lacks it. When it is behind, the command says so and points at `cinna account refresh-context`; `cinna improve list` prints the same nudge, since its playbook lives there.
185
+ Also reports the workspace's **context package version** against the platform's current one — the orchestrator guides ship in that tree, so a workspace set up before a guide existed silently lacks it. When it is behind, the command says so and points at `cinna account refresh-context`; `cinna improve list` prints the same nudge, since its playbook lives there. Likewise it compares the installed **cinna-cli version** with the one the platform pins (its `/.well-known/cinna-desktop` discovery document) and suggests `uv tool install cinna-cli==<pin>` when behind; no pin means "unknown", not an error.
186
+
187
+ With `--json` it prints a single line: `{"result":"ok","workspace",…,"token":"valid|expired|unreachable","synced_agents":N,"agents":[…],"context_package":{"local","remote","state"},"cli":{"installed","required","state"}}`.
171
188
 
172
189
  ### `cinna account refresh-context`
173
190
 
@@ -26,7 +26,11 @@ Your Editor / Claude Code
26
26
  ## Prerequisites
27
27
 
28
28
  - **Python 3.10+**
29
- - **[Mutagen](https://mutagen.io)** (version pinned by the platform — `cinna setup` checks and prompts to install)
29
+ - **[Mutagen](https://mutagen.io)** (version pinned by the platform — `cinna setup` checks and prompts to install; set `CINNA_MUTAGEN_BIN=/abs/path/mutagen` to use a specific binary instead of the one on PATH)
30
+
31
+ ### Cinna Desktop
32
+
33
+ [Cinna Desktop](https://github.com/opencinna/cinna-desktop) installs its **own pinned copy** of cinna-cli (with `uv` and Mutagen alongside, inside its data folder) and drives it as a child process to create and keep alive an account workspace under `<AgentsHome>/Cloud/<host>/` right after you sign in — no `cinna login`, no token to paste. That private copy is not on your `PATH` unless you opt in from the desktop's settings; installing cinna-cli yourself (`uv tool install cinna-cli`) for terminal use is fine and both operate on the same workspace. The version the platform pins is reported by `cinna account status` / `cinna doctor`.
30
34
 
31
35
  ## Getting Started
32
36
 
@@ -98,7 +102,7 @@ cinna login app.example.com --dir my-cinna # always into a named sub
98
102
  # ✓ Account workspace ready.
99
103
  ```
100
104
 
101
- Use it when `cinna account status` or `cinna doctor` reports the account token has expired. Because the per-agent tokens minted from an account (`cinna agent sync`) can only be re-minted while the account token is valid, the flow is: `cinna login` (refresh the account), then `cinna doctor` (re-mint the dependent sub-agent tokens). If the platform doesn't expose the device-login endpoints yet, `cinna login` says so and points you at the `cinna account setup` paste fallback.
105
+ Use it when `cinna account status` or `cinna doctor` reports the account token has expired. Because the per-agent tokens minted from an account (`cinna agent sync`) can only be re-minted while the account token is valid, the flow is: `cinna login` (refresh the account), then `cinna doctor` (re-mint the dependent sub-agent tokens). If the platform doesn't expose the device-login endpoints yet, `cinna login` says so and points you at the `cinna account set-token` paste fallback.
102
106
 
103
107
  ### `cinna account setup <token_or_url>`
104
108
 
@@ -108,7 +112,7 @@ Initialize an **account workspace** — a multi-agent root from which you can di
108
112
  curl -sL https://your-platform.com/api/cli-setup/account/TOKEN | python3 -
109
113
  ```
110
114
 
111
- Accepts the same input forms as `cinna setup` (full curl command, URL, or bare token — bare tokens need `CINNA_PLATFORM_URL`). Creates `my-cinna/` (override with `--dir`) containing:
115
+ Accepts the same input forms as `cinna setup` (full curl command, URL, or bare token — bare tokens need `CINNA_PLATFORM_URL`). Creates a folder named after the platform domain (override with `--dir`: an absolute path is used as is with parents created, a relative one lands under the current directory; an existing account workspace there is refused before the token is spent) containing:
112
116
 
113
117
  ```
114
118
  my-cinna/
@@ -122,6 +126,17 @@ Setup also downloads the **context package** into `context/` — curated platfor
122
126
 
123
127
  The account token is only used for the account-level endpoints (listing agents, minting per-agent tokens, the context package). Per-agent work always runs on each child workspace's own token. Revoking the account session in Settings disconnects every agent synced from it.
124
128
 
129
+ `account setup`, `account set-token` and `account status` take `--no-input` (never prompt: defaults, or fail with code `needs_input`; also `CINNA_NO_INPUT=1`, accepted before or after the subcommand) and `--json` (one JSON object per line on stdout — progress `{"step","total","status","message"}` lines, then a final `{"result":"ok"|"error",…}`; implies `--no-input`). Exit codes are stable for every command: `0` ok, `10` setup token invalid/expired/used, `11` token for another account, `12` platform unreachable or 5xx, `1` anything else (the JSON `code` says what), `2` usage.
130
+
131
+ ### `cinna account set-token <token_or_url>`
132
+
133
+ Refresh the **account** token in place from a fresh account setup token — the account counterpart of `cinna set-token`, and the paste alternative to `cinna login`. Run inside the account workspace; it re-exchanges under the stored machine name and rewrites only `account_token` (plus a refreshed platform/frontend URL) in `.cinna/account.json`. The active user workspace, machine name, `context/` and every synced child under `agents/` are untouched; run `cinna doctor` afterwards to re-mint expired child tokens. A token for a different account is refused (exit `11`) and nothing is written. Bare tokens reuse the stored platform URL.
134
+
135
+ ```bash
136
+ cd my-cinna/
137
+ cinna account set-token 'curl -sL https://your-platform.com/api/cli-setup/account/TOKEN | python3 -'
138
+ ```
139
+
125
140
  ### `cinna account agents`
126
141
 
127
142
  List the agents your account can access (run from inside the account workspace). For each agent: display name + ID, building rights (`✓ can build`, `view-only`, or `foreign install` — installed bundles are publisher-managed and can't be synced), whether a remote environment is active, and whether a local workspace already exists under `agents/`.
@@ -130,7 +145,9 @@ List the agents your account can access (run from inside the account workspace).
130
145
 
131
146
  One-shot summary of the account workspace: platform/frontend URLs, machine name, synced-agent count, and an account-token probe (`valid token` / `expired token` / `no connection`) — the account-level counterpart of `cinna status`.
132
147
 
133
- Also reports the workspace's **context package version** against the platform's current one — the orchestrator guides ship in that tree, so a workspace set up before a guide existed silently lacks it. When it is behind, the command says so and points at `cinna account refresh-context`; `cinna improve list` prints the same nudge, since its playbook lives there.
148
+ Also reports the workspace's **context package version** against the platform's current one — the orchestrator guides ship in that tree, so a workspace set up before a guide existed silently lacks it. When it is behind, the command says so and points at `cinna account refresh-context`; `cinna improve list` prints the same nudge, since its playbook lives there. Likewise it compares the installed **cinna-cli version** with the one the platform pins (its `/.well-known/cinna-desktop` discovery document) and suggests `uv tool install cinna-cli==<pin>` when behind; no pin means "unknown", not an error.
149
+
150
+ With `--json` it prints a single line: `{"result":"ok","workspace",…,"token":"valid|expired|unreachable","synced_agents":N,"agents":[…],"context_package":{"local","remote","state"},"cli":{"installed","required","state"}}`.
134
151
 
135
152
  ### `cinna account refresh-context`
136
153
 
@@ -137,6 +137,7 @@ Key properties:
137
137
  A second token type (`token_type="cli-account"`) issued to an **account workspace** (`.cinna/account.json`). Scoped only to the `/account/*` routes — it discovers agents and mints per-agent CLI tokens (`cinna agent sync`), but cannot itself sync or exec. Same 7-day rolling expiry as a CLI token.
138
138
 
139
139
  - **Refreshable without a paste** — `cinna login` runs an RFC 8628 device-authorization flow: the CLI prints a short code + URL, the user clicks **Authorize** in the browser (already signed in), and the CLI swaps the fresh token into `.cinna/account.json` in place. Run from an empty/new folder, the same command instead bootstraps a brand-new account workspace.
140
+ - **Refreshable from a setup token** — `cinna account set-token <token_or_url>` re-exchanges a fresh *account* setup token under the stored machine name and rewrites only `account_token` (same in-place swap). Bound to the same account: a different platform origin or token subject aborts with exit `11`. This is the path Cinna Desktop drives (`--no-input --json`) so the user never runs `cinna login`.
140
141
  - **Mints child tokens** — per-agent tokens minted from it carry its id as provenance and are re-mintable via `POST /account/agents/{id}/mint` (used by `cinna agent sync` and `cinna doctor`).
141
142
 
142
143
  ### Knowledge Source
@@ -208,16 +209,17 @@ main.py (CLI commands — Click)
208
209
  ├── config.py — .cinna/config.json: load/save/find
209
210
  ├── auth.py — JWT storage, Authorization headers
210
211
  ├── client.py — PlatformClient: HTTP + SSE stream_exec
211
- ├── mutagen_runtime.py — detect/install Mutagen; gate on version match
212
+ ├── mutagen_runtime.py — detect/install Mutagen; gate on version match; `CINNA_MUTAGEN_BIN` override
213
+ ├── cli_version.py — installed vs platform-pinned cinna-cli version
212
214
  ├── sync_session.py — wrap the `mutagen` CLI (start/stop/status/conflicts)
213
215
  ├── sync_tui.py — live Textual TUI shown by `cinna dev` (Sync/Details/Conflicts tabs)
214
216
  ├── sync_ssh_shim.py — `cinna-sync-ssh` entry point (WebSocket transport)
215
217
  ├── sync.py — tarball/zip extraction helpers (initial clone only)
216
218
  ├── context.py — CLAUDE.md, BUILDING_AGENT.md, .mcp.json, opencode.json
217
219
  ├── mcp_proxy.py — MCP stdio server for knowledge_query
218
- ├── console.py — Rich helpers
220
+ ├── console.py — Rich helpers + the `--json` / `--no-input` switches (prompt/confirm wrappers, JSON lines)
219
221
  ├── logging.py — cinna.log (rotating file handler)
220
- └── errors.py — exception hierarchy
222
+ └── errors.py — exception hierarchy; `CinnaExit` = stable exit code + machine code
221
223
  ```
222
224
 
223
225
  ### Local Directory Layout
@@ -282,8 +284,8 @@ authoring convention.
282
284
 
283
285
  | Feature | Command surface | Docs |
284
286
  |---|---|---|
285
- | **Bootstrap & onboarding** | `cinna setup` / `set-token` / `login` / `list` / `status` / `disconnect[-all]` / `completion` / `dev` / `redev` | [business](features/bootstrap_onboarding/bootstrap_onboarding.md) · [tech](features/bootstrap_onboarding/bootstrap_onboarding_tech.md) · [acceptance](features/bootstrap_onboarding/bootstrap_onboarding_acceptance.md) |
286
- | **Account workspace** | `cinna account` (setup, agents, status, refresh-context, user-workspace, credentials) | [business](features/account_workspace/account_workspace.md) · [tech](features/account_workspace/account_workspace_tech.md) · [acceptance](features/account_workspace/account_workspace_acceptance.md) |
287
+ | **Bootstrap & onboarding** | `cinna setup` / `set-token` / `login` / `list` / `status` / `disconnect[-all]` / `completion` / `dev` / `redev`; the no-TTY contract (`--no-input`, `--json`, exit codes, `CINNA_MUTAGEN_BIN`) | [business](features/bootstrap_onboarding/bootstrap_onboarding.md) · [tech](features/bootstrap_onboarding/bootstrap_onboarding_tech.md) · [acceptance](features/bootstrap_onboarding/bootstrap_onboarding_acceptance.md) |
288
+ | **Account workspace** | `cinna account` (setup, set-token, agents, status, refresh-context, user-workspace, credentials); desktop-managed workspaces | [business](features/account_workspace/account_workspace.md) · [tech](features/account_workspace/account_workspace_tech.md) · [acceptance](features/account_workspace/account_workspace_acceptance.md) |
287
289
  | **Agent management** | `cinna agent` (sync, unsync, create, restart-env, show, status) | [business](features/agent_management/agent_management.md) · [tech](features/agent_management/agent_management_tech.md) · [acceptance](features/agent_management/agent_management_acceptance.md) |
288
290
  | **Agent schedules** | `cinna agent schedule` (list, generate, create, update, run, logs, delete) | [business](features/agent_schedules/agent_schedules.md) · [tech](features/agent_schedules/agent_schedules_tech.md) · [acceptance](features/agent_schedules/agent_schedules_acceptance.md) |
289
291
  | **Live sync** | `cinna sync` (status, conflicts, push, pull, resolve) + Mutagen transport | [business](features/live_sync/live_sync.md) · [tech](features/live_sync/live_sync_tech.md) · [acceptance](features/live_sync/live_sync_acceptance.md) |
@@ -605,6 +607,8 @@ When a CLI token expires (or is revoked) the normal remedy is `cinna set-token <
605
607
  | Method | Route | Auth | Purpose |
606
608
  |--------|-------|------|---------|
607
609
  | POST | `/api/cli-setup/{token}` | Token | Exchange setup token for CLI token |
610
+ | POST | `/api/cli-setup/account/{token}` | Token | Exchange an **account** setup token (`cinna account setup` / `cinna account set-token`); 4xx ⇒ exit 10, 5xx ⇒ exit 12 |
611
+ | GET | `/.well-known/cinna-desktop` | None | Desktop discovery document; `local_dev.cinna_cli_version` is the cinna-cli pin `cinna account status` / `cinna doctor` compare against (absent ⇒ "unknown") |
608
612
  | GET | `/api/v1/cli/agents/{id}/workspace` | CLI JWT | One-shot tarball for initial clone |
609
613
  | GET | `/api/v1/cli/agents/{id}/building-context` | CLI JWT | Assembled building prompt |
610
614
  | POST | `/api/v1/cli/agents/{id}/knowledge/search` | CLI JWT | Knowledge base search (via MCP proxy) |
@@ -645,6 +649,8 @@ uv run ruff format --check src/
645
649
 
646
650
  The CLI is published to [PyPI](https://pypi.org/project/cinna-cli/) by `.github/workflows/publish.yml`, which runs on any pushed tag matching `v*` (and via manual `workflow_dispatch`). It builds the sdist + wheel with `uv build`, runs `twine check`, then publishes through PyPI **Trusted Publishing** (OIDC — no stored API token) from the GitHub `pypi` environment.
647
651
 
652
+ Cinna Desktop installs a **pinned** version (`uv tool install cinna-cli==<version>`) — the version each cinna-core instance advertises as `local_dev.cinna_cli_version` in its discovery document (`CINNA_CLI_VERSION` setting). A release that changes the driver contract (exit codes, `--json` shapes, the three desktop-driven verbs) must be published to PyPI **before** cinna-core's pin moves to it.
653
+
648
654
  ### Cutting a release
649
655
 
650
656
  Versioning is SemVer (`MAJOR.MINOR.PATCH`); a patch release is the common case. From a clean `main` with the changes you want to ship already merged:
@@ -43,7 +43,16 @@ each with its own token, registry entry, and Mutagen session.
43
43
 
44
44
  - **Account CLI token** — `cli-account` JWT in `.cinna/account.json`. Same 7-day
45
45
  rolling expiry as a per-agent token. Refreshed in place by `cinna login` (a
46
- browser device-authorization flow — no paste). Mints child tokens; never syncs.
46
+ browser device-authorization flow — no paste) or by `cinna account set-token`
47
+ (paste / hand over a fresh account setup token). Mints child tokens; never
48
+ syncs.
49
+ - **Desktop-managed workspace** — an account workspace that Cinna Desktop
50
+ created and keeps alive by driving `cinna` as a child process with no
51
+ terminal: `account setup` once (into `<AgentsHome>/Cloud/<host>/`), then
52
+ `account set-token` whenever it mints a new setup token with its own
53
+ session, and `account status --json` to reconcile. The user never runs
54
+ `cinna login`, yet the workspace is byte-identical to a terminal-made one —
55
+ the user's own terminal can `cd` in and use every command.
47
56
  - **`agents/` directory** — where `cinna agent sync` materializes per-agent
48
57
  checkouts. Account commands that touch local state (`status`, `agents`,
49
58
  `refresh-context`) walk this tree to find synced children.
@@ -74,11 +83,52 @@ each with its own token, registry entry, and Mutagen session.
74
83
  `agents/`, the orchestrator `CLAUDE.md` + `.claude/settings.json`, the
75
84
  knowledge MCP wiring, and the `context/` package. The folder name defaults to
76
85
  the platform domain (e.g. `demo-core_opencinna_io`); `--dir` or the prompt
77
- overrides it.
86
+ overrides it. `--dir` is a contract: an **absolute** path is used exactly as
87
+ given (missing parents are created), a relative one lands under the current
88
+ directory. Either way an existing `.cinna/account.json` at the target is
89
+ refused before the token is spent.
78
90
  3. Alternatively, `cinna login <domain>` from an empty folder bootstraps the same
79
91
  workspace via the browser device flow (no paste); run inside an existing
80
92
  account workspace it refreshes the token in place.
81
93
 
94
+ ### Refresh the token from a setup token — `cinna account set-token`
95
+ 1. Mint a fresh **account** setup token (Settings → Local Development, or Cinna
96
+ Desktop does it with its own session).
97
+ 2. Inside the account workspace, `cinna account set-token <token-or-url>`
98
+ re-exchanges it under the **stored** machine name and swaps only
99
+ `account_token` (plus a server-refreshed platform / frontend URL) into
100
+ `.cinna/account.json`. The active user workspace, machine name, `context/`
101
+ and every child under `agents/` are untouched — the same in-place contract
102
+ `cinna login` gives, minus the browser. A bare token reuses the stored
103
+ platform URL.
104
+ 3. The new token must be for the **same account**: a different platform origin,
105
+ or a different `sub` on the token, is refused (exit 11) and nothing is written.
106
+ 4. Child tokens are not touched; `cinna doctor` re-mints expired ones as usual.
107
+
108
+ ### Driven by Cinna Desktop (no terminal)
109
+ 1. The desktop obtains a setup token from the platform with its own session and
110
+ spawns `cinna account setup "<setup_command>" --dir <absolute> --name <machine>
111
+ --no-input --json`.
112
+ 2. `--no-input` guarantees the process never waits for a human: every prompt
113
+ takes its default (machine name, folder), or fails with the machine code
114
+ `needs_input`. `CINNA_NO_INPUT=1` does the same for any command. `--json`
115
+ implies it.
116
+ 3. `--json` turns the output into one JSON object per line on stdout: progress
117
+ lines `{step, total, status: start|ok|warn|fail, message}` mirroring the
118
+ numbered steps, then exactly one final line — `{"result": "ok", …}` with the
119
+ workspace path, platform / frontend URLs, machine name and context-package
120
+ outcome, or `{"result": "error", "code", "detail"}`. Nothing else touches
121
+ stdout; logs stay in `cinna.log`.
122
+ 4. The process exit code is stable: `0` ok · `10` setup token invalid / expired /
123
+ already used · `11` the token is for another account · `12` the platform is
124
+ unreachable or answered 5xx · `1` anything else, with a specific `code`
125
+ (`workspace_exists`, `needs_input`, `mutagen_missing`, `mutagen_mismatch`,
126
+ `not_an_account_workspace`, …) · `2` bad invocation.
127
+ 5. Later the desktop runs `cinna account set-token "<setup_command>" --no-input
128
+ --json` to refresh silently, and `cinna account status --json` to read token
129
+ validity, synced agents, context-package freshness and whether the installed
130
+ cinna-cli matches the version the platform pins.
131
+
82
132
  ### Discover and attach agents
83
133
  1. `cinna account agents` lists the agents the account can access — name + id,
84
134
  build rights (foreign bundle installs are view-only), whether the remote env
@@ -126,7 +176,25 @@ each with its own token, registry entry, and Mutagen session.
126
176
  sync/exec always use the per-agent child token via the per-agent client.
127
177
  - **Single-use setup token is guarded before it's burned.** `cinna account setup`
128
178
  refuses an existing-workspace target *before* exchanging the token, so a doomed
129
- run never wastes the one-time token.
179
+ run never wastes the one-time token (exit 1, code `workspace_exists`).
180
+ - **`set-token` is account-bound, fail-loud.** The refreshed token must match
181
+ the workspace's platform origin and (when both tokens carry one) the same
182
+ subject; otherwise nothing is written and the command exits 11
183
+ (`account_mismatch`). It never rebinds a workspace to another account.
184
+ - **Never hang without a terminal.** Under `--no-input` (or `CINNA_NO_INPUT=1`,
185
+ or `--json`) no command blocks on stdin: prompts take their default or fail
186
+ with `needs_input`; confirmations take their default (usually "No", which
187
+ aborts). The `/dev/tty` fallback the `curl | python3` bootstrap uses for the
188
+ folder prompt is skipped too.
189
+ - **`--json` is a pure stream.** Rich output is suppressed entirely; each stdout
190
+ line is one JSON object and the last one is always the `result` line, on
191
+ success and on failure alike. Human output is unchanged when the flag is off.
192
+ - **Exit codes are a contract.** 0 / 10 / 11 / 12 / 1 (+ `code`) / 2 as listed
193
+ in the desktop flow; every error the CLI raises on purpose maps onto it, so a
194
+ driver switches on the number, not on message text.
195
+ - **Version pin is advisory.** `account status` compares the running cinna-cli
196
+ with the version the platform publishes in its discovery document; a missing
197
+ pin is `unknown`, never an error.
130
198
  - **Fail-loud on backend errors.** Token exchange, mint, and every account call
131
199
  surface the backend's `detail` verbatim (expired/used token, foreign-install
132
200
  403, ambiguous agent). The CLI does not pre-judge build rights client-side —
@@ -173,7 +241,11 @@ cinna account setup ────────────────────
173
241
  ▼
174
242
  .cinna/account.json (account CLI token, cli-account scope)
175
243
  │
244
+ ├── cinna account set-token <token> ───────────────────► POST /cli-setup/account/<token>
245
+ │ (stored machine name; same-account check; rewrites account_token only)
246
+ ├── cinna login (device flow) ─────────────────────────► /api/v1/cli/account/login/*
176
247
  ├── cinna account agents / status / refresh-context ─► AccountClient ─► /api/v1/cli/account/*
248
+ │ (status: + GET /.well-known/cinna-desktop for the cinna-cli pin)
177
249
  ├── cinna account user-workspace … (client-side active selection)
178
250
  ├── cinna account credentials … (metadata-only drafts)
179
251
  │
@@ -205,11 +277,17 @@ cinna account setup ────────────────────
205
277
  publisher-install flag its ownership step depends on. See
206
278
  [improvement_requests](../improvement_requests/improvement_requests.md).
207
279
  - **Doctor / login** — `cinna doctor` re-mints expired per-agent tokens through
208
- the account token, and groups blocked agents under a single `cinna login` hint
209
- when the account token itself has expired.
280
+ the account token, and groups blocked agents under a single `cinna login` /
281
+ `cinna account set-token` hint when the account token itself has expired. It
282
+ also reports a cinna-cli that differs from the platform's pin.
283
+ - **Bootstrap / onboarding** — the no-terminal contract (`--no-input`, `--json`,
284
+ exit codes, `CINNA_MUTAGEN_BIN`) is CLI-wide and described in
285
+ [Bootstrap & Onboarding](../bootstrap_onboarding/bootstrap_onboarding.md);
286
+ this doc covers how the account verbs use it.
287
+ - **Cinna Desktop** — installs its own pinned cinna-cli (with uv and Mutagen) and
288
+ drives the three verbs above; the desktop-side plan lives in the cinna-desktop
289
+ repo (`plans/one-click-onboarding-desktop.md`). <!-- nocheck: cross-repo path -->
210
290
 
211
291
  Implementation: see [account_workspace_tech.md](account_workspace_tech.md).
212
292
  Real-usage e2e test scenarios: see
213
293
  [account_workspace_acceptance.md](account_workspace_acceptance.md).
214
- </content>
215
- </invoke>
@@ -26,8 +26,10 @@ the silent-secret and scope-drift bugs live.
26
26
  - **Editable install** of the CLI under test:
27
27
  `python3 -c "import cinna,os;print(os.path.dirname(cinna.__file__))"` must point
28
28
  at this repo's `src/cinna`. Confirm `which cinna` resolves and
29
- `cinna account --help` lists `setup / agents / status / refresh-context /
30
- user-workspace / credentials`.
29
+ `cinna account --help` lists `setup / set-token / agents / status /
30
+ refresh-context / user-workspace / credentials`.
31
+ - For the no-terminal scenarios (15–18): `jq` to inspect the JSON lines, and a
32
+ second **account** setup token per run (every exchange burns one).
31
33
  - `git` and `mutagen` on `PATH` (needed once a child agent is synced and
32
34
  exercised).
33
35
 
@@ -253,8 +255,114 @@ the silent-secret and scope-drift bugs live.
253
255
  - **Watch for:** user files deleted; the registry entry surviving; a revoke error
254
256
  aborting the command.
255
257
 
258
+ ### 15. Desktop-style setup: absolute `--dir`, `--no-input`, `--json`, no TTY
259
+
260
+ - **Goal:** Cinna Desktop creates the account workspace as a child process with
261
+ no terminal and parses the result.
262
+ - **Setup:** a fresh account setup token; note the `setup_command` string the
263
+ platform returns (`curl -sL …/api/cli-setup/account/<TOKEN> | python3 -`).
264
+ - **Steps:**
265
+ ```
266
+ TARGET="$HOME/CinnaAgents/Cloud/localhost" # parents must not exist yet
267
+ cinna account setup 'curl -sL http://localhost:8000/api/cli-setup/account/<TOKEN> | python3 -' \
268
+ --dir "$TARGET" --name desktop-test --no-input --json < /dev/null > out.jsonl; echo "exit $?"
269
+ cat out.jsonl | jq -c .
270
+ ls -a "$TARGET"; cat "$TARGET/.cinna/account.json"
271
+ ```
272
+ - **Expected:** exit `0`. Every line of `out.jsonl` parses; the first three are
273
+ `{"step":1..3,"total":3,"status":"start",…}`, the last is `{"result":"ok",
274
+ "workspace":"<TARGET>","platform_url":…,"frontend_url":…,"machine_name":
275
+ "desktop-test","context_package":"ok"}`. The workspace sits exactly at
276
+ `$TARGET` (dots in the host kept, parents created), with the same files as
277
+ scenario 1. No table, hint or spinner text on stdout.
278
+ - **Watch for:** the workspace landing under cwd instead of the absolute path;
279
+ a non-JSON line on stdout (Rich leaking); the process waiting on stdin
280
+ (folder / machine-name prompt); `context_package` not reflecting a failed
281
+ download (should be `failed` with a preceding `"status":"warn"` line, exit
282
+ still 0).
283
+
284
+ ### 16. Desktop-style refresh: `account set-token` in place, same account only
285
+
286
+ - **Goal:** the desktop refreshes an expiring account token silently; a token
287
+ for another account can never be swapped in.
288
+ - **Setup:** scenario 15's workspace; `cinna account user-workspace activate
289
+ <name>` so a client-side selection exists; one synced child (scenario 5).
290
+ Mint two fresh account setup tokens: one as the **same** user, one as a
291
+ **different** user on the same platform.
292
+ - **Steps:**
293
+ ```
294
+ cd "$TARGET"; cp .cinna/account.json /tmp/before.json
295
+ cinna account set-token 'curl -sL http://localhost:8000/api/cli-setup/account/<SAME_USER_TOKEN> | python3 -' \
296
+ --no-input --json < /dev/null; echo "exit $?"
297
+ diff <(jq 'del(.account_token)' /tmp/before.json) <(jq 'del(.account_token)' .cinna/account.json)
298
+ cat agents/<slug>/.cinna/config.json | jq .cli_token # unchanged
299
+ cinna account set-token 'curl -sL http://localhost:8000/api/cli-setup/account/<OTHER_USER_TOKEN> | python3 -' \
300
+ --no-input --json < /dev/null; echo "exit $?"
301
+ cinna account set-token '<SAME_USER_TOKEN again>' --json; echo "exit $?"
302
+ ```
303
+ - **Expected:** first call exits `0`, prints two `start` steps, an `ok` line
304
+ and `{"result":"ok",…,"context_package":"skipped"}`; only `account_token`
305
+ changed (the `diff` is empty), the child token is untouched, and the exchange
306
+ was made with the **stored** machine name (check the CLI token list in
307
+ Settings — no new machine). The other-user call exits `11` with
308
+ `{"result":"error","code":"account_mismatch",…}` and `account.json` is
309
+ unchanged. The reused (already burned) token exits `10` with
310
+ `code: "setup_token_invalid"` and the backend detail.
311
+ - **Watch for:** the mismatch being detected only after the file was written;
312
+ `user_workspace_id` or `machine_name` reset; a new machine appearing in the
313
+ platform's token list; exit `1` where `10` / `11` is required.
314
+
315
+ ### 17. Exit-code contract
316
+
317
+ - **Goal:** a driver can act on the exit code alone.
318
+ - **Steps:** run each from a scratch dir with `--no-input --json < /dev/null`
319
+ and record `$?` plus the final line's `code`:
320
+ ```
321
+ cinna account setup '<ALREADY_USED_TOKEN_URL>' --dir /tmp/x1 # 10 setup_token_invalid
322
+ cinna account setup '<VALID_URL>' --dir "$TARGET" # 1 workspace_exists (token NOT burned)
323
+ cinna account setup 'http://127.0.0.1:1/api/cli-setup/account/X' --dir /tmp/x2 # 12 network
324
+ cinna account status # 1 not_an_account_workspace
325
+ cinna --no-input login # 1 needs_input (no domain, human text)
326
+ cinna account setup # 2 usage
327
+ ```
328
+ Then, with the backend stopped: `cinna account set-token '<URL>' --json` → `12`.
329
+ - **Expected:** the codes in the comments. For the `workspace_exists` case,
330
+ re-use the same valid token afterwards in a fresh dir — it must still work.
331
+ - **Watch for:** any of these coming back as a bare `1`; a traceback instead of
332
+ a JSON error line; the guard case burning the token.
333
+
334
+ ### 18. `account status --json` and the cinna-cli version pin
335
+
336
+ - **Goal:** the desktop reconciles from one status line, including whether to
337
+ reinstall cinna-cli.
338
+ - **Steps:**
339
+ ```
340
+ cd "$TARGET"; cinna account status --json | jq .
341
+ curl -s http://localhost:8000/.well-known/cinna-desktop | jq .local_dev
342
+ cinna account status | tail -20
343
+ cinna doctor --dry-run
344
+ ```
345
+ - **Expected:** exactly one JSON line with `result`, `workspace`,
346
+ `platform_url`, `frontend_url`, `machine_name`, `active_workspace`
347
+ (`{id,name}` or `null`), `token` (`valid|expired|unreachable`),
348
+ `synced_agents`, `agents[]`, `context_package{local,remote,state}` and
349
+ `cli{installed,required,state}`. When the platform publishes
350
+ `local_dev.cinna_cli_version`, `cli.required` equals it and `state` is
351
+ `current` / `behind` / `ahead`; on an older platform `required` is `null`
352
+ and `state` is `unknown` — still exit 0. The human `status` shows the same
353
+ as a `cinna-cli` table row and a `uv tool install cinna-cli==<pin>` hint when
354
+ behind; `doctor` lists the platform under "manual action needed" only when
355
+ the pin differs.
356
+ - **Watch for:** a missing discovery document turning into an error; `doctor`
357
+ nagging when no pin is published; the JSON line missing `cli`.
358
+
256
359
  ## Cross-cutting invariants (must hold across all scenarios)
257
360
 
361
+ - **`--json` stdout is JSON only, last line is the verdict** — every stdout line
362
+ parses; exactly one `result` line; human hints never leak.
363
+ - **Never wait for a human without a terminal** — with `--no-input` / `--json`
364
+ / `CINNA_NO_INPUT=1` every command either proceeds or fails with
365
+ `needs_input`; nothing reads stdin or `/dev/tty`.
258
366
  - **No secret ever sent** — credential create/update bodies carry only metadata;
259
367
  the CLI cannot read or write a credential's secret value.
260
368
  - **Account token stays account-scoped** — it is used only on `/account/*`; every
@@ -284,4 +392,3 @@ the silent-secret and scope-drift bugs live.
284
392
  - Verify `~/.cinna/agents.json` has no leftover entries for the test children —
285
393
  especially if any helper ran **outside** pytest's global-state isolation (it
286
394
  would write the real registry).
287
- </content>
@@ -29,15 +29,25 @@ and tests in `tests/test_account.py`.
29
29
  - `src/cinna/mcp_proxy.py` — account-mode knowledge proxy
30
30
  (`run_mcp_proxy` / `_resolve_proxy_context` / `create_account_mcp_server`)
31
31
  wired by the account `.mcp.json`.
32
- - `src/cinna/errors.py` — `AccountConfigNotFoundError` ("Not in a cinna account
33
- workspace…").
32
+ - `src/cinna/errors.py` — `CinnaExit` (stable exit code + machine `code`) and
33
+ its subclasses used here: `SetupTokenError` (10), `AccountMismatchError` (11),
34
+ `NetworkError` / `PlatformError` 5xx (12), `WorkspaceExistsError`,
35
+ `AccountConfigNotFoundError`.
36
+ - `src/cinna/console.py` — the `json_mode` / `no_input` switches, the JSON line
37
+ writer (`emit_json` / `emit_result`) and the `prompt` / `confirm` /
38
+ `interactive` wrappers every prompt site goes through.
39
+ - `src/cinna/cli_version.py` — installed-vs-pinned cinna-cli version
40
+ (`cli_version_status`, `fetch_required_cli_version`).
34
41
  - `src/cinna/templates/ACCOUNT_CLAUDE.md.template` — the orchestrator
35
42
  `CLAUDE.md` source.
36
43
  - Tests: `tests/test_account.py` (setup, refresh-context, agents listing +
37
44
  workspace scoping, status, agent sync/unsync, exec `--agent`, child-workspace
38
45
  resolution incl. multi-segment subdir, user-workspace, credentials,
39
- `AccountClient` HTTP-level), `tests/test_client.py` (account client), and the
40
- MCP-proxy account-mode tests in `tests/test_account.py`.
46
+ `AccountClient` HTTP-level), `tests/test_client.py` (account client), the
47
+ MCP-proxy account-mode tests in `tests/test_account.py`, and
48
+ `tests/test_onboarding.py` (the driver contract: exit codes, absolute `--dir`,
49
+ `account set-token`, `--no-input`, `--json` line snapshots, version pin in
50
+ status / doctor).
41
51
 
42
52
  ## Command surface
43
53
 
@@ -46,6 +56,8 @@ Each verb → its handler in `src/cinna/main.py` → the body in
46
56
 
47
57
  - `cinna account setup` → `src/cinna/main.py:account_setup()` →
48
58
  `src/cinna/account.py:run_account_setup()`
59
+ - `cinna account set-token` → `src/cinna/main.py:account_set_token()` →
60
+ `src/cinna/account.py:run_account_set_token()`
49
61
  - `cinna account agents` → `src/cinna/main.py:account_agents()` →
50
62
  `src/cinna/account.py:run_account_agents()`
51
63
  - `cinna account status` → `src/cinna/main.py:account_status()` →
@@ -80,6 +92,14 @@ Each verb → its handler in `src/cinna/main.py` → the body in
80
92
  `src/cinna/main.py:account_credentials_share_with_agent()` →
81
93
  `src/cinna/account.py:run_credentials_share()`
82
94
 
95
+ `setup`, `set-token` and `status` also take `--no-input` and `--json`
96
+ (`src/cinna/main.py:no_input_option()` / `json_option()` — eager, value-less
97
+ options whose callbacks flip `src/cinna/console.py:set_no_input()` /
98
+ `set_json_mode()`); the root group takes `--no-input` too (also
99
+ `CINNA_NO_INPUT=1`). The per-command copy exists because
100
+ `ignore_unknown_options` + the `nargs=-1` setup argument would otherwise
101
+ swallow a flag placed after the subcommand, which is how the desktop invokes it.
102
+
83
103
  Related (documented here as integration points): `cinna agent sync` →
84
104
  `src/cinna/account.py:run_agent_sync()`, `cinna agent unsync` →
85
105
  `run_agent_unsync()`, `cinna login` → `run_login()`.
@@ -99,10 +119,32 @@ Related (documented here as integration points): `cinna agent sync` →
99
119
  bare token falls back to `CINNA_PLATFORM_URL`.
100
120
  - `src/cinna/account.py:default_account_dir_name()` — derives the default folder
101
121
  from the platform host (collapses non-`[A-Za-z0-9-]` to `_`).
102
- - `src/cinna/account.py:run_account_setup()` — parse → derive/prompt the dir →
103
- **guard the target before** `_exchange_account_setup_token()` → build
104
- `AccountConfig` → `_write_account_files()` → best-effort
105
- `_install_context_package()`.
122
+ - `src/cinna/account.py:resolve_account_dir()` — the `--dir` contract: absolute
123
+ (after `~` expansion) → as is, relative → under cwd. No symlink resolution, so
124
+ the path reported back equals the one passed.
125
+ - `src/cinna/account.py:run_account_setup()` — parse → derive/prompt the dir
126
+ (`_prompt_account_dir()` returns the default outright under `no_input`) →
127
+ `resolve_account_dir()` → **guard the target before**
128
+ `_exchange_account_setup_token()` (`WorkspaceExistsError`) → build
129
+ `AccountConfig` → `_write_account_files()` (creates parents) → best-effort
130
+ `_install_context_package()` → `console.emit_result()` with
131
+ `_account_result_fields()`.
132
+ - `src/cinna/account.py:_exchange_account_setup_token()` — the exchange with the
133
+ exit-code mapping: transport error → `NetworkError` (12), 5xx →
134
+ `PlatformError` (12), any other non-200 → `SetupTokenError` (10, backend
135
+ detail verbatim, `http_status` in the JSON error line).
136
+ - `src/cinna/account.py:run_account_set_token()` — `find_account_root()` →
137
+ `parse_account_setup_input(…, fallback_platform_url=<stored>/api)` →
138
+ exchange under the **stored** `machine_name` → `_same_origin()` check on
139
+ `platform_url` and `_jwt_claims()` `sub` comparison (both raise
140
+ `AccountMismatchError`, 11, before any write) → rewrite `account_token` +
141
+ refreshed `platform_url` / `frontend_url` → `save_account_config()`.
142
+ `user_workspace_*`, `machine_name`, `context/`, children untouched.
143
+ - `src/cinna/account.py:_jwt_claims()` — unverified base64 decode of a JWT
144
+ payload, only to compare `sub`; opaque tokens yield `None` (no comparison).
145
+ - `src/cinna/account.py:_account_result_fields()` — the shared `--json` final
146
+ line of setup / set-token (`workspace`, `platform_url`, `frontend_url`,
147
+ `machine_name`, `context_package: ok|failed|skipped`).
106
148
  - `src/cinna/account.py:_write_account_files()` — mkdir + `save_account_config` +
107
149
  `agents/` + `_write_account_claude_md` + `_write_account_claude_settings` +
108
150
  `_write_account_mcp_config`.
@@ -118,7 +160,15 @@ Related (documented here as integration points): `cinna agent sync` →
118
160
  **client-side** scope to `user_workspace_id` (unless `--all`), and annotate each
119
161
  row with the local checkout from `list_child_workspaces()`.
120
162
  - `src/cinna/account.py:run_account_status()` — `probe_account_token()` +
121
- `_synced_agents_table()` + `_print_token_reauth_hint()`.
163
+ `context_package_status()` + `cli_version_status()`; in JSON mode emits the
164
+ single `result` line (`token`, `active_workspace`, `synced_agents`, `agents[]`,
165
+ `context_package{local,remote,state}`, `cli{installed,required,state}`) and
166
+ returns; otherwise the Rich table + `_synced_agents_table()` +
167
+ `_print_token_reauth_hint()` (which now names `cinna account set-token` next
168
+ to `cinna login`).
169
+ - `src/cinna/cli_version.py:cli_version_status()` — `GET
170
+ {origin}/.well-known/cinna-desktop` → `local_dev.cinna_cli_version`, compared
171
+ with the running `__version__` (`current` / `behind` / `ahead` / `unknown`).
122
172
  - `src/cinna/account.py:probe_account_token()` — cheap `GET /account/agents`;
123
173
  2xx → valid, 401 → expired, else → unreachable.
124
174
  - `src/cinna/account.py:run_agent_sync()` — resolve via `_resolve_account_agent`,
@@ -185,7 +235,14 @@ All consumed by `src/cinna/client.py:AccountClient` with the account token
185
235
  - `POST /cli-setup/account/{token}` — exchange the account setup token
186
236
  (`src/cinna/account.py:_exchange_account_setup_token()`, plain `httpx.post`, not
187
237
  the client). Body: `{machine_name, machine_info}`. Returns `account_token`,
188
- `platform_url`, `frontend_url`, `machine_name`.
238
+ `platform_url`, `frontend_url`, `machine_name`. Used by both `account setup`
239
+ and `account set-token`; the CLI relies on the response being identical for a
240
+ first and a repeat exchange (no `user`/owner field is required — the
241
+ same-account check works from the origin and the token's own `sub`).
242
+ - `GET /.well-known/cinna-desktop` (unauthenticated, platform origin) — the
243
+ desktop discovery document; `local_dev.cinna_cli_version` is the cinna-cli
244
+ pin (`src/cinna/cli_version.py:fetch_required_cli_version()`). Absent block,
245
+ 404 or no network → `unknown`, silently.
189
246
  - `GET /api/v1/cli/account/agents` — accessible agents (`list_account_agents`;
190
247
  also the token probe).
191
248
  - `POST /api/v1/cli/account/agents/{id}/mint` — mint a per-agent child token
@@ -221,7 +278,31 @@ All consumed by `src/cinna/client.py:AccountClient` with the account token
221
278
  - **Guard before burning the setup token** — `run_account_setup` checks the
222
279
  target dir exists-check *before* calling `_exchange_account_setup_token`, so a
223
280
  doomed run never spends the single-use token.
224
- (`tests/test_account.py:test_account_setup_refuses_existing_workspace`)
281
+ (`tests/test_account.py:test_account_setup_refuses_existing_workspace`,
282
+ `tests/test_onboarding.py:test_setup_existing_absolute_dir_is_workspace_exists`)
283
+ - **Absolute `--dir` is used as is, parents created** — `resolve_account_dir()`;
284
+ the `workspace` reported in JSON is the path the caller passed.
285
+ (`tests/test_onboarding.py:test_setup_absolute_dir_used_as_is_and_parents_created`)
286
+ - **`set-token` never rebinds** — the origin / `sub` checks precede the write;
287
+ on mismatch `account.json` is byte-identical afterwards.
288
+ (`tests/test_onboarding.py:test_account_set_token_platform_mismatch_exits_11`,
289
+ `test_account_set_token_subject_mismatch_exits_11`)
290
+ - **`set-token` preserves everything but the token** — active user workspace,
291
+ machine name and child configs survive.
292
+ (`tests/test_onboarding.py:test_account_set_token_swaps_token_in_place`)
293
+ - **Exit codes are mapped centrally** — `src/cinna/main.py:CinnaGroup.invoke()`
294
+ wraps plain `ClickException`s, `httpx.TransportError`s and (in JSON mode)
295
+ unexpected exceptions into `CinnaExit`; `CinnaExit.show()` prints the JSON
296
+ error line instead of `Error: …` when `json_mode` is on.
297
+ (`tests/test_onboarding.py`, the "exit codes" block)
298
+ - **`--json` stdout is JSON only** — `set_json_mode()` swaps the Rich console
299
+ for a quiet one; `spinner()` is a no-op; every stdout line must parse.
300
+ (`tests/test_onboarding.py:test_setup_json_progress_and_result`,
301
+ `test_status_json`)
302
+ - **`--no-input` never blocks** — `_prompt_account_dir()` short-circuits, the
303
+ machine-name prompt takes its default, `console.confirm()` returns the
304
+ default. (`tests/test_onboarding.py:test_no_input_after_subcommand_skips_dir_prompt`,
305
+ `test_group_level_no_input_fails_needs_input`)
225
306
  - **Context refresh is non-destructive** — `_install_context_package(replace=True)`
226
307
  removes `context/` only after a successful download; the orchestrator/child
227
308
  `CLAUDE.md` regeneration is independent of the download.
@@ -252,4 +333,3 @@ All consumed by `src/cinna/client.py:AccountClient` with the account token
252
333
  - **Safe extraction** — the context tarball reuses the path-traversal/absolute/
253
334
  symlink-rejecting extractor.
254
335
  (`tests/test_account.py:test_context_extraction_rejects_malicious_members`)
255
- </content>