claude-setup 1.1.6 → 1.1.8

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/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
+ import { createInterface } from "readline";
3
4
  import { createRequire } from "module";
4
5
  import { runInit } from "./commands/init.js";
5
6
  import { runAdd } from "./commands/add.js";
@@ -10,6 +11,7 @@ import { runRemove } from "./commands/remove.js";
10
11
  import { runRestore } from "./commands/restore.js";
11
12
  import { runCompare } from "./commands/compare.js";
12
13
  import { runExport } from "./commands/export.js";
14
+ import { c } from "./output.js";
13
15
  const require = createRequire(import.meta.url);
14
16
  const pkg = require("../package.json");
15
17
  const program = new Command();
@@ -24,9 +26,9 @@ program
24
26
  .option("--template <path>", "Apply a template instead of scanning (local path or URL)")
25
27
  .action((opts) => runInit({ dryRun: opts.dryRun, template: opts.template }));
26
28
  program
27
- .command("add")
29
+ .command("add [input...]")
28
30
  .description("Add a multi-file capability")
29
- .action(runAdd);
31
+ .action((input) => runAdd({ input: input?.length ? input.join(" ") : undefined }));
30
32
  program
31
33
  .command("sync")
32
34
  .description("Update setup after project changes")
@@ -49,23 +51,51 @@ program
49
51
  testHooks: opts.testHooks,
50
52
  }));
51
53
  program
52
- .command("remove")
54
+ .command("remove [input...]")
53
55
  .description("Remove a capability cleanly")
54
- .action(runRemove);
55
- // Feature A: Time-travel snapshot commands
56
+ .action((input) => runRemove({ input: input?.length ? input.join(" ") : undefined }));
56
57
  program
57
58
  .command("restore")
58
59
  .description("Jump to any snapshot node, restore files to that state")
59
- .action(runRestore);
60
+ .option("--list", "Show snapshot timeline without prompting")
61
+ .option("--id <snapshotId>", "Restore directly to a specific snapshot ID")
62
+ .action((opts) => runRestore({ list: opts.list, id: opts.id }));
60
63
  program
61
64
  .command("compare")
62
65
  .description("Diff between any two snapshot nodes to see what changed")
63
66
  .action(runCompare);
64
- // Feature H: Config template export
65
67
  program
66
68
  .command("export")
67
69
  .description("Save current project config as a reusable template")
68
70
  .action(runExport);
69
- // Default action when no command given
70
- program.action(() => runInit({}));
71
+ // Default action — interactive menu when no command given
72
+ program.action(async () => {
73
+ const choices = [
74
+ { key: "1", label: "init", desc: "Full project setup", run: () => runInit({}) },
75
+ { key: "2", label: "add", desc: "Add a capability", run: () => runAdd({}) },
76
+ { key: "3", label: "sync", desc: "Update after changes", run: () => runSync({}) },
77
+ { key: "4", label: "status", desc: "Show current state", run: () => runStatus() },
78
+ { key: "5", label: "doctor", desc: "Validate environment", run: () => runDoctorCommand({}) },
79
+ { key: "6", label: "restore", desc: "Time-travel to snapshot", run: () => runRestore({}) },
80
+ { key: "7", label: "compare", desc: "Diff between snapshots", run: () => runCompare() },
81
+ { key: "8", label: "remove", desc: "Remove a capability", run: () => runRemove({}) },
82
+ { key: "9", label: "export", desc: "Save as template", run: () => runExport() },
83
+ ];
84
+ console.log(`\n${c.bold("Claude Setup")} ${c.dim(`v${pkg.version}`)}\n`);
85
+ for (const ch of choices) {
86
+ console.log(` ${c.cyan(ch.key)} ${ch.label.padEnd(10)} ${c.dim(ch.desc)}`);
87
+ }
88
+ console.log("");
89
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
90
+ const answer = await new Promise(resolve => {
91
+ rl.question(` ${c.bold("Choose (1-9):")} `, a => { rl.close(); resolve(a.trim()); });
92
+ });
93
+ const choice = choices.find(ch => ch.key === answer || ch.label === answer.toLowerCase());
94
+ if (!choice) {
95
+ console.log(`\n Invalid choice. Run ${c.cyan("npx claude-setup <command>")} or pick 1-9.\n`);
96
+ return;
97
+ }
98
+ console.log("");
99
+ await choice.run();
100
+ });
71
101
  program.parse();
@@ -104,10 +104,11 @@ export function buildMarketplaceInstructions(input) {
104
104
  const { categories, saasMatches } = classifyRequest(input);
105
105
  const categoryFilter = categories[0] ?? "";
106
106
  const lines = [];
107
- lines.push(`## Marketplace — fully automated install`);
107
+ lines.push(`## Marketplace — fully automated install (DO NOT ASK — JUST DO IT)`);
108
108
  lines.push(``);
109
109
  lines.push(`A plugin marketplace has 416 community skills + 13 official Anthropic plugins.`);
110
- lines.push(`Follow the steps below IN ORDER. Do not stop until a skill is installed.`);
110
+ lines.push(`Follow the steps below IN ORDER. Execute every command yourself. Do not stop until a skill is installed.`);
111
+ lines.push(`**DO NOT ask the user clarifying questions. DO NOT suggest commands for the user to run. Execute them yourself.**`);
111
112
  lines.push(``);
112
113
  if (categories.length > 0 || saasMatches.length > 0) {
113
114
  lines.push(`### Matched for "${input}":`);
@@ -138,11 +139,12 @@ export function buildMarketplaceInstructions(input) {
138
139
  lines.push(``);
139
140
  lines.push(`NOTE: /plugin marketplace add FAILS for this repo (one entry has broken schema).`);
140
141
  lines.push(`Use the DIRECT FETCH approach below instead — no marketplace add needed.`);
142
+ lines.push(`If the curl command fails or returns an error, skip to STEP 3 immediately — do NOT stop.`);
141
143
  lines.push(``);
142
144
  lines.push(`**2a. Fetch catalog and find matching plugin:**`);
143
145
  lines.push(`\`\`\`bash`);
144
146
  lines.push(`curl -s "${MARKETPLACE_CATALOG_URL}" \\`);
145
- lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));const q='${categoryFilter}';const r=d.plugins.filter(p=>(!q||p.category.includes(q))&&p.name&&p.source).slice(0,5).map(p=>({name:p.name,source:p.source,desc:p.description}));console.log(JSON.stringify(r,null,2));"`);
147
+ lines.push(` | node -e "const d=JSON.parse(require('fs').readFileSync(0,'utf8'));const q='${categoryFilter}';const r=d.plugins.filter(p=>(q===''||p.category.includes(q))&&p.name&&p.source).slice(0,10).map(p=>({name:p.name,source:p.source,desc:p.description}));console.log(JSON.stringify(r,null,2));"`);
146
148
  lines.push(`\`\`\``);
147
149
  lines.push(``);
148
150
  lines.push(`**2b. Pick the best match — get its source path (e.g. \`./plugins/productivity/my-skill\`)**`);
@@ -152,7 +154,7 @@ export function buildMarketplaceInstructions(input) {
152
154
  lines.push(`# Replace PLUGIN_SOURCE_PATH with value from step 2b (e.g. plugins/productivity/my-skill)`);
153
155
  lines.push(`PLUGIN_SOURCE_PATH="plugins/productivity/my-skill"`);
154
156
  lines.push(`curl -s "https://api.github.com/repos/${MARKETPLACE_REPO}/contents/\${PLUGIN_SOURCE_PATH}/skills" \\`);
155
- lines.push(` | node -e "const a=JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));console.log(a.map(x=>x.name).join('\\n'));"`);
157
+ lines.push(` | node -e "const a=JSON.parse(require('fs').readFileSync(0,'utf8'));console.log(a.map(x=>x.name).join('\\n'));"`);
156
158
  lines.push(`\`\`\``);
157
159
  lines.push(``);
158
160
  lines.push(`**2d. For each skill listed, download and install it:**`);
package/dist/os.d.ts CHANGED
@@ -1,18 +1,28 @@
1
- export type DetectedOS = "Windows" | "macOS" | "Linux";
1
+ export type DetectedOS = "Windows" | "macOS" | "Linux" | "WSL";
2
2
  /**
3
- * Detect OS once per session. Order per spec:
3
+ * Detect OS once per session. Order:
4
4
  * 1. COMSPEC set → Windows
5
5
  * 2. OS === "Windows_NT" → Windows
6
6
  * 3. process.platform === "win32" → Windows
7
- * 4. uname() === "Darwin" → macOS
8
- * 5. defaultLinux
7
+ * 4. /proc/version contains "microsoft" or "WSL" WSL
8
+ * 5. WSL_DISTRO_NAME env var set WSL
9
+ * 6. uname() === "Darwin" → macOS
10
+ * 7. default → Linux
9
11
  */
10
12
  export declare function detectOS(): DetectedOS;
13
+ /** Returns true if the OS uses Unix-style shell commands (bash, npx direct) */
14
+ export declare function isUnixLike(os: DetectedOS): boolean;
11
15
  /**
12
16
  * Verified MCP package names — ONLY use these.
13
17
  * If a service is not in this map, do not guess a package name.
14
18
  */
15
19
  export declare const VERIFIED_MCP_PACKAGES: Record<string, string>;
20
+ /** Default connection strings for local services — used when env vars are not set */
21
+ export declare const DEFAULT_SERVICE_CONNECTIONS: Record<string, {
22
+ envVar: string;
23
+ defaultUrl: string;
24
+ testCmd: Record<DetectedOS, string>;
25
+ }>;
16
26
  /** MCP command format per OS — always includes -y to prevent npx install hangs */
17
27
  export declare function mcpCommandFormat(os: DetectedOS, pkg: string): {
18
28
  command: string;
@@ -23,3 +33,22 @@ export declare function hookShellFormat(os: DetectedOS, cmd: string): {
23
33
  command: string;
24
34
  args: string[];
25
35
  };
36
+ /**
37
+ * Detect which services are available on the local machine.
38
+ * Returns a map of service name → detected info.
39
+ */
40
+ export declare function detectLocalServices(os: DetectedOS): Record<string, {
41
+ found: boolean;
42
+ defaultUrl: string;
43
+ envVar: string;
44
+ }>;
45
+ /**
46
+ * Scan project files for service evidence and return auto-discovery instructions.
47
+ * Reads docker-compose, .env.example, package.json to find which services are used.
48
+ */
49
+ export declare function discoverProjectServices(cwd: string): string[];
50
+ /**
51
+ * Build auto-discovery MCP configuration instructions for the detected OS.
52
+ * Returns markdown text to embed in templates.
53
+ */
54
+ export declare function buildServiceDiscoveryInstructions(cwd: string): string;
package/dist/os.js CHANGED
@@ -1,11 +1,15 @@
1
1
  import { execSync } from "child_process";
2
+ import { existsSync, readFileSync } from "fs";
3
+ import { join } from "path";
2
4
  /**
3
- * Detect OS once per session. Order per spec:
5
+ * Detect OS once per session. Order:
4
6
  * 1. COMSPEC set → Windows
5
7
  * 2. OS === "Windows_NT" → Windows
6
8
  * 3. process.platform === "win32" → Windows
7
- * 4. uname() === "Darwin" → macOS
8
- * 5. defaultLinux
9
+ * 4. /proc/version contains "microsoft" or "WSL" WSL
10
+ * 5. WSL_DISTRO_NAME env var set WSL
11
+ * 6. uname() === "Darwin" → macOS
12
+ * 7. default → Linux
9
13
  */
10
14
  export function detectOS() {
11
15
  if (process.env.COMSPEC)
@@ -14,6 +18,15 @@ export function detectOS() {
14
18
  return "Windows";
15
19
  if (process.platform === "win32")
16
20
  return "Windows";
21
+ // WSL detection — runs as Linux but under Windows kernel
22
+ if (process.env.WSL_DISTRO_NAME)
23
+ return "WSL";
24
+ try {
25
+ const procVersion = readFileSync("/proc/version", "utf8").toLowerCase();
26
+ if (procVersion.includes("microsoft") || procVersion.includes("wsl"))
27
+ return "WSL";
28
+ }
29
+ catch { /* not WSL or /proc not available */ }
17
30
  try {
18
31
  const uname = execSync("uname", { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
19
32
  if (uname === "Darwin")
@@ -22,6 +35,10 @@ export function detectOS() {
22
35
  catch { /* not unix — unlikely to reach here */ }
23
36
  return "Linux";
24
37
  }
38
+ /** Returns true if the OS uses Unix-style shell commands (bash, npx direct) */
39
+ export function isUnixLike(os) {
40
+ return os === "Linux" || os === "macOS" || os === "WSL";
41
+ }
25
42
  /**
26
43
  * Verified MCP package names — ONLY use these.
27
44
  * If a service is not in this map, do not guess a package name.
@@ -41,11 +58,55 @@ export const VERIFIED_MCP_PACKAGES = {
41
58
  mysql: "@benborla29/mcp-server-mysql",
42
59
  mongodb: "mcp-mongo-server",
43
60
  };
61
+ /** Default connection strings for local services — used when env vars are not set */
62
+ export const DEFAULT_SERVICE_CONNECTIONS = {
63
+ postgres: {
64
+ envVar: "DATABASE_URL",
65
+ defaultUrl: "postgresql://localhost:5432/postgres",
66
+ testCmd: {
67
+ Windows: "where psql 2>nul || where pg_isready 2>nul",
68
+ macOS: "command -v psql || command -v pg_isready",
69
+ Linux: "command -v psql || command -v pg_isready",
70
+ WSL: "command -v psql || command -v pg_isready || /mnt/c/Program\\ Files/PostgreSQL/*/bin/psql.exe --version 2>/dev/null",
71
+ },
72
+ },
73
+ mysql: {
74
+ envVar: "MYSQL_URL",
75
+ defaultUrl: "mysql://root@localhost:3306",
76
+ testCmd: {
77
+ Windows: "where mysql 2>nul",
78
+ macOS: "command -v mysql || brew list mysql 2>/dev/null",
79
+ Linux: "command -v mysql",
80
+ WSL: "command -v mysql || /mnt/c/Program\\ Files/MySQL/*/bin/mysql.exe --version 2>/dev/null",
81
+ },
82
+ },
83
+ mongodb: {
84
+ envVar: "MONGODB_URI",
85
+ defaultUrl: "mongodb://localhost:27017",
86
+ testCmd: {
87
+ Windows: "where mongosh 2>nul || where mongo 2>nul",
88
+ macOS: "command -v mongosh || command -v mongo || brew list mongodb-community 2>/dev/null",
89
+ Linux: "command -v mongosh || command -v mongo",
90
+ WSL: "command -v mongosh || command -v mongo || mongosh.exe --version 2>/dev/null",
91
+ },
92
+ },
93
+ redis: {
94
+ envVar: "REDIS_URL",
95
+ defaultUrl: "redis://localhost:6379",
96
+ testCmd: {
97
+ Windows: "where redis-cli 2>nul",
98
+ macOS: "command -v redis-cli || brew list redis 2>/dev/null",
99
+ Linux: "command -v redis-cli",
100
+ WSL: "command -v redis-cli || redis-cli.exe --version 2>/dev/null",
101
+ },
102
+ },
103
+ };
44
104
  /** MCP command format per OS — always includes -y to prevent npx install hangs */
45
105
  export function mcpCommandFormat(os, pkg) {
46
106
  if (os === "Windows") {
47
107
  return { command: "cmd", args: ["/c", "npx", "-y", pkg] };
48
108
  }
109
+ // macOS, Linux, and WSL all use npx directly
49
110
  return { command: "npx", args: ["-y", pkg] };
50
111
  }
51
112
  /** Hook shell format per OS */
@@ -53,5 +114,179 @@ export function hookShellFormat(os, cmd) {
53
114
  if (os === "Windows") {
54
115
  return { command: "cmd", args: ["/c", cmd] };
55
116
  }
117
+ // macOS, Linux, and WSL all use bash
56
118
  return { command: "bash", args: ["-c", cmd] };
57
119
  }
120
+ /**
121
+ * Detect which services are available on the local machine.
122
+ * Returns a map of service name → detected info.
123
+ */
124
+ export function detectLocalServices(os) {
125
+ const results = {};
126
+ for (const [service, config] of Object.entries(DEFAULT_SERVICE_CONNECTIONS)) {
127
+ let found = false;
128
+ try {
129
+ const cmd = config.testCmd[os];
130
+ execSync(cmd, { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 });
131
+ found = true;
132
+ }
133
+ catch { /* not installed */ }
134
+ results[service] = {
135
+ found,
136
+ defaultUrl: config.defaultUrl,
137
+ envVar: config.envVar,
138
+ };
139
+ }
140
+ return results;
141
+ }
142
+ /**
143
+ * Scan project files for service evidence and return auto-discovery instructions.
144
+ * Reads docker-compose, .env.example, package.json to find which services are used.
145
+ */
146
+ export function discoverProjectServices(cwd) {
147
+ const discovered = [];
148
+ const os = detectOS();
149
+ // Check docker-compose.yml for service definitions
150
+ for (const dcFile of ["docker-compose.yml", "docker-compose.yaml", "compose.yml", "compose.yaml"]) {
151
+ const dcPath = join(cwd, dcFile);
152
+ if (existsSync(dcPath)) {
153
+ try {
154
+ const content = readFileSync(dcPath, "utf8");
155
+ if (/postgres|pg_/i.test(content))
156
+ discovered.push("postgres");
157
+ if (/mysql|mariadb/i.test(content))
158
+ discovered.push("mysql");
159
+ if (/mongo/i.test(content))
160
+ discovered.push("mongodb");
161
+ if (/redis/i.test(content))
162
+ discovered.push("redis");
163
+ }
164
+ catch { /* skip */ }
165
+ }
166
+ }
167
+ // Check .env.example or .env.sample for service-related vars
168
+ for (const envFile of [".env.example", ".env.sample", ".env.template"]) {
169
+ const envPath = join(cwd, envFile);
170
+ if (existsSync(envPath)) {
171
+ try {
172
+ const content = readFileSync(envPath, "utf8");
173
+ if (/DATABASE_URL|POSTGRES|PG_/i.test(content) && !discovered.includes("postgres"))
174
+ discovered.push("postgres");
175
+ if (/MYSQL/i.test(content) && !discovered.includes("mysql"))
176
+ discovered.push("mysql");
177
+ if (/MONGO/i.test(content) && !discovered.includes("mongodb"))
178
+ discovered.push("mongodb");
179
+ if (/REDIS/i.test(content) && !discovered.includes("redis"))
180
+ discovered.push("redis");
181
+ }
182
+ catch { /* skip */ }
183
+ }
184
+ }
185
+ // Check package.json for database dependencies
186
+ const pkgPath = join(cwd, "package.json");
187
+ if (existsSync(pkgPath)) {
188
+ try {
189
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
190
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
191
+ if (allDeps.pg || allDeps.prisma || allDeps["@prisma/client"] || allDeps.knex) {
192
+ if (!discovered.includes("postgres"))
193
+ discovered.push("postgres");
194
+ }
195
+ if (allDeps.mysql2 || allDeps.mysql) {
196
+ if (!discovered.includes("mysql"))
197
+ discovered.push("mysql");
198
+ }
199
+ if (allDeps.mongoose || allDeps.mongodb) {
200
+ if (!discovered.includes("mongodb"))
201
+ discovered.push("mongodb");
202
+ }
203
+ if (allDeps.redis || allDeps.ioredis) {
204
+ if (!discovered.includes("redis"))
205
+ discovered.push("redis");
206
+ }
207
+ }
208
+ catch { /* skip */ }
209
+ }
210
+ // Check requirements.txt / pyproject.toml for Python projects
211
+ for (const pyFile of ["requirements.txt", "pyproject.toml"]) {
212
+ const pyPath = join(cwd, pyFile);
213
+ if (existsSync(pyPath)) {
214
+ try {
215
+ const content = readFileSync(pyPath, "utf8");
216
+ if (/psycopg|asyncpg|sqlalchemy/i.test(content) && !discovered.includes("postgres"))
217
+ discovered.push("postgres");
218
+ if (/pymysql|mysqlclient/i.test(content) && !discovered.includes("mysql"))
219
+ discovered.push("mysql");
220
+ if (/pymongo|motor/i.test(content) && !discovered.includes("mongodb"))
221
+ discovered.push("mongodb");
222
+ if (/redis/i.test(content) && !discovered.includes("redis"))
223
+ discovered.push("redis");
224
+ }
225
+ catch { /* skip */ }
226
+ }
227
+ }
228
+ return [...new Set(discovered)];
229
+ }
230
+ /**
231
+ * Build auto-discovery MCP configuration instructions for the detected OS.
232
+ * Returns markdown text to embed in templates.
233
+ */
234
+ export function buildServiceDiscoveryInstructions(cwd) {
235
+ const os = detectOS();
236
+ const projectServices = discoverProjectServices(cwd);
237
+ const localServices = detectLocalServices(os);
238
+ const lines = [];
239
+ if (projectServices.length === 0)
240
+ return "";
241
+ lines.push(`### Auto-discovered services`);
242
+ lines.push(`The following services were detected in your project files:\n`);
243
+ for (const service of projectServices) {
244
+ const local = localServices[service];
245
+ const pkg = VERIFIED_MCP_PACKAGES[service];
246
+ if (!pkg || !local)
247
+ continue;
248
+ const status = local.found ? "installed locally" : "not found locally";
249
+ const statusIcon = local.found ? "✅" : "⚠️";
250
+ lines.push(`**${service}** — ${statusIcon} ${status}`);
251
+ if (local.found) {
252
+ lines.push(`- Default connection: \`${local.defaultUrl}\``);
253
+ lines.push(`- Env var: \`${local.envVar}\``);
254
+ lines.push(`- If \`${local.envVar}\` is not set in the environment, use the default: \`${local.defaultUrl}\``);
255
+ }
256
+ else {
257
+ lines.push(`- Env var: \`${local.envVar}\` — must be set before starting Claude Code`);
258
+ lines.push(`- Use \`\${${local.envVar}}\` in .mcp.json env block`);
259
+ }
260
+ lines.push(``);
261
+ }
262
+ lines.push(`### MCP auto-configuration strategy`);
263
+ lines.push(``);
264
+ lines.push(`For each service above, configure .mcp.json as follows:`);
265
+ lines.push(`1. **Check if the env var is already set** in the user's environment`);
266
+ lines.push(`2. **If set** → use \`\${VARNAME}\` syntax in the env block`);
267
+ lines.push(`3. **If not set but service is installed locally** → use the default connection URL directly in the env block AND document the var in .env.example`);
268
+ lines.push(`4. **If not set and not installed** → use \`\${VARNAME}\` syntax and flag: "⚠️ Set ${"{VARNAME}"} before starting Claude Code"`);
269
+ lines.push(``);
270
+ // OS-specific MCP format reminder
271
+ if (os === "Windows") {
272
+ lines.push(`### OS format (Windows detected)`);
273
+ lines.push(`\`\`\`json`);
274
+ lines.push(`{ "command": "cmd", "args": ["/c", "npx", "-y", "<package>"], "env": { ... } }`);
275
+ lines.push(`\`\`\``);
276
+ }
277
+ else if (os === "WSL") {
278
+ lines.push(`### OS format (WSL detected — uses Linux-style commands)`);
279
+ lines.push(`\`\`\`json`);
280
+ lines.push(`{ "command": "npx", "args": ["-y", "<package>"], "env": { ... } }`);
281
+ lines.push(`\`\`\``);
282
+ lines.push(`Note: WSL can also access Windows-side services on localhost. If a service runs on the Windows host, it is reachable at \`localhost\` from WSL.`);
283
+ }
284
+ else {
285
+ lines.push(`### OS format (${os} detected)`);
286
+ lines.push(`\`\`\`json`);
287
+ lines.push(`{ "command": "npx", "args": ["-y", "<package>"], "env": { ... } }`);
288
+ lines.push(`\`\`\``);
289
+ }
290
+ lines.push(``);
291
+ return lines.join("\n");
292
+ }
package/dist/snapshot.js CHANGED
@@ -88,6 +88,7 @@ export function createSnapshot(cwd, command, changedFiles, opts = {}) {
88
88
  fullSnapshot: true,
89
89
  };
90
90
  timeline.nodes.push(node);
91
+ delete timeline.restoredTo; // User is at latest — clear any restore marker
91
92
  writeTimeline(cwd, timeline);
92
93
  writeNodeData(cwd, nodeId, data);
93
94
  return node;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-setup",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "Setup layer for Claude Code — reads your project, writes command files, Claude Code does the rest",
5
5
  "type": "module",
6
6
  "bin": {
package/templates/add.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  Add to Claude Code setup: "{{USER_INPUT}}"
4
4
 
5
+ **CRITICAL: Marketplace-first. Do NOT ask clarifying questions. Search and install automatically.**
6
+ **Execute every curl/install command yourself. The user expects zero-friction automation.**
7
+
5
8
  ## Project context
6
9
  {{PROJECT_CONTEXT}}
7
10
 
@@ -38,9 +41,38 @@ Parse the user's request and take ALL applicable actions:
38
41
  If the request mentions an external service (database, API, browser, etc.):
39
42
  - Check the verified MCP package list below
40
43
  - If found: add to `.mcp.json` with OS-correct format (detected: {{DETECTED_OS}})
41
- - Use `${VARNAME}` syntax for all credentials — NEVER hardcode
44
+ - **Smart connection strings** follow this order:
45
+ 1. Check if the env var is already set in the environment
46
+ 2. If not set, detect if the service is installed locally (run check command below)
47
+ 3. If local service found: use default localhost URL directly in env block
48
+ 4. If nothing found: use `${VARNAME}` syntax and flag the missing var
42
49
  - Document new env vars in `.env.example`
43
50
 
51
+ **Service detection commands ({{DETECTED_OS}}):**
52
+ {{#if IS_WINDOWS}}
53
+ - PostgreSQL: `where psql 2>nul` → default: `postgresql://localhost:5432/postgres`
54
+ - MongoDB: `where mongosh 2>nul` → default: `mongodb://localhost:27017`
55
+ - Redis: `where redis-cli 2>nul` → default: `redis://localhost:6379`
56
+ - MySQL: `where mysql 2>nul` → default: `mysql://root@localhost:3306`
57
+ {{else}}
58
+ {{#if IS_MACOS}}
59
+ - PostgreSQL: `command -v psql || brew list postgresql 2>/dev/null` → default: `postgresql://localhost:5432/postgres`
60
+ - MongoDB: `command -v mongosh || brew list mongodb-community 2>/dev/null` → default: `mongodb://localhost:27017`
61
+ - Redis: `command -v redis-cli || brew list redis 2>/dev/null` → default: `redis://localhost:6379`
62
+ - MySQL: `command -v mysql || brew list mysql 2>/dev/null` → default: `mysql://root@localhost:3306`
63
+ {{else}}
64
+ - PostgreSQL: `command -v psql` → default: `postgresql://localhost:5432/postgres`
65
+ - MongoDB: `command -v mongosh` → default: `mongodb://localhost:27017`
66
+ - Redis: `command -v redis-cli` → default: `redis://localhost:6379`
67
+ - MySQL: `command -v mysql` → default: `mysql://root@localhost:3306`
68
+ {{/if}}
69
+ {{#if IS_WSL}}
70
+ Note: WSL can access Windows-host services on localhost. If the service runs on the Windows side, it is reachable at `localhost` from WSL.
71
+ {{/if}}
72
+ {{/if}}
73
+
74
+ Run the check command. If the service IS installed locally and the env var is NOT set, use the default URL directly. This avoids the "MCP server not showing" problem where `${VARNAME}` fails silently.
75
+
44
76
  Verified MCP packages — ONLY use these for MCP servers:
45
77
  ```
46
78
  playwright → @playwright/mcp@latest
package/templates/sync.md CHANGED
@@ -1,8 +1,12 @@
1
1
  <!-- claude-setup sync {{DATE}} | last: {{LAST_RUN_DATE}} -->
2
2
 
3
3
  > **Step 1 — always do this first:** Run `npx claude-setup sync` to refresh this file with the latest diff.
4
- > - If it outputs "No changes since…" → respond "Stack already in sync." and STOP.
5
- > - Otherwise re-read `.claude/commands/stack-sync.md` (Step 2), then process the diff below (Step 3).
4
+ > - If the output says **"No changes"** and shows "Setup is current" → respond "No changes detected since last sync. Everything is up to date." and STOP.
5
+ > - If the output shows **changes** (any "+N added", "~N modified", or "-N deleted" with N > 0) → continue to Step 2.
6
+ > **Step 2:** Re-read `.claude/commands/stack-sync.md` — it now contains the actual diff with line-level changes.
7
+ > **Step 3:** Process every change listed below. Update CLAUDE.md, .mcp.json, settings.json as needed.
8
+
9
+ **IMPORTANT:** Do NOT tell the user to "run /stack-sync" — you ARE running it right now. Process the diff below.
6
10
 
7
11
  Project changed since last setup. Update ONLY what the changes demand.
8
12
 
@@ -38,16 +42,20 @@ Skills: {{SKILLS_LIST}} | Commands: {{COMMANDS_LIST}} | Workflows: {{WORKFLOWS_L
38
42
 
39
43
  ## Your job
40
44
 
41
- For EACH changed file: does this change have any implication for the Claude Code setup?
45
+ For EACH changed file, update the Claude Code setup:
46
+
47
+ **Source files added/removed/modified — ALWAYS update CLAUDE.md:**
48
+ - New source directories or modules → add to key dirs section
49
+ - New routes, services, controllers → document the new endpoints/patterns
50
+ - New dependencies or frameworks → update runtime section
51
+ - Renamed or restructured files → update stale paths
52
+ - CLAUDE.md must reflect the CURRENT project structure, not just config files
42
53
 
43
- Reason about the signal:
54
+ **Config and infrastructure changes:**
44
55
  - New dependency → new MCP server needed? New hook justified?
45
56
  - New docker-compose service → new MCP entry? Env vars changed?
46
- - Source file added/removed → CLAUDE.md paths stale? Skill still applies?
47
57
  - Config deleted → remove its MCP/hook reference if it was the only evidence?
48
58
 
49
- Update ONLY what the change demands.
50
- Do NOT update things that did not change.
51
59
  Do NOT rewrite files — surgical edits only.
52
60
  If unsure about a change's implication: flag it, don't guess.
53
61