@ritualai/cli 0.7.16 → 0.8.1
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/dist/commands/init.js +406 -96
- package/dist/commands/init.js.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/build-flow-explainer.js +226 -0
- package/dist/lib/build-flow-explainer.js.map +1 -0
- package/dist/lib/final-cta-box.js +224 -0
- package/dist/lib/final-cta-box.js.map +1 -0
- package/dist/lib/onboarding-state.js +140 -0
- package/dist/lib/onboarding-state.js.map +1 -0
- package/dist/lib/persona-picker.js +171 -0
- package/dist/lib/persona-picker.js.map +1 -0
- package/dist/lib/persona-samples.js +245 -0
- package/dist/lib/persona-samples.js.map +1 -0
- package/dist/lib/project-config.js.map +1 -1
- package/dist/lib/workspace-explainer.js +193 -0
- package/dist/lib/workspace-explainer.js.map +1 -0
- package/dist/lib/workspace-flow.js +8 -7
- package/dist/lib/workspace-flow.js.map +1 -1
- package/package.json +73 -73
- package/skills/claude-code/ritual/.ritual-bundle.json +2 -2
- package/skills/claude-code/ritual/references/build-flow.md +51 -14
- package/skills/codex/ritual/.ritual-bundle.json +2 -2
- package/skills/codex/ritual/references/build-flow.md +51 -14
- package/skills/cursor/ritual/.ritual-bundle.json +2 -2
- package/skills/cursor/ritual/references/build-flow.md +51 -14
- package/skills/gemini/ritual/.ritual-bundle.json +2 -2
- package/skills/gemini/ritual/references/build-flow.md +51 -14
- package/skills/kiro/ritual/.ritual-bundle.json +2 -2
- package/skills/kiro/ritual/references/build-flow.md +51 -14
- package/skills/vscode/ritual/.ritual-bundle.json +2 -2
- package/skills/vscode/ritual/references/build-flow.md +51 -14
package/dist/commands/init.js
CHANGED
|
@@ -39,6 +39,15 @@ const config_1 = require("../lib/config");
|
|
|
39
39
|
const api_client_1 = require("../lib/api-client");
|
|
40
40
|
const pat_store_1 = require("../lib/pat-store");
|
|
41
41
|
const oidc_1 = require("../lib/oidc");
|
|
42
|
+
const onboarding_state_1 = require("../lib/onboarding-state");
|
|
43
|
+
const persona_picker_1 = require("../lib/persona-picker");
|
|
44
|
+
const workspace_explainer_1 = require("../lib/workspace-explainer");
|
|
45
|
+
const build_flow_explainer_1 = require("../lib/build-flow-explainer");
|
|
46
|
+
const persona_samples_1 = require("../lib/persona-samples");
|
|
47
|
+
const project_config_1 = require("../lib/project-config");
|
|
48
|
+
const repo_name_1 = require("../lib/repo-name");
|
|
49
|
+
const colors_1 = require("../lib/colors");
|
|
50
|
+
const final_cta_box_1 = require("../lib/final-cta-box");
|
|
42
51
|
const detector_1 = require("../lib/agents/detector");
|
|
43
52
|
const providers_1 = require("../lib/agents/providers");
|
|
44
53
|
const configure_mcp_1 = require("../lib/agents/configure-mcp");
|
|
@@ -77,6 +86,13 @@ function resolveIssuerForInit(opts) {
|
|
|
77
86
|
return undefined; // let auth-flow.ts pick from env / default
|
|
78
87
|
}
|
|
79
88
|
async function initCommand(opts = {}) {
|
|
89
|
+
// --- --re-onboard reset (must happen BEFORE any "shouldShow*"
|
|
90
|
+
// gate is consulted by the FTUE helpers below, so the screens
|
|
91
|
+
// re-render). Cheap, idempotent — just rewrites
|
|
92
|
+
// `~/.config/ritual/onboarding.json` to an empty record.
|
|
93
|
+
if (opts.reOnboard) {
|
|
94
|
+
(0, onboarding_state_1.resetOnboardingState)();
|
|
95
|
+
}
|
|
80
96
|
// --- Welcome banner (TTY only; skipped for --list early-exit) ----
|
|
81
97
|
// Replaces the previous single-line opener. The banner surfaces
|
|
82
98
|
// identity (when signed in), workspace binding, and "what this
|
|
@@ -410,8 +426,22 @@ async function initCommand(opts = {}) {
|
|
|
410
426
|
console.log(` ✓ Signed in as ${tokenStatus.creds.user?.email ?? tokenStatus.creds.user?.sub ?? 'unknown'}`);
|
|
411
427
|
console.log('');
|
|
412
428
|
}
|
|
413
|
-
// --- 2. Detect agents (
|
|
429
|
+
// --- 2. Detect agents (silently — announcement deferred) --------
|
|
430
|
+
//
|
|
431
|
+
// We resolve which agents to write to NOW because the build-flow
|
|
432
|
+
// explainer below names them in its "your coding agent" zone. But
|
|
433
|
+
// we intentionally suppress the "Detected N coding agents — here
|
|
434
|
+
// are the config files we're about to touch" announcement and the
|
|
435
|
+
// connect-all confirmation gate until AFTER the FTUE explainers.
|
|
436
|
+
//
|
|
437
|
+
// Narrative reason: the user just learned what Ritual is (workspace
|
|
438
|
+
// explainer) and what /ritual build does (build-flow explainer).
|
|
439
|
+
// Only THEN does "OK we're going to wire that up across these N
|
|
440
|
+
// agents" land in the right context. Otherwise they're being asked
|
|
441
|
+
// to confirm a list of agent config writes before they know what
|
|
442
|
+
// Ritual even does.
|
|
414
443
|
let targets;
|
|
444
|
+
let agentSummaryToPrint = null;
|
|
415
445
|
if (opts.agent) {
|
|
416
446
|
const provider = (0, providers_1.findProviderById)(opts.agent);
|
|
417
447
|
if (!provider) {
|
|
@@ -423,7 +453,7 @@ async function initCommand(opts = {}) {
|
|
|
423
453
|
}
|
|
424
454
|
// Explicit `--agent` overrides detection — the user knows what
|
|
425
455
|
// they want, don't make them install/symlink the binary just
|
|
426
|
-
// to pass detection.
|
|
456
|
+
// to pass detection. Nothing to announce; they typed the name.
|
|
427
457
|
targets = [
|
|
428
458
|
{
|
|
429
459
|
id: provider.id,
|
|
@@ -450,73 +480,20 @@ async function initCommand(opts = {}) {
|
|
|
450
480
|
provider: claudeProvider,
|
|
451
481
|
},
|
|
452
482
|
];
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
}
|
|
460
|
-
console.log('');
|
|
483
|
+
agentSummaryToPrint = {
|
|
484
|
+
scenario: 'none-detected',
|
|
485
|
+
targets,
|
|
486
|
+
detected: [],
|
|
487
|
+
notDetected,
|
|
488
|
+
};
|
|
461
489
|
}
|
|
462
490
|
else if (detected.length === 1) {
|
|
463
|
-
// Single-detect: lead with what we're about to do, mention
|
|
464
|
-
// the escape hatch quietly. No menu, no confirmation —
|
|
465
|
-
// safe default + visible action (per CLI tenet: lead with
|
|
466
|
-
// single recommended action + escape hatch).
|
|
467
491
|
targets = detected;
|
|
468
|
-
|
|
469
|
-
console.log(` Detected ${t.name} locally — connecting Ritual there.`);
|
|
470
|
-
if (t.provider.mcpConfigPath) {
|
|
471
|
-
console.log(` Config: ${t.provider.mcpConfigPath}`);
|
|
472
|
-
}
|
|
473
|
-
else {
|
|
474
|
-
console.log(` Config: managed by \`${t.provider.id}\` CLI`);
|
|
475
|
-
}
|
|
476
|
-
if (notDetected.length > 0) {
|
|
477
|
-
console.log('');
|
|
478
|
-
console.log(` Also supported (not detected here): ${notDetected.map((d) => d.name).join(', ')}`);
|
|
479
|
-
console.log(' Connect another with `ritual init --agent <id>`.');
|
|
480
|
-
}
|
|
481
|
-
console.log('');
|
|
492
|
+
agentSummaryToPrint = { scenario: 'single', targets, detected, notDetected };
|
|
482
493
|
}
|
|
483
494
|
else {
|
|
484
|
-
// Multi-detect: this IS a real decision gate (one agent vs
|
|
485
|
-
// seven means writing to one config file vs seven). Tell
|
|
486
|
-
// the user EXACTLY which files we're about to touch, then
|
|
487
|
-
// pause with a single-keystroke escape hatch. We don't ask
|
|
488
|
-
// "which agent?" — connecting all detected agents is the
|
|
489
|
-
// safe recommended default. Power users can scope down by
|
|
490
|
-
// pressing Ctrl-C and re-running with `--agent <id>`.
|
|
491
495
|
targets = detected;
|
|
492
|
-
|
|
493
|
-
for (const d of detected) {
|
|
494
|
-
const cfg = d.provider.mcpConfigPath
|
|
495
|
-
? d.provider.mcpConfigPath
|
|
496
|
-
: `managed by \`${d.provider.id}\` CLI`;
|
|
497
|
-
console.log(` • ${d.name.padEnd(20)} ${cfg}`);
|
|
498
|
-
}
|
|
499
|
-
if (notDetected.length > 0) {
|
|
500
|
-
console.log('');
|
|
501
|
-
console.log(` Also supported (not detected here): ${notDetected.map((d) => d.name).join(', ')}`);
|
|
502
|
-
}
|
|
503
|
-
console.log('');
|
|
504
|
-
// TTY-only pause. Non-TTY (CI / scripted installs) skips
|
|
505
|
-
// straight through — we already printed exactly what we're
|
|
506
|
-
// about to do, and pausing in CI would hang forever.
|
|
507
|
-
if (process.stdin.isTTY) {
|
|
508
|
-
try {
|
|
509
|
-
await (0, prompt_1.prompt)(' Press Enter to connect all detected agents, or Ctrl-C to abort\n' +
|
|
510
|
-
' (to pick a single agent, abort and re-run with `ritual init --agent <id>`): ');
|
|
511
|
-
}
|
|
512
|
-
catch {
|
|
513
|
-
console.log('');
|
|
514
|
-
console.log(' Aborted.');
|
|
515
|
-
process.exitCode = 1;
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
console.log('');
|
|
496
|
+
agentSummaryToPrint = { scenario: 'multi', targets, detected, notDetected };
|
|
520
497
|
}
|
|
521
498
|
}
|
|
522
499
|
// Type guard for the compiler — by this point one of three paths
|
|
@@ -547,9 +524,35 @@ async function initCommand(opts = {}) {
|
|
|
547
524
|
process.exitCode = 1;
|
|
548
525
|
return;
|
|
549
526
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
//
|
|
527
|
+
// Note: the "✓ MCP key created — stored in: <agent list>" block is
|
|
528
|
+
// printed AFTER the FTUE explainers + agent-connect confirmation
|
|
529
|
+
// (see step 3.8.5 below). The mint itself happens here so any
|
|
530
|
+
// failure short-circuits init before we've spent the user's time
|
|
531
|
+
// teaching the mental model; we just defer the user-visible noise.
|
|
532
|
+
// --- 3.4 FTUE persona picker (one-time per machine) --------------
|
|
533
|
+
// Runs AFTER auth + PAT mint (so all the mechanical "make Ritual
|
|
534
|
+
// work" friction is behind us — token cap recovery dialogs, etc.,
|
|
535
|
+
// don't ambush the user mid-onboarding question) and BEFORE the
|
|
536
|
+
// workspace explainer (so the explainer can render in the
|
|
537
|
+
// persona's accent color).
|
|
538
|
+
const pickedPersona = await maybeRunPersonaPicker(opts);
|
|
539
|
+
const projectDir = opts.cwd ?? process.cwd();
|
|
540
|
+
// --- 3.5 FTUE workspace explainer (BEFORE bind) -----------------
|
|
541
|
+
// The user needs to understand what a workspace IS before being
|
|
542
|
+
// asked to create one. We render with the *detected* name (from
|
|
543
|
+
// git-remote or cwd-basename) — the same name resolveProjectWorkspace
|
|
544
|
+
// will offer as the default in the next step. If they accept the
|
|
545
|
+
// default the displayed name matches what gets bound; if they
|
|
546
|
+
// rename, no harm done — the explainer was teaching the concept,
|
|
547
|
+
// not committing the value.
|
|
548
|
+
//
|
|
549
|
+
// We use `loadProjectConfig` first so an already-bound repo (where
|
|
550
|
+
// the explainer would otherwise re-show on a different machine via
|
|
551
|
+
// onboarding-state) uses the bound name, not a stale detection.
|
|
552
|
+
const existingBind = (0, project_config_1.loadProjectConfig)(projectDir);
|
|
553
|
+
const detectedNameForExplainer = existingBind?.workspaceName ?? (0, repo_name_1.detectRepoName)(projectDir).name;
|
|
554
|
+
maybeShowWorkspaceExplainer(detectedNameForExplainer, pickedPersona);
|
|
555
|
+
// --- 3.6 Bind a project workspace --------------------------------
|
|
553
556
|
// Project-scoped state — see ./../lib/workspace-flow.ts for the
|
|
554
557
|
// resolution rules. The result is null when:
|
|
555
558
|
// - .ritual/config.json already exists (no change to make)
|
|
@@ -560,12 +563,60 @@ async function initCommand(opts = {}) {
|
|
|
560
563
|
// We don't fail init when this returns null — the project just
|
|
561
564
|
// doesn't get a default workspace bound, and downstream tools will
|
|
562
565
|
// prompt for one on first use.
|
|
563
|
-
|
|
566
|
+
//
|
|
564
567
|
// Commander semantics: `--no-workspace` sets `opts.workspace=false`.
|
|
565
568
|
// Default (no flag) leaves it `undefined`. Treat anything except
|
|
566
569
|
// explicit false as "go ahead and prompt".
|
|
570
|
+
let boundWorkspaceName = existingBind?.workspaceName ?? null;
|
|
567
571
|
if (opts.workspace !== false) {
|
|
568
|
-
await (0, workspace_flow_1.resolveProjectWorkspace)({ api, projectDir });
|
|
572
|
+
const bound = await (0, workspace_flow_1.resolveProjectWorkspace)({ api, projectDir });
|
|
573
|
+
boundWorkspaceName = bound?.workspaceName ?? boundWorkspaceName;
|
|
574
|
+
}
|
|
575
|
+
// --- 3.7 Persist personaSlug onto the project config -------------
|
|
576
|
+
// Repo-team-wide default. Per-machine onboarding-state already
|
|
577
|
+
// holds the user's pick; here we mirror it into .ritual/config.json
|
|
578
|
+
// so collaborators on the repo who run `ritual init` later inherit
|
|
579
|
+
// the same default template, and so `/ritual build` in this repo
|
|
580
|
+
// can read it without touching the user's home directory.
|
|
581
|
+
//
|
|
582
|
+
// We only write if (a) we have a slug picked this run AND (b) the
|
|
583
|
+
// project config either doesn't have one yet or has an explicit
|
|
584
|
+
// override request via `--persona`. Never overwrite a teammate's
|
|
585
|
+
// committed pick silently on a routine re-init.
|
|
586
|
+
if (pickedPersona) {
|
|
587
|
+
const existing = (0, project_config_1.loadProjectConfig)(projectDir);
|
|
588
|
+
if (existing) {
|
|
589
|
+
const shouldUpdate = !existing.personaSlug || (opts.persona && existing.personaSlug !== pickedPersona);
|
|
590
|
+
if (shouldUpdate) {
|
|
591
|
+
(0, project_config_1.saveProjectConfig)(projectDir, { ...existing, personaSlug: pickedPersona });
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// --- 3.8 Announce agent-config writes + gate on Enter ----------
|
|
596
|
+
// Agent connection is the LAST mechanical setup step before the
|
|
597
|
+
// build-flow explainer (which sits below as a "here's what
|
|
598
|
+
// /ritual build will do" payoff). User flow:
|
|
599
|
+
// workspace explainer → bind → wire agents → see /ritual build
|
|
600
|
+
// shape → "Setup complete" → they run /ritual build.
|
|
601
|
+
const proceed = await printAgentSummaryAndConfirm(agentSummaryToPrint);
|
|
602
|
+
if (!proceed) {
|
|
603
|
+
process.exitCode = 1;
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
// --- 3.9 FTUE build-flow explainer (right before "Setup complete") -
|
|
607
|
+
// Now that workspace + agents are wired, show the user what
|
|
608
|
+
// /ritual build will look like — this is the payoff screen, the
|
|
609
|
+
// last thing before they go run the actual command.
|
|
610
|
+
maybeShowBuildFlowExplainer(pickedPersona, targets.filter((t) => t.detected).map((t) => t.name));
|
|
611
|
+
// --- 3.9.5 Print the deferred "MCP key created" block -----------
|
|
612
|
+
// The PAT was minted silently in step 3; we held the user-visible
|
|
613
|
+
// summary until here. Only printed in verbose mode — compact mode
|
|
614
|
+
// (the default) folds this into the "Ready to ship" CTA below
|
|
615
|
+
// without listing PAT name, scope, expiry, or storage paths
|
|
616
|
+
// (those live one command away: `ritual init --verbose`).
|
|
617
|
+
if (opts.verbose) {
|
|
618
|
+
printKeyCreatedBlock(pat, (0, oidc_1.webAppUrlFromIssuer)(issuer), targets);
|
|
619
|
+
console.log('');
|
|
569
620
|
}
|
|
570
621
|
// --- 4. Copy skills + register MCP for each target agent ---------
|
|
571
622
|
const copyResults = [];
|
|
@@ -615,36 +666,76 @@ async function initCommand(opts = {}) {
|
|
|
615
666
|
}
|
|
616
667
|
// --- 5. Summary --------------------------------------------------
|
|
617
668
|
//
|
|
618
|
-
// Two
|
|
669
|
+
// Two render paths, chosen by `--verbose`:
|
|
619
670
|
//
|
|
620
|
-
// (
|
|
621
|
-
//
|
|
622
|
-
//
|
|
623
|
-
//
|
|
671
|
+
// COMPACT (default) — a single "Ready to ship" split-column box
|
|
672
|
+
// directly after the build-flow explainer above. Left side:
|
|
673
|
+
// per-agent restart instructions + the literal command to ask
|
|
674
|
+
// the coding agent next (`/ritual build "<your feature>"`).
|
|
675
|
+
// Right side: 3 secondary commands the user can invoke if
|
|
676
|
+
// they want detail (`ritual init --verbose`, `ritual doctor`,
|
|
677
|
+
// `ritual graph status`). The PAT name / scope / expiry,
|
|
678
|
+
// per-agent status table, skill copy paths, and verify-then-
|
|
679
|
+
// restart ceremony all live behind `--verbose`. Compact is
|
|
680
|
+
// the FTUE default: turn the moment into action instead of
|
|
681
|
+
// burying the CTA under setup transcript.
|
|
624
682
|
//
|
|
625
|
-
// (
|
|
626
|
-
//
|
|
627
|
-
//
|
|
628
|
-
//
|
|
629
|
-
//
|
|
630
|
-
//
|
|
631
|
-
|
|
632
|
-
//
|
|
633
|
-
//
|
|
634
|
-
//
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
683
|
+
// VERBOSE (--verbose) — legacy 3-block dump:
|
|
684
|
+
// (a) `Coding agent status:` — dotted-aligned table of every
|
|
685
|
+
// known agent w/ `configured` / `not detected` / `failed`.
|
|
686
|
+
// (b) `.gitignore` one-liner — fail-soft surface for the
|
|
687
|
+
// scaffolded-paths gitignore update.
|
|
688
|
+
// (c) `Next steps:` — verify-then-restart pattern, includes
|
|
689
|
+
// the ⚠ inside-Claude warning, `claude mcp list`
|
|
690
|
+
// verification, per-agent restart hints, and quick-
|
|
691
|
+
// reference command list. This is what a power user
|
|
692
|
+
// invokes when they want to see exactly what landed on
|
|
693
|
+
// disk. Same detail is also reachable via `ritual doctor`.
|
|
694
|
+
if (opts.verbose) {
|
|
695
|
+
printDetectedAgentsBlock(targets, copyResults, registrationResults);
|
|
696
|
+
if (gitignoreResult?.changed) {
|
|
697
|
+
const verb = gitignoreResult.preExisting ? 'Updated' : 'Created';
|
|
698
|
+
const count = gitignoreResult.entries.length;
|
|
699
|
+
const preview = count <= 2
|
|
700
|
+
? gitignoreResult.entries.join(', ')
|
|
701
|
+
: `${gitignoreResult.entries.slice(0, 1).join(', ')} + ${count - 1} more`;
|
|
702
|
+
console.log(` ✓ ${verb} .gitignore (added ${preview})`);
|
|
703
|
+
}
|
|
704
|
+
printNextStepsBlock({
|
|
705
|
+
targets,
|
|
706
|
+
registrationResults,
|
|
707
|
+
mcpUrl,
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
// Compact: render the "Ready to ship" CTA box. The inside-
|
|
712
|
+
// Claude warning is safety-critical (the CURRENT session
|
|
713
|
+
// won't see the new MCP server until restart) so it surfaces
|
|
714
|
+
// in both modes; the CTA box renders it as a bold amber row
|
|
715
|
+
// above the restart bullets.
|
|
716
|
+
const wiredAgentNames = targets
|
|
717
|
+
.filter((t, i) => registrationResults[i]?.success)
|
|
718
|
+
.map((t) => t.name);
|
|
719
|
+
const insideClaudeWarning = wiredAgentNames.includes('Claude Code') && isInsideClaudeCode()
|
|
720
|
+
? 'inside Claude Code — restart this session first'
|
|
721
|
+
: '';
|
|
722
|
+
// Tier the CTA renderer to the terminal's actual width. The
|
|
723
|
+
// rich split-column box wants ≥ 112 cols (LEFT_W=64 + divider
|
|
724
|
+
// + ~45-char right column + chrome) — anything narrower would
|
|
725
|
+
// either truncate the secondary command labels or push the
|
|
726
|
+
// inner divider out of alignment. Below that, fall through to
|
|
727
|
+
// the styled vertical stack (still ANSI-color-aware) which
|
|
728
|
+
// works cleanly at 78+. Below 78 — or non-TTY — colors degrade
|
|
729
|
+
// to plain via the colors.ts auto-detection.
|
|
730
|
+
const isTty = !!process.stdout.isTTY;
|
|
731
|
+
const cols = process.stdout.columns ?? 0;
|
|
732
|
+
if (isTty && cols >= 112) {
|
|
733
|
+
(0, final_cta_box_1.printFinalCtaBox)({ wiredAgentNames, insideClaudeWarning });
|
|
734
|
+
}
|
|
735
|
+
else {
|
|
736
|
+
(0, final_cta_box_1.printFinalCtaBoxTerse)({ wiredAgentNames, insideClaudeWarning });
|
|
737
|
+
}
|
|
738
|
+
}
|
|
648
739
|
}
|
|
649
740
|
// webAppUrlFromIssuer is exported from lib/oidc.ts — single source of
|
|
650
741
|
// truth so the dev/prod/self-hosted mapping stays consistent across
|
|
@@ -868,4 +959,223 @@ function isInsideClaudeCode() {
|
|
|
868
959
|
!!process.env.CLAUDE_CODE_ENTRYPOINT ||
|
|
869
960
|
!!process.env.CLAUDE_CODE_SESSION_ID);
|
|
870
961
|
}
|
|
962
|
+
/**
|
|
963
|
+
* Render the agent-connection summary (which configs we're about to
|
|
964
|
+
* touch) and, in the multi-detect case, gate on Enter-to-continue.
|
|
965
|
+
*
|
|
966
|
+
* Returns `false` if the user aborted; the caller must short-circuit
|
|
967
|
+
* the rest of init and set process.exitCode = 1. `true` means proceed.
|
|
968
|
+
*
|
|
969
|
+
* For the `explicit` path (--agent <id>) the caller passes null and
|
|
970
|
+
* we skip this entirely — the user typed the agent name, no surprise
|
|
971
|
+
* to soften.
|
|
972
|
+
*/
|
|
973
|
+
async function printAgentSummaryAndConfirm(summary) {
|
|
974
|
+
if (!summary)
|
|
975
|
+
return true;
|
|
976
|
+
// Shared 78-char box helpers — same width as the FTUE explainers
|
|
977
|
+
// so the agent-connect step reads as part of the same visual
|
|
978
|
+
// family rather than a console dump in the middle of boxed UI.
|
|
979
|
+
const W = 78;
|
|
980
|
+
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, ''); // eslint-disable-line no-control-regex
|
|
981
|
+
const padded = (content) => {
|
|
982
|
+
const visLen = stripAnsi(content).length;
|
|
983
|
+
const pad = ' '.repeat(Math.max(0, W - 2 - visLen));
|
|
984
|
+
return `│${content}${pad}│`;
|
|
985
|
+
};
|
|
986
|
+
const top = (title) => {
|
|
987
|
+
const visLen = stripAnsi(title).length;
|
|
988
|
+
const fill = '─'.repeat(Math.max(0, W - visLen - 4));
|
|
989
|
+
return `╭ ${title} ${fill}╮`;
|
|
990
|
+
};
|
|
991
|
+
const bottom = '╰' + '─'.repeat(W - 2) + '╯';
|
|
992
|
+
const renderBox = (titleBar, bodyLines) => {
|
|
993
|
+
console.log(top(titleBar));
|
|
994
|
+
console.log(padded(''));
|
|
995
|
+
for (const line of bodyLines) {
|
|
996
|
+
console.log(padded(line));
|
|
997
|
+
}
|
|
998
|
+
console.log(padded(''));
|
|
999
|
+
console.log(bottom);
|
|
1000
|
+
};
|
|
1001
|
+
// ─── scenario: none detected ────────────────────────────────────
|
|
1002
|
+
if (summary.scenario === 'none-detected') {
|
|
1003
|
+
const claude = summary.targets[0];
|
|
1004
|
+
renderBox((0, colors_1.color)(colors_1.RITUAL_TEAL, '─ wire your coding agent '), [
|
|
1005
|
+
` ${(0, colors_1.dim)('no coding agents detected on this machine — defaulting to:')}`,
|
|
1006
|
+
'',
|
|
1007
|
+
` ${(0, colors_1.boldColor)(colors_1.RITUAL_TEAL, '✓ ' + claude.name)} ${(0, colors_1.dim)('(the documented MCP launch partner)')}`,
|
|
1008
|
+
'',
|
|
1009
|
+
` ${(0, colors_1.dim)('also supported (re-run with --agent <id>):')}`,
|
|
1010
|
+
...providers_1.PROVIDERS.filter((p) => p.id !== claude.id).map((p) => ` ${(0, colors_1.dim)('·')} ${(0, colors_1.dim)(p.name.padEnd(20))} ${(0, colors_1.dim)('--agent ' + p.id)}`),
|
|
1011
|
+
]);
|
|
1012
|
+
console.log('');
|
|
1013
|
+
return true;
|
|
1014
|
+
}
|
|
1015
|
+
// ─── scenario: single detected ─────────────────────────────────
|
|
1016
|
+
if (summary.scenario === 'single') {
|
|
1017
|
+
const t = summary.detected[0];
|
|
1018
|
+
const notDetected = summary.notDetected.map((d) => d.name).join(', ');
|
|
1019
|
+
renderBox((0, colors_1.color)(colors_1.RITUAL_TEAL, '─ wire your coding agent '), [
|
|
1020
|
+
` ${(0, colors_1.dim)('detected one coding agent on this machine — wiring up:')}`,
|
|
1021
|
+
'',
|
|
1022
|
+
` ${(0, colors_1.boldColor)(colors_1.RITUAL_TEAL, '✓ ' + t.name)}`,
|
|
1023
|
+
'',
|
|
1024
|
+
...(notDetected
|
|
1025
|
+
? [
|
|
1026
|
+
` ${(0, colors_1.dim)('also supported (not detected here): ' + notDetected)}`,
|
|
1027
|
+
` ${(0, colors_1.dim)('connect another with `ritual init --agent <id>`')}`,
|
|
1028
|
+
]
|
|
1029
|
+
: []),
|
|
1030
|
+
]);
|
|
1031
|
+
console.log('');
|
|
1032
|
+
return true;
|
|
1033
|
+
}
|
|
1034
|
+
// ─── scenario: multi-detect (real decision gate) ────────────────
|
|
1035
|
+
const notDetectedList = summary.notDetected.map((d) => d.name).join(', ');
|
|
1036
|
+
renderBox((0, colors_1.color)(colors_1.RITUAL_TEAL, '─ wire your coding agents '), [
|
|
1037
|
+
` ${(0, colors_1.dim)(`detected ${summary.detected.length} coding agents on this machine — wiring up:`)}`,
|
|
1038
|
+
'',
|
|
1039
|
+
...summary.detected.map((d) => ` ${(0, colors_1.boldColor)(colors_1.RITUAL_TEAL, '✓ ' + d.name)}`),
|
|
1040
|
+
'',
|
|
1041
|
+
...(notDetectedList
|
|
1042
|
+
? [` ${(0, colors_1.dim)('also supported (not detected here): ' + notDetectedList)}`]
|
|
1043
|
+
: []),
|
|
1044
|
+
` ${(0, colors_1.dim)('config paths: ritual doctor')}`,
|
|
1045
|
+
]);
|
|
1046
|
+
console.log('');
|
|
1047
|
+
// TTY-only pause. Non-TTY (CI / scripted) skips through — we
|
|
1048
|
+
// already printed exactly what's about to happen, and pausing in
|
|
1049
|
+
// CI would hang forever.
|
|
1050
|
+
if (process.stdin.isTTY) {
|
|
1051
|
+
try {
|
|
1052
|
+
await (0, prompt_1.prompt)(` ${(0, colors_1.color)(colors_1.RITUAL_TEAL, '›')} press Enter to wire all ${summary.detected.length}, ` +
|
|
1053
|
+
`or Ctrl-C to abort\n` +
|
|
1054
|
+
` ${(0, colors_1.dim)('(single agent? abort + re-run with `ritual init --agent <id>`)')}: `);
|
|
1055
|
+
}
|
|
1056
|
+
catch {
|
|
1057
|
+
console.log('');
|
|
1058
|
+
console.log(' Aborted.');
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
console.log('');
|
|
1063
|
+
return true;
|
|
1064
|
+
}
|
|
1065
|
+
// ─── FTUE onboarding helpers ────────────────────────────────────────────────
|
|
1066
|
+
/**
|
|
1067
|
+
* Coerce a raw `--persona <slug>` CLI value into a known PersonaSlug.
|
|
1068
|
+
* Returns the validated slug on hit, prints a friendly error and
|
|
1069
|
+
* returns undefined on miss. The caller treats "miss" as "abort
|
|
1070
|
+
* init" — silently falling through to the picker would be confusing
|
|
1071
|
+
* when the user clearly intended a specific persona.
|
|
1072
|
+
*/
|
|
1073
|
+
function validatePersonaFlag(raw) {
|
|
1074
|
+
if (!raw)
|
|
1075
|
+
return undefined;
|
|
1076
|
+
const persona = (0, persona_samples_1.findPersona)(raw);
|
|
1077
|
+
if (!persona) {
|
|
1078
|
+
console.error(` ✗ Unknown persona: "${raw}".`);
|
|
1079
|
+
console.error(' Run `ritual init --list` for agents; persona slugs live in the CLI docs.');
|
|
1080
|
+
console.error('');
|
|
1081
|
+
return null;
|
|
1082
|
+
}
|
|
1083
|
+
return persona.slug;
|
|
1084
|
+
}
|
|
1085
|
+
/**
|
|
1086
|
+
* Run the persona-picker screen if the user hasn't been onboarded
|
|
1087
|
+
* yet (or `--re-onboard` is set / `--persona` was passed).
|
|
1088
|
+
*
|
|
1089
|
+
* Returns the resolved persona slug (or null if the user explicitly
|
|
1090
|
+
* skipped). Always updates the per-machine onboarding state with the
|
|
1091
|
+
* outcome so we don't re-prompt next time.
|
|
1092
|
+
*
|
|
1093
|
+
* Non-TTY: respects the `--persona` flag if provided; otherwise
|
|
1094
|
+
* silently no-ops and returns null. We never block scripted/CI init
|
|
1095
|
+
* on a missing persona — downstream tools degrade gracefully via
|
|
1096
|
+
* the generic persona.
|
|
1097
|
+
*/
|
|
1098
|
+
async function maybeRunPersonaPicker(opts) {
|
|
1099
|
+
const state = (0, onboarding_state_1.readOnboardingState)();
|
|
1100
|
+
// Explicit override via flag — always takes precedence.
|
|
1101
|
+
if (opts.persona) {
|
|
1102
|
+
const slug = validatePersonaFlag(opts.persona);
|
|
1103
|
+
if (slug === null) {
|
|
1104
|
+
// Unknown slug — caller will treat the absence as a soft fail.
|
|
1105
|
+
return null;
|
|
1106
|
+
}
|
|
1107
|
+
if (slug) {
|
|
1108
|
+
(0, onboarding_state_1.updateOnboardingState)({
|
|
1109
|
+
personaPickedAt: new Date().toISOString(),
|
|
1110
|
+
personaSlug: slug,
|
|
1111
|
+
});
|
|
1112
|
+
return slug;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
if (!(0, onboarding_state_1.shouldShowPersonaPicker)(state)) {
|
|
1116
|
+
// Already onboarded — re-use the stored pick. `null` here means
|
|
1117
|
+
// the user explicitly skipped on a previous init; we honor that.
|
|
1118
|
+
return state.personaSlug ?? null;
|
|
1119
|
+
}
|
|
1120
|
+
if (!process.stdin.isTTY) {
|
|
1121
|
+
// Non-TTY first-time init: no picker, no persistence. We don't
|
|
1122
|
+
// mark onboarding as "done" because a future interactive init
|
|
1123
|
+
// can still ask.
|
|
1124
|
+
return null;
|
|
1125
|
+
}
|
|
1126
|
+
try {
|
|
1127
|
+
const result = await (0, persona_picker_1.pickPersona)();
|
|
1128
|
+
(0, onboarding_state_1.updateOnboardingState)({
|
|
1129
|
+
personaPickedAt: new Date().toISOString(),
|
|
1130
|
+
personaSlug: result?.slug ?? null,
|
|
1131
|
+
});
|
|
1132
|
+
return result?.slug ?? null;
|
|
1133
|
+
}
|
|
1134
|
+
catch {
|
|
1135
|
+
// Readline failed for any reason — don't crash init.
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Show the workspace explainer (screen #2) if the user hasn't seen
|
|
1141
|
+
* it yet on this machine. Marks it seen on first render so we don't
|
|
1142
|
+
* lecture the user on every subsequent `ritual init` in a new repo.
|
|
1143
|
+
*
|
|
1144
|
+
* Falls back to the terse non-TTY printer when stdout isn't a TTY
|
|
1145
|
+
* OR the terminal is narrower than the rich layout assumes (<80
|
|
1146
|
+
* cols), so we still teach the model without busting the wrap.
|
|
1147
|
+
*/
|
|
1148
|
+
function maybeShowWorkspaceExplainer(workspaceName, personaSlug) {
|
|
1149
|
+
const state = (0, onboarding_state_1.readOnboardingState)();
|
|
1150
|
+
if (!(0, onboarding_state_1.shouldShowWorkspaceExplainer)(state))
|
|
1151
|
+
return;
|
|
1152
|
+
const isTty = !!process.stdout.isTTY;
|
|
1153
|
+
const cols = process.stdout.columns ?? 80;
|
|
1154
|
+
if (isTty && cols >= 80) {
|
|
1155
|
+
(0, workspace_explainer_1.printWorkspaceExplainer)({ workspaceName, personaSlug });
|
|
1156
|
+
}
|
|
1157
|
+
else {
|
|
1158
|
+
(0, workspace_explainer_1.printWorkspaceExplainerTerse)({ workspaceName, personaSlug });
|
|
1159
|
+
}
|
|
1160
|
+
(0, onboarding_state_1.updateOnboardingState)({ workspaceExplainerSeenAt: new Date().toISOString() });
|
|
1161
|
+
}
|
|
1162
|
+
/**
|
|
1163
|
+
* Show the build-flow explainer (screen #3) if the user hasn't seen
|
|
1164
|
+
* it yet on this machine. Mirrors the workspace explainer's gating
|
|
1165
|
+
* + non-TTY fallback. `detectedAgents` shapes the coding-agent zone.
|
|
1166
|
+
*/
|
|
1167
|
+
function maybeShowBuildFlowExplainer(personaSlug, detectedAgents) {
|
|
1168
|
+
const state = (0, onboarding_state_1.readOnboardingState)();
|
|
1169
|
+
if (!(0, onboarding_state_1.shouldShowBuildFlowExplainer)(state))
|
|
1170
|
+
return;
|
|
1171
|
+
const isTty = !!process.stdout.isTTY;
|
|
1172
|
+
const cols = process.stdout.columns ?? 80;
|
|
1173
|
+
if (isTty && cols >= 80) {
|
|
1174
|
+
(0, build_flow_explainer_1.printBuildFlowExplainer)({ personaSlug, detectedAgents });
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
1177
|
+
(0, build_flow_explainer_1.printBuildFlowExplainerTerse)({ personaSlug, detectedAgents });
|
|
1178
|
+
}
|
|
1179
|
+
(0, onboarding_state_1.updateOnboardingState)({ buildFlowExplainerSeenAt: new Date().toISOString() });
|
|
1180
|
+
}
|
|
871
1181
|
//# sourceMappingURL=init.js.map
|