@tokenoftrust/cli 2.4.8 → 2.4.9
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.9",
|
|
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",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The next step to print when the storefront refuses a call with 401/403.
|
|
3
|
+
*
|
|
4
|
+
* The server's admission rule (apps/storefront lib/pipeline/admission.ts) names exactly what is
|
|
5
|
+
* missing in `admission.missing[].grant`, and its reason is already in the refusal's `error`.
|
|
6
|
+
* The hint follows that answer instead of restating a policy of its own: a client copy of the
|
|
7
|
+
* rule goes stale — every refusal here used to say "you need a live ship-on-behalf grant", which
|
|
8
|
+
* is wrong for an unpaid store, where an invited developer needs no grant at all.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** The grant names a refusal body reports missing, or `[]` when it names none. */
|
|
12
|
+
export function missingGrantsOf(body) {
|
|
13
|
+
const missing = body && typeof body === "object" ? body.admission?.missing : undefined;
|
|
14
|
+
return Array.isArray(missing)
|
|
15
|
+
? missing.map((m) => m?.grant).filter((g) => typeof g === "string")
|
|
16
|
+
: [];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** One actionable next step for a refused call, from the grants the server named. */
|
|
20
|
+
export function refusalHint(status, missingGrants = []) {
|
|
21
|
+
if (status === 401 || missingGrants.includes("session")) return "run `tot login`, then re-run";
|
|
22
|
+
if (missingGrants.includes("tenant-binding")) {
|
|
23
|
+
return "your sign-in is for a different store — check --tenant, then re-run";
|
|
24
|
+
}
|
|
25
|
+
if (missingGrants.includes("tenant-standing")) {
|
|
26
|
+
return "ask the store owner to invite you to this store from its Team page, then re-run";
|
|
27
|
+
}
|
|
28
|
+
if (missingGrants.includes("ship-on-behalf")) {
|
|
29
|
+
return "this store's live site is active, so ask the store owner for a ship-on-behalf grant from its Team page, then re-run";
|
|
30
|
+
}
|
|
31
|
+
if (missingGrants.includes("owner-standing")) return "only the store owner can do this";
|
|
32
|
+
if (missingGrants.includes("console-capability")) {
|
|
33
|
+
return "the operator secret is acting with preview-access — set X-Tot-Capability: ship-on-behalf (or ACCEPT_OPERATOR_CAPABILITY), then re-run";
|
|
34
|
+
}
|
|
35
|
+
return "ask the store owner for access to this store — or use an operator secret authorised for it";
|
|
36
|
+
}
|
package/src/commands/accept.mjs
CHANGED
|
@@ -47,8 +47,9 @@ import { createMcpClient } from "../mcp.mjs";
|
|
|
47
47
|
import { establishSession, AuthUnavailableError } from "../auth.mjs";
|
|
48
48
|
import { scopeToTenant } from "../tenant-scope.mjs";
|
|
49
49
|
// No-operator-secret path: mint a viewer session from the developer's OWN `tot`
|
|
50
|
-
// login and integrate as themselves (server
|
|
50
|
+
// login and integrate as themselves (the server's admission rule decides).
|
|
51
51
|
import { resolveViewerTransport } from "../viewer-session.mjs";
|
|
52
|
+
import { missingGrantsOf, refusalHint } from "../admission-hint.mjs";
|
|
52
53
|
// The doctor PUSH: on a failed accept/integrate, auto-append the COMPACT
|
|
53
54
|
// "here's what's blocking you" summary over the SAME transport we just used, so a
|
|
54
55
|
// developer doesn't have to remember to run `tot preview doctor` themselves.
|
|
@@ -529,7 +530,7 @@ export async function runIntegrate(
|
|
|
529
530
|
// - OPERATOR SECRET (operators/CI): Bearer + X-Tot-Owner on the generic host.
|
|
530
531
|
// - VIEWER SESSION (an invited developer, no secret): mint a `tot_session` from
|
|
531
532
|
// their OWN `tot` login on the TENANT'S host and send it as a cookie. The
|
|
532
|
-
// server
|
|
533
|
+
// server's admission rule authorizes either way.
|
|
533
534
|
// No `content-type` here: it's added per-POST; the mergeable preflight GET wants none.
|
|
534
535
|
let authHeaders;
|
|
535
536
|
if (secret) {
|
|
@@ -688,7 +689,7 @@ function reportIntegrateHttpError(result, res, { tenant, label }) {
|
|
|
688
689
|
fail(
|
|
689
690
|
`the integration queue refused the request: ${msg}`,
|
|
690
691
|
res.status === 401 || res.status === 403
|
|
691
|
-
?
|
|
692
|
+
? refusalHint(res.status, missingGrantsOf(result.raw))
|
|
692
693
|
: res.status === 404
|
|
693
694
|
? `check that ${label} has a built candidate in ${tenant}'s queue (\`tot pr list --tenant ${tenant}\`)`
|
|
694
695
|
: "check --tenant / --url / --pr, then re-run",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
*/
|
|
39
39
|
import { fail } from "../errors.mjs";
|
|
40
40
|
import { resolveOperatorSecret } from "./ship.mjs";
|
|
41
|
+
import { missingGrantsOf, refusalHint } from "../admission-hint.mjs";
|
|
41
42
|
import { resolveViewerTransport } from "../viewer-session.mjs";
|
|
42
43
|
import {
|
|
43
44
|
DOCTOR_PATH,
|
|
@@ -165,7 +166,7 @@ export async function runDoctor(
|
|
|
165
166
|
console.error(
|
|
166
167
|
fail(
|
|
167
168
|
`not authorized to diagnose ${tenant}'s merges: ${data?.error || `HTTP ${res.status}`}`,
|
|
168
|
-
|
|
169
|
+
refusalHint(res.status, missingGrantsOf(data)),
|
|
169
170
|
),
|
|
170
171
|
);
|
|
171
172
|
return 2;
|
package/src/commands/revert.mjs
CHANGED
|
@@ -45,8 +45,9 @@ import { planForAction, printPlanAndConfirm } from "../plan.mjs";
|
|
|
45
45
|
// speak ONE operator-auth contract.
|
|
46
46
|
import { resolveOperatorSecret } from "./ship.mjs";
|
|
47
47
|
// No-operator-secret path: mint a viewer session from the developer's OWN `tot`
|
|
48
|
-
// login. The server still gates the request
|
|
48
|
+
// login. The server's admission rule still gates the request.
|
|
49
49
|
import { resolveViewerTransport } from "../viewer-session.mjs";
|
|
50
|
+
import { missingGrantsOf, refusalHint } from "../admission-hint.mjs";
|
|
50
51
|
import { isJobAccepted, waitForPipelineJob } from "../pipeline-job.mjs";
|
|
51
52
|
|
|
52
53
|
const DEFAULT_STOREFRONT_URL = "https://storefront.tokenoftrust.store";
|
|
@@ -337,7 +338,7 @@ export async function runRevert(
|
|
|
337
338
|
fail(
|
|
338
339
|
`the revert request was refused: ${msg}`,
|
|
339
340
|
res.status === 401 || res.status === 403
|
|
340
|
-
?
|
|
341
|
+
? refusalHint(res.status, missingGrantsOf(data))
|
|
341
342
|
: "check --tenant / --url / --preview, then re-run",
|
|
342
343
|
),
|
|
343
344
|
);
|
package/src/commands/ship.mjs
CHANGED
|
@@ -47,7 +47,8 @@
|
|
|
47
47
|
*
|
|
48
48
|
* TRANSPORT: operators/CI may use a Bearer operator secret. A signed-in owner or
|
|
49
49
|
* developer instead mints a storefront viewer session from their OWN `tot login`
|
|
50
|
-
* identity; the server
|
|
50
|
+
* identity; the server's admission rule decides who may ship (owner; a live
|
|
51
|
+
* `ship-on-behalf` grant; or, while the store is unpaid, any developer invited to it).
|
|
51
52
|
*/
|
|
52
53
|
import { createHash } from "node:crypto";
|
|
53
54
|
import { fail } from "../errors.mjs";
|
|
@@ -76,10 +77,10 @@ const USAGE = `tot ship — publish the tenant's CURRENT GREEN AGGREGATE to live
|
|
|
76
77
|
record the required reason, then continue publishing
|
|
77
78
|
tot ship --no-open don't open the live URL in your browser
|
|
78
79
|
|
|
79
|
-
Authentication:
|
|
80
|
-
|
|
81
|
-
secret via
|
|
82
|
-
TOT_OPERATOR_SECRET, or --secret.
|
|
80
|
+
Authentication: use your own \`tot login\` session if you own the store, hold
|
|
81
|
+
a live ship-on-behalf grant, or — while the store is unpaid — were invited to
|
|
82
|
+
it as a developer. Operators/CI may instead provide a secret via
|
|
83
|
+
PREVIEW_RECONCILE_SECRET, GRANTS_ADMIN_SECRET, TOT_OPERATOR_SECRET, or --secret.
|
|
83
84
|
|
|
84
85
|
\`tot ship\` has ONE meaning: it publishes the tenant's shared preview
|
|
85
86
|
aggregate — the batch of PRs that integrated cleanly and went GREEN — to
|
package/src/viewer-session.mjs
CHANGED
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
* a storefront VIEWER session from the developer's OWN MCP token (POST
|
|
7
7
|
* /api/dev/cli-session on the tenant's own host, which resolves WHO the opaque token
|
|
8
8
|
* is via the MCP and hands back a `tot_session` cookie), then hands the caller a
|
|
9
|
-
* cookie-based transport pointed at the tenant host. Authorization is
|
|
10
|
-
*
|
|
11
|
-
* this only carries their identity, it
|
|
9
|
+
* cookie-based transport pointed at the tenant host. Authorization is the server's
|
|
10
|
+
* admission rule at the ship route (owner; a live `ship-on-behalf` grant; or, while the
|
|
11
|
+
* store is unpaid, any developer invited to it) — this only carries their identity, it
|
|
12
|
+
* grants nothing.
|
|
12
13
|
*
|
|
13
14
|
* Minted FRESH per call (no disk cache): a `tot accept` is interactive + infrequent,
|
|
14
15
|
* and minting-per-call means a revoked session/grant is never honored past its life.
|