@tinycloud/cli 1.5.0-beta.9 → 1.5.1-beta.0
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 +479 -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 +1877 -101
- package/dist/index.js.map +1 -1
- package/dist/legacy-entry.js +6279 -2752
- 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 +23 -27
- package/skills/tinycloud-cli/INSTALL.md +36 -0
- package/skills/{tc-cli → tinycloud-cli}/REFERENCE.md +117 -97
- package/skills/{tc-cli → tinycloud-cli}/SDK.md +1 -1
- package/skills/{tc-cli → tinycloud-cli}/SKILL.md +34 -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
|
+
- 150 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,17 @@ 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
|
-
|
|
60
|
+
`auth login --method openkey` on a profile that does not exist creates a key-only profile first (as `init --key-only` does), then continues into the login; nothing is granted until the owner approves. A `--manifest` or `--device` login still needs the profile to exist (use `tinycloud init --key-only --name X` first). `auth logout` on a missing profile still fails with `PROFILE_NOT_FOUND` and creates nothing.
|
|
61
|
+
|
|
62
|
+
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
63
|
|
|
66
64
|
`builtin:share-publishing` requests, in the owner's `default` space, exactly:
|
|
67
65
|
|
|
@@ -83,17 +81,17 @@ No `del` anywhere and no `list` on the bearer prefix: no `tinycloud share` comma
|
|
|
83
81
|
## Delegations
|
|
84
82
|
|
|
85
83
|
```bash
|
|
86
|
-
|
|
84
|
+
tinycloud delegation create \
|
|
87
85
|
--to did:pkh:eip155:1:0xRecipient... \
|
|
88
86
|
--path kv/shared \
|
|
89
87
|
--actions kv/get,kv/put \
|
|
90
88
|
--expiry 24h
|
|
91
89
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
90
|
+
tinycloud delegation list # All
|
|
91
|
+
tinycloud delegation list --granted # Granted by me
|
|
92
|
+
tinycloud delegation list --received # Received by me
|
|
93
|
+
tinycloud delegation info <cid>
|
|
94
|
+
tinycloud delegation revoke <cid>
|
|
97
95
|
```
|
|
98
96
|
|
|
99
97
|
Actions are auto-prefixed with `tinycloud.` if not already.
|
|
@@ -104,16 +102,18 @@ warning that revocation signs you out. After the node confirms that exact CID,
|
|
|
104
102
|
the CLI runs its normal durable logout, removes the restorable session and key,
|
|
105
103
|
and blocks implicit reauthentication. Unrelated CIDs do not sign you out.
|
|
106
104
|
|
|
107
|
-
|
|
105
|
+
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.
|
|
106
|
+
|
|
107
|
+
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
108
|
|
|
109
109
|
`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
110
|
|
|
111
111
|
### Stored grants
|
|
112
112
|
|
|
113
113
|
```bash
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
tinycloud --profile PROFILE auth grants list --json
|
|
115
|
+
tinycloud --profile PROFILE auth grants remove <cid>
|
|
116
|
+
tinycloud --profile PROFILE auth grants remove <cid> --revoke --yes
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
`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 +133,25 @@ A concurrent local removal after revocation reports `removed: 0` as success. Oth
|
|
|
133
133
|
## Spaces
|
|
134
134
|
|
|
135
135
|
```bash
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
136
|
+
tinycloud space list
|
|
137
|
+
tinycloud space create myspace
|
|
138
|
+
tinycloud space info # Current space
|
|
139
|
+
tinycloud space info <space-id> # Specific space
|
|
140
|
+
tinycloud space switch myspace
|
|
141
141
|
```
|
|
142
142
|
|
|
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 `
|
|
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 `tinycloud auth request --grant --cap tinycloud.kv:account:spaces/:get,list`.
|
|
144
144
|
|
|
145
145
|
## Profiles
|
|
146
146
|
|
|
147
147
|
Multiple identities/environments with isolated keys and sessions.
|
|
148
148
|
|
|
149
149
|
```bash
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
150
|
+
tinycloud profile list
|
|
151
|
+
tinycloud profile create staging --host https://staging.tinycloud.xyz
|
|
152
|
+
tinycloud profile show # Current profile
|
|
153
|
+
tinycloud profile switch staging # Change default
|
|
154
|
+
tinycloud profile delete old-profile
|
|
155
155
|
```
|
|
156
156
|
|
|
157
157
|
Per-command override: `tinycloud kv get mykey --profile staging`
|
|
@@ -168,33 +168,49 @@ network grant` takes the short name, resolves the network, and issues
|
|
|
168
168
|
separately from any KV or SQL reads the app also needs.
|
|
169
169
|
|
|
170
170
|
```bash
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
printf '%s' "$ANTHROPIC_API_KEY" |
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
171
|
+
tinycloud secrets network init
|
|
172
|
+
tinycloud secrets network show
|
|
173
|
+
tinycloud secrets network grant did:pkh:eip155:1:0xRecipient...
|
|
174
|
+
|
|
175
|
+
printf '%s' "$ANTHROPIC_API_KEY" | tinycloud secrets put ANTHROPIC_API_KEY --stdin # keeps the value out of argv and shell history
|
|
176
|
+
tinycloud secrets get ANTHROPIC_API_KEY
|
|
177
|
+
tinycloud secrets get ANTHROPIC_API_KEY -o key.txt # atomic owner-only (0600) replacement
|
|
178
|
+
tinycloud secrets list
|
|
179
|
+
tinycloud secrets delete ANTHROPIC_API_KEY
|
|
180
180
|
```
|
|
181
181
|
|
|
182
182
|
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
183
|
|
|
184
|
+
### Running a command with secrets: `run`
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
tinycloud secrets run NAME... [--map NAME=ENV_VAR]... [--scope S] [--space S] [--delegation SOURCE] [--private-key HEX] -- <command> [args...]
|
|
188
|
+
tinycloud --profile agent secrets run OPENAI_API_KEY -- python train.py
|
|
189
|
+
tinycloud --profile agent secrets run --map ANTHROPIC_KEY=ANTHROPIC_API_KEY -- node server.js
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`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.
|
|
193
|
+
|
|
194
|
+
- 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.
|
|
195
|
+
- 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.
|
|
196
|
+
- 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).
|
|
197
|
+
- 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.
|
|
198
|
+
- 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.
|
|
199
|
+
- 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>)`.
|
|
200
|
+
|
|
201
|
+
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.
|
|
202
|
+
|
|
184
203
|
### Asking the owner for a secret: `request`, `wait`, `manage`
|
|
185
204
|
|
|
186
205
|
`/usr/sbin/tc` (iproute2 traffic control) can shadow the older `tc` name on `PATH`, so scripts call the CLI by absolute path:
|
|
187
206
|
|
|
188
207
|
```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
|
|
208
|
+
tinycloud --profile agent secrets request OPENAI_API_KEY --requester "agent@host" --json
|
|
193
209
|
# {"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
210
|
# send the url to the owner once, then:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
211
|
+
tinycloud --profile agent secrets wait OPENAI_API_KEY --timeout 9m --json
|
|
212
|
+
tinycloud --profile agent secrets get OPENAI_API_KEY --raw
|
|
213
|
+
tinycloud secrets manage OPENAI_API_KEY --no-open --json # {"url":"…","opened":false}: the replace/manage page
|
|
198
214
|
```
|
|
199
215
|
|
|
200
216
|
- **`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 +250,11 @@ An agent profile gets read access to named secrets through a scoped OpenKey past
|
|
|
234
250
|
|
|
235
251
|
```bash
|
|
236
252
|
echo '{ "app_id": "my.agent", "space": "secrets", "secrets": { "OPENAI_API_KEY": true } }' > agent.json
|
|
237
|
-
|
|
238
|
-
|
|
253
|
+
tinycloud init --name agent --key-only
|
|
254
|
+
tinycloud --profile agent auth login --method openkey --paste --manifest agent.json --owner did:pkh:eip155:1:0xOWNER < /dev/null
|
|
239
255
|
# exits 3 (PASTE_CODE_MISSING) and names the approval URL; the owner approves and returns a code
|
|
240
|
-
printf '%s\n' "$CODE" |
|
|
241
|
-
|
|
256
|
+
printf '%s\n' "$CODE" | tinycloud --profile agent auth login --method openkey --paste --manifest agent.json --owner did:pkh:eip155:1:0xOWNER
|
|
257
|
+
tinycloud --profile agent secrets get OPENAI_API_KEY --raw
|
|
242
258
|
```
|
|
243
259
|
|
|
244
260
|
`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 +263,23 @@ When neither stdin nor stderr is a terminal, `secrets get|list|put|delete` on an
|
|
|
247
263
|
|
|
248
264
|
## Local Replicas
|
|
249
265
|
|
|
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.
|
|
266
|
+
`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
267
|
|
|
252
268
|
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
269
|
|
|
254
270
|
```bash
|
|
255
271
|
# device
|
|
256
|
-
|
|
272
|
+
tinycloud --profile device auth request --cap tinycloud.kv:SPACE:notes/:get,list,metadata,sync --expiry 30d --emit req.json
|
|
257
273
|
# owner
|
|
258
|
-
|
|
274
|
+
tinycloud --profile owner auth grant req.json > grant.json
|
|
259
275
|
# device
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
276
|
+
tinycloud --profile device auth import grant.json
|
|
277
|
+
tinycloud --profile device replica sync --prefix notes/ # first sync creates the replica "notes"
|
|
278
|
+
tinycloud --profile device replica get notes/todo.md --raw # offline
|
|
279
|
+
tinycloud --profile device replica list notes/ --json
|
|
280
|
+
tinycloud --profile device replica status
|
|
281
|
+
tinycloud --profile device replica sync # catch up: updates and deletes
|
|
282
|
+
tinycloud --profile device replica reset [--purge]
|
|
267
283
|
```
|
|
268
284
|
|
|
269
285
|
- 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 +343,8 @@ session, logout signs that session out and removes only the profile's legacy
|
|
|
327
343
|
`replicas/` tree; the other account's gate and flag-owned replicas stay. An
|
|
328
344
|
interrupted earlier sign-out is reported, and a second `auth logout` completes
|
|
329
345
|
it. A gate that no longer matches its logout record (for example, restored
|
|
330
|
-
from an older copy) refuses logout; run `
|
|
331
|
-
`
|
|
346
|
+
from an older copy) refuses logout; run `tinycloud profile delete <name>` (after
|
|
347
|
+
`tinycloud profile switch <other>` if it is the default) and sign in again.
|
|
332
348
|
|
|
333
349
|
`--keep-replicas` retains legacy files only when no replica gate, flag-owned
|
|
334
350
|
partition or pending purge exists; gated profiles refuse before mutation and
|
|
@@ -343,49 +359,51 @@ replayed and the covered read is served locally. If explicit `kv/get` plus
|
|
|
343
359
|
## Node Health
|
|
344
360
|
|
|
345
361
|
```bash
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
362
|
+
tinycloud node health # Ping + latency
|
|
363
|
+
tinycloud node version # Server version
|
|
364
|
+
tinycloud node status # Combined
|
|
349
365
|
```
|
|
350
366
|
|
|
351
367
|
## Shell Completions
|
|
352
368
|
|
|
353
369
|
```bash
|
|
354
|
-
eval "$(
|
|
355
|
-
eval "$(
|
|
356
|
-
|
|
370
|
+
eval "$(tinycloud completion bash)"
|
|
371
|
+
eval "$(tinycloud completion zsh)"
|
|
372
|
+
tinycloud completion fish | source
|
|
357
373
|
```
|
|
358
374
|
|
|
359
375
|
## Share Publish, Inspect, Receive, List, Revoke
|
|
360
376
|
|
|
361
|
-
Publishing needs the publishing scope on a dedicated profile
|
|
377
|
+
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 ...`.
|
|
378
|
+
|
|
379
|
+
Sharing to an email address, domain or DID isn't available in this release; link shares are.
|
|
362
380
|
|
|
363
381
|
```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
|
-
|
|
382
|
+
tinycloud share publish ./note.md --json # bearer link; JSON has no URL: use share show <id> --reveal-link
|
|
383
|
+
tinycloud share publish ./note.md --to email:alice@example.com --notify # addressed: exact email, emailed invitation (read required)
|
|
384
|
+
tinycloud share publish ./note.md --to did:pkh:eip155:1:0xRecipient... # addressed: one DID
|
|
385
|
+
tinycloud share publish ./note.md --to domain:example.com # addressed: any verified address at the domain
|
|
386
|
+
tinycloud share publish ./note.md --to domain:example.com --notify --notify-to alice@example.com # invite one matching mailbox; node 1.17.3+
|
|
387
|
+
cat note.md | tinycloud share publish - --name note.md
|
|
388
|
+
tinycloud share publish ./site/ --json # folder with index.html -> one self-contained page, bearer link
|
|
389
|
+
|
|
390
|
+
printf '%s' "$SHARE_URL" | tinycloud share inspect - --json # verify, print safe metadata
|
|
391
|
+
printf '%s' "$SHARE_URL" | tinycloud share receive - --stdout # bearer link: verified bytes
|
|
392
|
+
printf '%s' "$SHARE_URL" | tinycloud share receive - --output .
|
|
393
|
+
|
|
394
|
+
tinycloud share list --json # sender history, no complete URLs; human rows are shareId, target, expiresAt, status (active, revoked or expired), tab-separated
|
|
395
|
+
tinycloud share show <id> [--reveal-link]
|
|
396
|
+
tinycloud share notify <id> --to alice@example.com # retry email delivery within 5 minutes of publication
|
|
397
|
+
tinycloud share revoke <id> # bearer or addressed share this profile created
|
|
398
|
+
tinycloud share history recover [--json] # move an unreadable sender history aside (renamed, never deleted)
|
|
381
399
|
```
|
|
382
400
|
|
|
383
401
|
**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
402
|
|
|
385
403
|
```bash
|
|
386
|
-
|
|
387
|
-
cat report-v2.md |
|
|
388
|
-
|
|
404
|
+
tinycloud share publish ./report.md --artifact new --to email:alice@example.com --json # v1, pinned
|
|
405
|
+
cat report-v2.md | tinycloud share publish - --name report.md --artifact art_0123456789abcdef --to email:alice@example.com --note "second draft"
|
|
406
|
+
tinycloud share versions art_0123456789abcdef --json # versions and the shares (from local history) pinned to each
|
|
389
407
|
```
|
|
390
408
|
|
|
391
409
|
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 +465,17 @@ Style with the TinyCloud tokens so the artifact follows the viewer's light and d
|
|
|
447
465
|
|
|
448
466
|
```bash
|
|
449
467
|
# 1. Publish; the JSON carries the share id and the artifact id, never the link
|
|
450
|
-
|
|
468
|
+
tinycloud --profile publisher share publish ./report.md --to anyone --artifact new --json
|
|
451
469
|
# {"metadata":{"shareId":"…"},"artifactId":"art_…","version":1,"mode":"pinned", …}
|
|
452
470
|
|
|
453
471
|
# 2. Get the link: a second call. Write it straight to a private file
|
|
454
|
-
|
|
472
|
+
tinycloud --profile publisher share show <shareId> --reveal-link
|
|
455
473
|
|
|
456
474
|
# 3. Revise: publish the next pinned version; it gets its own link, and earlier links keep showing the version they pinned
|
|
457
|
-
|
|
475
|
+
tinycloud --profile publisher share publish ./report-v2.md --to anyone --artifact art_0123456789abcdef --json
|
|
458
476
|
|
|
459
477
|
# 4. Take a link down; the node confirms before it reports revoked
|
|
460
|
-
|
|
478
|
+
tinycloud --profile publisher share revoke <shareId>
|
|
461
479
|
```
|
|
462
480
|
|
|
463
481
|
- 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 +546,8 @@ Use `--json` for machine-readable output; interactive commands can render human
|
|
|
528
546
|
|
|
529
547
|
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
548
|
|
|
549
|
+
`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).
|
|
550
|
+
|
|
531
551
|
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
552
|
|
|
533
553
|
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);
|