@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.
- package/README.md +5 -0
- package/bin/tot.mjs +58 -79
- package/package.json +6 -1
- package/src/activity.mjs +15 -14
- package/src/app-scaffold.mjs +4 -4
- package/src/auth.mjs +13 -5
- package/src/candidate-state.mjs +3 -3
- package/src/commands/accept.mjs +65 -38
- package/src/commands/app/dev.mjs +8 -4
- package/src/commands/app/index.mjs +3 -3
- package/src/commands/app/scaffold.mjs +1 -1
- package/src/commands/branches.mjs +4 -3
- package/src/commands/cleanup.mjs +7 -11
- package/src/commands/clone.mjs +23 -20
- package/src/commands/dev.mjs +42 -24
- package/src/commands/doctor.mjs +4 -4
- package/src/commands/git-credential.mjs +2 -2
- package/src/commands/go-live.mjs +9 -5
- package/src/commands/grants.mjs +7 -5
- package/src/commands/hotfix.mjs +1 -1
- package/src/commands/ideas.mjs +2 -2
- package/src/commands/link.mjs +2 -2
- package/src/commands/login.mjs +5 -6
- package/src/commands/pr.mjs +33 -19
- package/src/commands/preview-build.mjs +6 -6
- package/src/commands/preview-doctor.mjs +225 -0
- package/src/commands/preview-retry-evidence.mjs +156 -0
- package/src/commands/preview.mjs +19 -3
- package/src/commands/revert.mjs +5 -5
- package/src/commands/rollback.mjs +18 -16
- package/src/commands/ship.mjs +29 -12
- package/src/commands/start.mjs +61 -51
- package/src/commands/submit.mjs +360 -50
- package/src/commands/sync.mjs +2 -2
- package/src/commands/validate.mjs +4 -3
- package/src/commands/whoami.mjs +1 -1
- package/src/dev-heartbeat.mjs +3 -2
- package/src/dev-logs.mjs +2 -2
- package/src/errors.mjs +11 -4
- package/src/git-credential.mjs +94 -21
- package/src/last-tenant.mjs +1 -1
- package/src/mcp.mjs +6 -1
- package/src/merge-doctor-report.mjs +208 -0
- package/src/oauth.mjs +18 -14
- package/src/obstacle-beacon.cjs +2 -2
- package/src/obstacle.mjs +1 -1
- package/src/plan.mjs +16 -21
- package/src/sample.mjs +4 -4
- package/src/validate.mjs +135 -15
- package/src/vendor/private-apps-devkit.mjs +3 -3
- package/src/viewer-session.mjs +118 -0
- package/template/private-app/README.md +12 -6
- 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
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
198
|
-
// (
|
|
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
|
|
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
|
|
222
|
-
// force-reset, NO touch to main/live. Backed by POST /api/changes/revert
|
|
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
|
|
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 →
|
|
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
|
|
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
|
|
268
|
-
// delete
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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.
|
|
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
|
|
2
|
+
* emitActivity — the CLI's operational-activity emitter.
|
|
3
3
|
*
|
|
4
|
-
* This is a hand-maintained JS MIRROR of the
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
348
|
-
// alongside the
|
|
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
|
|
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.
|
package/src/app-scaffold.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Private App scaffolding — `tot app scaffold <name>` (PrivateApps epic
|
|
3
|
-
*
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
/**
|
|
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
|
|
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 = {}) {
|
package/src/candidate-state.mjs
CHANGED
|
@@ -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 --
|
|
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 (
|
|
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 `--
|
|
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")) {
|