@tokenoftrust/cli 4.7.4 → 4.7.5

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": "4.7.4",
3
+ "version": "4.7.5",
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",
@@ -13,7 +13,7 @@
13
13
  * `--json` prints `{ token, exp, ttlSeconds, tenantId, previewVersion }` instead.
14
14
  * Treat the token like a password: anyone holding it sees the unpublished site.
15
15
  */
16
- import { resolveViewerTransport } from "../viewer-session.mjs";
16
+ import { resolveViewerTransport, describeIdentity } from "../viewer-session.mjs";
17
17
  import { missingGrantsOf, refusalHint } from "../admission-hint.mjs";
18
18
 
19
19
  const MAX_TTL_SECONDS = 24 * 3600;
@@ -115,8 +115,10 @@ export async function run(argv, ctx, deps = {}) {
115
115
  /* non-JSON error body — this endpoint answers 401/403 as plain text */
116
116
  }
117
117
  if (res.status === 401 || res.status === 403) {
118
+ const checked = describeIdentity(transport.identity);
118
119
  err(
119
- `✗ your account can't view ${tenant}'s preview.\n\n` +
120
+ `✗ your account can't view ${tenant}'s preview.\n` +
121
+ ` checked: ${checked ?? "your `tot login` session"} at ${transport.base}\n\n` +
120
122
  ` → next: ${refusalHint(res.status, missingGrantsOf(data))}.`,
121
123
  );
122
124
  return 1;
@@ -133,7 +133,8 @@ export function describeCliSessionRefusal(status, body, signedInMcpUrl) {
133
133
 
134
134
  /**
135
135
  * Resolve a viewer-session transport for `tenant`. Returns
136
- * { ok:true, base, authHeaders } — base = tenant host; cookie transport
136
+ * { ok:true, base, identity, authHeaders } — base = tenant host; cookie transport;
137
+ * identity = { account (masked), mcp origin }
137
138
  * { ok:false, message, hint } — a clean, actionable refusal
138
139
  * Never throws.
139
140
  *
@@ -144,7 +145,8 @@ export function describeCliSessionRefusal(status, body, signedInMcpUrl) {
144
145
  * storefrontUrl?:string|null, resolveDev?:typeof resolveDeveloperSession,
145
146
  * verbose?:boolean, log?:(s:string)=>void }} params
146
147
  * @returns {Promise<
147
- * { ok:true, base:string, authHeaders:Record<string,string> } |
148
+ * { ok:true, base:string, identity:{ account:string|null, mcp:string|null },
149
+ * authHeaders:Record<string,string> } |
148
150
  * { ok:false, message:string, hint:string }
149
151
  * >}
150
152
  */
@@ -231,6 +233,7 @@ export async function resolveViewerTransport({
231
233
  };
232
234
  }
233
235
 
236
+ const minted = await readJson(res);
234
237
  const cookie = parseSessionCookie(res.headers.get("set-cookie"));
235
238
  if (!cookie) {
236
239
  return {
@@ -243,6 +246,10 @@ export async function resolveViewerTransport({
243
246
  return {
244
247
  ok: true,
245
248
  base,
249
+ identity: {
250
+ account: maskAccount(typeof minted?.email === "string" ? minted.email : null),
251
+ mcp: originOf(dev.mcpUrl),
252
+ },
246
253
  authHeaders: {
247
254
  cookie: `${SESSION_COOKIE}=${cookie}`,
248
255
  // Matches the admin browser client; the server IGNORES this hint and re-reads
@@ -252,6 +259,28 @@ export async function resolveViewerTransport({
252
259
  };
253
260
  }
254
261
 
262
+ /** A recognizable stand-in for the signed-in account: first character and domain
263
+ * (`d…@example.com`), never the full address.
264
+ * @param {string|null} email @returns {string|null} */
265
+ export function maskAccount(email) {
266
+ const at = email ? email.lastIndexOf("@") : -1;
267
+ if (!email || at < 1) return null;
268
+ return `${email[0]}…${email.slice(at)}`;
269
+ }
270
+
271
+ /**
272
+ * Which identity and tier the store checked, for a refusal line:
273
+ * `d…@example.com via https://mcp.tokenoftrust.com`.
274
+ * @param {{ account?: string|null, mcp?: string|null }|null|undefined} identity
275
+ * @returns {string|null}
276
+ */
277
+ export function describeIdentity(identity) {
278
+ const account = identity?.account || null;
279
+ const mcp = identity?.mcp || null;
280
+ if (account && mcp) return `${account} via ${mcp}`;
281
+ return account || (mcp ? `your ${mcp} sign-in` : null);
282
+ }
283
+
255
284
  /** The refusal body, or null when the body is not JSON (describeCliSessionRefusal
256
285
  * falls back cleanly). Any other failure — a body already consumed, a stream
257
286
  * error — is a bug or a fault, not "no body", and propagates.