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 +88 -107
- package/dist/builder.d.ts +6 -0
- package/dist/builder.js +133 -76
- package/dist/commands/init.js +8 -1
- package/dist/commands/sync.js +76 -12
- package/dist/marketplace.js +3 -2
- package/dist/os.d.ts +33 -4
- package/dist/os.js +238 -3
- package/package.json +1 -1
- package/templates/add.md +33 -1
- package/templates/sync.md +6 -2
package/README.md
CHANGED
|
@@ -1,161 +1,142 @@
|
|
|
1
1
|
# claude-setup
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Your project already has the answers — `claude-setup` reads them and configures Claude Code so you don't have to.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
One command. No manual config. Works on **Windows, macOS, Linux, and WSL**.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Get started
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npx claude-setup
|
|
10
|
+
npx claude-setup
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Then open Claude Code and run
|
|
13
|
+
Pick `1` (init). Then open Claude Code and run:
|
|
14
14
|
|
|
15
|
-
|
|
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
|
-
|
|
19
|
+
That's it. Claude Code now knows your stack, your services, your conventions.
|
|
42
20
|
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
|
50
|
-
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
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
|
-
|
|
33
|
+
Every line comes from evidence in your project files. No guessing.
|
|
59
34
|
|
|
60
|
-
|
|
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
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
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 URL — no broken `${VARNAME}` that fails silently
|
|
42
|
+
- Formats commands for your OS (`cmd /c npx` on Windows, `npx` everywhere else)
|
|
72
43
|
|
|
73
|
-
##
|
|
44
|
+
## After init
|
|
74
45
|
|
|
75
|
-
|
|
46
|
+
These slash commands work inside Claude Code:
|
|
76
47
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
-
|
|
83
|
-
npx claude-setup init --template my-template.claude-template.json
|
|
57
|
+
### `/stack-add` searches the marketplace for you
|
|
84
58
|
|
|
85
|
-
|
|
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
|
-
|
|
90
|
-
-
|
|
91
|
-
|
|
92
|
-
|
|
61
|
+
```
|
|
62
|
+
/stack-add
|
|
63
|
+
> "E2E testing and Stripe integration"
|
|
64
|
+
```
|
|
93
65
|
|
|
94
|
-
|
|
66
|
+
### `/stack-sync` shows what changed
|
|
95
67
|
|
|
96
|
-
Every
|
|
68
|
+
Every sync creates a snapshot and shows a color-coded diff:
|
|
97
69
|
|
|
98
70
|
```
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
83
|
+
Claude Code sees the actual line-level changes and updates your setup surgically.
|
|
104
84
|
|
|
105
|
-
##
|
|
85
|
+
## Snapshots
|
|
106
86
|
|
|
107
|
-
|
|
87
|
+
Every init and sync saves a full snapshot. You can jump to any point in time:
|
|
108
88
|
|
|
109
|
-
```
|
|
110
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
99
|
+
Snapshots are never deleted. Go back, go forward, freely.
|
|
129
100
|
|
|
130
|
-
|
|
101
|
+
## All CLI commands
|
|
131
102
|
|
|
132
103
|
```bash
|
|
133
|
-
npx claude-setup
|
|
134
|
-
|
|
135
|
-
|
|
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`
|
|
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
|
-
"
|
|
147
|
-
"
|
|
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
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:
|
|
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
|
-
//
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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 => `-
|
|
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
|
|
184
|
-
> -
|
|
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:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
:
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
{
|
package/dist/commands/init.js
CHANGED
|
@@ -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
|
|
package/dist/commands/sync.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
229
|
-
|
|
230
|
-
${c.green(`+${diff.added.length}`)} added ${c.yellow(`~${diff.changed.length}`)} modified ${c.red(`-${diff.deleted.length}`)} deleted
|
|
231
|
-
|
|
232
|
-
|
|
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
|
}
|
package/dist/marketplace.js
CHANGED
|
@@ -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
|
|
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.
|
|
8
|
-
* 5.
|
|
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
|
|
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.
|
|
8
|
-
* 5.
|
|
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
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
|
-
-
|
|
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
|
|
5
|
-
> -
|
|
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
|
|