claude-setup 1.1.7 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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.9",
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,10 @@
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
+ **A fetch failure is NOT a stop — it is a routing signal to the next catalog.**
8
+
5
9
  ## Project context
6
10
  {{PROJECT_CONTEXT}}
7
11
 
@@ -34,13 +38,55 @@ Skills: {{SKILLS_LIST}} | Commands: {{COMMANDS_LIST}}
34
38
 
35
39
  Parse the user's request and take ALL applicable actions:
36
40
 
37
- ### 1. MCP servers
41
+ ### 1. Agents (if request is about agents/orchestration/subagents)
42
+ If the marketplace pipeline installed an agent file to `.claude/agents/`:
43
+ - Verify the file has YAML frontmatter (name, description, tools, model) and a body
44
+ - Document it in CLAUDE.md under a **separate agents section** (not mixed with skills)
45
+ - Agent entry format in CLAUDE.md:
46
+ ```
47
+ ## Agents
48
+ - **agent-name** — what it orchestrates, when to invoke it
49
+ ```
50
+
51
+ Agent files live in `.claude/agents/<name>.md` — NOT in `.claude/skills/`.
52
+ Agents and skills are architecturally different and must never be mixed.
53
+
54
+ ### 2. MCP servers
38
55
  If the request mentions an external service (database, API, browser, etc.):
39
56
  - Check the verified MCP package list below
40
57
  - If found: add to `.mcp.json` with OS-correct format (detected: {{DETECTED_OS}})
41
- - Use `${VARNAME}` syntax for all credentials — NEVER hardcode
58
+ - **Smart connection strings** follow this order:
59
+ 1. Check if the env var is already set in the environment
60
+ 2. If not set, detect if the service is installed locally (run check command below)
61
+ 3. If local service found: use default localhost URL directly in env block
62
+ 4. If nothing found: use `${VARNAME}` syntax and flag the missing var
42
63
  - Document new env vars in `.env.example`
43
64
 
65
+ **Service detection commands ({{DETECTED_OS}}):**
66
+ {{#if IS_WINDOWS}}
67
+ - PostgreSQL: `where psql 2>nul` → default: `postgresql://localhost:5432/postgres`
68
+ - MongoDB: `where mongosh 2>nul` → default: `mongodb://localhost:27017`
69
+ - Redis: `where redis-cli 2>nul` → default: `redis://localhost:6379`
70
+ - MySQL: `where mysql 2>nul` → default: `mysql://root@localhost:3306`
71
+ {{else}}
72
+ {{#if IS_MACOS}}
73
+ - PostgreSQL: `command -v psql || brew list postgresql 2>/dev/null` → default: `postgresql://localhost:5432/postgres`
74
+ - MongoDB: `command -v mongosh || brew list mongodb-community 2>/dev/null` → default: `mongodb://localhost:27017`
75
+ - Redis: `command -v redis-cli || brew list redis 2>/dev/null` → default: `redis://localhost:6379`
76
+ - MySQL: `command -v mysql || brew list mysql 2>/dev/null` → default: `mysql://root@localhost:3306`
77
+ {{else}}
78
+ - PostgreSQL: `command -v psql` → default: `postgresql://localhost:5432/postgres`
79
+ - MongoDB: `command -v mongosh` → default: `mongodb://localhost:27017`
80
+ - Redis: `command -v redis-cli` → default: `redis://localhost:6379`
81
+ - MySQL: `command -v mysql` → default: `mysql://root@localhost:3306`
82
+ {{/if}}
83
+ {{#if IS_WSL}}
84
+ Note: WSL can access Windows-host services on localhost. If the service runs on the Windows side, it is reachable at `localhost` from WSL.
85
+ {{/if}}
86
+ {{/if}}
87
+
88
+ 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.
89
+
44
90
  Verified MCP packages — ONLY use these for MCP servers:
45
91
  ```
46
92
  playwright → @playwright/mcp@latest
@@ -93,8 +139,8 @@ MCP format — create new .mcp.json:
93
139
  ```
94
140
  {{/if}}
95
141
 
96
- ### 2. Skills
97
- If the request mentions skills or capabilities:
142
+ ### 3. Skills
143
+ If the request mentions skills or capabilities (NOT agents):
98
144
  - Create `.claude/skills/<name>/SKILL.md` with proper frontmatter
99
145
  - Use `description:` so Claude knows when to load the skill
100
146
  - Search the marketplace for matching pre-built skills (see above)
@@ -109,7 +155,7 @@ description: What this skill does
109
155
  Instructions...
110
156
  ```
111
157
 
112
- ### 3. Hooks
158
+ ### 4. Hooks
113
159
  If the request implies automated actions (formatting, building, notifications):
114
160
  - Add to `.claude/settings.json` using the CORRECT hooks format
115
161
  - Verify the tool exists before adding a hook for it
@@ -133,13 +179,11 @@ Correct hooks format:
133
179
  }
134
180
  ```
135
181
 
136
- ### 4. Plugins
137
- If the request matches a marketplace category or SaaS platform:
138
- - Suggest the relevant plugin with install commands
139
- - Show the user exactly how to install it
140
-
141
182
  ### 5. CLAUDE.md
142
183
  Document any new capabilities, services, or patterns added.
184
+ - Skills go under `## Skills` section
185
+ - Agents go under `## Agents` section (separate — never mixed)
186
+ - MCP servers go under `## MCP Servers` section
143
187
 
144
188
  ## Rules
145
189
  - Read current content above before writing. Merge/append only.
@@ -148,6 +192,9 @@ Document any new capabilities, services, or patterns added.
148
192
  - All env var refs use `${VARNAME}` syntax. Document new vars in .env.example.
149
193
  - **NEVER write a "model" key into settings.json**
150
194
  - Produce valid JSON only.
195
+ - Agents install to `.claude/agents/` — Skills install to `.claude/skills/`
196
+ - Every installed file must contain real, functional content (Rule 7).
197
+ - A fetch failure is a routing signal, not a stop condition (Rule 6).
151
198
 
152
199
  ## Output — one line per file
153
200
  Updated: ✅ [path] — [what and why]
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