@tinycloud/cli 1.1.0-beta.12 → 1.1.0-beta.14
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/dist/index.js +192 -31
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +334 -114
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +5 -5
- package/skills/tc-cli/AUTH.md +6 -4
- package/skills/tc-cli/REFERENCE.md +6 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinycloud/cli",
|
|
3
|
-
"version": "1.1.0-beta.
|
|
3
|
+
"version": "1.1.0-beta.14",
|
|
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-beta.2",
|
|
33
33
|
"@tinycloud/share-envelope": "1.1.0-beta.0",
|
|
34
|
-
"@tinycloud/node-sdk": "3.1.0-beta.
|
|
35
|
-
"@tinycloud/node-sdk-wasm": "1.7.
|
|
36
|
-
"@tinycloud/sdk-core": "3.1.0-beta.
|
|
37
|
-
"@tinycloud/operations": "0.4.0-beta.
|
|
34
|
+
"@tinycloud/node-sdk": "3.1.0-beta.8",
|
|
35
|
+
"@tinycloud/node-sdk-wasm": "1.7.7-beta.0",
|
|
36
|
+
"@tinycloud/sdk-core": "3.1.0-beta.8",
|
|
37
|
+
"@tinycloud/operations": "0.4.0-beta.12",
|
|
38
38
|
"chalk": "^5.3.0",
|
|
39
39
|
"commander": "^14.0.0",
|
|
40
40
|
"open": "^11.0.0",
|
package/skills/tc-cli/AUTH.md
CHANGED
|
@@ -89,6 +89,8 @@ tc --profile agent-secrets secrets get OPENAI_API_KEY --raw
|
|
|
89
89
|
- The network belongs to the owner, so a profile with no recorded owner needs `--owner`, the did:pkh of the account that will approve (OpenKey refuses a network owned by anyone else). Without it the login fails with `OWNER_DID_UNKNOWN`; it never falls back to the profile's session `did:key`. A recorded or explicit owner address is EIP-55 checksummed in the network URN.
|
|
90
90
|
- If the owner unchecks decrypt, the login still succeeds and lists the decrypt entry in `declined`; reads of that secret then fail. Older OpenKey deployments sign decrypt inside the owner's secrets space rather than as a top-level network grant. That does not grant raw decrypt: it is listed in `declined` with an old-deployment warning and omitted from saved approved permissions. A later `auth request --grant` or secret-read escalation refuses that old nested proof before activating or storing a delegation, with `OPENKEY_GRANT_BROADENED`; obtain a new approval from a deployment that signs the raw network grant.
|
|
91
91
|
- A secrets command (`get`, `list`, `put`, `delete`) on an OpenKey profile without a grant for the name fails fast only when neither stdin nor stderr is a terminal: `PERMISSION_DENIED` (exit 5) with a hint naming this scoped login. Redirecting stdout (`|`, `>`, or command substitution) does not prevent a person at a terminal from seeing the browser approval prompt. If the session is missing or expired, a headless secrets command, including the canonical `secrets get` operation, instead fails with `AUTH_REQUIRED` (exit 3) and the scoped paste-login hint before invoking the node or starting an unscoped browser refresh.
|
|
92
|
+
- At a terminal, the owner approves the missing grant in OpenKey. The request carries the missing entries, `tinycloud.capabilities/read` on the space root (the deployed OpenKey refuses a request without it; newer OpenKey releases require it only when it is requested) and raw decrypt in the `encryption` pseudo-space with the owner's address EIP-55 checksummed; a decrypt-only request is placed in the `secrets` space (or `--space`), since OpenKey needs exactly one space. The signed `capabilities/read` counts as requested; anything else beyond the request is refused (`OPENKEY_GRANT_BROADENED`). The grant is stored with its signed SIWE proof (`siweProof`) and a request binding to exactly its signed capabilities (`cli-grant:<cid>`; see below). `secrets get` and other operations (including MCP), in this process or a new one, re-verify that proof against the profile's session key and owner, and hold the grant to its binding, before using it. A stored grant that fails that check (for example one approved by an earlier release, which stored no proof) is not used by them, and a read that needed it asks for approval again. Each such grant is reported by a fixed reason code (`proof_missing`, `unbound`, `outside_request`, `host_mismatch`, `activation_rejected`, ...) and, when the stored CID is the CID of the stored authorization bytes, that CID; never by error text. In JSON mode (`--json`, or stdout not a terminal) the warnings go inside the error's `warnings` array (beside any validated `meta`) when the command fails, or as one `{"warnings": [...]}` line on stderr after the command's output when it succeeds, and stderr carries nothing else; otherwise they are one stderr line saying to approve access again. Expired grants are dropped without a warning. A stale grant needs no cleanup: it expires on its own.
|
|
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. 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. This needs a signed session: a delegate session created by `tc auth import` stores no SIWE or expiry, so its expiry is detected only when the node refuses it.
|
|
92
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.
|
|
93
95
|
|
|
94
96
|
## More spaces after login
|
|
@@ -99,20 +101,20 @@ Login covers one space. Request further spaces on the logged-in profile:
|
|
|
99
101
|
tc --profile PROFILE auth request --manifest FILE --grant --device
|
|
100
102
|
```
|
|
101
103
|
|
|
102
|
-
`auth request --device` defaults to a 7-day lifetime (`--expiry` to change it, at most `30d`). Without `--device`, `--grant` opens the OpenKey browser flow. Browser grants require a signed proof for the profile's session key and owner. Only the permissions and expiry in that proof can be activated and stored; an older space-nested decrypt proof, missing or foreign-owner proof, broader grant, wrong space, or too-long expiry is refused without storing a delegation.
|
|
104
|
+
`auth request --device` defaults to a 7-day lifetime (`--expiry` to change it, at most `30d`). Without `--device`, `--grant` opens the OpenKey browser flow. Each space's request adds `tinycloud.capabilities/read` on that space's root, which OpenKey requires; a request of only raw decrypt is placed in the profile's primary space. Browser grants require a signed proof for the profile's session key and owner. Only the permissions and expiry in that proof can be activated and stored; an older space-nested decrypt proof, missing or foreign-owner proof, broader grant, wrong space, or too-long expiry is refused without storing a delegation.
|
|
103
105
|
|
|
104
106
|
## Imported delegations and their request bindings
|
|
105
107
|
|
|
106
|
-
Delegations stored in a profile's `additional-delegations.json` are installed again on every later command. A compact-UCAN delegation is installed only through validated activation, and only when its record carries an `authorityRequest` binding whose `requested` capabilities contain everything the delegation's signed bytes grant. A delegation broader than its binding is refused before it is activated. The CLI, `tc secrets get`, and MCP tools all apply this rule. A stored record whose `delegationHeader` is anything but a single string `Authorization` installs nothing.
|
|
108
|
+
Delegations stored in a profile's `additional-delegations.json` are installed again on every later command. A compact-UCAN delegation, or a signed-login (CACAO) grant stored with its `siweProof`, is installed only through validated activation, and only when its record carries an `authorityRequest` binding whose `requested` capabilities contain everything the delegation's signed bytes grant. A delegation broader than its binding is refused before it is activated. The CLI, `tc secrets get`, and MCP tools all apply this rule. A stored record whose `delegationHeader` is anything but a single string `Authorization` installs nothing.
|
|
107
109
|
|
|
108
110
|
- **Request-bound import.** `tc auth import` of an artifact with a `requestId` (from `tc auth grant`, or MCP `tinycloud_auth_import`) binds the delegation to the stored request it was checked against. Importing a stored delegation again this way replaces its record and binding.
|
|
109
111
|
- **Import without a request.** `tc auth import` of a bare portable delegation, a stored record, or an envelope with no `requestId`, addressed to this profile's session key, is validated the same way and bound to exactly its own signed capabilities: request ID `unbound-import:<cid>`, with an `authorityRequestAudit` note. No request limits it: the profile gets the delegation's full signed authority, whatever the artifact's `permissions` say. To limit what a delegation may grant, import it against a request instead (`tc auth request --emit`, then `tc auth grant` by the owner, then `tc auth import` of the result). This route never replaces or weakens a binding already stored for the same CID. A delegation whose `delegationHeader` is anything but a single string `Authorization` is refused with `INVALID_AUTH_IMPORT`.
|
|
110
112
|
- **One-time migration.** Records stored by earlier releases have no binding. The first command that restores the profile's own session binds each one to exactly its own signed capabilities (`migrated:<cid>`, audit note `source: "migration"`) and writes `delegation-binding-migration.json` in the profile directory, listing the CIDs it bound and the ones it left unbound. A migrated record never grants more than it did before. A record that cannot be bound (expired, for another session key, malformed, or signing capabilities no request can hold) stays unbound and installs nothing afterwards; the others are still migrated. Migration is best-effort and restartable: it runs under the profile lock, only while the stored session is still the one the command restored, writes the bound records first and the marker last, and a command that is interrupted or finds the lock busy leaves the rest to the next command. Bindings already written are kept.
|
|
111
113
|
- **After migration**, a compact-UCAN record without a valid binding installs nothing. For example, a delegation an older release imported, or a record pasted into the file. To recover it, run `tc auth import` on the artifact again with this release, or import a request-bound artifact for it.
|
|
112
114
|
- Older releases ignore the new fields. They do not enforce bindings, and an older `tc auth import` of a CID that is already stored replaces its record with an unbound one, which this release then refuses until it is imported again.
|
|
113
|
-
- Signed-login grants
|
|
115
|
+
- **CLI grants.** Signed-login grants from `tc auth request --grant` (OpenKey or local key) and secret-read approvals are checked against the request they answer, then stored with their `siweProof` and bound to exactly their own signed capabilities, read through the same validated activation replay uses (request ID `cli-grant:<cid>`, audit note `source: "cli-grant"`). The CLI, `tc secrets get` and MCP tools all install them under the rule above. A local-key grant of only raw decrypt on the owner's network names no space; it is verified and stored like any other grant. A new grant never replaces a stored record for the same CID that carries a binding. Signed-login grants stored by earlier releases carry neither proof nor binding: the CLI installs them as before, and the operations runtime does not use them (`proof_missing`). A signed-login record that carries an `authorityRequest` but no `siweProof` cannot be held to its binding and installs nothing; one with a `siweProof` but no valid binding installs nothing either (`unbound`). A cross-user delegation (addressed to the profile's identity rather than its session key) is not installed at all; `secrets get --delegation` reads it directly.
|
|
114
116
|
|
|
115
|
-
The binding is local profile data. It stops records written by other paths from granting authority. It does not stop someone who can write the profile directory, who already holds the session key and the signed bytes.
|
|
117
|
+
The binding is local profile data. It stops records written by other paths from granting authority. A request binding limits a stored grant to its signed authority, but it does not prove where the record came from: anyone who can write the profile store and holds a valid grant for this session key can add one. It does not stop someone who can write the profile directory, who already holds the session key and the signed bytes.
|
|
116
118
|
|
|
117
119
|
## Unscoped login and sign-out
|
|
118
120
|
|
|
@@ -144,7 +144,7 @@ tc --profile agent secrets get OPENAI_API_KEY --raw
|
|
|
144
144
|
|
|
145
145
|
`secrets: { NAME: true }` requests, in the owner's `secrets` space, `kv/get` on `vault/secrets/NAME` and `capabilities/read` on `""`, plus the raw network entry `{ "service": "tinycloud.encryption", "space": "encryption", "path": "urn:tinycloud:encryption:<ownerDid>:default", "actions": ["tinycloud.encryption/decrypt"] }`. The owner DID comes from the profile's recorded owner, else `--owner`, and its address is EIP-55 checksummed in the URN; with neither the login fails with `OWNER_DID_UNKNOWN`. The JSON result lists approved `permissions` and `declined` entries. A decrypt signed inside the owner's space by an older OpenKey deployment is not a raw grant: scoped login reports it in `declined` and omits it from saved permissions. Browser `auth request --grant` or secret-read escalation refuses that old nested proof before activating or storing a grant (`OPENKEY_GRANT_BROADENED`); a renewed approval must sign the raw network grant.
|
|
146
146
|
|
|
147
|
-
When neither stdin nor stderr is a terminal, `secrets get|list|put|delete` on an OpenKey profile that lacks the grant fails with `PERMISSION_DENIED` (exit 5) and a scoped paste-login hint. Missing or expired sessions instead fail with `AUTH_REQUIRED` (exit 3) before a canonical read invokes the node or starts an unscoped browser refresh. Redirected stdout alone does not disable an owner's browser approval; its URL appears on stderr and terminal stdin accepts a code. `secrets get -o` validates the destination and existing directory before any read or decrypt, then syncs a fresh 0600 inode and atomically replaces an existing regular file. Directory sync is best-effort after replacement and cannot turn a completed write into a reported failure. It refuses symlinks, directories, devices and absent parent directories (`INVALID_ARGUMENT`); an unreplaceable filesystem target fails with a sanitized error naming the destination, without a non-atomic fallback. A node that refuses decrypt (HTTP 401/403) is reported as missing authority on the invoked network, not as an undecryptable secret.
|
|
147
|
+
When neither stdin nor stderr is a terminal, `secrets get|list|put|delete` on an OpenKey profile that lacks the grant fails with `PERMISSION_DENIED` (exit 5) and a scoped paste-login hint. Missing or expired sessions instead fail with `AUTH_REQUIRED` (exit 3) before a canonical read invokes the node or starts an unscoped browser refresh. On any profile kind, a stored signed session that has expired or no longer verifies fails with `AUTH_REQUIRED` (exit 3) and a sign-in hint before any node request, never as a retryable `NODE_ERROR`; operations and MCP report a non-retryable `AUTH_REQUIRED`. Any service result coded `AUTH_REQUIRED` or `PERMISSION_DENIED` without an HTTP status now exits 3 or 5 instead of 1 in commands using `cliErrorFromService` (account, delegation, duckdb, kv, secrets list/put/delete, space, sql, vars and vault); for example, `tc secrets list` without a grant returns the NodeSecretsService's status-less `PERMISSION_DENIED` with exit 5. `tc secrets get` and `tc auth import` additionally supply the profile kind's sign-in hint for `AUTH_REQUIRED`. A delegate session bootstrapped by `tc auth import` stores no SIWE or expiry, so its expiry is found only when the node refuses it. At a terminal, the owner approves a missing grant in OpenKey: the request adds `tinycloud.capabilities/read` on the space root and keeps raw decrypt in the `encryption` pseudo-space, and a decrypt-only request is placed in the secrets space. The grant is stored with its signed SIWE proof and bound to exactly its signed capabilities, and `secrets get` (like every operation, including MCP) re-verifies it against the session key and owner, and holds it to that binding, in the same process or a new one; a stored grant that fails (such as one from an earlier release, stored without a proof) is skipped and reported as a `STORED_GRANT_SKIPPED` warning with a fixed reason code and its CID when that CID is the CID of the stored bytes: inside the JSON error's `warnings` (including a failed `-o` write), as one `{"warnings": [...]}` stderr line after a successful read's output, or as one human-readable stderr line. `--json` selects the JSON forms even with stdout on a terminal, and suppresses progress spinners. Expired grants are dropped silently. Operations and MCP results carry the same `warnings` array. A JSON error can carry both `meta` (validated authorization fields) and `warnings`. A canonical `secrets get` that still lacks authority fails with `PERMISSION_DENIED` (exit 5). Redirected stdout alone does not disable an owner's browser approval; its URL appears on stderr and terminal stdin accepts a code. `secrets get -o` validates the destination and existing directory before any read or decrypt, then syncs a fresh 0600 inode and atomically replaces an existing regular file. Directory sync is best-effort after replacement and cannot turn a completed write into a reported failure. It refuses symlinks, directories, devices and absent parent directories (`INVALID_ARGUMENT`); an unreplaceable filesystem target fails with a sanitized error naming the destination, without a non-atomic fallback. A node that refuses decrypt (HTTP 401/403) is reported as missing authority on the invoked network, not as an undecryptable secret.
|
|
148
148
|
|
|
149
149
|
## Node Health
|
|
150
150
|
|
|
@@ -194,7 +194,7 @@ The fragment after `#` is the read authority. It never reaches a server in a que
|
|
|
194
194
|
|
|
195
195
|
**Concurrent history changes.** A Share command resolves its profile once for authentication and every sender-history operation, including retries; a later default-profile change cannot redirect a share link into another profile. If a write waits more than about 2 seconds to acquire the profile lock, the CLI writes one waiting notice to stderr naming that profile. A profile removed after an earlier history read or during an operation, or repeated key/salt changes, returns `SHARE_HISTORY_RETRY` (exit 1) with a retry hint; a profile missing from the outset still returns `PROFILE_NOT_FOUND` with the setup hint. If a history retry follows a revoke, check the node state before trying again: revocation might already have succeeded even though sender history could not record it.
|
|
196
196
|
|
|
197
|
-
Share publication without `--expires` requests a seven-day lifetime. For a session-only profile, the CLI clamps that request to the verified SIWE session expiry, prints a notice to stderr, and reports `"expiryClamped": true` in JSON. Explicit lifetimes beyond the session end, or with less than 60 seconds left after second-precision rounding, fail with `SESSION_LIFETIME_EXCEEDED`. Expired/invalid restored sessions and missing owner authority return `AUTH_REQUIRED`; rejected KV upload or delegation scopes return `PERMISSION_DENIED`. A session-only profile whose authority for an anyone-with-link share carries signed restrictions (caveats) is refused with `PERMISSION_DENIED` before anything is stored, because the link's delegation cannot carry the caveats; approve Share publishing without restrictions on a new profile (`tc init --name publisher --key-only && tc --profile publisher enable share`).
|
|
197
|
+
Share publication without `--expires` requests a seven-day lifetime. For a session-only profile, the CLI clamps that request to the verified SIWE session expiry, prints a notice to stderr, and reports `"expiryClamped": true` in JSON. Explicit lifetimes beyond the session end, or with less than 60 seconds left after second-precision rounding, fail with `SESSION_LIFETIME_EXCEEDED`. Expired/invalid restored sessions and missing owner authority return `AUTH_REQUIRED`; rejected KV upload or delegation scopes return `PERMISSION_DENIED`. A session-only profile whose authority for an anyone-with-link share carries signed restrictions (caveats) is refused with `PERMISSION_DENIED` before anything is stored, because the link's delegation cannot carry the caveats; approve Share publishing without restrictions on a new profile (`tc init --name publisher --key-only && tc --profile publisher enable share`). Full TinyCloud storage returns the general storage-full error (`STORAGE_QUOTA_EXCEEDED` or `STORAGE_LIMIT_REACHED`, exit 10), and any other failed upload returns `UPLOAD_FAILED`; nothing is shared in either case. A filename that has characters other than `A-Z a-z 0-9 . _ -`, starts with anything but a letter or digit, contains `..`, or is longer than 128 characters is stored under a readable URI-safe name. It keeps its extension when that is 1-16 ASCII letters or digits (`Q3 plan (draft).md` is stored as `Q3-plan-draft.md`, `.env` as `share.env`); otherwise its dots become dashes, so it never gains one. Bearer links show the stored name and `receive` writes it, while addressed links keep the original. JSON publish output includes `expiryClamped: false` when no clamp was needed.
|
|
198
198
|
|
|
199
199
|
### Share Publish Options
|
|
200
200
|
|
|
@@ -220,7 +220,7 @@ Share publication without `--expires` requests a seven-day lifetime. For a sessi
|
|
|
220
220
|
| File | `tc kv put key --file ./data.txt` |
|
|
221
221
|
| Stdin | `echo "data" \| tc kv put key --stdin` |
|
|
222
222
|
|
|
223
|
-
KV keys cannot contain spaces or control characters: the SDK sends keys unescaped in the node resource URI, so `get`, `put`, `head`, `delete` and `list --prefix` refuse them with `USAGE_ERROR` (exit 2) before authenticating. A full storage
|
|
223
|
+
KV keys cannot contain spaces or control characters: the SDK sends keys unescaped in the node resource URI, so `get`, `put`, `head`, `delete` and `list --prefix` refuse them with `USAGE_ERROR` (exit 2) before authenticating. A `put` on full storage returns the general storage-full error (exit 10).
|
|
224
224
|
|
|
225
225
|
## Profile Directory Structure
|
|
226
226
|
|
|
@@ -260,6 +260,8 @@ On either recovery path, an orphaned `.recover-*` claim can hold a replacement o
|
|
|
260
260
|
|
|
261
261
|
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.
|
|
262
262
|
|
|
263
|
-
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. `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
|
|
263
|
+
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.
|
|
264
264
|
|
|
265
265
|
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.
|
|
266
|
+
|
|
267
|
+
Every command that writes reports full TinyCloud storage the same way: `STORAGE_QUOTA_EXCEEDED` (storage is full) or `STORAGE_LIMIT_REACHED` (this write is larger than what is left), exit 10, with nothing written (`tc sql copy` instead names the table and the rows it had already copied). The hint gives the account totals when the node reports them (`371.7 MiB used of 100 MiB (free plan)`), says reading still works, and links to https://account.tinycloud.xyz/billing. Retrying cannot succeed until the owner frees up space or upgrades.
|