@tinycloud/cli 1.2.0-beta.1 → 1.2.0-beta.2

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinycloud/cli",
3
- "version": "1.2.0-beta.1",
3
+ "version": "1.2.0-beta.2",
4
4
  "description": "TinyCloud CLI - manage data, delegations, and nodes from the terminal",
5
5
  "author": "TinyCloud, Inc.",
6
6
  "license": "EGPL",
@@ -31,10 +31,10 @@
31
31
  "dependencies": {
32
32
  "@tinycloud/share-sdk": "1.1.0",
33
33
  "@tinycloud/share-envelope": "1.1.0",
34
- "@tinycloud/node-sdk": "3.2.0-beta.1",
34
+ "@tinycloud/node-sdk": "3.2.0-beta.2",
35
35
  "@tinycloud/node-sdk-wasm": "1.7.7",
36
- "@tinycloud/sdk-core": "3.2.0-beta.0",
37
- "@tinycloud/operations": "0.5.0-beta.1",
36
+ "@tinycloud/sdk-core": "3.2.0-beta.2",
37
+ "@tinycloud/operations": "0.5.0-beta.2",
38
38
  "chalk": "^5.3.0",
39
39
  "commander": "^14.0.0",
40
40
  "open": "^11.0.0",
@@ -93,6 +93,25 @@ tc --profile agent-secrets secrets get OPENAI_API_KEY --raw
93
93
  - A stored session that has expired, or no longer verifies for the profile's key, fails every command that needs it with `AUTH_REQUIRED` (exit 3) before any node request, whatever the profile kind; secrets commands on an owner OpenKey profile whose session has passed its expiry refresh it in the browser at a terminal instead. A delegate session created by `tc auth import` has no SIWE; its lifetime is the `exp` its compact-UCAN delegation signs, read from the stored authorization bytes, so it is refused the same way, including sessions imported by earlier releases. Expiry is judged by this machine's clock. `context` reports that expiry, and an expired imported session does not block the scoped paste login the hint names. A session imported from a CACAO (wallet-signed) delegation is still refused only by the node. The hint names a new scoped paste login for a delegate profile, `auth login --method local` for a local owner key, and `auth login --method openkey` for an OpenKey owner; Share commands keep their `auth login --device --manifest builtin:share-publishing` guidance. It is never reported as a retryable node error. Operations and MCP report it as `AUTH_REQUIRED` (`retryable: false`), the code they also use for a node's 401.
94
94
  - `secrets get -o FILE` validates the destination and its existing directory before fetching or decrypting secret bytes. It refuses symlinks, directories and devices, syncs a new owner-only (0600) file, then atomically replaces the destination. Directory sync is attempted after replacement where supported; its failure does not report a failed write when the destination was already replaced. A destination that cannot be replaced fails with a sanitized filesystem error naming the destination; there is no unsafe fallback.
95
95
 
96
+ ### Asking the owner to add a missing secret
97
+
98
+ When the secret does not exist yet, the agent sends the owner one message with a request link, redeems the consent short code if the owner approved a scoped login, then waits. Call the CLI by absolute path: a bare `tc` on `PATH` can be iproute2's `/usr/sbin/tc` (traffic control), not TinyCloud.
99
+
100
+ ```bash
101
+ TC_BIN="$(npm prefix --global)/bin/tc"
102
+ "$TC_BIN" --profile agent-secrets secrets request OPENAI_API_KEY --requester "my-agent@$(hostname -s)" --json # offline; prints {url, names, owner}
103
+ # One message to the owner: the url (plus the scoped-login approval URL above if the profile has no grant yet).
104
+ # If the owner sends back a consent short code, redeem it first:
105
+ printf '%s\n' "$CODE" | "${LOGIN[@]}"
106
+ "$TC_BIN" --profile agent-secrets secrets wait OPENAI_API_KEY --timeout 9m --json # 0 ready, 8 still pending
107
+ "$TC_BIN" --profile agent-secrets secrets get OPENAI_API_KEY --raw
108
+ ```
109
+
110
+ - `wait` never signs in: it needs a stored session and doesn't accept `TC_PRIVATE_KEY` (exit 3). Exit 3 carries `error.recovery.command`, the persistent login to run once.
111
+ - After a CLI upgrade, run one normal command per profile first (for example `"$TC_BIN" --profile agent-secrets auth caps`). Otherwise the first `wait` returns `PROFILE_MIGRATION_REQUIRED` (exit 1) once, with that command in `error.recovery.command`.
112
+ - Exit 8 means the owner has not added it yet: rerun `wait` without sending the link again. Exit 5 means the grant does not cover a name: `error.recovery.names` lists them. [REFERENCE.md](REFERENCE.md#asking-the-owner-for-a-secret-request-wait-manage) has the full exit table.
113
+ - A local identity (local-owner profile, a profile with a stored `privateKey`, `--private-key`, `TC_PRIVATE_KEY`) owns its secrets and gets `LOCAL_OWNER_PROFILE` from `request`: store them with `secrets put NAME --stdin`.
114
+
96
115
  ## More spaces after login
97
116
 
98
117
  Login covers one space. Request further spaces on the logged-in profile:
@@ -3,12 +3,13 @@
3
3
  <!-- BEGIN GENERATED TINYCloud operations coverage -->
4
4
  This release has **Commander coverage tracked, not complete parity**:
5
5
 
6
- - 1 migrated registration(s).
6
+ - 2 migrated registration(s).
7
7
  - 1 partially migrated registration(s).
8
- - 125 legacy registration(s) remain Commander-owned.
8
+ - 127 legacy registration(s) remain Commander-owned.
9
9
 
10
10
  - `auth import [source]` → `tinycloud.auth.import@1` (partial; legacy inputs: v1 delegation artifact, v1 permission artifact without command, bare portable delegation, stored delegation wrapper, cross-user delegation persisted with activated=false).
11
11
  - `secrets get <name>` → `tinycloud.secrets.get@1` (migrated).
12
+ - `secrets probe <name>` → `tinycloud.secrets.status@1` (migrated).
12
13
  <!-- END GENERATED TINYCloud operations coverage -->
13
14
 
14
15
  ## Calling the CLI
@@ -138,13 +139,60 @@ tc secrets network init
138
139
  tc secrets network show
139
140
  tc secrets network grant did:pkh:eip155:1:0xRecipient...
140
141
 
141
- tc secrets put ANTHROPIC_API_KEY "sk-..."
142
+ printf '%s' "$ANTHROPIC_API_KEY" | tc secrets put ANTHROPIC_API_KEY --stdin # keeps the value out of argv and shell history
142
143
  tc secrets get ANTHROPIC_API_KEY
143
144
  tc secrets get ANTHROPIC_API_KEY -o key.txt # atomic owner-only (0600) replacement
144
145
  tc secrets list
145
146
  tc secrets delete ANTHROPIC_API_KEY
146
147
  ```
147
148
 
149
+ A missing secret makes `tc secrets get` exit 4 (`NOT_FOUND`). For an identity that is not local, `error.setup` carries the owner's request link; a local identity (or a name/scope no link can express) gets only a hint naming `tc secrets put NAME --stdin`. `tc secrets doctor NAME` hints `tc secrets request NAME` for a missing secret, or `tc secrets put NAME --stdin` for a local identity. `tc secrets get --delegation` follows the same rule, and a delegated read the node refuses (403, a capability 401, `AUTH_UNAUTHORIZED`) exits 5.
150
+
151
+ ### Asking the owner for a secret: `request`, `wait`, `manage`
152
+
153
+ `/usr/sbin/tc` (iproute2 traffic control) can shadow TinyCloud's `tc` on `PATH`, so scripts call the CLI by absolute path:
154
+
155
+ ```bash
156
+ TC_BIN="$(npm prefix --global)/bin/tc"
157
+ "$TC_BIN" --profile agent secrets request OPENAI_API_KEY --requester "agent@host" --json
158
+ # {"url":"https://secrets.tinycloud.xyz/request?owner=…&name=OPENAI_API_KEY&requester=…&v=1","names":["OPENAI_API_KEY"],"owner":"did:pkh:eip155:1:0x…"}
159
+ # send the url to the owner once, then:
160
+ "$TC_BIN" --profile agent secrets wait OPENAI_API_KEY --timeout 9m --json
161
+ "$TC_BIN" --profile agent secrets get OPENAI_API_KEY --raw
162
+ "$TC_BIN" secrets manage OPENAI_API_KEY --no-open --json # {"url":"…","opened":false}: the replace/manage page
163
+ ```
164
+
165
+ - **`secrets request <NAME...> [--scope S | --app ID] [--owner DID] [--requester LABEL] [--open]`** prints `{url, names, scope?, owner}` and exits 0. It is offline: no session, no node request. 1–10 names (`^[A-Z][A-Z0-9_]*$`, ≤ 64, duplicates dropped). `--app ID` derives the scope from an app id (`xyz.tinycloud.feed` → `xyz-tinycloud-feed`); `--app` with `--scope` exits 2. `--requester` defaults to `user@host`. `--open` also opens the link; a failure to open only adds a `BROWSER_OPEN_FAILED` warning on stderr. Errors: 2 for `INVALID_SECRET_NAME`, `TOO_MANY_SECRET_NAMES`, `INVALID_SECRET_SCOPE`, `INVALID_REQUESTER`, `INVALID_OWNER`, `OWNER_MISMATCH`, `LOCAL_OWNER_PROFILE`; 3 for `OWNER_DID_UNKNOWN` (pass `--owner`).
166
+ - **`secrets wait <NAME...> [--scope S | --app ID] [--owner DID] [--timeout D]`** polls until every name is readable. `D` is `0`, `Ns` or `Nm`, default `5m`, at most `9m`; `--timeout 0` makes exactly one check (≤ 21 s). The whole wait is bounded by timeout + 21 s. Each check runs in a child process that is killed if it overruns.
167
+ - **`secrets manage [NAME...] [--scope S | --app ID] [--no-open]`** prints `{url, opened}` for the Secret Manager page, opening a browser unless `--no-open`.
168
+
169
+ **`wait` never signs in: it needs a stored session and doesn't accept `TC_PRIVATE_KEY`** (an explicit key exits 3). It never writes profile state, never saves a consent request, never prints a value and never prompts. After a CLI upgrade, run one normal command per profile first (for example `tc --profile agent auth caps`); otherwise the first `wait` returns `PROFILE_MIGRATION_REQUIRED` once, with that command in `error.recovery.command`.
170
+
171
+ | `wait` exit | Meaning |
172
+ |---|---|
173
+ | 0 | stdout `{status:"ready", names, scope?}` |
174
+ | 8 | `PENDING`: stdout `{status:"pending", pending, present, url}` (or `hint` instead of `url` for a local identity). Rerun `wait` without re-sending the link |
175
+ | 5 | `PERMISSION_DENIED`: `error.recovery` `{action:"request_access", names, owner, scope?}` names the denied names; `error.setup` covers names still pending |
176
+ | 3 | `AUTH_REQUIRED`: no stored session, an expired one, a 401, or `TC_PRIVATE_KEY` set. `error.recovery.command` is the persistent login to run once |
177
+ | 2 | usage, `OWNER_MISMATCH` |
178
+ | 6 | `NETWORK_ERROR` after repeated node failures; `error.progress` `{present, pending}` from the last successful check |
179
+ | 1 | `PROFILE_MIGRATION_REQUIRED` (run `error.recovery.command`, `tc --profile P auth caps`, once, then retry) or `PROBE_FAILED` |
180
+ | 130 | `CANCELLED` (SIGINT/SIGTERM); `error.progress` when a check had succeeded |
181
+
182
+ **Which identity gets a link (posture table):**
183
+
184
+ | Identity | `request` | missing secret on `get` / `wait` |
185
+ |---|---|---|
186
+ | `owner-openkey` (recorded owner) | link for the recorded owner | `error.setup` / `url` |
187
+ | `delegate-session` | link for the owner of its space (or `--owner`) | `error.setup` / `url` |
188
+ | key-only `did:key` with no recorded owner | `OWNER_DID_UNKNOWN` (3) unless `--owner` | `wait`: needs a session (3) |
189
+ | `local-owner-key`, a profile holding a `privateKey`, `--private-key`, `TC_PRIVATE_KEY` | `LOCAL_OWNER_PROFILE` (2) | hint `tc secrets put NAME --stdin`, no link |
190
+
191
+ **Error JSON fields** (stderr, JSON mode; only these keys ever appear in `error`: `code`, `message`, `hint`, `recovery`, `setup`, `progress`, `meta`, `warnings`):
192
+ - `error.setup` `{url, names, scope?, owner, action:"add_secret"}`: the canonical request link, on `NOT_FOUND` and on `wait`'s `PERMISSION_DENIED` for pending names.
193
+ - `error.recovery`: `{action:"sign_in", command?}` (a persistent login), `{action:"complete_migration", command?, operationId:"tinycloud.auth.capabilities"}`, or `{action:"request_access", names, owner, scope?}`. `command` is omitted for profile names that are not shell-safe; sign in to that profile, or run `auth caps` for it, yourself.
194
+ - `error.progress` `{present, pending}`: on `NETWORK_ERROR` and `CANCELLED` from `wait`, only after at least one successful check.
195
+
148
196
  ### Scoped secret reads for agents
149
197
 
150
198
  An agent profile gets read access to named secrets through a scoped OpenKey paste login with a manifest that names them; [AUTH.md](AUTH.md#secret-reads-for-an-agent) has the full flow. Device login cannot grant secrets.
@@ -304,9 +352,9 @@ On either recovery path, an orphaned `.recover-*` claim can hold a replacement o
304
352
  - **Session key**: `did:key:z6Mk...#z6Mk...` — generated at init
305
353
  - **Owner DID**: `did:pkh:eip155:{chainId}:{address}` — after auth
306
354
 
307
- Use `--json` for machine-readable output; interactive commands can render human output. General command errors go to stderr as `{error: {code, message, hint?, meta?}}`. `meta` contains a typed HTTP `status` and, only when validated as a TinyCloud capability (and against the KV request when available), a `resource` and `requiredAction`. Inspect the structured code, not only the exit status.
355
+ Use `--json` for machine-readable output; interactive commands can render human output. General command errors go to stderr as `{error: {code, message, hint?, recovery?, setup?, progress?, meta?, warnings?}}` (`recovery`, `setup` and `progress` are described under [Secrets](#asking-the-owner-for-a-secret-request-wait-manage)). `meta` contains a typed HTTP `status` and, only when validated as a TinyCloud capability (and against the KV request when available), a `resource` and `requiredAction`. Inspect the structured code, not only the exit status.
308
356
 
309
- General storage/auth exit codes: 0 success, 1 operation error, 2 invalid input, 3 authentication required, 4 not found, 5 permission denied, 6 network error, 7 node error, 10 storage full. `tc share` uses its own: 1 profile lock timeout or sender-history retry (`PROFILE_LOCK_TIMEOUT`, `SHARE_HISTORY_RETRY`), 3 upload authority required, 4 unavailable, expired or upload failed, 5 permission denied (`PERMISSION_DENIED`) or verification failed, 6 recipient authorization required or network error, 7 byte limit, 8 output conflict or unsafe filename, 9 partial success, 10 storage full.
357
+ General storage/auth exit codes: 0 success, 1 operation error (including `PROFILE_MIGRATION_REQUIRED` and `PROBE_FAILED`), 2 invalid input, 3 authentication required, 4 not found, 5 permission denied, 6 network error, 7 node error, 8 pending (`tc secrets wait` only), 10 storage full, 130 interrupted (`CANCELLED`). `tc share` uses its own: 1 profile lock timeout or sender-history retry (`PROFILE_LOCK_TIMEOUT`, `SHARE_HISTORY_RETRY`), 3 upload authority required, 4 unavailable, expired or upload failed, 5 permission denied (`PERMISSION_DENIED`) or verification failed, 6 recipient authorization required or network error, 7 byte limit, 8 output conflict or unsafe filename, 9 partial success, 10 storage full.
310
358
 
311
359
  For `tc kv get|put|head|list|delete`, `tc space list|host`, `tc sql`, `tc vault`, `tc vars`, `tc duckdb`, `tc secrets list|put|delete`, `tc delegation`, and `tc account`, service-result errors use typed authorization: a plain HTTP 401 is `AUTH_REQUIRED` (exit 3); a 403 is `PERMISSION_DENIED` (exit 5). A 401 with a validated missing capability, or `AUTH_UNAUTHORIZED` without an HTTP status, is `PERMISSION_DENIED` (exit 5); a validated capability also produces a `tc auth request --cap` hint. This changes the former exit 1 for status-less `AUTH_UNAUTHORIZED` in the listed commands. Other HTTP failures retain their service-specific codes. `tc share` has separate exit mappings below.
312
360