cinna-cli 0.5.1__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.1 → cinna_cli-0.5.3}/PKG-INFO +40 -3
  2. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/README.md +39 -2
  3. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/README.md +4 -2
  4. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/account_workspace/account_workspace.md +9 -2
  5. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/account_workspace/account_workspace_acceptance.md +7 -3
  6. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/account_workspace/account_workspace_tech.md +23 -1
  7. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/agent_api/agent_api.md +82 -3
  8. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/agent_api/agent_api_acceptance.md +112 -1
  9. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/agent_api/agent_api_tech.md +117 -2
  10. cinna_cli-0.5.3/docs/features/artifacts/artifacts.md +138 -0
  11. cinna_cli-0.5.3/docs/features/artifacts/artifacts_acceptance.md +140 -0
  12. cinna_cli-0.5.3/docs/features/artifacts/artifacts_tech.md +124 -0
  13. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/delegation/delegation.md +2 -1
  14. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/git_versioning/git_versioning.md +26 -2
  15. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/git_versioning/git_versioning_acceptance.md +29 -0
  16. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/git_versioning/git_versioning_tech.md +26 -2
  17. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/local_agent_import/local_agent_import.md +4 -0
  18. cinna_cli-0.5.3/docs/features/team_library/team_library.md +128 -0
  19. cinna_cli-0.5.3/docs/features/team_library/team_library_acceptance.md +136 -0
  20. cinna_cli-0.5.3/docs/features/team_library/team_library_tech.md +129 -0
  21. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/pyproject.toml +1 -1
  22. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/account.py +537 -14
  23. cinna_cli-0.5.3/src/cinna/artifacts.py +971 -0
  24. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/client.py +304 -2
  25. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/git_versioning.py +178 -11
  26. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/kit_contract.py +1 -1
  27. cinna_cli-0.5.3/src/cinna/library.py +1203 -0
  28. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/local_import.py +66 -3
  29. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/main.py +318 -10
  30. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/templates/ACCOUNT_CLAUDE.md.template +36 -2
  31. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/templates/GIT_VERSIONING.md +7 -0
  32. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_account.py +300 -4
  33. cinna_cli-0.5.3/tests/test_agent_api_access.py +512 -0
  34. cinna_cli-0.5.3/tests/test_artifacts.py +645 -0
  35. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_client.py +132 -0
  36. cinna_cli-0.5.3/tests/test_library.py +743 -0
  37. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_local_import.py +101 -1
  38. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/uv.lock +1 -1
  39. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/.claude/commands/cinna-cli.feature.doc.md +0 -0
  40. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/.github/workflows/publish.yml +0 -0
  41. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/.gitignore +0 -0
  42. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/LICENSE.md +0 -0
  43. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/agent_addons/agent_addons.md +0 -0
  44. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/agent_addons/agent_addons_acceptance.md +0 -0
  45. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/agent_addons/agent_addons_tech.md +0 -0
  46. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/agent_management/agent_management.md +0 -0
  47. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/agent_management/agent_management_acceptance.md +0 -0
  48. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/agent_management/agent_management_tech.md +0 -0
  49. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/agent_schedules/agent_schedules.md +0 -0
  50. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/agent_schedules/agent_schedules_acceptance.md +0 -0
  51. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/agent_schedules/agent_schedules_tech.md +0 -0
  52. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/bootstrap_onboarding/bootstrap_onboarding.md +0 -0
  53. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/bootstrap_onboarding/bootstrap_onboarding_acceptance.md +0 -0
  54. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/bootstrap_onboarding/bootstrap_onboarding_tech.md +0 -0
  55. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/delegation/delegation_acceptance.md +0 -0
  56. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/delegation/delegation_tech.md +0 -0
  57. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/doctor/doctor.md +0 -0
  58. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/doctor/doctor_acceptance.md +0 -0
  59. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/doctor/doctor_tech.md +0 -0
  60. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/improvement_requests/improvement_requests.md +0 -0
  61. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/improvement_requests/improvement_requests_acceptance.md +0 -0
  62. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/improvement_requests/improvement_requests_tech.md +0 -0
  63. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/live_sync/live_sync.md +0 -0
  64. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/live_sync/live_sync_acceptance.md +0 -0
  65. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/live_sync/live_sync_tech.md +0 -0
  66. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/local_agent_import/local_agent_import_acceptance.md +0 -0
  67. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/local_agent_import/local_agent_import_tech.md +0 -0
  68. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/mcp_integration/mcp_integration.md +0 -0
  69. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/mcp_integration/mcp_integration_acceptance.md +0 -0
  70. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/mcp_integration/mcp_integration_tech.md +0 -0
  71. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/remote_chat/remote_chat.md +0 -0
  72. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/remote_chat/remote_chat_acceptance.md +0 -0
  73. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/remote_chat/remote_chat_tech.md +0 -0
  74. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/remote_exec/remote_exec.md +0 -0
  75. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/remote_exec/remote_exec_acceptance.md +0 -0
  76. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/features/remote_exec/remote_exec_tech.md +0 -0
  77. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/interface.md +0 -0
  78. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/docs/mutagen_capabilities.md +0 -0
  79. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/scripts/check_docs_references.py +0 -0
  80. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/__init__.py +0 -0
  81. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/auth.py +0 -0
  82. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/bootstrap.py +0 -0
  83. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/chat.py +0 -0
  84. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/cli_version.py +0 -0
  85. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/config.py +0 -0
  86. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/console.py +0 -0
  87. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/context.py +0 -0
  88. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/delegation.py +0 -0
  89. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/doctor.py +0 -0
  90. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/errors.py +0 -0
  91. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/improve.py +0 -0
  92. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/logging.py +0 -0
  93. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/mcp_proxy.py +0 -0
  94. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/mutagen_runtime.py +0 -0
  95. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/scenarios.py +0 -0
  96. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/sync.py +0 -0
  97. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/sync_session.py +0 -0
  98. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/sync_ssh_shim.py +0 -0
  99. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/sync_tui.py +0 -0
  100. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/templates/CHAT_TESTING.md +0 -0
  101. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/templates/CLAUDE.md.template +0 -0
  102. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/src/cinna/templates/__init__.py +0 -0
  103. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/__init__.py +0 -0
  104. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/conftest.py +0 -0
  105. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_auth.py +0 -0
  106. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_bootstrap.py +0 -0
  107. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_chat.py +0 -0
  108. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_cli_version.py +0 -0
  109. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_config.py +0 -0
  110. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_console.py +0 -0
  111. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_context.py +0 -0
  112. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_delegation.py +0 -0
  113. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_doctor.py +0 -0
  114. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_git_versioning.py +0 -0
  115. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_improve.py +0 -0
  116. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_kit_contract.py +0 -0
  117. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_main.py +0 -0
  118. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_mutagen_runtime.py +0 -0
  119. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_onboarding.py +0 -0
  120. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_scenarios.py +0 -0
  121. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_sync.py +0 -0
  122. {cinna_cli-0.5.1 → cinna_cli-0.5.3}/tests/test_sync_session.py +0 -0
  123. {cinna_cli-0.5.1 → 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.1
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
@@ -486,6 +486,24 @@ Take a package out of the catalog. Agents that already installed it keep what th
486
486
 
487
487
  Put a delisted package back. `delist` has no inverse route of its own — without this verb the only way back would be `cinna api`, which would make delisting the one door in this group that opens only outwards.
488
488
 
489
+ ### `cinna agent-api access list | issue | revoke | scopes`
490
+
491
+ Give people (or your own local agent) API access to a producer, from the account workspace. A key is issued to a **person** and lands in their credentials; **its value never appears in the CLI** — not on self-issue, not in `--json`. Reveal and rotate live on the web credential page, and so does the external-access switch (when it is off, `issue` is refused with the producer's Integrations URL). Writes need the `agent-developer` role.
492
+
493
+ - `list <producer> [--all] [--json]` — people, their scopes and keys (prefix, status, expiry, last used, linked agents, id). Ended keys (revoked, expired, removed) are hidden unless `--all`; `--json` is always the full listing.
494
+ - `issue <producer> --to <email|me> [--scopes a,b] [--expires-in-days N] [--service-uri SLOT] [--label TEXT] [--read-only] [--json]` — `--to` is required. `me` puts the key in your own credentials and prints the web URL where you reveal it; a local agent in Cinna Desktop calls it with `cinna_credentials.agent_api_session("<slot>")`, a cloud agent is wired with `cinna connect agent-api` instead.
495
+ - `revoke <producer> <key> [--yes] [--json]` — `<key>` is the key id or the exact token prefix (an elided one is refused). Asks first; `--json` needs `--yes`; an already revoked key is reported, exit 0. The person's scopes stay.
496
+ - `scopes <producer> --to <email|me> (--set a,b | --clear) [--json]` — change a grant without touching keys.
497
+
498
+ ```bash
499
+ cinna agent-api access issue orders-agent --to me --service-uri orders-api
500
+ cinna agent-api access issue orders-agent --to ana@example.com --scopes read --expires-in-days 30
501
+ cinna agent-api access list orders-agent --all
502
+ cinna agent-api access revoke orders-agent a1b2c3d4
503
+ ```
504
+
505
+ `cinna account credentials list` shows an issued key's state in its Status column, and a skill slot filled by a revoked or expired key reads as not usable. `cinna agent import` does not draft `agent_api` credentials; it prints the `access issue` / `connect agent-api` command instead.
506
+
489
507
  ### `cinna connect agent-api --producer <agent> --consumer <agent> [--label TEXT] [--read-only]`
490
508
 
491
509
  Wire one agent to another's REST API from the account workspace. Resolves both agents (name, slug, or ID), mints a producer API token, and attaches it to the consumer as a credential — which rides the consumer's normal credential sync into its remote environment, so no key ever touches your machine. Prints the credential ID, token prefix, base URL, and spec URL. `--read-only` restricts the consumer to read-only access; `--label` names the credential. Backend errors surface verbatim: 400 if the producer's REST API is disabled, 403/404 for ownership violations.
@@ -508,7 +526,7 @@ Generic escape hatch into the platform API, authenticated with the account token
508
526
 
509
527
  - `--json '<obj>'` or `--data @file.json` supply a JSON request body (mutually exclusive); repeatable `--query k=v` supplies query parameters (repeating a key builds a list).
510
528
  - The inner response is passed through verbatim: the body prints to stdout (pretty-printed for JSON) and the exit code is `0` for 2xx and `1` for an inner 4xx/5xx — so it composes in shell pipelines.
511
- - When the escape hatch itself refuses the call, the detail prints to stderr and the exit code is `2`: policy denials (credentials, user management, admin, CLI, MFA/auth, and streaming routes are excluded — shown as `blocked by platform policy: …`), rate limiting (429, with the Retry-After delay), and request/response size caps (413/502).
529
+ - When the escape hatch itself refuses the call, the detail prints to stderr and the exit code is `2`: policy denials (credentials, user management, admin, CLI, MFA/auth, and streaming routes are excluded, as are routes that would hand out a secret: any method on `agents/*/agent-api/keys…` (issued keys), on `environments/*/workspace/download` and `view-file`, and on `dashboards/*/blocks/*/env-file`; and POST on A2A access-tokens, MCP/ACP connector tokens, agent and task-trigger webhook create/regenerate-token. Paths are matched after percent-decoding. — shown as `blocked by platform policy: …`), rate limiting (429, with the Retry-After delay), and request/response size caps (413/502).
512
530
 
513
531
  `<path>` accepts the same **agent references** the rest of the CLI does: a segment straight after `agents/` that is not already a UUID is resolved against the account's agent listing, and the substitution is announced on stderr so a `--json` stdout stream stays pure. Resolution is sugar and never breaks the hatch — a reference that does not resolve (or an agent listing that cannot be reached) leaves the path exactly as typed, because `agents/` is a route prefix as well as a collection.
514
532
 
@@ -557,7 +575,7 @@ Hand work to a remote agent as a **durable delegation** — a platform task with
557
575
 
558
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.
559
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.
560
- - `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.
561
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.
562
580
  - `reply TASK_ID --result-id ID --message M` — answer a blocked question.
563
581
  - `--json` (after the subcommand) prints `{"result": "ok", "delegation": {…}}`.
@@ -572,6 +590,25 @@ cinna delegation report --status done --summary 'Finished' # inside the clou
572
590
 
573
591
  Inside cloud tasks, cinna-core's MCP task server also offers a `handover_report` tool for the same report.
574
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
+
575
612
  ### `cinna dev`
576
613
 
577
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`.
@@ -449,6 +449,24 @@ Take a package out of the catalog. Agents that already installed it keep what th
449
449
 
450
450
  Put a delisted package back. `delist` has no inverse route of its own — without this verb the only way back would be `cinna api`, which would make delisting the one door in this group that opens only outwards.
451
451
 
452
+ ### `cinna agent-api access list | issue | revoke | scopes`
453
+
454
+ Give people (or your own local agent) API access to a producer, from the account workspace. A key is issued to a **person** and lands in their credentials; **its value never appears in the CLI** — not on self-issue, not in `--json`. Reveal and rotate live on the web credential page, and so does the external-access switch (when it is off, `issue` is refused with the producer's Integrations URL). Writes need the `agent-developer` role.
455
+
456
+ - `list <producer> [--all] [--json]` — people, their scopes and keys (prefix, status, expiry, last used, linked agents, id). Ended keys (revoked, expired, removed) are hidden unless `--all`; `--json` is always the full listing.
457
+ - `issue <producer> --to <email|me> [--scopes a,b] [--expires-in-days N] [--service-uri SLOT] [--label TEXT] [--read-only] [--json]` — `--to` is required. `me` puts the key in your own credentials and prints the web URL where you reveal it; a local agent in Cinna Desktop calls it with `cinna_credentials.agent_api_session("<slot>")`, a cloud agent is wired with `cinna connect agent-api` instead.
458
+ - `revoke <producer> <key> [--yes] [--json]` — `<key>` is the key id or the exact token prefix (an elided one is refused). Asks first; `--json` needs `--yes`; an already revoked key is reported, exit 0. The person's scopes stay.
459
+ - `scopes <producer> --to <email|me> (--set a,b | --clear) [--json]` — change a grant without touching keys.
460
+
461
+ ```bash
462
+ cinna agent-api access issue orders-agent --to me --service-uri orders-api
463
+ cinna agent-api access issue orders-agent --to ana@example.com --scopes read --expires-in-days 30
464
+ cinna agent-api access list orders-agent --all
465
+ cinna agent-api access revoke orders-agent a1b2c3d4
466
+ ```
467
+
468
+ `cinna account credentials list` shows an issued key's state in its Status column, and a skill slot filled by a revoked or expired key reads as not usable. `cinna agent import` does not draft `agent_api` credentials; it prints the `access issue` / `connect agent-api` command instead.
469
+
452
470
  ### `cinna connect agent-api --producer <agent> --consumer <agent> [--label TEXT] [--read-only]`
453
471
 
454
472
  Wire one agent to another's REST API from the account workspace. Resolves both agents (name, slug, or ID), mints a producer API token, and attaches it to the consumer as a credential — which rides the consumer's normal credential sync into its remote environment, so no key ever touches your machine. Prints the credential ID, token prefix, base URL, and spec URL. `--read-only` restricts the consumer to read-only access; `--label` names the credential. Backend errors surface verbatim: 400 if the producer's REST API is disabled, 403/404 for ownership violations.
@@ -471,7 +489,7 @@ Generic escape hatch into the platform API, authenticated with the account token
471
489
 
472
490
  - `--json '<obj>'` or `--data @file.json` supply a JSON request body (mutually exclusive); repeatable `--query k=v` supplies query parameters (repeating a key builds a list).
473
491
  - The inner response is passed through verbatim: the body prints to stdout (pretty-printed for JSON) and the exit code is `0` for 2xx and `1` for an inner 4xx/5xx — so it composes in shell pipelines.
474
- - When the escape hatch itself refuses the call, the detail prints to stderr and the exit code is `2`: policy denials (credentials, user management, admin, CLI, MFA/auth, and streaming routes are excluded — shown as `blocked by platform policy: …`), rate limiting (429, with the Retry-After delay), and request/response size caps (413/502).
492
+ - When the escape hatch itself refuses the call, the detail prints to stderr and the exit code is `2`: policy denials (credentials, user management, admin, CLI, MFA/auth, and streaming routes are excluded, as are routes that would hand out a secret: any method on `agents/*/agent-api/keys…` (issued keys), on `environments/*/workspace/download` and `view-file`, and on `dashboards/*/blocks/*/env-file`; and POST on A2A access-tokens, MCP/ACP connector tokens, agent and task-trigger webhook create/regenerate-token. Paths are matched after percent-decoding. — shown as `blocked by platform policy: …`), rate limiting (429, with the Retry-After delay), and request/response size caps (413/502).
475
493
 
476
494
  `<path>` accepts the same **agent references** the rest of the CLI does: a segment straight after `agents/` that is not already a UUID is resolved against the account's agent listing, and the substitution is announced on stderr so a `--json` stdout stream stays pure. Resolution is sugar and never breaks the hatch — a reference that does not resolve (or an agent listing that cannot be reached) leaves the path exactly as typed, because `agents/` is a route prefix as well as a collection.
477
495
 
@@ -520,7 +538,7 @@ Hand work to a remote agent as a **durable delegation** — a platform task with
520
538
 
521
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.
522
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.
523
- - `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.
524
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.
525
543
  - `reply TASK_ID --result-id ID --message M` — answer a blocked question.
526
544
  - `--json` (after the subcommand) prints `{"result": "ok", "delegation": {…}}`.
@@ -535,6 +553,25 @@ cinna delegation report --status done --summary 'Finished' # inside the clou
535
553
 
536
554
  Inside cloud tasks, cinna-core's MCP task server also offers a `handover_report` tool for the same report.
537
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
+
538
575
  ### `cinna dev`
539
576
 
540
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`.
@@ -292,13 +292,15 @@ authoring convention.
292
292
  | **Live sync** | `cinna sync` (status, conflicts `[--diff]`, push, pull, resolve) + Mutagen transport; the session is started by `cinna agent sync` so the first push has a baseline | [business](features/live_sync/live_sync.md) · [tech](features/live_sync/live_sync_tech.md) · [acceptance](features/live_sync/live_sync_acceptance.md) |
293
293
  | **Remote exec** | `cinna exec` | [business](features/remote_exec/remote_exec.md) · [tech](features/remote_exec/remote_exec_tech.md) · [acceptance](features/remote_exec/remote_exec_acceptance.md) |
294
294
  | **Remote chat** | `cinna chat` (incl. `--attach` / `--show` to recover or read an existing session; poll failures retried within `--timeout`) · `cinna agent scenarios list / run` (replay `docs/test_scenarios/` Say rows, one session per row) | <!-- nocheck: agent-workspace path --> [business](features/remote_chat/remote_chat.md) · [tech](features/remote_chat/remote_chat_tech.md) · [acceptance](features/remote_chat/remote_chat_acceptance.md) |
295
- | **Agent API** | `cinna agent-api` (enable, refresh, spec, call) · `cinna api` · `cinna connect agent-api` | [business](features/agent_api/agent_api.md) · [tech](features/agent_api/agent_api_tech.md) · [acceptance](features/agent_api/agent_api_acceptance.md) |
295
+ | **Agent API** | `cinna agent-api` (enable, refresh, spec, call, access list/issue/revoke/scopes) · `cinna api` · `cinna connect agent-api` | [business](features/agent_api/agent_api.md) · [tech](features/agent_api/agent_api_tech.md) · [acceptance](features/agent_api/agent_api_acceptance.md) |
296
296
  | **Agent addons** | `cinna skills` (list — with each declared credential slot's readiness, publish, install, uninstall, update, toggle, refresh, catalog, show, revisions, files, grants, grant, revoke, visibility, delist, relist) — the whole skills lifecycle: what an agent carries, publishing one of its `skills/<name>/` folders to the catalog, installing a package on another agent and keeping it current, and sharing after the fact | [business](features/agent_addons/agent_addons.md) · [tech](features/agent_addons/agent_addons_tech.md) · [acceptance](features/agent_addons/agent_addons_acceptance.md) |
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
- | **Git versioning** | `cinna git` (link, status, commit, push, pull, log, checkout, unlink) | [business](features/git_versioning/git_versioning.md) · [tech](features/git_versioning/git_versioning_tech.md) · [acceptance](features/git_versioning/git_versioning_acceptance.md) |
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) |
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) |
302
304
 
303
305
  The sections below (Git Versioning, Sync Transport, Remote Exec, Remote Chat,
304
306
  Bootstrap Flow) remain as in-README quick references and backend contracts; the
@@ -156,7 +156,12 @@ each with its own token, registry entry, and Mutagen session.
156
156
  step; `--workspace` overrides the target workspace.
157
157
  - `cinna account credentials list` shows credentials with their type, **slot**,
158
158
  setup status (complete / needs setup, plus a placeholder marker) and full id —
159
- metadata only. The slot is the credential's service URI: the non-secret id a
159
+ metadata only. For an issued Agent API key the Status cell shows the key's
160
+ state instead (Active; API turned off by producer / External access paused;
161
+ Revoked by issuer / Producer removed / Issuer account removed / Expired), and
162
+ `agent show` appends it when not active. A draft with `--type agent_api` is
163
+ refused by the platform: such a credential has no key behind it; use
164
+ `cinna agent-api access issue` (see [agent_api](../agent_api/agent_api.md)). The slot is the credential's service URI: the non-secret id a
160
165
  skill's `credentials:` block names and a script looks the credential up by, so
161
166
  it is the column that answers "which credential fills this skill's slot". The
162
167
  hint under the table names `cinna account credentials update <id> --service-uri
@@ -214,7 +219,9 @@ each with its own token, registry entry, and Mutagen session.
214
219
  orchestrator/child `CLAUDE.md` regeneration is offline and per-workspace, so one
215
220
  unreadable child never aborts the rest.
216
221
  - **User-edited config is preserved.** `.claude/settings.json` is create-if-absent
217
- (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
218
225
  `CLAUDE.md` are auto-generated infra and are overwritten.
219
226
  - **Child checkouts are standard.** A synced child is identical to a `cinna setup`
220
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
@@ -216,6 +216,26 @@ Related (documented here as integration points): `cinna agent sync` →
216
216
  - `src/cinna/account.py:run_credentials_update()` — refuses an empty update;
217
217
  metadata fields only (`name`/`notes`/`service_uri`/`allow_sharing`).
218
218
  - `src/cinna/account.py:_credential_status_cell()` — complete / needs setup / —.
219
+ - `src/cinna/account.py:_key_status_cell()` / `_KEY_STATUS_LABELS` — an issued
220
+ key's status as a coloured label; `run_credentials_list()` uses it in place of
221
+ the complete / needs-setup cell when `agent_api_key_status` is set, and
222
+ `_credential_summary()` appends it when not `active`.
223
+ `agent_api_key_status` is in `_ACCOUNT_AUTHORITATIVE_FIELDS`, so linked-credential
224
+ overlays carry it. (`tests/test_account.py`)
225
+ - Skill slot readiness: `src/cinna/account.py:_slot_readiness()` treats a filled
226
+ carrier whose key status is not `active` as unusable and returns
227
+ `key_revoked` (revoked_by_issuer / producer_removed / issuer_removed),
228
+ `key_expired`, `key_paused` (api_disabled / external_access_paused) or
229
+ `key_unavailable` (a status this CLI does not know), via
230
+ `_key_unavailable_state()` / `_dead_key_carrier()` (every filled carrier of the
231
+ slot, of the declared type, holds a dead or paused key). A newer platform
232
+ flags such a slot itself as `access_revoked`; the CLI refines that reason to the
233
+ key state so the remedy fits. An older platform names no issue for catalog rows,
234
+ so the CLI checks key status there too. `_needs_linked_credentials()` fetches
235
+ the linked credentials for catalog rows with an `access_revoked` issue or an
236
+ unflagged declared slot. `_slot_remedy()` words the fix: ask the producer's owner
237
+ for a new key (`agent_api_issue_hint()`) for revoked/expired, "recovers by
238
+ itself" for paused, upgrade the CLI for unknown.
219
239
  - `src/cinna/account.py:run_credentials_list()` — `--json` echoes the listing
220
240
  untouched; otherwise the table adds a `Slot` column (`_credential_slot_cell()`,
221
241
  the escaped `service_uri` or a dim dash), marks `is_placeholder`, never wraps the
@@ -324,7 +344,9 @@ All consumed by `src/cinna/client.py:AccountClient` with the account token
324
344
  `CLAUDE.md` regeneration is independent of the download.
325
345
  (`tests/test_account.py:test_refresh_context_failure_preserves_old_tree`)
326
346
  - **Settings is create-if-absent** — `_write_account_claude_settings` never
327
- 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.
328
350
  (`tests/test_account.py:test_refresh_context_preserves_user_claude_settings`)
329
351
  - **Per-agent URL is rejected** — `parse_account_setup_input` must not silently
330
352
  accept a `/cli-setup/<token>` (non-account) URL.
@@ -5,7 +5,7 @@
5
5
  Let an agent **expose its own HTTP/OpenAPI surface** (a "producer" REST API)
6
6
  that another agent (a "consumer") can call, and give a coding agent the local
7
7
  loop to build, verify, and wire those connections from the account workspace:
8
- `cinna agent-api` (enable / refresh / spec / call) drives the
8
+ `cinna agent-api` (enable / refresh / spec / call / access) drives the
9
9
  **build→verify** loop on a producer; `cinna connect agent-api` does the
10
10
  **one-click wiring** of a consumer to it; and `cinna api` is a separate
11
11
  **escape hatch** for calling the platform's *own* control-plane API directly.
@@ -67,7 +67,8 @@ to test a producer endpoint — use `cinna agent-api call`.
67
67
  consumer authenticates its real calls with it.
68
68
  - **Escape hatch** — the account `api-proxy` route `cinna api` rides: a buffered
69
69
  JSON pass-through to the platform's own API, with excluded categories
70
- (credentials, user management, admin, CLI, MFA/auth, streaming) denied.
70
+ (credentials, user management, admin, CLI, MFA/auth, streaming, and the
71
+ secret-minting routes listed under "Escape-hatch deny-list") denied.
71
72
 
72
73
  ## User flows
73
74
 
@@ -97,6 +98,35 @@ to test a producer endpoint — use `cinna agent-api call`.
97
98
  appears read-only under `workspace/credentials/` in a synced workspace) —
98
99
  no manual key handling.
99
100
 
101
+ ### Give API access (a person, or a local agent)
102
+ - `cinna agent-api access list <producer>` shows who has access: one row per
103
+ person with their scopes and keys (prefix, status, expiry, last use, how many
104
+ agents use it, key id). `--all` also shows ended keys; `--json` is always the
105
+ full listing. The footer says whether external access is on and which scopes
106
+ the producer declares.
107
+ - `cinna agent-api access issue <producer> --to <email|me>` issues a key to a
108
+ person. `--scopes a,b` sets their scopes (`""` = an explicit empty grant),
109
+ `--expires-in-days N` (1-3650), `--service-uri SLOT` names the credential's
110
+ slot (default `<producer>-api`), `--label`, `--read-only`.
111
+ - `--to me`: the key lands in your own credentials; the output names the slot
112
+ and the web credential page where you reveal the value. A local agent
113
+ (Cinna Desktop) then calls it by slot; a cloud agent is wired with
114
+ `cinna connect agent-api` instead.
115
+ - `--to <email>`: the key lands in that person's credentials; you never see
116
+ the value. The output prints the revoke command.
117
+ - Scopes outside the producer's declared catalog are stored anyway, with a
118
+ warning.
119
+ - `cinna agent-api access revoke <producer> <key>` ends one key (asks first,
120
+ `--yes` skips). The person's scopes stay, so a new key can be issued without
121
+ re-granting.
122
+ - `cinna agent-api access scopes <producer> --to <email|me> --set a,b | --clear`
123
+ changes a person's scopes without touching their keys, so a working key keeps
124
+ working. `--clear` removes the grant.
125
+ - The result shows up elsewhere: `cinna account credentials list` shows the
126
+ key's status in the Status column, and a skill slot filled by a dead key
127
+ reads as not usable (see
128
+ [account_workspace](../account_workspace/account_workspace.md)).
129
+
100
130
  ### Call the platform's own API (unrelated to producer APIs)
101
131
  - `cinna api GET agents`, `cinna api POST agents/<id>/duplicate`,
102
132
  `cinna api PATCH agents/<id> --json '{…}'` — `PATH` is relative to the API
@@ -132,7 +162,8 @@ to test a producer endpoint — use `cinna agent-api call`.
132
162
  its own refusal from a mirrored inner error by a marker response header.
133
163
  - **Escape-hatch deny-list.** `cinna api` cannot reach credentials, user
134
164
  management, admin, CLI, MFA/auth, or streaming routes — the platform denies
135
- them; don't waste calls.
165
+ them; don't waste calls. It also refuses routes that would hand out a secret:
166
+ any method on `agents/*/agent-api/keys…` (issued keys), on `environments/*/workspace/download` and `view-file`, and on `dashboards/*/blocks/*/env-file`; and POST on A2A access-tokens, MCP/ACP connector tokens, agent and task-trigger webhook create/regenerate-token. Paths are matched after percent-decoding. These refuse with 403 `excluded_path` (exit 2). Prefix-only listings stay reachable.
136
167
  - **An elided id is refused locally.** A path segment carrying `…` or `...` is
137
168
  an id copied out of a table cell too narrow to print it whole. It reaches the
138
169
  API as a well-formed path and comes back `404 Agent not found` — a sentence
@@ -150,6 +181,37 @@ to test a producer endpoint — use `cinna agent-api call`.
150
181
  - **No secret round-trip.** `connect agent-api` mints the producer token
151
182
  server-side and attaches it as a consumer credential; the secret value never
152
183
  passes through the CLI as something the user pastes or stores locally.
184
+ - **A key value never crosses the CLI.** The platform sends none on the access
185
+ routes, the client drops any stray `token` field, and every runner drops it
186
+ again before printing or `--json`. Self-issue returns a web URL where the
187
+ person reveals the value; nobody should paste one into the CLI.
188
+ - **Rotate and reveal are web-only.** Rotation replaces a value someone outside
189
+ the platform holds, which the CLI could not hand over, and a coding agent
190
+ could otherwise start a countdown that breaks every holder of the old value.
191
+ There is no verb for either.
192
+ - **The external-access switch is web-only.** It is the deliberate human act
193
+ that exposes the API outside the platform. When it is off, `issue` is refused
194
+ with `external_access_disabled` and the producer's Integrations URL is
195
+ appended to the message (only when the platform's message lacks one); `list`
196
+ warns and prints the same URL.
197
+ - **Terminal keys are hidden unless `--all`.** Revoked, producer/issuer removed
198
+ and expired keys drop out of the `access list` table with a footer count;
199
+ `--json` always carries everything.
200
+ - **`--to` is required on `issue` and `scopes`.** The recipient is always a
201
+ deliberate choice: an email (resolved server-side, exact case-insensitive
202
+ match among active users) or `me`. An unknown email is refused, and one that
203
+ matches several accounts is refused as ambiguous rather than guessed.
204
+ - **A key reference is a key id or the exact token prefix.** A reference
205
+ carrying an elision marker is refused locally (same rule as other ids); no
206
+ match or several matches fail with a candidate listing. Revoking an
207
+ already-revoked key is a no-op that exits 0 ("already revoked"); `--json`
208
+ requires `--yes`.
209
+ - **Only the producer's owner (or an admin) manages access, and writes need
210
+ the developer role.** A producer that is missing or not yours reads as not
211
+ found. `list` is open to any account token.
212
+ - **The generic escape hatch cannot issue keys.** `cinna api POST
213
+ agents/<id>/agent-api/keys` is refused (exit 2); the `access` verbs are the
214
+ only CLI way.
153
215
  - **Read-only is a connect-time choice.** `--read-only` is applied when the
154
216
  connection is created; it scopes the *consumer's* access, not the producer's
155
217
  surface.
@@ -169,6 +231,15 @@ build/verify (producer):
169
231
  ▼ producer's env)
170
232
  agent-api call ─► owner-preview proxy ─► producer endpoint (no consumer token)
171
233
 
234
+ people access (issued keys and scopes):
235
+ cinna agent-api access list/issue/revoke/scopes
236
+ │ account token
237
+ ▼
238
+ /api/v1/cli/account/agent-api/{access,keys,keys/revoke,access/scopes}
239
+ │ key lands in the recipient's credentials (value never returned)
240
+ ▼
241
+ recipient reveals it on the web; a local agent calls it by slot
242
+
172
243
  wiring (consumer):
173
244
  cinna connect agent-api --producer A --consumer B
174
245
  │ account token
@@ -195,6 +266,14 @@ platform escape hatch (unrelated target):
195
266
  - **Remote sync / exec** — authoring a producer API means editing files in the
196
267
  producer's synced `workspace/agent_api/` and pushing them to the running env
197
268
  (`cinna dev` / `cinna exec`); the spec is harvested from that env.
269
+ - **Account workspace credentials and skill slots** — an issued key is a
270
+ credential whose listing row carries its key status; skill slot readiness
271
+ treats a revoked, expired or paused key as not usable. See
272
+ [account_workspace](../account_workspace/account_workspace.md).
273
+ - **Local agent import** — `cinna agent import` never drafts an `agent_api`
274
+ credential (it could never be used); it attaches an existing same-name one or
275
+ prints the `access issue` / `connect agent-api` command. See
276
+ [local_agent_import](../local_agent_import/local_agent_import.md).
198
277
  - **`cinna agent show`** — surfaces a producer's live `agent_api_status`
199
278
  alongside its prompts and credentials, reusing the same status renderer.
200
279
 
@@ -21,7 +21,7 @@ for** failure modes.
21
21
  - **Editable install** of the CLI under test:
22
22
  `python3 -c "import cinna,os;print(os.path.dirname(cinna.__file__))"` must
23
23
  point at this repo's `src/cinna`. Confirm `which cinna` resolves and
24
- `cinna agent-api --help` lists `enable / refresh / spec / call`.
24
+ `cinna agent-api --help` lists `enable / refresh / spec / call / access`.
25
25
  - **A producer agent** you own whose workspace has (or will have) an
26
26
  `agent_api/` package + `policy.yaml`. Ideally synced locally
27
27
  (`cinna agent sync <producer>`) so you can edit + push the endpoint code.
@@ -196,6 +196,111 @@ for** failure modes.
196
196
  - **Watch for:** trying to test a producer endpoint via `cinna api` (wrong
197
197
  target — it calls platform routes, not the producer's served API).
198
198
 
199
+ ### 13. Issue yourself a key: URL, never a value
200
+
201
+ - **Goal:** a builder gives themselves API access to a producer and never sees
202
+ the key value.
203
+ - **Setup:** the producer's REST API is enabled; turn **external access** on in
204
+ the web (Integrations tab of the producer). It is web-only by design.
205
+ - **Steps:**
206
+ ```
207
+ cinna agent-api access issue "<Producer>" --to me
208
+ cinna account credentials list
209
+ cinna agent-api access list "<Producer>"
210
+ ```
211
+ - **Expected:** `Key <prefix> is in your credentials as slot <producer>-api …`
212
+ with a `…/credential/<id>` URL and the `cinna_credentials.agent_api_session`
213
+ hint; the output contains no key value. `credentials list` shows the new row
214
+ with Status `Active`; `access list` shows you with that key as Active.
215
+ - **Watch for:** any full key or `token` field in the output (also with
216
+ `--json`); the credentials row showing `complete` instead of the key status.
217
+
218
+ ### 14. External access off refuses with the URL
219
+
220
+ - **Goal:** issuing is fail-loud while external access is off.
221
+ - **Setup:** turn external access off in the web.
222
+ - **Steps:**
223
+ ```
224
+ cinna agent-api access issue "<Producer>" --to me; echo "exit=$?"
225
+ cinna agent-api access list "<Producer>"
226
+ ```
227
+ - **Expected:** non-zero exit; the message says external access is disabled and
228
+ ends with `…/agent/<id>#integrations` (once, not doubled). `access list`
229
+ prints `External access: off` and a warning with the same URL. Turn it back on
230
+ before scenario 15.
231
+ - **Watch for:** a generic error without the URL; any CLI path that switches it
232
+ on.
233
+
234
+ ### 15. Issue to another person, scopes, and list
235
+
236
+ - **Goal:** give a colleague a scoped key without ever seeing it.
237
+ - **Steps:**
238
+ ```
239
+ cinna agent-api access issue "<Producer>" --to <other-email> --scopes read --expires-in-days 30 --label qa
240
+ cinna agent-api access scopes "<Producer>" --to <other-email> --set read,write
241
+ cinna agent-api access list "<Producer>"
242
+ cinna agent-api access list "<Producer>" --json
243
+ cinna agent-api access issue "<Producer>" --to nobody@example.invalid; echo "exit=$?"
244
+ cinna agent-api access issue "<Producer>" --to nobody@example.invalid --json; echo "exit=$?"
245
+ ```
246
+ - **Expected:** `Issued key <prefix> to <other-email>. It lands in their
247
+ credentials; you never see its value.` plus the revoke command. The scopes
248
+ command prints the person's resulting scopes and the key keeps working. The
249
+ table shows the person, scopes, key prefix, status, expiry, last used, linked
250
+ agent count and key id; `--json` is the full payload with no `token`
251
+ anywhere. The unknown recipient is refused and nothing is issued: the plain
252
+ run prints the server's sentence on stderr, and the `--json` run prints one
253
+ `{"result": "error", "code": "recipient_not_found", …}` line on stdout. Both
254
+ exit 1. An undeclared scope prints a warning but is stored.
255
+ - **Watch for:** `--to` omitted not being a usage error; a key value in any
256
+ output; the scopes change revoking or replacing the key.
257
+
258
+ ### 16. Revoke: hidden by default, visible with `--all`, recipient sees it
259
+
260
+ - **Goal:** revoking ends one key, keeps the grant, and is visible on both sides.
261
+ - **Steps:**
262
+ ```
263
+ cinna agent-api access revoke "<Producer>" <prefix> # answer n first, then y
264
+ cinna agent-api access list "<Producer>"
265
+ cinna agent-api access list "<Producer>" --all
266
+ cinna agent-api access revoke "<Producer>" <prefix> --yes # again
267
+ cinna agent-api access revoke "<Producer>" <prefix> --json # no --yes
268
+ ```
269
+ Then check the recipient's side. A recipient who is a developer can use their
270
+ own account workspace:
271
+ ```
272
+ cinna account credentials list
273
+ ```
274
+ An agent-user cannot create an account workspace (setup tokens are
275
+ developer-only), so for them sign in to the web app as the recipient and open
276
+ **Credentials**, or call `GET /api/v1/credentials` with their token and read
277
+ `agent_api_key_status`.
278
+ - **Expected:** declining sends nothing; accepting prints `Revoked key <prefix>
279
+ issued to <email>. Status: Revoked by issuer`. The default list hides the key
280
+ with `1 ended key hidden (--all)` while the person's scopes row stays;
281
+ `--all` shows `Revoked by issuer`. Revoking again prints `already revoked`
282
+ and exits 0. `--json` without `--yes` is a usage error. The recipient still
283
+ has the credential, with Status `Revoked by issuer` (`agent_api_key_status:
284
+ revoked_by_issuer` in the API).
285
+ - **Watch for:** the grant disappearing with the key; an elided prefix
286
+ (`ab12…`) not being refused locally; the recipient's credential being
287
+ deleted rather than flagged.
288
+
289
+ ### 17. The escape hatch cannot mint keys
290
+
291
+ - **Goal:** `cinna api` is not a back door around the no-value rule.
292
+ - **Steps:**
293
+ ```
294
+ cinna api POST agents/<producer-id>/agent-api/keys --json '{"label":"x"}'; echo "exit=$?"
295
+ cinna api GET agents/<producer-id>/agent-api/access; echo "exit=$?"
296
+ cinna agent-api access list "<Producer>"
297
+ ```
298
+ - **Expected:** the POST prints `blocked by platform policy: …` on stderr and
299
+ exits 2; no key was created (the list is unchanged). The `access` GET through
300
+ the hatch is still reachable. The same refusal (403 `excluded_path`, exit 2)
301
+ applies to any method on `agents/*/agent-api/keys…`, `environments/*/workspace/download` and `view-file`, `dashboards/*/blocks/*/env-file`, and POST on token-minting routes (A2A access-tokens, MCP/ACP connector tokens, webhook create/regenerate-token), including percent-encoded spellings.
302
+ - **Watch for:** exit 1 instead of 2; a key created by the call.
303
+
199
304
  ## Cross-cutting invariants (must hold across all scenarios)
200
305
 
201
306
  - **Resolve-before-mutate.** A bad agent ref fails before any enable / refresh /
@@ -209,6 +314,8 @@ for** failure modes.
209
314
  both `agent-api call` and `cinna api`; only `cinna api` adds exit 2 for the
210
315
  hatch's own refusal (policy / rate limit / size cap), distinguished by the
211
316
  marker header — never conflated with an inner 4xx.
317
+ - **No key value in any access output.** Not in the table, not in `--json`,
318
+ not on self-issue; reveal and rotate are web-only.
212
319
  - **Spec is pipeable.** `cinna agent-api spec` emits plain JSON to stdout (or the
213
320
  `-o` file), never Rich-decorated.
214
321
 
@@ -221,4 +328,8 @@ for** failure modes.
221
328
  `cinna agent-api enable "<Producer>" --disable`.
222
329
  - Remove any deliberately-broken `agent_api/` edits (revert + sync), then
223
330
  `cinna agent-api refresh` to restore a clean harvested spec.
331
+ - Revoke test keys (`cinna agent-api access revoke "<Producer>" <prefix> --yes`),
332
+ clear test grants (`cinna agent-api access scopes "<Producer>" --to <email>
333
+ --clear`), delete the revoked credentials in the web, and set external access
334
+ back to its prior state.
224
335
  - Delete scratch files written by `cinna agent-api spec -o spec.json`.