@tokenoftrust/cli 1.3.0-rc.1 → 1.3.0-rc.3
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/bin/tot.mjs +9 -0
- package/package.json +1 -1
- package/src/commands/dev.mjs +131 -28
- package/src/commands/start.mjs +2 -2
- package/src/sample.mjs +21 -17
- package/src/token-store.mjs +3 -2
package/bin/tot.mjs
CHANGED
|
@@ -58,12 +58,21 @@ tot — Token of Trust developer CLI
|
|
|
58
58
|
tot ideas copy-paste AI prompts that reliably wow
|
|
59
59
|
tot feedback "<msg>" send feedback to Token of Trust (attaches recent activity)
|
|
60
60
|
tot help show this help
|
|
61
|
+
tot --version print the CLI version
|
|
61
62
|
|
|
62
63
|
Run \`tot <command> --help\` for command-specific options.
|
|
63
64
|
`);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
async function dispatch(cmd, rest, ctx) {
|
|
68
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
69
|
+
// Bare version so it's greppable + machine-parseable (e.g. `tot --version` →
|
|
70
|
+
// `1.3.0-rc.2`). The FIRST thing to check when diagnosing "am I on the new
|
|
71
|
+
// CLI?" — a bare `npm i -g @tokenoftrust/cli` lands `latest`, not `@next`.
|
|
72
|
+
console.log(VERSION);
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
67
76
|
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
68
77
|
usage();
|
|
69
78
|
return cmd ? 0 : 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.3",
|
|
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/commands/dev.mjs
CHANGED
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
* either way.
|
|
33
33
|
*/
|
|
34
34
|
import { spawn, spawnSync, execFileSync } from "node:child_process";
|
|
35
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync, rmSync, createWriteStream } from "node:fs";
|
|
35
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, renameSync, rmSync, readdirSync, createWriteStream } from "node:fs";
|
|
36
36
|
import { homedir, tmpdir } from "node:os";
|
|
37
37
|
import { join, resolve } from "node:path";
|
|
38
38
|
import { createHash } from "node:crypto";
|
|
@@ -46,7 +46,7 @@ import { CliError, fail, formatError } from "../errors.mjs";
|
|
|
46
46
|
import { openBrowser, waitForServer, firstFreePort } from "../open.mjs";
|
|
47
47
|
import {
|
|
48
48
|
scaffoldSample, isSampleCheckout, sampleConfig,
|
|
49
|
-
resolveRendererSource as resolveLocalRendererSource, SAMPLE_DIR_NAME,
|
|
49
|
+
resolveRendererSource as resolveLocalRendererSource, newestCachedRunner, SAMPLE_DIR_NAME,
|
|
50
50
|
} from "../sample.mjs";
|
|
51
51
|
|
|
52
52
|
/** The published runner image (--docker fallback). Override with --image / TOT_DEV_IMAGE. */
|
|
@@ -180,9 +180,10 @@ async function runStandalone(workspace, args, ctx) {
|
|
|
180
180
|
* Native boot against the PUBLIC/local renderer (no MCP, no
|
|
181
181
|
* entitlement) — the fallback for a standalone checkout when the entitled
|
|
182
182
|
* artifact isn't available. Mirrors runNative but resolves the renderer via
|
|
183
|
-
* ensureSampleRenderer (env override →
|
|
184
|
-
*
|
|
185
|
-
* runner can't be fetched
|
|
183
|
+
* ensureSampleRenderer (env override → in-tree monorepo → version-pinned public
|
|
184
|
+
* npm, reusing the matching `public-<version>` cache), which throws
|
|
185
|
+
* NativeArtifactUnavailableError when even the public runner can't be fetched
|
|
186
|
+
* (offline) so the caller can drop to Docker.
|
|
186
187
|
*/
|
|
187
188
|
async function runNativePublic(workspace, args, ctx) {
|
|
188
189
|
const cfg = readWorkspaceConfig(workspace);
|
|
@@ -204,23 +205,42 @@ async function runNativePublic(workspace, args, ctx) {
|
|
|
204
205
|
return bootNative(runnerDir, workspace, port, url, args);
|
|
205
206
|
}
|
|
206
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
|
+
|
|
207
237
|
function runMonorepo(ctx, argv) {
|
|
208
238
|
const script = join(ctx.repoRoot, "scripts", "tot-dev.mjs");
|
|
209
239
|
if (!existsSync(script)) {
|
|
210
240
|
console.error(`✗ expected the dev runner at ${script} but it's missing.`);
|
|
211
241
|
return 2;
|
|
212
242
|
}
|
|
213
|
-
|
|
214
|
-
// minted alongside the invite's cli-signin-code paste) so the spawned tot-dev.mjs
|
|
215
|
-
// can report file-save activity up to the developer's own hosted /dev panel.
|
|
216
|
-
// Best-effort: an older cached session (or a bare `tot login`) simply has
|
|
217
|
-
// neither field, and the local loop runs exactly as before with no hosted signal.
|
|
218
|
-
const creds = readCredentials(defaultCredentialsPath(process.env));
|
|
219
|
-
const env = { ...process.env };
|
|
220
|
-
if (creds?.activityToken && creds?.activityUrl) {
|
|
221
|
-
env.TOT_DEV_ACTIVITY_TOKEN = creds.activityToken;
|
|
222
|
-
env.TOT_DEV_ACTIVITY_URL = creds.activityUrl;
|
|
223
|
-
}
|
|
243
|
+
const env = { ...process.env, ...activityBridgeEnv() };
|
|
224
244
|
return new Promise((resolvePromise) => {
|
|
225
245
|
const child = spawn(process.execPath, [script, ...argv], { stdio: "inherit", env });
|
|
226
246
|
child.on("exit", (code) => resolvePromise(code ?? 0));
|
|
@@ -270,7 +290,9 @@ async function runNative(workspace, args, ctx) {
|
|
|
270
290
|
* zero-login sample path (runSample) so both boot identically.
|
|
271
291
|
*/
|
|
272
292
|
function bootNative(runnerDir, workspace, port, url, args) {
|
|
273
|
-
|
|
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() });
|
|
274
296
|
|
|
275
297
|
// Auto-open the browser the moment the server answers (D). Non-blocking so
|
|
276
298
|
// Ctrl-C / logs are unaffected; --no-open suppresses it.
|
|
@@ -500,20 +522,64 @@ export async function ensureRendererArtifact(args, { client: providedClient } =
|
|
|
500
522
|
}
|
|
501
523
|
|
|
502
524
|
/**
|
|
503
|
-
*
|
|
504
|
-
*
|
|
505
|
-
*
|
|
506
|
-
*
|
|
525
|
+
* The version-pinned public-runner cache dir for `version` IFF it's present and
|
|
526
|
+
* fully installed (carries the `.tot-cache-complete` marker), else null. This is
|
|
527
|
+
* the VERSION-AWARE reuse that replaced the old "newest dir by mtime" reuse: it
|
|
528
|
+
* only ever returns the dir for the version the pin actually chose, so a stale
|
|
529
|
+
* pre-pin dir (public-0.1.0) can never be served in place of the pinned one
|
|
530
|
+
* (the 2026-07-14 "cached runner keeps being reused" bug). Pure fs probe.
|
|
531
|
+
*/
|
|
532
|
+
export function pinnedPublicCacheDir(cacheRoot, version) {
|
|
533
|
+
if (!cacheRoot || !version) return null;
|
|
534
|
+
const dir = join(cacheRoot, `public-${version}`);
|
|
535
|
+
return existsSync(join(dir, ".tot-cache-complete")) ? dir : null;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Delete every OTHER public-runner cache dir (`public-*` except `keepVersion`)
|
|
540
|
+
* once we've resolved the version this CLI pins to — so a pre-alignment dir
|
|
541
|
+
* (e.g. public-0.1.0 from before the version pin existed) can't linger to be
|
|
542
|
+
* picked up by the offline last-resort reuse, and disk doesn't accumulate one
|
|
543
|
+
* runner tree per version forever. Only touches `public-*` dirs: the entitled
|
|
544
|
+
* path caches by bare version and sample tarball runs by `sample-*`, both left
|
|
545
|
+
* alone. Best-effort — a failure here never breaks `tot dev`.
|
|
546
|
+
*/
|
|
547
|
+
function prunePublicRunnerCache(cacheRoot, keepVersion) {
|
|
548
|
+
try {
|
|
549
|
+
if (!cacheRoot || !existsSync(cacheRoot)) return;
|
|
550
|
+
const keep = `public-${keepVersion}`;
|
|
551
|
+
for (const name of readdirSync(cacheRoot)) {
|
|
552
|
+
if (!name.startsWith("public-") || name === keep) continue;
|
|
553
|
+
rmSync(join(cacheRoot, name), { recursive: true, force: true });
|
|
554
|
+
}
|
|
555
|
+
} catch {
|
|
556
|
+
/* best-effort cache hygiene */
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Resolve the renderer for the ZERO-LOGIN sample / public-fallback path WITHOUT
|
|
562
|
+
* any MCP call — the whole point of the free taste. Resolution order:
|
|
563
|
+
* 1. env override (TOT_RUNNER_DIR/TARBALL/URL) or the in-tree monorepo runner
|
|
564
|
+
* (resolveLocalRendererSource) — used verbatim.
|
|
565
|
+
* 2. otherwise the PUBLIC npm runner, VERSION-PINNED to this CLI (pickRunnerVersion):
|
|
566
|
+
* reuse the matching `public-<version>` cache dir if present, else download it.
|
|
567
|
+
*
|
|
568
|
+
* The cache is keyed `public-<version>`, so we reuse the RIGHT version offline and
|
|
569
|
+
* NEVER a stale one. This is the fix for the 2026-07-14 bug: the resolver used to
|
|
570
|
+
* return the newest cache dir by mtime with no version check, short-circuiting
|
|
571
|
+
* BEFORE the pin ran, so a pre-pin public-0.1.0 was reused forever and the CLI
|
|
572
|
+
* never asked npm for the aligned runner.
|
|
573
|
+
*
|
|
507
574
|
* Unlike ensureRendererArtifact it does NOT fall back to Docker — the sample is
|
|
508
575
|
* deliberately Docker-free and login-free.
|
|
509
576
|
* @returns {Promise<string>} the runner tree's root directory (has scripts/tot-dev.mjs).
|
|
510
577
|
*/
|
|
511
|
-
export async function ensureSampleRenderer(args, ctx, { env = process.env } = {}) {
|
|
578
|
+
export async function ensureSampleRenderer(args, ctx, { env = process.env, cacheRoot = RENDERER_CACHE_ROOT } = {}) {
|
|
512
579
|
const src = resolveLocalRendererSource({
|
|
513
580
|
env,
|
|
514
581
|
mode: ctx?.mode,
|
|
515
582
|
repoRoot: ctx?.repoRoot,
|
|
516
|
-
cacheRoot: RENDERER_CACHE_ROOT,
|
|
517
583
|
});
|
|
518
584
|
|
|
519
585
|
if (src.kind === "dir") {
|
|
@@ -535,12 +601,39 @@ export async function ensureSampleRenderer(args, ctx, { env = process.env } = {}
|
|
|
535
601
|
);
|
|
536
602
|
}
|
|
537
603
|
|
|
538
|
-
// kind === "none" —
|
|
539
|
-
//
|
|
604
|
+
// kind === "none" — no override, not in the monorepo: fetch the PUBLIC runner
|
|
605
|
+
// from npm, PINNED to this CLI's version. No MCP, no entitlement, no login.
|
|
606
|
+
const explicitPin = args.rendererVersion || env.TOT_RUNNER_VERSION || null;
|
|
607
|
+
|
|
608
|
+
// Fully-offline fast path: when the runner published lockstep at this exact CLI
|
|
609
|
+
// version is already cached, reuse it without touching npm — honouring "don't
|
|
610
|
+
// hit npm when the RIGHT version is already cached" without ever reusing a
|
|
611
|
+
// version the pin didn't choose. Safe because `public-<CLI_VERSION>` only exists
|
|
612
|
+
// if a prior run fetched exactly that (pinned) version. Skipped when an explicit
|
|
613
|
+
// pin is set (that must go through resolution).
|
|
614
|
+
if (!explicitPin) {
|
|
615
|
+
const exact = pinnedPublicCacheDir(cacheRoot, CLI_VERSION);
|
|
616
|
+
if (exact) {
|
|
617
|
+
console.error(`~ renderer: cached public runner ${CLI_VERSION} (matches this CLI)`);
|
|
618
|
+
prunePublicRunnerCache(cacheRoot, CLI_VERSION);
|
|
619
|
+
return exact;
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
540
623
|
let pub;
|
|
541
624
|
try {
|
|
542
625
|
pub = await resolvePublicRendererSource(args, env);
|
|
543
626
|
} catch (e) {
|
|
627
|
+
// OFFLINE / npm unreachable — no way to resolve the pinned version. Last
|
|
628
|
+
// resort: reuse ANY complete cached runner rather than hard-failing `tot dev`.
|
|
629
|
+
// This is the ONLY place a version-unchecked reuse survives, and only because
|
|
630
|
+
// there's no network to pin against. (Once one online run has aligned + pruned
|
|
631
|
+
// the cache, this reuses the correct version anyway.)
|
|
632
|
+
const cached = newestCachedRunner(cacheRoot);
|
|
633
|
+
if (cached && existsSync(join(cached, "scripts", "tot-dev.mjs"))) {
|
|
634
|
+
console.error(`~ renderer: offline — reusing cached runner ${cached} (couldn't reach npm to pin the version)`);
|
|
635
|
+
return cached;
|
|
636
|
+
}
|
|
544
637
|
throw new CliError(
|
|
545
638
|
`can't run the sample — no local runner and the public runner is unavailable: ${e?.message || e}`,
|
|
546
639
|
{
|
|
@@ -551,10 +644,14 @@ export async function ensureSampleRenderer(args, ctx, { env = process.env } = {}
|
|
|
551
644
|
);
|
|
552
645
|
}
|
|
553
646
|
console.error(`~ renderer: public npm ${pub.version} (${PUBLIC_RUNNER_PACKAGE})`);
|
|
554
|
-
|
|
647
|
+
const dir = await installRunnerTarball(
|
|
555
648
|
{ source: pub.url, version: pub.cacheKey, isUrl: true, strip: pub.strip },
|
|
556
649
|
{ log: (m) => console.error(m) },
|
|
557
650
|
);
|
|
651
|
+
// The pinned version is now installed under public-<version> — drop any other
|
|
652
|
+
// public-* dirs (e.g. the stale public-0.1.0) so they can never be reused.
|
|
653
|
+
prunePublicRunnerCache(cacheRoot, pub.version);
|
|
654
|
+
return dir;
|
|
558
655
|
}
|
|
559
656
|
|
|
560
657
|
/** A stable, filesystem-safe cache key for a renderer resolved from a tarball source. */
|
|
@@ -682,17 +779,19 @@ function runPnpmInstall(runnerDir) {
|
|
|
682
779
|
* auto-open-browser logic works unchanged for either runtime. Exported (and
|
|
683
780
|
* `stdio` overridable) so `tot start` can pipe the logs instead of inheriting.
|
|
684
781
|
*/
|
|
685
|
-
export function spawnNativeDev(runnerDir, workspace, port, { stdio = "inherit" } = {}) {
|
|
782
|
+
export function spawnNativeDev(runnerDir, workspace, port, { stdio = "inherit", env = {} } = {}) {
|
|
686
783
|
const script = join(runnerDir, "scripts", "tot-dev.mjs");
|
|
687
784
|
// Translate the same "inherit"|"piped" contract spawnDevContainer honors:
|
|
688
785
|
// "piped" means stdin ignored + stdout/stderr captured so `tot start` can hold
|
|
689
786
|
// the prompt on stdin and stream the logs after the aha. child_process.spawn
|
|
690
787
|
// doesn't understand the bare string "piped", so map it to the array here.
|
|
691
788
|
const stdioArr = stdio === "piped" ? ["ignore", "pipe", "pipe"] : stdio;
|
|
789
|
+
// `env` (e.g. activityBridgeEnv()) merges OVER process.env — {} is a pure
|
|
790
|
+
// passthrough, identical to the old no-env-key behavior.
|
|
692
791
|
const child = spawn(
|
|
693
792
|
process.execPath,
|
|
694
793
|
[script, "--workspace", workspace, "--port", port],
|
|
695
|
-
{ cwd: runnerDir, stdio: stdioArr },
|
|
794
|
+
{ cwd: runnerDir, stdio: stdioArr, env: { ...process.env, ...env } },
|
|
696
795
|
);
|
|
697
796
|
const handle = { child, exited: false, done: null };
|
|
698
797
|
handle.done = new Promise((resolvePromise) => {
|
|
@@ -798,6 +897,10 @@ export function buildContainerPlan(workspace, args, _ctx) {
|
|
|
798
897
|
"CHOKIDAR_USEPOLLING=1",
|
|
799
898
|
"-e",
|
|
800
899
|
"CHOKIDAR_INTERVAL=300",
|
|
900
|
+
// Docker containers don't inherit the host env — thread the activity-bridge
|
|
901
|
+
// credential explicitly (see activityBridgeEnv), or the Docker runtime never
|
|
902
|
+
// reports to the developer's hosted /dev panel even when native would.
|
|
903
|
+
...dockerEnvArgs(activityBridgeEnv()),
|
|
801
904
|
image,
|
|
802
905
|
];
|
|
803
906
|
|
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([
|
package/src/sample.mjs
CHANGED
|
@@ -142,19 +142,25 @@ export function scaffoldSample(destDir, { force = false, log = () => {} } = {})
|
|
|
142
142
|
* 3. TOT_RUNNER_URL — a PUBLIC https tarball URL. ← the seam WS3b bakes a
|
|
143
143
|
* default into (a well-known public CDN URL), so a bare
|
|
144
144
|
* `npx … dev --sample` on a clean machine Just Works.
|
|
145
|
-
* 4.
|
|
146
|
-
* downloaded under ~/.tot/cache/renderer/<version>/.
|
|
147
|
-
* Piggybacks on it with no network at all.
|
|
148
|
-
* 5. monorepo — inside the storefront monorepo the runner IS the tree;
|
|
145
|
+
* 4. monorepo — inside the storefront monorepo the runner IS the tree;
|
|
149
146
|
* run its in-tree scripts/tot-dev.mjs directly.
|
|
150
|
-
*
|
|
151
|
-
*
|
|
147
|
+
* 5. none — no override + not in the monorepo → caller fetches the
|
|
148
|
+
* VERSION-PINNED public runner from npm (dev.mjs).
|
|
149
|
+
*
|
|
150
|
+
* NOTE — cache reuse deliberately does NOT live here anymore. This resolver is
|
|
151
|
+
* sync + offline, but the correct cached runner is the one matching the version
|
|
152
|
+
* the CLI PINS to (pickRunnerVersion), which needs npm metadata. Reusing "the
|
|
153
|
+
* newest cache dir by mtime" here (the pre-2026-07-14 behaviour) short-circuited
|
|
154
|
+
* BEFORE the version pin ever ran, so a stale pre-pin dir (public-0.1.0) got
|
|
155
|
+
* reused forever. Version-aware reuse now happens in dev.mjs `ensureSampleRenderer`
|
|
156
|
+
* (reuse `public-<pinnedVersion>` iff present, else fetch); `newestCachedRunner`
|
|
157
|
+
* is exported below only as an OFFLINE last resort when npm can't be reached at all.
|
|
152
158
|
*
|
|
153
159
|
* Pure/deterministic given its inputs (env + fs probes) — no network, no MCP.
|
|
154
|
-
* @param {{ env?: NodeJS.ProcessEnv, mode?: string, repoRoot?: string|null
|
|
160
|
+
* @param {{ env?: NodeJS.ProcessEnv, mode?: string, repoRoot?: string|null }} [opts]
|
|
155
161
|
* @returns {{ kind: "dir"|"tarball"|"none", dir?: string, source?: string, isUrl?: boolean, why: string }}
|
|
156
162
|
*/
|
|
157
|
-
export function resolveRendererSource({ env = process.env, mode = "loose", repoRoot = null
|
|
163
|
+
export function resolveRendererSource({ env = process.env, mode = "loose", repoRoot = null } = {}) {
|
|
158
164
|
if (env.TOT_RUNNER_DIR) {
|
|
159
165
|
return { kind: "dir", dir: resolve(env.TOT_RUNNER_DIR), why: "TOT_RUNNER_DIR" };
|
|
160
166
|
}
|
|
@@ -164,27 +170,25 @@ export function resolveRendererSource({ env = process.env, mode = "loose", repoR
|
|
|
164
170
|
if (env.TOT_RUNNER_URL) {
|
|
165
171
|
return { kind: "tarball", source: env.TOT_RUNNER_URL, isUrl: true, why: "TOT_RUNNER_URL" };
|
|
166
172
|
}
|
|
167
|
-
const cached = newestCachedRunner(cacheRoot);
|
|
168
|
-
if (cached) {
|
|
169
|
-
return { kind: "dir", dir: cached, why: "cached runner (prior `tot dev`)" };
|
|
170
|
-
}
|
|
171
173
|
if (mode === "monorepo" && repoRoot && existsSync(join(repoRoot, "scripts", "tot-dev.mjs"))) {
|
|
172
174
|
return { kind: "dir", dir: repoRoot, why: "in-tree monorepo runner" };
|
|
173
175
|
}
|
|
174
176
|
return {
|
|
175
177
|
kind: "none",
|
|
176
178
|
why:
|
|
177
|
-
"no local runner and
|
|
179
|
+
"no local runner override and not in the monorepo — fetch the version-pinned public runner from npm (set TOT_RUNNER_URL / TOT_RUNNER_TARBALL / TOT_RUNNER_DIR to override)",
|
|
178
180
|
};
|
|
179
181
|
}
|
|
180
182
|
|
|
181
183
|
/**
|
|
182
184
|
* Newest fully-installed runner under the renderer cache (dirs carry a
|
|
183
|
-
* `.tot-cache-complete` marker written by dev.mjs).
|
|
184
|
-
*
|
|
185
|
-
*
|
|
185
|
+
* `.tot-cache-complete` marker written by dev.mjs). Returns the absolute dir or
|
|
186
|
+
* null. NOT part of the normal resolution order — it's version-UNAWARE (picks by
|
|
187
|
+
* mtime), so dev.mjs uses it only as an OFFLINE last resort (npm unreachable, so
|
|
188
|
+
* no version to pin against); the online path reuses the version-pinned
|
|
189
|
+
* `public-<version>` dir instead. Exported for that single caller.
|
|
186
190
|
*/
|
|
187
|
-
function newestCachedRunner(cacheRoot) {
|
|
191
|
+
export function newestCachedRunner(cacheRoot) {
|
|
188
192
|
if (!cacheRoot || !existsSync(cacheRoot)) return null;
|
|
189
193
|
let best = null;
|
|
190
194
|
let bestMtime = -1;
|
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).
|