@tinycloud/cli 1.5.0-beta.3 → 1.5.0-beta.30
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/README.md +315 -103
- package/bin/tinycloud +9 -0
- package/dist/courier-entry.js +10179 -503
- package/dist/courier-entry.js.map +1 -1
- package/dist/index.js +1893 -101
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +5213 -2230
- package/dist/legacy-entry.js.map +1 -1
- package/package.json +10 -10
- package/skills/courier/SKILL.md +21 -0
- package/skills/courier/start.md +17 -0
- package/skills/{tc-cli → tinycloud-cli}/AUTH.md +21 -27
- package/skills/tinycloud-cli/INSTALL.md +36 -0
- package/skills/{tc-cli → tinycloud-cli}/REFERENCE.md +113 -97
- package/skills/{tc-cli → tinycloud-cli}/SDK.md +1 -1
- package/skills/{tc-cli → tinycloud-cli}/SKILL.md +32 -48
- package/skills/{tc-cli → tinycloud-cli}/release.json +1 -1
- package/skills/tc-cli/INSTALL.md +0 -35
|
@@ -5,7 +5,7 @@ This release has **Commander coverage tracked, not complete parity**:
|
|
|
5
5
|
|
|
6
6
|
- 4 migrated registration(s).
|
|
7
7
|
- 1 partially migrated registration(s).
|
|
8
|
-
-
|
|
8
|
+
- 148 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).
|
|
@@ -16,13 +16,10 @@ This release has **Commander coverage tracked, not complete parity**:
|
|
|
16
16
|
|
|
17
17
|
## Calling the CLI
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Run the installed CLI as `tinycloud`. On Linux, `tc` is iproute2 traffic control at `/usr/sbin/tc`.
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
|
|
23
|
-
[ -n "${TC_BIN:-}" ] || [ -x "$TC" ] || ! command -v npm >/dev/null || TC="$(npm prefix --global)/bin/tinycloud" # else a global npm install
|
|
24
|
-
[ -x "$TC" ] || { echo "TC_NOT_FOUND: $TC is not executable; install the CLI from https://agents.tinycloud.xyz/cli or set TC_BIN" >&2; exit 1; }
|
|
25
|
-
"$TC" --version
|
|
22
|
+
tinycloud --version
|
|
26
23
|
```
|
|
27
24
|
|
|
28
25
|
## Global Options
|
|
@@ -41,10 +38,9 @@ TC="${TC_BIN:-$HOME/.local/share/tinycloud/cli/node_modules/.bin/tinycloud}" #
|
|
|
41
38
|
## Context and Login
|
|
42
39
|
|
|
43
40
|
```bash
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
"$TC" --profile publisher context --json
|
|
41
|
+
tinycloud onboard # once; every later task reuses this profile
|
|
42
|
+
tinycloud auth request --cap SPEC --grant --paste --expiry 30d # more access: ask once
|
|
43
|
+
tinycloud context --json
|
|
48
44
|
```
|
|
49
45
|
|
|
50
46
|
`context` always emits JSON: profile, `ownerDid`, `sessionDid`, host, `spaceId` and local session expiry, with `access: "not-tested"`. It never prints keys or signed material.
|
|
@@ -53,15 +49,15 @@ TC="${TC_BIN:-$HOME/.local/share/tinycloud/cli/node_modules/.bin/tinycloud}" #
|
|
|
53
49
|
|------|-------------|
|
|
54
50
|
| `--device` | Approve on another device (phone) through OpenKey device authorization. Requires a KV-scoped `--manifest` (no `tinycloud.sql`, no secret decrypt). Prints `Approve on your phone: URL (code XXXX-XXXX)` to stderr and waits for the whole approval window |
|
|
55
51
|
| `--manifest <file>` | Request only this manifest's permissions: one space, plus raw `tinycloud.encryption` network entries (space `encryption`) such as a secrets decrypt grant. Every scoped login adds `tinycloud.capabilities/read` on path `""` in that space when missing (OpenKey requires it to sign). File path, `base64:<json>`, or `builtin:share-publishing` |
|
|
56
|
-
| `--expiry <duration>` | Session lifetime (`1h`, `7d`, milliseconds; at least `1m`) counted from approval; ISO dates are refused because OpenKey signs approval time plus a lifetime. Enforced against the signed session on every login path. Device login: at most `30d`, default `30d` |
|
|
52
|
+
| `--expiry <duration>` | Session lifetime (`1h`, `7d`, milliseconds; at least `1m`) counted from approval; ISO dates are refused because OpenKey signs approval time plus a lifetime. Enforced against the signed session on every login path. Normal OpenKey login defaults to `7d`; onboarding explicitly requests `30d`. Device login: at most `30d`, default `30d` |
|
|
57
53
|
| `--owner <did>` | Refuse approval by any identity other than this `did:pkh`; must agree with an owner the profile already recorded. OpenKey preselects that owner's key, so this is how to sign in with a key that is not the account's primary key; it works on a plain login without `--manifest` too. Also names the secrets owner for a manifest's `secrets` when the profile has no recorded owner |
|
|
58
54
|
| `--replace-session` | Scoped or device login: replace a live session the new scope would narrow, change or shorten (otherwise `SESSION_IN_USE`); renewals and widenings that last at least as long need no flag |
|
|
59
55
|
| `--replication-prefix <prefix>` | Repeatable login request; adds `get` + `sync` only when an unrestricted covering `get` is already requested. Saves prefixes on the profile and reapplies them on later logins and rotation |
|
|
60
56
|
| `--replication-allow-secrets` | Explicitly allow prefixes in the `secrets` space or `vault` tree; replication stores ciphertext only |
|
|
61
57
|
| `--method openkey\|local` | Browser OpenKey flow or local Ethereum key |
|
|
62
|
-
| `--paste`, `--no-popup` |
|
|
58
|
+
| `--paste`, `--no-popup` | Normal OpenKey login prints a short approval link on stderr, waits for approval, then automatically verifies, durably saves and acknowledges the result. It works in a non-interactive terminal without a localhost callback or return code. `--no-popup` skips opening a local browser, not waiting. Re-run the same command after interruption to resume. `--paste` instead selects the explicit manual fallback with a five-minute, single-use return code on stdin; EOF without a code returns `PASTE_CODE_MISSING` without saving a key or session. Automatic links do not produce a manual code. |
|
|
63
59
|
|
|
64
|
-
Device login JSON lists the signed, approved `permissions`, owner-unchecked `declined` capabilities, `ownerDid`, `spaceId` and `expiresAt`. `tinycloud auth request --manifest FILE --grant --
|
|
60
|
+
Device login JSON lists the signed, approved `permissions`, owner-unchecked `declined` capabilities, `ownerDid`, `spaceId` and `expiresAt`. `tinycloud auth request --manifest FILE --grant --paste` adds another space to the onboarding profile: send the owner the link and pass their code to the CLI exactly as sent (default lifetime 7d; `--grant --device` also works for KV-scoped manifests). `tinycloud enable share [--replace-session]` is shorthand for device login with `builtin:share-publishing`. OpenKey refuses `tinycloud.sql` and other abilities outside its device policy; use normal OpenKey login (`--method openkey --manifest`) for those. Explicit `auth login` can wait for approval non-interactively; data commands never initiate consent to repair missing authority. Only an explicit `--host` is stored on the profile. Errors: `MANIFEST_REQUIRED`, `SCOPE_REJECTED` (OpenKey refused a capability; the message names it), `SESSION_IN_USE` (the request or the approved scope would narrow a live session; use a new profile or `--replace-session`), `PROFILE_CHANGED_DURING_LOGIN` (another login, key rotation or logout changed the profile while approval was pending; nothing saved), `PROFILE_STATE_INCONSISTENT` (profile, key and session disagree after an interrupted write; `--replace-session` or a new profile), `PROFILE_LOCK_TIMEOUT` (another process held the profile lock past 45 s), `LOCAL_OWNER_PROFILE` (scoped or device login on a local-owner-key profile; use a separate profile), `OPENKEY_UNREACHABLE`, `DEVICE_AUTH_DENIED`, `DEVICE_AUTH_EXPIRED`, `DEVICE_AUTH_BINDING_MISMATCH` (OpenKey's approval metadata disagrees with the signed grant), `OPENKEY_OWNER_MISMATCH`, `OPENKEY_GRANT_BROADENED`, `OPENKEY_EXPIRY_EXCEEDED` (the signed session outlives `--expiry`). Owner addresses compare case-insensitively (EIP-55 or lowercase); chain id and space name compare exactly. See [AUTH.md](AUTH.md).
|
|
65
61
|
|
|
66
62
|
`builtin:share-publishing` requests, in the owner's `default` space, exactly:
|
|
67
63
|
|
|
@@ -83,17 +79,17 @@ No `del` anywhere and no `list` on the bearer prefix: no `tinycloud share` comma
|
|
|
83
79
|
## Delegations
|
|
84
80
|
|
|
85
81
|
```bash
|
|
86
|
-
|
|
82
|
+
tinycloud delegation create \
|
|
87
83
|
--to did:pkh:eip155:1:0xRecipient... \
|
|
88
84
|
--path kv/shared \
|
|
89
85
|
--actions kv/get,kv/put \
|
|
90
86
|
--expiry 24h
|
|
91
87
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
88
|
+
tinycloud delegation list # All
|
|
89
|
+
tinycloud delegation list --granted # Granted by me
|
|
90
|
+
tinycloud delegation list --received # Received by me
|
|
91
|
+
tinycloud delegation info <cid>
|
|
92
|
+
tinycloud delegation revoke <cid>
|
|
97
93
|
```
|
|
98
94
|
|
|
99
95
|
Actions are auto-prefixed with `tinycloud.` if not already.
|
|
@@ -104,16 +100,18 @@ warning that revocation signs you out. After the node confirms that exact CID,
|
|
|
104
100
|
the CLI runs its normal durable logout, removes the restorable session and key,
|
|
105
101
|
and blocks implicit reauthentication. Unrelated CIDs do not sign you out.
|
|
106
102
|
|
|
107
|
-
|
|
103
|
+
From a delegate (agent) profile, `delegation list --granted` lists the delegations that profile's own key issued: every page of the node's issued-delegations listing, each with its `status`, sorted by delegation ID rather than by date (the output's `note` says so). A node that does not offer the listing yet answers `ISSUED_DELEGATIONS_UNAVAILABLE` (exit 7). `delegation revoke <cid>` from a delegate profile revokes a delegation its own key issued with a revocation signed by that key, so it needs no revoke grant from the owner. Revoking one that's already revoked also succeeds. A delegation the key did not issue is refused with the node's message (`REVOKE_UNAUTHORIZED`, exit 5). A chain too long for the node to check right now answers `CHAIN_TOO_DEEP` (exit 7); retry later. Owner profiles behave as described above.
|
|
104
|
+
|
|
105
|
+
A grant the owner signed through `auth grant` can be revoked from any of the owner's profiles, including one that never recorded it (another CLI profile or machine). When this profile has no local record of the CID, the CLI reads the owner's delegation records on the node and uses that record's space only if the grant was issued by the signed-in owner (the issuer is the owner or the signed-in session key, or a key that holds a delegation recorded on the node whose own issuer is, in turn, one of those: any holder along such an owner-rooted chain counts, up to 8 hops) and every space it covers is one plain space id (`tinycloud:pkh:eip155:<chain>:<address>:<name>`) that belongs to that owner. The lookup reads at most 5,000 records (pages of 100; a larger page is refused) and refuses rather than decides on a partial list. A CID the node does not list, one issued by someone else, a space the owner does not hold, or an ambiguous record is refused (`RAW_RECAP_RESOURCE_UNSUPPORTED`, "this CLI profile has no record of this grant and the node's record does not match the signed-in owner"). An OpenKey owner uses the same space-scoped request as for a locally recorded grant; OpenKey profiles hold no wallet signer, so there is no wallet-signed revoke for them. After the revoke, the agent's next call is refused (`GRANT_REVOKED`, exit 5, the same as `tinycloud replica`: "This grant was revoked. Ask the owner for a new grant") as soon as the node has recorded it.
|
|
108
106
|
|
|
109
107
|
`tinycloud auth import` stores delegations in `additional-delegations.json`. Every replay path installs only a grant with a locally verified proof and a binding containing its signed authority; unbound and proof-less grants never install, including while migration is deferred. [AUTH.md](AUTH.md#imported-delegations-and-their-request-bindings) covers migration and recovery.
|
|
110
108
|
|
|
111
109
|
### Stored grants
|
|
112
110
|
|
|
113
111
|
```bash
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
tinycloud --profile PROFILE auth grants list --json
|
|
113
|
+
tinycloud --profile PROFILE auth grants remove <cid>
|
|
114
|
+
tinycloud --profile PROFILE auth grants remove <cid> --revoke --yes
|
|
117
115
|
```
|
|
118
116
|
|
|
119
117
|
`list` is local and does not contact the node. Its JSON contains `profile`, `session` (`cid` recomputed from stored header and `expiresAt`, or `null`), `localVerification` (`available`, `session_missing`, `session_expired`, `session_invalid`), and `grants`. Each indexed grant row contains `cid` (CID-shaped or `null`), `expiry`, `expired`, `local` (`verified`, `refused`, `not_checked`, `for_another_key`), `reason` (fixed skip code or `null`), `node: "not_checked"`, `bound`, `proof` (`siwe`, `compact`, `missing`, or `null`), `source`, `scope`, and `scopeSource` (`signed` or `stored`). `for_another_key` means the stored audience names another key; its scope is unverified stored metadata and it cannot install in this session. Local verification checks signed scope and binding, not the node's delegation chain or revocation status. Human output shows `CID | LOCAL | EXPIRES | SOURCE | BOUND | SCOPE` and the session CID.
|
|
@@ -133,25 +131,25 @@ A concurrent local removal after revocation reports `removed: 0` as success. Oth
|
|
|
133
131
|
## Spaces
|
|
134
132
|
|
|
135
133
|
```bash
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
134
|
+
tinycloud space list
|
|
135
|
+
tinycloud space create myspace
|
|
136
|
+
tinycloud space info # Current space
|
|
137
|
+
tinycloud space info <space-id> # Specific space
|
|
138
|
+
tinycloud space switch myspace
|
|
141
139
|
```
|
|
142
140
|
|
|
143
|
-
`space list` reads the owner's account registry (`account/spaces/`) when the session holds `tinycloud.kv/get` and `tinycloud.kv/list` on the account space's `spaces/` prefix, and falls back to `tinycloud.space/list` only when the session holds that. A session with neither (including the default OpenKey owner sign-in, which covers only the `default` space) fails with `PERMISSION_DENIED` (exit 5) naming both; an owner grants the registry read with `
|
|
141
|
+
`space list` reads the owner's account registry (`account/spaces/`) when the session holds `tinycloud.kv/get` and `tinycloud.kv/list` on the account space's `spaces/` prefix, and falls back to `tinycloud.space/list` only when the session holds that. A session with neither (including the default OpenKey owner sign-in, which covers only the `default` space) fails with `PERMISSION_DENIED` (exit 5) naming both; an owner grants the registry read with `tinycloud auth request --grant --cap tinycloud.kv:account:spaces/:get,list`.
|
|
144
142
|
|
|
145
143
|
## Profiles
|
|
146
144
|
|
|
147
145
|
Multiple identities/environments with isolated keys and sessions.
|
|
148
146
|
|
|
149
147
|
```bash
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
148
|
+
tinycloud profile list
|
|
149
|
+
tinycloud profile create staging --host https://staging.tinycloud.xyz
|
|
150
|
+
tinycloud profile show # Current profile
|
|
151
|
+
tinycloud profile switch staging # Change default
|
|
152
|
+
tinycloud profile delete old-profile
|
|
155
153
|
```
|
|
156
154
|
|
|
157
155
|
Per-command override: `tinycloud kv get mykey --profile staging`
|
|
@@ -168,33 +166,49 @@ network grant` takes the short name, resolves the network, and issues
|
|
|
168
166
|
separately from any KV or SQL reads the app also needs.
|
|
169
167
|
|
|
170
168
|
```bash
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
printf '%s' "$ANTHROPIC_API_KEY" |
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
169
|
+
tinycloud secrets network init
|
|
170
|
+
tinycloud secrets network show
|
|
171
|
+
tinycloud secrets network grant did:pkh:eip155:1:0xRecipient...
|
|
172
|
+
|
|
173
|
+
printf '%s' "$ANTHROPIC_API_KEY" | tinycloud secrets put ANTHROPIC_API_KEY --stdin # keeps the value out of argv and shell history
|
|
174
|
+
tinycloud secrets get ANTHROPIC_API_KEY
|
|
175
|
+
tinycloud secrets get ANTHROPIC_API_KEY -o key.txt # atomic owner-only (0600) replacement
|
|
176
|
+
tinycloud secrets list
|
|
177
|
+
tinycloud secrets delete ANTHROPIC_API_KEY
|
|
180
178
|
```
|
|
181
179
|
|
|
182
180
|
A missing secret makes `tinycloud 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 `tinycloud secrets put NAME --stdin`. `tinycloud secrets doctor NAME` hints `tinycloud secrets request NAME` for a missing secret, or `tinycloud secrets put NAME --stdin` for a local identity. `tinycloud secrets get --delegation` follows the same rule, and a delegated read the node refuses (403, a capability 401, `AUTH_UNAUTHORIZED`) exits 5.
|
|
183
181
|
|
|
182
|
+
### Running a command with secrets: `run`
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
tinycloud secrets run NAME... [--map NAME=ENV_VAR]... [--scope S] [--space S] [--delegation SOURCE] [--private-key HEX] -- <command> [args...]
|
|
186
|
+
tinycloud --profile agent secrets run OPENAI_API_KEY -- python train.py
|
|
187
|
+
tinycloud --profile agent secrets run --map ANTHROPIC_KEY=ANTHROPIC_API_KEY -- node server.js
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`tinycloud secrets run` reads every named secret as `tinycloud secrets get` does, then starts `<command>` directly, without a shell, with each value in its environment: under the secret's name, or under `ENV_VAR` for `--map NAME=ENV_VAR`. A value replaces an inherited variable of the same name. The CLI doesn't pass its own `TC_PRIVATE_KEY` to the command. To give the command a key from a secret, use `--map NAME=TC_PRIVATE_KEY`. Everything after the first `--`, including any further `--`, goes to the command unchanged. The command uses the CLI's stdin, stdout and stderr; the CLI itself writes nothing to stdout, and its own messages go to stderr.
|
|
191
|
+
|
|
192
|
+
- Name at least one secret, as an argument or with `--map`. `ENV_VAR` must match `[A-Za-z_][A-Za-z0-9_]*`; two secrets mapped to the same variable, no `--`, or nothing after it exit 2 (`USAGE_ERROR`) before any read.
|
|
193
|
+
- If any secret cannot be read, the command does not start and the CLI exits with the code and error `tinycloud secrets get` gives for that secret. A value containing a NUL character exits 1 (`SECRET_NOT_EXPORTABLE`) without starting the command.
|
|
194
|
+
- Otherwise the CLI exits with the command's exit code. If the command was ended by SIGINT, SIGTERM, SIGHUP, SIGKILL, SIGALRM or SIGUSR2, the CLI ends by the same signal (or exits with 128 plus the signal number if that signal doesn't end it). If any other signal ended it, such as SIGQUIT or SIGSEGV, the CLI exits with 128 plus the signal number instead, so it does not cause a core dump (SIGUSR1 and SIGPIPE are left out too: Node uses SIGUSR1 for its inspector and ignores SIGPIPE).
|
|
195
|
+
- SIGINT, SIGTERM, SIGHUP and SIGQUIT sent to the CLI go to the command. When the CLI runs in the foreground of a terminal, it doesn't pass on SIGINT, SIGQUIT or SIGHUP: the terminal already sends them (Ctrl-C, Ctrl-\, hangup) to the command, so the command normally gets each one once. The CLI checks whether it's in the foreground using `/proc`, or `ps` where `/proc` isn't available. If neither works, it passes the signals on. When the CLI runs in the background, it passes them on, so a signal sent to the whole job (for example `kill -INT %1`) can reach the command twice.
|
|
196
|
+
- From a script, stop the command by sending SIGTERM to the CLI's process ID. The CLI passes it on, waits for the command to exit, then exits the way the command did (the command's own exit code if it handles SIGTERM itself). If the command ignores SIGTERM, the CLI keeps waiting.
|
|
197
|
+
- A command that does not exist exits 127; one that cannot be executed, or cannot be started for another reason, exits 126. These cases print one plain stderr line even with `--json`: `tinycloud secrets run: <command>: command not found`, `…: permission denied` or `…: cannot run (<error code>)`.
|
|
198
|
+
|
|
199
|
+
Limits: `tinycloud secrets run` keeps values out of the command line, your shell history and the CLI's own output. It doesn't hide them from the command: the command, and anything it starts, receives the values and can print, log or pass them on. Other programs running as the same user can often read a process's environment too.
|
|
200
|
+
|
|
184
201
|
### Asking the owner for a secret: `request`, `wait`, `manage`
|
|
185
202
|
|
|
186
203
|
`/usr/sbin/tc` (iproute2 traffic control) can shadow the older `tc` name on `PATH`, so scripts call the CLI by absolute path:
|
|
187
204
|
|
|
188
205
|
```bash
|
|
189
|
-
|
|
190
|
-
[ -n "${TC_BIN:-}" ] || [ -x "$TC" ] || ! command -v npm >/dev/null || TC="$(npm prefix --global)/bin/tinycloud" # else a global npm install
|
|
191
|
-
[ -x "$TC" ] || { echo "TC_NOT_FOUND: $TC is not executable; install the CLI from https://agents.tinycloud.xyz/cli or set TC_BIN" >&2; exit 1; }
|
|
192
|
-
"$TC" --profile agent secrets request OPENAI_API_KEY --requester "agent@host" --json
|
|
206
|
+
tinycloud --profile agent secrets request OPENAI_API_KEY --requester "agent@host" --json
|
|
193
207
|
# {"url":"https://secrets.tinycloud.xyz/request?owner=…&name=OPENAI_API_KEY&requester=…&v=1","names":["OPENAI_API_KEY"],"owner":"did:pkh:eip155:1:0x…"}
|
|
194
208
|
# send the url to the owner once, then:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
209
|
+
tinycloud --profile agent secrets wait OPENAI_API_KEY --timeout 9m --json
|
|
210
|
+
tinycloud --profile agent secrets get OPENAI_API_KEY --raw
|
|
211
|
+
tinycloud secrets manage OPENAI_API_KEY --no-open --json # {"url":"…","opened":false}: the replace/manage page
|
|
198
212
|
```
|
|
199
213
|
|
|
200
214
|
- **`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`).
|
|
@@ -234,11 +248,11 @@ An agent profile gets read access to named secrets through a scoped OpenKey past
|
|
|
234
248
|
|
|
235
249
|
```bash
|
|
236
250
|
echo '{ "app_id": "my.agent", "space": "secrets", "secrets": { "OPENAI_API_KEY": true } }' > agent.json
|
|
237
|
-
|
|
238
|
-
|
|
251
|
+
tinycloud init --name agent --key-only
|
|
252
|
+
tinycloud --profile agent auth login --method openkey --paste --manifest agent.json --owner did:pkh:eip155:1:0xOWNER < /dev/null
|
|
239
253
|
# exits 3 (PASTE_CODE_MISSING) and names the approval URL; the owner approves and returns a code
|
|
240
|
-
printf '%s\n' "$CODE" |
|
|
241
|
-
|
|
254
|
+
printf '%s\n' "$CODE" | tinycloud --profile agent auth login --method openkey --paste --manifest agent.json --owner did:pkh:eip155:1:0xOWNER
|
|
255
|
+
tinycloud --profile agent secrets get OPENAI_API_KEY --raw
|
|
242
256
|
```
|
|
243
257
|
|
|
244
258
|
`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.
|
|
@@ -247,23 +261,23 @@ When neither stdin nor stderr is a terminal, `secrets get|list|put|delete` on an
|
|
|
247
261
|
|
|
248
262
|
## Local Replicas
|
|
249
263
|
|
|
250
|
-
`tinycloud replica` keeps a durable, read-only copy of one KV prefix on this device. `sync` pulls the node's `tinycloud.kv/sync` change feed (node feature `kv-sync-v1`) and fetches each changed value, accepting bytes only if they hash to the ETag the node attested. `get`, `list`, `status` and `reset` never touch the network, so they work with the host offline. Reads report observed state from one source host, not global finality. Needs Node.js 22.
|
|
264
|
+
`tinycloud replica` keeps a durable, read-only copy of one KV prefix on this device. `sync` pulls the node's `tinycloud.kv/sync` change feed (node feature `kv-sync-v1`) and fetches each changed value, accepting bytes only if they hash to the ETag the node attested. `get`, `list`, `status` and `reset` never touch the network, so they work with the host offline. Reads report observed state from one source host, not global finality. Needs Node.js 22.20 or later, or Bun (built-in SQLite); on older Node every `tinycloud replica` command fails with `RUNTIME_UNSUPPORTED` (exit 1) and other commands are unaffected.
|
|
251
265
|
|
|
252
266
|
A replica needs a device grant carrying `get` and `sync` on the prefix. Neither `*` nor `tinycloud.kv/*` implies `sync`, and the owner's default session does not hold it; `tinycloud auth grant` acquires it for that grant only:
|
|
253
267
|
|
|
254
268
|
```bash
|
|
255
269
|
# device
|
|
256
|
-
|
|
270
|
+
tinycloud --profile device auth request --cap tinycloud.kv:SPACE:notes/:get,list,metadata,sync --expiry 30d --emit req.json
|
|
257
271
|
# owner
|
|
258
|
-
|
|
272
|
+
tinycloud --profile owner auth grant req.json > grant.json
|
|
259
273
|
# device
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
274
|
+
tinycloud --profile device auth import grant.json
|
|
275
|
+
tinycloud --profile device replica sync --prefix notes/ # first sync creates the replica "notes"
|
|
276
|
+
tinycloud --profile device replica get notes/todo.md --raw # offline
|
|
277
|
+
tinycloud --profile device replica list notes/ --json
|
|
278
|
+
tinycloud --profile device replica status
|
|
279
|
+
tinycloud --profile device replica sync # catch up: updates and deletes
|
|
280
|
+
tinycloud --profile device replica reset [--purge]
|
|
267
281
|
```
|
|
268
282
|
|
|
269
283
|
- The first `sync` needs `--prefix` (and `--space` when grants cover several spaces); it pins the source host (`--host` or the profile host, never a discovered local node). Later runs use the stored configuration; a different `--prefix`, `--space` or `--host` is `REPLICA_CONFIG_MISMATCH` (exit 2). `--replica <name>` selects or names a replica (default: named after the prefix, or the profile's only replica).
|
|
@@ -327,8 +341,8 @@ session, logout signs that session out and removes only the profile's legacy
|
|
|
327
341
|
`replicas/` tree; the other account's gate and flag-owned replicas stay. An
|
|
328
342
|
interrupted earlier sign-out is reported, and a second `auth logout` completes
|
|
329
343
|
it. A gate that no longer matches its logout record (for example, restored
|
|
330
|
-
from an older copy) refuses logout; run `
|
|
331
|
-
`
|
|
344
|
+
from an older copy) refuses logout; run `tinycloud profile delete <name>` (after
|
|
345
|
+
`tinycloud profile switch <other>` if it is the default) and sign in again.
|
|
332
346
|
|
|
333
347
|
`--keep-replicas` retains legacy files only when no replica gate, flag-owned
|
|
334
348
|
partition or pending purge exists; gated profiles refuse before mutation and
|
|
@@ -343,49 +357,49 @@ replayed and the covered read is served locally. If explicit `kv/get` plus
|
|
|
343
357
|
## Node Health
|
|
344
358
|
|
|
345
359
|
```bash
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
360
|
+
tinycloud node health # Ping + latency
|
|
361
|
+
tinycloud node version # Server version
|
|
362
|
+
tinycloud node status # Combined
|
|
349
363
|
```
|
|
350
364
|
|
|
351
365
|
## Shell Completions
|
|
352
366
|
|
|
353
367
|
```bash
|
|
354
|
-
eval "$(
|
|
355
|
-
eval "$(
|
|
356
|
-
|
|
368
|
+
eval "$(tinycloud completion bash)"
|
|
369
|
+
eval "$(tinycloud completion zsh)"
|
|
370
|
+
tinycloud completion fish | source
|
|
357
371
|
```
|
|
358
372
|
|
|
359
373
|
## Share Publish, Inspect, Receive, List, Revoke
|
|
360
374
|
|
|
361
|
-
Publishing needs the publishing scope on a dedicated profile
|
|
375
|
+
Publishing needs the publishing scope in the owner's `default` space, on a dedicated profile: `tinycloud init --name publisher --key-only`, then `tinycloud --profile publisher auth login --method openkey --paste --manifest builtin:share-publishing --expiry 30d`. Publish with `tinycloud --profile publisher share publish ...`.
|
|
362
376
|
|
|
363
377
|
```bash
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
cat note.md |
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
printf '%s' "$SHARE_URL" |
|
|
373
|
-
printf '%s' "$SHARE_URL" |
|
|
374
|
-
printf '%s' "$SHARE_URL" |
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
378
|
+
tinycloud share publish ./note.md --json # bearer link; JSON has no URL: use share show <id> --reveal-link
|
|
379
|
+
tinycloud share publish ./note.md --to email:alice@example.com --notify # addressed: exact email, emailed invitation (read required)
|
|
380
|
+
tinycloud share publish ./note.md --to did:pkh:eip155:1:0xRecipient... # addressed: one DID
|
|
381
|
+
tinycloud share publish ./note.md --to domain:example.com # addressed: any verified address at the domain
|
|
382
|
+
tinycloud share publish ./note.md --to domain:example.com --notify --notify-to alice@example.com # invite one matching mailbox; node 1.17.3+
|
|
383
|
+
cat note.md | tinycloud share publish - --name note.md
|
|
384
|
+
tinycloud share publish ./site/ --json # folder with index.html -> one self-contained page, bearer link
|
|
385
|
+
|
|
386
|
+
printf '%s' "$SHARE_URL" | tinycloud share inspect - --json # verify, print safe metadata
|
|
387
|
+
printf '%s' "$SHARE_URL" | tinycloud share receive - --stdout # bearer link: verified bytes
|
|
388
|
+
printf '%s' "$SHARE_URL" | tinycloud share receive - --output .
|
|
389
|
+
|
|
390
|
+
tinycloud share list --json # sender history, no complete URLs; human rows are shareId, target, expiresAt, status (active, revoked or expired), tab-separated
|
|
391
|
+
tinycloud share show <id> [--reveal-link]
|
|
392
|
+
tinycloud share notify <id> --to alice@example.com # retry email delivery within 5 minutes of publication
|
|
393
|
+
tinycloud share revoke <id> # bearer or addressed share this profile created
|
|
394
|
+
tinycloud share history recover [--json] # move an unreadable sender history aside (renamed, never deleted)
|
|
381
395
|
```
|
|
382
396
|
|
|
383
397
|
**Artifact versions (pinned shares).** `--artifact <id|new>` stores the file as the next write-once version, `artifacts/<id>/versions/<n>/<file>` plus a `manifest.json` written last (file sha256 and size, parent, `--note`), in the owner's space. The share is pinned to that version: its delegation covers only that file and never carries `put`. Publishing the same bytes again to a bearer link returns the same version; changed content makes the next one. Publishing never overwrites an earlier version, but the node has no write-once ACL for the owner, so the owner (or anyone holding the owner's session) can overwrite a version file afterwards; the CLI will not, and the writes it makes use `If-None-Match: *`. Only addressed artifact shares detect such an overwrite (see Pin below); a bearer artifact share carries no digest and would deliver the new bytes. An addressed share stores ciphertext under a fresh random key each time, so every addressed publish is a new version, and `manifest.json` records only the sha256 of the stored ciphertext, never of the plaintext (an unsalted plaintext hash next to the ciphertext would let a reader of the space confirm a guess). `--artifact new` mints an id. JSON output adds `artifactId`, `version` and `mode` (`pinned`). `--action edit` on an artifact share is refused (a pinned version is read-only). An artifact share takes one file. Every artifact share pins one version; shares that follow later versions are not available yet. Profiles signed in before this feature need `auth login --device --manifest builtin:share-publishing` again, because the manifest now includes `artifacts/`.
|
|
384
398
|
|
|
385
399
|
```bash
|
|
386
|
-
|
|
387
|
-
cat report-v2.md |
|
|
388
|
-
|
|
400
|
+
tinycloud share publish ./report.md --artifact new --to email:alice@example.com --json # v1, pinned
|
|
401
|
+
cat report-v2.md | tinycloud share publish - --name report.md --artifact art_0123456789abcdef --to email:alice@example.com --note "second draft"
|
|
402
|
+
tinycloud share versions art_0123456789abcdef --json # versions and the shares (from local history) pinned to each
|
|
389
403
|
```
|
|
390
404
|
|
|
391
405
|
Versions are kept by default and count against the owner's storage. The node does not reclaim overwritten or deleted values; byte reclamation depends on history GC (TC-1277).
|
|
@@ -447,17 +461,17 @@ Style with the TinyCloud tokens so the artifact follows the viewer's light and d
|
|
|
447
461
|
|
|
448
462
|
```bash
|
|
449
463
|
# 1. Publish; the JSON carries the share id and the artifact id, never the link
|
|
450
|
-
|
|
464
|
+
tinycloud --profile publisher share publish ./report.md --to anyone --artifact new --json
|
|
451
465
|
# {"metadata":{"shareId":"…"},"artifactId":"art_…","version":1,"mode":"pinned", …}
|
|
452
466
|
|
|
453
467
|
# 2. Get the link: a second call. Write it straight to a private file
|
|
454
|
-
|
|
468
|
+
tinycloud --profile publisher share show <shareId> --reveal-link
|
|
455
469
|
|
|
456
470
|
# 3. Revise: publish the next pinned version; it gets its own link, and earlier links keep showing the version they pinned
|
|
457
|
-
|
|
471
|
+
tinycloud --profile publisher share publish ./report-v2.md --to anyone --artifact art_0123456789abcdef --json
|
|
458
472
|
|
|
459
473
|
# 4. Take a link down; the node confirms before it reports revoked
|
|
460
|
-
|
|
474
|
+
tinycloud --profile publisher share revoke <shareId>
|
|
461
475
|
```
|
|
462
476
|
|
|
463
477
|
- Default to `--to anyone` for artifacts unless the person you are working for named a narrower audience. Choose `--expires` deliberately (the default is `7d`). Anyone holding the complete URL can open it until it expires or is revoked, and revoking does not recall copies already received. For content that only named people may read, use `--to email:` and tell the recipient it will download rather than render.
|
|
@@ -528,6 +542,8 @@ Use `--json` for machine-readable output; interactive commands can render human
|
|
|
528
542
|
|
|
529
543
|
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 (`tinycloud secrets wait` only), 10 storage full, 130 interrupted (`CANCELLED`). `tinycloud share` uses its own: 1 profile lock timeout, sender-history retry or unconfirmed revocation (`PROFILE_LOCK_TIMEOUT`, `SHARE_HISTORY_RETRY`, `SHARE_REVOKE_UNCONFIRMED`), 3 upload authority required, 4 unavailable, expired, revoked bearer link, upload failed or revocation refused (`SHARE_LINK_UNAVAILABLE`, `SHARE_REVOKED`, `SHARE_REVOKE_REJECTED`), 5 permission denied (`PERMISSION_DENIED`, `SHARE_ISSUER_KEY_UNAVAILABLE`) 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.
|
|
530
544
|
|
|
545
|
+
`tinycloud secrets run` exits with its command's status once the command has started. Before that it uses the codes above, plus 126 (the command cannot be executed or started) and 127 (the command does not exist).
|
|
546
|
+
|
|
531
547
|
For `tinycloud kv get|put|head|list|delete`, `tinycloud space list|host`, `tinycloud sql`, `tinycloud vault`, `tinycloud vars`, `tinycloud duckdb`, `tinycloud secrets list|put|delete`, `tinycloud delegation`, and `tinycloud 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 `tinycloud 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. `tinycloud share` has separate exit mappings below.
|
|
532
548
|
|
|
533
549
|
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 (`tinycloud 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/storage. Retrying cannot succeed until the owner frees up space or upgrades.
|
|
@@ -50,7 +50,7 @@ const { delegation } = await tc.delegateTo("did:pkh:eip155:1:0x...", [
|
|
|
50
50
|
{ service: "tinycloud.kv", space: "default", path: "kv/shared", actions: ["tinycloud.kv/get"] },
|
|
51
51
|
], { expiry: "1h" });
|
|
52
52
|
|
|
53
|
-
// Account delegation query, POST /delegation/query (what `
|
|
53
|
+
// Account delegation query, POST /delegation/query (what `tinycloud delegation list` uses). It includes
|
|
54
54
|
// delegations signed by the session key; account.delegations.list() reads the SQL index and does not.
|
|
55
55
|
await tc.account.delegations.query({ direction: "all" });
|
|
56
56
|
await tc.delegationManager.revoke(cid);
|