@tokenoftrust/cli 1.3.0-rc.3 → 1.3.0-rc.4

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": "1.3.0-rc.3",
3
+ "version": "1.3.0-rc.4",
4
4
  "description": "Token of Trust developer CLI — check out 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/auth.mjs CHANGED
@@ -119,6 +119,7 @@ export async function resolveDeveloperSession(client, env, deps = {}) {
119
119
  { hint: "run `tot login` to sign in again." },
120
120
  );
121
121
  }
122
+ const prior = creds;
122
123
  creds = credentialsFromToken({
123
124
  mcpUrl: creds.mcpUrl,
124
125
  clientId: creds.clientId,
@@ -128,6 +129,15 @@ export async function resolveDeveloperSession(client, env, deps = {}) {
128
129
  token: { ...refreshed, refresh_token: refreshed.refresh_token || creds.refreshToken },
129
130
  now,
130
131
  });
132
+ // credentialsFromToken only returns the OAuth shape — it knows nothing about
133
+ // activityToken/activityUrl, a SEPARATE credential (storefront-issued, cached
134
+ // by `tot login --code`; see token-store.mjs). Carry it across a silent
135
+ // refresh, or a routine near-expiry refresh permanently and silently kills
136
+ // the activity bridge (the invite code that could re-mint it is single-use).
137
+ if (prior.activityToken && prior.activityUrl) {
138
+ creds.activityToken = prior.activityToken;
139
+ creds.activityUrl = prior.activityUrl;
140
+ }
131
141
  writeCredentials(path, creds);
132
142
  }
133
143
 
@@ -240,7 +240,7 @@ function runMonorepo(ctx, argv) {
240
240
  console.error(`✗ expected the dev runner at ${script} but it's missing.`);
241
241
  return 2;
242
242
  }
243
- const env = { ...process.env, ...activityBridgeEnv() };
243
+ const env = { ...process.env, ...activityBridgeEnv(process.env) };
244
244
  return new Promise((resolvePromise) => {
245
245
  const child = spawn(process.execPath, [script, ...argv], { stdio: "inherit", env });
246
246
  child.on("exit", (code) => resolvePromise(code ?? 0));
@@ -289,10 +289,24 @@ async function runNative(workspace, args, ctx) {
289
289
  * promise. Shared by the authenticated native path (runNative) and the
290
290
  * zero-login sample path (runSample) so both boot identically.
291
291
  */
292
- function bootNative(runnerDir, workspace, port, url, args) {
293
- // activityBridgeEnv() is a no-op {} for the zero-login sample path (no cached
294
- // credential exists there) safe to always thread it through.
295
- const handle = spawnNativeDev(runnerDir, workspace, port, { stdio: "inherit", env: activityBridgeEnv() });
292
+ /**
293
+ * The env bootNative threads into the spawned runner. NEVER thread a real
294
+ * developer's activity-bridge credential into the zero-login --sample run:
295
+ * activityBridgeEnv() reads ~/.tot/credentials.json unconditionally, so a
296
+ * developer who's ever run `tot login --code` and then runs `tot dev --sample`
297
+ * (the "free taste"/demo path) would otherwise leak their real credential,
298
+ * posting fake sample-store activity to their real hosted /dev panel —
299
+ * contradicting the sample banner's "no login, no ToT account, nothing
300
+ * published" claim. Exported + pure (besides the credentials-file read) so
301
+ * this gate is testable without spawning a real process.
302
+ * @param {{ sample?: boolean }} args
303
+ */
304
+ export function bootNativeEnv(args) {
305
+ return args.sample ? {} : activityBridgeEnv();
306
+ }
307
+
308
+ export function bootNative(runnerDir, workspace, port, url, args) {
309
+ const handle = spawnNativeDev(runnerDir, workspace, port, { stdio: "inherit", env: bootNativeEnv(args) });
296
310
 
297
311
  // Auto-open the browser the moment the server answers (D). Non-blocking so
298
312
  // Ctrl-C / logs are unaffected; --no-open suppresses it.
@@ -100,8 +100,9 @@ export async function loginAndCache(mcpUrl, env = process.env, { log = () => {},
100
100
  creds = await deviceLoginFlow({ mcpUrl, clientId, log });
101
101
  }
102
102
  }
103
- writeCredentials(path, creds);
104
- return creds;
103
+ const merged = mergeActivityBridge(prior, mcpUrl, creds);
104
+ writeCredentials(path, merged);
105
+ return merged;
105
106
  }
106
107
 
107
108
  /**
@@ -115,7 +116,24 @@ export async function redeemAndCache(mcpUrl, code, env = process.env) {
115
116
  const prior = readCredentials(path);
116
117
  const clientId = prior && prior.mcpUrl === mcpUrl ? prior.clientId : undefined;
117
118
  const creds = await redeemCodeFlow({ mcpUrl, clientId, code });
118
- writeCredentials(path, creds);
119
+ const merged = mergeActivityBridge(prior, mcpUrl, creds);
120
+ writeCredentials(path, merged);
121
+ return merged;
122
+ }
123
+
124
+ /**
125
+ * `loginFlow`/`deviceLoginFlow`/`redeemCodeFlow` all return the bare OAuth shape —
126
+ * none of them know about activityToken/activityUrl, a SEPARATE credential this
127
+ * same file caches via cacheActivityBridge. A bare re-login (no --code) after a
128
+ * prior --code sign-in would otherwise silently drop it on the next `writeCredentials`
129
+ * (a full-object overwrite, not a merge) — carry it forward when re-authing against
130
+ * the SAME mcpUrl (a different MCP means a different session; the old bridge
131
+ * credential no longer applies).
132
+ */
133
+ export function mergeActivityBridge(prior, mcpUrl, creds) {
134
+ if (prior?.mcpUrl === mcpUrl && prior.activityToken && prior.activityUrl) {
135
+ return { ...creds, activityToken: prior.activityToken, activityUrl: prior.activityUrl };
136
+ }
119
137
  return creds;
120
138
  }
121
139
 
@@ -436,7 +436,12 @@ async function prefetchRuntime(client, devArgs, env, runtime, ctx) {
436
436
  console.log(` ~ entitled renderer unavailable (${e.message}) — using the public runner (no Docker).`);
437
437
  runtime.runnerDir = await ensureSampleRenderer(devArgs, ctx, { env });
438
438
  } catch (e2) {
439
- console.log(` ~ public runner unavailable (${e2?.message || e2}) falling back to the Docker runner.`);
439
+ // Only an actual "can't reach the public runner" failure should fall to
440
+ // Docker — anything else (a real bug in ensureSampleRenderer/pickRunnerVersion,
441
+ // say) would otherwise be silently masked behind a confusing Docker fallback
442
+ // that may not even be installed. Mirrors dev.mjs's runStandalone.
443
+ if (!(e2 instanceof NativeArtifactUnavailableError)) throw e2;
444
+ console.log(` ~ public runner unavailable (${e2.message}) — falling back to the Docker runner.`);
440
445
  runtime.useDocker = true;
441
446
  await prefetchDockerLogin(client, devArgs, env);
442
447
  }