@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 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,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
- 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
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.20",
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: 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,
@@ -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,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 --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
@@ -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")) {