@aslomon/effectum 0.2.1 → 0.3.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/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  <div align="center">
2
2
 
3
- # ⚡ Effectum
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="docs/logo-dark.svg">
5
+ <source media="(prefers-color-scheme: light)" srcset="docs/logo-light.svg">
6
+ <img src="docs/logo-light.svg" alt="effectum" width="280">
7
+ </picture>
8
+
9
+ <br><br>
4
10
 
5
11
  ### Describe what you want. Get production-ready code.
6
12
 
@@ -9,10 +15,11 @@ _Effectum (Latin): the result, the accomplishment — that which has been brough
9
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
10
16
  [![Claude Code](https://img.shields.io/badge/Built%20for-Claude%20Code-blueviolet)](https://claude.ai/claude-code)
11
17
  [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)
18
+ [![Website](https://img.shields.io/badge/Website-aslomon.github.io%2Feffectum-D97706)](https://aslomon.github.io/effectum/)
12
19
 
13
20
  <br>
14
21
 
15
- [Quick Start](#-quick-start) · [How It Works](#-how-it-works) · [The Workflow](#-the-workflow) · [PRD Workshop](#-the-prd-workshop) · [How is this different?](#-how-is-this-different) · [Limitations](#-limitations)
22
+ [Quick Start](#-quick-start) · [How It Works](#-how-it-works) · [The Workflow](#-the-workflow) · [PRD Workshop](#-the-prd-workshop) · [How is this different?](#-how-is-this-different) · [Website](https://aslomon.github.io/effectum/)
16
23
 
17
24
  </div>
18
25
 
@@ -82,15 +89,15 @@ claude
82
89
 
83
90
  One command. Everything you need for autonomous Claude Code development.
84
91
 
85
- | What | Details |
86
- |------|---------|
92
+ | What | Details |
93
+ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
87
94
  | **10 workflow commands** | `/plan`, `/tdd`, `/verify`, `/e2e`, `/code-review`, `/build-fix`, `/refactor-clean`, `/ralph-loop`, `/cancel-ralph`, `/checkpoint` |
88
- | **PRD Workshop** | 8 knowledge files for guided specification writing |
89
- | **4 MCP servers** | Context7, Playwright, Sequential Thinking, Filesystem |
90
- | **Playwright setup** | Browser install + `playwright.config.ts` |
91
- | **Stack presets** | Next.js + Supabase, Python + FastAPI, Swift/SwiftUI, Generic |
92
- | **Quality gates** | 8 automated checks (build, types, lint, tests, security, etc.) |
93
- | **Guardrails** | Rules that prevent known mistakes |
95
+ | **PRD Workshop** | 8 knowledge files for guided specification writing |
96
+ | **4 MCP servers** | Context7, Playwright, Sequential Thinking, Filesystem |
97
+ | **Playwright setup** | Browser install + `playwright.config.ts` |
98
+ | **Stack presets** | Next.js + Supabase, Python + FastAPI, Swift/SwiftUI, Generic |
99
+ | **Quality gates** | 8 automated checks (build, types, lint, tests, security, etc.) |
100
+ | **Guardrails** | Rules that prevent known mistakes |
94
101
 
95
102
  ---
96
103
 
@@ -296,12 +303,12 @@ The better the spec, the better the code.
296
303
 
297
304
  ## 🆚 How is this different?
298
305
 
299
- | Tool | What it does | What Effectum adds |
300
- |------|-------------|-------------------|
301
- | **GSD** | Context engineering, prevents context rot | PRD Workshop (helps you write the spec), Ralph Loop (autonomous overnight) |
302
- | **BMAD** | Full enterprise methodology | Same ideas, 90% less ceremony |
303
- | **SpecKit** | Living specifications | + Autonomous execution + Quality gates |
304
- | **Taskmaster** | Task breakdown from PRDs | + TDD workflow + Code review + E2E testing |
306
+ | Tool | What it does | What Effectum adds |
307
+ | -------------- | ----------------------------------------- | -------------------------------------------------------------------------- |
308
+ | **GSD** | Context engineering, prevents context rot | PRD Workshop (helps you write the spec), Ralph Loop (autonomous overnight) |
309
+ | **BMAD** | Full enterprise methodology | Same ideas, 90% less ceremony |
310
+ | **SpecKit** | Living specifications | + Autonomous execution + Quality gates |
311
+ | **Taskmaster** | Task breakdown from PRDs | + TDD workflow + Code review + E2E testing |
305
312
 
306
313
  The short version: Effectum doesn't invent new concepts. It combines what already works, removes what doesn't, and packages it so it actually runs.
307
314
 
package/bin/install.js CHANGED
@@ -1,14 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Effectum interactive installer.
4
- * Rewritten with @clack/prompts for full TUI experience.
3
+ * Effectum interactive installer — Intelligent Setup Recommender.
4
+ *
5
+ * 9-step flow:
6
+ * 1. Install Scope (global / local)
7
+ * 2. Project Basics (name, stack, package manager)
8
+ * 3. App Type (web-app, api, mobile, cli, ...)
9
+ * 4. Description (free-text intent)
10
+ * 5. Language (15+ languages + custom)
11
+ * 6. Autonomy (conservative / standard / full)
12
+ * 7. Recommendation (preview calculated setup)
13
+ * 8. Decision (use recommended / customize / manual)
14
+ * 9. Install (write files)
5
15
  *
6
16
  * Usage:
7
- * npx @aslomon/effectum interactive install
8
- * npx @aslomon/effectum --global non-interactive global install
9
- * npx @aslomon/effectum --local non-interactive local install
10
- * npx @aslomon/effectum --yes non-interactive with smart defaults
11
- * npx @aslomon/effectum --dry-run show plan without writing
17
+ * npx @aslomon/effectum -> interactive install
18
+ * npx @aslomon/effectum --global -> non-interactive global install
19
+ * npx @aslomon/effectum --local -> non-interactive local install
20
+ * npx @aslomon/effectum --yes -> non-interactive with smart defaults
21
+ * npx @aslomon/effectum --dry-run -> show plan without writing
12
22
  */
13
23
  "use strict";
14
24
 
@@ -22,26 +32,38 @@ const {
22
32
  buildSubstitutionMap,
23
33
  renderTemplate,
24
34
  findTemplatePath,
25
- findRemainingPlaceholders,
26
35
  } = require("./lib/template");
27
36
  const { writeConfig } = require("./lib/config");
28
37
  const { AUTONOMY_MAP, FORMATTER_MAP, MCP_SERVERS } = require("./lib/constants");
29
- const { ensureDir, deepMerge, findRepoRoot: findRepoRootShared } = require("./lib/utils");
38
+ const {
39
+ ensureDir,
40
+ deepMerge,
41
+ findRepoRoot: findRepoRootShared,
42
+ } = require("./lib/utils");
43
+ const { recommend } = require("./lib/recommendation");
30
44
  const {
31
45
  initClack,
32
46
  getClack,
33
47
  printBanner,
48
+ askScope,
34
49
  askProjectName,
35
50
  askStack,
51
+ askAppType,
52
+ askDescription,
36
53
  askLanguage,
37
54
  askAutonomy,
38
- askMcpServers,
55
+ showRecommendation,
56
+ askSetupMode,
57
+ askCustomize,
58
+ askManual,
39
59
  askPlaywright,
40
60
  askGitBranch,
41
61
  showSummary,
42
62
  showOutro,
43
63
  } = require("./lib/ui");
44
64
 
65
+ // ─── File helpers ─────────────────────────────────────────────────────────────
66
+
45
67
  function copyFile(src, dest, opts = {}) {
46
68
  if (fs.existsSync(dest) && opts.skipExisting) {
47
69
  return { status: "skipped", dest };
@@ -71,7 +93,7 @@ function findRepoRoot() {
71
93
  return findRepoRootShared();
72
94
  }
73
95
 
74
- // ─── Parse CLI args ───────────────────────────────────────────────────────
96
+ // ─── Parse CLI args ─────────────────────────────────────────────────────────
75
97
 
76
98
  function parseArgs(argv) {
77
99
  const args = argv.slice(2);
@@ -84,11 +106,11 @@ function parseArgs(argv) {
84
106
  yes: args.includes("--yes") || args.includes("-y"),
85
107
  dryRun: args.includes("--dry-run"),
86
108
  help: args.includes("--help") || args.includes("-h"),
87
- nonInteractive: false, // computed below
109
+ nonInteractive: false,
88
110
  };
89
111
  }
90
112
 
91
- // ─── MCP server install helpers ───────────────────────────────────────────
113
+ // ─── MCP server install helpers ─────────────────────────────────────────────
92
114
 
93
115
  function checkPackageAvailable(pkg) {
94
116
  try {
@@ -174,7 +196,7 @@ function addMcpToSettings(settingsPath, mcpResults, targetDir) {
174
196
  );
175
197
  }
176
198
 
177
- // ─── Playwright install helpers ───────────────────────────────────────────
199
+ // ─── Playwright install helpers ─────────────────────────────────────────────
178
200
 
179
201
  function installPlaywrightBrowsers() {
180
202
  try {
@@ -228,7 +250,7 @@ export default defineConfig({
228
250
  return { status: "created", dest: tsConfig };
229
251
  }
230
252
 
231
- // ─── Core install: copy commands, templates, stacks ───────────────────────
253
+ // ─── Core install: copy commands, templates, stacks ─────────────────────────
232
254
 
233
255
  function installBaseFiles(targetDir, repoRoot, isGlobal) {
234
256
  const claudeDir = isGlobal ? targetDir : path.join(targetDir, ".claude");
@@ -278,7 +300,7 @@ function installBaseFiles(targetDir, repoRoot, isGlobal) {
278
300
  return steps;
279
301
  }
280
302
 
281
- // ─── Generate configured files (CLAUDE.md, settings.json, guardrails.md)
303
+ // ─── Generate configured files (CLAUDE.md, settings.json, guardrails.md) ───
282
304
 
283
305
  function generateConfiguredFiles(config, targetDir, repoRoot, isGlobal) {
284
306
  const claudeDir = isGlobal ? targetDir : path.join(targetDir, ".claude");
@@ -292,16 +314,14 @@ function generateConfiguredFiles(config, targetDir, repoRoot, isGlobal) {
292
314
  const claudeMdTmpl = findTemplatePath("CLAUDE.md.tmpl", targetDir, repoRoot);
293
315
  const { content: claudeMdContent, remaining: claudeMdRemaining } =
294
316
  renderTemplate(claudeMdTmpl, vars);
295
- const claudeMdDest = isGlobal
296
- ? path.join(targetDir, "CLAUDE.md")
297
- : path.join(targetDir, "CLAUDE.md");
317
+ const claudeMdDest = path.join(targetDir, "CLAUDE.md");
298
318
  ensureDir(path.dirname(claudeMdDest));
299
319
  fs.writeFileSync(claudeMdDest, claudeMdContent, "utf8");
300
320
  steps.push({ status: "created", dest: claudeMdDest });
301
321
 
302
322
  if (claudeMdRemaining.length > 0) {
303
323
  console.warn(
304
- `⚠ CLAUDE.md has remaining placeholders: ${claudeMdRemaining.join(", ")}`,
324
+ `Warning: CLAUDE.md has remaining placeholders: ${claudeMdRemaining.join(", ")}`,
305
325
  );
306
326
  }
307
327
 
@@ -327,6 +347,12 @@ function generateConfiguredFiles(config, targetDir, repoRoot, isGlobal) {
327
347
  deny: settingsObj.permissions?.deny || [],
328
348
  };
329
349
 
350
+ // Apply Agent Teams env var
351
+ if (settingsObj.env) {
352
+ settingsObj.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS =
353
+ config.recommended && config.recommended.agentTeams ? "1" : "0";
354
+ }
355
+
330
356
  // Apply formatter in PostToolUse hook
331
357
  const formatter = FORMATTER_MAP[config.stack] || FORMATTER_MAP.generic;
332
358
  if (settingsObj.hooks?.PostToolUse) {
@@ -374,7 +400,6 @@ function generateConfiguredFiles(config, targetDir, repoRoot, isGlobal) {
374
400
  const guardrailsRaw = fs.readFileSync(guardrailsTmpl, "utf8");
375
401
  let guardrailsContent = guardrailsRaw;
376
402
 
377
- // Replace "No stack-specific guardrails..." with actual content
378
403
  if (stackSections.STACK_SPECIFIC_GUARDRAILS) {
379
404
  guardrailsContent = guardrailsContent.replace(
380
405
  /No stack-specific guardrails configured yet\. Run \/setup to configure for your stack\./,
@@ -396,26 +421,39 @@ function generateConfiguredFiles(config, targetDir, repoRoot, isGlobal) {
396
421
  return steps;
397
422
  }
398
423
 
399
- // ─── Smart defaults for non-interactive mode ──────────────────────────────
424
+ // ─── Smart defaults for non-interactive mode ────────────────────────────────
400
425
 
401
426
  function buildSmartDefaults(targetDir) {
402
427
  const detected = detectAll(targetDir);
403
- const formatter =
404
- FORMATTER_MAP[detected.stack || "generic"] || FORMATTER_MAP.generic;
428
+ const stack = detected.stack || "generic";
429
+ const formatter = FORMATTER_MAP[stack] || FORMATTER_MAP.generic;
430
+
431
+ const rec = recommend({
432
+ stack,
433
+ appType: "web-app",
434
+ description: "",
435
+ autonomyLevel: "standard",
436
+ language: "english",
437
+ });
438
+
405
439
  return {
406
440
  projectName: detected.projectName,
407
- stack: detected.stack || "generic",
441
+ stack,
442
+ appType: "web-app",
443
+ description: "",
408
444
  language: "english",
409
445
  autonomyLevel: "standard",
410
446
  packageManager: detected.packageManager,
411
447
  formatter: formatter.name,
412
- mcpServers: MCP_SERVERS.map((s) => s.key),
413
- playwrightBrowsers: true,
448
+ mcpServers: rec.mcps,
449
+ playwrightBrowsers: rec.mcps.includes("playwright"),
414
450
  installScope: "local",
451
+ recommended: rec,
452
+ mode: "recommended",
415
453
  };
416
454
  }
417
455
 
418
- // ─── Git branch creation ──────────────────────────────────────────────────
456
+ // ─── Git branch creation ────────────────────────────────────────────────────
419
457
 
420
458
  function createGitBranch(name) {
421
459
  const result = spawnSync("git", ["checkout", "-b", name], {
@@ -425,7 +463,7 @@ function createGitBranch(name) {
425
463
  return result.status === 0;
426
464
  }
427
465
 
428
- // ─── Main ─────────────────────────────────────────────────────────────────
466
+ // ─── Main ───────────────────────────────────────────────────────────────────
429
467
 
430
468
  async function main() {
431
469
  const args = parseArgs(process.argv);
@@ -434,10 +472,10 @@ async function main() {
434
472
  // Help
435
473
  if (args.help) {
436
474
  console.log(`
437
- effectum — autonomous development system for Claude Code
475
+ effectum — intelligent setup for Claude Code
438
476
 
439
477
  Usage:
440
- npx effectum Interactive installer
478
+ npx effectum Interactive installer (9-step flow)
441
479
  npx effectum init Per-project init (after global install)
442
480
  npx effectum reconfigure Re-apply config from .effectum.json
443
481
  npx effectum --global Install globally (~/.claude/, no prompts)
@@ -477,7 +515,7 @@ Options:
477
515
  const homeClaudeDir = path.join(os.homedir(), ".claude");
478
516
  const targetDir = isGlobal ? homeClaudeDir : process.cwd();
479
517
 
480
- // ── Non-interactive mode ────────────────────────────────────────────────
518
+ // ── Non-interactive mode ──────────────────────────────────────────────────
481
519
  if (isNonInteractive) {
482
520
  const config = buildSmartDefaults(targetDir);
483
521
  config.installScope = isGlobal ? "global" : "local";
@@ -505,17 +543,19 @@ Options:
505
543
  writeConfig(targetDir, config);
506
544
  }
507
545
 
508
- // MCP servers
509
- if (args.withMcp) {
510
- const mcpResults = installMcpServers(config.mcpServers);
546
+ // MCP servers — always install recommended MCPs (or explicit --with-mcp)
547
+ const mcpKeys = config.mcpServers || (config.recommended ? config.recommended.mcps : []) || [];
548
+ if (mcpKeys.length > 0 || args.withMcp) {
549
+ const keysToInstall = mcpKeys.length > 0 ? mcpKeys : MCP_SERVERS.map((s) => s.key);
550
+ const mcpResults = installMcpServers(keysToInstall);
511
551
  const settingsPath = isGlobal
512
552
  ? path.join(homeClaudeDir, "settings.json")
513
553
  : path.join(targetDir, ".claude", "settings.json");
514
554
  addMcpToSettings(settingsPath, mcpResults, targetDir);
515
555
  }
516
556
 
517
- // Playwright
518
- if (args.withPlaywright) {
557
+ // Playwright — install if recommended or explicit
558
+ if (args.withPlaywright || config.playwrightBrowsers) {
519
559
  installPlaywrightBrowsers();
520
560
  if (!isGlobal) ensurePlaywrightConfig(process.cwd());
521
561
  }
@@ -524,7 +564,7 @@ Options:
524
564
  process.exit(0);
525
565
  }
526
566
 
527
- // ── Interactive mode ────────────────────────────────────────────────────
567
+ // ── Interactive mode — 9-step flow ────────────────────────────────────────
528
568
  const p = await initClack();
529
569
  printBanner();
530
570
 
@@ -536,47 +576,77 @@ Options:
536
576
  );
537
577
  }
538
578
 
539
- // Scope
540
- const scopeValue = await p.select({
541
- message: "Install scope",
542
- options: [
543
- {
544
- value: "local",
545
- label: "Local",
546
- hint: "This project only (./.claude/)",
547
- },
548
- {
549
- value: "global",
550
- label: "Global",
551
- hint: "All projects (~/.claude/)",
552
- },
553
- ],
554
- initialValue: "local",
555
- });
556
- if (p.isCancel(scopeValue)) {
557
- p.cancel("Setup cancelled.");
558
- process.exit(0);
559
- }
579
+ // ── Step 1: Install Scope ─────────────────────────────────────────────────
580
+ const scopeValue = await askScope();
560
581
  const installGlobal = scopeValue === "global";
561
582
  const installTargetDir = installGlobal ? homeClaudeDir : process.cwd();
562
583
 
563
- // Project name
564
- const projectName = await askProjectName(detected.projectName);
584
+ // Global install: skip project-specific steps, install base only
585
+ if (installGlobal) {
586
+ if (args.dryRun) {
587
+ p.log.info("Dry run — no files will be written.");
588
+ p.note(
589
+ [
590
+ "~/.claude/commands/*.md",
591
+ "~/.claude/settings.json",
592
+ "~/.claude/guardrails.md",
593
+ "~/.effectum/templates/",
594
+ "~/.effectum/stacks/",
595
+ ].join("\n"),
596
+ "Files to be created",
597
+ );
598
+ p.outro("Dry run complete. No changes made.");
599
+ process.exit(0);
600
+ }
565
601
 
566
- // Stack
602
+ const s = p.spinner();
603
+ s.start("Installing global workflow commands and templates...");
604
+ installBaseFiles(installTargetDir, repoRoot, true);
605
+ s.stop("Global base files installed");
606
+
607
+ showOutro(true);
608
+ process.exit(0);
609
+ }
610
+
611
+ // ── Step 2: Project Basics ────────────────────────────────────────────────
612
+ const projectName = await askProjectName(detected.projectName);
567
613
  const stack = await askStack(detected.stack);
568
614
 
569
- // Language
615
+ // ── Step 3: App Type ──────────────────────────────────────────────────────
616
+ const appType = await askAppType();
617
+
618
+ // ── Step 4: Description ───────────────────────────────────────────────────
619
+ const description = await askDescription();
620
+
621
+ // ── Step 5: Language ──────────────────────────────────────────────────────
570
622
  const langResult = await askLanguage();
571
623
 
572
- // Autonomy
624
+ // ── Step 6: Autonomy ──────────────────────────────────────────────────────
573
625
  const autonomyLevel = await askAutonomy();
574
626
 
575
- // MCP servers
576
- const mcpServerKeys = await askMcpServers();
627
+ // ── Step 7: Recommendation Preview ────────────────────────────────────────
628
+ const rec = recommend({
629
+ stack,
630
+ appType,
631
+ description,
632
+ autonomyLevel,
633
+ language: langResult.language,
634
+ });
635
+
636
+ showRecommendation(rec);
577
637
 
578
- // Playwright
579
- const wantPlaywright = mcpServerKeys.includes("playwright")
638
+ // ── Step 8: Decision ──────────────────────────────────────────────────────
639
+ const setupMode = await askSetupMode();
640
+
641
+ let finalSetup = rec;
642
+ if (setupMode === "customize") {
643
+ finalSetup = await askCustomize(rec);
644
+ } else if (setupMode === "manual") {
645
+ finalSetup = await askManual();
646
+ }
647
+
648
+ // Check if playwright is in the final MCP list and ask about browser install
649
+ const wantPlaywright = finalSetup.mcps.includes("playwright")
580
650
  ? await askPlaywright()
581
651
  : false;
582
652
 
@@ -584,23 +654,34 @@ Options:
584
654
  const gitBranch = await askGitBranch();
585
655
 
586
656
  // Build config object
587
- const formatter = FORMATTER_MAP[stack] || FORMATTER_MAP.generic;
657
+ const formatterDef = FORMATTER_MAP[stack] || FORMATTER_MAP.generic;
588
658
  const config = {
589
659
  projectName,
590
660
  stack,
661
+ appType,
662
+ description,
591
663
  language: langResult.language,
592
664
  ...(langResult.customLanguage
593
665
  ? { customLanguage: langResult.customLanguage }
594
666
  : {}),
595
667
  autonomyLevel,
596
668
  packageManager: detected.packageManager,
597
- formatter: formatter.name,
598
- mcpServers: mcpServerKeys,
669
+ formatter: formatterDef.name,
670
+ mcpServers: finalSetup.mcps,
599
671
  playwrightBrowsers: wantPlaywright,
600
- installScope: installGlobal ? "global" : "local",
672
+ installScope: "local",
673
+ recommended: {
674
+ commands: finalSetup.commands,
675
+ hooks: finalSetup.hooks,
676
+ skills: finalSetup.skills,
677
+ mcps: finalSetup.mcps,
678
+ subagents: finalSetup.subagents,
679
+ agentTeams: finalSetup.agentTeams,
680
+ },
681
+ mode: setupMode,
601
682
  };
602
683
 
603
- // ── Dry run ─────────────────────────────────────────────────────────────
684
+ // ── Dry run ───────────────────────────────────────────────────────────────
604
685
  if (args.dryRun) {
605
686
  p.log.info("Dry run — no files will be written.");
606
687
  p.note(JSON.stringify(config, null, 2), "Planned Configuration");
@@ -612,7 +693,7 @@ Options:
612
693
  "AUTONOMOUS-WORKFLOW.md",
613
694
  ".effectum.json",
614
695
  ];
615
- if (mcpServerKeys.length > 0) {
696
+ if (finalSetup.mcps.length > 0) {
616
697
  plannedFiles.push("MCP servers in settings.json");
617
698
  }
618
699
  p.note(plannedFiles.join("\n"), "Files to be created/updated");
@@ -620,62 +701,60 @@ Options:
620
701
  process.exit(0);
621
702
  }
622
703
 
623
- // ── Create git branch ──────────────────────────────────────────────────
704
+ // ── Step 9: Install ───────────────────────────────────────────────────────
705
+
706
+ // Create git branch if requested
624
707
  if (gitBranch.create) {
625
- const s = p.spinner();
626
- s.start("Creating git branch...");
708
+ const sGit = p.spinner();
709
+ sGit.start("Creating git branch...");
627
710
  const ok = createGitBranch(gitBranch.name);
628
711
  if (ok) {
629
- s.stop(`Branch "${gitBranch.name}" created`);
712
+ sGit.stop(`Branch "${gitBranch.name}" created`);
630
713
  } else {
631
- s.stop("Could not create branch (may already exist)");
714
+ sGit.stop("Could not create branch (may already exist)");
632
715
  }
633
716
  }
634
717
 
635
- // ── Step 1: Base files ─────────────────────────────────────────────────
718
+ // 9a: Base files
636
719
  const s1 = p.spinner();
637
720
  s1.start("Installing workflow commands and templates...");
638
- const baseSteps = installBaseFiles(installTargetDir, repoRoot, installGlobal);
721
+ const baseSteps = installBaseFiles(installTargetDir, repoRoot, false);
639
722
  s1.stop(
640
723
  `Installed ${baseSteps.filter((s) => s.status === "created").length} files`,
641
724
  );
642
725
 
643
- // ── Step 2: Configure (local only) ────────────────────────────────────
644
- const configSteps = [];
645
- if (!installGlobal) {
646
- const s2 = p.spinner();
647
- s2.start(
648
- "Generating configured files (CLAUDE.md, settings.json, guardrails.md)...",
649
- );
650
- const cSteps = generateConfiguredFiles(
651
- config,
652
- installTargetDir,
653
- repoRoot,
654
- installGlobal,
655
- );
656
- configSteps.push(...cSteps);
657
- s2.stop("Configuration files generated");
658
- }
726
+ // 9b: Configure
727
+ const s2 = p.spinner();
728
+ s2.start("Generating CLAUDE.md, settings.json, guardrails.md...");
729
+ const configSteps = generateConfiguredFiles(
730
+ config,
731
+ installTargetDir,
732
+ repoRoot,
733
+ false,
734
+ );
735
+ s2.stop("Configuration files generated");
659
736
 
660
- // ── Step 3: MCP servers ────────────────────────────────────────────────
661
- if (mcpServerKeys.length > 0) {
737
+ // 9c: MCP servers
738
+ if (finalSetup.mcps.length > 0) {
662
739
  const s3 = p.spinner();
663
740
  s3.start("Setting up MCP servers...");
664
- const mcpResults = installMcpServers(mcpServerKeys);
665
- const settingsPath = installGlobal
666
- ? path.join(homeClaudeDir, "settings.json")
667
- : path.join(installTargetDir, ".claude", "settings.json");
741
+ const mcpResults = installMcpServers(finalSetup.mcps);
742
+ const settingsPath = path.join(
743
+ installTargetDir,
744
+ ".claude",
745
+ "settings.json",
746
+ );
668
747
  addMcpToSettings(settingsPath, mcpResults, installTargetDir);
669
748
  const okCount = mcpResults.filter((r) => r.ok).length;
670
749
  s3.stop(`${okCount} MCP servers configured`);
671
750
  }
672
751
 
673
- // ── Step 4: Playwright ─────────────────────────────────────────────────
752
+ // 9d: Playwright
674
753
  if (wantPlaywright) {
675
754
  const s4 = p.spinner();
676
755
  s4.start("Installing Playwright browsers...");
677
756
  const pwResult = installPlaywrightBrowsers();
678
- if (!installGlobal) ensurePlaywrightConfig(process.cwd());
757
+ ensurePlaywrightConfig(process.cwd());
679
758
  s4.stop(
680
759
  pwResult.ok
681
760
  ? "Playwright browsers installed"
@@ -683,13 +762,11 @@ Options:
683
762
  );
684
763
  }
685
764
 
686
- // ── Step 5: Save config ────────────────────────────────────────────────
687
- if (!installGlobal) {
688
- const configPath = writeConfig(installTargetDir, config);
689
- configSteps.push({ status: "created", dest: configPath });
690
- }
765
+ // 9e: Save config
766
+ const configPath = writeConfig(installTargetDir, config);
767
+ configSteps.push({ status: "created", dest: configPath });
691
768
 
692
- // ── Summary ─────────────────────────────────────────────────────────────
769
+ // ── Summary ───────────────────────────────────────────────────────────────
693
770
  const allSteps = [...baseSteps, ...configSteps];
694
771
  const allFiles = allSteps
695
772
  .filter((s) => s && s.dest)
@@ -700,10 +777,9 @@ Options:
700
777
  : path.relative(process.cwd(), s.dest);
701
778
  });
702
779
 
703
- // Deduplicate for summary
704
780
  const uniqueFiles = [...new Set(allFiles)].slice(0, 20);
705
781
  showSummary(config, uniqueFiles);
706
- showOutro(installGlobal);
782
+ showOutro(false);
707
783
  }
708
784
 
709
785
  main().catch((err) => {