cinna-cli 0.4.3__tar.gz → 0.5.1__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 (112) hide show
  1. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/PKG-INFO +3 -1
  2. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/README.md +2 -0
  3. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/delegation/delegation.md +5 -3
  4. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/delegation/delegation_tech.md +3 -2
  5. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/local_agent_import/local_agent_import.md +42 -6
  6. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/local_agent_import/local_agent_import_acceptance.md +45 -3
  7. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/local_agent_import/local_agent_import_tech.md +103 -6
  8. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/pyproject.toml +1 -1
  9. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/client.py +9 -0
  10. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/delegation.py +11 -5
  11. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/kit_contract.py +7 -2
  12. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/local_import.py +111 -1
  13. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_client.py +15 -0
  14. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_delegation.py +10 -0
  15. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_local_import.py +327 -5
  16. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/uv.lock +1 -1
  17. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/.claude/commands/cinna-cli.feature.doc.md +0 -0
  18. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/.github/workflows/publish.yml +0 -0
  19. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/.gitignore +0 -0
  20. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/LICENSE.md +0 -0
  21. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/README.md +0 -0
  22. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/account_workspace/account_workspace.md +0 -0
  23. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/account_workspace/account_workspace_acceptance.md +0 -0
  24. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/account_workspace/account_workspace_tech.md +0 -0
  25. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/agent_addons/agent_addons.md +0 -0
  26. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/agent_addons/agent_addons_acceptance.md +0 -0
  27. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/agent_addons/agent_addons_tech.md +0 -0
  28. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/agent_api/agent_api.md +0 -0
  29. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/agent_api/agent_api_acceptance.md +0 -0
  30. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/agent_api/agent_api_tech.md +0 -0
  31. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/agent_management/agent_management.md +0 -0
  32. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/agent_management/agent_management_acceptance.md +0 -0
  33. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/agent_management/agent_management_tech.md +0 -0
  34. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/agent_schedules/agent_schedules.md +0 -0
  35. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/agent_schedules/agent_schedules_acceptance.md +0 -0
  36. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/agent_schedules/agent_schedules_tech.md +0 -0
  37. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/bootstrap_onboarding/bootstrap_onboarding.md +0 -0
  38. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/bootstrap_onboarding/bootstrap_onboarding_acceptance.md +0 -0
  39. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/bootstrap_onboarding/bootstrap_onboarding_tech.md +0 -0
  40. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/delegation/delegation_acceptance.md +0 -0
  41. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/doctor/doctor.md +0 -0
  42. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/doctor/doctor_acceptance.md +0 -0
  43. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/doctor/doctor_tech.md +0 -0
  44. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/git_versioning/git_versioning.md +0 -0
  45. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/git_versioning/git_versioning_acceptance.md +0 -0
  46. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/git_versioning/git_versioning_tech.md +0 -0
  47. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/improvement_requests/improvement_requests.md +0 -0
  48. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/improvement_requests/improvement_requests_acceptance.md +0 -0
  49. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/improvement_requests/improvement_requests_tech.md +0 -0
  50. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/live_sync/live_sync.md +0 -0
  51. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/live_sync/live_sync_acceptance.md +0 -0
  52. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/live_sync/live_sync_tech.md +0 -0
  53. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/mcp_integration/mcp_integration.md +0 -0
  54. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/mcp_integration/mcp_integration_acceptance.md +0 -0
  55. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/mcp_integration/mcp_integration_tech.md +0 -0
  56. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/remote_chat/remote_chat.md +0 -0
  57. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/remote_chat/remote_chat_acceptance.md +0 -0
  58. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/remote_chat/remote_chat_tech.md +0 -0
  59. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/remote_exec/remote_exec.md +0 -0
  60. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/remote_exec/remote_exec_acceptance.md +0 -0
  61. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/features/remote_exec/remote_exec_tech.md +0 -0
  62. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/interface.md +0 -0
  63. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/docs/mutagen_capabilities.md +0 -0
  64. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/scripts/check_docs_references.py +0 -0
  65. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/__init__.py +0 -0
  66. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/account.py +0 -0
  67. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/auth.py +0 -0
  68. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/bootstrap.py +0 -0
  69. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/chat.py +0 -0
  70. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/cli_version.py +0 -0
  71. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/config.py +0 -0
  72. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/console.py +0 -0
  73. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/context.py +0 -0
  74. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/doctor.py +0 -0
  75. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/errors.py +0 -0
  76. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/git_versioning.py +0 -0
  77. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/improve.py +0 -0
  78. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/logging.py +0 -0
  79. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/main.py +0 -0
  80. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/mcp_proxy.py +0 -0
  81. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/mutagen_runtime.py +0 -0
  82. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/scenarios.py +0 -0
  83. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/sync.py +0 -0
  84. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/sync_session.py +0 -0
  85. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/sync_ssh_shim.py +0 -0
  86. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/sync_tui.py +0 -0
  87. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/templates/ACCOUNT_CLAUDE.md.template +0 -0
  88. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/templates/CHAT_TESTING.md +0 -0
  89. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/templates/CLAUDE.md.template +0 -0
  90. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/templates/GIT_VERSIONING.md +0 -0
  91. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/src/cinna/templates/__init__.py +0 -0
  92. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/__init__.py +0 -0
  93. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/conftest.py +0 -0
  94. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_account.py +0 -0
  95. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_auth.py +0 -0
  96. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_bootstrap.py +0 -0
  97. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_chat.py +0 -0
  98. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_cli_version.py +0 -0
  99. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_config.py +0 -0
  100. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_console.py +0 -0
  101. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_context.py +0 -0
  102. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_doctor.py +0 -0
  103. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_git_versioning.py +0 -0
  104. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_improve.py +0 -0
  105. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_kit_contract.py +0 -0
  106. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_main.py +0 -0
  107. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_mutagen_runtime.py +0 -0
  108. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_onboarding.py +0 -0
  109. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_scenarios.py +0 -0
  110. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_sync.py +0 -0
  111. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/tests/test_sync_session.py +0 -0
  112. {cinna_cli-0.4.3 → cinna_cli-0.5.1}/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.4.3
3
+ Version: 0.5.1
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
@@ -338,6 +338,8 @@ The folder rules come from the kit's versioned contract, `.cinna-kit/layout.json
338
338
 
339
339
  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.
340
340
 
341
+ The manifest's `runtime.engine` (`opencode`, `claude`, `codex`, …) is sent when the agent is **created**; the platform runs an engine it does not support on OpenCode, and `--update` never changes an existing agent's engine. The plan prints an `Engine:` line, and a `Not carried to the cloud:` line naming the manifest fields present that the cloud has no place for (`runtime.model`, `runtime.complexity`, `runtime.credential`, `runtime.permissions`, unknown `runtime.*` keys, `handovers` — the import sends no handover at all — and `capabilities`). A malformed `runtime` costs a warning, never the import.
342
+
341
343
  ### `cinna skills list <agent> [--json]`
342
344
 
343
345
  List everything an agent carries beyond its prompt, as one deduplicated list. An **addon** is either an installed plugin or a `skills/<name>/` folder — a `SKILL.md` plus its files that the engine loads on demand. The two overlap (a skill installed from the catalog is *also* a plugin link), and the platform owns the dedupe rule, so this prints the server's projection rather than folding the halves itself: one row per addon, with its kind, source (`marketplace` / `bundle` / `catalog` / `local`), status, name and version.
@@ -301,6 +301,8 @@ The folder rules come from the kit's versioned contract, `.cinna-kit/layout.json
301
301
 
302
302
  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.
303
303
 
304
+ The manifest's `runtime.engine` (`opencode`, `claude`, `codex`, …) is sent when the agent is **created**; the platform runs an engine it does not support on OpenCode, and `--update` never changes an existing agent's engine. The plan prints an `Engine:` line, and a `Not carried to the cloud:` line naming the manifest fields present that the cloud has no place for (`runtime.model`, `runtime.complexity`, `runtime.credential`, `runtime.permissions`, unknown `runtime.*` keys, `handovers` — the import sends no handover at all — and `capabilities`). A malformed `runtime` costs a warning, never the import.
305
+
304
306
  ### `cinna skills list <agent> [--json]`
305
307
 
306
308
  List everything an agent carries beyond its prompt, as one deduplicated list. An **addon** is either an installed plugin or a `skills/<name>/` folder — a `SKILL.md` plus its files that the engine loads on demand. The two overlap (a skill installed from the catalog is *also* a plugin link), and the platform owns the dedupe rule, so this prints the server's projection rather than folding the halves itself: one row per addon, with its kind, source (`marketplace` / `bundle` / `catalog` / `local`), status, name and version.
@@ -152,9 +152,11 @@ result attached to the current task ──► delivered to the requester (Deskto
152
152
  - **Remote chat** — a delegated task runs as a platform session like the ones
153
153
  `cinna chat` drives. See [Remote Chat](../remote_chat/remote_chat.md).
154
154
  - **cinna-core task MCP server (not in this repo)** — inside cloud tasks the
155
- backend's MCP task server exposes a `handover_report` tool that records the
156
- same kind of result. It is implemented and documented in cinna-core; this CLI
157
- only offers the equivalent command-line path.
155
+ backend's `cinna` MCP server exposes a `tasks_report` tool (the Cinna Task
156
+ Protocol, kit contract 1.6.0) that records the same kind of result; an
157
+ environment built before protocol version 1 still has it as `handover_report`
158
+ on the older `agent_task` server. It is implemented and documented in
159
+ cinna-core; this CLI only offers the equivalent command-line path.
158
160
 
159
161
  Implementation: see [delegation_tech.md](delegation_tech.md).
160
162
  Real-usage e2e scenarios: see [delegation_acceptance.md](delegation_acceptance.md).
@@ -122,8 +122,9 @@ Executor side — direct call, agent-token auth:
122
122
  belongs to this environment's agent and owner, and resolves the task from the
123
123
  session. Any 2xx is success.
124
124
 
125
- Related, not in this repo: cinna-core's cloud MCP task server exposes a
126
- `handover_report` tool for the same executor-side report.
125
+ Related, not in this repo: cinna-core's `cinna` MCP server exposes a
126
+ `tasks_report` tool for the same executor-side report (`handover_report` on
127
+ environments built before protocol version 1).
127
128
 
128
129
  ## Edge cases & guardrails (preserve these)
129
130
 
@@ -21,7 +21,9 @@ The kit's machine-readable half is a **versioned contract** (`layout.json`,
21
21
  `GET /api/agent-start/contract.tar.gz` with a cheap version poll at
22
22
  `GET /api/agent-start/contract/version`. cinna-cli reads the copy installed at
23
23
  `.cinna-kit/`; a shipped build should obtain it from those routes rather than
24
- vendoring one.
24
+ vendoring one. This build implements contract **1.5.0** (`runtime.engine`,
25
+ `runtime.complexity` and `handovers[].target_kind` in core's one schema); only
26
+ the major decides compatibility, so any `1.x` folder imports.
25
27
 
26
28
  Nothing platform-side is new: the import replays the manifest through the
27
29
  existing account-scoped verbs (`agent create`, the bulk prompt write, `agent
@@ -57,6 +59,30 @@ sync`, `sync push`, `account credentials create`, `agent schedule create`,
57
59
  `.env.sample` and `.env.template` still travel. Credentials are created as
58
60
  **empty drafts** and the command prints the URLs the user opens to fill each
59
61
  one in the browser. No secret value is read, sent, or printed at any point.
62
+ - **A credential spec can name its slot.** Since contract 1.4.0 a manifest
63
+ credential may carry `service_uri` — the slot the agent's credential reader
64
+ matches an attached credential on. The import passes it to the draft
65
+ unchanged; one that is not a non-empty string is refused with the other
66
+ manifest guards, before the agent is created.
67
+ - **The engine is a preference, carried once.** A manifest's `runtime.engine`
68
+ (`opencode`, `claude`, `codex`, or a name this build has never heard of) is
69
+ sent to the platform when the agent is **created**, which maps it to the
70
+ environment's SDK and runs an engine it does not support on OpenCode — the
71
+ folder is never refused over it. `--update` never changes an existing agent's
72
+ engine. A `runtime` that is not an object, or an engine that is not a string,
73
+ costs a warning and nothing is sent.
74
+ - **What the cloud does not carry is named, not dropped silently.** The other
75
+ `runtime` fields (`model`, `complexity`, `credential`, `permissions`, and any
76
+ key this build does not know) have no cloud counterpart, and the import
77
+ sends no `handovers` at all (not only the desktop's `target_kind`) and maps
78
+ `capabilities` onto nothing; the plan lists the ones present in one
79
+ `Not carried to the cloud:` line.
80
+ - **Using cloud credentials on the desktop is not an import concern.** A
81
+ locally running agent in Cinna Desktop gets credential values from the
82
+ platform's desktop delivery route, gated per credential by *allow local use*
83
+ for credentials shared with you (your own always qualify). That exchange is
84
+ between the platform and the desktop; `cinna agent import` never reads
85
+ credential values, and the CLI has no switch for *allow local use* yet.
60
86
  - **The ledger is a sibling file, never a manifest key.** Where an agent folder
61
87
  has been published is recorded in `publications.json` beside
62
88
  `cinna-agent.json` — one entry per Cinna instance. It has to be a separate
@@ -83,10 +109,11 @@ Each prints as `[n/9]`:
83
109
  contract: refuse a folder built against a newer **major** contract version,
84
110
  warn on an older one or on a folder that records none, resolve the exclude
85
111
  list and the secret rules, plan the copy and compute the tree's
86
- `content_hash`.
112
+ `content_hash`. Read `runtime` tolerantly and print the `Engine:` line and,
113
+ when any are present, the `Not carried to the cloud:` line.
87
114
  2. **Agent** — resolve by the `publications.json` entry for this instance
88
115
  (requires `--update`) or create a new agent in the active user workspace
89
- (`--workspace` overrides).
116
+ (`--workspace` overrides), passing `runtime.engine` on creation only.
90
117
  3. **Prompts + metadata** — one bulk write carrying `description`,
91
118
  `router_trigger_prompt`, `example_prompts`, and the three document prompts
92
119
  read from the files named in `prompts`; then the status refresh command.
@@ -99,7 +126,8 @@ Each prints as `[n/9]`:
99
126
  6. **Push** — `cinna sync push` equivalent (skipped by `--no-push`).
100
127
  7. **Credentials** — one empty draft per spec, attached to the agent; an
101
128
  existing credential of the same name is attached rather than recreated.
102
- Setup URLs are collected for the summary.
129
+ A spec's `service_uri` is set on a new draft; an existing credential keeps
130
+ its own. Setup URLs are collected for the summary.
103
131
  8. **Schedules** — one per spec, name-idempotent; `--update` rewrites an
104
132
  existing schedule in place.
105
133
  9. **Record** — add or update this instance's entry in `publications.json`
@@ -131,7 +159,11 @@ file that would be copied, every credential draft, every schedule — and makes
131
159
  `publications.json` entry whose `platform_url` matches the instance you are
132
160
  logged into (not by workspace, and not by position in the list), rewrites
133
161
  prompts and metadata, re-copies the tree, pushes, attaches existing credentials,
134
- and updates the schedules in place.
162
+ and updates the schedules in place. The agent's engine is left as it is; when
163
+ the manifest names one, the `Engine:` line says it was not applied. An
164
+ `--update` with no known agent id cannot tell from the plan whether it will
165
+ find the agent by name or create it, so the plan says the engine is sent only
166
+ on creation, and the lookup then says which happened.
135
167
 
136
168
  ### Resume a partial import
137
169
 
@@ -160,11 +192,15 @@ created are reused, not duplicated.
160
192
  | `schema_version` newer than supported | Refused with an upgrade hint — never half-read |
161
193
  | 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
194
  | Folder records no `contract_version` | Warned and imported — every folder created before contract 1.0.0 is in that state |
195
+ | A credential's `service_uri` is empty or not a string | Refused before any platform call, naming the credential |
196
+ | `runtime` is not an object, or `runtime.engine` is not a string | Warned and imported; no engine is sent, so the agent gets the platform default |
197
+ | `runtime.engine` names an engine the cloud does not support | Imported; the platform runs the agent on OpenCode |
198
+ | Older platform without the `engine` create field | Imported; the platform ignores the unknown field and the agent gets its default engine |
163
199
  | Already imported to this instance, no `--update` | Refused, naming the agent id and where the link was recorded |
164
200
  | The recorded agent id is not among your agents | Refused — you are logged into a different platform, or the agent was deleted |
165
201
  | `--update` with several name matches | Refused, listing them; add the `publications.json` entry to disambiguate |
166
202
  | 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 |
203
+ | `.cinna-kit/layout.json` missing or unreadable | Imported with the built-in copy of contract 1.5.0, 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
204
  | 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
205
  | `publications.json` unreadable | Refused, and the file is left exactly as it was |
170
206
  | Credential/schedule 403 | The platform error surfaces verbatim; earlier steps stay done and `--update` resumes |
@@ -121,7 +121,8 @@ any change to the orchestrator.
121
121
 
122
122
  - **Steps:** in a scratch copy of the agent folder, one at a time: bump
123
123
  `schema_version` to `2`; rename the folder so it disagrees with `slug`; make
124
- `cron_string` `"every morning"`; drop a credential's `type`.
124
+ `cron_string` `"every morning"`; drop a credential's `type`; set a
125
+ credential's `service_uri` to `""`.
125
126
  - **Expected:** each is refused *before* any platform call, with a message that
126
127
  names the offending field.
127
128
  - **Watch for:** a half-applied import (agent created, then the manifest
@@ -242,14 +243,55 @@ any change to the orchestrator.
242
243
  ### 10f. The `contract_version` gate
243
244
 
244
245
  - **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
+ to `1.5.2` and import; then remove the key entirely and import.
246
247
  - **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
+ `1.5.2` passes **silently** (same major — a minor contract change is additive
248
249
  by definition and reaches a non-adopting reader as silence); the missing key
249
250
  warns about a re-stamp and imports anyway.
250
251
  - **Watch for:** a warning on the same-major pair. That is the gate's shape, not
251
252
  a bug: anything an old reader must notice needs a major bump.
252
253
 
254
+ ### 10g. A credential slot reaches the draft (contract 1.4.0)
255
+
256
+ - **Goal:** a manifest credential that names its slot gets a draft the agent's
257
+ credential reader can match, in the cloud and on the desktop.
258
+ - **Setup:** a fresh agent whose manifest credential carries
259
+ `"service_uri": "imap://billing"` and whose name does **not** exist yet in the
260
+ account.
261
+ - **Steps:**
262
+ ```bash
263
+ cinna agent import MyAgents/Local/<agent> --yes
264
+ cinna account credentials list
265
+ ```
266
+ - **Expected:** the draft is listed with `imap://billing` in the **Slot** column; after it is filled
267
+ in the browser, `cinna skills list <agent>` (if a skill declares the slot)
268
+ shows it ready. Re-running with `--update` does not change the URI of the
269
+ existing credential.
270
+ - **Watch for:** a draft created without the slot, so the reader falls back to
271
+ name matching; a second draft created because the slot differs.
272
+
273
+ ### 10h. The engine travels at creation, and nothing else from `runtime` (contract 1.5.0)
274
+
275
+ - **Setup:** a fresh agent whose manifest carries
276
+ `"runtime": {"engine": "claude", "model": "claude-sonnet", "complexity": "medium"}`
277
+ and a handover with `"target_kind": "coordinator"`. Requires a cinna-core that
278
+ accepts `engine` on `POST /api/v1/cli/account/agents`.
279
+ - **Steps:**
280
+ ```bash
281
+ cinna agent import MyAgents/Local/<agent> --dry-run
282
+ cinna agent import MyAgents/Local/<agent> --yes
283
+ cinna agent import MyAgents/Local/<agent> --update --yes
284
+ ```
285
+ - **Expected:** both the dry run and the import print
286
+ `Engine: claude — sent as a preference (the cloud runs engines it does not support on OpenCode)`
287
+ and `Not carried to the cloud: runtime.model, runtime.complexity, handovers`;
288
+ the new agent's environment runs on Claude Code. Set `"engine": "codex"` on a
289
+ fresh agent: it imports and runs on OpenCode. The `--update` run prints
290
+ `Engine: claude — not applied: --update never changes an existing agent's engine`
291
+ and the environment is unchanged.
292
+ - **Watch for:** the import refusing an unknown engine; the model override
293
+ changing; an older cinna-core rejecting the create (it should ignore `engine`).
294
+
253
295
  ### 11. Verification loop the guide promises
254
296
 
255
297
  - **Steps:**
@@ -39,7 +39,7 @@ endpoint exists for this feature.
39
39
 
40
40
  | Step | Call |
41
41
  |------|------|
42
- | 2 | `list_account_agents()` (resolve) / `create_agent(name, description, user_workspace_id=…)` |
42
+ | 2 | `list_account_agents()` (resolve) / `create_agent(name, description, user_workspace_id=…, engine=…)` — `engine` is in the body only when set |
43
43
  | 3 | `update_agent_config(agent_id, fields)` → `PUT agents/{id}` **through the api-proxy escape hatch**, then `set_status_refresh_command(agent_id, cmd)` |
44
44
  | 4 | `account.py:run_agent_sync(agent_id, None)` (mints the child token, provisions the workspace) — skipped when `resolve_child_workspace()` already finds one |
45
45
  | 6 | `sync_session.ensure_session()` + `sync_session.flush()` — the same pair `cinna sync push` uses |
@@ -70,10 +70,59 @@ pragmatic subset `kit.py validate` checks:
70
70
  - `slug` matches `^[a-z0-9][a-z0-9-]{1,62}$` **and** equals the folder name.
71
71
  - `name` present, ≤ 255 chars; `description` a string when present.
72
72
  - `prompts` an object; `example_prompts` a list of strings.
73
- - Each credential has a non-empty `name` and `type`.
73
+ - Each credential has a non-empty `name` and `type`, and a `service_uri`
74
+ (contract 1.4.0, optional) is a non-empty string when present. Checked here
75
+ rather than left to the platform because the agent already exists by the time
76
+ `_sync_credentials()` sends it — a 422 there is a half-applied import.
74
77
  - Each schedule has a name, a 5-field `cron_string`, a known `schedule_type`, and
75
78
  a `command` when it is a script schedule.
76
79
 
80
+ `runtime` is **not** validated here — `read_runtime(manifest)` reads it
81
+ tolerantly after the manifest loads, because its fields are additive minors
82
+ that must never cost the import:
83
+
84
+ - `runtime` not an object, or `runtime.engine` not a string → a
85
+ `console.warn` naming the type, and no engine is sent.
86
+ - `runtime.engine` stripped; empty means absent. Otherwise sent raw — it is not
87
+ an enum, and the backend owns the mapping (`claude` → Claude Code, everything
88
+ it does not support → OpenCode, decision 4 of the 1.5.0 unification plan).
89
+ - The fields the cloud import does not carry are returned as dotted paths, in
90
+ a fixed order: `runtime.model`, `runtime.complexity`, `runtime.credential`,
91
+ `runtime.permissions` (`_UNCARRIED_RUNTIME_KEYS`), then any unknown
92
+ `runtime.*` key sorted, then `handovers` — the whole list, when it is
93
+ non-empty, because this import sends no handover at all; naming only
94
+ `handovers[].target_kind` would imply the rest of a handover travels — then
95
+ `capabilities`, when any of its keys has a value (`x-import: ignored`). A
96
+ key whose value is `null` counts as absent.
97
+
98
+ The engine reaches `_resolve_or_create_agent()` and is passed to
99
+ `create_agent()` **only on the create branch**. An agent resolved by id or
100
+ reattached by name keeps its engine: the id case says so in the step-1 `Engine:`
101
+ line (`known_agent_id` is known before the plan prints). `--update` without a
102
+ known id only learns at step 2 whether a name match exists, so its step-1 line
103
+ is the undecided one, and step 2 prints either the kept line (name-matched
104
+ resume) or the sent line (no match, agent created). The `Engine:` values,
105
+ verbatim:
106
+
107
+ ```
108
+ Engine: claude — sent as a preference (the cloud runs engines it does not support on OpenCode)
109
+ Engine: claude — not applied: --update never changes an existing agent's engine
110
+ Engine: claude — sent only if this import creates the agent; --update keeps the engine of an existing agent it finds by name
111
+ Engine: not set — the cloud agent gets the platform's default
112
+ Not carried to the cloud: runtime.model, runtime.complexity, handovers
113
+ ```
114
+
115
+ The last line is printed only when the list is non-empty. The warnings:
116
+
117
+ ```
118
+ ! cinna-agent.json: 'runtime' is not an object (str) — ignored; the cloud agent gets the platform's default engine.
119
+ ! cinna-agent.json: 'runtime.engine' is not a string (int) — ignored; the cloud agent gets the platform's default engine.
120
+ ```
121
+
122
+ Sending `engine` to an older cinna-core is safe: `AccountAgentCreateBody` is a
123
+ plain `SQLModel` with no `extra` setting, so pydantic's default (`ignore`)
124
+ drops the unknown field and the agent is created with the default engine.
125
+
77
126
  Unknown keys are preserved: the dict is mutated and re-dumped, so a manifest
78
127
  written by a newer kit survives a round-trip through an older CLI.
79
128
 
@@ -91,7 +140,39 @@ hosts read it — Cinna Desktop, cinna-core's `kit.py`, and this CLI — so the
91
140
  folder rules are data rather than prose each host re-derives.
92
141
  `src/cinna/kit_contract.py` is cinna-cli's reader and the port of the algorithms
93
142
  that must stay byte-identical across hosts. `SUPPORTED_CONTRACT_VERSION` names
94
- the contract version this build implements.
143
+ the contract version this build implements — **1.5.0**. It is the tool half of
144
+ the compatibility pair when no kit sits beside the agent, and it is the version
145
+ named in the `DEGRADED —` `Exclusions:` line.
146
+
147
+ ### Contract 1.5.0 — what changed for this reader
148
+
149
+ 1.5.0 brings the desktop's `runtime.engine`, `runtime.complexity` and
150
+ `handovers[].target_kind` into cinna-core's one manifest schema. Nothing this
151
+ module evaluates changed — `cloud_import_excludes`, `secret_files` and the
152
+ ledger schema are as in 1.4.0. The manifest side is `local_import.py`:
153
+ `runtime.engine` now reaches the platform at creation, and the fields the cloud
154
+ does not carry are printed (see *Manifest handling*).
155
+
156
+ ### Contract 1.4.0 — what changed for this reader
157
+
158
+ 1.4.0 (cloud and local credential delivery) is additive for cinna-cli; the
159
+ cross-repo checks below pass unchanged against cinna-core's 1.4.0 files.
160
+
161
+ - `cloud_import_excludes` is unchanged in content: `credentials/` was already
162
+ excluded whole, and the notes now say why (the platform regenerates
163
+ `credentials/README.md` and feeds it to the cloud prompt). `DEFAULT_EXCLUDE`
164
+ and `DEFAULT_SECRET_FILE_RULES` equal the 1.4.0 lists.
165
+ - `desktop_owned` shrank to a bare path list (the `contract_keys` block is
166
+ gone). This module does not read `desktop_owned`, so nothing depends on it.
167
+ - The manifest schema gained an optional `credentials[].service_uri`;
168
+ `local_import.py:load_manifest()` validates it and
169
+ `local_import.py:_sync_credentials()` passes it to
170
+ `client.py:AccountClient.create_credential()`.
171
+ - Credential values for a locally running agent come from cinna-core's
172
+ `/api/v1/external/credentials` routes (list, and a desktop-only
173
+ `materialize`), gated per credential by `allow_local_use`. Those are
174
+ desktop-JWT routes; `POST|PUT /api/v1/cli/account/credentials` do not accept
175
+ `allow_local_use` yet, so the CLI neither sends nor shows it.
95
176
 
96
177
  ### Finding it
97
178
 
@@ -293,7 +374,9 @@ failure warns and yields no file rather than aborting the import.
293
374
  absent + no `--update` → create.
294
375
  - **Credentials** — `list_credentials()` keyed by name; an existing name is only
295
376
  *attached* (`share_credential_with_agent`), never recreated, so re-runs cannot
296
- produce a second empty draft that shadows the filled one.
377
+ produce a second empty draft that shadows the filled one. Its `service_uri`
378
+ is not compared or rewritten either — the manifest's slot applies to a new
379
+ draft only.
297
380
  - **Schedules** — `list_schedules()` keyed by name; existing + `--update` →
298
381
  `update_schedule`, existing without `--update` → left alone with a notice.
299
382
  - **Manifest settle** — `write_manifest()` runs after the confirmation and
@@ -316,7 +399,8 @@ failure warns and yields no file rather than aborting the import.
316
399
 
317
400
  `--dry-run` returns before the first `AccountClient` is constructed and before
318
401
  any local write: it prints the resolved plan (file list, credential drafts,
319
- schedules, create-vs-update) and stops. The test asserts the patched
402
+ schedules, the `Engine:` and `Not carried to the cloud:` lines,
403
+ create-vs-update) and stops. The test asserts the patched
320
404
  `AccountClient` class was never called at all.
321
405
 
322
406
  ## Tests
@@ -386,4 +470,17 @@ against `publications.schema.json`.
386
470
  - `run_agent_sync` is called exactly once when no workspace exists yet;
387
471
  - `--no-push` and push-with-conflicts both leave the ledger unwritten;
388
472
  - `layout.json` supplying the exclude list while `credentials/` stays excluded;
389
- - `--workspace` / `--name` threading and verbatim `PlatformError` surfacing.
473
+ - a bad credential `service_uri` refused by `load_manifest()`, and a good one
474
+ reaching `create_credential()`;
475
+ - `--workspace` / `--name` threading and verbatim `PlatformError` surfacing;
476
+ - contract 1.5.0 runtime: `read_runtime()` over absent, null, blank, unknown and
477
+ full `runtime` blocks and empty/non-empty `handovers`; the stripped engine
478
+ reaching `create_agent()`; the default-engine line; a non-object `runtime`
479
+ and a non-string engine warning without refusing; the exact
480
+ `Not carried to the cloud:` line on a real run and on `--dry-run`, with no
481
+ runtime field in the metadata write; `--update` by id and by name match
482
+ leaving the engine alone and saying so once, and saying nothing when the
483
+ manifest names no engine.
484
+
485
+ `tests/test_client.py` asserts `create_agent()` puts `engine` in the body only
486
+ when it is set.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cinna-cli"
3
- version = "0.4.3"
3
+ version = "0.5.1"
4
4
  description = "Local development CLI for Cinna Core agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -318,6 +318,7 @@ class AccountClient:
318
318
  name: str,
319
319
  description: str | None = None,
320
320
  user_workspace_id: str | None = None,
321
+ engine: str | None = None,
321
322
  ) -> dict:
322
323
  """POST /api/v1/cli/account/agents — create an agent (thin client).
323
324
 
@@ -325,12 +326,20 @@ class AccountClient:
325
326
  (AI credentials, env template, environment creation) and returns the
326
327
  full agent record. ``user_workspace_id`` targets the account's active
327
328
  user workspace (``None`` = Default).
329
+
330
+ ``engine`` is a Local Agent Kit ``runtime.engine`` preference
331
+ (``opencode``, ``claude``, ``codex``, …), sent raw: the backend maps it
332
+ to the environment's SDK and runs an engine it does not support on
333
+ OpenCode. It is sent only when set — an older backend ignores the
334
+ unknown field, so sending it is backward compatible.
328
335
  """
329
336
  body: dict = {"name": name}
330
337
  if description is not None:
331
338
  body["description"] = description
332
339
  if user_workspace_id is not None:
333
340
  body["user_workspace_id"] = user_workspace_id
341
+ if engine is not None:
342
+ body["engine"] = engine
334
343
  response = self._client.post("/api/v1/cli/account/agents", json=body)
335
344
  return self._handle_response(response).json()
336
345
 
@@ -78,8 +78,13 @@ def _emit_json(result) -> bool:
78
78
 
79
79
 
80
80
  def _parse_artifacts(values: tuple[str, ...]) -> list[dict]:
81
- """Each ``--artifact`` must be a JSON object with kind, name and an
82
- HTTP(S) ``ref`` — the only kind of reference another agent can open."""
81
+ """Each ``--artifact`` must be a JSON object with ``kind`` and ``name``.
82
+
83
+ A ``link`` needs an HTTP(S) ``ref`` — the only kind of reference another
84
+ agent can open. A ``file`` may leave ``ref`` out (the Cinna Task Protocol's
85
+ name-only file artifact, kit contract 1.6.0); when it carries one it must be
86
+ HTTP(S) too, never a local path.
87
+ """
83
88
  artifacts = []
84
89
  for value in values:
85
90
  try:
@@ -90,9 +95,10 @@ def _parse_artifacts(values: tuple[str, ...]) -> list[dict]:
90
95
  raise click.BadParameter(
91
96
  f"Not a JSON object: {value}", param_hint="--artifact"
92
97
  )
98
+ required = ("kind", "name") if artifact.get("kind") == "file" and "ref" not in artifact else ("kind", "name", "ref")
93
99
  missing = [
94
100
  key
95
- for key in ("kind", "name", "ref")
101
+ for key in required
96
102
  if not isinstance(artifact.get(key), str) or not artifact[key].strip()
97
103
  ]
98
104
  if missing:
@@ -100,7 +106,7 @@ def _parse_artifacts(values: tuple[str, ...]) -> list[dict]:
100
106
  f"Artifact needs non-empty string {', '.join(missing)}: {value}",
101
107
  param_hint="--artifact",
102
108
  )
103
- if urlparse(artifact["ref"]).scheme.lower() not in ("http", "https"):
109
+ if "ref" in artifact and urlparse(artifact["ref"]).scheme.lower() not in ("http", "https"):
104
110
  raise click.BadParameter(
105
111
  "Artifact ref must be an http(s) URL (upload local files first): "
106
112
  f"{artifact['ref']}",
@@ -311,7 +317,7 @@ def status(task_id):
311
317
  @click.option(
312
318
  "--artifact",
313
319
  multiple=True,
314
- help='JSON object {"kind", "name", "ref"} with an http(s) ref; repeatable.',
320
+ help='JSON object {"kind", "name", "ref"} with an http(s) ref; a "file" may omit ref; repeatable.',
315
321
  )
316
322
  @click.option("--body", default="", help="The full result text.")
317
323
  def report(task_id, result_status, summary, question, audience, artifact, body):
@@ -24,7 +24,12 @@ rather than compute it a different way. The safe direction is a property of the
24
24
  consequence, not a house style.
25
25
 
26
26
  Ported from cinna-core's ``docs/local_agent_kit/tools/kit.py``, which is the
27
- reference implementation, at contract version 1.0.0.
27
+ reference implementation, at contract version 1.0.0; the offline fallbacks below
28
+ are checked against contract 1.5.0. Neither 1.4.0 (cloud and local credential
29
+ delivery — ``credentials/`` was already excluded whole) nor 1.5.0 (the
30
+ ``runtime.engine``/``runtime.complexity``/``handovers[].target_kind`` manifest
31
+ fields brought into core's schema, read by :mod:`cinna.local_import`) changed
32
+ anything this module evaluates.
28
33
  """
29
34
 
30
35
  from __future__ import annotations
@@ -54,7 +59,7 @@ CONTRACT_VERSION_FILENAME = "CONTRACT_VERSION"
54
59
 
55
60
  #: The contract version this CLI implements. Only the MAJOR component is a
56
61
  #: compatibility question — see ``check_contract_compatibility``.
57
- SUPPORTED_CONTRACT_VERSION = "1.0.0"
62
+ SUPPORTED_CONTRACT_VERSION = "1.6.0"
58
63
 
59
64
 
60
65
  # ── Offline fallbacks ───────────────────────────────────────────────────────