cinna-cli 0.5.2__tar.gz → 0.5.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/PKG-INFO +21 -2
  2. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/README.md +20 -1
  3. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/README.md +1 -0
  4. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/account_workspace/account_workspace.md +3 -1
  5. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/account_workspace/account_workspace_acceptance.md +7 -3
  6. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/account_workspace/account_workspace_tech.md +3 -1
  7. cinna_cli-0.5.3/docs/features/artifacts/artifacts.md +138 -0
  8. cinna_cli-0.5.3/docs/features/artifacts/artifacts_acceptance.md +140 -0
  9. cinna_cli-0.5.3/docs/features/artifacts/artifacts_tech.md +124 -0
  10. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/delegation/delegation.md +2 -1
  11. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/pyproject.toml +1 -1
  12. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/account.py +28 -1
  13. cinna_cli-0.5.3/src/cinna/artifacts.py +971 -0
  14. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/client.py +102 -0
  15. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/main.py +17 -0
  16. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/templates/ACCOUNT_CLAUDE.md.template +10 -0
  17. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_account.py +18 -2
  18. cinna_cli-0.5.3/tests/test_artifacts.py +645 -0
  19. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/uv.lock +1 -1
  20. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/.claude/commands/cinna-cli.feature.doc.md +0 -0
  21. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/.github/workflows/publish.yml +0 -0
  22. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/.gitignore +0 -0
  23. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/LICENSE.md +0 -0
  24. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_addons/agent_addons.md +0 -0
  25. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_addons/agent_addons_acceptance.md +0 -0
  26. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_addons/agent_addons_tech.md +0 -0
  27. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_api/agent_api.md +0 -0
  28. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_api/agent_api_acceptance.md +0 -0
  29. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_api/agent_api_tech.md +0 -0
  30. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_management/agent_management.md +0 -0
  31. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_management/agent_management_acceptance.md +0 -0
  32. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_management/agent_management_tech.md +0 -0
  33. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_schedules/agent_schedules.md +0 -0
  34. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_schedules/agent_schedules_acceptance.md +0 -0
  35. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_schedules/agent_schedules_tech.md +0 -0
  36. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/bootstrap_onboarding/bootstrap_onboarding.md +0 -0
  37. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/bootstrap_onboarding/bootstrap_onboarding_acceptance.md +0 -0
  38. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/bootstrap_onboarding/bootstrap_onboarding_tech.md +0 -0
  39. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/delegation/delegation_acceptance.md +0 -0
  40. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/delegation/delegation_tech.md +0 -0
  41. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/doctor/doctor.md +0 -0
  42. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/doctor/doctor_acceptance.md +0 -0
  43. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/doctor/doctor_tech.md +0 -0
  44. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/git_versioning/git_versioning.md +0 -0
  45. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/git_versioning/git_versioning_acceptance.md +0 -0
  46. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/git_versioning/git_versioning_tech.md +0 -0
  47. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/improvement_requests/improvement_requests.md +0 -0
  48. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/improvement_requests/improvement_requests_acceptance.md +0 -0
  49. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/improvement_requests/improvement_requests_tech.md +0 -0
  50. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/live_sync/live_sync.md +0 -0
  51. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/live_sync/live_sync_acceptance.md +0 -0
  52. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/live_sync/live_sync_tech.md +0 -0
  53. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/local_agent_import/local_agent_import.md +0 -0
  54. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/local_agent_import/local_agent_import_acceptance.md +0 -0
  55. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/local_agent_import/local_agent_import_tech.md +0 -0
  56. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/mcp_integration/mcp_integration.md +0 -0
  57. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/mcp_integration/mcp_integration_acceptance.md +0 -0
  58. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/mcp_integration/mcp_integration_tech.md +0 -0
  59. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/remote_chat/remote_chat.md +0 -0
  60. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/remote_chat/remote_chat_acceptance.md +0 -0
  61. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/remote_chat/remote_chat_tech.md +0 -0
  62. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/remote_exec/remote_exec.md +0 -0
  63. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/remote_exec/remote_exec_acceptance.md +0 -0
  64. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/remote_exec/remote_exec_tech.md +0 -0
  65. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/team_library/team_library.md +0 -0
  66. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/team_library/team_library_acceptance.md +0 -0
  67. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/team_library/team_library_tech.md +0 -0
  68. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/interface.md +0 -0
  69. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/mutagen_capabilities.md +0 -0
  70. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/scripts/check_docs_references.py +0 -0
  71. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/__init__.py +0 -0
  72. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/auth.py +0 -0
  73. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/bootstrap.py +0 -0
  74. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/chat.py +0 -0
  75. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/cli_version.py +0 -0
  76. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/config.py +0 -0
  77. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/console.py +0 -0
  78. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/context.py +0 -0
  79. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/delegation.py +0 -0
  80. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/doctor.py +0 -0
  81. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/errors.py +0 -0
  82. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/git_versioning.py +0 -0
  83. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/improve.py +0 -0
  84. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/kit_contract.py +0 -0
  85. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/library.py +0 -0
  86. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/local_import.py +0 -0
  87. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/logging.py +0 -0
  88. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/mcp_proxy.py +0 -0
  89. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/mutagen_runtime.py +0 -0
  90. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/scenarios.py +0 -0
  91. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/sync.py +0 -0
  92. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/sync_session.py +0 -0
  93. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/sync_ssh_shim.py +0 -0
  94. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/sync_tui.py +0 -0
  95. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/templates/CHAT_TESTING.md +0 -0
  96. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/templates/CLAUDE.md.template +0 -0
  97. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/templates/GIT_VERSIONING.md +0 -0
  98. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/templates/__init__.py +0 -0
  99. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/__init__.py +0 -0
  100. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/conftest.py +0 -0
  101. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_agent_api_access.py +0 -0
  102. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_auth.py +0 -0
  103. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_bootstrap.py +0 -0
  104. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_chat.py +0 -0
  105. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_cli_version.py +0 -0
  106. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_client.py +0 -0
  107. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_config.py +0 -0
  108. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_console.py +0 -0
  109. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_context.py +0 -0
  110. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_delegation.py +0 -0
  111. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_doctor.py +0 -0
  112. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_git_versioning.py +0 -0
  113. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_improve.py +0 -0
  114. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_kit_contract.py +0 -0
  115. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_library.py +0 -0
  116. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_local_import.py +0 -0
  117. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_main.py +0 -0
  118. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_mutagen_runtime.py +0 -0
  119. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_onboarding.py +0 -0
  120. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_scenarios.py +0 -0
  121. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_sync.py +0 -0
  122. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_sync_session.py +0 -0
  123. {cinna_cli-0.5.2 → cinna_cli-0.5.3}/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.5.2
3
+ Version: 0.5.3
4
4
  Summary: Local development CLI for Cinna Core agents
5
5
  Project-URL: Homepage, https://github.com/opencinna/cinna-cli
6
6
  Project-URL: Repository, https://github.com/opencinna/cinna-cli
@@ -575,7 +575,7 @@ Hand work to a remote agent as a **durable delegation** — a platform task with
575
575
 
576
576
  - `create --id KEY --target AGENT_UUID --title T --brief B [--execute]` — the identity is `--target` + `--id`: a retry with the same pair returns the original task (changed title/brief are ignored) and never launches execution twice (the backend deduplicates). Prints the backend **task id** the other commands take, and whether it executes. `--depth 2 --root ROOT` makes a sub-delegation (`--root` is required at depth 2, not allowed at depth 1); `--group` tags related work.
577
577
  - `status TASK_ID` — one read: state, latest result, and any open question with its result id. A requester agent should end its turn and let Cinna Desktop deliver the result rather than poll.
578
- - `report [TASK_ID] --status in_progress|blocked|done|failed --summary S` — `blocked` needs `--question`; `--audience user` when a human must decide (default `requester`). `--artifact` (repeatable) is a JSON object with non-empty `kind` (backend accepts `file` or `link`), `name` and an `http(s)` `ref` — upload local files separately. `--body` adds detail.
578
+ - `report [TASK_ID] --status in_progress|blocked|done|failed --summary S` — `blocked` needs `--question`; `--audience user` when a human must decide (default `requester`). `--artifact` (repeatable) is a JSON object with non-empty `kind` (backend accepts `file` or `link`), `name` and an `http(s)` `ref` — upload local files separately (`cinna artifacts publish` prints such a link). `--body` adds detail.
579
579
  - **Inside a cloud task**, omit `TASK_ID`: the report goes to the current task using `AGENT_AUTH_TOKEN`, `BACKEND_URL`, `ENV_ID` and the session in `CINNA_SESSION_CONTEXT_PATH` (default `./session_context.json`); the backend checks the session belongs to this environment's agent and owner.
580
580
  - `reply TASK_ID --result-id ID --message M` — answer a blocked question.
581
581
  - `--json` (after the subcommand) prints `{"result": "ok", "delegation": {…}}`.
@@ -590,6 +590,25 @@ cinna delegation report --status done --summary 'Finished' # inside the clou
590
590
 
591
591
  Inside cloud tasks, cinna-core's MCP task server also offers a `handover_report` tool for the same report.
592
592
 
593
+ ### `cinna artifacts publish | list | show | open | download | share | link | delete`
594
+
595
+ Publish a folder or a file to Cinna as an **artifact** — a frozen, versioned copy people open on a Cinna page (a folder with `index.html` runs as a sandboxed static site; other files open in viewers). Run from the account workspace. Every verb first checks the server's artifacts capability and refuses with "This Cinna server does not support artifacts" when it is missing.
596
+
597
+ - `publish PATH [--title T] [--description D] [--entry index.html] [--note N]` — zips the path and prints the page link. `.git/`, `.env*`, `credentials/`, `app-data/`, `.claude/`, `.cinna/`, `node_modules/`, key files and symlinks are never uploaded; the command lists what it left out. Private until shared. Publishing the same path again adds a revision to the same artifact (same link) — the CLI remembers `path → id` in `.cinna/artifacts.json`; `--update ID` names the artifact, `--new` publishes a separate one.
598
+ - `list [--shared]` · `show ID` (revisions, people, links) · `open ID` (browser) · `download ID [--rev N] [-o DIR]` (default `artifacts/<short-id>/`). Ids may be shortened to a unique prefix.
599
+ - `share ID [--instance | --private] [--user EMAIL]… [--remove-user EMAIL]…` and `link create ID [--expires 7d|12h|2w|never] [--code] [--label L] [--rev N]` — **a person runs these, in a terminal**: they refuse under `--json`, `--no-input` or without a TTY (code `needs_person`), and the account workspace's `.claude/settings.json` denies them to Claude Code. `link create` prints the link (and code) once.
600
+ - `link revoke ID LINK` (link id or token prefix, from `show`) · `delete ID` — both confirm; `--yes` is required with `--json`.
601
+ - `--json` (after the subcommand): NDJSON progress lines, then `{"result": "ok", "artifact_id", "action", "revision", "url", "excluded", …}`.
602
+
603
+ ```bash
604
+ cinna artifacts publish ./reports/q3 --title "Q3 churn"
605
+ cinna artifacts publish ./reports/q3 # revision 2, same link
606
+ cinna artifacts share 3fa85f64 --user bob@example.com
607
+ cinna artifacts link create 3fa85f64 --expires 7d --code
608
+ ```
609
+
610
+ The printed page link is a valid `link` artifact for `cinna delegation report --artifact`.
611
+
593
612
  ### `cinna dev`
594
613
 
595
614
  Start a foreground dev session — creates / resumes the Mutagen sync session for this workspace and attaches the terminal to a two-tab TUI (status + raw Mutagen details). Ctrl-C terminates the session; sync does not outlive the TUI. To observe sync from another terminal without affecting it, use `cinna sync status`.
@@ -538,7 +538,7 @@ Hand work to a remote agent as a **durable delegation** — a platform task with
538
538
 
539
539
  - `create --id KEY --target AGENT_UUID --title T --brief B [--execute]` — the identity is `--target` + `--id`: a retry with the same pair returns the original task (changed title/brief are ignored) and never launches execution twice (the backend deduplicates). Prints the backend **task id** the other commands take, and whether it executes. `--depth 2 --root ROOT` makes a sub-delegation (`--root` is required at depth 2, not allowed at depth 1); `--group` tags related work.
540
540
  - `status TASK_ID` — one read: state, latest result, and any open question with its result id. A requester agent should end its turn and let Cinna Desktop deliver the result rather than poll.
541
- - `report [TASK_ID] --status in_progress|blocked|done|failed --summary S` — `blocked` needs `--question`; `--audience user` when a human must decide (default `requester`). `--artifact` (repeatable) is a JSON object with non-empty `kind` (backend accepts `file` or `link`), `name` and an `http(s)` `ref` — upload local files separately. `--body` adds detail.
541
+ - `report [TASK_ID] --status in_progress|blocked|done|failed --summary S` — `blocked` needs `--question`; `--audience user` when a human must decide (default `requester`). `--artifact` (repeatable) is a JSON object with non-empty `kind` (backend accepts `file` or `link`), `name` and an `http(s)` `ref` — upload local files separately (`cinna artifacts publish` prints such a link). `--body` adds detail.
542
542
  - **Inside a cloud task**, omit `TASK_ID`: the report goes to the current task using `AGENT_AUTH_TOKEN`, `BACKEND_URL`, `ENV_ID` and the session in `CINNA_SESSION_CONTEXT_PATH` (default `./session_context.json`); the backend checks the session belongs to this environment's agent and owner.
543
543
  - `reply TASK_ID --result-id ID --message M` — answer a blocked question.
544
544
  - `--json` (after the subcommand) prints `{"result": "ok", "delegation": {…}}`.
@@ -553,6 +553,25 @@ cinna delegation report --status done --summary 'Finished' # inside the clou
553
553
 
554
554
  Inside cloud tasks, cinna-core's MCP task server also offers a `handover_report` tool for the same report.
555
555
 
556
+ ### `cinna artifacts publish | list | show | open | download | share | link | delete`
557
+
558
+ Publish a folder or a file to Cinna as an **artifact** — a frozen, versioned copy people open on a Cinna page (a folder with `index.html` runs as a sandboxed static site; other files open in viewers). Run from the account workspace. Every verb first checks the server's artifacts capability and refuses with "This Cinna server does not support artifacts" when it is missing.
559
+
560
+ - `publish PATH [--title T] [--description D] [--entry index.html] [--note N]` — zips the path and prints the page link. `.git/`, `.env*`, `credentials/`, `app-data/`, `.claude/`, `.cinna/`, `node_modules/`, key files and symlinks are never uploaded; the command lists what it left out. Private until shared. Publishing the same path again adds a revision to the same artifact (same link) — the CLI remembers `path → id` in `.cinna/artifacts.json`; `--update ID` names the artifact, `--new` publishes a separate one.
561
+ - `list [--shared]` · `show ID` (revisions, people, links) · `open ID` (browser) · `download ID [--rev N] [-o DIR]` (default `artifacts/<short-id>/`). Ids may be shortened to a unique prefix.
562
+ - `share ID [--instance | --private] [--user EMAIL]… [--remove-user EMAIL]…` and `link create ID [--expires 7d|12h|2w|never] [--code] [--label L] [--rev N]` — **a person runs these, in a terminal**: they refuse under `--json`, `--no-input` or without a TTY (code `needs_person`), and the account workspace's `.claude/settings.json` denies them to Claude Code. `link create` prints the link (and code) once.
563
+ - `link revoke ID LINK` (link id or token prefix, from `show`) · `delete ID` — both confirm; `--yes` is required with `--json`.
564
+ - `--json` (after the subcommand): NDJSON progress lines, then `{"result": "ok", "artifact_id", "action", "revision", "url", "excluded", …}`.
565
+
566
+ ```bash
567
+ cinna artifacts publish ./reports/q3 --title "Q3 churn"
568
+ cinna artifacts publish ./reports/q3 # revision 2, same link
569
+ cinna artifacts share 3fa85f64 --user bob@example.com
570
+ cinna artifacts link create 3fa85f64 --expires 7d --code
571
+ ```
572
+
573
+ The printed page link is a valid `link` artifact for `cinna delegation report --artifact`.
574
+
556
575
  ### `cinna dev`
557
576
 
558
577
  Start a foreground dev session — creates / resumes the Mutagen sync session for this workspace and attaches the terminal to a two-tab TUI (status + raw Mutagen details). Ctrl-C terminates the session; sync does not outlive the TUI. To observe sync from another terminal without affecting it, use `cinna sync status`.
@@ -297,6 +297,7 @@ authoring convention.
297
297
  | **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) |
298
298
  | **Git versioning** | `cinna git` (link, status, commit, push, pull, log, checkout, unlink) — external remotes with your own credentials, `cinna` remotes with the CLI token | [business](features/git_versioning/git_versioning.md) · [tech](features/git_versioning/git_versioning_tech.md) · [acceptance](features/git_versioning/git_versioning_acceptance.md) |
299
299
  | **Durable delegation** | `cinna delegation` (create — idempotent per target + `--id`, status, report — owner side with a task id or from inside a cloud task, reply), gated on the backend capability version | [business](features/delegation/delegation.md) · [tech](features/delegation/delegation_tech.md) · [acceptance](features/delegation/delegation_acceptance.md) |
300
+ | **Artifacts** | `cinna artifacts` (publish — republishes a remembered path, `--new` / `--update`; list, show, open, download, share and link create — person-only, refused without a TTY; link revoke, delete), gated on the server's artifacts capability | [business](features/artifacts/artifacts.md) · [tech](features/artifacts/artifacts_tech.md) · [acceptance](features/artifacts/artifacts_acceptance.md) |
300
301
  | **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) |
301
302
  | **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) |
302
303
  | **Team library** | `cinna library` (list, get, share, update, status, grants, suggest) — kit folders in `Local/<slug>/` shared through cinna-core-hosted git with the account token; `cinna agent import --from-library` | [business](features/team_library/team_library.md) · [tech](features/team_library/team_library_tech.md) · [acceptance](features/team_library/team_library_acceptance.md) |
@@ -219,7 +219,9 @@ each with its own token, registry entry, and Mutagen session.
219
219
  orchestrator/child `CLAUDE.md` regeneration is offline and per-workspace, so one
220
220
  unreadable child never aborts the rest.
221
221
  - **User-edited config is preserved.** `.claude/settings.json` is create-if-absent
222
- (never clobbered on refresh); `.mcp.json` / `opencode.json` and the generated
222
+ (never clobbered on refresh; an existing file only gains the two `deny` rules
223
+ that keep `cinna artifacts share` / `link create` away from Claude Code — see
224
+ [Artifacts](../artifacts/artifacts.md)); `.mcp.json` / `opencode.json` and the generated
223
225
  `CLAUDE.md` are auto-generated infra and are overwritten.
224
226
  - **Child checkouts are standard.** A synced child is identical to a `cinna setup`
225
227
  checkout — same Model-A layout, same auto-link when git-versioned, same
@@ -53,7 +53,9 @@ the silent-secret and scope-drift bugs live.
53
53
  `account_token` / `platform_url` / `frontend_url` / `machine_name`; an empty
54
54
  `agents/` dir; an orchestrator `CLAUDE.md` (contains "Account Workspace");
55
55
  `.claude/settings.json` with `permissions.allow == ["Bash(cinna:*)",
56
- "mcp__platform-knowledge"]` and `enableAllProjectMcpServers: true`; `.mcp.json`
56
+ "mcp__platform-knowledge"]`, `permissions.deny == ["Bash(cinna artifacts
57
+ share:*)", "Bash(cinna artifacts link create:*)"]` and
58
+ `enableAllProjectMcpServers: true`; `.mcp.json`
57
59
  + `opencode.json` pointing the proxy at `.cinna/account.json` via
58
60
  `CINNA_ACCOUNT_CONFIG`; a `context/` tree; and the next-step hints
59
61
  (`cinna account agents`, `cinna agent sync`).
@@ -226,7 +228,8 @@ the silent-secret and scope-drift bugs live.
226
228
  - **Expected:** `context/` is replaced (old-only files gone); the orchestrator
227
229
  `CLAUDE.md`, `.mcp.json`/`opencode.json`, and **every** synced child's
228
230
  per-agent `CLAUDE.md` are regenerated from the bundled templates; a
229
- user-edited `.claude/settings.json` is left untouched; the run reports how many
231
+ user-edited `.claude/settings.json` keeps every entry and only gains the
232
+ artifact `deny` rules it lacks; the run reports how many
230
233
  child workspaces were regenerated.
231
234
  - **Watch for (non-destructive rule):** simulate a download failure (point at an
232
235
  unreachable platform) and confirm the **old `context/` survives** and the
@@ -388,7 +391,8 @@ the silent-secret and scope-drift bugs live.
388
391
  - **Single-use setup token is never burned on a doomed run** — guards precede the
389
392
  exchange.
390
393
  - **No silent clobber** — `refresh-context` removes the old `context/` only after
391
- a successful download; user-edited `.claude/settings.json` is never overwritten.
394
+ a successful download; user-edited `.claude/settings.json` is never overwritten
395
+ (only the missing artifact `deny` rules are merged in).
392
396
  - **No state drift between commands** — a synced child is a standard per-agent
393
397
  workspace (registry + config) indistinguishable from `cinna setup`; the active
394
398
  user workspace is the single source for both the listing scope and the create
@@ -344,7 +344,9 @@ All consumed by `src/cinna/client.py:AccountClient` with the account token
344
344
  `CLAUDE.md` regeneration is independent of the download.
345
345
  (`tests/test_account.py:test_refresh_context_failure_preserves_old_tree`)
346
346
  - **Settings is create-if-absent** — `_write_account_claude_settings` never
347
- clobbers a user-edited `.claude/settings.json`.
347
+ clobbers a user-edited `.claude/settings.json`; it only appends the missing
348
+ `artifacts.person_only_deny_rules()` to `permissions.deny`, and leaves a file
349
+ that is not a JSON object alone.
348
350
  (`tests/test_account.py:test_refresh_context_preserves_user_claude_settings`)
349
351
  - **Per-agent URL is rejected** — `parse_account_setup_input` must not silently
350
352
  accept a `/cli-setup/<token>` (non-account) URL.
@@ -0,0 +1,138 @@
1
+ # Artifacts (`cinna artifacts`)
2
+
3
+ ## Purpose
4
+
5
+ Publish a folder or a file from the user's machine to cinna-core as an
6
+ **artifact** — a frozen, versioned copy people open on a Cinna page — and get a
7
+ link back. The same command group lists, downloads, opens, shares and deletes
8
+ artifacts from a terminal.
9
+
10
+ ## Mental model / core concepts
11
+
12
+ An artifact lives **in cinna-core**, not on the machine and not in an agent
13
+ environment. The CLI is a publisher and a remote control; once published, the
14
+ artifact outlives the folder, the agent and the session it came from.
15
+
16
+ - **Artifact** — a stable identity (title, who can see it, its page link) over a
17
+ sequence of immutable **revisions**. Core renders it: a folder with an
18
+ `index.html` runs as a small static site in a sandbox; anything else is a file
19
+ set with viewers (Markdown, CSV, PDF, images, code). Not to be confused with a
20
+ delegation's `--artifact` — a link attached to a task result (see Integration
21
+ points).
22
+ - **Revision** — one publish of the files. Republishing adds a revision; the
23
+ page link stays the same and shows the latest one, so a shared link never
24
+ breaks when a report is refreshed.
25
+ - **Path memory** — the CLI remembers which artifact each local path was
26
+ published as, per account workspace (`.cinna/artifacts.json`), and republishes
27
+ into it. Nothing is ever written into the published folder itself.
28
+ - **Exclusions** — what never leaves the machine: `.git/`, `.env*` and `*.env`,
29
+ `credentials/`, `credentials.json`, `app-data/`, `.claude/`, `.cinna/` (it
30
+ holds the account token), `node_modules/`, `__pycache__/`, key and token
31
+ files (`*.pem`, `*.key`, `*.p12`, `id_rsa*` and the other SSH key names,
32
+ `.netrc`, `.npmrc`, `.pypirc`), `.DS_Store`, and every symlink. Mandatory, not
33
+ advisory; core drops its own set again on its side.
34
+ - **Who can see it** — "Only you" (private, the default), plus named people,
35
+ plus "Everyone on the instance", plus **public links** (anyone holding the
36
+ link, signed out; optional expiry, optional 4-digit code, revocable,
37
+ optionally pinned to a revision). The CLI never says "public" for
38
+ instance-wide.
39
+ - **Agents publish, people share** — an agent in the account workspace may
40
+ publish (always private) and manage its artifacts, but widening who can see
41
+ one is the user's call: `share` and `link create` are person-only verbs.
42
+
43
+ ## User flows
44
+
45
+ ### Publish a report and get its link
46
+ 1. `cinna artifacts publish ./reports/q3 --title "Q3 churn"` from the account
47
+ workspace (or any folder below it).
48
+ 2. The CLI prints the file count and size, then what it left out
49
+ (`.git/`, `.env`, …), uploads, and prints the page link and the id.
50
+ 3. The artifact is private; the user opens the link (or
51
+ `cinna artifacts open <id>`) to check it.
52
+
53
+ ### Refresh it later, same link
54
+ 4. Run the same `cinna artifacts publish ./reports/q3` again. The CLI says it
55
+ republished — revision 2, same link — because it remembered the path.
56
+ 5. `--update <id>` republishes into a named artifact instead; `--new` publishes
57
+ a separate artifact even though the path is known (the path then points at
58
+ the new one).
59
+
60
+ ### Share it with people
61
+ 6. In their own terminal the user runs
62
+ `cinna artifacts share <id> --user bob@example.com` and/or `--instance`;
63
+ `--private` and `--remove-user` narrow it again. The CLI prints who can see
64
+ it afterwards.
65
+ 7. For someone outside the instance:
66
+ `cinna artifacts link create <id> [--expires 7d] [--code] [--label …] [--rev N]`
67
+ prints the link (and code) **once**. `cinna artifacts show <id>` lists links
68
+ by id and prefix; `cinna artifacts link revoke <id> <link>` kills one.
69
+
70
+ ### Find, fetch, remove
71
+ 8. `cinna artifacts list` (yours) or `list --shared` (shared with you);
72
+ `show <id>` for revisions, people and links.
73
+ 9. `cinna artifacts download <id> [--rev N] [-o DIR]` extracts the files
74
+ (default `artifacts/<short-id>/` under the account root).
75
+ 10. `cinna artifacts delete <id>` asks first; the page and every link stop
76
+ working at once.
77
+
78
+ ## Business rules
79
+
80
+ - **Server support first** — every verb asks core for its artifacts capability
81
+ before anything else; a server without it (or with another version) gets
82
+ "This Cinna server does not support artifacts" and nothing is uploaded.
83
+ - **A secret path named directly is refused** — publishing `.cinna/account.json`,
84
+ a `credentials/` folder or anything inside an excluded folder stops before
85
+ any request; the walk alone would only see paths below what was named.
86
+ - **Exclusions are listed, always** — the human output and the `--json` result
87
+ name every path left out, the CLI's and the server's together.
88
+ - **Refuse what core would refuse, before uploading** — over the file count,
89
+ per-file size or total size the server advertises, the CLI stops locally.
90
+ - **Republish keeps the title** — on a republish only the flags the user passes
91
+ change (`--title`, `--description`, `--entry`, `--note`).
92
+ - **A remembered artifact that was deleted** — the CLI says so and publishes a
93
+ new one; an explicit `--update <id>` that no longer exists fails instead.
94
+ - **Person-only verbs (A8)** — `share` and `link create` refuse under `--json`,
95
+ `--no-input` / `CINNA_NO_INPUT`, or when stdin is not a terminal, with code
96
+ `needs_person`. There is no `--yes` bypass: an agent can pass any flag. The
97
+ generated `.claude/settings.json` also denies `Bash(cinna artifacts share:*)`
98
+ and `Bash(cinna artifacts link create:*)` to Claude Code. The CLI cannot tell
99
+ an agent from a person — this is a speed bump; core enforces A8 on its
100
+ agent-token route.
101
+ - **A script cannot share either** — a script without a terminal is refused the
102
+ same way. Sharing from automation is deliberately not offered in v1; the web
103
+ page's Share dialog is the alternative.
104
+ - **Destructive verbs confirm** — `delete` and `link revoke` ask (default No);
105
+ under `--json` they need `--yes`, under `--no-input` without `--yes` they
106
+ abort.
107
+ - **The link token is shown once** — core returns it only on creation; the
108
+ api-proxy never relays it, and `show` lists links by prefix only.
109
+ - **Owner-only writes** — share, link and delete check ownership first and say
110
+ so plainly rather than relaying a 404.
111
+
112
+ ## Architecture overview
113
+
114
+ ```
115
+ cinna artifacts publish PATH
116
+ → artifacts.py: walk + exclusions → temp zip (stored entries for very compressible files)
117
+ → AccountClient → POST /cli/account/artifacts[/{id}/revisions] (multipart, account token)
118
+ → .cinna/artifacts.json (path → artifact id)
119
+
120
+ cinna artifacts list|show|share|link revoke|delete
121
+ → AccountClient → api-proxy → /api/v1/artifacts…, /users/search
122
+
123
+ cinna artifacts link create | download
124
+ → AccountClient → /cli/account/artifacts/{id}/links | /download
125
+ ```
126
+
127
+ ## Integration points
128
+
129
+ - [Account workspace](../account_workspace/account_workspace.md) — the account
130
+ token, the active user workspace the artifact lands in, and the generated
131
+ `.claude/settings.json` that carries the deny rules.
132
+ - [Durable delegation](../delegation/delegation.md) — the page link a publish
133
+ prints is a valid `link` artifact for
134
+ `cinna delegation report --artifact '{"kind":"link","name":"…","ref":"<url>"}'`.
135
+ - `cinna api` — the generic api-proxy the JSON verbs ride; it refuses link
136
+ creation and blanks link codes, which is why `link create` has its own route.
137
+ - cinna-core's kit skill `cinna-artifacts` — how to build a folder that works
138
+ in the sandbox (bundle libraries, no `localStorage`, relative paths).
@@ -0,0 +1,140 @@
1
+ # Artifacts — Acceptance Scenarios
2
+
3
+ ## Preconditions
4
+
5
+ - A live cinna-core with artifacts (`curl -s $PLATFORM/.well-known/cinna-desktop`
6
+ shows `"artifacts": 1`) and the frontend reachable for the printed links.
7
+ - An account workspace (`cinna account setup …`) whose token is valid
8
+ (`cinna account status`).
9
+ - A second user on the same instance (for `--user` and `--shared`).
10
+ - The editable install: `which cinna` resolves to this repo's `src/cinna`
11
+ (or `uv run --project <repo> cinna`).
12
+ - A real terminal for scenarios 6–8 — or `script -q /dev/null cinna …` to give
13
+ the command a pseudo-terminal.
14
+ - A test folder:
15
+
16
+ ```bash
17
+ mkdir -p /tmp/art/report/.git /tmp/art/report/sub && cd /tmp/art/report
18
+ echo '<h1>Q3</h1><script src="app.js"></script>' > index.html
19
+ echo 'console.log(1)' > app.js
20
+ printf 'a,b\n1,2\n' > sub/data.csv
21
+ echo 'SECRET=1' > .env
22
+ echo '[core]' > .git/config
23
+ ln -sf /etc/hosts hosts-link
24
+ ```
25
+
26
+ ## Scenario catalog
27
+
28
+ ### 1. Publish prints a link that opens in core
29
+ - **Goal:** a folder becomes a private artifact with a page.
30
+ - **Setup:** no `.cinna/artifacts.json` entry for `/tmp/art/report`.
31
+ - **Steps:**
32
+ ```bash
33
+ cinna artifacts publish /tmp/art/report --title "Q3 churn"
34
+ ```
35
+ - **Expected:** "Publishing … (4 files …)", "Left out 3 paths (never
36
+ published): .git/, .env, hosts-link", "Published Q3 churn — site, revision 1,
37
+ only you can see it", the `/artifact/<id>` link and the id. The link opens
38
+ the page; the site renders.
39
+ - **Watch for:** a root-level `.git/` reaching core (it was once listed under
40
+ "The server also left out", meaning it was uploaded).
41
+
42
+ ### 2. Excluded files are absent from core's revision
43
+ - **Goal:** secrets never leave the machine.
44
+ - **Steps:**
45
+ ```bash
46
+ cinna artifacts download <id> -o /tmp/art/dl && find /tmp/art/dl -type f
47
+ ```
48
+ - **Expected:** `index.html`, `app.js`, `sub/data.csv` only — no `.env`,
49
+ `.git/config`, `hosts-link`.
50
+
51
+ ### 3. Republish without an id, then `--new`
52
+ - **Goal:** refreshing keeps the link (A12).
53
+ - **Steps:**
54
+ ```bash
55
+ echo 'console.log(2)' > /tmp/art/report/app.js
56
+ cinna artifacts publish /tmp/art/report
57
+ cinna artifacts publish /tmp/art/report --new
58
+ cinna artifacts list
59
+ ```
60
+ - **Expected:** the second command says "Republished … as revision 2 — same
61
+ link (remembered …)" with the same id; the third creates a second artifact;
62
+ `list` shows both. A further plain publish republishes the newer one.
63
+ - **Watch for:** a republish that changes the title when `--title` was not
64
+ passed; a duplicate artifact instead of revision 2.
65
+
66
+ ### 4. `--json` emits NDJSON ending in the result line
67
+ - **Steps:**
68
+ ```bash
69
+ cinna artifacts publish /tmp/art/report --json
70
+ ```
71
+ - **Expected:** `{"status": …}` progress lines, then one
72
+ `{"result": "ok", "artifact_id", "action", "revision", "url", "excluded", …}`
73
+ line, nothing else on stdout.
74
+
75
+ ### 5. A deleted remembered artifact
76
+ - **Steps:**
77
+ ```bash
78
+ cinna artifacts delete <id> --json --yes
79
+ cinna artifacts publish /tmp/art/report
80
+ ```
81
+ - **Expected:** if the deleted id was the remembered one, publish creates a new
82
+ artifact (revision 1). With `--update <deleted id>` it fails with the
83
+ server's 404 instead.
84
+
85
+ ### 6. Share and link refuse without a person
86
+ - **Steps:**
87
+ ```bash
88
+ cinna artifacts share <id> --instance --json
89
+ cinna artifacts share <id> --instance --no-input
90
+ echo | cinna artifacts link create <id>
91
+ ```
92
+ - **Expected:** each exits 1 with code `needs_person` and no change on the
93
+ artifact page.
94
+
95
+ ### 7. Share and link work in a terminal
96
+ - **Steps (in a terminal):**
97
+ ```bash
98
+ cinna artifacts share <id> --instance --user second-user@example.com
99
+ cinna artifacts link create <id> --code --label "for bob" --rev 1
100
+ curl -s $PLATFORM/api/v1/artifact-links/<token>/info
101
+ cinna artifacts show <id>
102
+ cinna artifacts link revoke <id> <link id or prefix> --yes
103
+ ```
104
+ - **Expected:** share prints "Everyone on the instance + 1 person"; the second
105
+ user sees it under `list --shared`. Link create prints the `/s/<token>` link,
106
+ the code and the expiry once; `info` reports `needs_code: true`. `show` lists
107
+ the link as `<id> <prefix>… for bob — works until … [code, pinned to rev 1]`.
108
+ After revoke, `info` answers "Link not found".
109
+
110
+ ### 8. Claude Code cannot run the share verbs
111
+ - **Setup:** `cinna account refresh-context`; `.claude/settings.json` holds the
112
+ two `deny` rules.
113
+ - **Steps:** from the account root
114
+ ```bash
115
+ claude -p "Run with the Bash tool: cinna artifacts share <id> --private, then cinna artifacts link create <id>. Quote any error."
116
+ ```
117
+ - **Expected:** both are refused with "Permission to use Bash with command … has
118
+ been denied".
119
+
120
+ ### 9. Older core
121
+ - **Setup:** a core without the artifacts routes (or temporarily point the
122
+ workspace at one).
123
+ - **Steps:** `cinna artifacts list --json`
124
+ - **Expected:** `{"result": "error", "code": "artifacts_unsupported", …}`.
125
+
126
+ ## Cross-cutting invariants
127
+
128
+ - Nothing in the A10 list, nor `.cinna/`, is ever in an uploaded zip.
129
+ - No file is written into the published folder; the path memory lives in the
130
+ account's `.cinna/`.
131
+ - The link token appears only in `link create`'s output.
132
+ - Under `--json` stdout carries JSON lines only, ending in one `result` line.
133
+ - No person-only verb succeeds under `--json`, `--no-input` or without a TTY.
134
+
135
+ ## Cleanup
136
+
137
+ ```bash
138
+ cinna artifacts list --json | jq -r '.artifacts[].id' | xargs -n1 -I{} cinna artifacts delete {} --json --yes
139
+ rm -rf /tmp/art
140
+ ```
@@ -0,0 +1,124 @@
1
+ # Artifacts — Technical Details
2
+
3
+ ## File locations
4
+
5
+ - `src/cinna/artifacts.py` — the `cinna artifacts` Click group, the exclusion
6
+ list and walk, the zip writer, the path memory, the person-only guard.
7
+ - `src/cinna/client.py` — `AccountClient` artifact methods (api-proxy JSON verbs
8
+ and the dedicated multipart / download / link routes).
9
+ - `src/cinna/main.py` — wires the group with `cli.add_command` and attaches
10
+ `--json` / `--no-input` to every leaf (`_attach_driver_options()`).
11
+ - `src/cinna/account.py:_write_account_claude_settings()` — writes or merges the
12
+ Claude Code deny rules.
13
+ - `src/cinna/templates/ACCOUNT_CLAUDE.md.template` — the orchestrator guide's
14
+ "Artifacts" table.
15
+ - `tests/test_artifacts.py` — exclusions, the archive writer, memory, the guard,
16
+ every verb against a respx-mocked platform; `tests/test_account.py` — the
17
+ settings deny rules.
18
+
19
+ ## Command surface
20
+
21
+ - `cinna artifacts publish` — `src/cinna/artifacts.py:publish()`
22
+ - `cinna artifacts list` — `src/cinna/artifacts.py:list_cmd()`
23
+ - `cinna artifacts show` — `src/cinna/artifacts.py:show()`
24
+ - `cinna artifacts open` — `src/cinna/artifacts.py:open_cmd()`
25
+ - `cinna artifacts download` — `src/cinna/artifacts.py:download()`
26
+ - `cinna artifacts share` — `src/cinna/artifacts.py:share()`
27
+ - `cinna artifacts link create` — `src/cinna/artifacts.py:link_create()`
28
+ - `cinna artifacts link revoke` — `src/cinna/artifacts.py:link_revoke()`
29
+ - `cinna artifacts delete` — `src/cinna/artifacts.py:delete()`
30
+
31
+ ## Key functions & flow
32
+
33
+ - `src/cinna/artifacts.py:_session()` — loads the account config, opens an
34
+ `AccountClient`, calls `get_artifacts_capabilities()`; 404/405 or a version
35
+ other than `SUPPORTED_VERSION` raises `_unsupported()` (code
36
+ `artifacts_unsupported`). Yields `_Session` with the capabilities' `limits`.
37
+ - `src/cinna/artifacts.py:refuse_excluded_source()` — every segment of the
38
+ resolved path is checked against the exclusions before the walk.
39
+ - `src/cinna/artifacts.py:collect_files()` — `os.walk(followlinks=False)`;
40
+ prunes excluded directories (recorded as `name/`), records symlinks and
41
+ special files; a single file is published under its own name.
42
+ - `src/cinna/artifacts.py:is_artifact_excluded()` — `kit_contract.is_excluded()`
43
+ over the casefolded path against `ARTIFACT_EXCLUDE`.
44
+ - `src/cinna/artifacts.py:_check_limits()` — file count, per-file and total size
45
+ against the advertised limits, before zipping.
46
+ - `src/cinna/artifacts.py:write_archive()` — zip into a temp dir;
47
+ `_deflate_ratio()` decides per entry whether to store instead of deflate.
48
+ - `src/cinna/artifacts.py:ArtifactMemory` — `lookup()` / `remember()` /
49
+ `forget()` over `.cinna/artifacts.json`.
50
+ - `src/cinna/artifacts.py:_list_all()` — pages `list_artifacts()` (200 per
51
+ page, the server's cap) until `count` is reached; used by `list` and by
52
+ `src/cinna/artifacts.py:_resolve_id()`, where a full UUID passes through and a
53
+ prefix is matched against both scopes.
54
+ - `src/cinna/artifacts.py:require_person()` / `person_only_deny_rules()` — both
55
+ read `PERSON_ONLY_VERBS`.
56
+ - `src/cinna/artifacts.py:_user_id_for()` — exact (casefolded) email match over
57
+ the fuzzy `search_users()` result (25, the server's cap). `share
58
+ --remove-user` falls back to it when a grant carries no email.
59
+ - `src/cinna/artifacts.py:parse_expiry()` — `12h` / `7d` / `2w` / `never`.
60
+ - `src/cinna/artifacts.py:extract_archive()` — refuses absolute paths, `..`,
61
+ symlink entries and anything resolving outside the destination; every entry is
62
+ checked before the first is written, so a refusal leaves nothing behind.
63
+
64
+ ## Config & registry
65
+
66
+ - `.cinna/artifacts.json` in the account root <!-- nocheck --> —
67
+ `{"version": 1, "published": {"<absolute path>": {artifact_id, platform_url,
68
+ title, published_at}}}`. Written atomically (temp file + replace). An entry
69
+ whose `platform_url` differs from the workspace's is ignored.
70
+ - `.claude/settings.json` in the account root <!-- nocheck --> —
71
+ `permissions.deny` gains `Bash(cinna artifacts share:*)` and
72
+ `Bash(cinna artifacts link create:*)`.
73
+ - Nothing is written to `.cinna/config.json` or the per-user agent registry.
74
+
75
+ ## External contracts
76
+
77
+ - `GET /api/v1/artifacts/capabilities` (api-proxy) — `{version: 1, limits:
78
+ {max_revision_bytes, max_revision_files, max_file_bytes, user_quota_bytes}}`.
79
+ - `POST /api/v1/cli/account/artifacts` — multipart `archive` + `title`,
80
+ `description`, `entry_path`, `source_kind=cli`, `source_label`
81
+ (`<machine> · <folder>`), `user_workspace_id` → `ArtifactPublished`
82
+ (`id`, `url`, `revision_number`, `kind`, `excluded`, …).
83
+ - `POST /api/v1/cli/account/artifacts/{id}/revisions` — multipart `archive` +
84
+ optional `title`, `description`, `entry_path`, `note`; owner only.
85
+ - `GET /api/v1/cli/account/artifacts/{id}/download?rev=` — zip, streamed to a
86
+ temp file.
87
+ - `POST /api/v1/cli/account/artifacts/{id}/links` — `{label, expires_at,
88
+ with_code, pinned_revision_id}` → `ArtifactLinkCreated` with `token`, `url`,
89
+ `code`.
90
+ - api-proxy: `GET artifacts?scope=mine|shared_with_me`, `GET artifacts/{id}`
91
+ (links' `code` blanked by the proxy), `PATCH artifacts/{id}` (`visibility`),
92
+ `PUT artifacts/{id}/grants` (full list of `{principal: {user_id}, access:
93
+ "view"}`), `DELETE artifacts/{id}/links/{link_id}`, `DELETE artifacts/{id}`,
94
+ `GET users/search?q=&include_self=true`. The proxy refuses
95
+ `POST artifacts/{id}/links`.
96
+ - Core's ingest rejects a zip entry whose compression ratio exceeds
97
+ `ARTIFACT_MAX_COMPRESSION_RATIO` (100 by default) — the CLI's 50 threshold
98
+ keeps under it.
99
+
100
+ ## Edge cases & guardrails
101
+
102
+ - The kit pattern engine's `**/` does not match zero segments in front of a
103
+ folder pattern, so `ARTIFACT_EXCLUDE` lists every name both bare and under
104
+ `**/` (`src/cinna/artifacts.py:_EXCLUDED_ANYWHERE`). Found live: `.git/` at
105
+ the root reached core before this.
106
+ - `ARTIFACT_EXCLUDE` is not `kit_contract.DEFAULT_EXCLUDE`: that list drops
107
+ `README.md`, `CLAUDE.md` and `temp/`, which are legitimate artifact content.
108
+ - The memory key is `Path.resolve()` of the argument, so `./q3`, `q3/` and an
109
+ absolute path are the same source.
110
+ - A 404 on a republish from memory forgets the entry and creates
111
+ (`publish()`); the same 404 on `--update` propagates.
112
+ - `require_person()` reads `console.json_mode`, `console.no_input` and
113
+ `_stdin_is_tty()` (patchable in tests); the root `--no-input`, the per-command
114
+ `--no-input` and `CINNA_NO_INPUT` all set `console.no_input`.
115
+ - Claude Code deny rules match by command prefix, so `cinna artifacts --json
116
+ share …` or `cinna -v artifacts share …` pass them; those are refused by
117
+ `require_person()` (Claude Code's Bash tool has no TTY on stdin). Verified
118
+ with `claude -p` in a generated workspace: the two prefixes report
119
+ "Permission … has been denied".
120
+ - `_write_account_claude_settings()` merges only into a JSON object whose
121
+ `permissions` / `deny` are an object / list; anything else is left untouched.
122
+ A user who deletes the rules gets them back on the next `refresh-context`.
123
+ - Rich markup in server text is escaped (`escape()`), including the
124
+ `[code, pinned to rev N]` tag of a link line.
@@ -109,7 +109,8 @@ remote control over it; nothing is written to the workspace.
109
109
  with a non-empty `kind`, a non-empty `name`, and an `http(s)` `ref`. The CLI
110
110
  checks only that shape; the backend further restricts `kind` (currently
111
111
  `file` or `link`). Local files must be uploaded separately first; the CLI
112
- never uploads them.
112
+ never uploads them. `cinna artifacts publish` is the way to do that: the page
113
+ link it prints is a valid `link` ref (see [Artifacts](../artifacts/artifacts.md)).
113
114
  - **Executor report is session-bound** — without a task id, `report` needs the
114
115
  environment's agent token, backend URL and environment id, plus the current
115
116
  session context. Missing any of them is a hard failure. The backend verifies
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cinna-cli"
3
- version = "0.5.2"
3
+ version = "0.5.3"
4
4
  description = "Local development CLI for Cinna Core agents"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -795,17 +795,44 @@ def _write_account_claude_settings(account_root: Path) -> None:
795
795
  Code doesn't prompt "New MCP server found in this project" on first launch,
796
796
  and the ``mcp__platform-knowledge`` allow rule pre-approves that server's
797
797
  tool calls (e.g. ``knowledge_query``) so each invocation doesn't prompt.
798
+ The ``deny`` rules keep the person-only artifact verbs (``share``, ``link
799
+ create``) away from Claude Code even under the ``Bash(cinna:*)`` allow
800
+ (A8: agents publish, people share; the verbs also refuse without a TTY).
801
+
798
802
  Create-if-absent: never clobbers a user's own edits (so it is safe to call
799
- again from ``refresh-context``).
803
+ again from ``refresh-context``). An existing file only gains the deny rules
804
+ it lacks; one that is not a JSON object is left alone.
800
805
  """
806
+ from cinna.artifacts import person_only_deny_rules
807
+
808
+ deny = person_only_deny_rules()
801
809
  settings_path = account_root / ".claude" / "settings.json"
802
810
  if settings_path.exists():
811
+ try:
812
+ settings = json.loads(settings_path.read_text())
813
+ except (OSError, ValueError):
814
+ return
815
+ permissions = settings.get("permissions") if isinstance(settings, dict) else None
816
+ if permissions is None and isinstance(settings, dict):
817
+ permissions = settings["permissions"] = {}
818
+ if not isinstance(permissions, dict):
819
+ return
820
+ existing = permissions.get("deny")
821
+ if existing is None:
822
+ existing = permissions["deny"] = []
823
+ if not isinstance(existing, list):
824
+ return
825
+ missing = [rule for rule in deny if rule not in existing]
826
+ if missing:
827
+ existing.extend(missing)
828
+ settings_path.write_text(json.dumps(settings, indent=2) + "\n")
803
829
  return
804
830
  settings_path.parent.mkdir(parents=True, exist_ok=True)
805
831
  settings = {
806
832
  "enableAllProjectMcpServers": True,
807
833
  "permissions": {
808
834
  "allow": ["Bash(cinna:*)", "mcp__platform-knowledge"],
835
+ "deny": deny,
809
836
  },
810
837
  }
811
838
  settings_path.write_text(json.dumps(settings, indent=2) + "\n")