@tokenoftrust/cli 1.4.0 → 1.5.0
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 +5 -0
- package/bin/tot.mjs +148 -57
- package/package.json +6 -1
- package/src/activity.mjs +379 -0
- package/src/app-scaffold.mjs +4 -4
- package/src/auth.mjs +13 -5
- package/src/candidate-state.mjs +3 -3
- package/src/commands/accept.mjs +498 -59
- package/src/commands/app/dev.mjs +8 -4
- package/src/commands/app/index.mjs +3 -3
- package/src/commands/app/scaffold.mjs +1 -1
- package/src/commands/branches.mjs +297 -0
- package/src/commands/cleanup.mjs +264 -0
- package/src/commands/clone.mjs +307 -25
- package/src/commands/dev.mjs +440 -156
- package/src/commands/doctor.mjs +4 -4
- package/src/commands/git-credential.mjs +180 -0
- package/src/commands/go-live.mjs +9 -5
- package/src/commands/grants.mjs +7 -5
- package/src/commands/hotfix.mjs +428 -0
- package/src/commands/ideas.mjs +2 -2
- package/src/commands/link.mjs +2 -2
- package/src/commands/login.mjs +5 -6
- package/src/commands/pr.mjs +62 -25
- package/src/commands/preview-build.mjs +6 -6
- package/src/commands/preview-doctor.mjs +225 -0
- package/src/commands/preview-retry-evidence.mjs +156 -0
- package/src/commands/preview.mjs +19 -3
- package/src/commands/revert.mjs +322 -0
- package/src/commands/rollback.mjs +18 -16
- package/src/commands/ship.mjs +51 -14
- package/src/commands/start.mjs +101 -59
- package/src/commands/submit.mjs +1183 -169
- package/src/commands/sync.mjs +203 -0
- package/src/commands/validate.mjs +10 -4
- package/src/commands/whoami.mjs +1 -1
- package/src/dev-heartbeat.mjs +3 -2
- package/src/dev-logs.mjs +2 -2
- package/src/errors.mjs +11 -4
- package/src/git-credential.mjs +257 -0
- package/src/last-tenant.mjs +1 -1
- package/src/mcp.mjs +6 -1
- package/src/merge-doctor-report.mjs +208 -0
- package/src/no-gitea-links.test.mjs +55 -0
- package/src/oauth.mjs +18 -14
- package/src/obstacle-beacon.cjs +2 -2
- package/src/obstacle.mjs +1 -1
- package/src/plan.mjs +83 -15
- package/src/sample.mjs +4 -4
- package/src/validate.mjs +187 -15
- package/src/vendor/private-apps-devkit.mjs +3 -3
- package/src/viewer-session.mjs +118 -0
- package/template/private-app/README.md +12 -6
- package/src/commands/retire.mjs +0 -203
package/src/commands/start.mjs
CHANGED
|
@@ -4,30 +4,30 @@
|
|
|
4
4
|
*
|
|
5
5
|
* It COMPOSES the other commands' cores in-process (no shelling out to `tot …`):
|
|
6
6
|
*
|
|
7
|
-
* preflight collectChecks()
|
|
7
|
+
* preflight collectChecks() — self-healing where it can be
|
|
8
8
|
* login establishSession() (developer OAuth; offers inline sign-in when
|
|
9
|
-
* there's no session yet — no re-run — see tryResolveSession)
|
|
10
|
-
*
|
|
9
|
+
* there's no session yet — no re-run — see tryResolveSession),
|
|
10
|
+
* run concurrently: neither gates the other; wait on the slower
|
|
11
11
|
* ↓
|
|
12
12
|
* store normalizeStores(client_list) → auto-pick if exactly one, else
|
|
13
|
-
* use --tenant, else the remembered last tenant, else choose
|
|
13
|
+
* use --tenant, else the remembered last tenant, else choose
|
|
14
14
|
* ↓
|
|
15
15
|
* checkout ─┬─ checkoutTenant() → ./<tenant>
|
|
16
16
|
* runner ─┘ prefetch the runner: the native renderer artifact by default
|
|
17
|
-
* (
|
|
17
|
+
* (no Docker prerequisite), or the Docker registry
|
|
18
18
|
* credential on --docker / as the automatic fallback if the
|
|
19
19
|
* native artifact can't be fetched (same fallback `tot dev`
|
|
20
|
-
* uses — see dev.mjs#NativeArtifactUnavailableError)
|
|
21
|
-
*
|
|
22
|
-
* session resolved above
|
|
20
|
+
* uses — see dev.mjs#NativeArtifactUnavailableError), run
|
|
21
|
+
* concurrently with checkout: both just need the
|
|
22
|
+
* session resolved above
|
|
23
23
|
* ↓
|
|
24
24
|
* dev spawnNativeDev() or spawnDevContainer() → wait for ready →
|
|
25
|
-
* open the browser
|
|
25
|
+
* open the browser
|
|
26
26
|
* ↓
|
|
27
|
-
* you're live crafted ending (elapsed time printed
|
|
28
|
-
* IDEAS[0]
|
|
27
|
+
* you're live crafted ending (elapsed time printed), seeded with
|
|
28
|
+
* IDEAS[0], THEN the optional "Connect Claude?" prompt: on
|
|
29
29
|
* yes, `claude mcp add` AND drop straight into that seeded
|
|
30
|
-
* prompt
|
|
30
|
+
* prompt — the payoff, not a pointer to go write one.
|
|
31
31
|
*
|
|
32
32
|
* ZERO-LOGIN FREE TASTE (--sample / no session): when the user passes --sample,
|
|
33
33
|
* or has no Token of Trust session (and doesn't decline), `tot start` SKIPS
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
* ends on the SAME "connect the ToT MCP for the real power" opt-in. The MCP is
|
|
37
37
|
* repositioned as the upsell, not a gate. See runSampleStart + sample.mjs.
|
|
38
38
|
*
|
|
39
|
-
* Value-first ordering
|
|
39
|
+
* Value-first ordering: the running site + browser come BEFORE any mention of
|
|
40
40
|
* connecting the MCP; Claude is the supercharge offered after the aha, not a gate.
|
|
41
|
-
* ONE felt browser login
|
|
41
|
+
* ONE felt browser login: `claude mcp add` only ever runs behind that final
|
|
42
42
|
* opt-in — the core loop above needs nothing but the cached `tot login` session.
|
|
43
|
-
* Every failure ends with the exact next command via the shared formatter
|
|
43
|
+
* Every failure ends with the exact next command via the shared formatter.
|
|
44
44
|
*
|
|
45
45
|
* Dependency-free (node:readline/promises + the cores above).
|
|
46
46
|
*/
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
activityBridgeEnv, NativeArtifactUnavailableError,
|
|
68
68
|
} from "./dev.mjs";
|
|
69
69
|
import { scaffoldSample, isSampleCheckout, sampleConfig, SAMPLE_DIR_NAME } from "../sample.mjs";
|
|
70
|
-
import { startHeartbeatFromEnv } from "../dev-heartbeat.mjs";
|
|
70
|
+
import { startHeartbeatFromEnv, resolveEditor } from "../dev-heartbeat.mjs";
|
|
71
71
|
import { streamDevLogs } from "../dev-logs.mjs";
|
|
72
72
|
import { milestoneBanner, cockpitUrlFrom, DEVELOPER_COCKPIT } from "../banner.mjs";
|
|
73
73
|
import { IDEAS } from "./ideas.mjs";
|
|
@@ -130,7 +130,7 @@ export function pickStore(stores) {
|
|
|
130
130
|
/**
|
|
131
131
|
* Decide which tenant to use, given the identity's available stores, an
|
|
132
132
|
* explicit `--tenant` override, and a remembered "last tenant" from a prior
|
|
133
|
-
* run
|
|
133
|
+
* run. Pure + exported so the precedence is unit-tested without any I/O.
|
|
134
134
|
* Precedence: explicit > single store (pickStore) > remembered (if still one
|
|
135
135
|
* of the available stores) > ambiguous ("many" — caller must prompt).
|
|
136
136
|
* @param {Array<{id:string,name:string}>} stores
|
|
@@ -191,7 +191,7 @@ export async function run(argv, ctx) {
|
|
|
191
191
|
|
|
192
192
|
const startedAt = Date.now();
|
|
193
193
|
|
|
194
|
-
// Explicit free taste: skip login/store/checkout entirely (
|
|
194
|
+
// Explicit free taste: skip login/store/checkout entirely (zero-login).
|
|
195
195
|
if (decideStartMode({ sampleFlag: args.sample, hasSession: true }) === "sample") {
|
|
196
196
|
return runSampleStart(args, ctx, env, startedAt);
|
|
197
197
|
}
|
|
@@ -205,7 +205,7 @@ export async function run(argv, ctx) {
|
|
|
205
205
|
const client = createMcpClient(baseUrl);
|
|
206
206
|
|
|
207
207
|
// Resolve a session, tolerating a no-session / no-network condition so we can
|
|
208
|
-
// offer the free local preview instead of dead-ending
|
|
208
|
+
// offer the free local preview instead of dead-ending.
|
|
209
209
|
const session = await tryResolveSession(client, env, args);
|
|
210
210
|
if (decideStartMode({ sampleFlag: false, hasSession: !!session }) === "sample") {
|
|
211
211
|
if (!(await confirmSampleFallback(args))) {
|
|
@@ -216,18 +216,18 @@ export async function run(argv, ctx) {
|
|
|
216
216
|
return await runSampleStart(args, ctx, env, startedAt);
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
-
// Authenticated path. Preflight
|
|
219
|
+
// Authenticated path. Preflight now that we know we're taking it — the
|
|
220
220
|
// local machine checks (node/git/Docker) that the authed loop needs.
|
|
221
221
|
await preflight(ctx, env);
|
|
222
222
|
console.log(` ✓ signed in as ${describeIdentity(session)} via ${mcpOrigin(baseUrl)}`);
|
|
223
223
|
|
|
224
224
|
// Resolve the activity-bridge credential + the hosted Developer Cockpit URL
|
|
225
225
|
// ONCE, up front — both the pre-pick "keep the cockpit alive" heartbeat below
|
|
226
|
-
//
|
|
226
|
+
// and the post-boot heartbeat + milestone banners use them.
|
|
227
227
|
const bridgeEnv = activityBridgeEnv(env);
|
|
228
228
|
const cockpitUrl = cockpitUrlFrom(bridgeEnv.TOT_DEV_ACTIVITY_URL);
|
|
229
229
|
|
|
230
|
-
//
|
|
230
|
+
// A >1-store identity blocks on the interactive store picker below, and
|
|
231
231
|
// (before this) the hosted cockpit got NO heartbeat until the dev server
|
|
232
232
|
// spawned much later, so a multi-store dev's cockpit sat on Step 1 forever
|
|
233
233
|
// while the terminal quietly waited for a number. Start beating NOW (no local
|
|
@@ -236,7 +236,7 @@ export async function run(argv, ctx) {
|
|
|
236
236
|
// the live localhost URL) takes over once we have it.
|
|
237
237
|
const stopEarlyHeartbeat = startHeartbeatFromEnv(bridgeEnv, {});
|
|
238
238
|
|
|
239
|
-
// 3. store — auto-pick, use --tenant, use the remembered one, or choose
|
|
239
|
+
// 3. store — auto-pick, use --tenant, use the remembered one, or choose.
|
|
240
240
|
// Keep the raw client_list so a tool ERROR (unauthenticated / not-entitled) is
|
|
241
241
|
// surfaced with who + where, not collapsed to an empty list (fb-...f45zg9).
|
|
242
242
|
let tenant;
|
|
@@ -254,31 +254,31 @@ export async function run(argv, ctx) {
|
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
// 4. checkout → ./<tenant> (reuse an existing checkout on a re-run),
|
|
257
|
-
// OVERLAPPED
|
|
258
|
-
// default (
|
|
257
|
+
// OVERLAPPED with prefetching the runner: the native artifact by
|
|
258
|
+
// default (no Docker prerequisite), or the Docker registry
|
|
259
259
|
// credential on --docker / as the automatic native-unavailable fallback.
|
|
260
260
|
// Both are independent authenticated calls once login (above) has
|
|
261
261
|
// resolved, so there's no reason to pay for them serially.
|
|
262
262
|
const dir = resolve(process.cwd(), tenant);
|
|
263
263
|
// Resolve a free port so the URL we open/poll/print matches what the runner
|
|
264
264
|
// binds (Vite strictPort is off → a busy port would drift). No-op if free.
|
|
265
|
-
const devArgs = {
|
|
265
|
+
const devArgs = /** @type {any} */ ({
|
|
266
266
|
image: null, port: String(await firstFreePort(Number(args.port || 4321))), mcp: args.mcp,
|
|
267
267
|
noLogin: false, noOpen: args.noOpen, docker: args.docker,
|
|
268
|
-
};
|
|
269
|
-
const runtime = { useDocker: args.docker, runnerDir: null };
|
|
268
|
+
});
|
|
269
|
+
const runtime = /** @type {{ useDocker: boolean, runnerDir: any }} */ ({ useDocker: args.docker, runnerDir: null });
|
|
270
270
|
await Promise.all([
|
|
271
271
|
ensureCheckout(client, tenant, dir, env),
|
|
272
272
|
prefetchRuntime(client, devArgs, env, runtime, ctx),
|
|
273
273
|
]);
|
|
274
274
|
|
|
275
|
-
//
|
|
275
|
+
// Say EXACTLY where the code landed + THE one next step, so a freshly
|
|
276
276
|
// checked-out dev is never left wondering what to open. Absolute path (works
|
|
277
277
|
// from any directory) and a single, unambiguous action.
|
|
278
278
|
printCheckoutLanding(dir, cockpitUrl);
|
|
279
279
|
|
|
280
|
-
// 5. dev — native by default
|
|
281
|
-
// above; wait for the server, open the browser
|
|
280
|
+
// 5. dev — native by default, Docker on --docker or the fallback
|
|
281
|
+
// above; wait for the server, open the browser.
|
|
282
282
|
const ctxDev = detectContext(dir);
|
|
283
283
|
let url, handle;
|
|
284
284
|
if (runtime.useDocker) {
|
|
@@ -292,11 +292,11 @@ export async function run(argv, ctx) {
|
|
|
292
292
|
handle = spawnNativeDev(runtime.runnerDir, dir, devArgs.port, { stdio: "piped", env: bridgeEnv });
|
|
293
293
|
}
|
|
294
294
|
|
|
295
|
-
// Heartbeat the hosted cockpit
|
|
295
|
+
// Heartbeat the hosted cockpit with the CLI version + this live localhost
|
|
296
296
|
// URL for the life of the run — CLI-side, using the cached bridge credential
|
|
297
297
|
// (no-op when none is cached). The runner keeps reporting file-saves itself.
|
|
298
298
|
// Hand off from the pre-pick (URL-less) beat to this URL-bearing one so the
|
|
299
|
-
// cockpit's live local link lights up exactly when the server is up
|
|
299
|
+
// cockpit's live local link lights up exactly when the server is up.
|
|
300
300
|
stopEarlyHeartbeat();
|
|
301
301
|
const stopHeartbeat = startHeartbeatFromEnv(bridgeEnv, { url, cwd: dir });
|
|
302
302
|
handle.done.finally(() => stopHeartbeat());
|
|
@@ -314,11 +314,11 @@ export async function run(argv, ctx) {
|
|
|
314
314
|
console.log(" ✓ opened your browser");
|
|
315
315
|
}
|
|
316
316
|
|
|
317
|
-
// 6. you're live
|
|
318
|
-
//
|
|
317
|
+
// 6. you're live — land on the store ROOT (not /dev), print elapsed time
|
|
318
|
+
// so the "instant" claim is measured. The "Connect Claude" step is
|
|
319
319
|
// intentionally removed for now — a blocking prompt here meant Ctrl-C'ing it
|
|
320
320
|
// tore down the dev server; revisit AI-connect as a non-blocking step later.
|
|
321
|
-
printLiveEnding(tenant, url, formatElapsed(Date.now() - startedAt), cockpitUrl);
|
|
321
|
+
printLiveEnding(tenant, url, formatElapsed(Date.now() - startedAt), cockpitUrl, dir);
|
|
322
322
|
|
|
323
323
|
// 7. hand the terminal to the running dev server until Ctrl-C.
|
|
324
324
|
console.log("\n Watching your store — edit content/home.html + save. Ctrl-C to stop.\n");
|
|
@@ -463,10 +463,10 @@ async function confirmSampleFallback(args) {
|
|
|
463
463
|
}
|
|
464
464
|
|
|
465
465
|
/**
|
|
466
|
-
* Self-healing preflight
|
|
467
|
-
* ends with the next command. Docker is informational-only here
|
|
466
|
+
* Self-healing preflight: run the readiness checks; a blocking failure
|
|
467
|
+
* ends with the next command. Docker is informational-only here — native
|
|
468
468
|
* is the default runtime, so Docker not running doesn't block `tot start`
|
|
469
|
-
* anymore
|
|
469
|
+
* anymore — its own readiness is handled lazily in prefetchDockerLogin(),
|
|
470
470
|
* only when the run actually needs it (--docker or the native fallback).
|
|
471
471
|
*/
|
|
472
472
|
async function preflight(ctx, env) {
|
|
@@ -482,8 +482,8 @@ async function preflight(ctx, env) {
|
|
|
482
482
|
}
|
|
483
483
|
|
|
484
484
|
/**
|
|
485
|
-
* Prefetch the runner
|
|
486
|
-
* for serially afterward: the native renderer artifact by default (
|
|
485
|
+
* Prefetch the runner so it's overlapped with checkout instead of paid
|
|
486
|
+
* for serially afterward: the native renderer artifact by default (no
|
|
487
487
|
* Docker prerequisite), or the Docker registry pull credential on --docker.
|
|
488
488
|
*
|
|
489
489
|
* On a native (entitled-artifact) fetch failure the runtime stays NATIVE: it
|
|
@@ -524,8 +524,8 @@ async function prefetchRuntime(client, devArgs, env, runtime, ctx) {
|
|
|
524
524
|
|
|
525
525
|
/**
|
|
526
526
|
* The Docker-path half of prefetchRuntime: registry login (reusing the
|
|
527
|
-
* already-authenticated client
|
|
528
|
-
* (self-healing
|
|
527
|
+
* already-authenticated client) plus a best-effort Docker auto-start
|
|
528
|
+
* (self-healing) so buildContainerPlan doesn't have to hard-fail on a
|
|
529
529
|
* Docker Desktop that just needs a nudge.
|
|
530
530
|
*/
|
|
531
531
|
async function prefetchDockerLogin(client, devArgs, env) {
|
|
@@ -570,7 +570,7 @@ function noStoresError({ session, baseUrl, listErr, list = null }) {
|
|
|
570
570
|
{ next: "run `tot whoami` to check your session, or `tot login` again — then re-run `tot start`" },
|
|
571
571
|
);
|
|
572
572
|
}
|
|
573
|
-
// Status-aware
|
|
573
|
+
// Status-aware: an UNLINKED identity is pointed at `tot link`, not the
|
|
574
574
|
// misleading "ask for a store invite" copy; the genuine zero-grants case keeps it.
|
|
575
575
|
const g = noStoresGuidance(list);
|
|
576
576
|
return new CliError(`signed in as ${who} via ${origin}, but ${g.headline}`, { next: g.next });
|
|
@@ -578,9 +578,13 @@ function noStoresError({ session, baseUrl, listErr, list = null }) {
|
|
|
578
578
|
|
|
579
579
|
/**
|
|
580
580
|
* Resolve the tenant to work on from the (normalized) store list, args, and
|
|
581
|
-
* the remembered last tenant
|
|
581
|
+
* the remembered last tenant — then remember whatever was decided so the
|
|
582
582
|
* next bare `tot start` doesn't have to ask again.
|
|
583
583
|
*/
|
|
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
|
+
*/
|
|
584
588
|
async function resolveTenant(stores, args, env, baseUrl, { session = null, listErr = null, list = null } = {}) {
|
|
585
589
|
const lastTenantPath = defaultLastTenantPath(env);
|
|
586
590
|
const pick = pickTenant(stores, {
|
|
@@ -603,22 +607,23 @@ async function resolveTenant(stores, args, env, baseUrl, { session = null, listE
|
|
|
603
607
|
} else {
|
|
604
608
|
// Many stores, nothing remembered — choose. Non-interactive (no TTY /
|
|
605
609
|
// --yes without a name) can't guess.
|
|
610
|
+
const many = /** @type {any} */ (pick);
|
|
606
611
|
if (!isInteractive() || args.yes) {
|
|
607
612
|
throw new CliError("you can build on several stores — pick one", {
|
|
608
|
-
next: `tot start --tenant <tenant> (one of: ${
|
|
613
|
+
next: `tot start --tenant <tenant> (one of: ${many.stores.map((s) => s.id).join(", ")})`,
|
|
609
614
|
});
|
|
610
615
|
}
|
|
611
616
|
// Loud + unmissable: a >1-store dev is often looking at their browser
|
|
612
617
|
// cockpit (which is waiting on this pick), so make the terminal shout for
|
|
613
|
-
// their attention rather than sitting as a quiet one-liner
|
|
618
|
+
// their attention rather than sitting as a quiet one-liner.
|
|
614
619
|
console.log("");
|
|
615
620
|
console.log(" ⚑ ACTION NEEDED IN YOUR TERMINAL — you can build on several stores.");
|
|
616
621
|
console.log(" Pick one here to light up your cockpit:\n");
|
|
617
|
-
|
|
622
|
+
many.stores.forEach((s, i) => console.log(` ${i + 1}. ${s.id}${s.name ? ` — ${s.name}` : ""}`));
|
|
618
623
|
console.log("");
|
|
619
|
-
const idx = await promptChoice(
|
|
624
|
+
const idx = await promptChoice(many.stores.length);
|
|
620
625
|
console.log("");
|
|
621
|
-
tenant =
|
|
626
|
+
tenant = many.stores[idx].id;
|
|
622
627
|
}
|
|
623
628
|
|
|
624
629
|
writeLastTenant(lastTenantPath, { mcpUrl: baseUrl, tenant });
|
|
@@ -668,7 +673,7 @@ async function waitForBoot(url, handle) {
|
|
|
668
673
|
|
|
669
674
|
/**
|
|
670
675
|
* Format elapsed milliseconds as a short human string for the "you're live"
|
|
671
|
-
* ending
|
|
676
|
+
* ending — measure, don't just claim, "instant". Pure + exported so it's
|
|
672
677
|
* unit-tested without any I/O.
|
|
673
678
|
* @param {number} ms
|
|
674
679
|
* @returns {string|null}
|
|
@@ -679,12 +684,13 @@ export function formatElapsed(ms) {
|
|
|
679
684
|
}
|
|
680
685
|
|
|
681
686
|
/**
|
|
682
|
-
*
|
|
687
|
+
* After checkout, tell the developer EXACTLY where their code landed and
|
|
683
688
|
* THE single next step. `dir` is absolute (from resolve(cwd, tenant)) so the
|
|
684
689
|
* "open this" path works no matter what directory they're in. When we know the
|
|
685
690
|
* Developer Cockpit URL, the one next step is framed as "edit a line → see it in
|
|
686
691
|
* your cockpit"; otherwise it's the same edit-to-see-it-reload action.
|
|
687
692
|
*/
|
|
693
|
+
/** @param {string} dir @param {string|null} [cockpitUrl] */
|
|
688
694
|
function printCheckoutLanding(dir, cockpitUrl = null) {
|
|
689
695
|
console.log("");
|
|
690
696
|
console.log(` 📁 Your store code is at: ${dir}`);
|
|
@@ -693,11 +699,38 @@ function printCheckoutLanding(dir, cockpitUrl = null) {
|
|
|
693
699
|
console.log(` content/home.html — you'll see it reflected in ${where}.`);
|
|
694
700
|
}
|
|
695
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
|
+
|
|
696
723
|
/** The crafted "you're live" ending — a PROMINENT milestone banner (u2) that,
|
|
697
724
|
* when we hold a Developer Cockpit URL, explicitly sends the developer BACK to
|
|
698
|
-
* their cockpit as the next place to look
|
|
699
|
-
*
|
|
700
|
-
|
|
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) {
|
|
701
734
|
const lines = [
|
|
702
735
|
`✨ You're live.${elapsed ? ` (${elapsed})` : ""}`,
|
|
703
736
|
` ${url}`,
|
|
@@ -711,10 +744,19 @@ function printLiveEnding(tenant, url, elapsed, cockpitUrl = null) {
|
|
|
711
744
|
console.log(milestoneBanner(lines));
|
|
712
745
|
console.log(" " + versionStamp("native"));
|
|
713
746
|
console.log("");
|
|
714
|
-
|
|
715
|
-
|
|
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.");
|
|
716
756
|
console.log("");
|
|
717
|
-
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.");
|
|
718
760
|
console.log("");
|
|
719
761
|
}
|
|
720
762
|
|
|
@@ -738,7 +780,7 @@ function printSampleLiveEnding(url, elapsed) {
|
|
|
738
780
|
}
|
|
739
781
|
|
|
740
782
|
/**
|
|
741
|
-
* Offer + wire Claude
|
|
783
|
+
* Offer + wire Claude: `claude mcp add`, then — the payoff, not just a
|
|
742
784
|
* pointer — drop the user straight into an interactive Claude session already
|
|
743
785
|
* seeded with IDEAS[0], so there's no blank slate to stare at.
|
|
744
786
|
*/
|