cinna-cli 0.5.0__tar.gz → 0.5.2__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 (118) hide show
  1. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/PKG-INFO +20 -2
  2. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/README.md +19 -1
  3. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/README.md +3 -2
  4. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/account_workspace/account_workspace.md +6 -1
  5. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/account_workspace/account_workspace_tech.md +20 -0
  6. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/agent_api/agent_api.md +82 -3
  7. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/agent_api/agent_api_acceptance.md +112 -1
  8. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/agent_api/agent_api_tech.md +117 -2
  9. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/delegation/delegation.md +5 -3
  10. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/delegation/delegation_tech.md +3 -2
  11. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/git_versioning/git_versioning.md +26 -2
  12. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/git_versioning/git_versioning_acceptance.md +29 -0
  13. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/git_versioning/git_versioning_tech.md +26 -2
  14. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/local_agent_import/local_agent_import.md +4 -0
  15. cinna_cli-0.5.2/docs/features/team_library/team_library.md +128 -0
  16. cinna_cli-0.5.2/docs/features/team_library/team_library_acceptance.md +136 -0
  17. cinna_cli-0.5.2/docs/features/team_library/team_library_tech.md +129 -0
  18. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/pyproject.toml +1 -1
  19. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/account.py +509 -13
  20. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/client.py +202 -2
  21. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/delegation.py +11 -5
  22. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/git_versioning.py +178 -11
  23. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/kit_contract.py +1 -1
  24. cinna_cli-0.5.2/src/cinna/library.py +1203 -0
  25. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/local_import.py +66 -3
  26. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/main.py +301 -10
  27. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/templates/ACCOUNT_CLAUDE.md.template +26 -2
  28. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/templates/GIT_VERSIONING.md +7 -0
  29. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_account.py +282 -2
  30. cinna_cli-0.5.2/tests/test_agent_api_access.py +512 -0
  31. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_client.py +132 -0
  32. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_delegation.py +10 -0
  33. cinna_cli-0.5.2/tests/test_library.py +743 -0
  34. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_local_import.py +101 -1
  35. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/uv.lock +1 -1
  36. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/.claude/commands/cinna-cli.feature.doc.md +0 -0
  37. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/.github/workflows/publish.yml +0 -0
  38. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/.gitignore +0 -0
  39. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/LICENSE.md +0 -0
  40. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/account_workspace/account_workspace_acceptance.md +0 -0
  41. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/agent_addons/agent_addons.md +0 -0
  42. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/agent_addons/agent_addons_acceptance.md +0 -0
  43. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/agent_addons/agent_addons_tech.md +0 -0
  44. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/agent_management/agent_management.md +0 -0
  45. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/agent_management/agent_management_acceptance.md +0 -0
  46. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/agent_management/agent_management_tech.md +0 -0
  47. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/agent_schedules/agent_schedules.md +0 -0
  48. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/agent_schedules/agent_schedules_acceptance.md +0 -0
  49. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/agent_schedules/agent_schedules_tech.md +0 -0
  50. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/bootstrap_onboarding/bootstrap_onboarding.md +0 -0
  51. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/bootstrap_onboarding/bootstrap_onboarding_acceptance.md +0 -0
  52. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/bootstrap_onboarding/bootstrap_onboarding_tech.md +0 -0
  53. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/delegation/delegation_acceptance.md +0 -0
  54. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/doctor/doctor.md +0 -0
  55. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/doctor/doctor_acceptance.md +0 -0
  56. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/doctor/doctor_tech.md +0 -0
  57. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/improvement_requests/improvement_requests.md +0 -0
  58. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/improvement_requests/improvement_requests_acceptance.md +0 -0
  59. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/improvement_requests/improvement_requests_tech.md +0 -0
  60. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/live_sync/live_sync.md +0 -0
  61. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/live_sync/live_sync_acceptance.md +0 -0
  62. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/live_sync/live_sync_tech.md +0 -0
  63. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/local_agent_import/local_agent_import_acceptance.md +0 -0
  64. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/local_agent_import/local_agent_import_tech.md +0 -0
  65. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/mcp_integration/mcp_integration.md +0 -0
  66. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/mcp_integration/mcp_integration_acceptance.md +0 -0
  67. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/mcp_integration/mcp_integration_tech.md +0 -0
  68. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/remote_chat/remote_chat.md +0 -0
  69. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/remote_chat/remote_chat_acceptance.md +0 -0
  70. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/remote_chat/remote_chat_tech.md +0 -0
  71. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/remote_exec/remote_exec.md +0 -0
  72. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/remote_exec/remote_exec_acceptance.md +0 -0
  73. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/features/remote_exec/remote_exec_tech.md +0 -0
  74. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/interface.md +0 -0
  75. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/docs/mutagen_capabilities.md +0 -0
  76. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/scripts/check_docs_references.py +0 -0
  77. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/__init__.py +0 -0
  78. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/auth.py +0 -0
  79. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/bootstrap.py +0 -0
  80. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/chat.py +0 -0
  81. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/cli_version.py +0 -0
  82. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/config.py +0 -0
  83. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/console.py +0 -0
  84. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/context.py +0 -0
  85. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/doctor.py +0 -0
  86. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/errors.py +0 -0
  87. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/improve.py +0 -0
  88. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/logging.py +0 -0
  89. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/mcp_proxy.py +0 -0
  90. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/mutagen_runtime.py +0 -0
  91. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/scenarios.py +0 -0
  92. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/sync.py +0 -0
  93. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/sync_session.py +0 -0
  94. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/sync_ssh_shim.py +0 -0
  95. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/sync_tui.py +0 -0
  96. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/templates/CHAT_TESTING.md +0 -0
  97. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/templates/CLAUDE.md.template +0 -0
  98. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/src/cinna/templates/__init__.py +0 -0
  99. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/__init__.py +0 -0
  100. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/conftest.py +0 -0
  101. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_auth.py +0 -0
  102. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_bootstrap.py +0 -0
  103. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_chat.py +0 -0
  104. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_cli_version.py +0 -0
  105. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_config.py +0 -0
  106. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_console.py +0 -0
  107. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_context.py +0 -0
  108. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_doctor.py +0 -0
  109. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_git_versioning.py +0 -0
  110. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_improve.py +0 -0
  111. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_kit_contract.py +0 -0
  112. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_main.py +0 -0
  113. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_mutagen_runtime.py +0 -0
  114. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_onboarding.py +0 -0
  115. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_scenarios.py +0 -0
  116. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_sync.py +0 -0
  117. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/tests/test_sync_session.py +0 -0
  118. {cinna_cli-0.5.0 → cinna_cli-0.5.2}/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.0
3
+ Version: 0.5.2
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
 
@@ -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
 
@@ -292,13 +292,14 @@ 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
300
  | **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
301
  | **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
+ | **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
303
 
303
304
  The sections below (Git Versioning, Sync Transport, Remote Exec, Remote Chat,
304
305
  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
@@ -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
@@ -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`.
@@ -13,10 +13,16 @@ talks to the platform through `AccountClient` with the account token.
13
13
  shared status renderer.
14
14
  - `src/cinna/client.py` — `AccountClient` methods that hit the account routes
15
15
  (`set_agent_api_enabled`, `refresh_agent_api`, `get_agent_api_spec`,
16
- `call_agent_api`, `connect_agent_api`, `connect_mcp`, `api_proxy`).
16
+ `call_agent_api`, `connect_agent_api`, `connect_mcp`, `api_proxy`, and the
17
+ four access methods `list_agent_api_access`, `issue_agent_api_key`,
18
+ `revoke_agent_api_key`, `set_agent_api_scopes`) plus the `drop_token()` helper.
17
19
  - Tests: `tests/test_account.py` (enable/refresh/spec/call, connect agent-api,
18
20
  `cinna api` exit-code matrix, `api_proxy` client behavior),
19
- `tests/test_client.py` (`AccountClient` route shapes).
21
+ `tests/test_client.py` (`AccountClient` route shapes, the access methods,
22
+ coded refusals, `drop_token`), `tests/test_agent_api_access.py` (the four
23
+ `access` verbs end to end against a mocked client), `tests/test_account.py`
24
+ (key status cells, slot readiness key states), `tests/test_local_import.py`
25
+ (`agent_api` spec skipped).
20
26
 
21
27
  ## Command surface
22
28
 
@@ -31,6 +37,16 @@ Each verb → its handler in `src/cinna/main.py` → the `run_*` in
31
37
  `src/cinna/account.py:run_agent_api_spec()`
32
38
  - `cinna agent-api call` → `src/cinna/main.py:agent_api_call()` →
33
39
  `src/cinna/account.py:run_agent_api_call()`
40
+ - `cinna agent-api access list` → `src/cinna/main.py:agent_api_access_list()` →
41
+ `src/cinna/account.py:run_agent_api_access_list()`
42
+ - `cinna agent-api access issue` → `src/cinna/main.py:agent_api_access_issue()` →
43
+ `src/cinna/account.py:run_agent_api_access_issue()` (`--to` required;
44
+ `--expires-in-days` is `click.IntRange(1, 3650)`)
45
+ - `cinna agent-api access revoke` → `src/cinna/main.py:agent_api_access_revoke()` →
46
+ `src/cinna/account.py:run_agent_api_access_revoke()`
47
+ - `cinna agent-api access scopes` → `src/cinna/main.py:agent_api_access_scopes()` →
48
+ `src/cinna/account.py:run_agent_api_access_scopes()` (exactly one of `--set` /
49
+ `--clear`, checked in the Click handler)
34
50
  - `cinna connect agent-api` → `src/cinna/main.py:connect_agent_api()` →
35
51
  `src/cinna/account.py:run_connect_agent_api()`
36
52
  - `cinna connect mcp` → `src/cinna/main.py:connect_mcp()` →
@@ -75,6 +91,49 @@ Each verb → its handler in `src/cinna/main.py` → the `run_*` in
75
91
  `client.api_proxy(method, path, query, json_body)`, then branches on the
76
92
  marker header (see below) to set the exit code.
77
93
 
94
+ ## Access verbs: key functions
95
+
96
+ - `src/cinna/account.py:_access_recipient()` — `--to` as the request field:
97
+ `me` becomes a null email, blank is a usage error.
98
+ - `src/cinna/account.py:_parse_scope_list()` — `a,b` to a list, `""` to an
99
+ explicit empty list, unset to `None` (leave the grant as is).
100
+ - `src/cinna/main.py:_access_json_mode()` — each `access` command's own
101
+ `--json` also turns on console JSON mode, so a refusal prints the
102
+ `{"result": "error", "code", "detail", "http_status"}` line with the server's
103
+ code instead of a bare `Error: <sentence>`
104
+ (`tests/test_agent_api_access.py:test_json_refusal_carries_the_server_code`).
105
+ - `src/cinna/account.py:run_agent_api_access_list()` — one row per person/key;
106
+ a grant-only person is one row with Key `—`; terminal keys filtered by
107
+ `_TERMINAL_KEY_STATUSES` unless `--all` with an "N ended keys hidden" footer;
108
+ `_print_access_footer()` prints external access on/off (warning plus
109
+ `manage_url` when off) and the scope catalog.
110
+ - `src/cinna/account.py:run_agent_api_access_issue()` — catches a
111
+ `CodedRefusal` with code `external_access_disabled` and re-raises it with
112
+ `_manage_url()` appended only when the message has no `http` URL; self-issue
113
+ prints the slot, `credential_url` and the `cinna_credentials.agent_api_session`
114
+ hint, other-issue prints the revoke hint via `_agent_ref_for_hint()`;
115
+ `_warn_unknown_scopes()` warns, never refuses.
116
+ - `src/cinna/account.py:_resolve_access_key()` — key id first, then exact
117
+ `token_prefix`, resolved against a fresh access listing; elided refs
118
+ (`_ELIDED_MARKERS`) are a usage error; none/several matches list candidates.
119
+ - `src/cinna/account.py:run_agent_api_access_revoke()` — `_require_yes_for_json()`
120
+ first; already `revoked_by_issuer` returns without a call; other ended
121
+ statuses ask "revoke it anyway?"; confirmation declined aborts with no call.
122
+ - `src/cinna/account.py:run_agent_api_access_scopes()` — `--clear` sends `None`;
123
+ prints the resulting scopes or "Grant removed".
124
+ - `src/cinna/account.py:_KEY_STATUS_LABELS` / `_key_status_cell()` — the one
125
+ status-word table (green Active; yellow API turned off by producer / External
126
+ access paused; red Revoked by issuer / Producer removed / Issuer account
127
+ removed / Expired; unknown values printed raw and dim).
128
+ - `src/cinna/account.py:_key_unavailable_state()`,
129
+ `agent_api_producer_from_slot()`, `agent_api_issue_hint()` — shared by slot
130
+ readiness and the importer; the hint derives the producer from a
131
+ `<producer>-api` slot (else `<producer>`) and omits `--service-uri` when there
132
+ is no slot.
133
+ - `src/cinna/client.py:drop_token()` — recursively removes every `token` key
134
+ from a payload; applied by `AccountClient._access_response()` and again by
135
+ each runner.
136
+
78
137
  ## The escape hatch's exit-code logic (`run_api`)
79
138
 
80
139
  `src/cinna/account.py:run_api()` distinguishes three outcomes by the presence of
@@ -107,6 +166,22 @@ contract but has no exit-2 case (it returns the buffered
107
166
  - `call_agent_api(agent_id, method, path, query, json_body)` → `POST
108
167
  /api/v1/cli/account/agent-api/call`; owner-preview invocation, returns
109
168
  `{status_code, headers, body, is_json}`.
169
+ - `list_agent_api_access(agent_id)` → `GET
170
+ /api/v1/cli/account/agent-api/access` (`agent_id` query param).
171
+ - `issue_agent_api_key(agent_id, *, issued_to_email, scopes, expires_in_days,
172
+ service_uri, label, read_only)` → `POST /api/v1/cli/account/agent-api/keys`;
173
+ `None` fields are omitted from the body.
174
+ - `revoke_agent_api_key(agent_id, key_id)` → `POST
175
+ /api/v1/cli/account/agent-api/keys/revoke`.
176
+ - `set_agent_api_scopes(agent_id, user_email, scopes)` → `PUT
177
+ /api/v1/cli/account/agent-api/access/scopes`; `scopes` is always sent, as an
178
+ explicit `null` for `--clear` (core answers 422 if the field is omitted; null
179
+ removes the grant), `user_email` omitted for the caller.
180
+ - All four go through `AccountClient._access_response()`: a bare
181
+ `{"detail": "Not Found"}` 404 becomes a `PlatformError` saying the platform
182
+ predates API access from the CLI; otherwise `_handle_response(coded=True)`
183
+ raises a `CodedRefusal` for a `{detail: {code, message}}` body (not for 401);
184
+ then `drop_token()`.
110
185
  - `connect_agent_api(producer_agent_id, consumer_agent_id, credential_label,
111
186
  read_only_override)` → `POST /api/v1/cli/account/connect/agent-api`.
112
187
  - `connect_mcp(connector_id, consumer_agent_id, …)` → `POST
@@ -126,6 +201,26 @@ All under `/api/v1/cli/account/*`, account-token auth:
126
201
  - `GET …/agent-api/spec` — the harvested OpenAPI spec JSON.
127
202
  - `POST …/agent-api/call` — owner-preview endpoint invocation (query forwarded,
128
203
  no consumer token, no policy edge).
204
+ - `GET …/agent-api/access?agent_id=` — people, scopes, keys (terminal keys
205
+ included), `external_access_enabled`, `identity_enabled`, `scope_catalog`,
206
+ `manage_url`.
207
+ - `POST …/agent-api/keys` — issue a key; answers the key's public fields plus
208
+ `base_url`, `spec_url`, `service_uri`, `issued_to_self`, `credential_url`
209
+ (self-issue only) and `unknown_scopes`. Never a key value.
210
+ - `POST …/agent-api/keys/revoke` — body `{agent_id, key_id}`; idempotent
211
+ (`status: revoked_by_issuer`); the grant is untouched.
212
+ - `PUT …/agent-api/access/scopes` — body `{agent_id, user_email, scopes}`;
213
+ `scopes` is required but nullable, so `--clear` sends `"scopes": null`
214
+ explicitly (omitting the field is a 422 on the core side); answers `{entry, unknown_scopes}`.
215
+ - `GET …/credentials` — each row carries `agent_api_key_status` for issued
216
+ keys (null for connections); `POST …/credentials` with type `agent_api` is
217
+ refused with a 400 that names the issue command.
218
+ - `api-proxy` refusals added with issued keys: 403 `excluded_path` (CLI exit 2)
219
+ for any method on `agents/*/agent-api/keys…`, `environments/*/workspace/download`
220
+ and `view-file`, `dashboards/*/blocks/*/env-file`, and POST on A2A
221
+ access-tokens, MCP/ACP connector tokens and agent / task-trigger webhook
222
+ create/regenerate-token; paths are matched percent-decoded. The CLI needs no
223
+ code for this: the hatch's own refusal already exits 2.
129
224
  - `POST …/connect/agent-api` — mint the producer token + attach to the consumer
130
225
  as a credential.
131
226
  - `POST …/connect/mcp` — the agent2agent MCP wiring (sibling).
@@ -133,6 +228,13 @@ All under `/api/v1/cli/account/*`, account-token auth:
133
228
  the "Platform API Endpoints" table in `docs/README.md` for the canonical
134
229
  account-route list.
135
230
 
231
+ Error codes on the four access routes (`{"detail": {"code", "message"}}`,
232
+ surfaced as `CodedRefusal`): `agent_not_found` (404, not owned: no leak),
233
+ `recipient_not_found` (404), `ambiguous_recipient` (409, case-only duplicate
234
+ emails), `external_access_disabled` (400), `api_disabled` (400), `key_not_found`
235
+ (404), and `invalid_request` / `not_found` / `conflict` for any other platform
236
+ refusal. 422 is body validation. A non-developer gets a plain 403 on writes.
237
+
136
238
  Producer-side author contract (not CLI files — they live in the producer's
137
239
  synced workspace, harvested by the platform): `workspace/agent_api/*.py`
138
240
  (endpoint modules) and `workspace/policy.yaml` (access/guardrail policy).
@@ -160,5 +262,18 @@ synced workspace, harvested by the platform): `workspace/agent_api/*.py`
160
262
  raises only on 401.)
161
263
  (`tests/test_account.py` — the `test_api_*` matrix,
162
264
  `…test_account_client_api_proxy_*`)
265
+ - **No key value, ever.** `drop_token()` runs in the client and again in each
266
+ runner before printing or `--json`; the revoke path also scrubs the listing it
267
+ resolves the key from. (`tests/test_client.py` token-drop tests,
268
+ `tests/test_agent_api_access.py` self-issue test with a mock that returns a
269
+ `token`.)
270
+ - **`external_access_disabled` carries a URL.** The URL is appended only when
271
+ absent so a platform that already names it is not doubled.
272
+ (`src/cinna/account.py:run_agent_api_access_issue()`)
273
+ - **Unknown key status is unavailable, not usable.** `_key_unavailable_state()`
274
+ returns `key_unavailable` for a status this CLI does not know; the cell prints
275
+ it raw and dim. (`tests/test_account.py` slot readiness tests)
276
+ - **Confirm before revoke, nothing sent on decline.** `--json` without `--yes`
277
+ is a usage error. (`tests/test_agent_api_access.py`)
163
278
  - **`--json` / `--data` mutual exclusion** in `run_api`; `--data @file` strips a
164
279
  leading `@`. (`src/cinna/account.py:run_api()`)
@@ -152,9 +152,11 @@ result attached to the current task ──► delivered to the requester (Deskto
152
152
  - **Remote chat** — a delegated task runs as a platform session like the ones
153
153
  `cinna chat` drives. See [Remote Chat](../remote_chat/remote_chat.md).
154
154
  - **cinna-core task MCP server (not in this repo)** — inside cloud tasks the
155
- backend's MCP task server exposes a `handover_report` tool that records the
156
- same kind of result. It is implemented and documented in cinna-core; this CLI
157
- only offers the equivalent command-line path.
155
+ backend's `cinna` MCP server exposes a `tasks_report` tool (the Cinna Task
156
+ Protocol, kit contract 1.6.0) that records the same kind of result; an
157
+ environment built before protocol version 1 still has it as `handover_report`
158
+ on the older `agent_task` server. It is implemented and documented in
159
+ cinna-core; this CLI only offers the equivalent command-line path.
158
160
 
159
161
  Implementation: see [delegation_tech.md](delegation_tech.md).
160
162
  Real-usage e2e scenarios: see [delegation_acceptance.md](delegation_acceptance.md).
@@ -122,8 +122,9 @@ Executor side — direct call, agent-token auth:
122
122
  belongs to this environment's agent and owner, and resolves the task from the
123
123
  session. Any 2xx is success.
124
124
 
125
- Related, not in this repo: cinna-core's cloud MCP task server exposes a
126
- `handover_report` tool for the same executor-side report.
125
+ Related, not in this repo: cinna-core's `cinna` MCP server exposes a
126
+ `tasks_report` tool for the same executor-side report (`handover_report` on
127
+ environments built before protocol version 1).
127
128
 
128
129
  ## Edge cases & guardrails (preserve these)
129
130