cinna-cli 0.2.6__tar.gz → 0.3.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 (98) hide show
  1. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/.gitignore +1 -0
  2. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/PKG-INFO +20 -1
  3. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/README.md +19 -0
  4. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/README.md +5 -0
  5. cinna_cli-0.3.0/docs/features/local_agent_import/local_agent_import.md +181 -0
  6. cinna_cli-0.3.0/docs/features/local_agent_import/local_agent_import_acceptance.md +264 -0
  7. cinna_cli-0.3.0/docs/features/local_agent_import/local_agent_import_tech.md +389 -0
  8. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/pyproject.toml +1 -1
  9. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/client.py +14 -0
  10. cinna_cli-0.3.0/src/cinna/kit_contract.py +857 -0
  11. cinna_cli-0.3.0/src/cinna/local_import.py +1131 -0
  12. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/main.py +60 -0
  13. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/sync.py +20 -1
  14. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/templates/ACCOUNT_CLAUDE.md.template +5 -1
  15. cinna_cli-0.3.0/tests/test_kit_contract.py +433 -0
  16. cinna_cli-0.3.0/tests/test_local_import.py +1238 -0
  17. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_sync.py +42 -0
  18. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/uv.lock +1 -1
  19. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/.claude/commands/cinna-cli.feature.doc.md +0 -0
  20. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/.github/workflows/publish.yml +0 -0
  21. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/LICENSE.md +0 -0
  22. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/account_workspace/account_workspace.md +0 -0
  23. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/account_workspace/account_workspace_acceptance.md +0 -0
  24. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/account_workspace/account_workspace_tech.md +0 -0
  25. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/agent_api/agent_api.md +0 -0
  26. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/agent_api/agent_api_acceptance.md +0 -0
  27. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/agent_api/agent_api_tech.md +0 -0
  28. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/agent_management/agent_management.md +0 -0
  29. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/agent_management/agent_management_acceptance.md +0 -0
  30. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/agent_management/agent_management_tech.md +0 -0
  31. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/agent_schedules/agent_schedules.md +0 -0
  32. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/agent_schedules/agent_schedules_acceptance.md +0 -0
  33. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/agent_schedules/agent_schedules_tech.md +0 -0
  34. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/bootstrap_onboarding/bootstrap_onboarding.md +0 -0
  35. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/bootstrap_onboarding/bootstrap_onboarding_acceptance.md +0 -0
  36. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/bootstrap_onboarding/bootstrap_onboarding_tech.md +0 -0
  37. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/doctor/doctor.md +0 -0
  38. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/doctor/doctor_acceptance.md +0 -0
  39. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/doctor/doctor_tech.md +0 -0
  40. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/git_versioning/git_versioning.md +0 -0
  41. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/git_versioning/git_versioning_acceptance.md +0 -0
  42. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/git_versioning/git_versioning_tech.md +0 -0
  43. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/improvement_requests/improvement_requests.md +0 -0
  44. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/improvement_requests/improvement_requests_acceptance.md +0 -0
  45. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/improvement_requests/improvement_requests_tech.md +0 -0
  46. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/live_sync/live_sync.md +0 -0
  47. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/live_sync/live_sync_acceptance.md +0 -0
  48. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/live_sync/live_sync_tech.md +0 -0
  49. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/mcp_integration/mcp_integration.md +0 -0
  50. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/mcp_integration/mcp_integration_acceptance.md +0 -0
  51. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/mcp_integration/mcp_integration_tech.md +0 -0
  52. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/remote_chat/remote_chat.md +0 -0
  53. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/remote_chat/remote_chat_acceptance.md +0 -0
  54. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/remote_chat/remote_chat_tech.md +0 -0
  55. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/remote_exec/remote_exec.md +0 -0
  56. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/remote_exec/remote_exec_acceptance.md +0 -0
  57. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/features/remote_exec/remote_exec_tech.md +0 -0
  58. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/interface.md +0 -0
  59. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/docs/mutagen_capabilities.md +0 -0
  60. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/scripts/check_docs_references.py +0 -0
  61. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/__init__.py +0 -0
  62. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/account.py +0 -0
  63. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/auth.py +0 -0
  64. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/bootstrap.py +0 -0
  65. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/chat.py +0 -0
  66. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/config.py +0 -0
  67. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/console.py +0 -0
  68. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/context.py +0 -0
  69. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/doctor.py +0 -0
  70. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/errors.py +0 -0
  71. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/git_versioning.py +0 -0
  72. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/improve.py +0 -0
  73. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/logging.py +0 -0
  74. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/mcp_proxy.py +0 -0
  75. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/mutagen_runtime.py +0 -0
  76. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/sync_session.py +0 -0
  77. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/sync_ssh_shim.py +0 -0
  78. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/sync_tui.py +0 -0
  79. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/templates/CHAT_TESTING.md +0 -0
  80. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/templates/CLAUDE.md.template +0 -0
  81. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/templates/GIT_VERSIONING.md +0 -0
  82. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/src/cinna/templates/__init__.py +0 -0
  83. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/__init__.py +0 -0
  84. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/conftest.py +0 -0
  85. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_account.py +0 -0
  86. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_auth.py +0 -0
  87. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_bootstrap.py +0 -0
  88. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_chat.py +0 -0
  89. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_client.py +0 -0
  90. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_config.py +0 -0
  91. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_context.py +0 -0
  92. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_doctor.py +0 -0
  93. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_git_versioning.py +0 -0
  94. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_improve.py +0 -0
  95. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_main.py +0 -0
  96. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_mutagen_runtime.py +0 -0
  97. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_sync_session.py +0 -0
  98. {cinna_cli-0.2.6 → cinna_cli-0.3.0}/tests/test_sync_ssh_shim.py +0 -0
@@ -8,4 +8,5 @@ __pycache__/
8
8
  dist/
9
9
  build/
10
10
  cinna.log
11
+ cinna.log.*
11
12
  .claude/settings.local.json
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cinna-cli
3
- Version: 0.2.6
3
+ Version: 0.3.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
@@ -259,6 +259,25 @@ works exactly as for a manually set-up agent. Synced agents also appear in `cinn
259
259
 
260
260
  Detach a synced workspace: stop its sync session, revoke the minted CLI token server-side (via the account-scoped revoke endpoint, authenticated with the account token; idempotent), then perform the equivalent of `cinna disconnect`: remove `.cinna/`, generated files, and the registry entry. Workspace files under `agents/<slug>/workspace/` are preserved. The revoke degrades gracefully — if it fails (no connection, or a workspace synced before token-id tracking), a warning is printed and the local teardown still completes; the token then expires on its own or can be revoked from the agent's Integrations tab.
261
261
 
262
+ ### `cinna agent import <path> [--name TEXT] [--workspace REF] [--update] [--dry-run] [--no-push] [--yes]`
263
+
264
+ Import an agent that was built **locally** with the [Local Agent Kit](docs/features/local_agent_import/local_agent_import.md) — a folder holding a `cinna-agent.json` manifest, typically `../Local/<slug>` next to this account workspace. Run it from the account workspace root (or any folder inside it).
265
+
266
+ Nine idempotent steps, each printed as `[n/9]`: read and validate the manifest → create (or resolve) the cloud agent → write its description, router trigger, example prompts and the three document prompts in one bulk write, plus the status refresh command → attach a local workspace (`cinna agent sync`) → copy the tree into `agents/<slug>/workspace/` honouring the contract's exclude list and secret rules, generating `workspace_requirements.txt` from `pyproject.toml` when the agent doesn't ship one → `cinna sync push` → create the credential drafts and attach them → create the schedules → record the publication in `publications.json` and print the summary.
267
+
268
+ ```bash
269
+ cinna agent import ../Local/invoice-watcher --dry-run # see the plan, touch nothing
270
+ cinna agent import ../Local/invoice-watcher --yes
271
+ cinna chat --agent invoice-watcher "check invoices from last week"
272
+
273
+ # after more local work:
274
+ cinna agent import ../Local/invoice-watcher --update --yes
275
+ ```
276
+
277
+ The folder rules come from the kit's versioned contract, `.cinna-kit/layout.json` — the same file Cinna Desktop reads — so a correction published there reaches this command on the next kit refresh rather than waiting for a new cinna-cli. `credentials/` and `app-data/` are **never** copied, not even if the contract drops them, and on top of the exclude list the contract's `secret_files` rules withhold every dotenv shape (`.env`, `.env.prod`, `staging.env`) wherever it sits, while `.env.example` still travels. No secret value is ever read, sent, or printed: credentials are created as empty drafts and the command prints the URLs the user opens to fill them in the browser.
278
+
279
+ Where the folder has been published is recorded in `publications.json`, a **sibling** of `cinna-agent.json` holding one entry per Cinna instance — it cannot live inside the manifest, because each entry records a hash of the exported tree and the manifest is part of that tree. Every step is idempotent (agent by the entry whose `platform_url` matches the instance you are logged into, credentials by name, schedules by name), so a partial import is resumed with `--update` instead of duplicating anything. The publication is recorded **only after the push settled** — `--no-push`, a failed flush, or remaining conflicts leave it unrecorded, and the next run is a plain `--update`. A legacy `cloud` block is migrated into the ledger on the first write and is still read until then, so an older folder's `--update` never creates a second agent. `--dry-run` makes no platform call and writes nothing.
280
+
262
281
  ### `cinna connect agent-api --producer <agent> --consumer <agent> [--label TEXT] [--read-only]`
263
282
 
264
283
  Wire one agent to another's REST API from the account workspace. Resolves both agents (name, slug, or ID), mints a producer API token, and attaches it to the consumer as a credential — which rides the consumer's normal credential sync into its remote environment, so no key ever touches your machine. Prints the credential ID, token prefix, base URL, and spec URL. `--read-only` restricts the consumer to read-only access; `--label` names the credential. Backend errors surface verbatim: 400 if the producer's REST API is disabled, 403/404 for ownership violations.
@@ -222,6 +222,25 @@ works exactly as for a manually set-up agent. Synced agents also appear in `cinn
222
222
 
223
223
  Detach a synced workspace: stop its sync session, revoke the minted CLI token server-side (via the account-scoped revoke endpoint, authenticated with the account token; idempotent), then perform the equivalent of `cinna disconnect`: remove `.cinna/`, generated files, and the registry entry. Workspace files under `agents/<slug>/workspace/` are preserved. The revoke degrades gracefully — if it fails (no connection, or a workspace synced before token-id tracking), a warning is printed and the local teardown still completes; the token then expires on its own or can be revoked from the agent's Integrations tab.
224
224
 
225
+ ### `cinna agent import <path> [--name TEXT] [--workspace REF] [--update] [--dry-run] [--no-push] [--yes]`
226
+
227
+ Import an agent that was built **locally** with the [Local Agent Kit](docs/features/local_agent_import/local_agent_import.md) — a folder holding a `cinna-agent.json` manifest, typically `../Local/<slug>` next to this account workspace. Run it from the account workspace root (or any folder inside it).
228
+
229
+ Nine idempotent steps, each printed as `[n/9]`: read and validate the manifest → create (or resolve) the cloud agent → write its description, router trigger, example prompts and the three document prompts in one bulk write, plus the status refresh command → attach a local workspace (`cinna agent sync`) → copy the tree into `agents/<slug>/workspace/` honouring the contract's exclude list and secret rules, generating `workspace_requirements.txt` from `pyproject.toml` when the agent doesn't ship one → `cinna sync push` → create the credential drafts and attach them → create the schedules → record the publication in `publications.json` and print the summary.
230
+
231
+ ```bash
232
+ cinna agent import ../Local/invoice-watcher --dry-run # see the plan, touch nothing
233
+ cinna agent import ../Local/invoice-watcher --yes
234
+ cinna chat --agent invoice-watcher "check invoices from last week"
235
+
236
+ # after more local work:
237
+ cinna agent import ../Local/invoice-watcher --update --yes
238
+ ```
239
+
240
+ The folder rules come from the kit's versioned contract, `.cinna-kit/layout.json` — the same file Cinna Desktop reads — so a correction published there reaches this command on the next kit refresh rather than waiting for a new cinna-cli. `credentials/` and `app-data/` are **never** copied, not even if the contract drops them, and on top of the exclude list the contract's `secret_files` rules withhold every dotenv shape (`.env`, `.env.prod`, `staging.env`) wherever it sits, while `.env.example` still travels. No secret value is ever read, sent, or printed: credentials are created as empty drafts and the command prints the URLs the user opens to fill them in the browser.
241
+
242
+ Where the folder has been published is recorded in `publications.json`, a **sibling** of `cinna-agent.json` holding one entry per Cinna instance — it cannot live inside the manifest, because each entry records a hash of the exported tree and the manifest is part of that tree. Every step is idempotent (agent by the entry whose `platform_url` matches the instance you are logged into, credentials by name, schedules by name), so a partial import is resumed with `--update` instead of duplicating anything. The publication is recorded **only after the push settled** — `--no-push`, a failed flush, or remaining conflicts leave it unrecorded, and the next run is a plain `--update`. A legacy `cloud` block is migrated into the ledger on the first write and is still read until then, so an older folder's `--update` never creates a second agent. `--dry-run` makes no platform call and writes nothing.
243
+
225
244
  ### `cinna connect agent-api --producer <agent> --consumer <agent> [--label TEXT] [--read-only]`
226
245
 
227
246
  Wire one agent to another's REST API from the account workspace. Resolves both agents (name, slug, or ID), mints a producer API token, and attaches it to the consumer as a credential — which rides the consumer's normal credential sync into its remote environment, so no key ever touches your machine. Prints the credential ID, token prefix, base URL, and spec URL. `--read-only` restricts the consumer to read-only access; `--label` names the credential. Backend errors surface verbatim: 400 if the producer's REST API is disabled, 403/404 for ownership violations.
@@ -201,6 +201,10 @@ main.py (CLI commands — Click)
201
201
  ├── doctor.py — `cinna doctor`: reconcile registry ↔ Mutagen, delete stalled / terminate active sessions, refresh tokens
202
202
  ├── chat.py — `cinna chat`: session-backed conversation testing (poll + NDJSON) over the api-proxy
203
203
  ├── improve.py — `cinna improve`: improvement requests users shared about your agents (list/show/download/status)
204
+ ├── local_import.py — `cinna agent import`: the Local Agent Kit go-cloud step
205
+ ├── kit_contract.py — the Local Agent Kit contract as data (`.cinna-kit/layout.json`):
206
+ │ exclude patterns, secret-file rules, export walk, content_hash,
207
+ │ publications.json ledger, contract-version gate
204
208
  ├── config.py — .cinna/config.json: load/save/find
205
209
  ├── auth.py — JWT storage, Authorization headers
206
210
  ├── client.py — PlatformClient: HTTP + SSE stream_exec
@@ -289,6 +293,7 @@ authoring convention.
289
293
  | **MCP integration** | `cinna connect mcp` · `cinna mcp-proxy` (knowledge stdio server) | [business](features/mcp_integration/mcp_integration.md) · [tech](features/mcp_integration/mcp_integration_tech.md) · [acceptance](features/mcp_integration/mcp_integration_acceptance.md) |
290
294
  | **Git versioning** | `cinna git` (link, status, commit, push, pull, log, checkout, unlink) | [business](features/git_versioning/git_versioning.md) · [tech](features/git_versioning/git_versioning_tech.md) · [acceptance](features/git_versioning/git_versioning_acceptance.md) |
291
295
  | **Improvement requests** | `cinna improve` (list, show, download, status) | [business](features/improvement_requests/improvement_requests.md) · [tech](features/improvement_requests/improvement_requests_tech.md) · [acceptance](features/improvement_requests/improvement_requests_acceptance.md) |
296
+ | **Local agent import** | `cinna agent import` (Local Agent Kit → cloud agent), incl. the versioned folder contract (`.cinna-kit/layout.json`), `publications.json` and `content_hash` | [business](features/local_agent_import/local_agent_import.md) · [tech](features/local_agent_import/local_agent_import_tech.md) · [acceptance](features/local_agent_import/local_agent_import_acceptance.md) |
292
297
 
293
298
  The sections below (Git Versioning, Sync Transport, Remote Exec, Remote Chat,
294
299
  Bootstrap Flow) remain as in-README quick references and backend contracts; the
@@ -0,0 +1,181 @@
1
+ # Local Agent Import (`cinna agent import`)
2
+
3
+ ## Purpose
4
+
5
+ Take an agent that was built **entirely offline** — with any coding assistant,
6
+ by a user who did not yet have a Cinna account — and put it on the platform in
7
+ one verb: the cloud agent is created, its prompts and metadata are written, a
8
+ local workspace is attached, the files are copied and pushed, credential drafts
9
+ and schedules are created, and the local manifest is stamped with the cloud
10
+ link.
11
+
12
+ This is the "go-cloud" step of the **Local Agent Kit**: the platform serves a
13
+ public, unauthenticated kit (`GET /agent-start`) that teaches an assistant how to
14
+ scaffold `~/Documents/MyAgents/Local/<slug>/` — a folder whose layout is
15
+ byte-compatible with a cloud agent workspace and whose `cinna-agent.json`
16
+ manifest carries the definitional metadata a bundle revision carries. `cinna
17
+ agent import` is the only CLI verb that reads that manifest.
18
+
19
+ The kit's machine-readable half is a **versioned contract** (`layout.json`,
20
+ `CONTRACT_VERSION`, the JSON schemas), served separately at
21
+ `GET /api/agent-start/contract.tar.gz` with a cheap version poll at
22
+ `GET /api/agent-start/contract/version`. cinna-cli reads the copy installed at
23
+ `.cinna-kit/`; a shipped build should obtain it from those routes rather than
24
+ vendoring one.
25
+
26
+ Nothing platform-side is new: the import replays the manifest through the
27
+ existing account-scoped verbs (`agent create`, the bulk prompt write, `agent
28
+ sync`, `sync push`, `account credentials create`, `agent schedule create`,
29
+ `agent status set-command`).
30
+
31
+ ## Mental model
32
+
33
+ - **The manifest is the plan.** `cinna-agent.json` (at the agent folder root)
34
+ says what the agent *is*: name, slug, description, example prompts, router
35
+ trigger, which files hold the three prompts, the status refresh command, the
36
+ credentials it needs, and its schedules. The import writes exactly that and
37
+ invents nothing.
38
+ - **Two folders, one direction.** `Local/<slug>/` is where the agent was built;
39
+ `Cloud/` is the account workspace (`cinna login <host> --dir Cloud`). Import
40
+ runs **from `Cloud/`** and pushes `Local/<slug>/` into
41
+ `Cloud/agents/<slug>/workspace/`. After a successful import the cloud copy is
42
+ the live one — keep iterating there with `cinna dev`, or keep experimenting
43
+ locally and re-import with `--update`.
44
+ - **The contract is the authority.** The folder rules — what may be copied to
45
+ the cloud, what can hold a credential value — are **data**, not prose:
46
+ `.cinna-kit/layout.json`, published by cinna-core and read by three hosts
47
+ (Cinna Desktop, a coding assistant's kit tools, and this CLI). Import reads
48
+ them from that file rather than re-deriving them, so a correction cinna-core
49
+ publishes reaches this tool on the next kit refresh instead of waiting for a
50
+ new cinna-cli. When the contract cannot be found the run falls back to a
51
+ built-in copy and says so as a **degradation**, not as a mode.
52
+ - **Secrets never travel.** `credentials/` (which holds the local `.env`) and
53
+ `app-data/` (runtime state) are never copied — not even if the contract is
54
+ edited to allow them. On top of the exclude list the contract carries rules a
55
+ glob list cannot express: every dotenv shape (`.env`, `.env.prod`,
56
+ `staging.env`) is withheld wherever it sits, while `.env.example`,
57
+ `.env.sample` and `.env.template` still travel. Credentials are created as
58
+ **empty drafts** and the command prints the URLs the user opens to fill each
59
+ one in the browser. No secret value is read, sent, or printed at any point.
60
+ - **The ledger is a sibling file, never a manifest key.** Where an agent folder
61
+ has been published is recorded in `publications.json` beside
62
+ `cinna-agent.json` — one entry per Cinna instance. It has to be a separate
63
+ file: each entry records a hash *of the exported tree*, the manifest is a
64
+ member of that tree, so a hash written into the manifest could never match the
65
+ tree it describes. The folder would read "1 unpublished change" the instant a
66
+ publish succeeded, and republishing to clear it would create the next
67
+ mismatch.
68
+ - **Every step is idempotent.** The agent is matched by the ledger entry for
69
+ this instance, credentials by name, schedules by name. A run that dies halfway
70
+ is resumed with `--update`; nothing is duplicated.
71
+ - **The record is the commit point.** `publications.json` is written **only
72
+ after the workspace push settled**. If the push failed, was skipped
73
+ (`--no-push`), or left conflicts, nothing is recorded and the re-run is a
74
+ plain `--update`.
75
+
76
+ ## The nine steps
77
+
78
+ Each prints as `[n/9]`:
79
+
80
+ 1. **Manifest** — load and validate `cinna-agent.json`; refuse a
81
+ `schema_version` newer than this CLI understands, a slug that disagrees with
82
+ the folder name, a malformed cron, a credential without a type. Then the
83
+ contract: refuse a folder built against a newer **major** contract version,
84
+ warn on an older one or on a folder that records none, resolve the exclude
85
+ list and the secret rules, plan the copy and compute the tree's
86
+ `content_hash`.
87
+ 2. **Agent** — resolve by the `publications.json` entry for this instance
88
+ (requires `--update`) or create a new agent in the active user workspace
89
+ (`--workspace` overrides).
90
+ 3. **Prompts + metadata** — one bulk write carrying `description`,
91
+ `router_trigger_prompt`, `example_prompts`, and the three document prompts
92
+ read from the files named in `prompts`; then the status refresh command.
93
+ 4. **Workspace** — reuse the synced workspace under `agents/<slug>/` if the
94
+ agent already has one, otherwise run `cinna agent sync`.
95
+ 5. **Copy** — copy the tree into `agents/<slug>/workspace/`, honouring the
96
+ contract's exclude list and secret rules, and generate
97
+ `workspace_requirements.txt` from `pyproject.toml` when the agent does not
98
+ ship one.
99
+ 6. **Push** — `cinna sync push` equivalent (skipped by `--no-push`).
100
+ 7. **Credentials** — one empty draft per spec, attached to the agent; an
101
+ existing credential of the same name is attached rather than recreated.
102
+ Setup URLs are collected for the summary.
103
+ 8. **Schedules** — one per spec, name-idempotent; `--update` rewrites an
104
+ existing schedule in place.
105
+ 9. **Record** — add or update this instance's entry in `publications.json`
106
+ (`platform_url`, `agent_id`, `workspace`, `imported_at`, `updated_at`,
107
+ `contract_version`, `content_hash`) and print the summary (agent id, web UI
108
+ link, workspace path, credential setup URLs, and the `cinna chat`
109
+ verification line).
110
+
111
+ ## User flows
112
+
113
+ ### First import
114
+
115
+ 1. In the kit root: `cinna login <platform> --dir Cloud` (once).
116
+ 2. `python3 .cinna-kit/tools/kit.py validate Local/<slug>` — must pass.
117
+ 3. `cd Cloud && cinna agent import ../Local/<slug>`.
118
+ 4. Confirm the plan (or pass `--yes`), then open the printed credential setup
119
+ URLs and fill the secrets in the browser.
120
+ 5. Verify: `cinna chat --agent <slug> "<first example prompt>"`.
121
+
122
+ ### Look before you leap
123
+
124
+ `cinna agent import ../Local/<slug> --dry-run` prints the whole plan — every
125
+ file that would be copied, every credential draft, every schedule — and makes
126
+ **zero** platform calls and zero local writes.
127
+
128
+ ### Re-import after more local work
129
+
130
+ `cinna agent import ../Local/<slug> --update` — resolves the agent by the
131
+ `publications.json` entry whose `platform_url` matches the instance you are
132
+ logged into (not by workspace, and not by position in the list), rewrites
133
+ prompts and metadata, re-copies the tree, pushes, attaches existing credentials,
134
+ and updates the schedules in place.
135
+
136
+ ### Resume a partial import
137
+
138
+ If the run died before the record was written, `--update` reattaches by a unique
139
+ name match (and refuses when several agents share the name — add the entry to
140
+ `publications.json` manually to disambiguate). Credentials and schedules already
141
+ created are reused, not duplicated.
142
+
143
+ ## Options
144
+
145
+ | Flag | Effect |
146
+ |------|--------|
147
+ | `--name TEXT` | Override the manifest's display name for the *created* agent |
148
+ | `--workspace REF` | Target user workspace (name or id; `default` for the Default one) for the agent and its credential drafts |
149
+ | `--update` | Re-import into the agent the manifest points at (required for any second run) |
150
+ | `--dry-run` | Print the plan; no platform call, no local write |
151
+ | `--no-push` | Copy the files but skip the sync push (leaves the manifest unstamped) |
152
+ | `--yes` / `-y` | Skip the confirmation prompt |
153
+
154
+ ## Failure modes and what they mean
155
+
156
+ | Situation | Behaviour |
157
+ |-----------|-----------|
158
+ | Not in an account workspace | The standard "not in a cinna account workspace" error — run `cinna login <host> --dir Cloud` first |
159
+ | `cinna-agent.json` missing | Refused, with the hint that the path must be the agent folder |
160
+ | `schema_version` newer than supported | Refused with an upgrade hint — never half-read |
161
+ | Folder built against a newer **major** contract version | Refused with an upgrade hint — a minor or patch difference is not a compatibility question and passes silently |
162
+ | Folder records no `contract_version` | Warned and imported — every folder created before contract 1.0.0 is in that state |
163
+ | Already imported to this instance, no `--update` | Refused, naming the agent id and where the link was recorded |
164
+ | The recorded agent id is not among your agents | Refused — you are logged into a different platform, or the agent was deleted |
165
+ | `--update` with several name matches | Refused, listing them; add the `publications.json` entry to disambiguate |
166
+ | Push failed or left conflicts | Warned, **nothing recorded**, with the resolve + `--update` hint |
167
+ | `.cinna-kit/layout.json` missing or unreadable | Imported with the built-in contract copy, the `Exclusions:` line marked `DEGRADED`, and **no `content_hash` recorded** — a hash over a file set another host would not select is worse than none |
168
+ | A travelling file or directory cannot be read | Refused — a complete-looking export with a subtree missing from it is the one outcome worth stopping for |
169
+ | `publications.json` unreadable | Refused, and the file is left exactly as it was |
170
+ | Credential/schedule 403 | The platform error surfaces verbatim; earlier steps stay done and `--update` resumes |
171
+ | A file under `credentials/` or `app-data/`, or any dotenv shape, reaches the copy plan | Hard refusal — the exclude list or the secret rules are broken, and that is a bug to report, not to work around |
172
+
173
+ ## Related
174
+
175
+ - [Agent Management](../agent_management/agent_management.md) — `agent create`,
176
+ `agent sync`, `agent show`, `agent status set-command`.
177
+ - [Agent Schedules](../agent_schedules/agent_schedules.md) — the schedule verbs
178
+ the import replays.
179
+ - [Account Workspace](../account_workspace/account_workspace.md) — the `Cloud/`
180
+ workspace, credential drafts, and the bulk prompt write.
181
+ - [Live Sync](../live_sync/live_sync.md) — what `sync push` actually does.
@@ -0,0 +1,264 @@
1
+ # Local Agent Import — Acceptance Scenarios (live e2e)
2
+
3
+ Real-usage scenarios for an agent doing *integration* testing of `cinna agent
4
+ import` against a **live** platform: a real backend, a real account workspace,
5
+ and a real local agent folder. These are not unit tests — they exercise agent
6
+ creation, the bulk prompt write through the escape hatch, child-token minting,
7
+ Mutagen sync, credential drafting, and schedule CRUD end to end.
8
+
9
+ How to use: pick the scenarios relevant to the change, run the **Steps**
10
+ verbatim, assert the **Expected**, and watch for the **Watch for** failure
11
+ modes. Scenarios 3–6 (idempotency and the stamp) are the highest-value ones for
12
+ any change to the orchestrator.
13
+
14
+ ## Preconditions
15
+
16
+ - A reachable platform with an **account workspace**: `Cloud/.cinna/account.json`
17
+ (created by `cinna login <platform> --dir Cloud`). Every command below runs
18
+ from inside `Cloud/`.
19
+ - The account's user can create agents (`agent-developer` role) — otherwise
20
+ step 2 returns 403.
21
+ - **Editable install** of the CLI under test:
22
+ `python3 -c "import cinna,os;print(os.path.dirname(cinna.__file__))"` points at
23
+ this repo's `src/cinna`; `cinna agent import --help` lists all six flags.
24
+ - Mutagen installed (the import pushes through a real sync session).
25
+ - A **local agent folder** at `../Local/<slug>/` holding a valid
26
+ `cinna-agent.json` — scaffold one with the kit
27
+ (`python3 .cinna-kit/tools/kit.py new <slug>`), fill the three prompt files,
28
+ declare one credential and one schedule, and put a throwaway secret in
29
+ `credentials/.env` so the secret-leak assertions are meaningful.
30
+
31
+ ## Scenario catalog
32
+
33
+ ### 1. Dry run shows the plan and touches nothing
34
+
35
+ - **Goal:** the user can inspect the import before committing to it.
36
+ - **Steps:**
37
+ ```
38
+ cinna agent import ../Local/<slug> --dry-run
39
+ ```
40
+ - **Expected:** the file list, credential drafts, and schedules are printed;
41
+ "Dry run — nothing was sent to the platform."; `cinna account agents` shows no
42
+ new agent; `../Local/<slug>/cinna-agent.json` still has `cloud.agent_id: null`.
43
+ - **Watch for:** any HTTP request in `-v` output; a workspace appearing under
44
+ `agents/`.
45
+
46
+ ### 2. First import, end to end
47
+
48
+ - **Steps:**
49
+ ```
50
+ cinna agent import ../Local/<slug> --yes
51
+ ```
52
+ - **Expected:** nine `[n/9]` steps; the agent exists in the UI with the
53
+ manifest's description, example prompts, and router trigger (`cinna agent show
54
+ <slug> --prompts` matches the three local prompt files); `agents/<slug>/`
55
+ exists and `agents/<slug>/workspace/` holds the agent's files;
56
+ `cinna exec --agent <slug> ls` on the remote env lists the same files; the
57
+ credential appears as **needs setup** with the printed setup URL; the schedule
58
+ is listed by `cinna agent schedule list <slug>`; the status refresh command is
59
+ set (`cinna agent status show <slug>`); the manifest's `cloud` block now
60
+ carries `platform_url`, `agent_id`, `imported_at`.
61
+ - **Watch for:** `credentials/` or `app-data/` reaching the remote env (check
62
+ with `cinna exec --agent <slug> ls -a`); any secret value in the terminal
63
+ output; `workspace_requirements.txt` missing when the agent has a
64
+ `pyproject.toml`.
65
+
66
+ ### 3. Second import without `--update` is refused
67
+
68
+ - **Steps:** re-run scenario 2's command.
69
+ - **Expected:** exit ≠ 0, the error names the agent id and tells you to pass
70
+ `--update`; nothing changed on the platform.
71
+ - **Watch for:** a **second** agent with the same name appearing.
72
+
73
+ ### 4. `--update` re-imports without duplicating
74
+
75
+ - **Steps:** edit `docs/WORKFLOW_PROMPT.md` and a script locally, then <!-- nocheck: path inside the local agent folder, not this repo -->
76
+ ```
77
+ cinna agent import ../Local/<slug> --update --yes
78
+ ```
79
+ - **Expected:** the same agent id; `cinna agent show <slug> --prompts` shows the
80
+ edited workflow prompt; the edited script is live in the env; **no** second
81
+ credential (the existing one is re-attached) and **no** second schedule (it is
82
+ updated in place); `cinna account credentials list` count unchanged.
83
+ - **Watch for:** a duplicate empty credential draft shadowing the one the user
84
+ already filled — that would break the running agent.
85
+
86
+ ### 5. The stamp only happens after a successful push
87
+
88
+ - **Steps:**
89
+ ```
90
+ cinna agent import ../Local/<slug2> --no-push --yes
91
+ ```
92
+ - **Expected:** files are copied into `agents/<slug2>/workspace/`, the manifest
93
+ is **not** stamped, and the output tells you to `cinna sync push --agent
94
+ <slug2>` and re-run with `--update`.
95
+ - **Watch for:** a stamped manifest pointing at an agent whose environment never
96
+ received the files.
97
+
98
+ ### 6. Resume after a mid-run failure
99
+
100
+ - **Goal:** a partial import is recoverable.
101
+ - **Steps:** cause step 7 to fail (e.g. an unreachable platform, or a credential
102
+ type the account cannot create), then fix it and re-run with `--update`.
103
+ - **Expected:** the second run reuses the agent (by name when the manifest is
104
+ unstamped, by id when stamped), skips what already exists, and completes.
105
+ - **Watch for:** a second agent created because the name match was ambiguous —
106
+ the command must **refuse** with a "set cloud.agent_id" hint, not guess.
107
+
108
+ ### 7. Targeting a user workspace
109
+
110
+ - **Steps:**
111
+ ```
112
+ cinna account user-workspace list
113
+ cinna agent import ../Local/<slug3> --workspace "<Workspace Name>" --yes
114
+ ```
115
+ - **Expected:** the agent and its credential drafts land in that workspace
116
+ (visible in the UI sidebar and in `cinna account agents`).
117
+ - **Watch for:** the agent in the target workspace but the credential in Default
118
+ — the agent then cannot see its own credential.
119
+
120
+ ### 8. Manifest guards
121
+
122
+ - **Steps:** in a scratch copy of the agent folder, one at a time: bump
123
+ `schema_version` to `2`; rename the folder so it disagrees with `slug`; make
124
+ `cron_string` `"every morning"`; drop a credential's `type`.
125
+ - **Expected:** each is refused *before* any platform call, with a message that
126
+ names the offending field.
127
+ - **Watch for:** a half-applied import (agent created, then the manifest
128
+ rejected).
129
+
130
+ ### 9. Secrets never leave the machine
131
+
132
+ - **Steps:** with a recognizable secret in `credentials/.env`, run a full import
133
+ with `-v`, then
134
+ ```
135
+ cinna exec --agent <slug> "grep -r '<secret>' . | head"
136
+ ```
137
+ - **Expected:** no match remotely; the secret appears nowhere in the CLI output
138
+ or logs; `credentials/` does not exist in the remote workspace.
139
+ - **Watch for:** a `.env` copied because it sat outside `credentials/` — the
140
+ import must refuse the plan outright in that case. Scenario 10b covers the
141
+ `.env.<suffix>` shapes that used to clear every gate.
142
+
143
+ ### 10. The contract's exclude list is honoured, and it is `layout.json`
144
+
145
+ - **Goal:** a correction cinna-core publishes reaches this tool on the next kit
146
+ refresh instead of waiting for a new cinna-cli release.
147
+ - **Setup:** a `.cinna-kit/layout.json` above the agent, and a `notes/` folder
148
+ inside it.
149
+ - **Steps:** give `layout.json` a `cloud_import_excludes` list that adds
150
+ `notes/` **and** omits `credentials/`; import into a fresh agent. Then edit
151
+ the list on disk (add `config/`) and re-run with `--update`.
152
+ - **Expected:** `notes/` is absent remotely; `credentials/` is *still* absent
153
+ (the mandatory exclusion cannot be removed); the `Exclusions:` line names the
154
+ `layout.json` path it read and does **not** say `DEGRADED`; the second run
155
+ drops `config/` without any change to cinna-cli.
156
+ - **Watch for:** the built-in list being used silently when a `layout.json`
157
+ exists; and a `kit.json` `cloud_import.exclude` still being honoured —
158
+ decision D6 deleted that key and reading it back would be a second authority
159
+ for one list.
160
+
161
+ ### 10a. A missing contract is reported as a degradation
162
+
163
+ - **Goal:** the failure that made the previous regression invisible. The old
164
+ line read `Exclusions: built-in default list` — it announced a *mode*, so
165
+ nobody saw an error and the first symptom was a `.env.prod` in a cloud
166
+ workspace.
167
+ - **Steps:** import an agent with **no** `.cinna-kit/` anywhere above it.
168
+ - **Expected:** the run succeeds; the `Exclusions:` line begins `DEGRADED —`
169
+ and says why; **no `content_hash` is recorded** in `publications.json` and the
170
+ run prints `Content hash: WITHHELD`.
171
+ - **Watch for:** a hash recorded anyway. "But the fallback is the same list" is
172
+ a claim about this build's contract, not about the one in the folder — which
173
+ is the only one the other host is reading.
174
+
175
+ ### 10b. Dotenv suffixes never travel, examples still do
176
+
177
+ - **Goal:** the leak the contract's `secret_files` rules close. `.env.prod` and
178
+ `.env.local` clear every glob in the exclude list *and* the old
179
+ `endswith(".env")` check, at the agent root and at any depth.
180
+ - **Setup:** in the agent folder, outside `credentials/`, create `.env.prod`,
181
+ `config/.env.staging`, `staging.env` and `.env.example`, each with a
182
+ recognizable secret except the example.
183
+ - **Steps:**
184
+ ```
185
+ cinna agent import ../Local/<slug> --dry-run
186
+ cinna agent import ../Local/<slug> --update -y
187
+ cinna exec --agent <slug> "ls -a; ls -a config 2>/dev/null"
188
+ ```
189
+ - **Expected:** the dry-run plan lists `.env.example` and none of the others;
190
+ remotely only `.env.example` exists; the secret string appears nowhere in the
191
+ output.
192
+ - **Watch for:** `.env.example` being withheld too — that is the `unless` block
193
+ doing its job, and a fix written as a bare `startswith(".env.")` loses it.
194
+
195
+ ### 10c. A withheld secret does not move the `content_hash`
196
+
197
+ - **Goal:** "unpublished changes forever" — a path withheld from the upload but
198
+ counted in the hash makes the hash move for a change that can never be
199
+ published.
200
+ - **Steps:** import once and note `publications.json`'s `content_hash`. Edit
201
+ `.env.prod` (a withheld file) and re-run `--update`; then edit
202
+ the agent's own `docs/WORKFLOW_PROMPT.md` (a travelling file) and re-run <!-- nocheck: a path inside the agent folder, not this repo -->
203
+ again.
204
+ - **Expected:** the hash is unchanged after the first edit and different after
205
+ the second. Immediately after any successful import, the recorded hash matches
206
+ a fresh scan of the folder — the folder is never "behind" the instant a
207
+ publish succeeds.
208
+ - **Watch for:** a hash that moves on the very first re-run with no edits at
209
+ all. That means the manifest was written *after* the hash was computed.
210
+
211
+ ### 10d. The ledger is a sibling file, and the legacy `cloud` block migrates
212
+
213
+ - **Goal:** `publications.json` is written, `cloud` is retired behind it, and no
214
+ `--update` ever creates a second agent.
215
+ - **Setup:** an agent folder carrying a legacy `cloud` block naming a real
216
+ `platform_url` and `agent_id`, and no `publications.json`.
217
+ - **Steps:** run `--update` against that instance, then inspect both files, then
218
+ run `--update` once more.
219
+ - **Expected:** the first run resolves the agent from the `cloud` block and does
220
+ **not** create a new one; afterwards `cinna-agent.json` has no `cloud` key,
221
+ `publications.json` holds an entry for that `platform_url`, and
222
+ `publications.json` is **not** present in the remote workspace. The second run
223
+ resolves from the ledger and is a no-op on identity.
224
+ - **Watch for:** a second agent appearing on the platform (the deprecated read
225
+ was dropped too early); a `cloud` block deleted while the ledger recorded
226
+ nothing (a destination must be reached before the source may be removed); a
227
+ `publications.json` that travelled.
228
+
229
+ ### 10e. Two instances, resolved by `platform_url`
230
+
231
+ - **Goal:** the resolution key is part of the contract — not `workspace`, not
232
+ position in the array.
233
+ - **Setup:** two account workspaces for two different platform hosts.
234
+ - **Steps:** import the same agent folder into instance A, then into instance B,
235
+ then re-run `--update` against A.
236
+ - **Expected:** `publications.json` holds two entries; the A re-run updates A's
237
+ agent and leaves B's entry byte-identical; an entry whose `workspace` key is
238
+ absent entirely still resolves (that is the shape Cinna Desktop writes).
239
+ - **Watch for:** the second import being refused as "already imported", or the
240
+ A re-run updating B's entry.
241
+
242
+ ### 10f. The `contract_version` gate
243
+
244
+ - **Steps:** set the manifest's `contract_version` to `2.0.0` and import; then
245
+ to `1.4.2` and import; then remove the key entirely and import.
246
+ - **Expected:** `2.0.0` is refused before any platform call, naming `2.x`;
247
+ `1.4.2` passes **silently** (same major — a minor contract change is additive
248
+ by definition and reaches a non-adopting reader as silence); the missing key
249
+ warns about a re-stamp and imports anyway.
250
+ - **Watch for:** a warning on the same-major pair. That is the gate's shape, not
251
+ a bug: anything an old reader must notice needs a major bump.
252
+
253
+ ### 11. Verification loop the guide promises
254
+
255
+ - **Steps:**
256
+ ```
257
+ cinna chat --agent <slug> "<first example prompt>"
258
+ ```
259
+ - **Expected:** the agent answers using its imported workflow prompt and
260
+ scripts. With the credential still empty, it should say what it needs rather
261
+ than crash.
262
+ - **Watch for:** prompts that landed in the DB but not in the env — if the env
263
+ was already running, `cinna api POST agents/<id>/sync-prompts` is the manual
264
+ push.