claude-setup 1.1.3 → 1.1.4

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
@@ -2,9 +2,9 @@
2
2
 
3
3
  Setup layer for Claude Code. Reads your project, writes command files, Claude Code does the rest.
4
4
 
5
- **The CLI has zero intelligence.** All reasoning is delegated to Claude Code via the command files. The CLI reads files. Claude Code decides.
5
+ **The CLI has zero intelligence.** All reasoning is delegated to Claude Code via the command files.
6
6
 
7
- ## Install & Quick Start
7
+ ## Install
8
8
 
9
9
  ```bash
10
10
  npx claude-setup init
@@ -16,47 +16,128 @@ Then open Claude Code and run `/stack-init`.
16
16
 
17
17
  | Command | What it does |
18
18
  |---------|-------------|
19
- | `npx claude-setup init` | Full project setup — new or existing. Detects empty projects automatically. |
20
- | `npx claude-setup add` | Add a multi-file capability (MCP + hooks + skills together) |
21
- | `npx claude-setup sync` | Update setup after project changes (uses diff, not full re-scan) |
22
- | `npx claude-setup status` | Show current setup state OS, servers, hooks, staleness |
23
- | `npx claude-setup doctor` | Validate environment — OS/MCP format, hook quoting, env vars, stale skills |
24
- | `npx claude-setup remove` | Remove a capability cleanly with dangling reference detection |
19
+ | `init` | Full project setup — detects empty projects, generates atomic setup steps |
20
+ | `add` | Add capabilities MCP servers, skills, hooks, plugins in one go |
21
+ | `sync` | Update setup after project changes diff-based, not full re-scan |
22
+ | `status` | Dashboard project info, setup files, snapshots, token usage |
23
+ | `doctor` | Validate everything — OS format, hooks, env vars, stale skills |
24
+ | `remove` | Remove capabilities cleanly with dangling reference detection |
25
+ | `restore` | Jump to any snapshot — restore files to a previous state |
26
+ | `compare` | Diff two snapshots — find exactly where something changed |
27
+ | `export` | Save your setup as a reusable template |
25
28
 
26
29
  ### Flags
27
30
 
28
31
  ```bash
29
- npx claude-setup init --dry-run # Preview without writing
30
- npx claude-setup sync --dry-run # Show changes without writing
31
- npx claude-setup doctor --verbose # Include passing checks in output
32
+ npx claude-setup init --dry-run # Preview without writing
33
+ npx claude-setup init --template my.json # Apply a saved template
34
+ npx claude-setup sync --dry-run # Show changes without writing
35
+ npx claude-setup sync --budget 3000 # Override token budget
36
+ npx claude-setup doctor --verbose # Include passing checks
37
+ npx claude-setup doctor --fix # Auto-fix issues
38
+ npx claude-setup doctor --test-hooks # Run every hook in sandbox
32
39
  ```
33
40
 
34
41
  ## How it works
35
42
 
36
- 1. **CLI collects** — reads project files (configs, source samples) with strict token cost controls
37
- 2. **CLI writes command files** — assembles markdown instructions into `.claude/commands/`
38
- 3. **Claude Code executes** — you run `/stack-init` (or `/stack-sync`, etc.) in Claude Code
43
+ 1. **CLI collects** — reads project files with strict token cost controls
44
+ 2. **CLI writes** — generates markdown instructions into `.claude/commands/`
45
+ 3. **Claude Code executes** — you run `/stack-init`, `/stack-sync`, etc.
39
46
 
40
- ## Three project states
47
+ ## What it creates
41
48
 
42
- - **Empty project** Claude Code asks 3 discovery questions, then sets up a tailored environment
43
- - **In development** — reads existing files, writes setup that references actual code patterns
44
- - **Production** same as development; merge rules protect existing Claude config (append only, never rewrite)
49
+ | File | Purpose |
50
+ |------|---------|
51
+ | `CLAUDE.md` | Project-specific context for Claude Code |
52
+ | `.mcp.json` | MCP server connections (only if evidenced by project files) |
53
+ | `.claude/settings.json` | Hooks in correct Claude Code format |
54
+ | `.claude/skills/` | Reusable patterns with frontmatter |
55
+ | `.claude/commands/` | Project-specific slash commands |
56
+ | `.github/workflows/` | CI workflows (only with confirmation) |
45
57
 
46
- ## What it creates
58
+ ## Snapshots
59
+
60
+ Every `init` and `sync` creates a snapshot node — a checkpoint on a timeline. Snapshots store the content of changed files only.
61
+
62
+ ```
63
+ init ──→ sync#1 ──→ sync#2 ──→ sync#3 (current)
64
+ │ │
65
+ │ └─ bug introduced here
66
+ └─ jump back here
67
+ ```
68
+
69
+ - `npx claude-setup restore` — pick any snapshot and restore files to that state
70
+ - `npx claude-setup compare` — diff any two snapshots to find what changed
71
+ - Jumping does **not** delete other snapshots — all are preserved
72
+
73
+ ## Templates
74
+
75
+ Save your setup and reuse it across projects.
47
76
 
48
- - `CLAUDE.md` — project-specific context for Claude Code
49
- - `.mcp.json` MCP server connections (only if evidenced by project files, OS-correct format)
50
- - `.claude/settings.json` — hooks (only if warranted, OS-correct shell format)
51
- - `.claude/skills/` reusable patterns (only if recurring, with `applies-when` frontmatter)
52
- - `.claude/commands/` — project-specific slash commands
53
- - `.github/workflows/` CI workflows (only if `.github/` exists)
77
+ ```bash
78
+ # Export current setup
79
+ npx claude-setup export
80
+ # creates my-template.claude-template.json
81
+
82
+ # Apply to a new project
83
+ npx claude-setup init --template my-template.claude-template.json
84
+
85
+ # Apply from a URL
86
+ npx claude-setup init --template https://example.com/template.json
87
+ ```
88
+
89
+ Templates capture CLAUDE.md, MCP servers, hooks, skills, and commands. On import:
90
+ - Existing content is kept, new content is merged
91
+ - MCP commands are auto-adapted for the target OS
92
+ - Skills and commands with the same name are skipped
54
93
 
94
+ ## Token Cost Tracking
95
+
96
+ Every command shows estimated token usage and cost across all Claude models.
97
+
98
+ ```
99
+ Token cost
100
+ ~2,450 input tokens (Opus $0.0368 | Sonnet $0.0074 | Haiku $0.0006)
101
+ ```
55
102
 
103
+ Status shows cumulative stats, per-command averages, and cost trends. Use `--budget` on sync to override the token limit for a single run.
104
+
105
+ ## Doctor
106
+
107
+ Validates your entire setup and reports issues by severity.
108
+
109
+ ```bash
110
+ npx claude-setup doctor # Check everything
111
+ npx claude-setup doctor --fix # Auto-fix what's possible
112
+ npx claude-setup doctor --test-hooks # Run each hook, report pass/fail
113
+ ```
114
+
115
+ What `--fix` can repair:
116
+ - Remove accidental model overrides from settings.json
117
+ - Convert MCP commands to the correct OS format
118
+ - Add missing `-y` flags to npx calls
119
+ - Re-snapshot files modified outside the CLI
120
+
121
+ What `--test-hooks` checks per hook:
122
+ - Command exists on the system
123
+ - Command executes without error
124
+ - Exit code and stderr
125
+ - Execution time and timeout detection
126
+ - Matcher regex validity
127
+
128
+ ## Marketplace
129
+
130
+ The `add` command integrates with [claude-code-plugins-plus-skills](https://github.com/jeremylongshore/claude-code-plugins-plus-skills) — 340+ plugins and 1,367+ skills across 20 categories.
131
+
132
+ ```bash
133
+ npx claude-setup add
134
+ # → "Stripe and frontend skills"
135
+ # → generates stack-add.md with marketplace search + install instructions
136
+ ```
56
137
 
57
138
  ## Configuration
58
139
 
59
- Create `.claude-setup.json` in your project root to customize:
140
+ Auto-generated on first run. Edit `.claude-setup.json` to customize:
60
141
 
61
142
  ```json
62
143
  {
@@ -70,27 +151,11 @@ Create `.claude-setup.json` in your project root to customize:
70
151
  "remove": 2000
71
152
  },
72
153
  "digestMode": true,
73
- "extraBlockedDirs": ["my-custom-dir"],
74
- "sourceDirs": ["src", "lib"]
154
+ "extraBlockedDirs": [],
155
+ "sourceDirs": []
75
156
  }
76
157
  ```
77
158
 
78
- ## Digest mode
79
-
80
- When `digestMode` is enabled (default), the CLI extracts compact signal instead of dumping raw file content:
81
-
82
- - **Config files found** — just names, not content
83
- - **Dependencies** — extracted from any package manifest
84
- - **Scripts** — available commands/tasks
85
- - **Env vars** — names from `.env.example`
86
- - **Directory tree** — compact structure (3 levels deep)
87
- - **Source signatures** — imports, exports, declarations (not full content)
88
-
89
- ## OS detection
90
-
91
- The CLI detects your OS and ensures command files tell Claude Code to use the correct format:
92
-
93
- - **Windows**: `{ "command": "cmd", "args": ["/c", "npx", "<package>"] }`
94
- - **macOS/Linux**: `{ "command": "npx", "args": ["<package>"] }`
159
+ ## License
95
160
 
96
- `doctor` checks for mismatches and reports them as critical issues.
161
+ MIT
package/dist/builder.js CHANGED
@@ -3,6 +3,7 @@ import { join, dirname } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { loadConfig } from "./config.js";
5
5
  import { detectOS, VERIFIED_MCP_PACKAGES } from "./os.js";
6
+ import { buildMarketplaceInstructions } from "./marketplace.js";
6
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
7
8
  const TEMPLATES_DIR = join(__dirname, "..", "templates");
8
9
  function estimateTokens(content) {
@@ -97,6 +98,7 @@ function buildFlags(_collected, state) {
97
98
  HAS_MCP_JSON: state.mcpJson.exists,
98
99
  HAS_SETTINGS: state.settings.exists,
99
100
  HAS_GITHUB_DIR: state.hasGithubDir,
101
+ IS_WINDOWS: detectOS() === "Windows",
100
102
  };
101
103
  }
102
104
  // --- Token budget enforcement ---
@@ -137,10 +139,18 @@ export function buildInitCommand(collected, state) {
137
139
  }
138
140
  export function buildEmptyProjectCommand() {
139
141
  const template = loadTemplate("init-empty.md");
140
- return replaceVars(template, { VERSION: getVersion(), DATE: new Date().toISOString().split("T")[0] });
142
+ const vars = { VERSION: getVersion(), DATE: new Date().toISOString().split("T")[0], DETECTED_OS: detectOS() };
143
+ const flags = { IS_WINDOWS: detectOS() === "Windows" };
144
+ let content = replaceVars(template, vars);
145
+ content = processConditionals(content, flags);
146
+ return content;
141
147
  }
142
148
  export function buildAddCommand(input, collected, state) {
143
- return applyTemplate("add.md", collected, state, { USER_INPUT: input }, "add");
149
+ const marketplaceSection = buildMarketplaceInstructions(input);
150
+ return applyTemplate("add.md", collected, state, {
151
+ USER_INPUT: input,
152
+ MARKETPLACE_INSTRUCTIONS: marketplaceSection,
153
+ }, "add");
144
154
  }
145
155
  export function buildSyncCommand(diff, collected, state) {
146
156
  // Compact diff format — paths + one-line summary, not full content
@@ -214,11 +224,13 @@ export function buildAtomicSteps(collected, state) {
214
224
  ? `### Current content — MERGE ONLY, never remove existing entries:\n${vars.MCP_JSON_CONTENT}\n\n`
215
225
  : `Does not exist.\n\n`) +
216
226
  `### When to create/update\n` +
217
- `Add an MCP server ONLY if you find evidence in /stack-0-context:\n` +
218
- `- Import statement referencing an external service\n` +
219
- `- docker-compose service (database, cache, queue)\n` +
220
- `- Env var name in .env.example matching a known service pattern\n` +
221
- `- Explicit dependency on an MCP-compatible package\n\n` +
227
+ `Add an MCP server if you find ANY of these signals in /stack-0-context:\n` +
228
+ `- Import statement referencing an external service (e.g., pg, mysql2, mongoose, redis, stripe)\n` +
229
+ `- docker-compose service (database, cache, queue, message broker)\n` +
230
+ `- Env var name in .env.example matching a known service pattern (DATABASE_URL, REDIS_URL, STRIPE_KEY, etc.)\n` +
231
+ `- Explicit dependency on an MCP-compatible package\n` +
232
+ `- User mentioned external services during init questions\n\n` +
233
+ `If ANY evidence is found, create .mcp.json with the corresponding servers.\n` +
222
234
  `No evidence = no server. Do not invent services.\n\n` +
223
235
  `### Verified MCP package names — ONLY use these\n` +
224
236
  `\`\`\`\n` +
@@ -273,21 +285,53 @@ export function buildAtomicSteps(collected, state) {
273
285
  `### When to create/update\n` +
274
286
  `Add a hook ONLY if it runs on a pattern that repeats every session AND the cost is justified.\n` +
275
287
  `Every hook adds overhead on every Claude Code action. Only add if clearly earned.\n\n` +
276
- `### OS-correct hook format (detected: ${os})\n` +
288
+ `### CORRECT Claude Code hooks format USE THIS EXACTLY\n` +
289
+ `The hooks object must be nested inside a top-level \`"hooks"\` key.\n` +
290
+ `Each event contains an array of matcher objects, each with its own \`"hooks"\` array.\n\n` +
291
+ `\`\`\`json\n` +
292
+ `{\n` +
293
+ ` "hooks": {\n` +
294
+ ` "PostToolUse": [\n` +
295
+ ` {\n` +
296
+ ` "matcher": "Edit|Write",\n` +
297
+ ` "hooks": [\n` +
298
+ ` {\n` +
299
+ ` "type": "command",\n` +
300
+ ` "command": "<shell command here>"\n` +
301
+ ` }\n` +
302
+ ` ]\n` +
303
+ ` }\n` +
304
+ ` ]\n` +
305
+ ` }\n` +
306
+ `}\n` +
307
+ `\`\`\`\n\n` +
308
+ `**WRONG formats (do NOT use):**\n` +
309
+ `- \`"hooks": { "post-edit": ["mvn compile"] }\` — INVALID event name and structure\n` +
310
+ `- \`"PostToolUse": [{ "command": "bash", "args": [...] }]\` — missing top-level "hooks" key\n` +
311
+ `- \`{ "command": "cmd", "args": ["/c", "..."] }\` — old format, must use "type": "command"\n\n` +
312
+ `### Valid hook event names — use ONLY these\n` +
313
+ `\`PreToolUse\`, \`PostToolUse\`, \`PostToolUseFailure\`, \`Stop\`, \`SessionStart\`,\n` +
314
+ `\`Notification\`, \`UserPromptSubmit\`, \`PermissionRequest\`, \`ConfigChange\`,\n` +
315
+ `\`SubagentStart\`, \`SubagentStop\`, \`SessionEnd\`\n\n` +
316
+ `### Matcher patterns\n` +
317
+ `- \`"Edit|Write"\` — fires only on file edits\n` +
318
+ `- \`"Bash"\` — fires only on shell commands\n` +
319
+ `- \`""\` (empty) — fires on all occurrences of the event\n\n` +
320
+ `### BUG 8 FIX: Verify build tools exist BEFORE adding hooks\n` +
321
+ `Before adding any hook that runs a build tool, verify it is installed:\n` +
277
322
  (os === "Windows"
278
- ? `Use: \`{ "command": "cmd", "args": ["/c", "<command>"] }\`\n`
279
- : `Use: \`{ "command": "bash", "args": ["-c", "<command>"] }\`\n` +
280
- `**Bash quoting rule**: never use bare \`"\` inside \`-c "..."\` — use \`\\x22\` instead.\n` +
281
- `Replace \`'\` with \`\\x27\`, \`$\` in character classes with \`\\x24\`.\n`) +
282
- `\n### Valid hook event namesuse ONLY these\n` +
283
- `\`PreToolUse\`, \`PostToolUse\`, \`PostToolUseFailure\`, \`Stop\`, \`SessionStart\`\n` +
284
- `If unsure which event name to use: do not write the hook. Print:\n` +
285
- `\`SKIPPED — hook event name uncertain. Valid names: PreToolUse, PostToolUse, PostToolUseFailure, Stop, SessionStart\`\n\n` +
323
+ ? `\`\`\`\nwhere mvn 2>nul && mvn compile -q\nwhere gradle 2>nul && gradle build\nwhere npm 2>nul && npm run build\n\`\`\`\n`
324
+ : `\`\`\`\ncommand -v mvn && mvn compile -q\ncommand -v gradle && gradle build\ncommand -v npm && npm run build\n\`\`\`\n`) +
325
+ `If the tool is NOT installed:\n` +
326
+ `- Wrap the command with an existence check: \`command -v mvn && mvn compile -q\`\n` +
327
+ `- OR skip the hook and print: \`⚠️ SKIPPED mvn hook Maven not found. Install Maven first.\`\n` +
328
+ `- NEVER add a hook for a tool that doesn't exist on the system\n\n` +
286
329
  `### Rules\n` +
287
330
  `- **NEVER write a "model" key into settings.json** — it overrides the user's model selection silently\n` +
288
331
  `- If it exists above: audit quoting of existing hooks first, fix broken ones\n` +
289
332
  `- Only add hooks for patterns that genuinely recur for this project type\n` +
290
- `- Produce valid JSON only\n\n` +
333
+ `- Produce valid JSON only\n` +
334
+ `- The \`"type"\` field in each hook must be one of: \`"command"\`, \`"prompt"\`, \`"agent"\`, \`"http"\`\n\n` +
291
335
  `### Output\n` +
292
336
  `Created/Updated: ✅ settings.json — [hook name and justification]\n` +
293
337
  `Skipped: ⏭ settings.json — [why no hooks warranted]\n`,
@@ -299,16 +343,37 @@ export function buildAtomicSteps(collected, state) {
299
343
  `## Target: .claude/skills/\n` +
300
344
  `Installed: ${vars.SKILLS_LIST}\n\n` +
301
345
  `### When to create\n` +
302
- `Create a skill ONLY if:\n` +
346
+ `Create a skill if:\n` +
303
347
  `- A recurring multi-step project-specific pattern exists in /stack-0-context\n` +
304
- `- It is NOT something Claude already knows (standard patterns don't need skills)\n` +
348
+ `- The project type has standard workflows worth automating (build, deploy, test patterns)\n` +
305
349
  `- It will save time across multiple Claude Code sessions\n\n` +
350
+ `### Correct skill file format\n` +
351
+ `Skills must be created as \`.claude/skills/<skill-name>/SKILL.md\` with YAML frontmatter:\n\n` +
352
+ `\`\`\`yaml\n` +
353
+ `---\n` +
354
+ `name: skill-name\n` +
355
+ `description: What this skill does and when to use it\n` +
356
+ `---\n\n` +
357
+ `Skill instructions here...\n` +
358
+ `\`\`\`\n\n` +
359
+ `Optional frontmatter fields:\n` +
360
+ `- \`disable-model-invocation: true\` — only user can invoke (for commands with side effects)\n` +
361
+ `- \`allowed-tools: Read, Grep\` — restrict which tools the skill can use\n` +
362
+ `- \`context: fork\` — run in isolated subagent\n` +
363
+ `- \`agent: Explore\` — which agent type to use with context: fork\n\n` +
364
+ `### Project-specific skills to consider\n` +
365
+ `Based on what you see in /stack-0-context, consider creating skills for:\n` +
366
+ `- Build/deploy workflows specific to this stack\n` +
367
+ `- Code review patterns specific to this codebase\n` +
368
+ `- Database migration patterns if migration files exist\n` +
369
+ `- Testing patterns if test infrastructure exists\n\n` +
306
370
  `### Rules\n` +
307
- `- Use \`applies-when:\` frontmatter so skills load only when relevant, not every message\n` +
371
+ `- Use \`description:\` frontmatter so Claude knows when to load the skill\n` +
308
372
  `- If a similar skill already exists above: extend it, don't create a parallel one\n` +
309
- `- Empty is valid — no skills is better than useless skills\n\n` +
373
+ `- Empty is valid — no skills is better than useless skills\n` +
374
+ `- Each skill directory MUST contain a SKILL.md file\n\n` +
310
375
  `### Output\n` +
311
- `Created: ✅ .claude/skills/[name] — [what pattern it captures]\n` +
376
+ `Created: ✅ .claude/skills/[name]/SKILL.md — [what pattern it captures]\n` +
312
377
  `Skipped: ⏭ skills — checked [patterns], found [nothing project-specific]\n`,
313
378
  },
314
379
  // --- Step 5: .claude/commands/ ---
@@ -431,12 +496,12 @@ export function buildOrchestratorCommand(steps) {
431
496
  const stepList = runSteps
432
497
  .map((s, i) => `${i + 1}. /${s.filename.replace(".md", "")}`)
433
498
  .join("\n");
434
- return `<!-- claude-setup ${version} ${date} -->
435
-
436
- Run these in order. If one fails, fix and continue from that step.
437
-
438
- ${stepList}
439
-
440
- After all complete: one-line summary of what was created.
499
+ return `<!-- claude-setup ${version} ${date} -->
500
+
501
+ Run these in order. If one fails, fix and continue from that step.
502
+
503
+ ${stepList}
504
+
505
+ After all complete: one-line summary of what was created.
441
506
  `;
442
507
  }
@@ -4,7 +4,8 @@ import { collectProjectFiles } from "../collect.js";
4
4
  import { readState } from "../state.js";
5
5
  import { updateManifest } from "../manifest.js";
6
6
  import { buildAddCommand } from "../builder.js";
7
- import { c } from "../output.js";
7
+ import { estimateTokens, estimateCost } from "../tokens.js";
8
+ import { c, section } from "../output.js";
8
9
  function ensureDir(dir) {
9
10
  if (!existsSync(dir))
10
11
  mkdirSync(dir, { recursive: true });
@@ -46,8 +47,18 @@ capabilities that need documentation, MCP servers, skills, and hooks together.
46
47
  // add only needs config files — source files are irrelevant and waste tokens
47
48
  const collected = await collectProjectFiles(process.cwd(), "configOnly");
48
49
  const content = buildAddCommand(userInput, collected, state);
50
+ // Token tracking
51
+ const tokens = estimateTokens(content);
52
+ const cost = estimateCost(tokens);
49
53
  ensureDir(".claude/commands");
50
54
  writeFileSync(".claude/commands/stack-add.md", content, "utf8");
51
- await updateManifest("add", collected, { input: userInput });
52
- console.log(`\n${c.green("✅")} Ready. Open Claude Code and run:\n ${c.cyan("/stack-add")}\n`);
55
+ await updateManifest("add", collected, {
56
+ input: userInput,
57
+ estimatedTokens: tokens,
58
+ estimatedCost: cost,
59
+ });
60
+ console.log(`\n${c.green("✅")} Ready. Open Claude Code and run:\n ${c.cyan("/stack-add")}`);
61
+ section("Token cost");
62
+ console.log(` ~${tokens.toLocaleString()} input tokens (${c.dim(`Opus $${cost.opus.toFixed(4)} | Sonnet $${cost.sonnet.toFixed(4)} | Haiku $${cost.haiku.toFixed(4)}`)})`);
63
+ console.log("");
53
64
  }
@@ -0,0 +1 @@
1
+ export declare function runCompare(): Promise<void>;
@@ -0,0 +1,84 @@
1
+ import { readTimeline, compareSnapshots } from "../snapshot.js";
2
+ import { c, section } from "../output.js";
3
+ import { createInterface } from "readline";
4
+ async function promptFreeText(question) {
5
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
6
+ return new Promise((resolve) => {
7
+ rl.question(question + " ", (answer) => {
8
+ rl.close();
9
+ resolve(answer.trim());
10
+ });
11
+ });
12
+ }
13
+ export async function runCompare() {
14
+ const cwd = process.cwd();
15
+ const timeline = readTimeline(cwd);
16
+ if (timeline.nodes.length < 2) {
17
+ console.log(`${c.yellow("⚠️")} Need at least 2 snapshots to compare. ` +
18
+ `Run ${c.cyan("npx claude-setup sync")} to create more.`);
19
+ return;
20
+ }
21
+ // Display available nodes
22
+ section("Available snapshots");
23
+ console.log("");
24
+ for (let i = 0; i < timeline.nodes.length; i++) {
25
+ const node = timeline.nodes[i];
26
+ const date = new Date(node.timestamp).toLocaleString();
27
+ const inputStr = node.input ? ` "${node.input}"` : "";
28
+ console.log(` ${c.cyan(node.id)} ${node.command}${inputStr} ${c.dim(date)} ${node.summary}`);
29
+ }
30
+ console.log("");
31
+ const idA = await promptFreeText("Enter first snapshot ID (older):");
32
+ if (!idA) {
33
+ console.log("No input provided.");
34
+ return;
35
+ }
36
+ const idB = await promptFreeText("Enter second snapshot ID (newer):");
37
+ if (!idB) {
38
+ console.log("No input provided.");
39
+ return;
40
+ }
41
+ const nodeA = timeline.nodes.find(n => n.id === idA);
42
+ const nodeB = timeline.nodes.find(n => n.id === idB);
43
+ if (!nodeA) {
44
+ console.log(`${c.red("🔴")} Snapshot "${idA}" not found.`);
45
+ return;
46
+ }
47
+ if (!nodeB) {
48
+ console.log(`${c.red("🔴")} Snapshot "${idB}" not found.`);
49
+ return;
50
+ }
51
+ console.log(`\nComparing ${c.cyan(idA)} → ${c.cyan(idB)}...\n`);
52
+ const result = compareSnapshots(cwd, idA, idB);
53
+ if (result.onlyInA.length) {
54
+ section(`Only in ${idA} (removed after)`);
55
+ for (const f of result.onlyInA) {
56
+ console.log(` ${c.red("-")} ${f}`);
57
+ }
58
+ }
59
+ if (result.onlyInB.length) {
60
+ section(`Only in ${idB} (added after)`);
61
+ for (const f of result.onlyInB) {
62
+ console.log(` ${c.green("+")} ${f}`);
63
+ }
64
+ }
65
+ if (result.changed.length) {
66
+ section("Changed between snapshots");
67
+ for (const f of result.changed) {
68
+ console.log(` ${c.yellow("~")} ${f.path} (${f.linesA} → ${f.linesB} lines)`);
69
+ }
70
+ }
71
+ if (result.identical.length) {
72
+ console.log(`\n ${c.dim(`${result.identical.length} file(s) identical between snapshots`)}`);
73
+ }
74
+ const totalDiffs = result.onlyInA.length + result.onlyInB.length + result.changed.length;
75
+ if (totalDiffs === 0) {
76
+ console.log(`\n${c.green("✅")} Snapshots are identical — no differences found.`);
77
+ }
78
+ else {
79
+ console.log(`\n${c.bold(`${totalDiffs} difference(s)`)} between ${c.cyan(idA)} and ${c.cyan(idB)}.`);
80
+ if (result.changed.length) {
81
+ console.log(`${c.dim("Use")} ${c.cyan("npx claude-setup restore")} ${c.dim("to jump to either snapshot.")}`);
82
+ }
83
+ }
84
+ }
@@ -2,4 +2,6 @@ import { runDoctor } from "../doctor.js";
2
2
  export { runDoctor };
3
3
  export declare function runDoctorCommand(opts?: {
4
4
  verbose?: boolean;
5
+ fix?: boolean;
6
+ testHooks?: boolean;
5
7
  }): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import { runDoctor } from "../doctor.js";
2
2
  export { runDoctor };
3
3
  export async function runDoctorCommand(opts = {}) {
4
- return runDoctor(opts.verbose ?? false);
4
+ return runDoctor(opts.verbose ?? false, opts.fix ?? false, opts.testHooks ?? false);
5
5
  }
@@ -0,0 +1,25 @@
1
+ export interface ConfigTemplate {
2
+ name: string;
3
+ version: "1";
4
+ exportedAt: string;
5
+ exportedFrom: string;
6
+ os: string;
7
+ claudeMd?: string;
8
+ mcpJson?: Record<string, unknown>;
9
+ settings?: Record<string, unknown>;
10
+ skills: Array<{
11
+ name: string;
12
+ content: string;
13
+ }>;
14
+ commands: Array<{
15
+ name: string;
16
+ content: string;
17
+ }>;
18
+ }
19
+ export declare function runExport(): Promise<void>;
20
+ /**
21
+ * Apply a template to the current project.
22
+ * Merge logic: existing content kept, new content added.
23
+ * OS adaptation: MCP commands auto-converted for target OS.
24
+ */
25
+ export declare function applyTemplate(templateSource: string): Promise<void>;