@tokenoftrust/cli 2.2.0 → 2.2.2

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.2.0",
3
+ "version": "2.2.2",
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",
@@ -342,6 +342,27 @@ async function runReadOnlyAlias(alias, args, { env, createClient, establish, sig
342
342
  }
343
343
  }
344
344
 
345
+ /**
346
+ * Where an invited developer whose store does not resolve gets the exact broken step: the
347
+ * owner's Team page "Check access" (tot-mcp access_explain), which walks their sign-in and
348
+ * checkout chain and names the first failure with its fix.
349
+ * @param {string} tenant
350
+ */
351
+ export const ACCESS_EXPLAIN_HINT = (tenant) =>
352
+ `To see exactly which step breaks, ask the owner of ${tenant} to click "Check access" ` +
353
+ "next to your name on their Team page (or run the tot MCP tool access_explain).";
354
+
355
+ /**
356
+ * Does tenant_checkout's own denial send the caller to the entitlement plane (no grant or
357
+ * client context resolved for the tenant), as opposed to a staff-preview authorization?
358
+ * Read from the structured fields tenant_checkout sets, never from message text.
359
+ * @param {unknown} checkout
360
+ */
361
+ export function checkoutPointsAtEntitlement(checkout) {
362
+ const c = /** @type {any} */ (checkout);
363
+ return c?.status === "not_entitled" || c?.nextAction === "grant_introspect";
364
+ }
365
+
345
366
  /**
346
367
  * Is this client_switch failure the EXPECTED "that tenant is not one of your clients"
347
368
  * refusal, as opposed to a broken session (transport, auth, malformed envelope, internal
@@ -446,7 +467,8 @@ export async function checkoutTenant(client, { tenant, tag = "main", cloneDir =
446
467
  throw new CliError(redact(String(expectedSwitchRefusal?.message || expectedSwitchRefusal)), {
447
468
  next:
448
469
  "confirm you're entitled to this store — `tot grants` lists what your session can " +
449
- "act on, and `tot login` refreshes a grant issued after you signed in",
470
+ "act on, and `tot login` refreshes a grant issued after you signed in. " +
471
+ ACCESS_EXPLAIN_HINT(tenant),
450
472
  cause: expectedSwitchRefusal,
451
473
  });
452
474
  }
@@ -460,7 +482,18 @@ export async function checkoutTenant(client, { tenant, tag = "main", cloneDir =
460
482
  if (err) {
461
483
  // AUTHORITATIVE: the checkout rendered a decision, so it is the answer — even when the
462
484
  // switch also refused. Substituting grant guidance here is what would tell someone whose
463
- // staff authorization had EXPIRED to go looking at their grants.
485
+ // staff authorization had EXPIRED to go looking at their grants. Only when the checkout's
486
+ // OWN denial points at entitlement does the switch refusal ride below it: for an invited
487
+ // developer it is the one that names the cause (the store is not among the tenants their
488
+ // session resolved), and dropping it left only a downstream "Over-scoped" that reads like
489
+ // a selection error.
490
+ if (expectedSwitchRefusal && checkoutPointsAtEntitlement(checkout)) {
491
+ const switchMessage = String(expectedSwitchRefusal?.message || expectedSwitchRefusal);
492
+ throw new CliError(
493
+ redact(`${err.message}\n client_switch also refused: ${switchMessage}`),
494
+ { next: `${err.next} ${ACCESS_EXPLAIN_HINT(tenant)}`, cause: expectedSwitchRefusal },
495
+ );
496
+ }
464
497
  throw new CliError(redact(err.message), { next: err.next });
465
498
  }
466
499
  const gitRemote = checkout.gitRemote;
@@ -84,7 +84,7 @@
84
84
  <p>A fictional demo store for the Token of Trust local dev loop.<br>Not a real merchant — sample content only.</p>
85
85
  </div>
86
86
  <div class="footer-col">
87
- <h4>Shop</h4>
87
+ <p class="footer-col-title">Shop</p>
88
88
  <ul>
89
89
  <li><a href="/collections/e-liquid">E-Liquid</a></li>
90
90
  <li><a href="/collections/salt-nic">Salt Nic</a></li>
@@ -94,7 +94,7 @@
94
94
  </ul>
95
95
  </div>
96
96
  <div class="footer-col">
97
- <h4>Support</h4>
97
+ <p class="footer-col-title">Support</p>
98
98
  <ul>
99
99
  <li><a href="/search">Search</a></li>
100
100
  <li><a href="/pages/about-us">About Us</a></li>
@@ -103,7 +103,7 @@
103
103
  </ul>
104
104
  </div>
105
105
  <div class="footer-col">
106
- <h4>Company</h4>
106
+ <p class="footer-col-title">Company</p>
107
107
  <ul>
108
108
  <li><a href="/pages/shipping-returns">Shipping &amp; Returns</a></li>
109
109
  <li><a href="/pages/privacy-policy">Privacy Policy</a></li>
@@ -60,7 +60,7 @@
60
60
  .footer-news input { flex: 1; border: 1px solid rgba(255,255,255,.3); background: transparent; color: #fff; border-radius: 999px; padding: 10px 16px; font-size: .9rem; }
61
61
  .footer-news input::placeholder { color: #a9b8e6; }
62
62
  .footer-news button { background: #fff; color: var(--navy-ink); border: 0; border-radius: 999px; width: 42px; font-size: 1.1rem; font-weight: 700; cursor: pointer; }
63
- .footer-col > p { color: #a9b8e6; font-size: .88rem; margin-top: 4px; }
63
+ .footer-col > p:not(.footer-col-title) { color: #a9b8e6; font-size: .88rem; margin-top: 4px; }
64
64
 
65
65
  /* --- Payment badges ------------------------------------------------------- */
66
66
  .pay-badges { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
@@ -147,7 +147,7 @@
147
147
  .footer-grid { display: grid; grid-template-columns: 1.4fr repeat(3, 1fr); gap: clamp(24px, 4vw, 56px); }
148
148
  .footer-brand img { height: 40px; filter: brightness(0) invert(1); opacity: .95; }
149
149
  .footer-brand p { color: #a9b8e6; margin-top: 14px; font-size: .9rem; max-width: 34ch; }
150
- .footer-col h4 { color: #fff; font-size: .82rem; letter-spacing: .1em; text-transform: uppercase; margin-bottom: 14px; }
150
+ .footer-col .footer-col-title { color: #fff; font-size: .82rem; font-weight: bold; letter-spacing: .1em; text-transform: uppercase; margin: 0 0 14px; }
151
151
  .footer-col ul { display: flex; flex-direction: column; gap: 9px; }
152
152
  .footer-col a { color: #c3cff0; font-size: .92rem; }
153
153
  .footer-col a:hover { color: #fff; text-decoration: underline; }