@tokenoftrust/cli 2.0.15 → 2.0.17
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 +3 -5
- package/package.json +1 -1
- package/src/commands/clone.mjs +32 -2
- package/src/commands/dev.mjs +11 -30
- package/src/commands/submit.mjs +6 -7
package/README.md
CHANGED
|
@@ -15,9 +15,7 @@ tot ship # promote a reconciled preview live (diff + one con
|
|
|
15
15
|
|
|
16
16
|
**Prerequisites: Node.js and an invite. Nothing else.** `tot dev` downloads the
|
|
17
17
|
moat-free storefront runner and runs it as a plain host process — no Docker
|
|
18
|
-
install
|
|
19
|
-
fallback — `tot dev --docker` — or automatically if the native artifact can't
|
|
20
|
-
be fetched.)
|
|
18
|
+
install or hand-provisioned AWS credentials.
|
|
21
19
|
|
|
22
20
|
For Storefront Private App contracts, examples, and LLM-ready developer material, start at
|
|
23
21
|
[Storefront Devkit — Apps / Private](https://github.com/tokenoftrust/storefront-devkit/tree/main/apps/private).
|
|
@@ -30,7 +28,7 @@ published npm SDK.
|
|
|
30
28
|
| --- | --- | --- |
|
|
31
29
|
| `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. |
|
|
32
30
|
| `tot validate` | next | Lint your store before you preview. |
|
|
33
|
-
| `tot dev` | **built** | Run your store locally with save→reload —
|
|
31
|
+
| `tot dev` | **built** | Run your store locally with save→reload — natively. |
|
|
34
32
|
| `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. |
|
|
35
33
|
| `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. |
|
|
36
34
|
| `tot pr [list\|view\|close]` | **built** | See and manage the candidate PRs `tot preview` opens (`gh pr`-shaped). |
|
|
@@ -75,4 +73,4 @@ A clean short name (`staff`, `dev`, `test-7`) becomes a readable `credentials.<n
|
|
|
75
73
|
|
|
76
74
|
## Design notes
|
|
77
75
|
|
|
78
|
-
Dependency-free by design (global `fetch` + `git` + the system `tar`), so `npm i -g @tokenoftrust/cli` stays light. `tot dev` is the only command that pulls in the storefront runner: for a standalone checkout it downloads the moat-free runner artifact (`dev_renderer_artifact`, entitlement-gated
|
|
76
|
+
Dependency-free by design (global `fetch` + `git` + the system `tar`), so `npm i -g @tokenoftrust/cli` stays light. `tot dev` is the only command that pulls in the storefront runner: for a standalone checkout it downloads the moat-free runner artifact (`dev_renderer_artifact`, entitlement-gated) once per version to `~/.tot/cache/renderer/<version>/`, `pnpm install`s it there (via corepack — no separate pnpm install needed), and reuses that cache on every later run. See `src/` for the small modules: `context.mjs` (detection), `mcp.mjs` (transport), `auth.mjs` (the identity seam), `commands/`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.17",
|
|
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",
|
package/src/commands/clone.mjs
CHANGED
|
@@ -117,7 +117,7 @@ const USAGE = `tot clone — clone a tenant store you can build on (mirrors \`gi
|
|
|
117
117
|
<tenant>-pr<N>)
|
|
118
118
|
|
|
119
119
|
Options:
|
|
120
|
-
--tag <tag>
|
|
120
|
+
--tag <tag> deprecated compatibility input; tenant repos use the bare domain.
|
|
121
121
|
--pr <N> clone PR #<N>'s head instead of the default branch — a
|
|
122
122
|
READ-ONLY cross-tenant credential, not a push checkout.
|
|
123
123
|
--remote-only don't clone; just mint + print the clone URL for <tenant>.
|
|
@@ -287,7 +287,22 @@ export async function run(argv, ctx) {
|
|
|
287
287
|
* cloned: boolean, dir: string|null, head: string|null }>}
|
|
288
288
|
*/
|
|
289
289
|
export async function checkoutTenant(client, { tenant, tag = "main", cloneDir = null, redact = (s) => s }) {
|
|
290
|
-
|
|
290
|
+
// The switch is LOAD-BEARING, so its refusal is the error the developer must see. When
|
|
291
|
+
// the tenant is not one of this session's clients the server refuses here with the real
|
|
292
|
+
// cause (no grant / a grant issued to a client this identity is not bound to / a grant
|
|
293
|
+
// issued after sign-in that needs a re-auth) — far more specific than the tenant_checkout
|
|
294
|
+
// refusal that follows it. Surfacing the switch's own words, with the server's nextAction,
|
|
295
|
+
// stops a genuinely-unentitled tenant from being reported as a scope-selection slip.
|
|
296
|
+
try {
|
|
297
|
+
await client.callTool("client_switch", { tenant });
|
|
298
|
+
} catch (e) {
|
|
299
|
+
throw new CliError(redact(String(e?.message || e)), {
|
|
300
|
+
next:
|
|
301
|
+
"confirm you're entitled to this store — `tot grants` lists what your session can " +
|
|
302
|
+
"act on, and `tot login` refreshes a grant issued after you signed in",
|
|
303
|
+
cause: e,
|
|
304
|
+
});
|
|
305
|
+
}
|
|
291
306
|
// vc-app-binding safety-net (08-18, §5): ensure THIS developer is bound to the
|
|
292
307
|
// storefront version-control app BEFORE checkout, so tenant_checkout can resolve
|
|
293
308
|
// `appForCaller` for them instead of 403-ing "No version-control app is bound." The
|
|
@@ -541,6 +556,21 @@ export function checkoutError(checkout) {
|
|
|
541
556
|
(c && (c.message || (typeof c.error === "string" ? c.error : c.error?.message))) ||
|
|
542
557
|
(c && typeof c.raw === "string" && c.raw.trim() ? c.raw.trim() : null) ||
|
|
543
558
|
null;
|
|
559
|
+
// Not entitled: the session resolved NO grant or client context for the asserted tenant.
|
|
560
|
+
// Distinct from both not-provisioned (the store is fine) and over-scope (you hold it but
|
|
561
|
+
// haven't switched) — and the one case where the remedy is upstream of this checkout.
|
|
562
|
+
if (
|
|
563
|
+
typeof msg === "string" &&
|
|
564
|
+
/not among the tenants your session holds|^Not entitled/i.test(msg)
|
|
565
|
+
) {
|
|
566
|
+
return {
|
|
567
|
+
message: msg,
|
|
568
|
+
next:
|
|
569
|
+
"`tot grants` lists what your session can actually act on — a grant whose grantee " +
|
|
570
|
+
"your identity doesn't resolve to never becomes visible here; if it was issued after " +
|
|
571
|
+
"you signed in, run `tot login` to refresh, then retry",
|
|
572
|
+
};
|
|
573
|
+
}
|
|
544
574
|
// The store's repo isn't provisioned yet — it isn't set up for this developer.
|
|
545
575
|
if (typeof msg === "string" && /not provisioned/i.test(msg)) {
|
|
546
576
|
return {
|
package/src/commands/dev.mjs
CHANGED
|
@@ -9,16 +9,14 @@
|
|
|
9
9
|
*
|
|
10
10
|
* checkout — a standalone tenant checkout has no runner. `tot dev` DOWNLOADS the
|
|
11
11
|
* published, RUNNER ARTIFACT (a plain tarball — the exact
|
|
12
|
-
* same tree
|
|
12
|
+
* same renderer tree used by the platform, see
|
|
13
13
|
* scripts/build/build-runner.mjs) and runs it NATIVELY: no Docker, no
|
|
14
14
|
* container. Cached once per version under ~/.tot/cache/renderer/, so
|
|
15
15
|
* only the first run pays the download+install cost. The runner's own
|
|
16
16
|
* scripts/tot-dev.mjs grafts the checkout in and boots astro dev
|
|
17
17
|
* --host at http://localhost:<port>/<appDomain>/, with real (not
|
|
18
18
|
* polled) file-watch HMR. Nothing is published; product data renders
|
|
19
|
-
* from local fixtures.
|
|
20
|
-
* (--docker, or automatically if the native artifact can't be
|
|
21
|
-
* fetched) — see runContainer below.
|
|
19
|
+
* from local fixtures.
|
|
22
20
|
*
|
|
23
21
|
* --sample — the ZERO-LOGIN, NO-MCP free taste. Scaffolds a bundled sample store
|
|
24
22
|
* onto disk and runs it natively WITHOUT any authenticated
|
|
@@ -28,8 +26,7 @@
|
|
|
28
26
|
*
|
|
29
27
|
* IP note: the artifact is the pruned runner (control plane physically absent),
|
|
30
28
|
* vended by a short-lived signed URL (`dev_renderer_artifact`) gated on the same
|
|
31
|
-
* developer entitlement
|
|
32
|
-
* either way.
|
|
29
|
+
* developer entitlement — no hand-provisioned AWS credentials.
|
|
33
30
|
*/
|
|
34
31
|
import { spawn, spawnSync, execFileSync } from "node:child_process";
|
|
35
32
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync, rmSync, readdirSync, createWriteStream, openSync, closeSync, writeSync } from "node:fs";
|
|
@@ -55,7 +52,7 @@ import { streamDevLogs } from "../dev-logs.mjs";
|
|
|
55
52
|
import { cockpitUrlFrom } from "../banner.mjs";
|
|
56
53
|
import { recordDiagnostic } from "../diagnostics.mjs";
|
|
57
54
|
|
|
58
|
-
|
|
55
|
+
// Kept for the internal `tot start` migration path. `tot dev` itself is native-only.
|
|
59
56
|
const DEFAULT_DEV_IMAGE =
|
|
60
57
|
"242086487598.dkr.ecr.us-east-1.amazonaws.com/tot-dev:latest";
|
|
61
58
|
const DEFAULT_MCP_URL = "https://mcp.tokenoftrust.com";
|
|
@@ -67,19 +64,17 @@ const DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
|
|
|
67
64
|
|
|
68
65
|
export function parseArgs(argv) {
|
|
69
66
|
const a = {
|
|
70
|
-
workspace: null, port: "4321",
|
|
71
|
-
noLogin: false, noOpen: false,
|
|
67
|
+
workspace: null, port: "4321", mcp: null,
|
|
68
|
+
noLogin: false, noOpen: false, sample: false, help: false,
|
|
72
69
|
rendererVersion: null,
|
|
73
70
|
};
|
|
74
71
|
for (let i = 0; i < argv.length; i++) {
|
|
75
72
|
const t = argv[i];
|
|
76
73
|
if (t === "--workspace") a.workspace = argv[++i];
|
|
77
74
|
else if (t === "--port") a.port = argv[++i];
|
|
78
|
-
else if (t === "--image") a.image = argv[++i];
|
|
79
75
|
else if (t === "--mcp") a.mcp = argv[++i];
|
|
80
76
|
else if (t === "--no-login") a.noLogin = true;
|
|
81
77
|
else if (t === "--no-open") a.noOpen = true;
|
|
82
|
-
else if (t === "--docker") a.docker = true;
|
|
83
78
|
else if (t === "--sample") a.sample = true;
|
|
84
79
|
else if (t === "--renderer-version") a.rendererVersion = argv[++i];
|
|
85
80
|
else if (t === "--help" || t === "-h") a.help = true;
|
|
@@ -95,8 +90,6 @@ const USAGE = `tot dev — run your store locally with save→reload
|
|
|
95
90
|
no MCP, no account (the free first taste)
|
|
96
91
|
tot dev --workspace <dir> run a specific checkout directory
|
|
97
92
|
tot dev --port <n> host port (default 4321)
|
|
98
|
-
tot dev --docker use the Docker runner image instead of the native path
|
|
99
|
-
tot dev --image <ref> runner image ref, only with --docker (default: the published image)
|
|
100
93
|
tot dev --no-open don't auto-open the browser when the server is up
|
|
101
94
|
|
|
102
95
|
Edit content/*.html or the theme + save → the browser reloads. Private local
|
|
@@ -128,8 +121,7 @@ export async function run(argv, ctx) {
|
|
|
128
121
|
return runMonorepo(ctx, argv);
|
|
129
122
|
}
|
|
130
123
|
|
|
131
|
-
// Standalone checkout (or an explicit --workspace anywhere): native
|
|
132
|
-
// Docker on request (--docker) or as an automatic fallback (see runStandalone).
|
|
124
|
+
// Standalone checkout (or an explicit --workspace anywhere): native runtime.
|
|
133
125
|
const workspace = args.workspace
|
|
134
126
|
? resolve(args.workspace)
|
|
135
127
|
: ctx.mode === "checkout"
|
|
@@ -148,31 +140,20 @@ export async function run(argv, ctx) {
|
|
|
148
140
|
}
|
|
149
141
|
|
|
150
142
|
/**
|
|
151
|
-
* Standalone-checkout entrypoint: native
|
|
152
|
-
*
|
|
153
|
-
* fallback when the native artifact can't be fetched (e.g. the MCP's
|
|
154
|
-
* dev_renderer_artifact isn't configured on this deployment yet) — so `tot dev`
|
|
155
|
-
* keeps working instead of hard-failing.
|
|
143
|
+
* Standalone-checkout entrypoint: run the native renderer. When the entitled
|
|
144
|
+
* artifact is unavailable, use the public runner package on the same release line.
|
|
156
145
|
*/
|
|
157
146
|
async function runStandalone(workspace, args, ctx) {
|
|
158
|
-
if (args.docker) return runContainer(workspace, args, ctx);
|
|
159
147
|
try {
|
|
160
148
|
return await runNative(workspace, args, ctx);
|
|
161
149
|
} catch (e) {
|
|
162
150
|
if (e instanceof NativeArtifactUnavailableError) {
|
|
163
151
|
// Entitled artifact unavailable — stay NATIVE on the public npm runner
|
|
164
152
|
// (@tokenoftrust/storefront-runner, the SAME renderer bits), so
|
|
165
|
-
// `tot dev` never needs Docker just because dev_renderer_artifact isn't
|
|
166
|
-
// configured. Docker is the last resort only if the public runner is also
|
|
167
|
-
// unreachable (offline).
|
|
168
153
|
try {
|
|
169
|
-
console.error(`~ couldn't set up your store preview the usual way (${e.message}) — using the
|
|
154
|
+
console.error(`~ couldn't set up your store preview the usual way (${e.message}) — using the public preview engine instead.`);
|
|
170
155
|
return await runNativePublic(workspace, args, ctx);
|
|
171
156
|
} catch (e2) {
|
|
172
|
-
if (e2 instanceof NativeArtifactUnavailableError) {
|
|
173
|
-
console.error(`~ couldn't reach the fallback preview engine either (${e2.message}) — switching to the Docker runner.`);
|
|
174
|
-
return runContainer(workspace, args, ctx);
|
|
175
|
-
}
|
|
176
157
|
console.error(formatError(e2));
|
|
177
158
|
return e2 instanceof CliError ? (e2.exitCode ?? 2) : 2;
|
|
178
159
|
}
|
|
@@ -189,7 +170,7 @@ async function runStandalone(workspace, args, ctx) {
|
|
|
189
170
|
* ensureSampleRenderer (env override → in-tree monorepo → version-pinned public
|
|
190
171
|
* npm, reusing the matching `public-<version>` cache), which throws
|
|
191
172
|
* NativeArtifactUnavailableError when even the public runner can't be fetched
|
|
192
|
-
* (offline) so the caller can
|
|
173
|
+
* (offline) so the caller can report the unavailable local preview.
|
|
193
174
|
*/
|
|
194
175
|
async function runNativePublic(workspace, args, ctx) {
|
|
195
176
|
const cfg = readWorkspaceConfig(workspace);
|
package/src/commands/submit.mjs
CHANGED
|
@@ -769,9 +769,9 @@ export function parseNameStatus(text) {
|
|
|
769
769
|
}
|
|
770
770
|
|
|
771
771
|
/**
|
|
772
|
-
* Derive the g1b forge repo name (
|
|
772
|
+
* Derive the g1b forge repo name (the bare tenant domain, e.g. `"acme.com"`)
|
|
773
773
|
* from the checkout's authenticated origin remote — the SAME name
|
|
774
|
-
* `tenant_checkout`
|
|
774
|
+
* `tenant_checkout` resolved, so no separate lookup or stored tag is
|
|
775
775
|
* needed. Returns null when the remote URL can't be parsed (candidate_open is
|
|
776
776
|
* then skipped, reported, never crashed). Pure — unit-tested.
|
|
777
777
|
* @param {string} remoteUrl
|
|
@@ -812,11 +812,10 @@ export function isForgeAuthError(text) {
|
|
|
812
812
|
}
|
|
813
813
|
|
|
814
814
|
/**
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
* unit-tested.
|
|
815
|
+
* Retain compatibility with historical `"<tenant>-<tag>"` repos when re-minting a
|
|
816
|
+
* checkout credential. Current repos use the bare tenant domain and therefore
|
|
817
|
+
* resolve to the compatibility default `main`; the server ignores that tag for
|
|
818
|
+
* current repo naming. Pure — unit-tested.
|
|
820
819
|
* @param {string|null} repoName
|
|
821
820
|
* @param {string} tenant
|
|
822
821
|
* @returns {string}
|