@tinycloud/cli 1.0.1-beta.5 → 1.1.0-beta.11
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 +2253 -1863
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +2443 -588
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +5 -5
- package/skills/tc-cli/AUTH.md +14 -1
- package/skills/tc-cli/REFERENCE.md +37 -7
- package/skills/tc-cli/SKILL.md +5 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinycloud/cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0-beta.11",
|
|
4
4
|
"description": "TinyCloud CLI - manage data, delegations, and nodes from the terminal",
|
|
5
5
|
"author": "TinyCloud, Inc.",
|
|
6
6
|
"license": "EGPL",
|
|
@@ -29,12 +29,12 @@
|
|
|
29
29
|
"check:skill-pack": "node scripts/check-skill-pack.mjs"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@tinycloud/share-sdk": "1.0
|
|
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.
|
|
34
|
+
"@tinycloud/node-sdk": "3.1.0-beta.7",
|
|
35
35
|
"@tinycloud/node-sdk-wasm": "1.7.6",
|
|
36
|
-
"@tinycloud/sdk-core": "3.1.0-beta.
|
|
37
|
-
"@tinycloud/operations": "0.
|
|
36
|
+
"@tinycloud/sdk-core": "3.1.0-beta.7",
|
|
37
|
+
"@tinycloud/operations": "0.4.0-beta.10",
|
|
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
|
@@ -23,7 +23,7 @@ TC="$(npm prefix --global)/bin/tc"
|
|
|
23
23
|
- Scoped and device logins refuse a profile that holds a local owner key (`LOCAL_OWNER_PROFILE`): a local-owner-key posture, `authMethod: "local"`, or a stored private key, whatever the recorded posture. Use a separate profile.
|
|
24
24
|
- A live, unexpired session is replaced only by a scope that keeps everything it holds for the same owner and lasts at least as long: renewing the same manifest, or widening a session the owner narrowed earlier. Anything else, including an approval the owner narrowed, one that signs a caveat (a restriction such as a tenant) onto an action the live session holds unrestricted, or a renewal that would end earlier, refuses with `SESSION_IN_USE` and saves nothing. Use a new profile; pass `--replace-session` only when replacing that session is intended. The check runs on the request before consent and again on the approved scope before saving.
|
|
25
25
|
- Approval can take minutes. Before saving, the CLI re-reads the profile, key and session under the profile lock; if another login, key rotation or logout changed them meanwhile, it refuses with `PROFILE_CHANGED_DURING_LOGIN` and saves nothing. The CLI's profile, key and session writers take the same lock, and a failed commit write restores all three before the lock is released. If that restore fails too, the login fails with `PROFILE_STATE_INCONSISTENT` naming each failure; a crash between writes surfaces the same code on the next scoped login; check `tc --profile PROFILE context`, then use `--replace-session` or a new profile. The commit waits up to 45 s for the lock (a crashed holder's lock is reclaimed after 30 s), else `PROFILE_LOCK_TIMEOUT`; other commands that time out on the lock report the same code. Wait for the other tc or MCP process and retry.
|
|
26
|
-
- The other profile writers follow the same discipline. Local `tc auth rotate` signs in first and replaces the session in that compare-and-commit, so a change meanwhile refuses with `PROFILE_CHANGED_DURING_LOGIN` and the previous session stays. `tc profile delete` waits for the profile lock and removes the profile's files while holding it; a name that is not one path segment fails with `INVALID_PROFILE_NAME`. When `tc auth import` bootstraps a delegate profile's first session, the bootstrap reads and writes under the lock and never replaces a session that appeared after the import checked for one (`PROFILE_CHANGED_DURING_IMPORT`); if the bootstrap itself fails, it is rolled back. If a request-bound import (an artifact with a `requestId`) then fails, its bootstrap is rolled back only if the profile still
|
|
26
|
+
- The other profile writers follow the same discipline. Local `tc auth rotate` signs in first and replaces the session in that compare-and-commit, so a change meanwhile refuses with `PROFILE_CHANGED_DURING_LOGIN` and the previous session stays. `tc profile delete` waits for the profile lock and removes the profile's files while holding it; a key, session, delegation or permission-request write that was waiting for the lock meanwhile then fails with `PROFILE_NOT_FOUND` instead of recreating the profile (see REFERENCE.md, Profile lock). A name that is not one path segment fails with `INVALID_PROFILE_NAME`. When `tc auth import` bootstraps a delegate profile's first session, the bootstrap reads and writes under the lock and never replaces a session that appeared after the import checked for one (`PROFILE_CHANGED_DURING_IMPORT`); if the bootstrap itself fails, it is rolled back. If a request-bound import (an artifact with a `requestId`) then fails, its bootstrap is rolled back only if the session and the profile's session DID and space still hold exactly what the bootstrap wrote (other profile changes made meanwhile, such as a new default space, are kept); if another login, logout or profile update replaced them, that newer state is kept and the error says so. Other import forms keep a successful bootstrap when a later step fails.
|
|
27
27
|
|
|
28
28
|
While waiting, the command writes one approval line to stderr and keeps polling for the whole approval window (about 10 minutes), riding out dropped connections:
|
|
29
29
|
|
|
@@ -100,6 +100,19 @@ tc --profile PROFILE auth request --manifest FILE --grant --device
|
|
|
100
100
|
|
|
101
101
|
`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.
|
|
102
102
|
|
|
103
|
+
## Imported delegations and their request bindings
|
|
104
|
+
|
|
105
|
+
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.
|
|
106
|
+
|
|
107
|
+
- **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.
|
|
108
|
+
- **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`.
|
|
109
|
+
- **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.
|
|
110
|
+
- **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.
|
|
111
|
+
- 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.
|
|
112
|
+
- Signed-login grants stored by `tc auth request --grant` and secret-read approvals are installed by the CLI as before, and a new grant never replaces a stored record for the same CID that carries a binding. A signed-login record that carries an `authorityRequest` but no `siweProof` cannot be held to its binding and installs nothing. The operations runtime behind MCP tools and `tc secrets get` never installed them and still does not. 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.
|
|
113
|
+
|
|
114
|
+
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.
|
|
115
|
+
|
|
103
116
|
## Unscoped login and sign-out
|
|
104
117
|
|
|
105
118
|
`tc init` and `tc auth login` without a manifest request broad default-space consent, including writes, and replace the profile's session as before. Prefer `init --key-only` followed by a scoped login. They still verify OpenKey's signed proof whenever it carries one, the profile records an owner, or `--expiry` is given, and they keep the recorded owner. Non-interactive `auth login` never switches to device mode on its own, and never waits on a browser that cannot open: without `--device --manifest`, `--paste` or `--no-popup` it fails fast with `INTERACTIVE_LOGIN_REQUIRED`.
|
|
@@ -80,6 +80,8 @@ tc delegation revoke <cid>
|
|
|
80
80
|
|
|
81
81
|
Actions are auto-prefixed with `tinycloud.` if not already.
|
|
82
82
|
|
|
83
|
+
`tc auth import` stores delegations in the profile's `additional-delegations.json`; a compact-UCAN delegation addressed to the profile's session key is stored with an `authorityRequest` binding. Later commands install such a delegation only when its signed capabilities fit inside that binding. [AUTH.md](AUTH.md#imported-delegations-and-their-request-bindings) describes how each import route binds a delegation, the one-time migration of records from earlier releases, and how to recover a record that installs nothing.
|
|
84
|
+
|
|
83
85
|
## Spaces
|
|
84
86
|
|
|
85
87
|
```bash
|
|
@@ -166,9 +168,10 @@ Publishing needs the publishing scope on a dedicated profile (`tc --profile publ
|
|
|
166
168
|
|
|
167
169
|
```bash
|
|
168
170
|
tc share publish ./note.md --json # bearer link
|
|
169
|
-
tc share publish ./note.md --to email:alice@example.com --notify # addressed: exact email, emailed invitation
|
|
171
|
+
tc share publish ./note.md --to email:alice@example.com --notify # addressed: exact email, emailed invitation (read required)
|
|
170
172
|
tc share publish ./note.md --to did:pkh:eip155:1:0xRecipient... # addressed: one DID
|
|
171
173
|
tc share publish ./note.md --to domain:example.com # addressed: any verified address at the domain
|
|
174
|
+
tc share publish ./note.md --to domain:example.com --notify --notify-to alice@example.com # invite one matching mailbox; node 1.17.3+
|
|
172
175
|
cat note.md | tc share publish - --name note.md
|
|
173
176
|
|
|
174
177
|
printf '%s' "$SHARE_URL" | tc share inspect - --json # verify, print safe metadata
|
|
@@ -177,13 +180,19 @@ printf '%s' "$SHARE_URL" | tc share receive - --output .
|
|
|
177
180
|
|
|
178
181
|
tc share list --json # sender history, no complete URLs
|
|
179
182
|
tc share show <id> [--reveal-link]
|
|
180
|
-
tc share notify <id> --to alice@example.com # retry email delivery
|
|
183
|
+
tc share notify <id> --to alice@example.com # retry email delivery within 5 minutes of publication
|
|
181
184
|
tc share revoke <id>
|
|
182
185
|
```
|
|
183
186
|
|
|
184
|
-
**Bearer vs addressed.** A bearer link (default, `--to anyone`) is `/viewer#tc1=<TinyCloud delegation>`: anyone holding the complete URL can read the file until it expires, and revocation cannot recall copies already received. Addressed links (`--to email:`, `--to did:`, `--to domain:`) are `/s/inline#v=2&p=<sealed Policy/v3 envelope>`: the content is encrypted and only the named recipient can open it after proving their email or DID in Share. `tc share receive` returns the bytes of a bearer link; for an addressed link it exits 6 with `CLAIM_REQUIRED` because the recipient claims it in Share. `--notify`
|
|
187
|
+
**Bearer vs addressed.** A bearer link (default, `--to anyone`) is `/viewer#tc1=<TinyCloud delegation>`: anyone holding the complete URL can read the file until it expires, and revocation cannot recall copies already received. Addressed links (`--to email:`, `--to did:`, `--to domain:`) are `/s/inline#v=2&p=<sealed Policy/v3 envelope>`: the content is encrypted and only the named recipient can open it after proving their email or DID in Share. `tc share receive` returns the bytes of a bearer link; for an addressed link it exits 6 with `CLAIM_REQUIRED` because the recipient claims it in Share. `--notify` requires the `read` action: exact-email targets invite the matched mailbox; domain targets need `--notify-to` naming one canonical mailbox at that exact domain and a node reporting version 1.17.3 or later. The CLI refuses unsupported domain invitations before uploading or registering a share. A session-only publisher can deliver its own domain policy when the invoking DID matches its signed owner DID; a different sender DID cannot. `tc share notify <id> --to <mailbox>` can address another mailbox in a domain share under the same node and sender-DID requirements; it refuses stored shares without `read` or on an unsupported node before sending.
|
|
188
|
+
|
|
189
|
+
The fragment after `#` is the read authority. It never reaches a server in a query string or HTTP request; keep complete URLs out of logs. Human publish output is exactly one URL. JSON publish output retains the redacted publication fields and adds `notification` (the `ShareNotifyResult`, including `state`, `attempts`, and optional failure `reason`) when `--notify` was requested. Inspect never prints plaintext or secret-bearing fields. Receive uses a sanitized single-segment filename and refuses overwrite unless `--force`. `revoke` revokes addressed shares at the owner node and reports bearer retention honestly. Pre-cutover blob-backed and plaintext `?tc2` links are not accepted.
|
|
190
|
+
|
|
191
|
+
**Notification window.** The CLI requests a stable signed delivery authorization until the earlier of share expiry and 5 minutes after its sender-history `registeredAt` timestamp. The node allows an authorization no more than 5 minutes in the future; keeping the expiry stable also makes idempotent retries use the same request. After the window, `tc share notify` prints `partial-failure` (exit 9; JSON `retryable: false`, `reason: "delivery-window-expired"`) and explains that the sender must publish a new share and invite the recipient then. `share publish --notify --json` includes the same failure under `notification`. A confirmed repeat within the window prints `already-delivered`; an unconfirmed failed attempt remains retryable until the window closes. If the sender history cannot record a successful delivery confirmation, the CLI warns on stderr but preserves the successful publish/notify result; a later repeat can report `delivered` again.
|
|
185
192
|
|
|
186
|
-
|
|
193
|
+
**History and node preflight.** Post-publication sender-history writes wait up to 45 seconds for another process's profile lock (including stale-lock recovery). If the first history write still fails, the CLI warns but prints the published link with its normal exit status; a later `share list`, `share notify <id>`, or `share revoke <id>` cannot find that share by ID. An immediate `--notify` can still deliver using the in-process record. Other share-command profile-lock timeouts report `PROFILE_LOCK_TIMEOUT` with a retry hint. Standalone domain notification validates the recipient and expired delivery window locally before contacting `/info`: an expired window remains exit 9 even if the node is unavailable. A node-info transport failure, including a stalled response body, is `UNAVAILABLE` (exit 4); a completed but unsupported or malformed version is `INVALID_ARGUMENT` (exit 2). Publish-time refusals say nothing was shared and no invitation was sent; standalone notify refusals say no invitation was sent.
|
|
194
|
+
|
|
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.
|
|
187
196
|
|
|
188
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`). A full storage quota returns `STORAGE_QUOTA_EXCEEDED`, with the used and limit sizes when the node reports them, 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.
|
|
189
198
|
|
|
@@ -194,7 +203,8 @@ Share publication without `--expires` requests a seven-day lifetime. For a sessi
|
|
|
194
203
|
| `files` | One or more files, or `-` for bounded stdin | required |
|
|
195
204
|
| `--name <filename>` | Safe filename for stdin | `stdin.md` |
|
|
196
205
|
| `--to <target>` | `anyone`, recipient DID, email, or `domain:<name>` | `anyone` |
|
|
197
|
-
| `--notify` |
|
|
206
|
+
| `--notify` | Email the exact-email recipient, or one domain mailbox with `--notify-to`; requires `read` | off |
|
|
207
|
+
| `--notify-to <email>` | Canonical mailbox at the `--to domain:<name>` target (node 1.17.3+) | required for domain `--notify` |
|
|
198
208
|
| `--expires <duration>` | Duration: `1h`, `7d`, `1w`, or ISO date | implicit `7d`, clamped for session-only profiles |
|
|
199
209
|
| `--media-type <type>` | Media type for a single input | inferred |
|
|
200
210
|
| `--action <actions...>` | Addressed permission: `read`, `list`, or `edit` | `read` |
|
|
@@ -217,19 +227,39 @@ KV keys cannot contain spaces or control characters: the SDK sends keys unescape
|
|
|
217
227
|
```
|
|
218
228
|
~/.tinycloud/ # 0700
|
|
219
229
|
├── config.json # Global config (defaultProfile)
|
|
230
|
+
├── profile-locks/
|
|
231
|
+
│ └── {name}/ # Turn lock (see below); kept after `tc profile delete`
|
|
220
232
|
└── profiles/
|
|
221
233
|
└── {name}/ # 0700
|
|
222
234
|
├── profile.json # Host, DID, chainId, ownerDid, spaceId (0600)
|
|
223
235
|
├── key.json # Ed25519 JWK keypair (0600)
|
|
224
236
|
├── session.json # Delegation, spaceId (0600)
|
|
237
|
+
├── additional-delegations.json # Imported and granted delegations, with request bindings (0600)
|
|
238
|
+
├── auth-requests.json # Stored permission requests (0600)
|
|
239
|
+
├── delegation-binding-migration.json # Present once unbound records were migrated (0600)
|
|
240
|
+
├── .lock/ # Lock shared with older releases, while held
|
|
225
241
|
└── cache/
|
|
226
242
|
```
|
|
227
243
|
|
|
244
|
+
### Profile lock
|
|
245
|
+
|
|
246
|
+
Every write to a profile (key, session, settings, delegations, permission requests) runs under the profile's lock, shared by `tc` and the MCP server. Processes of this release first take a turn in `profile-locks/{name}/`, then the `profiles/{name}/.lock` directory every release uses, so they keep excluding CLI 1.0.0-beta.16 and older (which hold `.lock` from its creation) and 1.0.0-beta.17 … 1.0.1-beta.4 (which hold it once `owner.json` is linked in), and those exclude them. Among processes of this release, mutual exclusion holds however long any of them is paused, and needs no hard links (FAT/exFAT and SMB mounts work).
|
|
247
|
+
|
|
248
|
+
That guarantee assumes every process using the profile runs on the same host and in the same PID namespace (a crashed process is recognised by its PID being gone), and a filesystem whose `rename` of a directory fails when the target exists. It does not hold for a `TC_HOME` shared across machines (NFS) or between containers that do not share a PID namespace: there a live process can look crashed, have its turn or staging directory removed, and the lock can be taken twice or stop working. Older releases running at the same time keep their own limits: a 1.0.0-beta.16-or-older process paused for longer than 30 s right after creating `.lock`, or a 1.0.0-beta.17 … 1.0.1-beta.4 process reclaiming `.lock` at the same moment as such a process, can still let two writers in. CLI 1.0.0-beta.16 and older never reclaim an abandoned `.lock` without an owner record; a newer release does, once it is 30 s old.
|
|
249
|
+
|
|
250
|
+
A crashed holder's lock is reclaimed automatically (`.lock` once 30 s old). With hard links, dead-owner recovery keeps a `.recover-*` claim inside `.lock` until it finishes; it never unlinks or moves a newly published live owner, and a failed claim never removes another writer's empty `.lock`. A crashed current-release recoverer may leave only a `.recover-*` claim. Releases through 1.0.1-beta.4 cannot reclaim it even after it ages. A current-release command that acquires the affected profile's lock processes the claim, restoring a live owner's record if it contains one.
|
|
251
|
+
|
|
252
|
+
On either recovery path, an orphaned `.recover-*` claim can hold a replacement owner's live record: the hard-link path may claim it before detecting a mismatch, and the no-hard-links path may move it before putting it back. If the recoverer crashes and that holder releases while its process stays alive, the next acquisition restores the claimed record as `owner.json`. Acquisition can then remain blocked until the PID exits **and** the record is at least 30 s old—for a long-lived MCP server, possibly until it restarts. This costs availability only; it never admits a second holder. To clear the stalled profile manually, remove its `profiles/{name}/.lock` **only when no `tc` or MCP process is running**.
|
|
253
|
+
|
|
254
|
+
`PROFILE_LOCK_TIMEOUT` naming `profile-locks/{name}` means its turn lock is damaged (for example by a disk error): when no `tc` or MCP process is using that profile, remove `~/.tinycloud/profile-locks/{name}`; it is recreated on the next write. A write that waited for the lock while `tc profile delete` removed the profile fails with `PROFILE_NOT_FOUND` instead of recreating it.
|
|
255
|
+
|
|
228
256
|
## DID Formats
|
|
229
257
|
|
|
230
258
|
- **Session key**: `did:key:z6Mk...#z6Mk...` — generated at init
|
|
231
259
|
- **Owner DID**: `did:pkh:eip155:{chainId}:{address}` — after auth
|
|
232
260
|
|
|
233
|
-
Use `--json` for machine-readable output; interactive commands can render human output. General command errors go to stderr as `{error: {code, message, hint?}}`. Inspect the structured code, not only the exit status.
|
|
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
|
+
|
|
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, storage quota exceeded 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.
|
|
234
264
|
|
|
235
|
-
|
|
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.
|
package/skills/tc-cli/SKILL.md
CHANGED
|
@@ -62,7 +62,11 @@ tc --profile publisher share publish ./note.md --json
|
|
|
62
62
|
tc --profile publisher share publish ./note.md --to email:alice@example.com --notify --json
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
Without `--to`, publish creates a bearer link: anyone holding the complete URL can read it. `--to email:`, `--to did:` and `--to domain:` create addressed links that only the named recipient can open after proving who they are. Keep complete URLs, including the `#` fragment, out of logs. The default lifetime is seven days, clamped to the verified session expiry; JSON output reports `expiryClamped`. Explicit `--expires` values beyond the session end fail instead of being silently shortened. See [REFERENCE.md](REFERENCE.md) for inspect, receive, list, show and revoke.
|
|
65
|
+
Without `--to`, publish creates a bearer link: anyone holding the complete URL can read it. `--to email:`, `--to did:` and `--to domain:` create addressed links that only the named recipient can open after proving who they are. Keep complete URLs, including the `#` fragment, out of logs. `--notify` requires `read`; domain invitations also require `--notify-to <email>` at the exact domain, a matching sender/owner DID and node 1.17.3+. Session-only publishers can notify their own domain policies. Standalone `tc share notify <id> --to <email>` refuses records without `read` and domain delivery on unsupported nodes before sending. It retries within 5 minutes of publication; after that it reports a non-retryable `partial-failure` (exit 9), and the sender must publish a new share to invite again. A confirmed repeat reports `already-delivered`; a failed history confirmation warns without hiding a successful delivery. Human publish prints only the URL; with `--notify --json`, the redacted publication JSON also contains `notification` with delivery status and attempts. The default lifetime is seven days, clamped to the verified session expiry; JSON output reports `expiryClamped`. Explicit `--expires` values beyond the session end fail instead of being silently shortened. See [REFERENCE.md](REFERENCE.md) for inspect, receive, list, show and revoke.
|
|
66
|
+
|
|
67
|
+
After a share is published, a profile-lock or history-write failure warns without hiding the published link; later `share list`, `share notify <id>`, and `share revoke <id>` cannot find an unrecorded share. Standalone domain notification checks the recipient and local five-minute window before probing the node; an expired window stays a non-retryable partial failure even when `/info` is unreachable.
|
|
68
|
+
|
|
69
|
+
Share commands pin one profile for authentication and all history writes, even when the default changes. History writes name that profile on stderr after about 2 seconds waiting for its lock. If a previously observed profile disappears, including between reading a share and recording its revocation, or its signing key keeps changing, `SHARE_HISTORY_RETRY` gives a retry hint; a profile that never existed instead reports `PROFILE_NOT_FOUND` with a setup hint. After a revoke reports a history retry, check the node's state before trying again because revocation may already have completed.
|
|
66
70
|
|
|
67
71
|
## Other operations
|
|
68
72
|
|