@tokenoftrust/cli 1.4.0-rc.2 → 1.4.0-rc.21
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/README.md +12 -9
- package/bin/tot.mjs +219 -44
- package/package.json +7 -2
- package/src/activity.mjs +379 -0
- package/src/app-scaffold.mjs +2 -2
- package/src/auth.mjs +13 -5
- package/src/candidate-state.mjs +137 -0
- package/src/commands/accept.mjs +736 -0
- package/src/commands/app/dev.mjs +7 -3
- package/src/commands/app/index.mjs +2 -2
- package/src/commands/branches.mjs +297 -0
- package/src/commands/cleanup.mjs +269 -0
- package/src/commands/clone.mjs +713 -0
- package/src/commands/dev.mjs +441 -93
- package/src/commands/doctor.mjs +4 -3
- package/src/commands/git-credential.mjs +180 -0
- package/src/commands/go-live.mjs +486 -0
- package/src/commands/grants.mjs +14 -7
- package/src/commands/hotfix.mjs +428 -0
- package/src/commands/link.mjs +225 -0
- package/src/commands/login.mjs +12 -8
- package/src/commands/pr.mjs +425 -0
- package/src/commands/preview-build.mjs +225 -0
- package/src/commands/preview.mjs +80 -0
- package/src/commands/retire.mjs +203 -0
- package/src/commands/revert.mjs +322 -0
- package/src/commands/rollback.mjs +403 -0
- package/src/commands/ship.mjs +517 -0
- package/src/commands/start.mjs +91 -29
- package/src/commands/submit.mjs +1360 -131
- package/src/commands/sync.mjs +203 -0
- package/src/commands/validate.mjs +11 -5
- package/src/commands/whoami.mjs +6 -2
- package/src/context.mjs +2 -2
- package/src/dev-heartbeat.mjs +2 -1
- package/src/errors.mjs +8 -4
- package/src/git-credential.mjs +185 -0
- package/src/mcp.mjs +6 -1
- package/src/no-gitea-links.test.mjs +55 -0
- package/src/oauth.mjs +26 -11
- package/src/obstacle-beacon.cjs +3 -3
- package/src/obstacle.mjs +1 -1
- package/src/plan.mjs +262 -0
- package/src/sample.mjs +30 -4
- package/src/validate.mjs +56 -0
- package/src/viewer-session.mjs +118 -0
- package/src/commands/checkout.mjs +0 -330
package/src/commands/start.mjs
CHANGED
|
@@ -57,8 +57,9 @@ import { CliError, fail, formatError, exitCodeFor } from "../errors.mjs";
|
|
|
57
57
|
import { openBrowser, waitForServer, firstFreePort } from "../open.mjs";
|
|
58
58
|
import { startProgress } from "../progress.mjs";
|
|
59
59
|
import { defaultLastTenantPath, readLastTenant, writeLastTenant } from "../last-tenant.mjs";
|
|
60
|
+
import { readCredentials, defaultCredentialsPath } from "../token-store.mjs";
|
|
60
61
|
import { collectChecks } from "./doctor.mjs";
|
|
61
|
-
import { normalizeStores, storeListError, checkoutTenant } from "./
|
|
62
|
+
import { normalizeStores, storeListError, checkoutTenant, noStoresGuidance } from "./clone.mjs";
|
|
62
63
|
import {
|
|
63
64
|
buildContainerPlan, spawnDevContainer, dockerAvailable, tryStartDocker,
|
|
64
65
|
resolveDevImage, isPrivateRegistryImage, ensureRegistryLogin,
|
|
@@ -66,7 +67,7 @@ import {
|
|
|
66
67
|
activityBridgeEnv, NativeArtifactUnavailableError,
|
|
67
68
|
} from "./dev.mjs";
|
|
68
69
|
import { scaffoldSample, isSampleCheckout, sampleConfig, SAMPLE_DIR_NAME } from "../sample.mjs";
|
|
69
|
-
import { startHeartbeatFromEnv } from "../dev-heartbeat.mjs";
|
|
70
|
+
import { startHeartbeatFromEnv, resolveEditor } from "../dev-heartbeat.mjs";
|
|
70
71
|
import { streamDevLogs } from "../dev-logs.mjs";
|
|
71
72
|
import { milestoneBanner, cockpitUrlFrom, DEVELOPER_COCKPIT } from "../banner.mjs";
|
|
72
73
|
import { IDEAS } from "./ideas.mjs";
|
|
@@ -161,6 +162,24 @@ export function decideStartMode({ sampleFlag, hasSession }) {
|
|
|
161
162
|
return hasSession ? "authed" : "sample";
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
/**
|
|
166
|
+
* The MCP base URL the cached session was minted on — what `tot login` stored in the
|
|
167
|
+
* credentials file (honoring TOT_PROFILE via defaultCredentialsPath). Lets `tot start`
|
|
168
|
+
* (and callers) FOLLOW wherever the developer signed in rather than defaulting to prod,
|
|
169
|
+
* which would look up a session on the wrong MCP and report "not signed in". Returns
|
|
170
|
+
* null when there's no cached session or it can't be read (falls through to the default).
|
|
171
|
+
* @param {NodeJS.ProcessEnv} env
|
|
172
|
+
* @returns {string|null}
|
|
173
|
+
*/
|
|
174
|
+
export function cachedMcpUrl(env) {
|
|
175
|
+
try {
|
|
176
|
+
const creds = readCredentials(defaultCredentialsPath(env));
|
|
177
|
+
return creds && typeof creds.mcpUrl === "string" && creds.mcpUrl ? creds.mcpUrl : null;
|
|
178
|
+
} catch {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
164
183
|
/** @param {string[]} argv @param {any} ctx */
|
|
165
184
|
export async function run(argv, ctx) {
|
|
166
185
|
const env = process.env;
|
|
@@ -178,7 +197,11 @@ export async function run(argv, ctx) {
|
|
|
178
197
|
}
|
|
179
198
|
|
|
180
199
|
try {
|
|
181
|
-
|
|
200
|
+
// Resolve the MCP: an explicit flag / env wins, then the MCP the cached session was
|
|
201
|
+
// minted on (what `tot login` stored — so `start` FOLLOWS wherever you signed in
|
|
202
|
+
// instead of defaulting to prod and reporting "not signed in"), then the prod default.
|
|
203
|
+
const baseUrl =
|
|
204
|
+
args.mcp || env.MCP_BASE_URL || env.TOT_MCP_URL || cachedMcpUrl(env) || DEFAULT_MCP_URL;
|
|
182
205
|
const client = createMcpClient(baseUrl);
|
|
183
206
|
|
|
184
207
|
// Resolve a session, tolerating a no-session / no-network condition so we can
|
|
@@ -223,6 +246,7 @@ export async function run(argv, ctx) {
|
|
|
223
246
|
tenant = await resolveTenant(stores, args, env, baseUrl, {
|
|
224
247
|
session,
|
|
225
248
|
listErr: storeListError(listResp),
|
|
249
|
+
list: listResp,
|
|
226
250
|
});
|
|
227
251
|
} catch (e) {
|
|
228
252
|
stopEarlyHeartbeat();
|
|
@@ -238,11 +262,11 @@ export async function run(argv, ctx) {
|
|
|
238
262
|
const dir = resolve(process.cwd(), tenant);
|
|
239
263
|
// Resolve a free port so the URL we open/poll/print matches what the runner
|
|
240
264
|
// binds (Vite strictPort is off → a busy port would drift). No-op if free.
|
|
241
|
-
const devArgs = {
|
|
265
|
+
const devArgs = /** @type {any} */ ({
|
|
242
266
|
image: null, port: String(await firstFreePort(Number(args.port || 4321))), mcp: args.mcp,
|
|
243
267
|
noLogin: false, noOpen: args.noOpen, docker: args.docker,
|
|
244
|
-
};
|
|
245
|
-
const runtime = { useDocker: args.docker, runnerDir: null };
|
|
268
|
+
});
|
|
269
|
+
const runtime = /** @type {{ useDocker: boolean, runnerDir: any }} */ ({ useDocker: args.docker, runnerDir: null });
|
|
246
270
|
await Promise.all([
|
|
247
271
|
ensureCheckout(client, tenant, dir, env),
|
|
248
272
|
prefetchRuntime(client, devArgs, env, runtime, ctx),
|
|
@@ -294,7 +318,7 @@ export async function run(argv, ctx) {
|
|
|
294
318
|
// (A3) so the "instant" claim is measured. The "Connect Claude" step is
|
|
295
319
|
// intentionally removed for now — a blocking prompt here meant Ctrl-C'ing it
|
|
296
320
|
// tore down the dev server; revisit AI-connect as a non-blocking step later.
|
|
297
|
-
printLiveEnding(tenant, url, formatElapsed(Date.now() - startedAt), cockpitUrl);
|
|
321
|
+
printLiveEnding(tenant, url, formatElapsed(Date.now() - startedAt), cockpitUrl, dir);
|
|
298
322
|
|
|
299
323
|
// 7. hand the terminal to the running dev server until Ctrl-C.
|
|
300
324
|
console.log("\n Watching your store — edit content/home.html + save. Ctrl-C to stop.\n");
|
|
@@ -537,7 +561,7 @@ function mcpOrigin(baseUrl) {
|
|
|
537
561
|
* (surface the reason — likely an auth/entitlement problem) from a genuinely empty
|
|
538
562
|
* result (invite may still be propagating, or you need one). Points at `tot whoami`.
|
|
539
563
|
*/
|
|
540
|
-
function noStoresError({ session, baseUrl, listErr }) {
|
|
564
|
+
function noStoresError({ session, baseUrl, listErr, list = null }) {
|
|
541
565
|
const who = describeIdentity(session);
|
|
542
566
|
const origin = mcpOrigin(baseUrl);
|
|
543
567
|
if (listErr) {
|
|
@@ -546,14 +570,10 @@ function noStoresError({ session, baseUrl, listErr }) {
|
|
|
546
570
|
{ next: "run `tot whoami` to check your session, or `tot login` again — then re-run `tot start`" },
|
|
547
571
|
);
|
|
548
572
|
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
"if you were just invited, it may still be propagating — try again in a minute; " +
|
|
554
|
-
"otherwise ask your Token of Trust contact for a store invite (see `tot whoami`)",
|
|
555
|
-
},
|
|
556
|
-
);
|
|
573
|
+
// Status-aware (card c2): an UNLINKED identity is pointed at `tot link`, not the
|
|
574
|
+
// misleading "ask for a store invite" copy; the genuine zero-grants case keeps it.
|
|
575
|
+
const g = noStoresGuidance(list);
|
|
576
|
+
return new CliError(`signed in as ${who} via ${origin}, but ${g.headline}`, { next: g.next });
|
|
557
577
|
}
|
|
558
578
|
|
|
559
579
|
/**
|
|
@@ -561,7 +581,11 @@ function noStoresError({ session, baseUrl, listErr }) {
|
|
|
561
581
|
* the remembered last tenant (A4) — then remember whatever was decided so the
|
|
562
582
|
* next bare `tot start` doesn't have to ask again.
|
|
563
583
|
*/
|
|
564
|
-
|
|
584
|
+
/**
|
|
585
|
+
* @param {any} stores @param {any} args @param {any} env @param {string} baseUrl
|
|
586
|
+
* @param {{ session?: any, listErr?: string|null, list?: any }} [opts]
|
|
587
|
+
*/
|
|
588
|
+
async function resolveTenant(stores, args, env, baseUrl, { session = null, listErr = null, list = null } = {}) {
|
|
565
589
|
const lastTenantPath = defaultLastTenantPath(env);
|
|
566
590
|
const pick = pickTenant(stores, {
|
|
567
591
|
explicit: args.tenant || null,
|
|
@@ -570,7 +594,7 @@ async function resolveTenant(stores, args, env, baseUrl, { session = null, listE
|
|
|
570
594
|
|
|
571
595
|
let tenant;
|
|
572
596
|
if (pick.kind === "none") {
|
|
573
|
-
throw noStoresError({ session, baseUrl, listErr });
|
|
597
|
+
throw noStoresError({ session, baseUrl, listErr, list });
|
|
574
598
|
} else if (pick.kind === "explicit") {
|
|
575
599
|
tenant = pick.tenant;
|
|
576
600
|
console.log(` → your store: ${tenant} (--tenant)`);
|
|
@@ -583,9 +607,10 @@ async function resolveTenant(stores, args, env, baseUrl, { session = null, listE
|
|
|
583
607
|
} else {
|
|
584
608
|
// Many stores, nothing remembered — choose. Non-interactive (no TTY /
|
|
585
609
|
// --yes without a name) can't guess.
|
|
610
|
+
const many = /** @type {any} */ (pick);
|
|
586
611
|
if (!isInteractive() || args.yes) {
|
|
587
612
|
throw new CliError("you can build on several stores — pick one", {
|
|
588
|
-
next: `tot start --tenant <tenant> (one of: ${
|
|
613
|
+
next: `tot start --tenant <tenant> (one of: ${many.stores.map((s) => s.id).join(", ")})`,
|
|
589
614
|
});
|
|
590
615
|
}
|
|
591
616
|
// Loud + unmissable: a >1-store dev is often looking at their browser
|
|
@@ -594,11 +619,11 @@ async function resolveTenant(stores, args, env, baseUrl, { session = null, listE
|
|
|
594
619
|
console.log("");
|
|
595
620
|
console.log(" ⚑ ACTION NEEDED IN YOUR TERMINAL — you can build on several stores.");
|
|
596
621
|
console.log(" Pick one here to light up your cockpit:\n");
|
|
597
|
-
|
|
622
|
+
many.stores.forEach((s, i) => console.log(` ${i + 1}. ${s.id}${s.name ? ` — ${s.name}` : ""}`));
|
|
598
623
|
console.log("");
|
|
599
|
-
const idx = await promptChoice(
|
|
624
|
+
const idx = await promptChoice(many.stores.length);
|
|
600
625
|
console.log("");
|
|
601
|
-
tenant =
|
|
626
|
+
tenant = many.stores[idx].id;
|
|
602
627
|
}
|
|
603
628
|
|
|
604
629
|
writeLastTenant(lastTenantPath, { mcpUrl: baseUrl, tenant });
|
|
@@ -613,7 +638,7 @@ async function ensureCheckout(client, tenant, dir, env) {
|
|
|
613
638
|
console.log(` ✓ reusing existing checkout ./${tenant}`);
|
|
614
639
|
return;
|
|
615
640
|
}
|
|
616
|
-
throw new CliError(`./${tenant} already exists and isn't a
|
|
641
|
+
throw new CliError(`./${tenant} already exists and isn't a store checkout`, {
|
|
617
642
|
next: `remove it (or run \`tot start\` from an empty directory)`,
|
|
618
643
|
exitCode: 2,
|
|
619
644
|
});
|
|
@@ -665,6 +690,7 @@ export function formatElapsed(ms) {
|
|
|
665
690
|
* Developer Cockpit URL, the one next step is framed as "edit a line → see it in
|
|
666
691
|
* your cockpit"; otherwise it's the same edit-to-see-it-reload action.
|
|
667
692
|
*/
|
|
693
|
+
/** @param {string} dir @param {string|null} [cockpitUrl] */
|
|
668
694
|
function printCheckoutLanding(dir, cockpitUrl = null) {
|
|
669
695
|
console.log("");
|
|
670
696
|
console.log(` 📁 Your store code is at: ${dir}`);
|
|
@@ -673,11 +699,38 @@ function printCheckoutLanding(dir, cockpitUrl = null) {
|
|
|
673
699
|
console.log(` content/home.html — you'll see it reflected in ${where}.`);
|
|
674
700
|
}
|
|
675
701
|
|
|
702
|
+
/**
|
|
703
|
+
* OS/editor-aware suggestions for opening the checkout, shown once at the
|
|
704
|
+
* "you're live" moment. $VISUAL/$EDITOR (same signal the cockpit's "open this
|
|
705
|
+
* file" hint uses — see dev-heartbeat.mjs#resolveEditor) wins when set;
|
|
706
|
+
* otherwise a short per-platform shortlist of common editor launch commands.
|
|
707
|
+
* Deliberately NOT probed against PATH — a spawnSync per candidate would add
|
|
708
|
+
* real latency to the crafted "aha" ending for the common case (nothing set),
|
|
709
|
+
* so this is a labeled suggestion list, not a detection result.
|
|
710
|
+
*/
|
|
711
|
+
function editorOpenLines(dir, { platform = process.platform, env = process.env } = {}) {
|
|
712
|
+
const configured = resolveEditor(env);
|
|
713
|
+
if (configured) return [`${configured} ${dir}`];
|
|
714
|
+
if (platform === "darwin") {
|
|
715
|
+
return [`code ${dir} (VS Code, if installed)`, `cursor ${dir} (Cursor, if installed)`, `open ${dir} (Finder)`];
|
|
716
|
+
}
|
|
717
|
+
if (platform === "win32") {
|
|
718
|
+
return [`code ${dir} (VS Code, if installed)`, `explorer ${dir} (File Explorer)`];
|
|
719
|
+
}
|
|
720
|
+
return [`code ${dir} (VS Code, if installed)`, `cursor ${dir} (Cursor, if installed)`, `xdg-open ${dir} (file manager)`];
|
|
721
|
+
}
|
|
722
|
+
|
|
676
723
|
/** The crafted "you're live" ending — a PROMINENT milestone banner (u2) that,
|
|
677
724
|
* when we hold a Developer Cockpit URL, explicitly sends the developer BACK to
|
|
678
|
-
* their cockpit as the next place to look
|
|
679
|
-
*
|
|
680
|
-
|
|
725
|
+
* their cockpit as the next place to look. Then: an OS-suited "open this in
|
|
726
|
+
* an editor" suggestion, and the four-pane layout tip (this terminal, cockpit,
|
|
727
|
+
* local preview, editor) so a build problem, a reload, and the code are never
|
|
728
|
+
* more than a glance apart. */
|
|
729
|
+
/**
|
|
730
|
+
* @param {string} tenant @param {string} url @param {string|null} elapsed
|
|
731
|
+
* @param {string|null} [cockpitUrl] @param {string|null} [dir]
|
|
732
|
+
*/
|
|
733
|
+
function printLiveEnding(tenant, url, elapsed, cockpitUrl = null, dir = null) {
|
|
681
734
|
const lines = [
|
|
682
735
|
`✨ You're live.${elapsed ? ` (${elapsed})` : ""}`,
|
|
683
736
|
` ${url}`,
|
|
@@ -691,10 +744,19 @@ function printLiveEnding(tenant, url, elapsed, cockpitUrl = null) {
|
|
|
691
744
|
console.log(milestoneBanner(lines));
|
|
692
745
|
console.log(" " + versionStamp("native"));
|
|
693
746
|
console.log("");
|
|
694
|
-
|
|
695
|
-
|
|
747
|
+
if (dir) {
|
|
748
|
+
console.log(" Open your project in an editor (an AI coding agent like Claude works too —");
|
|
749
|
+
console.log(" same idea, different hands on the keyboard):");
|
|
750
|
+
for (const line of editorOpenLines(dir)) console.log(` ${line}`);
|
|
751
|
+
console.log("");
|
|
752
|
+
}
|
|
753
|
+
console.log(" Keep THIS terminal open — it's your live build/status feed, the fastest way to");
|
|
754
|
+
console.log(" see a problem the moment it happens. Need the command line for something else?");
|
|
755
|
+
console.log(" Open a NEW terminal window rather than closing this one.");
|
|
696
756
|
console.log("");
|
|
697
|
-
console.log("
|
|
757
|
+
console.log(" Best view: arrange this terminal, your Developer Cockpit, the local preview,");
|
|
758
|
+
console.log(" and your editor so you can see all four at once — site, cockpit, build status,");
|
|
759
|
+
console.log(" and code, together.");
|
|
698
760
|
console.log("");
|
|
699
761
|
}
|
|
700
762
|
|