@tokenoftrust/cli 1.4.0-rc.20 → 1.4.0-rc.22
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/bin/tot.mjs +52 -54
- package/package.json +6 -1
- package/src/activity.mjs +5 -4
- package/src/app-scaffold.mjs +2 -2
- package/src/auth.mjs +13 -5
- package/src/candidate-state.mjs +2 -2
- package/src/commands/accept.mjs +489 -50
- package/src/commands/app/dev.mjs +7 -3
- package/src/commands/app/index.mjs +2 -2
- package/src/commands/branches.mjs +1 -0
- package/src/commands/cleanup.mjs +2 -1
- package/src/commands/clone.mjs +51 -20
- package/src/commands/dev.mjs +30 -12
- package/src/commands/git-credential.mjs +180 -0
- package/src/commands/go-live.mjs +6 -2
- package/src/commands/grants.mjs +6 -4
- package/src/commands/link.mjs +2 -2
- package/src/commands/login.mjs +3 -4
- package/src/commands/pr.mjs +6 -5
- package/src/commands/preview-doctor.mjs +225 -0
- package/src/commands/preview.mjs +9 -1
- package/src/commands/rollback.mjs +6 -4
- package/src/commands/ship.mjs +19 -1
- package/src/commands/start.mjs +59 -11
- package/src/commands/submit.mjs +526 -68
- package/src/commands/sync.mjs +11 -0
- package/src/commands/validate.mjs +10 -4
- 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/merge-doctor-report.mjs +208 -0
- package/src/oauth.mjs +12 -8
- package/src/obstacle-beacon.cjs +2 -2
- package/src/obstacle.mjs +1 -1
- package/src/plan.mjs +3 -3
- package/src/sample.mjs +3 -3
- package/src/validate.mjs +56 -0
- package/src/viewer-session.mjs +118 -0
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,48 @@ 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: "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
|
+
|
|
57
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");
|
|
58
74
|
console.log(`
|
|
59
75
|
tot — Token of Trust developer CLI
|
|
60
76
|
|
|
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
|
|
77
|
+
${lines}
|
|
89
78
|
|
|
90
79
|
Run \`tot <command> --help\` for command-specific options.
|
|
91
80
|
`);
|
|
@@ -117,12 +106,12 @@ async function dispatch(cmd, rest, ctx) {
|
|
|
117
106
|
|
|
118
107
|
if (cmd === "logout") {
|
|
119
108
|
const { run } = await import("../src/commands/logout.mjs");
|
|
120
|
-
return run(rest, ctx);
|
|
109
|
+
return /** @type {any} */ (run)(rest, ctx);
|
|
121
110
|
}
|
|
122
111
|
|
|
123
112
|
if (cmd === "feedback") {
|
|
124
113
|
const { run } = await import("../src/commands/feedback.mjs");
|
|
125
|
-
return run(rest, ctx);
|
|
114
|
+
return /** @type {any} */ (run)(rest, ctx);
|
|
126
115
|
}
|
|
127
116
|
|
|
128
117
|
if (cmd === "whoami") {
|
|
@@ -155,6 +144,15 @@ async function dispatch(cmd, rest, ctx) {
|
|
|
155
144
|
return run(rest, ctx);
|
|
156
145
|
}
|
|
157
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");
|
|
153
|
+
return run(rest, ctx);
|
|
154
|
+
}
|
|
155
|
+
|
|
158
156
|
if (cmd === "validate") {
|
|
159
157
|
const { run } = await import("../src/commands/validate.mjs");
|
|
160
158
|
return run(rest, ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "1.4.0-rc.
|
|
3
|
+
"version": "1.4.0-rc.22",
|
|
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
|
@@ -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)`);
|
|
@@ -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,
|
package/src/app-scaffold.mjs
CHANGED
|
@@ -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,7 +7,7 @@
|
|
|
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
|
|
@@ -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")) {
|