@tokenoftrust/cli 1.4.0-rc.2 → 1.4.0-rc.21
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 +12 -9
- package/bin/tot.mjs +219 -44
- package/package.json +7 -2
- package/src/activity.mjs +379 -0
- package/src/app-scaffold.mjs +2 -2
- package/src/auth.mjs +13 -5
- package/src/candidate-state.mjs +137 -0
- package/src/commands/accept.mjs +736 -0
- package/src/commands/app/dev.mjs +7 -3
- package/src/commands/app/index.mjs +2 -2
- package/src/commands/branches.mjs +297 -0
- package/src/commands/cleanup.mjs +269 -0
- package/src/commands/clone.mjs +713 -0
- package/src/commands/dev.mjs +441 -93
- package/src/commands/doctor.mjs +4 -3
- package/src/commands/git-credential.mjs +180 -0
- package/src/commands/go-live.mjs +486 -0
- package/src/commands/grants.mjs +14 -7
- package/src/commands/hotfix.mjs +428 -0
- package/src/commands/link.mjs +225 -0
- package/src/commands/login.mjs +12 -8
- package/src/commands/pr.mjs +425 -0
- package/src/commands/preview-build.mjs +225 -0
- package/src/commands/preview.mjs +80 -0
- package/src/commands/retire.mjs +203 -0
- package/src/commands/revert.mjs +322 -0
- package/src/commands/rollback.mjs +403 -0
- package/src/commands/ship.mjs +517 -0
- package/src/commands/start.mjs +91 -29
- package/src/commands/submit.mjs +1360 -131
- package/src/commands/sync.mjs +203 -0
- package/src/commands/validate.mjs +11 -5
- package/src/commands/whoami.mjs +6 -2
- package/src/context.mjs +2 -2
- package/src/dev-heartbeat.mjs +2 -1
- package/src/errors.mjs +8 -4
- package/src/git-credential.mjs +185 -0
- package/src/mcp.mjs +6 -1
- package/src/no-gitea-links.test.mjs +55 -0
- package/src/oauth.mjs +26 -11
- package/src/obstacle-beacon.cjs +3 -3
- package/src/obstacle.mjs +1 -1
- package/src/plan.mjs +262 -0
- package/src/sample.mjs +30 -4
- package/src/validate.mjs +56 -0
- package/src/viewer-session.mjs +118 -0
- package/src/commands/checkout.mjs +0 -330
package/README.md
CHANGED
|
@@ -5,11 +5,12 @@ One command to go from a Token of Trust invite to a running store.
|
|
|
5
5
|
```sh
|
|
6
6
|
npm i -g @tokenoftrust/cli
|
|
7
7
|
|
|
8
|
-
tot
|
|
9
|
-
tot
|
|
8
|
+
tot clone # list the stores you can build on
|
|
9
|
+
tot clone <tenant> my-store # mirrors `git clone`; dir defaults to <tenant>
|
|
10
10
|
cd my-store
|
|
11
11
|
tot dev # run it locally with save→reload — no Docker needed
|
|
12
|
-
tot
|
|
12
|
+
tot preview # bundle your edits into a compliance-reviewed preview
|
|
13
|
+
tot ship # promote a reconciled preview live (diff + one confirm)
|
|
13
14
|
```
|
|
14
15
|
|
|
15
16
|
**Prerequisites: Node.js and an invite. Nothing else.** `tot dev` downloads the
|
|
@@ -22,10 +23,12 @@ be fetched.)
|
|
|
22
23
|
|
|
23
24
|
| Command | Status | What it does |
|
|
24
25
|
| --- | --- | --- |
|
|
25
|
-
| `tot
|
|
26
|
-
| `tot validate` | next | Lint your store before you
|
|
26
|
+
| `tot clone [<tenant>] [<dir>]` | **built** | Clone a store you're entitled to build on (mirrors `git clone`), with an authenticated remote configured. Dir defaults to `<tenant>`. No arg → list your stores. |
|
|
27
|
+
| `tot validate` | next | Lint your store before you preview. |
|
|
27
28
|
| `tot dev` | **built** | Run your store locally with save→reload — NATIVELY (no Docker; falls back to it with `--docker` or automatically if the native artifact isn't available). |
|
|
28
|
-
| `tot
|
|
29
|
+
| `tot preview` | **built** | Bundle your edits into a compliance-reviewed preview (validates, auto-commits the known content trees, pushes the preview ref, opens/updates a candidate PR, reports the reconcile/compliance verdict + preview URL). `tot submit` / `tot deploy` still work as teaching aliases for this same flow. |
|
|
30
|
+
| `tot ship` | **built** | Promote a reconciled preview live: always shows a diff-vs-live and asks for one `[y/N]` confirm (no `--yes`, refuses outside a terminal); records an approval request if you're not authorised to ship yourself. |
|
|
31
|
+
| `tot pr [list\|view\|close]` | **built** | See and manage the candidate PRs `tot preview` opens (`gh pr`-shaped). |
|
|
29
32
|
| `tot doctor` | built | Check this machine is ready and show which context `tot` detected. |
|
|
30
33
|
|
|
31
34
|
## Context-aware
|
|
@@ -34,7 +37,7 @@ The same `tot` does the right thing wherever you run it (walks up like `git`):
|
|
|
34
37
|
|
|
35
38
|
- **storefront monorepo** — a full platform checkout;
|
|
36
39
|
- **tenant checkout** — a standalone `content/ public/ theme.json .tot/config.json` clone (tenant read from `.tot/config.json`);
|
|
37
|
-
- **loose** — anywhere else; `tot
|
|
40
|
+
- **loose** — anywhere else; `tot clone <tenant>` gets you a checkout.
|
|
38
41
|
|
|
39
42
|
`tot doctor` prints the detected context.
|
|
40
43
|
|
|
@@ -42,8 +45,8 @@ The same `tot` does the right thing wherever you run it (walks up like `git`):
|
|
|
42
45
|
|
|
43
46
|
`tot` talks to the Token of Trust MCP (default `https://mcp.tokenoftrust.com`, override with `--mcp` or `MCP_BASE_URL`). It is **single-plane**: the only identity is **you**, signed in against the MCP over OAuth.
|
|
44
47
|
|
|
45
|
-
- Run `tot login` once — it opens your browser (or falls back to a device code on a headless box), you sign in as yourself, and the session is cached at `~/.tot/credentials.json` and refreshed silently. Every later command (`tot
|
|
46
|
-
- Not signed in? On a terminal, `tot start` / `tot
|
|
48
|
+
- Run `tot login` once — it opens your browser (or falls back to a device code on a headless box), you sign in as yourself, and the session is cached at `~/.tot/credentials.json` and refreshed silently. Every later command (`tot clone`, `tot start`, `tot preview`, `tot ship`, …) runs as you, with no re-auth. Entitlement is derived server-side from your ToT memberships.
|
|
49
|
+
- Not signed in? On a terminal, `tot start` / `tot clone` **offer to sign you in right there** and continue in-flow — no "run `tot login`, then re-run".
|
|
47
50
|
- The old operator env-triple (`TOT_API_KEY` / `TOT_SECRET_KEY` / `TOT_APP_DOMAIN`) **no longer signs the CLI in** — tot-mcp went OAuth-first on 2026-07-23. If those vars are set, `tot` prints a one-line advisory and uses your `tot login` session anyway; it never reads them for auth.
|
|
48
51
|
|
|
49
52
|
### Optional: multiple identities at once (`TOT_PROFILE`)
|
package/bin/tot.mjs
CHANGED
|
@@ -2,29 +2,16 @@
|
|
|
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 grants capability/tier/expiry per store you can act on ← built (introspection diagnostics)
|
|
12
|
-
* tot checkout [<tenant>] clone a store you can build on ← built
|
|
13
|
-
* tot validate lint your store before you submit ← built
|
|
14
|
-
* tot dev run your store locally with save→reload ← built (monorepo: host astro; standalone: runs the published runner image)
|
|
15
|
-
* tot submit submit your store for preview ← built (validate + push preview ref; MCP preview_status read-back)
|
|
16
|
-
* tot doctor check this machine is ready
|
|
17
|
-
* tot ideas copy-paste AI prompts that reliably wow
|
|
18
|
-
* tot feedback send a note to ToT + your recent CLI activity ← built (activity-log.mjs → feedback_submit MCP tool)
|
|
19
|
-
* tot app scaffold/dev build + locally exercise a Private App ← built (offline; see src/commands/app/)
|
|
20
|
-
* 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.
|
|
21
8
|
*
|
|
22
9
|
* Context-aware (see src/context.mjs): the same `tot` does the right thing from
|
|
23
10
|
* inside the storefront monorepo, from inside a standalone tenant checkout, or
|
|
24
11
|
* from anywhere else. Every command receives the detected context.
|
|
25
12
|
*
|
|
26
13
|
* This package is `@tokenoftrust/cli` (bin `tot`). Its heaviest command, `tot dev`,
|
|
27
|
-
* fetches the published storefront runner; `tot
|
|
14
|
+
* fetches the published storefront runner; `tot clone/validate/submit` are
|
|
28
15
|
* pure Node. Dependency-free by design so `npm i -g @tokenoftrust/cli` stays light.
|
|
29
16
|
*/
|
|
30
17
|
import "../src/ensure-node.mjs"; // hard Node-version gate — must stay first (see the module doc)
|
|
@@ -32,9 +19,10 @@ import { readFileSync } from "node:fs";
|
|
|
32
19
|
import { detectContext } from "../src/context.mjs";
|
|
33
20
|
import { printError } from "../src/errors.mjs";
|
|
34
21
|
import { recordActivity, redactArgs } from "../src/activity-log.mjs";
|
|
22
|
+
import { emitActivity, capRendered } from "../src/activity.mjs";
|
|
35
23
|
import { maybeNotifyUpdate } from "../src/update-check.mjs";
|
|
36
24
|
|
|
37
|
-
const BUILD_ORDER = ["
|
|
25
|
+
const BUILD_ORDER = ["clone", "validate", "dev", "preview"];
|
|
38
26
|
|
|
39
27
|
// CLI version (stamped into the activity log). Read from our own package.json; best-effort.
|
|
40
28
|
const VERSION = (() => {
|
|
@@ -45,26 +33,48 @@ const VERSION = (() => {
|
|
|
45
33
|
}
|
|
46
34
|
})();
|
|
47
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: "retire", blurb: "evict a candidate PR's preview to reclaim space — operator verb, rebuildable (plan → confirm → evict)" },
|
|
58
|
+
{ invocation: "go-live", blurb: "cut the apex domain over to the storefront (readiness → confirm → cutover)" },
|
|
59
|
+
{ invocation: "pr", blurb: "list / view / close your candidate PRs" },
|
|
60
|
+
{ invocation: "branches", blurb: "full branch-cleanup report — every branch, any PR state, cleanup eligibility" },
|
|
61
|
+
{ invocation: "cleanup", blurb: "owner-confirmed branch GC (--dry-run to classify only; deletes terminal branches only)" },
|
|
62
|
+
{ invocation: "doctor", blurb: "check this machine is ready" },
|
|
63
|
+
{ invocation: "ideas", blurb: "copy-paste AI prompts that reliably wow" },
|
|
64
|
+
{ invocation: 'feedback "<msg>"', blurb: "send feedback to Token of Trust (attaches recent activity)" },
|
|
65
|
+
{ invocation: "app scaffold <name>", blurb: "scaffold a Storefront Private App" },
|
|
66
|
+
{ invocation: "app dev ...", blurb: "local, offline Private App webhook/manifest/JWT harness" },
|
|
67
|
+
{ invocation: "help", blurb: "show this help" },
|
|
68
|
+
{ invocation: "--version", blurb: "print the CLI version" },
|
|
69
|
+
];
|
|
70
|
+
|
|
48
71
|
function usage() {
|
|
72
|
+
const col = Math.max(...COMMANDS.map((c) => `tot ${c.invocation}`.length)) + 2;
|
|
73
|
+
const lines = COMMANDS.map((c) => ` ${`tot ${c.invocation}`.padEnd(col)}${c.blurb}`).join("\n");
|
|
49
74
|
console.log(`
|
|
50
75
|
tot — Token of Trust developer CLI
|
|
51
76
|
|
|
52
|
-
|
|
53
|
-
tot login sign in to Token of Trust
|
|
54
|
-
tot logout sign out (clear the cached session)
|
|
55
|
-
tot whoami show who you're signed in as
|
|
56
|
-
tot grants capability/tier/expiry per store you can act on
|
|
57
|
-
tot checkout [<tenant>] clone a store you can build on
|
|
58
|
-
tot validate lint your store before you submit
|
|
59
|
-
tot dev run your store locally with save→reload
|
|
60
|
-
tot submit submit your store for preview
|
|
61
|
-
tot doctor check this machine is ready
|
|
62
|
-
tot ideas copy-paste AI prompts that reliably wow
|
|
63
|
-
tot feedback "<msg>" send feedback to Token of Trust (attaches recent activity)
|
|
64
|
-
tot app scaffold <name> scaffold a Storefront Private App
|
|
65
|
-
tot app dev ... local, offline Private App webhook/manifest/JWT harness
|
|
66
|
-
tot help show this help
|
|
67
|
-
tot --version print the CLI version
|
|
77
|
+
${lines}
|
|
68
78
|
|
|
69
79
|
Run \`tot <command> --help\` for command-specific options.
|
|
70
80
|
`);
|
|
@@ -96,12 +106,12 @@ async function dispatch(cmd, rest, ctx) {
|
|
|
96
106
|
|
|
97
107
|
if (cmd === "logout") {
|
|
98
108
|
const { run } = await import("../src/commands/logout.mjs");
|
|
99
|
-
return run(rest, ctx);
|
|
109
|
+
return /** @type {any} */ (run)(rest, ctx);
|
|
100
110
|
}
|
|
101
111
|
|
|
102
112
|
if (cmd === "feedback") {
|
|
103
113
|
const { run } = await import("../src/commands/feedback.mjs");
|
|
104
|
-
return run(rest, ctx);
|
|
114
|
+
return /** @type {any} */ (run)(rest, ctx);
|
|
105
115
|
}
|
|
106
116
|
|
|
107
117
|
if (cmd === "whoami") {
|
|
@@ -109,6 +119,11 @@ async function dispatch(cmd, rest, ctx) {
|
|
|
109
119
|
return run(rest, ctx);
|
|
110
120
|
}
|
|
111
121
|
|
|
122
|
+
if (cmd === "link") {
|
|
123
|
+
const { run } = await import("../src/commands/link.mjs");
|
|
124
|
+
return run(rest, ctx);
|
|
125
|
+
}
|
|
126
|
+
|
|
112
127
|
if (cmd === "doctor") {
|
|
113
128
|
const { run } = await import("../src/commands/doctor.mjs");
|
|
114
129
|
return run(rest, ctx);
|
|
@@ -124,8 +139,17 @@ async function dispatch(cmd, rest, ctx) {
|
|
|
124
139
|
return run(rest, ctx);
|
|
125
140
|
}
|
|
126
141
|
|
|
127
|
-
if (cmd === "
|
|
128
|
-
const { run } = await import("../src/commands/
|
|
142
|
+
if (cmd === "clone") {
|
|
143
|
+
const { run } = await import("../src/commands/clone.mjs");
|
|
144
|
+
return run(rest, ctx);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Plumbing, not a developer-facing verb (like git's own credential helpers,
|
|
148
|
+
// never listed in `git help`) — `tot clone` configures it as this checkout's
|
|
149
|
+
// `credential.helper`, and git alone invokes it (get/store/erase) on every
|
|
150
|
+
// fetch/push. See src/commands/git-credential.mjs's header (unit u10).
|
|
151
|
+
if (cmd === "git-credential") {
|
|
152
|
+
const { run } = await import("../src/commands/git-credential.mjs");
|
|
129
153
|
return run(rest, ctx);
|
|
130
154
|
}
|
|
131
155
|
|
|
@@ -139,8 +163,103 @@ async function dispatch(cmd, rest, ctx) {
|
|
|
139
163
|
return run(rest, ctx);
|
|
140
164
|
}
|
|
141
165
|
|
|
142
|
-
|
|
143
|
-
|
|
166
|
+
// dev → preview → ship. `preview` is the first-class verb; `submit`/`deploy`
|
|
167
|
+
// reach the SAME preview flow as teaching aliases (preview.mjs prints a one-line
|
|
168
|
+
// hint nudging the developer onto `tot preview` / `tot ship`).
|
|
169
|
+
if (cmd === "preview") {
|
|
170
|
+
const { run } = await import("../src/commands/preview.mjs");
|
|
171
|
+
return run(rest, ctx);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (cmd === "submit" || cmd === "deploy") {
|
|
175
|
+
const { run } = await import("../src/commands/preview.mjs");
|
|
176
|
+
return run(rest, ctx, { alias: cmd });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (cmd === "ship") {
|
|
180
|
+
const { run } = await import("../src/commands/ship.mjs");
|
|
181
|
+
return run(rest, ctx);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// `accept` queue-integrates a PR into the protected `preview` aggregate (b08) —
|
|
185
|
+
// NO merge-to-main, NO go-live; going live is `tot ship`, which publishes the
|
|
186
|
+
// whole current GREEN AGGREGATE (b10). Backed by POST /api/changes/integrate
|
|
187
|
+
// (b07's TenantIntegrationQueue.enqueue); see accept.mjs's header. `merge` is a
|
|
188
|
+
// first-class alias, not a teaching nudge: both dispatch straight to the same command.
|
|
189
|
+
if (cmd === "accept" || cmd === "merge") {
|
|
190
|
+
const { run } = await import("../src/commands/accept.mjs");
|
|
191
|
+
return run(rest, ctx);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// `sync` is the common accept-conflict recovery path (P1 item 12): fetches the
|
|
195
|
+
// protected `preview` branch and merges it into the developer's local branch so
|
|
196
|
+
// they can resolve locally, then re-`tot preview`. Purely local — no push, no
|
|
197
|
+
// MCP call, never touches the shared `preview`/`main` refs themselves. See
|
|
198
|
+
// sync.mjs's header for the full contract.
|
|
199
|
+
if (cmd === "sync") {
|
|
200
|
+
const { run } = await import("../src/commands/sync.mjs");
|
|
201
|
+
return run(rest, ctx);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (cmd === "rollback") {
|
|
205
|
+
const { run } = await import("../src/commands/rollback.mjs");
|
|
206
|
+
return run(rest, ctx);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// `revert --preview <PR|integration-sha>` removes already-integrated content from
|
|
210
|
+
// the protected `preview` aggregate via a NEW auditable revert commit (b21) — NO
|
|
211
|
+
// force-reset, NO touch to main/live. Backed by POST /api/changes/revert (b07's
|
|
212
|
+
// TenantIntegrationQueue.enqueueRevert). To undo something already LIVE, that's
|
|
213
|
+
// `tot rollback`, not this. See revert.mjs's header.
|
|
214
|
+
if (cmd === "revert") {
|
|
215
|
+
const { run } = await import("../src/commands/revert.mjs");
|
|
216
|
+
return run(rest, ctx);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// `hotfix --pr <N>` is the OWNER-ONLY EXCEPTION lane (b22): release an urgent fix
|
|
220
|
+
// from `main` to live while `preview` still holds other unshipped work, EXCLUDING
|
|
221
|
+
// that unshipped preview head, then automatically forward-integrate main → preview
|
|
222
|
+
// and re-validate. It is DELIBERATELY a distinct verb — never a `--base main` flag
|
|
223
|
+
// on `tot ship` (which publishes the whole green preview aggregate). Backed by
|
|
224
|
+
// GET/POST /api/changes/hotfix → b22's HotfixOrchestrator. See hotfix.mjs's header.
|
|
225
|
+
if (cmd === "hotfix") {
|
|
226
|
+
const { run } = await import("../src/commands/hotfix.mjs");
|
|
227
|
+
return run(rest, ctx);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// `retire` evicts a candidate PR's hosted preview to reclaim space (unit U7) —
|
|
231
|
+
// a DISTINCT operator verb from `accept`/reject: retire touches no change
|
|
232
|
+
// lifecycle and is reversible-by-rebuild (`tot preview build`). See retire.mjs.
|
|
233
|
+
if (cmd === "retire") {
|
|
234
|
+
const { run } = await import("../src/commands/retire.mjs");
|
|
235
|
+
return run(rest, ctx);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (cmd === "go-live") {
|
|
239
|
+
const { run } = await import("../src/commands/go-live.mjs");
|
|
240
|
+
return run(rest, ctx);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (cmd === "pr") {
|
|
244
|
+
const { run } = await import("../src/commands/pr.mjs");
|
|
245
|
+
return run(rest, ctx);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// `branches` is the FULL branch-cleanup report (every branch, any PR state) —
|
|
249
|
+
// distinct from `tot pr list`, which only shows OPEN PRs (P1 item 9). Read-only;
|
|
250
|
+
// always safe to run, and the report `tot cleanup` reuses before deleting anything.
|
|
251
|
+
if (cmd === "branches") {
|
|
252
|
+
const { run } = await import("../src/commands/branches.mjs");
|
|
253
|
+
return run(rest, ctx);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// `cleanup` is owner-confirmed BRANCH GC (P1 items 9/10) — an irreversible git-ref
|
|
257
|
+
// delete, distinct from `tot retire`'s hosted-preview eviction (rebuildable, no
|
|
258
|
+
// branch touched). `--dry-run` classifies and prints; it never deletes. Age alone
|
|
259
|
+
// never makes a branch eligible; main/preview are always kept; orphans are
|
|
260
|
+
// quarantined, never auto-deleted. See cleanup.mjs's header.
|
|
261
|
+
if (cmd === "cleanup") {
|
|
262
|
+
const { run } = await import("../src/commands/cleanup.mjs");
|
|
144
263
|
return run(rest, ctx);
|
|
145
264
|
}
|
|
146
265
|
|
|
@@ -149,10 +268,21 @@ async function dispatch(cmd, rest, ctx) {
|
|
|
149
268
|
return run(rest, ctx);
|
|
150
269
|
}
|
|
151
270
|
|
|
271
|
+
// `checkout` was deliberately freed up: it's no longer a tot verb precisely so it
|
|
272
|
+
// can mean what it means in git. Point both ways rather than a bare "unknown".
|
|
273
|
+
if (cmd === "checkout") {
|
|
274
|
+
console.error(
|
|
275
|
+
"`tot checkout` isn't a tot command — `checkout` now means what it does in git.\n\n" +
|
|
276
|
+
" • switch branches / restore files in your store: git checkout <ref>\n" +
|
|
277
|
+
" • clone a store you can build on: tot clone <tenant>\n",
|
|
278
|
+
);
|
|
279
|
+
return 2;
|
|
280
|
+
}
|
|
281
|
+
|
|
152
282
|
if (BUILD_ORDER.includes(cmd)) {
|
|
153
283
|
console.error(
|
|
154
284
|
`\`tot ${cmd}\` isn't built yet.\n\n` +
|
|
155
|
-
`Build order: ${BUILD_ORDER.map((c) => (c === "
|
|
285
|
+
`Build order: ${BUILD_ORDER.map((c) => (c === "clone" ? `${c}✓` : c)).join(" → ")}.\n` +
|
|
156
286
|
`Inside a storefront checkout you can use the in-repo scripts today ` +
|
|
157
287
|
`(e.g. \`pnpm tot ${cmd}\`).`,
|
|
158
288
|
);
|
|
@@ -164,12 +294,39 @@ async function dispatch(cmd, rest, ctx) {
|
|
|
164
294
|
return 2;
|
|
165
295
|
}
|
|
166
296
|
|
|
297
|
+
/**
|
|
298
|
+
* The safe `subcommand` value for a command's activity event. Only the fixed
|
|
299
|
+
* sub-dispatcher verbs (e.g. `app scaffold` / `app dev`) are surfaced — an
|
|
300
|
+
* arbitrary positional (a tenant name, a path, a code) is NEVER used, so the
|
|
301
|
+
* activity `subcommand` field can't carry PII even before redaction. Returns
|
|
302
|
+
* undefined for every command without a fixed subcommand vocabulary.
|
|
303
|
+
*/
|
|
304
|
+
function safeSubcommand(cmd, rest) {
|
|
305
|
+
if (cmd === "app") {
|
|
306
|
+
const sub = rest.find((t) => t && !t.startsWith("-"));
|
|
307
|
+
if (sub === "scaffold" || sub === "dev") return sub;
|
|
308
|
+
}
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
|
|
167
312
|
async function main() {
|
|
168
313
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
169
314
|
const ctx = detectContext();
|
|
170
315
|
const startedAt = Date.now();
|
|
316
|
+
const command = cmd || "(none)";
|
|
317
|
+
const subcommand = safeSubcommand(cmd, rest);
|
|
171
318
|
let code = 0;
|
|
172
319
|
let errMsg = null;
|
|
320
|
+
|
|
321
|
+
// D3: emit `cli.command.invoked` on start — best-effort, a SILENT no-op without a
|
|
322
|
+
// hosted-bridge credential (never a network call / never blocks). Fired without
|
|
323
|
+
// await so it adds no latency to the command; settled alongside the result below.
|
|
324
|
+
const invokedEmit = emitActivity({
|
|
325
|
+
action: "cli.command.invoked",
|
|
326
|
+
outcome: { status: "invoked" },
|
|
327
|
+
payload: { args: { command, ...(subcommand ? { subcommand } : {}), cliVersion: VERSION, node: process.version } },
|
|
328
|
+
});
|
|
329
|
+
|
|
173
330
|
try {
|
|
174
331
|
code = await dispatch(cmd, rest, ctx);
|
|
175
332
|
return code;
|
|
@@ -177,17 +334,35 @@ async function main() {
|
|
|
177
334
|
errMsg = e?.message || String(e);
|
|
178
335
|
throw e;
|
|
179
336
|
} finally {
|
|
337
|
+
const exitCode = errMsg ? 1 : (code ?? 0);
|
|
338
|
+
const durationMs = Date.now() - startedAt;
|
|
180
339
|
// Best-effort activity breadcrumb (never throws, never blocks). `feedback`'s own
|
|
181
340
|
// free-text message is omitted — it's user-typed and belongs only in the report.
|
|
182
341
|
recordActivity({
|
|
183
342
|
ts: new Date().toISOString(),
|
|
184
343
|
v: VERSION,
|
|
185
|
-
cmd:
|
|
344
|
+
cmd: command,
|
|
186
345
|
args: cmd === "feedback" ? ["«omitted»"] : redactArgs(rest),
|
|
187
|
-
code:
|
|
188
|
-
ms:
|
|
346
|
+
code: exitCode,
|
|
347
|
+
ms: durationMs,
|
|
189
348
|
...(errMsg ? { err: String(errMsg).slice(0, 200) } : {}),
|
|
190
349
|
});
|
|
350
|
+
// D3: emit `cli.command.result` (exit code + duration + a bounded/redacted
|
|
351
|
+
// rendered field). The house-style error text is capped + run through the JS
|
|
352
|
+
// redaction mirror; per the D0 catalog the `cli.*` rendered lane is empty-
|
|
353
|
+
// allowlisted, so it's DROPPED by design (the safe default for a high-volume
|
|
354
|
+
// action) — the emit path still exercises the mirror. Bounded-await here (with
|
|
355
|
+
// the invoked emit) so a live bridge flushes before the process exits; a no-op
|
|
356
|
+
// when there's no credential.
|
|
357
|
+
const resultEmit = emitActivity({
|
|
358
|
+
action: "cli.command.result",
|
|
359
|
+
outcome: { status: errMsg ? "failed" : "succeeded", durationMs },
|
|
360
|
+
payload: {
|
|
361
|
+
args: { command, ...(subcommand ? { subcommand } : {}), cliVersion: VERSION, exitCode, durationMs },
|
|
362
|
+
...(errMsg ? { rendered: { output: capRendered(errMsg) } } : {}),
|
|
363
|
+
},
|
|
364
|
+
});
|
|
365
|
+
await Promise.allSettled([invokedEmit, resultEmit]);
|
|
191
366
|
// Nudge if a newer/unsupported version exists (drawn from cache — instant),
|
|
192
367
|
// and kick a detached registry refresh if stale. Never throws, never blocks.
|
|
193
368
|
maybeNotifyUpdate(VERSION);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "1.4.0-rc.
|
|
4
|
-
"description": "Token of Trust developer CLI —
|
|
3
|
+
"version": "1.4.0-rc.21",
|
|
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",
|
|
7
7
|
"homepage": "https://www.tokenoftrust.com",
|
|
@@ -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
|
}
|