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.
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/PKG-INFO +21 -2
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/README.md +20 -1
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/README.md +1 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/account_workspace/account_workspace.md +3 -1
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/account_workspace/account_workspace_acceptance.md +7 -3
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/account_workspace/account_workspace_tech.md +3 -1
- cinna_cli-0.5.3/docs/features/artifacts/artifacts.md +138 -0
- cinna_cli-0.5.3/docs/features/artifacts/artifacts_acceptance.md +140 -0
- cinna_cli-0.5.3/docs/features/artifacts/artifacts_tech.md +124 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/delegation/delegation.md +2 -1
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/pyproject.toml +1 -1
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/account.py +28 -1
- cinna_cli-0.5.3/src/cinna/artifacts.py +971 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/client.py +102 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/main.py +17 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/templates/ACCOUNT_CLAUDE.md.template +10 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_account.py +18 -2
- cinna_cli-0.5.3/tests/test_artifacts.py +645 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/uv.lock +1 -1
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/.claude/commands/cinna-cli.feature.doc.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/.github/workflows/publish.yml +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/.gitignore +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/LICENSE.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_addons/agent_addons.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_addons/agent_addons_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_addons/agent_addons_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_api/agent_api.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_api/agent_api_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_api/agent_api_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_management/agent_management.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_management/agent_management_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_management/agent_management_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_schedules/agent_schedules.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_schedules/agent_schedules_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/agent_schedules/agent_schedules_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/bootstrap_onboarding/bootstrap_onboarding.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/bootstrap_onboarding/bootstrap_onboarding_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/bootstrap_onboarding/bootstrap_onboarding_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/delegation/delegation_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/delegation/delegation_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/doctor/doctor.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/doctor/doctor_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/doctor/doctor_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/git_versioning/git_versioning.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/git_versioning/git_versioning_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/git_versioning/git_versioning_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/improvement_requests/improvement_requests.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/improvement_requests/improvement_requests_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/improvement_requests/improvement_requests_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/live_sync/live_sync.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/live_sync/live_sync_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/live_sync/live_sync_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/local_agent_import/local_agent_import.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/local_agent_import/local_agent_import_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/local_agent_import/local_agent_import_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/mcp_integration/mcp_integration.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/mcp_integration/mcp_integration_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/mcp_integration/mcp_integration_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/remote_chat/remote_chat.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/remote_chat/remote_chat_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/remote_chat/remote_chat_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/remote_exec/remote_exec.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/remote_exec/remote_exec_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/remote_exec/remote_exec_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/team_library/team_library.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/team_library/team_library_acceptance.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/team_library/team_library_tech.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/interface.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/mutagen_capabilities.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/scripts/check_docs_references.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/__init__.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/auth.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/bootstrap.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/chat.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/cli_version.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/config.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/console.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/context.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/delegation.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/doctor.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/errors.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/git_versioning.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/improve.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/kit_contract.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/library.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/local_import.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/logging.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/mcp_proxy.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/mutagen_runtime.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/scenarios.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/sync.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/sync_session.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/sync_ssh_shim.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/sync_tui.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/templates/CHAT_TESTING.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/templates/CLAUDE.md.template +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/templates/GIT_VERSIONING.md +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/src/cinna/templates/__init__.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/__init__.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/conftest.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_agent_api_access.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_auth.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_bootstrap.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_chat.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_cli_version.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_client.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_config.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_console.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_context.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_delegation.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_doctor.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_git_versioning.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_improve.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_kit_contract.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_library.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_local_import.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_main.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_mutagen_runtime.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_onboarding.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_scenarios.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_sync.py +0 -0
- {cinna_cli-0.5.2 → cinna_cli-0.5.3}/tests/test_sync_session.py +0 -0
- {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.
|
|
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
|
|
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
|
{cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/account_workspace/account_workspace_acceptance.md
RENAMED
|
@@ -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"]`
|
|
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`
|
|
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
|
{cinna_cli-0.5.2 → cinna_cli-0.5.3}/docs/features/account_workspace/account_workspace_tech.md
RENAMED
|
@@ -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
|
|
@@ -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")
|