claude-setup 1.1.7 → 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/README.md CHANGED
@@ -1,161 +1,142 @@
1
1
  # claude-setup
2
2
 
3
- Setup layer for Claude Code. Reads your project, writes command files, Claude Code does the rest.
3
+ Your project already has the answers `claude-setup` reads them and configures Claude Code so you don't have to.
4
4
 
5
- **The CLI has zero intelligence.** All reasoning is delegated to Claude Code via the command files.
5
+ One command. No manual config. Works on **Windows, macOS, Linux, and WSL**.
6
6
 
7
- ## Install
7
+ ## Get started
8
8
 
9
9
  ```bash
10
- npx claude-setup init
10
+ npx claude-setup
11
11
  ```
12
12
 
13
- Then open Claude Code and run `/stack-init`.
13
+ Pick `1` (init). Then open Claude Code and run:
14
14
 
15
- ## Commands
16
-
17
- | Command | What it does |
18
- |---------|-------------|
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 |
28
-
29
- ### Flags
30
-
31
- ```bash
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
15
+ ```
16
+ /stack-init
39
17
  ```
40
18
 
41
- ## How it works
19
+ That's it. Claude Code now knows your stack, your services, your conventions.
42
20
 
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.
21
+ ## What happens during init
46
22
 
47
- ## What it creates
23
+ `claude-setup` scans your project files — `package.json`, `docker-compose.yml`, `.env.example`, source code — and generates everything Claude Code needs:
48
24
 
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) |
25
+ | Generated | What it does |
26
+ |-----------|-------------|
27
+ | **CLAUDE.md** | Project context stack, structure, commands, conventions |
28
+ | **.mcp.json** | MCP server connections auto-detected from your dependencies |
29
+ | **settings.json** | Hooks auto-format, token tracking, build triggers |
30
+ | **skills/** | Reusable patterns for your workflow |
31
+ | **commands/** | Slash commands that work inside Claude Code |
57
32
 
58
- ## Snapshots
33
+ Every line comes from evidence in your project files. No guessing.
59
34
 
60
- Every `init` and `sync` creates a snapshot node — a checkpoint on a timeline. Snapshots store the content of changed files only.
35
+ ### MCP servers are auto-configured
61
36
 
62
- ```
63
- init ──→ sync#1 ──→ sync#2 ──→ sync#3 (current)
64
- │ │
65
- │ └─ bug introduced here
66
- └─ jump back here
67
- ```
37
+ `claude-setup` detects your databases and services automatically:
68
38
 
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
39
+ - Finds PostgreSQL, MongoDB, Redis, MySQL from your deps, docker-compose, or env files
40
+ - Checks if the service is installed locally (`psql`, `mongosh`, `redis-cli`)
41
+ - Uses the right connection URLno broken `${VARNAME}` that fails silently
42
+ - Formats commands for your OS (`cmd /c npx` on Windows, `npx` everywhere else)
72
43
 
73
- ## Templates
44
+ ## After init
74
45
 
75
- Save your setup and reuse it across projects.
46
+ These slash commands work inside Claude Code:
76
47
 
77
- ```bash
78
- # Export current setup
79
- npx claude-setup export
80
- # creates my-template.claude-template.json
48
+ | Command | What it does |
49
+ |---------|-------------|
50
+ | `/stack-sync` | Detect file changes, update your setup |
51
+ | `/stack-add` | Add a capability — searches 400+ marketplace plugins first |
52
+ | `/stack-status` | Show project state, snapshots, token usage |
53
+ | `/stack-doctor` | Validate environment, auto-fix issues |
54
+ | `/stack-restore` | Time-travel to any snapshot |
55
+ | `/stack-remove` | Remove a capability cleanly |
81
56
 
82
- # Apply to a new project
83
- npx claude-setup init --template my-template.claude-template.json
57
+ ### `/stack-add` searches the marketplace for you
84
58
 
85
- # Apply from a URL
86
- npx claude-setup init --template https://example.com/template.json
87
- ```
59
+ Say what you want — it searches 400+ community plugins and 13 official Anthropic plugins, downloads and installs matching skills automatically. No manual steps.
88
60
 
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
61
+ ```
62
+ /stack-add
63
+ > "E2E testing and Stripe integration"
64
+ ```
93
65
 
94
- ## Token Cost Tracking
66
+ ### `/stack-sync` shows what changed
95
67
 
96
- Every command shows estimated token usage and cost across all Claude models.
68
+ Every sync creates a snapshot and shows a color-coded diff:
97
69
 
98
70
  ```
99
- Token cost
100
- ~2,450 input tokens (Opus $0.0368 | Sonnet $0.0074 | Haiku $0.0006)
71
+ Changes since 2026-03-28T14:32:01.904Z:
72
+ +2 added ~3 modified -1 deleted
73
+
74
+ Added files:
75
+ + src/api/payments.ts (48 lines)
76
+ + src/api/webhooks.ts (32 lines)
77
+
78
+ Modified files:
79
+ ~ package.json (+3 lines, -1 lines)
80
+ ~ src/index.ts (+8 lines, -2 lines)
101
81
  ```
102
82
 
103
- Status shows cumulative stats, per-command averages, and cost trends. Use `--budget` on sync to override the token limit for a single run.
83
+ Claude Code sees the actual line-level changes and updates your setup surgically.
104
84
 
105
- ## Doctor
85
+ ## Snapshots
106
86
 
107
- Validates your entire setup and reports issues by severity.
87
+ Every init and sync saves a full snapshot. You can jump to any point in time:
108
88
 
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
89
+ ```
90
+ /stack-restore
113
91
  ```
114
92
 
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
93
+ ```
94
+ init ──> sync#1 ──> sync#2 ──> sync#3 (you are here)
95
+ |
96
+ └── jump back here anytime
97
+ ```
127
98
 
128
- ## Marketplace
99
+ Snapshots are never deleted. Go back, go forward, freely.
129
100
 
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.
101
+ ## All CLI commands
131
102
 
132
103
  ```bash
133
- npx claude-setup add
134
- # "Stripe and frontend skills"
135
- # → generates stack-add.md with marketplace search + install instructions
104
+ npx claude-setup # Interactive menu
105
+ npx claude-setup init # Full project setup
106
+ npx claude-setup sync # Checkpoint + update
107
+ npx claude-setup add "postgres and testing" # Add capabilities
108
+ npx claude-setup status # Dashboard
109
+ npx claude-setup doctor # Validate everything
110
+ npx claude-setup doctor --fix # Auto-fix issues
111
+ npx claude-setup restore # Time-travel
112
+ npx claude-setup compare # Diff two snapshots
113
+ npx claude-setup remove "redis" # Remove cleanly
114
+ npx claude-setup export # Save as template
115
+ npx claude-setup init --template file # Apply a template
136
116
  ```
137
117
 
138
118
  ## Configuration
139
119
 
140
- Auto-generated on first run. Edit `.claude-setup.json` to customize:
120
+ Auto-generated on first run. Edit `.claude-setup.json` if needed:
141
121
 
142
122
  ```json
143
123
  {
144
124
  "maxSourceFiles": 15,
145
125
  "maxDepth": 6,
146
- "maxFileSizeKB": 80,
147
- "tokenBudget": {
148
- "init": 12000,
149
- "sync": 6000,
150
- "add": 3000,
151
- "remove": 2000
152
- },
153
- "digestMode": true,
154
- "extraBlockedDirs": [],
155
- "sourceDirs": []
126
+ "tokenBudget": { "init": 12000, "sync": 6000, "add": 3000 },
127
+ "digestMode": true
156
128
  }
157
129
  ```
158
130
 
131
+ ## Supported platforms
132
+
133
+ | Platform | Status | MCP format |
134
+ |----------|--------|-----------|
135
+ | Windows | Full support | `cmd /c npx -y <pkg>` |
136
+ | macOS | Full support + Homebrew detection | `npx -y <pkg>` |
137
+ | Linux | Full support | `npx -y <pkg>` |
138
+ | WSL | Full support + Windows host access | `npx -y <pkg>` |
139
+
159
140
  ## License
160
141
 
161
142
  MIT
package/dist/builder.d.ts CHANGED
@@ -11,6 +11,12 @@ export interface FileDiff {
11
11
  changed: Array<{
12
12
  path: string;
13
13
  current: string;
14
+ previous?: string;
15
+ lineDiff?: {
16
+ added: string[];
17
+ removed: string[];
18
+ summary: string;
19
+ };
14
20
  }>;
15
21
  deleted: string[];
16
22
  }
package/dist/builder.js CHANGED
@@ -2,7 +2,7 @@ import { readFileSync } from "fs";
2
2
  import { join, dirname } from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { loadConfig } from "./config.js";
5
- import { detectOS, VERIFIED_MCP_PACKAGES } from "./os.js";
5
+ import { detectOS, isUnixLike, VERIFIED_MCP_PACKAGES, buildServiceDiscoveryInstructions } from "./os.js";
6
6
  import { buildMarketplaceInstructions } from "./marketplace.js";
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
8
  const TEMPLATES_DIR = join(__dirname, "..", "templates");
@@ -91,6 +91,7 @@ function buildVars(collected, state) {
91
91
  };
92
92
  }
93
93
  function buildFlags(_collected, state) {
94
+ const os = detectOS();
94
95
  return {
95
96
  HAS_SOURCE: _collected.source.length > 0,
96
97
  HAS_SKIPPED: _collected.skipped.length > 0,
@@ -98,7 +99,10 @@ function buildFlags(_collected, state) {
98
99
  HAS_MCP_JSON: state.mcpJson.exists,
99
100
  HAS_SETTINGS: state.settings.exists,
100
101
  HAS_GITHUB_DIR: state.hasGithubDir,
101
- IS_WINDOWS: detectOS() === "Windows",
102
+ IS_WINDOWS: os === "Windows", // WSL uses Unix-style commands, not cmd
103
+ IS_WSL: os === "WSL",
104
+ IS_MACOS: os === "macOS",
105
+ IS_UNIX_LIKE: isUnixLike(os),
102
106
  };
103
107
  }
104
108
  // --- Token budget enforcement ---
@@ -153,15 +157,43 @@ export function buildAddCommand(input, collected, state) {
153
157
  }, "add");
154
158
  }
155
159
  export function buildSyncCommand(diff, collected, state) {
156
- // Compact diff format — paths + one-line summary, not full content
160
+ // Rich diff format — paths + line-level changes for modified files
157
161
  const addedStr = diff.added.length > 0
158
162
  ? diff.added.map(f => `- **${f.path}** (new) — ${f.content.split("\n").length} lines`).join("\n")
159
163
  : "(none)";
160
- const modifiedStr = diff.changed.length > 0
161
- ? diff.changed.map(f => `- **${f.path}** (modified)`).join("\n")
162
- : "(none)";
164
+ // Modified files now include line-level diffs
165
+ let modifiedStr;
166
+ if (diff.changed.length > 0) {
167
+ const parts = [];
168
+ for (const f of diff.changed) {
169
+ const lines = [`- **${f.path}** (modified)`];
170
+ if (f.lineDiff) {
171
+ lines[0] += ` — ${f.lineDiff.summary}`;
172
+ if (f.lineDiff.removed.length > 0 || f.lineDiff.added.length > 0) {
173
+ lines.push(" ```diff");
174
+ for (const l of f.lineDiff.removed.slice(0, 8)) {
175
+ lines.push(` - ${l.trimEnd().slice(0, 120)}`);
176
+ }
177
+ for (const l of f.lineDiff.added.slice(0, 8)) {
178
+ lines.push(` + ${l.trimEnd().slice(0, 120)}`);
179
+ }
180
+ const totalShown = Math.min(f.lineDiff.removed.length, 8) + Math.min(f.lineDiff.added.length, 8);
181
+ const total = f.lineDiff.removed.length + f.lineDiff.added.length;
182
+ if (total > totalShown) {
183
+ lines.push(` # ... +${total - totalShown} more changes`);
184
+ }
185
+ lines.push(" ```");
186
+ }
187
+ }
188
+ parts.push(lines.join("\n"));
189
+ }
190
+ modifiedStr = parts.join("\n");
191
+ }
192
+ else {
193
+ modifiedStr = "(none)";
194
+ }
163
195
  const deletedStr = diff.deleted.length > 0
164
- ? diff.deleted.map(f => `- ${f}`).join("\n")
196
+ ? diff.deleted.map(f => `- ~~${f}~~ (deleted)`).join("\n")
165
197
  : "(none)";
166
198
  const lastRun = state.manifest?.runs.at(-1);
167
199
  return applyTemplate("sync.md", collected, state, {
@@ -180,8 +212,12 @@ export function buildBootstrapSync() {
180
212
  return `<!-- stack-sync: self-refreshing sync command. Last generated: ${new Date().toISOString().split("T")[0]} -->
181
213
 
182
214
  > **Step 1 — always do this first:** Run \`npx claude-setup sync\` to refresh this file with the latest diff.
183
- > - If it outputs "No changes since…" → respond "Stack already in sync." and STOP.
184
- > - Otherwise re-read \`.claude/commands/stack-sync.md\` (Step 2), then process the diff below (Step 3).
215
+ > - 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.
216
+ > - If the output shows **changes** (any "+N added", "~N modified", or "-N deleted" with N > 0) → it means the file has been regenerated. Continue to Step 2.
217
+ > **Step 2:** Re-read \`.claude/commands/stack-sync.md\` — it now contains the actual diff with line-level changes.
218
+ > **Step 3:** Process every change listed. Update CLAUDE.md, .mcp.json, settings.json as needed. Surgical edits only.
219
+
220
+ **IMPORTANT:** The sync command creates a snapshot AND regenerates this file. The "Run /stack-sync" message in the output is for YOU — it means this file is now ready to be re-read. Do NOT tell the user to run /stack-sync again.
185
221
 
186
222
  ## Changes since last setup
187
223
 
@@ -246,73 +282,94 @@ export function buildAtomicSteps(collected, state) {
246
282
  // --- Step 2: .mcp.json ---
247
283
  {
248
284
  filename: "stack-2-mcp.md",
249
- content: header + preamble +
250
- `## Target: .mcp.json\n\n` +
251
- (state.mcpJson.exists
252
- ? `### Current content — MERGE ONLY, never remove existing entries:\n${vars.MCP_JSON_CONTENT}\n\n`
253
- : `Does not exist.\n\n`) +
254
- `### When to create/update\n` +
255
- `Add an MCP server if you find ANY of these signals in /stack-0-context:\n` +
256
- `- Import statement referencing an external service (e.g., pg, mysql2, mongoose, redis, stripe)\n` +
257
- `- docker-compose service (database, cache, queue, message broker)\n` +
258
- `- Env var name in .env.example matching a known service pattern (DATABASE_URL, REDIS_URL, STRIPE_KEY, etc.)\n` +
259
- `- Explicit dependency on an MCP-compatible package\n` +
260
- `- User mentioned external services during init questions\n\n` +
261
- `If ANY evidence is found, create .mcp.json with the corresponding servers.\n` +
262
- `No evidence = no server. Do not invent services.\n\n` +
263
- `### Verified MCP package names ONLY use these\n` +
264
- `\`\`\`\n` +
265
- Object.entries(VERIFIED_MCP_PACKAGES).map(([k, v]) => `${k.padEnd(12)} ${v}`).join("\n") +
266
- `\n\`\`\`\n` +
267
- `If the service is not in this list, print:\n` +
268
- `\`⚠️ UNKNOWN PACKAGE — [service] MCP server not added: package name unverified. Find it at https://github.com/modelcontextprotocol/servers\`\n` +
269
- `Do not add a placeholder. Do not guess.\n\n` +
270
- `### OS-correct format (detected: ${os})\n` +
271
- `**Preferred: use CLI to add (writes to .mcp.json automatically):**\n` +
272
- (os === "Windows"
273
- ? `\`\`\`\nclaude mcp add --scope project --transport stdio <name> -- cmd /c npx -y <package>\n\`\`\`\n`
274
- : `\`\`\`\nclaude mcp add --scope project --transport stdio <name> -- npx -y <package>\n\`\`\`\n`) +
275
- `**Or write .mcp.json directly:**\n` +
276
- (os === "Windows"
277
- ? `Use: \`{ "command": "cmd", "args": ["/c", "npx", "-y", "<package>"] }\`\n`
278
- : `Use: \`{ "command": "npx", "args": ["-y", "<package>"] }\`\n`) +
279
- `Always include \`-y\` in npx args to prevent install hangs.\n` +
280
- `\n### Connection strings NEVER hardcode\n` +
281
- `Wrong: \`"args": [..., "postgresql://localhost:5432/db"]\`\n` +
282
- `Right: \`"env": { "DATABASE_URL": "\${DATABASE_URL}" }\`\n` +
283
- `All credentials and connection strings must go in \`env\` using \`\${VARNAME}\` syntax.\n` +
284
- `After adding any server with env vars, flag them:\n` +
285
- `\`⚠️ Add DATABASE_URL to .env.example and populate before starting Claude Code\`\n\n` +
286
- `\n### Rules\n` +
287
- `- All env var refs use \`\${VARNAME}\` syntax — never literal values\n` +
288
- `- Produce valid JSON only\n` +
289
- `- If creating: document every new env var in .env.example\n\n` +
290
- `### Channels (Telegram, Discord) — special MCP servers\n` +
291
- `Channels are MCP servers that push events INTO a session. They require:\n` +
292
- `- Claude Code v2.1.80+\n` +
293
- `- claude.ai login (not API key / Console)\n` +
294
- `- Bun runtime installed\n` +
295
- `- \`--channels\` flag at EVERY session launch\n\n` +
296
- `Verified channel plugins:\n` +
297
- `\`\`\`\n` +
298
- `Telegram plugin:telegram@claude-plugins-official\n` +
299
- `Discord → plugin:discord@claude-plugins-official\n` +
300
- `\`\`\`\n\n` +
301
- `If adding a channel-type server, bot tokens must NEVER be hardcoded:\n` +
302
- (os === "Windows"
303
- ? `\`{ "command": "cmd", "args": ["/c", "bun", "run", "\${CLAUDE_PLUGIN_ROOT}/servers/telegram"], "env": { "TELEGRAM_BOT_TOKEN": "\${TELEGRAM_BOT_TOKEN}" } }\`\n`
304
- : `\`{ "command": "bun", "args": ["run", "\${CLAUDE_PLUGIN_ROOT}/servers/telegram"], "env": { "TELEGRAM_BOT_TOKEN": "\${TELEGRAM_BOT_TOKEN}" } }\`\n`) +
305
- `After adding, flag: \`⚠️ CHANNEL ACTIVATION REQUIRED — launch with: claude --channels plugin:telegram@claude-plugins-official\`\n\n` +
306
- `### Self-correction fallback\n` +
307
- `If MCP configuration fails or produces warnings:\n` +
308
- `1. Read the official MCP documentation: https://modelcontextprotocol.io/introduction\n` +
309
- `2. Verify the package name against the MCP servers registry: https://github.com/modelcontextprotocol/servers\n` +
310
- `3. Check the server's README for required env vars and correct args format\n` +
311
- `4. Re-run \`npx claude-setup doctor\` to validate the fix\n` +
312
- `Do NOT leave broken MCP configuration in place — either fix it or remove the entry.\n\n` +
313
- `### Output\n` +
314
- `Created/Updated: .mcp.json [what server and evidence source]\n` +
315
- `Skipped: .mcp.json — checked [files], found [nothing], no action\n`,
285
+ content: (() => {
286
+ const serviceDiscovery = buildServiceDiscoveryInstructions(process.cwd());
287
+ return header + preamble +
288
+ `## Target: .mcp.json\n\n` +
289
+ (state.mcpJson.exists
290
+ ? `### Current content — MERGE ONLY, never remove existing entries:\n${vars.MCP_JSON_CONTENT}\n\n`
291
+ : `Does not exist.\n\n`) +
292
+ `### When to create/update\n` +
293
+ `Add an MCP server if you find ANY of these signals in /stack-0-context:\n` +
294
+ `- Import statement referencing an external service (e.g., pg, mysql2, mongoose, redis, stripe)\n` +
295
+ `- docker-compose service (database, cache, queue, message broker)\n` +
296
+ `- Env var name in .env.example matching a known service pattern (DATABASE_URL, REDIS_URL, STRIPE_KEY, etc.)\n` +
297
+ `- Explicit dependency on an MCP-compatible package\n` +
298
+ `- User mentioned external services during init questions\n\n` +
299
+ `If ANY evidence is found, create .mcp.json with the corresponding servers.\n` +
300
+ `No evidence = no server. Do not invent services.\n\n` +
301
+ (serviceDiscovery ? serviceDiscovery + `\n` : ``) +
302
+ `### Verified MCP package names — ONLY use these\n` +
303
+ `\`\`\`\n` +
304
+ Object.entries(VERIFIED_MCP_PACKAGES).map(([k, v]) => `${k.padEnd(12)} ${v}`).join("\n") +
305
+ `\n\`\`\`\n` +
306
+ `If the service is not in this list, print:\n` +
307
+ `\`⚠️ UNKNOWN PACKAGE — [service] MCP server not added: package name unverified. Find it at https://github.com/modelcontextprotocol/servers\`\n` +
308
+ `Do not add a placeholder. Do not guess.\n\n` +
309
+ `### OS-correct format (detected: ${os}${os === "WSL" ? " uses Unix-style commands, services reachable on localhost" : ""})\n` +
310
+ `**Preferred: use CLI to add (writes to .mcp.json automatically):**\n` +
311
+ (os === "Windows"
312
+ ? `\`\`\`\nclaude mcp add --scope project --transport stdio <name> -- cmd /c npx -y <package>\n\`\`\`\n`
313
+ : `\`\`\`\nclaude mcp add --scope project --transport stdio <name> -- npx -y <package>\n\`\`\`\n`) +
314
+ `**Or write .mcp.json directly:**\n` +
315
+ (os === "Windows"
316
+ ? `Use: \`{ "command": "cmd", "args": ["/c", "npx", "-y", "<package>"] }\`\n`
317
+ : `Use: \`{ "command": "npx", "args": ["-y", "<package>"] }\`\n`) +
318
+ `Always include \`-y\` in npx args to prevent install hangs.\n` +
319
+ (os === "WSL" ? `Note: WSL uses Unix-style npx do NOT use \`cmd /c\` wrapper.\n` : ``) +
320
+ (os === "macOS" ? `Note: On macOS, Homebrew services run on localhost by default. Check with \`brew services list\`.\n` : ``) +
321
+ `\n` +
322
+ `### Connection strings — smart auto-configuration\n` +
323
+ `For each MCP server that needs a connection string:\n` +
324
+ `1. **Check environment first:** If \`\${VARNAME}\` is set in the user's environment, use \`"env": { "VAR": "\${VAR}" }\`\n` +
325
+ `2. **Detect local service:** Run the OS-appropriate check command to see if the service is installed locally\n` +
326
+ (os === "Windows"
327
+ ? ` - PostgreSQL: \`where psql 2>nul\`\n - MongoDB: \`where mongosh 2>nul\`\n - Redis: \`where redis-cli 2>nul\`\n - MySQL: \`where mysql 2>nul\`\n`
328
+ : os === "macOS"
329
+ ? ` - PostgreSQL: \`command -v psql || brew list postgresql 2>/dev/null\`\n - MongoDB: \`command -v mongosh || brew list mongodb-community 2>/dev/null\`\n - Redis: \`command -v redis-cli || brew list redis 2>/dev/null\`\n - MySQL: \`command -v mysql || brew list mysql 2>/dev/null\`\n`
330
+ : ` - PostgreSQL: \`command -v psql\`\n - MongoDB: \`command -v mongosh\`\n - Redis: \`command -v redis-cli\`\n - MySQL: \`command -v mysql\`\n`) +
331
+ `3. **If local service found and env var NOT set:** Use the well-known default URL directly in the env block:\n` +
332
+ ` - PostgreSQL: \`postgresql://localhost:5432/postgres\`\n` +
333
+ ` - MongoDB: \`mongodb://localhost:27017\`\n` +
334
+ ` - Redis: \`redis://localhost:6379\`\n` +
335
+ ` - MySQL: \`mysql://root@localhost:3306\`\n` +
336
+ ` AND document the var in .env.example with the default value\n` +
337
+ `4. **If neither env var nor local service found:** Use \`\${VARNAME}\` syntax and flag:\n` +
338
+ ` \`⚠️ Set VARNAME in your environment or .env file before starting Claude Code\`\n\n` +
339
+ `**NEVER hardcode credentials.** Default localhost URLs are acceptable for dev environments.\n` +
340
+ `After adding any server with env vars, always document them in .env.example.\n\n` +
341
+ `### Rules\n` +
342
+ `- Produce valid JSON only\n` +
343
+ `- If creating: document every new env var in .env.example\n` +
344
+ `- OS format must match detected OS: ${os}\n\n` +
345
+ `### Channels (Telegram, Discord) special MCP servers\n` +
346
+ `Channels are MCP servers that push events INTO a session. They require:\n` +
347
+ `- Claude Code v2.1.80+\n` +
348
+ `- claude.ai login (not API key / Console)\n` +
349
+ `- Bun runtime installed\n` +
350
+ `- \`--channels\` flag at EVERY session launch\n\n` +
351
+ `Verified channel plugins:\n` +
352
+ `\`\`\`\n` +
353
+ `Telegram → plugin:telegram@claude-plugins-official\n` +
354
+ `Discord → plugin:discord@claude-plugins-official\n` +
355
+ `\`\`\`\n\n` +
356
+ `If adding a channel-type server, bot tokens must NEVER be hardcoded:\n` +
357
+ (os === "Windows"
358
+ ? `\`{ "command": "cmd", "args": ["/c", "bun", "run", "\${CLAUDE_PLUGIN_ROOT}/servers/telegram"], "env": { "TELEGRAM_BOT_TOKEN": "\${TELEGRAM_BOT_TOKEN}" } }\`\n`
359
+ : `\`{ "command": "bun", "args": ["run", "\${CLAUDE_PLUGIN_ROOT}/servers/telegram"], "env": { "TELEGRAM_BOT_TOKEN": "\${TELEGRAM_BOT_TOKEN}" } }\`\n`) +
360
+ (os === "WSL" ? `(WSL note: Bun must be installed inside WSL, not the Windows-side installation.)\n` : ``) +
361
+ `After adding, flag: \`⚠️ CHANNEL ACTIVATION REQUIRED — launch with: claude --channels plugin:telegram@claude-plugins-official\`\n\n` +
362
+ `### Self-correction fallback\n` +
363
+ `If MCP configuration fails or produces warnings:\n` +
364
+ `1. Read the official MCP documentation: https://modelcontextprotocol.io/introduction\n` +
365
+ `2. Verify the package name against the MCP servers registry: https://github.com/modelcontextprotocol/servers\n` +
366
+ `3. Check the server's README for required env vars and correct args format\n` +
367
+ `4. Re-run \`npx claude-setup doctor\` to validate the fix\n` +
368
+ `Do NOT leave broken MCP configuration in place — either fix it or remove the entry.\n\n` +
369
+ `### Output\n` +
370
+ `Created/Updated: ✅ .mcp.json — [what server and evidence source]\n` +
371
+ `Skipped: ⏭ .mcp.json — checked [files], found [nothing], no action\n`;
372
+ })(),
316
373
  },
317
374
  // --- Step 3: .claude/settings.json ---
318
375
  {
@@ -21,7 +21,14 @@ function installBootstrapCommands(dir) {
21
21
  > **Step 1:** Ask the user: "What do you want to add to your Claude Code setup?"
22
22
  > **Step 2:** After the user responds, run: \`npx claude-setup add "<their exact answer>"\`
23
23
  > **Step 3:** Re-read this file (\`.claude/commands/stack-add.md\`) — it was just regenerated with full marketplace instructions.
24
- > **Step 4:** Follow the instructions in the updated file.
24
+ > **Step 4:** Follow the marketplace install instructions in the updated file. Do NOT ask clarifying questions — go straight to searching and installing.
25
+
26
+ **IMPORTANT — marketplace-first approach:**
27
+ - After Step 3, the regenerated file contains a 4-step marketplace search flow. Follow it AUTOMATICALLY.
28
+ - Do NOT ask the user "do you mean X or Y?" — search the marketplace for ALL interpretations.
29
+ - Do NOT suggest manual installation steps — execute the curl/install commands yourself.
30
+ - The goal is ZERO friction: user says what they want → you search → you install → done.
31
+ - Only ask the user if the marketplace returns multiple equally-valid matches and you need to pick one.
25
32
  `,
26
33
  "stack-status.md": `<!-- stack-status: show project state -->
27
34
 
@@ -46,6 +46,25 @@ function truncate(content, maxChars) {
46
46
  return content;
47
47
  return content.slice(0, maxChars) + "\n[... truncated for sync diff]";
48
48
  }
49
+ /**
50
+ * Compute a simple line-level diff between two strings.
51
+ * Returns added lines (green) and removed lines (red).
52
+ */
53
+ function computeLineDiff(oldContent, newContent, maxLines = 20) {
54
+ const oldLines = oldContent.split("\n");
55
+ const newLines = newContent.split("\n");
56
+ const oldSet = new Set(oldLines);
57
+ const newSet = new Set(newLines);
58
+ const added = newLines.filter(l => !oldSet.has(l) && l.trim() !== "");
59
+ const removed = oldLines.filter(l => !newSet.has(l) && l.trim() !== "");
60
+ const totalChanges = added.length + removed.length;
61
+ const summary = `+${added.length} lines, -${removed.length} lines`;
62
+ return {
63
+ added: added.slice(0, maxLines),
64
+ removed: removed.slice(0, maxLines),
65
+ summary,
66
+ };
67
+ }
49
68
  /**
50
69
  * Legacy diff — compares manifest hashes against collected files.
51
70
  * Only used when no snapshot data is available (e.g., old projects).
@@ -96,6 +115,7 @@ function computeLegacyDiff(snapshot, collected, cwd) {
96
115
  /**
97
116
  * Full-scan diff — compares every file on disk against a reference snapshot.
98
117
  * This is the authoritative diff: catches ALL file changes, no sampling.
118
+ * Now includes line-level diffs for modified files.
99
119
  */
100
120
  function computeFullDiff(currentFiles, referenceFiles) {
101
121
  const added = [];
@@ -111,7 +131,13 @@ function computeFullDiff(currentFiles, referenceFiles) {
111
131
  const currentHash = sha256(f.content);
112
132
  const refHash = sha256(referenceFiles[f.path]);
113
133
  if (currentHash !== refHash) {
114
- changed.push({ path: f.path, current: truncate(f.content, 2000) });
134
+ const lineDiff = computeLineDiff(referenceFiles[f.path], f.content);
135
+ changed.push({
136
+ path: f.path,
137
+ current: truncate(f.content, 2000),
138
+ previous: truncate(referenceFiles[f.path], 2000),
139
+ lineDiff,
140
+ });
115
141
  }
116
142
  }
117
143
  }
@@ -198,18 +224,22 @@ export async function runSync(opts = {}) {
198
224
  console.log(c.bold("[DRY RUN] Changes detected:\n"));
199
225
  if (diff.added.length) {
200
226
  console.log(c.green(` +${diff.added.length} added`));
201
- for (const f of diff.added)
202
- console.log(` ${f.path}`);
227
+ for (const f of diff.added) {
228
+ const lineCount = f.content.split("\n").length;
229
+ console.log(c.green(` + ${f.path}`) + c.dim(` (${lineCount} lines)`));
230
+ }
203
231
  }
204
232
  if (diff.changed.length) {
205
233
  console.log(c.yellow(` ~${diff.changed.length} modified`));
206
- for (const f of diff.changed)
207
- console.log(` ${f.path}`);
234
+ for (const f of diff.changed) {
235
+ const diffInfo = f.lineDiff ? ` (${f.lineDiff.summary})` : "";
236
+ console.log(c.yellow(` ~ ${f.path}`) + c.dim(diffInfo));
237
+ }
208
238
  }
209
239
  if (diff.deleted.length) {
210
240
  console.log(c.red(` -${diff.deleted.length} deleted`));
211
241
  for (const f of diff.deleted)
212
- console.log(` ${f}`);
242
+ console.log(c.red(` - ${f}`));
213
243
  }
214
244
  console.log(`\n Would write: .claude/commands/stack-sync.md (~${tokens.toLocaleString()} tokens)`);
215
245
  section("Token cost estimate");
@@ -225,10 +255,44 @@ export async function runSync(opts = {}) {
225
255
  createSnapshot(cwd, "sync", currentFiles, {
226
256
  summary: `+${diff.added.length} added, ~${diff.changed.length} modified, -${diff.deleted.length} deleted`,
227
257
  });
228
- console.log(`
229
- Changes since ${c.dim(lastRun.at)}:
230
- ${c.green(`+${diff.added.length}`)} added ${c.yellow(`~${diff.changed.length}`)} modified ${c.red(`-${diff.deleted.length}`)} deleted
231
-
232
- ${c.green("✅")} Run ${c.cyan("/stack-sync")} in Claude Code to apply.
233
- `);
258
+ // --- Detailed diff output ---
259
+ console.log(`\nChanges since ${c.dim(lastRun.at)}:`);
260
+ console.log(` ${c.green(`+${diff.added.length}`)} added ${c.yellow(`~${diff.changed.length}`)} modified ${c.red(`-${diff.deleted.length}`)} deleted\n`);
261
+ if (diff.added.length > 0) {
262
+ console.log(c.green(c.bold(" Added files:")));
263
+ for (const f of diff.added) {
264
+ const lineCount = f.content.split("\n").length;
265
+ console.log(c.green(` + ${f.path}`) + c.dim(` (${lineCount} lines)`));
266
+ }
267
+ console.log("");
268
+ }
269
+ if (diff.changed.length > 0) {
270
+ console.log(c.yellow(c.bold(" Modified files:")));
271
+ for (const f of diff.changed) {
272
+ const diffInfo = f.lineDiff ? ` (${f.lineDiff.summary})` : "";
273
+ console.log(c.yellow(` ~ ${f.path}`) + c.dim(diffInfo));
274
+ if (f.lineDiff) {
275
+ for (const line of f.lineDiff.removed.slice(0, 3)) {
276
+ console.log(c.red(` - ${line.trim().slice(0, 80)}`));
277
+ }
278
+ for (const line of f.lineDiff.added.slice(0, 3)) {
279
+ console.log(c.green(` + ${line.trim().slice(0, 80)}`));
280
+ }
281
+ const totalShown = Math.min(f.lineDiff.removed.length, 3) + Math.min(f.lineDiff.added.length, 3);
282
+ const totalChanges = f.lineDiff.removed.length + f.lineDiff.added.length;
283
+ if (totalChanges > totalShown) {
284
+ console.log(c.dim(` ... +${totalChanges - totalShown} more changes`));
285
+ }
286
+ }
287
+ }
288
+ console.log("");
289
+ }
290
+ if (diff.deleted.length > 0) {
291
+ console.log(c.red(c.bold(" Deleted files:")));
292
+ for (const f of diff.deleted) {
293
+ console.log(c.red(` - ${f}`));
294
+ }
295
+ console.log("");
296
+ }
297
+ console.log(`${c.green("✅")} Run ${c.cyan("/stack-sync")} in Claude Code to apply.\n`);
234
298
  }
@@ -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}":`);
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-setup",
3
- "version": "1.1.7",
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