@tokenoftrust/cli 1.3.0-rc.2 → 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 +1 -1
- package/src/auth.mjs +10 -0
- package/src/commands/dev.mjs +56 -15
- package/src/commands/login.mjs +21 -3
- package/src/commands/start.mjs +8 -3
- package/src/token-store.mjs +3 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokenoftrust/cli",
|
|
3
|
-
"version": "1.3.0-rc.
|
|
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
|
|
package/src/commands/dev.mjs
CHANGED
|
@@ -205,23 +205,42 @@ async function runNativePublic(workspace, args, ctx) {
|
|
|
205
205
|
return bootNative(runnerDir, workspace, port, url, args);
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
/**
|
|
209
|
+
* Read the hosted-activity-bridge credential (minted alongside the invite's
|
|
210
|
+
* browserless `tot login --code` paste — see login.mjs's cacheActivityBridge)
|
|
211
|
+
* and shape it as the two env vars dev-loop-state.mjs's postActivity reads.
|
|
212
|
+
* Returns {} when absent (an older cached session, a bare `tot login`, or the
|
|
213
|
+
* zero-login --sample path) — the local loop runs exactly the same either way,
|
|
214
|
+
* it just has no hosted signal to report. EVERY path that spawns the runner
|
|
215
|
+
* (native monorepo, native standalone, Docker) must thread this in, or the
|
|
216
|
+
* developer's hosted /dev panel never lights up even though they cached a
|
|
217
|
+
* valid bridge credential at login.
|
|
218
|
+
* @param {NodeJS.ProcessEnv} [env]
|
|
219
|
+
* @returns {{TOT_DEV_ACTIVITY_TOKEN?: string, TOT_DEV_ACTIVITY_URL?: string}}
|
|
220
|
+
*/
|
|
221
|
+
export function activityBridgeEnv(env = process.env) {
|
|
222
|
+
const creds = readCredentials(defaultCredentialsPath(env));
|
|
223
|
+
if (!creds?.activityToken || !creds?.activityUrl) return {};
|
|
224
|
+
return { TOT_DEV_ACTIVITY_TOKEN: creds.activityToken, TOT_DEV_ACTIVITY_URL: creds.activityUrl };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Flatten an env object into repeated `-e KEY=VAL` docker-run args. Pure, so the
|
|
229
|
+
* Docker activity-bridge threading is testable without a real Docker daemon.
|
|
230
|
+
* @param {Record<string, string>} env
|
|
231
|
+
* @returns {string[]}
|
|
232
|
+
*/
|
|
233
|
+
export function dockerEnvArgs(env) {
|
|
234
|
+
return Object.entries(env).flatMap(([k, v]) => ["-e", `${k}=${v}`]);
|
|
235
|
+
}
|
|
236
|
+
|
|
208
237
|
function runMonorepo(ctx, argv) {
|
|
209
238
|
const script = join(ctx.repoRoot, "scripts", "tot-dev.mjs");
|
|
210
239
|
if (!existsSync(script)) {
|
|
211
240
|
console.error(`✗ expected the dev runner at ${script} but it's missing.`);
|
|
212
241
|
return 2;
|
|
213
242
|
}
|
|
214
|
-
|
|
215
|
-
// minted alongside the invite's cli-signin-code paste) so the spawned tot-dev.mjs
|
|
216
|
-
// can report file-save activity up to the developer's own hosted /dev panel.
|
|
217
|
-
// Best-effort: an older cached session (or a bare `tot login`) simply has
|
|
218
|
-
// neither field, and the local loop runs exactly as before with no hosted signal.
|
|
219
|
-
const creds = readCredentials(defaultCredentialsPath(process.env));
|
|
220
|
-
const env = { ...process.env };
|
|
221
|
-
if (creds?.activityToken && creds?.activityUrl) {
|
|
222
|
-
env.TOT_DEV_ACTIVITY_TOKEN = creds.activityToken;
|
|
223
|
-
env.TOT_DEV_ACTIVITY_URL = creds.activityUrl;
|
|
224
|
-
}
|
|
243
|
+
const env = { ...process.env, ...activityBridgeEnv(process.env) };
|
|
225
244
|
return new Promise((resolvePromise) => {
|
|
226
245
|
const child = spawn(process.execPath, [script, ...argv], { stdio: "inherit", env });
|
|
227
246
|
child.on("exit", (code) => resolvePromise(code ?? 0));
|
|
@@ -270,8 +289,24 @@ async function runNative(workspace, args, ctx) {
|
|
|
270
289
|
* promise. Shared by the authenticated native path (runNative) and the
|
|
271
290
|
* zero-login sample path (runSample) so both boot identically.
|
|
272
291
|
*/
|
|
273
|
-
|
|
274
|
-
|
|
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) });
|
|
275
310
|
|
|
276
311
|
// Auto-open the browser the moment the server answers (D). Non-blocking so
|
|
277
312
|
// Ctrl-C / logs are unaffected; --no-open suppresses it.
|
|
@@ -758,17 +793,19 @@ function runPnpmInstall(runnerDir) {
|
|
|
758
793
|
* auto-open-browser logic works unchanged for either runtime. Exported (and
|
|
759
794
|
* `stdio` overridable) so `tot start` can pipe the logs instead of inheriting.
|
|
760
795
|
*/
|
|
761
|
-
export function spawnNativeDev(runnerDir, workspace, port, { stdio = "inherit" } = {}) {
|
|
796
|
+
export function spawnNativeDev(runnerDir, workspace, port, { stdio = "inherit", env = {} } = {}) {
|
|
762
797
|
const script = join(runnerDir, "scripts", "tot-dev.mjs");
|
|
763
798
|
// Translate the same "inherit"|"piped" contract spawnDevContainer honors:
|
|
764
799
|
// "piped" means stdin ignored + stdout/stderr captured so `tot start` can hold
|
|
765
800
|
// the prompt on stdin and stream the logs after the aha. child_process.spawn
|
|
766
801
|
// doesn't understand the bare string "piped", so map it to the array here.
|
|
767
802
|
const stdioArr = stdio === "piped" ? ["ignore", "pipe", "pipe"] : stdio;
|
|
803
|
+
// `env` (e.g. activityBridgeEnv()) merges OVER process.env — {} is a pure
|
|
804
|
+
// passthrough, identical to the old no-env-key behavior.
|
|
768
805
|
const child = spawn(
|
|
769
806
|
process.execPath,
|
|
770
807
|
[script, "--workspace", workspace, "--port", port],
|
|
771
|
-
{ cwd: runnerDir, stdio: stdioArr },
|
|
808
|
+
{ cwd: runnerDir, stdio: stdioArr, env: { ...process.env, ...env } },
|
|
772
809
|
);
|
|
773
810
|
const handle = { child, exited: false, done: null };
|
|
774
811
|
handle.done = new Promise((resolvePromise) => {
|
|
@@ -874,6 +911,10 @@ export function buildContainerPlan(workspace, args, _ctx) {
|
|
|
874
911
|
"CHOKIDAR_USEPOLLING=1",
|
|
875
912
|
"-e",
|
|
876
913
|
"CHOKIDAR_INTERVAL=300",
|
|
914
|
+
// Docker containers don't inherit the host env — thread the activity-bridge
|
|
915
|
+
// credential explicitly (see activityBridgeEnv), or the Docker runtime never
|
|
916
|
+
// reports to the developer's hosted /dev panel even when native would.
|
|
917
|
+
...dockerEnvArgs(activityBridgeEnv()),
|
|
877
918
|
image,
|
|
878
919
|
];
|
|
879
920
|
|
package/src/commands/login.mjs
CHANGED
|
@@ -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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
|
package/src/commands/start.mjs
CHANGED
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
buildContainerPlan, spawnDevContainer, dockerAvailable, tryStartDocker,
|
|
61
61
|
resolveDevImage, isPrivateRegistryImage, ensureRegistryLogin,
|
|
62
62
|
ensureRendererArtifact, ensureSampleRenderer, spawnNativeDev, deriveUrl,
|
|
63
|
-
NativeArtifactUnavailableError,
|
|
63
|
+
activityBridgeEnv, NativeArtifactUnavailableError,
|
|
64
64
|
} from "./dev.mjs";
|
|
65
65
|
import { scaffoldSample, isSampleCheckout, sampleConfig, SAMPLE_DIR_NAME } from "../sample.mjs";
|
|
66
66
|
import { IDEAS } from "./ideas.mjs";
|
|
@@ -233,7 +233,7 @@ export async function run(argv, ctx) {
|
|
|
233
233
|
} else {
|
|
234
234
|
url = deriveUrl(ctxDev.config || {}, devArgs.port).url;
|
|
235
235
|
console.log(` → starting dev … ${url}`);
|
|
236
|
-
handle = spawnNativeDev(runtime.runnerDir, dir, devArgs.port, { stdio: "piped" });
|
|
236
|
+
handle = spawnNativeDev(runtime.runnerDir, dir, devArgs.port, { stdio: "piped", env: activityBridgeEnv(env) });
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
const up = await Promise.race([
|
|
@@ -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
|
-
|
|
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
|
}
|
package/src/token-store.mjs
CHANGED
|
@@ -14,8 +14,9 @@
|
|
|
14
14
|
* to a different one. `activityToken`/`activityUrl` are a SEPARATE credential
|
|
15
15
|
* (storefront-issued, not MCP OAuth) that `tot login --code` caches when the
|
|
16
16
|
* invite paste carries it — see commands/login.mjs `cacheActivityBridge` and
|
|
17
|
-
* commands/dev.mjs `
|
|
18
|
-
*
|
|
17
|
+
* commands/dev.mjs `activityBridgeEnv`, which every runner-spawning path (native
|
|
18
|
+
* monorepo, native standalone, Docker) threads in so the local dev-loop process
|
|
19
|
+
* can report file saves to the developer's hosted /dev panel.
|
|
19
20
|
* Dependency-free (node:fs/os/path).
|
|
20
21
|
*
|
|
21
22
|
* `TOT_HOME` overrides the home dir (used by tests to point at a temp dir).
|