@tokenoftrust/cli 1.4.1 → 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.
Files changed (53) hide show
  1. package/README.md +5 -0
  2. package/bin/tot.mjs +58 -79
  3. package/package.json +6 -1
  4. package/src/activity.mjs +15 -14
  5. package/src/app-scaffold.mjs +4 -4
  6. package/src/auth.mjs +13 -5
  7. package/src/candidate-state.mjs +3 -3
  8. package/src/commands/accept.mjs +65 -38
  9. package/src/commands/app/dev.mjs +8 -4
  10. package/src/commands/app/index.mjs +3 -3
  11. package/src/commands/app/scaffold.mjs +1 -1
  12. package/src/commands/branches.mjs +4 -3
  13. package/src/commands/cleanup.mjs +7 -11
  14. package/src/commands/clone.mjs +23 -20
  15. package/src/commands/dev.mjs +42 -24
  16. package/src/commands/doctor.mjs +4 -4
  17. package/src/commands/git-credential.mjs +2 -2
  18. package/src/commands/go-live.mjs +9 -5
  19. package/src/commands/grants.mjs +7 -5
  20. package/src/commands/hotfix.mjs +1 -1
  21. package/src/commands/ideas.mjs +2 -2
  22. package/src/commands/link.mjs +2 -2
  23. package/src/commands/login.mjs +5 -6
  24. package/src/commands/pr.mjs +33 -19
  25. package/src/commands/preview-build.mjs +6 -6
  26. package/src/commands/preview-doctor.mjs +225 -0
  27. package/src/commands/preview-retry-evidence.mjs +156 -0
  28. package/src/commands/preview.mjs +19 -3
  29. package/src/commands/revert.mjs +5 -5
  30. package/src/commands/rollback.mjs +18 -16
  31. package/src/commands/ship.mjs +29 -12
  32. package/src/commands/start.mjs +61 -51
  33. package/src/commands/submit.mjs +360 -50
  34. package/src/commands/sync.mjs +2 -2
  35. package/src/commands/validate.mjs +4 -3
  36. package/src/commands/whoami.mjs +1 -1
  37. package/src/dev-heartbeat.mjs +3 -2
  38. package/src/dev-logs.mjs +2 -2
  39. package/src/errors.mjs +11 -4
  40. package/src/git-credential.mjs +94 -21
  41. package/src/last-tenant.mjs +1 -1
  42. package/src/mcp.mjs +6 -1
  43. package/src/merge-doctor-report.mjs +208 -0
  44. package/src/oauth.mjs +18 -14
  45. package/src/obstacle-beacon.cjs +2 -2
  46. package/src/obstacle.mjs +1 -1
  47. package/src/plan.mjs +16 -21
  48. package/src/sample.mjs +4 -4
  49. package/src/validate.mjs +135 -15
  50. package/src/vendor/private-apps-devkit.mjs +3 -3
  51. package/src/viewer-session.mjs +118 -0
  52. package/template/private-app/README.md +12 -6
  53. package/src/commands/retire.mjs +0 -203
package/README.md CHANGED
@@ -19,6 +19,11 @@ install, no hand-provisioned AWS credentials. (Docker remains available as a
19
19
  fallback — `tot dev --docker` — or automatically if the native artifact can't
20
20
  be fetched.)
21
21
 
22
+ For Storefront Private App contracts, examples, and LLM-ready developer material, start at
23
+ [Storefront Devkit — Apps / Private](https://github.com/tokenoftrust/storefront-devkit/tree/main/apps/private).
24
+ The `tot app` commands use the CLI's self-contained helper; they do not require a separately
25
+ published npm SDK.
26
+
22
27
  ## Commands
23
28
 
24
29
  | Command | Status | What it does |
package/bin/tot.mjs CHANGED
@@ -2,30 +2,9 @@
2
2
  /**
3
3
  * `tot` — the Token of Trust developer CLI.
4
4
  *
5
- * One command a developer learns to go from an invite to a running store:
6
- *
7
- * tot start invite running store in one command ← built (orchestrates the below, value-first)
8
- * tot login sign in to Token of Trust (OAuth) ← built (MCP OAuth PKCE loopback; caches ~/.tot/credentials.json)
9
- * tot logout sign out (clear the cached session) ← built (deletes ~/.tot/credentials.json; local-only, no server revoke)
10
- * tot whoami who you're signed in as ← built
11
- * tot link link your identity to the ToT broker (resolve store scope) ← built
12
- * tot grants capability/tier/expiry per store you can act on ← built (introspection diagnostics)
13
- * tot clone [<tenant>] clone a store you can build on ← built
14
- * tot validate lint your store before you submit ← built
15
- * tot dev run your store locally with save→reload ← built (monorepo: host astro; standalone: runs the published runner image)
16
- * tot preview push your store to a reviewable preview ← built (validate + push preview ref; MCP preview_status read-back). `submit`/`deploy` are teaching aliases.
17
- * tot ship publish the current green aggregate live ← built (GET/POST /api/changes/ship → b09 orchestrator; exact plan + y/N confirm; waits for VERIFIED live truth)
18
- * tot accept / tot merge queue a PR into the preview aggregate ← built (b08: plan + y/N confirm → POST /api/changes/integrate = b07 queue enqueue; NO merge-to-main, NO go-live; refuses non-TTY without --yes)
19
- * tot sync fetch `preview` + merge it into your branch ← built (b16: the common accept-conflict recovery path; local-only, stops safely on conflict)
20
- * tot rollback [<version>] instant re-point to a prior live version ← built (u3 promotion_status/promotion_rollback seam; diff + y/N confirm; refuses non-TTY / ineligible)
21
- * tot pr list / view / close your candidate PRs ← built (candidate_status/candidate_close; gh-pr-shaped)
22
- * tot branches full branch-cleanup report (every branch, not just open PRs) ← built (b14: candidate_list)
23
- * tot cleanup owner-confirmed branch GC (deletes terminal branches only) ← built (b14: candidate_list + candidate_delete; --dry-run classifies only)
24
- * tot doctor check this machine is ready
25
- * tot ideas copy-paste AI prompts that reliably wow
26
- * tot feedback send a note to ToT + your recent CLI activity ← built (activity-log.mjs → feedback_submit MCP tool)
27
- * tot app scaffold/dev build + locally exercise a Private App ← built (offline; see src/commands/app/)
28
- * tot help this help
5
+ * One command a developer learns to go from an invite to a running store. The
6
+ * user-facing command list is the single source of truth in `COMMANDS` below;
7
+ * `usage()` renders it, so add new commands there never in a second list.
29
8
  *
30
9
  * Context-aware (see src/context.mjs): the same `tot` does the right thing from
31
10
  * inside the storefront monorepo, from inside a standalone tenant checkout, or
@@ -54,38 +33,47 @@ const VERSION = (() => {
54
33
  }
55
34
  })();
56
35
 
36
+ // Single source of truth for the user-facing command list. `usage()` renders
37
+ // it and the file header points here; keep new commands in this one array so the
38
+ // help output can't drift from a second hand-maintained copy. `invocation` is the
39
+ // text after `tot ` (including any argument hint); `blurb` is the one-line help.
40
+ const COMMANDS = [
41
+ { invocation: "start", blurb: "invite → running store in one command (start here)" },
42
+ { invocation: "login", blurb: "sign in to Token of Trust" },
43
+ { invocation: "logout", blurb: "sign out (clear the cached session)" },
44
+ { invocation: "whoami", blurb: "show who you're signed in as" },
45
+ { invocation: "link", blurb: "link your identity so your stores resolve" },
46
+ { invocation: "grants", blurb: "capability/tier/expiry per store you can act on" },
47
+ { invocation: "clone [<tenant>]", blurb: "clone a store you can build on" },
48
+ { invocation: "validate", blurb: "lint your store before you submit" },
49
+ { invocation: "dev", blurb: "run your store locally with save→reload" },
50
+ { invocation: "preview", blurb: "push your store to a reviewable preview" },
51
+ { invocation: "ship", blurb: "publish the tenant's current green aggregate live (plan → confirm → ship)" },
52
+ { invocation: "accept / tot merge", blurb: "queue a PR into the preview aggregate — operator verb, no go-live (plan → confirm → integrate)" },
53
+ { invocation: "sync", blurb: "fetch `preview` and merge it into your local branch (accept-conflict recovery)" },
54
+ { invocation: "rollback [<version>]", blurb: "instant re-point to a prior live version (list → confirm → rollback)" },
55
+ { invocation: "revert --preview <PR|sha>", blurb: "remove already-integrated content from the preview aggregate via a new revert commit — no force-reset (plan → confirm → revert)" },
56
+ { invocation: "hotfix --pr <N>", blurb: "OWNER-ONLY exception: release an urgent fix from main to live, bypassing unshipped preview work (plan → confirm → release → auto forward-integrate)", },
57
+ { invocation: "go-live", blurb: "cut the apex domain over to the storefront (readiness → confirm → cutover)" },
58
+ { invocation: "pr", blurb: "list / view / close your candidate PRs" },
59
+ { invocation: "branches", blurb: "full branch-cleanup report — every branch, any PR state, cleanup eligibility" },
60
+ { invocation: "cleanup", blurb: "owner-confirmed branch GC (--dry-run to classify only; deletes terminal branches only)" },
61
+ { invocation: "doctor", blurb: "check this machine is ready" },
62
+ { invocation: "ideas", blurb: "copy-paste AI prompts that reliably wow" },
63
+ { invocation: 'feedback "<msg>"', blurb: "send feedback to Token of Trust (attaches recent activity)" },
64
+ { invocation: "app scaffold <name>", blurb: "scaffold a Storefront Private App" },
65
+ { invocation: "app dev ...", blurb: "local, offline Private App webhook/manifest/JWT harness" },
66
+ { invocation: "help", blurb: "show this help" },
67
+ { invocation: "--version", blurb: "print the CLI version" },
68
+ ];
69
+
57
70
  function usage() {
71
+ const col = Math.max(...COMMANDS.map((c) => `tot ${c.invocation}`.length)) + 2;
72
+ const lines = COMMANDS.map((c) => ` ${`tot ${c.invocation}`.padEnd(col)}${c.blurb}`).join("\n");
58
73
  console.log(`
59
74
  tot — Token of Trust developer CLI
60
75
 
61
- tot start invite → running store in one command (start here)
62
- tot login sign in to Token of Trust
63
- tot logout sign out (clear the cached session)
64
- tot whoami show who you're signed in as
65
- tot link link your identity so your stores resolve
66
- tot grants capability/tier/expiry per store you can act on
67
- tot clone [<tenant>] clone a store you can build on
68
- tot validate lint your store before you submit
69
- tot dev run your store locally with save→reload
70
- tot preview push your store to a reviewable preview
71
- tot ship publish the tenant's current green aggregate live (plan → confirm → ship)
72
- tot accept / tot merge queue a PR into the preview aggregate — operator verb, no go-live (plan → confirm → integrate)
73
- tot sync fetch \`preview\` and merge it into your local branch (accept-conflict recovery)
74
- tot rollback [<version>] instant re-point to a prior live version (list → confirm → rollback)
75
- tot revert --preview <PR|sha> remove already-integrated content from the preview aggregate via a new revert commit — no force-reset (plan → confirm → revert)
76
- tot hotfix --pr <N> OWNER-ONLY exception: release an urgent fix from main to live, bypassing unshipped preview work (plan → confirm → release → auto forward-integrate)
77
- tot retire evict a candidate PR's preview to reclaim space — operator verb, rebuildable (plan → confirm → evict)
78
- tot go-live cut the apex domain over to the storefront (readiness → confirm → cutover)
79
- tot pr list / view / close your candidate PRs
80
- tot branches full branch-cleanup report — every branch, any PR state, cleanup eligibility
81
- tot cleanup owner-confirmed branch GC (--dry-run to classify only; deletes terminal branches only)
82
- tot doctor check this machine is ready
83
- tot ideas copy-paste AI prompts that reliably wow
84
- tot feedback "<msg>" send feedback to Token of Trust (attaches recent activity)
85
- tot app scaffold <name> scaffold a Storefront Private App
86
- tot app dev ... local, offline Private App webhook/manifest/JWT harness
87
- tot help show this help
88
- tot --version print the CLI version
76
+ ${lines}
89
77
 
90
78
  Run \`tot <command> --help\` for command-specific options.
91
79
  `);
@@ -117,12 +105,12 @@ async function dispatch(cmd, rest, ctx) {
117
105
 
118
106
  if (cmd === "logout") {
119
107
  const { run } = await import("../src/commands/logout.mjs");
120
- return run(rest, ctx);
108
+ return /** @type {any} */ (run)(rest, ctx);
121
109
  }
122
110
 
123
111
  if (cmd === "feedback") {
124
112
  const { run } = await import("../src/commands/feedback.mjs");
125
- return run(rest, ctx);
113
+ return /** @type {any} */ (run)(rest, ctx);
126
114
  }
127
115
 
128
116
  if (cmd === "whoami") {
@@ -158,7 +146,7 @@ async function dispatch(cmd, rest, ctx) {
158
146
  // Plumbing, not a developer-facing verb (like git's own credential helpers,
159
147
  // never listed in `git help`) — `tot clone` configures it as this checkout's
160
148
  // `credential.helper`, and git alone invokes it (get/store/erase) on every
161
- // fetch/push. See src/commands/git-credential.mjs's header (unit u10).
149
+ // fetch/push. See src/commands/git-credential.mjs's header.
162
150
  if (cmd === "git-credential") {
163
151
  const { run } = await import("../src/commands/git-credential.mjs");
164
152
  return run(rest, ctx);
@@ -192,17 +180,17 @@ async function dispatch(cmd, rest, ctx) {
192
180
  return run(rest, ctx);
193
181
  }
194
182
 
195
- // `accept` queue-integrates a PR into the protected `preview` aggregate (b08)
183
+ // `accept` queue-integrates a PR into the protected `preview` aggregate —
196
184
  // NO merge-to-main, NO go-live; going live is `tot ship`, which publishes the
197
- // whole current GREEN AGGREGATE (b10). Backed by POST /api/changes/integrate
198
- // (b07's TenantIntegrationQueue.enqueue); see accept.mjs's header. `merge` is a
185
+ // whole current GREEN AGGREGATE. Backed by POST /api/changes/integrate
186
+ // (TenantIntegrationQueue.enqueue); see accept.mjs's header. `merge` is a
199
187
  // first-class alias, not a teaching nudge: both dispatch straight to the same command.
200
188
  if (cmd === "accept" || cmd === "merge") {
201
189
  const { run } = await import("../src/commands/accept.mjs");
202
190
  return run(rest, ctx);
203
191
  }
204
192
 
205
- // `sync` is the common accept-conflict recovery path (P1 item 12): fetches the
193
+ // `sync` is the common accept-conflict recovery path: fetches the
206
194
  // protected `preview` branch and merges it into the developer's local branch so
207
195
  // they can resolve locally, then re-`tot preview`. Purely local — no push, no
208
196
  // MCP call, never touches the shared `preview`/`main` refs themselves. See
@@ -218,34 +206,26 @@ async function dispatch(cmd, rest, ctx) {
218
206
  }
219
207
 
220
208
  // `revert --preview <PR|integration-sha>` removes already-integrated content from
221
- // the protected `preview` aggregate via a NEW auditable revert commit (b21) — NO
222
- // force-reset, NO touch to main/live. Backed by POST /api/changes/revert (b07's
223
- // TenantIntegrationQueue.enqueueRevert). To undo something already LIVE, that's
209
+ // the protected `preview` aggregate via a NEW auditable revert commit — NO
210
+ // force-reset, NO touch to main/live. Backed by POST /api/changes/revert
211
+ // (TenantIntegrationQueue.enqueueRevert). To undo something already LIVE, that's
224
212
  // `tot rollback`, not this. See revert.mjs's header.
225
213
  if (cmd === "revert") {
226
214
  const { run } = await import("../src/commands/revert.mjs");
227
215
  return run(rest, ctx);
228
216
  }
229
217
 
230
- // `hotfix --pr <N>` is the OWNER-ONLY EXCEPTION lane (b22): release an urgent fix
218
+ // `hotfix --pr <N>` is the OWNER-ONLY EXCEPTION lane: release an urgent fix
231
219
  // from `main` to live while `preview` still holds other unshipped work, EXCLUDING
232
220
  // that unshipped preview head, then automatically forward-integrate main → preview
233
221
  // and re-validate. It is DELIBERATELY a distinct verb — never a `--base main` flag
234
222
  // on `tot ship` (which publishes the whole green preview aggregate). Backed by
235
- // GET/POST /api/changes/hotfix → b22's HotfixOrchestrator. See hotfix.mjs's header.
223
+ // GET/POST /api/changes/hotfix → HotfixOrchestrator. See hotfix.mjs's header.
236
224
  if (cmd === "hotfix") {
237
225
  const { run } = await import("../src/commands/hotfix.mjs");
238
226
  return run(rest, ctx);
239
227
  }
240
228
 
241
- // `retire` evicts a candidate PR's hosted preview to reclaim space (unit U7) —
242
- // a DISTINCT operator verb from `accept`/reject: retire touches no change
243
- // lifecycle and is reversible-by-rebuild (`tot preview build`). See retire.mjs.
244
- if (cmd === "retire") {
245
- const { run } = await import("../src/commands/retire.mjs");
246
- return run(rest, ctx);
247
- }
248
-
249
229
  if (cmd === "go-live") {
250
230
  const { run } = await import("../src/commands/go-live.mjs");
251
231
  return run(rest, ctx);
@@ -257,16 +237,15 @@ async function dispatch(cmd, rest, ctx) {
257
237
  }
258
238
 
259
239
  // `branches` is the FULL branch-cleanup report (every branch, any PR state) —
260
- // distinct from `tot pr list`, which only shows OPEN PRs (P1 item 9). Read-only;
240
+ // distinct from `tot pr list`, which only shows OPEN PRs. Read-only;
261
241
  // always safe to run, and the report `tot cleanup` reuses before deleting anything.
262
242
  if (cmd === "branches") {
263
243
  const { run } = await import("../src/commands/branches.mjs");
264
244
  return run(rest, ctx);
265
245
  }
266
246
 
267
- // `cleanup` is owner-confirmed BRANCH GC (P1 items 9/10) — an irreversible git-ref
268
- // delete, distinct from `tot retire`'s hosted-preview eviction (rebuildable, no
269
- // branch touched). `--dry-run` classifies and prints; it never deletes. Age alone
247
+ // `cleanup` is owner-confirmed BRANCH GC — an irreversible git-ref
248
+ // delete for terminal candidates. `--dry-run` classifies and prints; it never deletes. Age alone
270
249
  // never makes a branch eligible; main/preview are always kept; orphans are
271
250
  // quarantined, never auto-deleted. See cleanup.mjs's header.
272
251
  if (cmd === "cleanup") {
@@ -329,7 +308,7 @@ async function main() {
329
308
  let code = 0;
330
309
  let errMsg = null;
331
310
 
332
- // D3: emit `cli.command.invoked` on start — best-effort, a SILENT no-op without a
311
+ // Emit `cli.command.invoked` on start — best-effort, a SILENT no-op without a
333
312
  // hosted-bridge credential (never a network call / never blocks). Fired without
334
313
  // await so it adds no latency to the command; settled alongside the result below.
335
314
  const invokedEmit = emitActivity({
@@ -358,9 +337,9 @@ async function main() {
358
337
  ms: durationMs,
359
338
  ...(errMsg ? { err: String(errMsg).slice(0, 200) } : {}),
360
339
  });
361
- // D3: emit `cli.command.result` (exit code + duration + a bounded/redacted
340
+ // Emit `cli.command.result` (exit code + duration + a bounded/redacted
362
341
  // rendered field). The house-style error text is capped + run through the JS
363
- // redaction mirror; per the D0 catalog the `cli.*` rendered lane is empty-
342
+ // redaction mirror; per the activity catalog the `cli.*` rendered lane is empty-
364
343
  // allowlisted, so it's DROPPED by design (the safe default for a high-volume
365
344
  // action) — the emit path still exercises the mirror. Bounded-await here (with
366
345
  // the invoked emit) so a live bridge flushes before the process exits; a no-op
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/cli",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Token of Trust developer CLI — clone a tenant store, run it locally with save→reload, and submit it for preview. Installs the `tot` command.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Token of Trust",
@@ -36,7 +36,12 @@
36
36
  "access": "public",
37
37
  "registry": "https://registry.npmjs.org"
38
38
  },
39
+ "devDependencies": {
40
+ "@types/node": "^20.16.0",
41
+ "typescript": "^5.6.3"
42
+ },
39
43
  "scripts": {
44
+ "typecheck": "tsc --noEmit",
40
45
  "test": "node --test",
41
46
  "prepublishOnly": "node ../../scripts/build/check-cli-clean.mjs ."
42
47
  }
package/src/activity.mjs CHANGED
@@ -1,11 +1,11 @@
1
1
  /**
2
- * emitActivity — the CLI's operational-activity emitter (card D3).
2
+ * emitActivity — the CLI's operational-activity emitter.
3
3
  *
4
- * This is a hand-maintained JS MIRROR of the D0 actor×action contract that lives,
4
+ * This is a hand-maintained JS MIRROR of the actor×action contract that lives,
5
5
  * in TypeScript, in `packages/public-runtime/src/activity/` (event.ts / catalog.ts
6
6
  * / redaction.ts). The CLI is published to npm STANDALONE and is deliberately pure
7
7
  * JS with NO dependency on `@tot/public-runtime` (a TS package) — see that module's
8
- * README ("Consuming this from the downstream cards → D3"). So instead of importing
8
+ * README ("Consuming this from the downstream cards"). So instead of importing
9
9
  * the contract, we mirror the slice of it the CLI needs, and a keep-in-step test
10
10
  * (activity-mirror.test.mjs) fails LOUDLY the moment this mirror drifts from the TS
11
11
  * source of truth (the same discipline as no-gitea-links.test.mjs).
@@ -191,6 +191,7 @@ export function redactPayload(action, raw) {
191
191
  * and runs the raw payload through redactPayload() so the returned event's payload
192
192
  * is already default-denied + canary-scanned. Throws only on an out-of-catalog
193
193
  * (non-`cli.*`) action — a programming error.
194
+ * @param {{ action: any, actor?: any, source?: string, outcome?: any, scope?: any, payload?: any, at?: any, id?: any }} input
194
195
  */
195
196
  export function createActivityEvent({ action, actor, source = "cli", outcome, scope, payload, at, id }) {
196
197
  if (!isCliActionKey(action)) throw new Error(`activity: unknown CLI action "${action}" (not in the CLI mirror catalog)`);
@@ -217,7 +218,7 @@ export function createActivityEvent({ action, actor, source = "cli", outcome, sc
217
218
  const EMIT_TIMEOUT_MS = 1500;
218
219
 
219
220
  /**
220
- * The ONE activity-telemetry kill switch (card D8) — the SAME flag name the server
221
+ * The ONE activity-telemetry kill switch — the SAME flag name the server
221
222
  * honours (`apps/storefront/src/lib/activity/killSwitch.ts`). Set
222
223
  * `ACTIVITY_TELEMETRY_DISABLED=1` (or true/yes/on) and every CLI emit becomes a silent
223
224
  * no-op — no build, no redaction, no network — while the command itself runs unchanged
@@ -253,9 +254,9 @@ export function resolveActivityBridge(env = process.env) {
253
254
 
254
255
  /**
255
256
  * The event's actor — kind `dev` (or `agent` when the caller signals an automated
256
- * run) with an OPAQUE, non-reversible id, per the D0 contract (actor.id in the core
257
+ * run) with an OPAQUE, non-reversible id, per the contract (actor.id in the core
257
258
  * zone is NEVER raw PII). Until the canonical pseudonymization salt lands (an open
258
- * question owned by D1/D4 — see redaction.ts header), we derive the id from an
259
+ * question — see redaction.ts header), we derive the id from an
259
260
  * already-opaque handle: the masked emailHint if present, else a one-way hash of the
260
261
  * bridge token, else "anonymous". Never reversible, never raw PII.
261
262
  */
@@ -279,7 +280,7 @@ export function resolveActor(env = process.env) {
279
280
  * event it triggers share one trace (matching the existing feedback/heartbeat use).
280
281
  */
281
282
  function resolveScope(env = process.env, extra = {}) {
282
- const scope = { ...extra };
283
+ const scope = /** @type {any} */ ({ ...extra });
283
284
  try {
284
285
  const creds = readCredentials(defaultCredentialsPath(env));
285
286
  if (creds?.traceId && !scope.traceId) scope.traceId = creds.traceId;
@@ -298,10 +299,10 @@ function resolveScope(env = process.env, extra = {}) {
298
299
  * Returns `{ sent, event?, report? }` so callers/tests can assert what would be
299
300
  * emitted without any network. `fetchImpl` and `bridge` are injectable for tests.
300
301
  *
301
- * @param {{ action: string, outcome: object, actor?: object, scope?: object,
302
+ * @param {{ action?: string, outcome?: object, actor?: object, scope?: object,
302
303
  * payload?: object, source?: string, env?: NodeJS.ProcessEnv,
303
- * fetchImpl?: typeof fetch, bridge?: {url:string,token:string}|null }} input
304
- * @returns {Promise<{sent:boolean, event?:object, report?:object}>}
304
+ * fetchImpl?: typeof fetch, bridge?: {url:string,token:string}|null }} [input]
305
+ * @returns {Promise<{sent:boolean, event?:object, report?:object, disabled?:boolean}>}
305
306
  */
306
307
  export async function emitActivity({
307
308
  action,
@@ -315,7 +316,7 @@ export async function emitActivity({
315
316
  bridge,
316
317
  } = {}) {
317
318
  try {
318
- // Kill switch (D8): telemetry off ⇒ true no-op (no build/redaction/network),
319
+ // Kill switch: telemetry off ⇒ true no-op (no build/redaction/network),
319
320
  // fail-open — the command is unaffected.
320
321
  if (isActivityDisabled(env)) return { sent: false, disabled: true };
321
322
  const resolvedBridge = bridge !== undefined ? bridge : resolveActivityBridge(env);
@@ -344,8 +345,8 @@ export async function emitActivity({
344
345
  "content-type": "application/json",
345
346
  authorization: `Bearer ${resolvedBridge.token}`,
346
347
  },
347
- // `event: "activity"` tags the stream for the bridge/D4 ingest to route,
348
- // alongside the D0 envelope. Best-effort — an older bridge that doesn't
348
+ // `event: "activity"` tags the stream for the bridge ingest to route,
349
+ // alongside the standard envelope. Best-effort — an older bridge that doesn't
349
350
  // recognize it simply ignores the post.
350
351
  body: JSON.stringify({ event: "activity", ...event }),
351
352
  signal: controller.signal,
@@ -366,7 +367,7 @@ export async function emitActivity({
366
367
  * Cap + surface a bit of rendered output for a command-result event. The full raw
367
368
  * stdout is NEVER stored — this caps to MAX_VALUE_LEN and hands the string to the
368
369
  * redaction mirror via payload.rendered. NB the `cli.command.*` actions carry an
369
- * EMPTY renderedAllow in the D0 catalog, so this string is DROPPED at redaction by
370
+ * EMPTY renderedAllow in the action catalog, so this string is DROPPED at redaction by
370
371
  * design (the safe default for a high-volume action) — running it through the
371
372
  * mirror is the belt-and-suspenders the contract prescribes, and keeps the emit
372
373
  * path honest if the allowlist ever opens. Pure.
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Private App scaffolding — `tot app scaffold <name>` (PrivateApps epic D6
3
- * Chunk C). Mirrors `sample.mjs`'s `scaffoldSample`: materialize a bundled
2
+ * Private App scaffolding — `tot app scaffold <name>` (PrivateApps epic).
3
+ * Mirrors `sample.mjs`'s `scaffoldSample`: materialize a bundled
4
4
  * template onto disk, idempotently, fully offline. No login, no MCP, no
5
5
  * network — a developer building a Storefront Private App gets a runnable
6
6
  * skeleton before they've registered anything with Token of Trust.
@@ -63,14 +63,14 @@ export function scaffoldApp(destDir, { force = false, log = () => {} } = {}) {
63
63
  const err = new Error(
64
64
  `${dir} isn't empty and isn't an app scaffold — scaffold into an empty directory (or pass a new name)`,
65
65
  );
66
- err.code = "ENOTEMPTY_APP";
66
+ /** @type {any} */ (err).code ="ENOTEMPTY_APP";
67
67
  throw err;
68
68
  }
69
69
 
70
70
  const template = appTemplateDir();
71
71
  if (!existsSync(template)) {
72
72
  const err = new Error("the private-app template isn't available in this release yet — it's coming soon.");
73
- err.code = "TEMPLATE_UNAVAILABLE";
73
+ /** @type {any} */ (err).code ="TEMPLATE_UNAVAILABLE";
74
74
  throw err;
75
75
  }
76
76
 
package/src/auth.mjs CHANGED
@@ -26,8 +26,13 @@ import { refreshAccessToken, credentialsFromToken } from "./oauth.mjs";
26
26
 
27
27
  /** Thrown when no provider can authenticate — carries actionable guidance. */
28
28
  export class AuthUnavailableError extends Error {
29
- constructor(message, { hint, reason } = {}) {
29
+ /**
30
+ * @param {string} message
31
+ * @param {{ hint?: string|null, reason?: string|null }} [opts]
32
+ */
33
+ constructor(message, opts = {}) {
30
34
  super(message);
35
+ const { hint, reason } = opts;
31
36
  this.name = "AuthUnavailableError";
32
37
  this.hint = hint || null;
33
38
  this.reason = reason || null;
@@ -72,7 +77,10 @@ export function credentialEmailHint(creds) {
72
77
  );
73
78
  }
74
79
 
75
- /** Hosted cockpit URL for regenerating local CLI credentials, when we cached one. */
80
+ /**
81
+ * Hosted cockpit URL for regenerating local CLI credentials, when we cached one.
82
+ * @param {any} activityUrl @param {string|null} [emailHint]
83
+ */
76
84
  export function cockpitRecoveryUrl(activityUrl, emailHint = null) {
77
85
  if (!activityUrl) return null;
78
86
  try {
@@ -125,10 +133,10 @@ export function warnLegacyOperatorEnv(env = process.env, warn = (m) => console.e
125
133
  * every tool call carries the developer identity. Fails with a clear next step
126
134
  * (never a stack trace) when there's no usable session.
127
135
  *
128
- * @param {import("./mcp.mjs").createMcpClient extends (...a:any)=>infer R ? R : any} client
136
+ * @param {(import("./mcp.mjs").createMcpClient extends (...a:any)=>infer R ? R : any)|null} client
129
137
  * @param {NodeJS.ProcessEnv} env
130
138
  * @param {{ fetchImpl?: typeof fetch, now?: number }} [deps] injectable for tests
131
- * @returns {Promise<{ identity: "developer", appDomain: null, token: string }>}
139
+ * @returns {Promise<{ identity: "developer", appDomain: null, token: string, email?: string|null }>}
132
140
  */
133
141
  export async function resolveDeveloperSession(client, env, deps = {}) {
134
142
  const { fetchImpl = fetch, now = Date.now() } = deps;
@@ -219,7 +227,7 @@ export async function resolveDeveloperSession(client, env, deps = {}) {
219
227
  * are never read for auth.
220
228
  *
221
229
  * @param {ReturnType<import("./mcp.mjs").createMcpClient>} client
222
- * @param {{ env?: NodeJS.ProcessEnv, initialize?: () => Promise<any> }} [opts]
230
+ * @param {{ env?: NodeJS.ProcessEnv, initialize?: () => Promise<any>, prefer?: string }} [opts]
223
231
  * @returns {Promise<{ identity: "developer", appDomain: null, token: string, email?: string|null }>}
224
232
  */
225
233
  export async function establishSession(client, opts = {}) {
@@ -7,14 +7,14 @@
7
7
  * nothing here (backward-compatible with the stateless original). This file only
8
8
  * records a DIVERGENCE from that stable default:
9
9
  *
10
- * - `tot submit --new` forks a fresh candidate and remembers it here, so the
10
+ * - `tot submit --fork-candidate` forks a fresh candidate and remembers it here, so the
11
11
  * NEXT plain `tot submit` keeps updating the NEW PR (like pushing more commits
12
12
  * to a `gh pr` branch), not the old one; and
13
13
  * - a terminal-roll (the active candidate was merged/closed) records the fresh
14
14
  * candidate it rolled to, so you're never wedged submitting to a dead PR.
15
15
  *
16
16
  * ONE file, `~/.tot/candidates.json`, a map keyed by `<mcpUrl>::<repo>` on the
17
- * DEFAULT branch and `<mcpUrl>::<repo>::<branch>` on any other (u4 — branch-bound
17
+ * DEFAULT branch and `<mcpUrl>::<repo>::<branch>` on any other (branch-bound
18
18
  * candidates): a different MCP, repo, OR non-default git branch is a different
19
19
  * candidate namespace, so a feature branch gets its OWN candidate PR instead of
20
20
  * fighting main's over the same handle. The default branch deliberately keeps the
@@ -123,7 +123,7 @@ export function clearActiveChangeId(filePath, { mcpUrl, repo, branch }) {
123
123
  /**
124
124
  * A fresh candidate handle forked from a stable base — `<baseId>-<suffix>`, still
125
125
  * matching candidate_open's `[a-z0-9._-]` handle grammar. The suffix defaults to
126
- * 6 random hex chars (so two `--new` runs never collide); tests inject a fixed
126
+ * 6 random hex chars (so two `--fork-candidate` runs never collide); tests inject a fixed
127
127
  * suffix. Pure given `suffix`.
128
128
  */
129
129
  export function mintFreshChangeId(baseId, suffix = randomBytes(3).toString("hex")) {