@tinycloud/cli 1.5.0-beta.9 → 1.5.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 CHANGED
@@ -1,51 +1,50 @@
1
1
  # @tinycloud/cli
2
2
 
3
- Self-sovereign storage from the terminal. `tc` is the command-line interface for [TinyCloud](https://tinycloud.xyz) — manage data, delegations, spaces, and nodes without leaving your shell.
3
+ Self-sovereign storage from the terminal. `tinycloud` is the command-line interface for [TinyCloud](https://tinycloud.xyz) — manage data, delegations, spaces, and nodes without leaving your shell.
4
4
 
5
5
  ## Install
6
6
 
7
7
  ```bash
8
- npm install -g @tinycloud/cli
8
+ npm install -g @tinycloud/cli@1.5.0-beta.20
9
9
  ```
10
10
 
11
- Requires Node.js >= 20.
11
+ Requires Node.js >= 20; TinyCloud's agent guides use Node.js 22.20 or later.
12
+ Agents: start at https://agents.tinycloud.xyz/onboarding.
12
13
 
13
- The package installs two identical commands: `tc` and `tinycloud`. On Linux,
14
- iproute2 installs `/usr/sbin/tc` (traffic control), which can shadow `tc` on
15
- PATH; `tinycloud` avoids that clash and accepts the same arguments. If the npm global prefix is root-owned, run
16
- `npm config set prefix ~/.local` before installing: Ubuntu's default PATH puts
17
- `~/.local/bin` before `/usr/sbin`, but `~/.profile` adds it only if the
18
- directory existed at login, so start a new login shell after the first
19
- install. Run `hash -r` if `tc` already ran in the same shell. Verify with
20
- `command -v tc`, or call `"$(npm prefix --global)/bin/tc"` when in doubt.
14
+ The package exposes `tinycloud` as its preferred command and keeps `tc` as a
15
+ deprecated compatibility alias at the same entry. Both names accept the same
16
+ commands and flags, with identical exit codes and stdout. A POSIX `tc` executable
17
+ link emits one deprecation notice on stderr, never stdout. On Linux,
18
+ `/usr/sbin/tc` is iproute2's traffic-control tool. Verify the pinned install
19
+ with `tinycloud --version`.
21
20
 
22
21
  ## Agent skills
23
22
 
24
- The package ships the general `tc-cli` skill and its references in
25
- `skills/tc-cli`, and the `courier` skill for `tc courier` in `skills/courier`.
26
- See [skill installation](skills/tc-cli/INSTALL.md) for a
27
- pinned, cross-agent installation with OpenCode, Codex and Claude Code, and
28
- [authentication](skills/tc-cli/AUTH.md) for device login and relaying the
23
+ The package ships the general `tinycloud-cli` skill and its references in
24
+ `skills/tinycloud-cli`, and the `courier` skill for `tinycloud courier` in `skills/courier`.
25
+ See [skill installation](skills/tinycloud-cli/INSTALL.md) for a
26
+ pinned, cross-agent installation for any agent, and
27
+ [authentication](skills/tinycloud-cli/AUTH.md) for device login and relaying the
29
28
  approval code to the owner.
30
29
 
31
30
  ## Courier
32
31
 
33
- `tc courier …` runs [Courier](https://github.com/TinyCloudLabs/js-sdk/tree/master/packages/courier#readme), agent-to-agent threads
32
+ `tinycloud courier …` runs [Courier](https://github.com/TinyCloudLabs/js-sdk/tree/master/packages/courier#readme), agent-to-agent threads
34
33
  over TinyCloud, bundled from `@tinycloud/courier` (beta). It takes the same
35
34
  arguments as the standalone `courier` bin and keeps its own agent identity in
36
- `$COURIER_HOME` (default `~/.courier`), not in a tc profile, so put every flag
35
+ `$COURIER_HOME` (default `~/.courier`), not in a tinycloud profile, so put every flag
37
36
  after `courier`:
38
37
 
39
38
  ```bash
40
- tc courier init --paste --handle alice # the owner approves in OpenKey; paste the XXXX-XXXX code
41
- tc courier inbox
42
- tc courier --help
39
+ tinycloud courier init --paste --handle alice # the owner approves in OpenKey; paste the XXXX-XXXX code
40
+ tinycloud courier inbox
41
+ tinycloud courier --help
43
42
  ```
44
43
 
45
44
  ## Operations coverage
46
45
 
47
- The migrated `tc secrets get` path is owned by `@tinycloud/operations` and
48
- uses delegated authority by default. `tc auth import` is partially migrated:
46
+ The migrated `tinycloud secrets get` path is owned by `@tinycloud/operations` and
47
+ uses delegated authority by default. `tinycloud auth import` is partially migrated:
49
48
  the request-bound active-session v1 path is canonical, while legacy delegation
50
49
  and permission input forms remain Commander-owned. The complete registration
51
50
  ledger is checked in `../operations/coverage.json` and checked against the
@@ -61,104 +60,471 @@ fallback for delegated execution.
61
60
 
62
61
  ```bash
63
62
  # Set up a profile and generate keys
64
- tc init
63
+ tinycloud init
65
64
 
66
65
  # Authenticate via browser
67
- tc auth login
66
+ tinycloud auth login
68
67
 
69
68
  # Authenticate on a remote/headless machine: the owner approves an explicit
70
69
  # manifest on their phone (URL + code printed to stderr). Use a new profile
71
70
  # name if it already exists; keep the user's existing profiles.
72
- tc init --name publisher --key-only
73
- tc --profile publisher auth login --device --manifest builtin:share-publishing
71
+ tinycloud init --name publisher --key-only
72
+ tinycloud --profile publisher auth login --device --manifest builtin:share-publishing
74
73
 
75
74
  # Same Share publishing scope, as a shortcut (on the dedicated profile)
76
- tc --profile publisher enable share
75
+ tinycloud --profile publisher enable share
77
76
 
78
77
  # Selected profile, owner, host and space (JSON; does not test access)
79
- tc context
78
+ tinycloud context
80
79
 
81
80
  # Store and retrieve data
82
- tc kv put greeting "Hello, world"
83
- tc kv get greeting
84
- tc kv list
81
+ tinycloud kv put greeting "Hello, world"
82
+ tinycloud kv get greeting
83
+ tinycloud kv list
85
84
 
86
85
  # Manage network-encrypted secrets on the default network
87
- tc secrets network init
88
- printf '%s' "$ANTHROPIC_API_KEY" | tc secrets put ANTHROPIC_API_KEY --stdin
89
- tc secrets get ANTHROPIC_API_KEY
86
+ tinycloud secrets network init
87
+ printf '%s' "$ANTHROPIC_API_KEY" | tinycloud secrets put ANTHROPIC_API_KEY --stdin
88
+ tinycloud secrets get ANTHROPIC_API_KEY
89
+ tinycloud secrets run ANTHROPIC_API_KEY -- python train.py # the value only in the command's environment
90
90
 
91
91
  # Ask the owner for a secret an agent needs, then wait for it
92
- # (call TinyCloud's CLI by absolute path: a bare `tc` can be iproute2's /usr/sbin/tc)
93
- TC_BIN="$(npm prefix --global)/bin/tc"
94
- "$TC_BIN" --profile agent secrets request OPENAI_API_KEY --json # offline; prints the owner's link
95
- "$TC_BIN" --profile agent secrets wait OPENAI_API_KEY --timeout 9m --json # 0 ready, 8 pending
92
+ # (call TinyCloud's CLI by absolute path when using tc: a bare tc can be iproute2's /usr/sbin/tc)
93
+ tinycloud --profile agent secrets request OPENAI_API_KEY --json # offline; prints the owner's link
94
+ tinycloud --profile agent secrets wait OPENAI_API_KEY --timeout 9m --json # 0 ready, 8 pending
96
95
 
97
96
  # Manage spaces
98
- tc space list
99
- tc space create
97
+ tinycloud space list
98
+ tinycloud space create
100
99
 
101
100
  # Grant access to another user
102
- tc delegation create --to did:pkh:eip155:1:0x...
101
+ tinycloud delegation create --to did:pkh:eip155:1:0x...
103
102
  ```
104
103
 
104
+ ## Home feed
105
+
106
+ `tinycloud feed` uses separate owner-provisioned SQLite databases for each agent's
107
+ cards/handling and the owner's append-only responses. Owner registry and view
108
+ state live separately. SDK consumers use the same contract from
109
+ `@tinycloud/sdk-services/feed` with existing SQL database handles; see the
110
+ [canonical types](../sdk-services/src/feed/types.ts).
111
+
112
+ Use an authenticated **owner-openkey or local-owner-key** profile for `connect`.
113
+ That owner needs SQL read/write/schema authority for
114
+ `xyz.tinycloud.tinychat/home_feed` and the `xyz.tinycloud.tinychat/feed/`
115
+ namespace, plus authority to delegate the exact agent grants below. The trailing
116
+ slash denotes a database namespace, not an individual database.
117
+
118
+ Create a separate agent profile and set `AGENT_DID` to its printed DID. Use the
119
+ same node as the owner:
120
+
121
+ ```bash
122
+ tinycloud profile create feed-agent --posture delegate-session --operator agent
123
+ tinycloud --profile owner feed connect --agent writer --to "$AGENT_DID" --label Writer \
124
+ --space applications > feed-grant.json
125
+ tinycloud --profile feed-agent auth import feed-grant.json
126
+ ```
127
+
128
+ `connect` reserves a stable token/DID/resource binding, provisions both schemas,
129
+ and activates one bundled delegation through the existing SDK. It registers
130
+ actual grant provenance only after activation; a failed activation may be
131
+ retried with the same DID and resources. The agent receives read/write on
132
+ `xyz.tinycloud.tinychat/feed/feed_output_writer`, read on
133
+ `xyz.tinycloud.tinychat/feed/feed_responses_writer`, and hook subscription on
134
+ `sql/xyz.tinycloud.tinychat/feed/feed_responses_writer`. Agents do not receive
135
+ response-write or schema authority. Display labels and tokens select resources;
136
+ the signed grants determine access.
137
+
138
+ Publish `card.json` from the agent profile:
139
+
140
+ ```json
141
+ {"cardId":"card-draft-01","revision":1,"issuanceId":"issuance-draft-01","kind":"decision","needsResponse":true,"title":"Create draft?","body":"Create a reversible draft only.","choices":[{"id":"create","label":"Create draft"}],"urgency":1,"createdAt":"2026-10-29T00:00:00Z"}
142
+ ```
143
+
144
+ ```bash
145
+ tinycloud --profile feed-agent feed publish --agent writer --file card.json
146
+ tinycloud --profile feed-agent feed watch --agent writer --recipient agent-runtime \
147
+ --runner '["node","agent-handler.js"]'
148
+ ```
149
+
150
+ Run `watch` in the agent terminal, then save `response.json` from the owner
151
+ profile in another terminal, or answer the card through Home:
152
+
153
+ ```json
154
+ {"responseId":"response-draft-01","cardId":"card-draft-01","revision":1,"issuanceId":"issuance-draft-01","kind":"answer","choiceId":"create","createdAt":"2026-10-29T00:01:00Z"}
155
+ ```
156
+
157
+ ```bash
158
+ tinycloud --profile owner feed respond --agent writer --file response.json
159
+ tinycloud --profile feed-agent feed responses --agent writer --after 0
160
+ tinycloud --profile feed-agent feed handling --agent writer --file handling.json
161
+ tinycloud --profile owner feed show card-draft-01 --agent writer
162
+ ```
163
+
164
+ For an explicit outbound note to that agent, save a separate `message.json`:
165
+
166
+ ```json
167
+ {"responseId":"message-draft-01","cardId":"card-draft-01","revision":1,"issuanceId":"issuance-draft-01","kind":"message","text":"Please explain the draft before I answer.","createdAt":"2026-10-29T00:02:00Z"}
168
+ ```
169
+
170
+ ```bash
171
+ tinycloud --profile owner feed message --agent writer --file message.json
172
+ ```
173
+
174
+ Messages also work on information cards with `needsResponse: false`. They do not
175
+ answer a card, clear Needs action, or replace its current answer or handling.
176
+ `choiceId` and `supersedesResponseId` are refused on messages. Agent response pages
177
+ and watch include messages, with acknowledgements/outcomes tied to their own full
178
+ response IDs. Private owner notes, task ticks, and seen state remain owner-only
179
+ registry data; they are not outbound messages.
180
+
181
+ For a new response with private structured data, `payload.json` is a versioned
182
+ JSON envelope, for example:
183
+
184
+ ```json
185
+ {"type":"example.private-action","version":1,"data":{"code":"SYNTHETIC-ONLY"}}
186
+ ```
187
+
188
+ Include it on the **first** save, instead of the plain `respond` command above:
189
+
190
+ ```bash
191
+ tinycloud --profile owner feed respond --agent writer --file response.json --payload-file payload.json
192
+ tinycloud --profile feed-agent feed payload --agent writer --response response-draft-01 \
193
+ --card card-draft-01 --revision 1 --issuance issuance-draft-01
194
+ ```
195
+
196
+ The header and payload commit atomically in the same owner-response database.
197
+ Retries of the same response ID must include the same header and payload; a
198
+ changed, omitted, or later-added payload is refused. `feed payload` verifies the
199
+ full response/card/revision/issuance identity, returning its envelope, or `null`
200
+ for a known response without a payload. Unknown or mismatched identities fail.
201
+ Keep approval codes in `payload.data`, never the normal response text.
202
+
203
+ Default `list`, `show`, `responses`, and `watch` output contains only response
204
+ headers and optional `payloadType`/`payloadVersion` metadata, not payload data.
205
+ The SDK equivalents are the optional fourth `appendFeedResponse` or
206
+ `appendFeedMessage` argument and the explicit `readFeedResponsePayload` method.
207
+ `feed message` accepts the same `--payload-file` option on its first save.
208
+ Owner schema initialization adds `feed_response_payloads`; existing response-read
209
+ authority also authorizes an explicit payload read. “Private” here means excluded
210
+ from notification copies, not a new authorization boundary against an agent already
211
+ granted database read.
212
+
213
+ `readFeedResponseHandling(output, identity)` reads acknowledgement and outcome for
214
+ one full response/card/revision/issuance identity, including historical answers or
215
+ messages after later arrivals. Unknown or mismatched handling returns `NOT_FOUND`,
216
+ not a synthetic empty receipt. Known acknowledgement-only or outcome-only history
217
+ retains the other slot as `null`. `getFeedCardState` selects only the current
218
+ answer/follow-up; a saved answer without handling still has `null` receipt slots.
219
+
220
+ Retain the full response ID on a retry. Reusing it with different content is
221
+ refused. Responses bind the full card ID, revision, and issuance; corrections
222
+ are new follow-up records, optionally naming `supersedesResponseId`. Short card
223
+ references work only when unambiguous. Handling records carry their own
224
+ `handlingId`, the response/card identities, `createdAt`, and `phase`:
225
+ `acknowledged`, `succeeded`, or `failed`, with optional structured `detail`.
226
+ Acknowledgement history and the terminal outcome are retained independently.
227
+
228
+ **Saved, delivered, acknowledged, and completed are different states.**
229
+ The runner receives allowlisted response-header JSON and optional payload
230
+ type/version metadata on stdin through an argv array, never shell interpolation.
231
+ Exit zero must mean durable acceptance of that response ID, not completion of
232
+ its action. The agent publishes acknowledgement and outcome through `handling`.
233
+
234
+ `watch` requires **Bun or Node.js 22.13+** for its durable local SQLite ledger;
235
+ other commands keep the CLI's Node.js 20 requirement. By default the ledger is
236
+ stored under the selected profile; `--handoff` selects another durable file.
237
+ The listener subscribes before draining and reconciles on readiness, writes,
238
+ reconnects, and five-minute renewal, without a rapid idle query loop.
239
+
240
+ Before attempting a send, the ledger records `unconfirmed`. A crash, uncertain
241
+ runtime result, or interruption leaves that identity unconfirmed rather than
242
+ automatically sending again. A `prepared` record can resume only before an
243
+ attempt began. Ctrl-C/SIGTERM stops the watcher and forcibly terminates its
244
+ current runner; an already-started action may still have happened. Keep response
245
+ IDs as downstream idempotency keys where supported. Exactly-once external
246
+ execution is not promised.
247
+
248
+ ### Prepared FORMAT1 owner import
249
+
250
+ `feed import` accepts an already-resolved FORMAT1 JSON bundle, not the raw snapshot
251
+ written by `feed export`. Supply the full target space explicitly:
252
+
253
+ ```bash
254
+ tinycloud --profile owner feed import \
255
+ --space tinycloud:pkh:eip155:1:0x2222222222222222222222222222222222222222:applications \
256
+ --file ./prepared-owner-bundle.json
257
+ ```
258
+
259
+ The authenticated owner needs SQL read/write/schema authority for
260
+ `xyz.tinycloud.tinychat/home_feed` and the registered per-agent database pairs.
261
+ The command initializes the owner archive and uses the canonical SDK to initialize
262
+ the current pair schemas. Agent response-read grants are insufficient. Import neither
263
+ registers agents nor broadens their grants.
264
+
265
+ Before writing, the importer validates versions, raw base64/SHA-256 proofs, full
266
+ source references and card/response identities. Publisher agent IDs, DIDs, and
267
+ resource pairs must match actual `feed_agents` rows and the canonical feed resource
268
+ convention, with distinct database basenames. Labels and stored provenance do not
269
+ replace signed node authority. The importer does not classify legacy data, choose
270
+ current answers, resolve recipient names, or recalculate expiry.
271
+
272
+ The complete prepared JSON value stays in owner-only
273
+ `home_feed.feed_owner_imports`, immutable under
274
+ `(source_space_id, namespace, snapshot_id)`. A declared `previousSnapshotId` must
275
+ already exist in that same scope. Reusing a snapshot ID with changed content is
276
+ refused. Raw source rows, original receipts, owner-only state and other archival
277
+ metadata are not copied to agent databases; deletion records are data, not SQL
278
+ deletes. Only envelopes declared `restoreTarget: "agent-response"` are restored
279
+ remotely, using their supplied complete canonical cards and optional private
280
+ response payloads.
281
+
282
+ Remote delivery recipients come only from explicit `runtimeTarget.agentId`
283
+ mappings. Configure `feed watch --recipient` with that same runtime ID, not a
284
+ logical recipient key, legacy name, or label. Partial mappings retain unresolved
285
+ names in the owner archive. Multiple logical keys for one runtime retain all
286
+ evidence, including conflicts; the default-deny delivery policy below still applies.
287
+ Private payloads retain the response-read boundary described above.
288
+
289
+ The archive and per-agent databases do not share a transaction. An identical
290
+ snapshot replay can finish partial progress without replacing existing content.
291
+ Replay is not exactly-once external delivery; runtime durable acceptance,
292
+ deduplication, and deadline checks remain necessary. Import does not pause source
293
+ writers, install a write fence, or establish a live-source cutover.
294
+
295
+ ### Owner SDK restore and delivery policy
296
+
297
+ The generic restore API is exported from `@tinycloud/sdk-services/feed`; the
298
+ prepared importer above uses it. Call `initializeFeedDatabases({ output, responses })`
299
+ with owner-authorized SQL handles to initialize the current pair's tables before
300
+ restoring or listening. Agents retain read-only access to responses and cannot
301
+ initialize or restore that database. Missing import/policy tables fail closed.
302
+
303
+ `restoreFeedRecords({ output, responses }, preparedRecords, { signal })` accepts
304
+ complete `FeedCard` records and response headers with optional private payloads,
305
+ source references, and minimal delivery evidence. It does not interpret legacy
306
+ records, choose current answers, or resolve recipients. Historical responses
307
+ bypass live-submission eligibility only; they still require the full published
308
+ card/revision/issuance identity. Existing live response IDs cannot be reclassified
309
+ as imported. Replays must preserve header/payload content and source bindings.
310
+ Valid literal JSON payload keys are preserved; unknown card-envelope fields are
311
+ still refused. Completed query or batch failures retain their classification if
312
+ caller cancellation races completion or later readback. Without cancellation, an
313
+ identical concurrent restore can still recover through complete content and policy readback.
314
+
315
+ Cards are stored first. Each imported response atomically stores its content-bound
316
+ claim, header, optional private payload, origins, and delivery policies in the
317
+ response database. There is no transaction across the two databases; a repeated
318
+ prepared restore can complete partial progress without replacing existing content.
319
+
320
+ Every import origin has a default `*` deny policy. Dispatch requires each origin
321
+ to have consistent evidence for the exact recipient: `state: "pending"`,
322
+ `dispatch: "resolved-pending"`, `resolvedOwnerMapping: true`, and a required
323
+ `dispatchBefore` containing a valid future timestamp or explicit `null` for no
324
+ absolute cutoff. Held, delivered, unconfirmed, refused, unknown, or conflicting
325
+ evidence does not dispatch. Missing or malformed metadata fails the page rather
326
+ than treating it as live data. Policy fields record owner decisions, not grants.
327
+ Keep raw receipts and complete legacy ledgers in owner-only storage; remote policy
328
+ rows contain only source references, state, mapping evidence, and the cutoff.
329
+
330
+ `listFeedResponseDeliveries(responses, { recipient, afterSequence, limit, signal })`
331
+ reads dispatch-eligible headers; `listFeedResponses` alone is history access, not
332
+ dispatch authority. `listenFeedResponses` applies the policy before local handoff.
333
+ Safe notification JSON carries the validated cutoff but no private payload data.
334
+ The SDK checks the cutoff before handoff and again before invoking the runner.
335
+ A queued runtime must check it immediately before acting. Expiry after a durable
336
+ claim leaves the handoff unconfirmed, without automatic resend. Runtime durable
337
+ acceptance and deduplication are still required; exactly-once external execution
338
+ is not guaranteed.
339
+
340
+ ### Card issuance history
341
+
342
+ `listFeedCardHistory(output, { afterIdentity, limit, signal })` from
343
+ `@tinycloud/sdk-services/feed` reads full stored card issuances and their payloads,
344
+ including earlier revisions and distinct issuances of the same revision.
345
+ Validation preserves literal JSON payload keys, including an own `"__proto__"` key;
346
+ it does not substitute a reconstructed payload or change its prototype.
347
+ Pages use the full `(cardId, revision, issuanceId)` cursor in SQLite BINARY order
348
+ for IDs and numeric order for revision, with default limit 100 and maximum 1000.
349
+ The result is `{ cards, nextIdentity, hasMore }`; `nextIdentity` is the last
350
+ returned full tuple, or `null` for an empty page.
351
+
352
+ This snapshot cursor is not a durable publication tail: new IDs can sort before
353
+ an existing cursor. Start each full reconciliation without `afterIdentity`.
354
+ `listFeedCards` still returns only the latest published issuance per card.
355
+ History reads use the existing output read capability and perform no DDL or writes.
356
+
357
+ ### Raw owner-only source export
358
+
359
+ `feed export` captures raw KV source bytes, not resolved Home cards or a restore plan.
360
+ Supply the full approved source space, a space-relative namespace ending in `/`,
361
+ and an unused output path. There is no installed-legacy or production default:
362
+
363
+ ```bash
364
+ tinycloud --profile owner feed export \
365
+ --source-space tinycloud:pkh:eip155:1:0x1111111111111111111111111111111111111111:applications \
366
+ --prefix example/legacy/ --out ./raw-owner-snapshot.json \
367
+ --sidecars ./raw-sidecars.json
368
+ ```
369
+
370
+ The selected profile must already hold source `tinycloud.kv/get` and explicit
371
+ `tinycloud.kv/sync` authority on that namespace. Normal per-agent SQL feed grants
372
+ do not authorize this export. The command does not broaden grants or fall back
373
+ to LIST when sync is denied.
374
+
375
+ `--sidecars` is optional. Its JSON array explicitly names every local file to
376
+ include; relative paths resolve beside that configuration file:
377
+
378
+ ```json
379
+ [
380
+ {"sourceKey":"owner/watch/hold-actions","sourceArea":"watch-state","path":"hold-actions.json"},
381
+ {"sourceKey":"owner/watch/thread","sourceArea":"thread-watch-state","path":"thread-state.json"},
382
+ {"sourceKey":"owner/expiry","sourceArea":"expiry-state","path":"expiry-state.json"}
383
+ ]
384
+ ```
385
+
386
+ Sidecar files are read as exact bytes, including malformed JSON or binary data.
387
+ No watcher is discovered or executed. Duplicate or conflicting full source keys
388
+ are refused rather than overwritten.
389
+
390
+ The owner-only versioned snapshot retains full KV keys, base64 bytes, SHA-256,
391
+ strong ETags, change metadata, tombstones, opaque cursors, and node source/authority
392
+ attestations. It bootstraps every change-feed page and verifies each binary network
393
+ read against its ETag. One empty final tail at the end cursor is required before
394
+ `completePrefixSnapshot: true` can be published. Source changes, cursor gaps,
395
+ scope drift, or mismatches fail the capture. The exporter does not restart a changed
396
+ capture or create a write fence; SDK requests retain their own transport behavior.
397
+
398
+ The complete file is fsynced and published exclusively with mode `0600`; an existing
399
+ path is never silently replaced. Keep this archive private: it can contain answers
400
+ or codes. Normal command output is only the snapshot ID, output path, and counts.
401
+ The output directory must belong to the effective user and forbid group/world
402
+ writes. Ancestors must belong to that user or root; writable shared ancestors
403
+ require sticky-bit protection of the next trusted-owned child. Unverifiable paths
404
+ are refused. Mode `0600` protects contents, not the staging pathname's identity:
405
+ the directory precondition prevents another UID from replacing that entry.
406
+ The same UID and root remain trusted OS principals, not isolated adversaries.
407
+ Validation includes every intermediate directory-alias target, not only the spelled
408
+ path and its final resolved destination.
409
+ Export does not classify records, choose current answers, resolve recipients,
410
+ evaluate legacy expiry, restore state, or dispatch pending responses.
411
+
105
412
  ## Commands
106
413
 
107
414
  | Command | Description |
108
415
  |---------|-------------|
109
- | `tc init` | Set up a profile and generate keys |
110
- | `tc auth login` | Authenticate via browser |
111
- | `tc auth login --device --manifest <file>` | Approve exactly a manifest's permissions on another device (phone) |
112
- | `tc auth login --manifest <file>` | Browser OpenKey login scoped to a manifest |
113
- | `tc enable share` | Device login with the built-in Share publishing manifest (use a dedicated profile) |
114
- | `tc context` | Show the selected profile, owner, host, space and session state |
115
- | `tc auth status` | Show authentication status |
116
- | `tc auth whoami` | Show current identity |
117
- | `tc auth request --preset apps-agent [--app <id>]... [--write]` | Request access for an agent to the user's installed apps: read-only by default, for one app, several, or all. `--write --app <id>` also lets the agent change that app's data: write its `knowledge/` and `prefs/` files and add, change or delete its table rows. For the Health Log app's catalog database, it can also create, change and drop tables (nodes before 1.21.6 do not enforce that split). It cannot change the app's code, manifest or install record |
118
- | `tc knowledge list` | List the installed apps that have a knowledge bundle |
119
- | `tc knowledge read <app_id> [concept]` | Read an app's knowledge index, or one concept with its frontmatter parsed |
120
- | `tc apps install <app_id>` | Install a pinned catalog app into the applications space of the profile it runs under (the owner's, for a delegate profile, through its grant): each file is verified against its pinned digest, the pinned DDL runs only if every statement is allowed, and knowledge notes are created create-only. Notes you have edited are left as they are and reported (exit 0) |
121
- | `tc kv get <key>` | Retrieve a value |
122
- | `tc kv put <key> <value>` | Store a value |
123
- | `tc kv list` | List all keys |
124
- | `tc sql query <sql>` | Run a SQLite SELECT query |
125
- | `tc sql execute <sql>` | Run a SQLite write or schema statement |
126
- | `tc sql export` | Export a SQLite database file |
127
- | `tc space list` | List your spaces |
128
- | `tc space create` | Create a new space |
129
- | `tc delegation create` | Grant access to another user |
130
- | `tc delegation list` | List delegations |
131
- | `tc share` | Share data with another user |
132
- | `tc node` | Manage TinyCloud nodes |
133
- | `tc profile list` | List profiles |
134
- | `tc profile show` | Show profile details |
135
- | `tc profile create` | Create a new profile |
136
- | `tc vault` | Manage encrypted KV vaults |
137
- | `tc secrets` | Manage network-encrypted secrets |
138
- | `tc secrets network show` | Show a secrets decryption network |
139
- | `tc secrets network init` | Create or fetch the default secrets network |
140
- | `tc secrets network grant` | Grant `tinycloud.encryption/decrypt` on a secrets network |
141
- | `tc secrets request` | Print a Secret Manager link asking the owner to add secrets (offline) |
142
- | `tc secrets wait` | Wait until secrets exist and are readable (never signs in; needs a stored session; exit 8 pending, 130 interrupted) |
143
- | `tc secrets manage` | Print or open the Secret Manager page, `{url, opened}` |
144
- | `tc vars` | Manage environment variables |
145
- | `tc doctor` | Run diagnostic checks |
146
- | `tc completion` | Generate shell completions |
147
- | `tc courier` | Agent-to-agent threads over TinyCloud (beta; see [Courier](#courier)) |
416
+ | `tinycloud init` | Set up a profile and generate keys |
417
+ | `tinycloud auth login` | Authenticate via browser |
418
+ | `tinycloud auth login --device --manifest <file>` | Approve exactly a manifest's permissions on another device (phone) |
419
+ | `tinycloud auth login --manifest <file>` | Browser OpenKey login scoped to a manifest |
420
+ | `tinycloud enable share` | Device login with the built-in Share publishing manifest (use a dedicated profile) |
421
+ | `tinycloud context` | Show the selected profile, owner, host, space and session state |
422
+ | `tinycloud auth status` | Show authentication status |
423
+ | `tinycloud auth whoami` | Show current identity |
424
+ | `tinycloud auth request --preset apps-agent [--app <id>]... [--write]` | Request access for an agent to the user's installed apps: read-only by default, for one app, several, or all. `--write --app <id>` also lets the agent change that app's data: write its `knowledge/` and `prefs/` files and add, change or delete its table rows. For the Health Log app's catalog database, it can also create, change and drop tables (nodes before 1.21.6 do not enforce that split). It cannot change the app's code, manifest or install record |
425
+ | `tinycloud knowledge list` | List the installed apps that have a knowledge bundle |
426
+ | `tinycloud knowledge read <app_id> [concept]` | Read an app's knowledge index, or one concept with its frontmatter parsed |
427
+ | `tinycloud apps install <app_id>` | Install a pinned catalog app into the applications space of the profile it runs under (the owner's, for a delegate profile, through its grant): each file is verified against its pinned digest, the pinned DDL runs only if every statement is allowed, and knowledge notes are created create-only. Notes you have edited are left as they are and reported (exit 0) |
428
+ | `tinycloud kv get <key>` | Retrieve a value |
429
+ | `tinycloud kv put <key> <value>` | Store a value |
430
+ | `tinycloud kv list` | List all keys |
431
+ | `tinycloud sql query <sql>` | Run a SQLite SELECT query |
432
+ | `tinycloud sql execute <sql>` | Run a SQLite write or schema statement |
433
+ | `tinycloud sql export` | Export a SQLite database file |
434
+ | `tinycloud feed` | Connect an agent, publish cards, save responses, record handling, and watch for runtime handoff; see [Home feed](#home-feed) |
435
+ | `tinycloud space list` | List your spaces |
436
+ | `tinycloud space create` | Create a new space |
437
+ | `tinycloud delegation create` | Grant access to another user |
438
+ | `tinycloud delegation list` | List delegations |
439
+ | `tinycloud delegation revoke <cid>` | Revoke a delegation. From a delegate profile, revoke one its own key issued. |
440
+ | `tinycloud share` | Share data with another user |
441
+ | `tinycloud node` | Manage TinyCloud nodes |
442
+ | `tinycloud profile list` | List profiles |
443
+ | `tinycloud profile show` | Show profile details |
444
+ | `tinycloud profile create` | Create a new profile |
445
+ | `tinycloud vault` | Manage encrypted KV vaults |
446
+ | `tinycloud secrets` | Manage network-encrypted secrets |
447
+ | `tinycloud secrets network show` | Show a secrets decryption network |
448
+ | `tinycloud secrets network init` | Create or fetch the default secrets network |
449
+ | `tinycloud secrets network grant` | Grant `tinycloud.encryption/decrypt` on a secrets network |
450
+ | `tinycloud secrets run` | Run a command with secrets in its environment |
451
+ | `tinycloud secrets request` | Print a Secret Manager link asking the owner to add secrets (offline) |
452
+ | `tinycloud secrets wait` | Wait until secrets exist and are readable (never signs in; needs a stored session; exit 8 pending, 130 interrupted) |
453
+ | `tinycloud secrets manage` | Print or open the Secret Manager page, `{url, opened}` |
454
+ | `tinycloud vars` | Manage environment variables |
455
+ | `tinycloud doctor` | Run diagnostic checks |
456
+ | `tinycloud completion` | Generate shell completions |
457
+ | `tinycloud courier` | Agent-to-agent threads over TinyCloud (beta; see [Courier](#courier)) |
148
458
 
149
459
  Secret names are env-style uppercase identifiers such as `FIREFLIES_API_KEY`.
150
- `tc secrets network show` accepts either a short network name or a full
151
- `urn:tinycloud:encryption:<ownerDid>:<network>` identifier. `tc secrets
460
+ `tinycloud secrets network show` accepts either a short network name or a full
461
+ `urn:tinycloud:encryption:<ownerDid>:<network>` identifier. `tinycloud secrets
152
462
  network grant` takes the short name, resolves the network, and grants
153
463
  `tinycloud.encryption/decrypt`.
154
464
 
155
- `tc apps install` re-creates the pinned views on every run (reported as
465
+ `tinycloud apps install` re-creates the pinned views on every run (reported as
156
466
  `viewsRecreated`). Knowledge notes whose bytes differ from the pinned version
157
467
  are left as you edited them and listed under `conflicts` (JSON); the command
158
468
  still exits 0 and installs the rest. If the schema fails after notes were
159
469
  written, it says "notes saved; tables not set up: run again"; running it again
160
470
  is safe.
161
471
 
472
+ ### Running a command with secrets
473
+
474
+ ```bash
475
+ tinycloud secrets run NAME... [--map NAME=ENV_VAR]... [--scope S] [--space S] [--delegation SOURCE] [--private-key HEX] -- <command> [args...]
476
+
477
+ tinycloud secrets run ANTHROPIC_API_KEY OPENAI_API_KEY -- python train.py
478
+ tinycloud secrets run --map ANTHROPIC_KEY=ANTHROPIC_API_KEY -- node server.js
479
+ ```
480
+
481
+ `tinycloud secrets run` reads every named secret as `tinycloud secrets get` does, then
482
+ starts `<command>` directly, without a shell, with each value in its
483
+ environment: under the secret's name, or under `ENV_VAR` for `--map
484
+ NAME=ENV_VAR`. A value replaces an inherited variable of the same name.
485
+ tinycloud doesn't pass its own `TC_PRIVATE_KEY` to the command. To give the command
486
+ a key from a secret, use `--map NAME=TC_PRIVATE_KEY`.
487
+ Everything after the first `--`, including any further `--`, goes to the
488
+ command unchanged. The command uses tinycloud's stdin, stdout and stderr; tinycloud itself
489
+ writes nothing to stdout, and its own messages go to stderr.
490
+
491
+ Name at least one secret, as an argument or with `--map`. `ENV_VAR` must
492
+ match `[A-Za-z_][A-Za-z0-9_]*`, and two secrets cannot be mapped to the same
493
+ variable (exit 2). If any secret cannot be read, the command does not start
494
+ and tinycloud exits with the code and error `tinycloud secrets get` gives for that secret.
495
+ Otherwise tinycloud exits with the command's exit code. If the command was ended by
496
+ SIGINT, SIGTERM, SIGHUP, SIGKILL, SIGALRM or SIGUSR2, tinycloud ends by the same
497
+ signal (or exits with 128 plus the signal number if that signal doesn't end
498
+ tinycloud). If any other signal ended it, such as SIGQUIT or SIGSEGV, tinycloud exits with
499
+ 128 plus the signal number instead, so tinycloud does not cause a core dump
500
+ (SIGUSR1 and SIGPIPE are left out too: Node uses SIGUSR1 for its inspector
501
+ and ignores SIGPIPE).
502
+
503
+ SIGINT, SIGTERM, SIGHUP and SIGQUIT sent to tinycloud go to the command. When tinycloud
504
+ runs in the foreground of a terminal, it doesn't pass on SIGINT, SIGQUIT or
505
+ SIGHUP: the terminal already sends them (Ctrl-C, Ctrl-\, hangup) to the
506
+ command, so the command normally gets each one once. tinycloud checks whether it's
507
+ in the foreground using `/proc`, or `ps` where `/proc` isn't available. If
508
+ neither works, it passes the signals on. When tinycloud runs in the background, it
509
+ passes them on, so a signal sent to the whole job (for example `kill -INT
510
+ %1`) can reach the command twice.
511
+
512
+ From a script, stop the command by sending SIGTERM to tinycloud's process ID. tinycloud
513
+ passes it on, waits for the command to exit, then exits the way the command
514
+ did (the command's own exit code if it handles SIGTERM itself). If the
515
+ command ignores SIGTERM, tinycloud keeps waiting.
516
+
517
+ A command that does not exist exits 127; one that cannot be executed, or
518
+ cannot be started for another reason, exits 126. These cases print one plain
519
+ line on stderr, even with `--json`: `tinycloud secrets run: <command>: command not
520
+ found`, `…: permission denied` or `…: cannot run (<error code>)`.
521
+
522
+ Limits: `tinycloud secrets run` keeps values out of the command line, your shell
523
+ history and tinycloud's own output. It doesn't hide them from the command: the
524
+ command, and anything it starts, receives the values and can print, log or
525
+ pass them on. Other programs running as the same user can often read a
526
+ process's environment too.
527
+
162
528
  ## Global Options
163
529
 
164
530
  ```
@@ -176,7 +542,7 @@ is safe.
176
542
  Replication is off by default. `--replication` enables it for one invocation;
177
543
  `--no-replication` overrides `TC_REPLICATION=1`, and `--replication-debug`
178
544
  writes diagnostics to stderr. Save explicit login prefixes with
179
- `tc auth login --replication-prefix notes/`; saved prefixes are reapplied on
545
+ `tinycloud auth login --replication-prefix notes/`; saved prefixes are reapplied on
180
546
  later logins and rotation. Prefixes are folders (`notes/`) or `/` for the
181
547
  whole space. Whole-space replication requires `kv-sync-root` support and
182
548
  `--replication-allow-secrets`, because it includes `vault/`. Replication needs
@@ -197,7 +563,7 @@ Chromium browsers that enforce Local Network Access without granting it (headles
197
563
  Chromium cannot show the prompt), may block the HTTPS page's plain-HTTP loopback
198
564
  callback: use an interactive CLI terminal and paste the same one-line code shown
199
565
  by OpenKey, or start with
200
- `tc auth login --method openkey --replication-prefix notes/ --owner DID --paste`
566
+ `tinycloud auth login --method openkey --replication-prefix notes/ --owner DID --paste`
201
567
  for a headless terminal. Non-TTY `--no-popup` cannot accept a paste; if that
202
568
  callback fails, it exits nonzero at the five-minute expiry with a `--paste`
203
569
  retry hint and discards the candidate key. Restarting requires a new approval
@@ -205,10 +571,20 @@ and cannot reuse the old signed code. Ctrl-C, SIGINT or SIGTERM before approval
205
571
  arrives cancels the OpenKey handoff (best effort) and exits 130 `CANCELLED`
206
572
  without saving a key or session.
207
573
 
208
- Use `tc replica report` for read-only aggregates of persisted replication events
574
+ Normal OpenKey login prints a short approval link on stderr and waits. Relay it
575
+ to the owner from a headless or remote CLI; no localhost callback or pasted code
576
+ is needed. `--no-popup` skips opening a local browser, not automatic result
577
+ retrieval. The CLI verifies the encrypted result, durably saves it, then
578
+ acknowledges delivery. Re-run the same command after an interruption to resume.
579
+ The one-hour request expiry is independent of the requested grant lifetime and
580
+ result retention. Normal login keeps its seven-day default; onboarding requests
581
+ 30 days explicitly. `--paste` remains an explicit manual fallback with a
582
+ five-minute, single-use return code, not the automatic short-link transport.
583
+
584
+ Use `tinycloud replica report` for read-only aggregates of persisted replication events
209
585
  and identity partitions. Live replica status is unavailable there. Report
210
586
  `--purge` and `--clear-pending` refuse with exit 2 `REPORT_READ_ONLY`.
211
- `tc replica reset --replica <name> --purge` is a partial alternative for a
587
+ `tinycloud replica reset --replica <name> --purge` is a partial alternative for a
212
588
  named local replica only. Flag-owned purge and pending clearing have no
213
589
  equivalent yet (TC-1309). On macOS and Windows, `replica reset --purge` and
214
590
  `auth logout` with a legacy `replicas/` tree refuse local removal rather than
@@ -247,7 +623,7 @@ flag-owned replicas untouched; startup finishes the legacy removal if it is
247
623
  interrupted. If an earlier sign-out was interrupted, logout reports it and a
248
624
  second `auth logout` completes it. A gate that no longer matches its logout
249
625
  record (for example, restored from an older copy) refuses logout; switch the
250
- default profile if needed, then run `tc profile delete <name>` and sign in
626
+ default profile if needed, then run `tinycloud profile delete <name>` and sign in
251
627
  again.
252
628
 
253
629
  `auth logout`, `auth rotate`, and replica-enabled `auth login` that replaces the
@@ -256,20 +632,20 @@ unexpired shares, or cannot be read, the command refuses with exit 5
256
632
  (`SHARE_HISTORY_UNEXPIRED` or `SHARE_HISTORY_UNAVAILABLE`) and changes nothing.
257
633
  Pass `--force` to proceed anyway; `auth logout --revoke` also skips the check. A
258
634
  plain re-login that keeps the key is not checked, and local-owner profiles are not
259
- checked. `tc share history recover` moves an unreadable history file aside to
635
+ checked. `tinycloud share history recover` moves an unreadable history file aside to
260
636
  `share-history-v2.json.unreadable-<UTC timestamp>` and reports the new path. It
261
637
  never deletes or overwrites a file. If the file exists but cannot be read,
262
638
  recover exits 1 with `SHARE_HISTORY_RECOVER_FAILED` and moves nothing.
263
639
 
264
- `tc auth grants list` labels the signed-in delegation CID `(current session)`.
265
- `tc delegation revoke <cid>` still accepts any eligible exact CID, but when
640
+ `tinycloud auth grants list` labels the signed-in delegation CID `(current session)`.
641
+ `tinycloud delegation revoke <cid>` still accepts any eligible exact CID, but when
266
642
  `<cid>` is the current session it requires `--yes` or an interactive confirmation
267
643
  before contacting the node. An exact-CID success then runs the same durable
268
644
  local sign-out and key shred as `auth logout`; revoking another CID leaves the
269
645
  session intact.
270
646
 
271
647
  B-int release acceptance must
272
- verify the OpenKey `tc auth request --grant` fallback against a real OpenKey
648
+ verify the OpenKey `tinycloud auth request --grant` fallback against a real OpenKey
273
649
  using a test account and no production writes: approve the request, confirm the
274
650
  grant is stored, start a fresh process, and verify the saved prefixes are
275
651
  replayed and the covered read is served locally. If OpenKey rejects explicit
@@ -277,7 +653,7 @@ replayed and the covered read is served locally. If OpenKey rejects explicit
277
653
 
278
654
  ## Output Modes
279
655
 
280
- `tc` auto-detects your terminal:
656
+ `tinycloud` auto-detects your terminal:
281
657
 
282
658
  - **Interactive (TTY)** — human-friendly output with colors, tables, and status icons
283
659
  - **Piped / redirected** — structured JSON for scripting
@@ -285,29 +661,29 @@ replayed and the covered read is served locally. If OpenKey rejects explicit
285
661
 
286
662
  ```bash
287
663
  # Human-friendly table
288
- tc kv list
664
+ tinycloud kv list
289
665
 
290
666
  # JSON for scripting
291
- tc kv list --json
292
- tc kv list | jq '.[]'
667
+ tinycloud kv list --json
668
+ tinycloud kv list | jq '.[]'
293
669
  ```
294
670
 
295
671
  ## Diagnostics
296
672
 
297
673
  ```bash
298
- tc doctor
674
+ tinycloud doctor
299
675
  ```
300
676
 
301
677
  Checks Node.js version, profile configuration, keys, session status, node connectivity (with latency), and space availability. Outputs actionable guidance for any failing checks.
302
678
 
303
679
  ## Profiles
304
680
 
305
- `tc` supports multiple profiles for different environments:
681
+ `tinycloud` supports multiple profiles for different environments:
306
682
 
307
683
  ```bash
308
- tc profile create staging --host https://staging.tinycloud.xyz
309
- tc profile list
310
- tc kv list --profile staging
684
+ tinycloud profile create staging --host https://staging.tinycloud.xyz
685
+ tinycloud profile list
686
+ tinycloud kv list --profile staging
311
687
  ```
312
688
 
313
689
  ### Pointing a profile at a self-hosted OpenKey
@@ -325,7 +701,7 @@ profile JSON directly:
325
701
  }
326
702
  ```
327
703
 
328
- `tc auth login --method openkey` and `tc auth request` honor this for the
704
+ `tinycloud auth login --method openkey` and `tinycloud auth request` honor this for the
329
705
  profile. Set `TC_OPENKEY_HOST` to override per invocation without editing the
330
706
  file.
331
707