@aslomon/effectum 0.3.4 → 0.5.0
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/bin/install.js +81 -5
- package/bin/lib/cli-tools.js +371 -0
- package/bin/lib/specializations.js +11 -1
- package/bin/lib/template.js +14 -0
- package/bin/lib/tool-loader.js +243 -0
- package/bin/lib/ui.js +172 -0
- package/package.json +1 -1
- package/system/agents/data-engineer.md +268 -0
- package/system/agents/mobile-developer.md +257 -0
- package/system/templates/CLAUDE.md.tmpl +6 -0
- package/system/templates/settings.json.tmpl +11 -0
- package/system/tools/_schema.json +112 -0
- package/system/tools/foundation.json +56 -0
- package/system/tools/generic.json +20 -0
- package/system/tools/nextjs-supabase.json +56 -0
- package/system/tools/python-fastapi.json +47 -0
- package/system/tools/swift-ios.json +33 -0
package/bin/install.js
CHANGED
|
@@ -53,6 +53,9 @@ const {
|
|
|
53
53
|
askLanguage,
|
|
54
54
|
askAutonomy,
|
|
55
55
|
showRecommendation,
|
|
56
|
+
showSystemCheck,
|
|
57
|
+
showInstallPlan,
|
|
58
|
+
showAuthCheck,
|
|
56
59
|
askSetupMode,
|
|
57
60
|
askCustomize,
|
|
58
61
|
askManual,
|
|
@@ -61,6 +64,15 @@ const {
|
|
|
61
64
|
showSummary,
|
|
62
65
|
showOutro,
|
|
63
66
|
} = require("./lib/ui");
|
|
67
|
+
const {
|
|
68
|
+
checkAllTools,
|
|
69
|
+
checkSystemBasics,
|
|
70
|
+
formatToolStatus,
|
|
71
|
+
categorizeForInstall,
|
|
72
|
+
formatInstallPlan,
|
|
73
|
+
checkAllAuth,
|
|
74
|
+
formatAuthStatus,
|
|
75
|
+
} = require("./lib/cli-tools");
|
|
64
76
|
|
|
65
77
|
// ─── File helpers ─────────────────────────────────────────────────────────────
|
|
66
78
|
|
|
@@ -343,7 +355,11 @@ function installRecommendedAgents(targetDir, repoRoot, recommendedAgents) {
|
|
|
343
355
|
const agentsDest = path.join(targetDir, ".claude", "agents");
|
|
344
356
|
const steps = [];
|
|
345
357
|
|
|
346
|
-
if (
|
|
358
|
+
if (
|
|
359
|
+
!fs.existsSync(agentsSrc) ||
|
|
360
|
+
!recommendedAgents ||
|
|
361
|
+
recommendedAgents.length === 0
|
|
362
|
+
) {
|
|
347
363
|
return steps;
|
|
348
364
|
}
|
|
349
365
|
|
|
@@ -592,6 +608,45 @@ Options:
|
|
|
592
608
|
process.exit(0);
|
|
593
609
|
}
|
|
594
610
|
|
|
611
|
+
// System basics check (report only in non-interactive mode)
|
|
612
|
+
const sysCheck = checkSystemBasics();
|
|
613
|
+
if (sysCheck.missing.length > 0) {
|
|
614
|
+
console.log("\n System Basics:");
|
|
615
|
+
console.log(formatToolStatus(sysCheck.tools));
|
|
616
|
+
console.log(
|
|
617
|
+
`\n ${sysCheck.missing.length} system tool(s) not installed. Run installer interactively to install.`,
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// CLI tool check (report only, no install in non-interactive mode)
|
|
622
|
+
const toolCheck = checkAllTools(config.stack);
|
|
623
|
+
if (toolCheck.missing.length > 0) {
|
|
624
|
+
const plan = categorizeForInstall(toolCheck.tools);
|
|
625
|
+
console.log("\n Stack Tools:");
|
|
626
|
+
console.log(formatToolStatus(toolCheck.tools));
|
|
627
|
+
if (plan.autoInstall.length > 0 || plan.manual.length > 0) {
|
|
628
|
+
console.log("\n Installation Plan:");
|
|
629
|
+
console.log(formatInstallPlan(plan));
|
|
630
|
+
}
|
|
631
|
+
console.log(
|
|
632
|
+
`\n ${toolCheck.missing.length} tool(s) not installed. Run installer interactively to install.`,
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// Auth check (report only in non-interactive mode)
|
|
637
|
+
const authResults = checkAllAuth(toolCheck.tools);
|
|
638
|
+
const unauthenticated = authResults.filter((t) => !t.authenticated);
|
|
639
|
+
if (unauthenticated.length > 0) {
|
|
640
|
+
console.log("\n Auth Status:");
|
|
641
|
+
console.log(formatAuthStatus(authResults));
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Store tool status in config
|
|
645
|
+
config.detectedTools = [...sysCheck.tools, ...toolCheck.tools].map((t) => ({
|
|
646
|
+
key: t.key,
|
|
647
|
+
installed: t.installed,
|
|
648
|
+
}));
|
|
649
|
+
|
|
595
650
|
// Install base files
|
|
596
651
|
installBaseFiles(targetDir, repoRoot, isGlobal);
|
|
597
652
|
|
|
@@ -609,9 +664,13 @@ Options:
|
|
|
609
664
|
}
|
|
610
665
|
|
|
611
666
|
// MCP servers — always install recommended MCPs (or explicit --with-mcp)
|
|
612
|
-
const mcpKeys =
|
|
667
|
+
const mcpKeys =
|
|
668
|
+
config.mcpServers ||
|
|
669
|
+
(config.recommended ? config.recommended.mcps : []) ||
|
|
670
|
+
[];
|
|
613
671
|
if (mcpKeys.length > 0 || args.withMcp) {
|
|
614
|
-
const keysToInstall =
|
|
672
|
+
const keysToInstall =
|
|
673
|
+
mcpKeys.length > 0 ? mcpKeys : MCP_SERVERS.map((s) => s.key);
|
|
615
674
|
const mcpResults = installMcpServers(keysToInstall);
|
|
616
675
|
const settingsPath = isGlobal
|
|
617
676
|
? path.join(homeClaudeDir, "settings.json")
|
|
@@ -673,6 +732,9 @@ Options:
|
|
|
673
732
|
process.exit(0);
|
|
674
733
|
}
|
|
675
734
|
|
|
735
|
+
// ── Phase 1: System Basics Check ──────────────────────────────────────────
|
|
736
|
+
await showSystemCheck();
|
|
737
|
+
|
|
676
738
|
// ── Step 2: Project Basics ────────────────────────────────────────────────
|
|
677
739
|
const projectName = await askProjectName(detected.projectName);
|
|
678
740
|
const stack = await askStack(detected.stack);
|
|
@@ -700,6 +762,9 @@ Options:
|
|
|
700
762
|
|
|
701
763
|
showRecommendation(rec);
|
|
702
764
|
|
|
765
|
+
// ── Phase 3: Consolidated Tool Plan ───────────────────────────────────────
|
|
766
|
+
const cliToolResult = await showInstallPlan(stack, installTargetDir);
|
|
767
|
+
|
|
703
768
|
// ── Step 8: Decision ──────────────────────────────────────────────────────
|
|
704
769
|
const setupMode = await askSetupMode();
|
|
705
770
|
|
|
@@ -733,6 +798,10 @@ Options:
|
|
|
733
798
|
packageManager: detected.packageManager,
|
|
734
799
|
formatter: formatterDef.name,
|
|
735
800
|
mcpServers: finalSetup.mcps,
|
|
801
|
+
detectedTools: cliToolResult.tools.map((t) => ({
|
|
802
|
+
key: t.key,
|
|
803
|
+
installed: t.installed,
|
|
804
|
+
})),
|
|
736
805
|
playwrightBrowsers: wantPlaywright,
|
|
737
806
|
installScope: "local",
|
|
738
807
|
recommended: {
|
|
@@ -804,7 +873,11 @@ Options:
|
|
|
804
873
|
if (recAgents.length > 0) {
|
|
805
874
|
const sAgents = p.spinner();
|
|
806
875
|
sAgents.start("Installing agent specializations...");
|
|
807
|
-
const agentSteps = installRecommendedAgents(
|
|
876
|
+
const agentSteps = installRecommendedAgents(
|
|
877
|
+
installTargetDir,
|
|
878
|
+
repoRoot,
|
|
879
|
+
recAgents,
|
|
880
|
+
);
|
|
808
881
|
const agentCount = agentSteps.filter((s) => s.status === "created").length;
|
|
809
882
|
sAgents.stop(`${agentCount} agent specializations installed`);
|
|
810
883
|
configSteps.push(...agentSteps);
|
|
@@ -838,7 +911,10 @@ Options:
|
|
|
838
911
|
);
|
|
839
912
|
}
|
|
840
913
|
|
|
841
|
-
// 9e:
|
|
914
|
+
// 9e: Auth check
|
|
915
|
+
await showAuthCheck(cliToolResult.tools);
|
|
916
|
+
|
|
917
|
+
// 9f: Save config
|
|
842
918
|
const configPath = writeConfig(installTargetDir, config);
|
|
843
919
|
configSteps.push({ status: "created", dest: configPath });
|
|
844
920
|
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI tool detection, installation, and auth checking.
|
|
3
|
+
*
|
|
4
|
+
* Tool definitions are loaded dynamically from JSON files in system/tools/
|
|
5
|
+
* via the tool-loader module. This module provides the runtime operations:
|
|
6
|
+
* check, install, auth, and formatting.
|
|
7
|
+
*/
|
|
8
|
+
"use strict";
|
|
9
|
+
|
|
10
|
+
const { spawnSync } = require("child_process");
|
|
11
|
+
const os = require("os");
|
|
12
|
+
const { loadToolDefinitions, getSystemBasics } = require("./tool-loader");
|
|
13
|
+
|
|
14
|
+
// ─── Platform ────────────────────────────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the platform key for install commands.
|
|
18
|
+
* @returns {"darwin"|"linux"}
|
|
19
|
+
*/
|
|
20
|
+
function getPlatform() {
|
|
21
|
+
const p = os.platform();
|
|
22
|
+
return p === "darwin" ? "darwin" : "linux";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ─── Tool check ──────────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a CLI tool is installed by looking up its binary.
|
|
29
|
+
* Uses the tool's `check` command if available, otherwise falls back to `which`.
|
|
30
|
+
* @param {object|string} toolOrBin - tool object or binary name
|
|
31
|
+
* @returns {boolean}
|
|
32
|
+
*/
|
|
33
|
+
function checkTool(toolOrBin) {
|
|
34
|
+
const bin = typeof toolOrBin === "string" ? toolOrBin : toolOrBin.bin;
|
|
35
|
+
const checkCmd =
|
|
36
|
+
typeof toolOrBin === "object" && toolOrBin.check ? toolOrBin.check : null;
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
if (checkCmd) {
|
|
40
|
+
const result = spawnSync("bash", ["-c", checkCmd], {
|
|
41
|
+
timeout: 5000,
|
|
42
|
+
stdio: "pipe",
|
|
43
|
+
encoding: "utf8",
|
|
44
|
+
});
|
|
45
|
+
return result.status === 0;
|
|
46
|
+
}
|
|
47
|
+
const result = spawnSync("which", [bin], {
|
|
48
|
+
timeout: 5000,
|
|
49
|
+
stdio: "pipe",
|
|
50
|
+
encoding: "utf8",
|
|
51
|
+
});
|
|
52
|
+
return result.status === 0 && result.stdout.trim().length > 0;
|
|
53
|
+
} catch (_) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ─── Tool retrieval ──────────────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get the relevant tools for a given stack, loaded from JSON definitions.
|
|
62
|
+
* Foundation tools are always included.
|
|
63
|
+
* @param {string} stack - stack key (e.g., "nextjs-supabase")
|
|
64
|
+
* @param {string} [targetDir] - project directory for community overrides
|
|
65
|
+
* @returns {Array<object>}
|
|
66
|
+
*/
|
|
67
|
+
function getToolsForStack(stack, targetDir) {
|
|
68
|
+
return loadToolDefinitions(stack, targetDir);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check all relevant tools for a stack and return status.
|
|
73
|
+
* @param {string} stack
|
|
74
|
+
* @param {string} [targetDir] - project directory for community overrides
|
|
75
|
+
* @returns {{ tools: Array<object>, missing: Array<object>, installed: Array<object> }}
|
|
76
|
+
*/
|
|
77
|
+
function checkAllTools(stack, targetDir) {
|
|
78
|
+
const relevant = getToolsForStack(stack, targetDir);
|
|
79
|
+
const results = relevant.map((tool) => ({
|
|
80
|
+
...tool,
|
|
81
|
+
installed: checkTool(tool),
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
tools: results,
|
|
86
|
+
missing: results.filter((t) => !t.installed),
|
|
87
|
+
installed: results.filter((t) => t.installed),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check system basics (Homebrew, Git, Node.js, Claude Code).
|
|
93
|
+
* @returns {{ tools: Array<object>, missing: Array<object>, installed: Array<object> }}
|
|
94
|
+
*/
|
|
95
|
+
function checkSystemBasics() {
|
|
96
|
+
const basics = getSystemBasics();
|
|
97
|
+
const results = basics.map((tool) => ({
|
|
98
|
+
...tool,
|
|
99
|
+
installed: checkTool(tool),
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
tools: results,
|
|
104
|
+
missing: results.filter((t) => !t.installed),
|
|
105
|
+
installed: results.filter((t) => t.installed),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ─── Tool installation ───────────────────────────────────────────────────────
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get the install command for a tool on the current platform.
|
|
113
|
+
* @param {object} tool
|
|
114
|
+
* @returns {string|null}
|
|
115
|
+
*/
|
|
116
|
+
function getInstallCommand(tool) {
|
|
117
|
+
if (!tool.install) return null;
|
|
118
|
+
const platform = getPlatform();
|
|
119
|
+
return tool.install[platform] || tool.install.all || null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Install a single tool using its platform-appropriate command.
|
|
124
|
+
* @param {object} tool
|
|
125
|
+
* @returns {{ ok: boolean, command: string|null, error?: string }}
|
|
126
|
+
*/
|
|
127
|
+
function installTool(tool) {
|
|
128
|
+
const command = getInstallCommand(tool);
|
|
129
|
+
if (!command) {
|
|
130
|
+
return {
|
|
131
|
+
ok: false,
|
|
132
|
+
command: null,
|
|
133
|
+
error: "No install command for this platform",
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const result = spawnSync("bash", ["-c", command], {
|
|
139
|
+
timeout: 120000,
|
|
140
|
+
stdio: "pipe",
|
|
141
|
+
encoding: "utf8",
|
|
142
|
+
});
|
|
143
|
+
if (result.status === 0) {
|
|
144
|
+
return { ok: true, command };
|
|
145
|
+
}
|
|
146
|
+
return { ok: false, command, error: result.stderr || "Install failed" };
|
|
147
|
+
} catch (err) {
|
|
148
|
+
return { ok: false, command, error: err.message };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Categorize tools into auto-installable and manual-only groups.
|
|
154
|
+
* @param {Array<object>} tools - tools with `installed` status
|
|
155
|
+
* @returns {{ autoInstall: Array<object>, manual: Array<object> }}
|
|
156
|
+
*/
|
|
157
|
+
function categorizeForInstall(tools) {
|
|
158
|
+
const missing = tools.filter((t) => !t.installed);
|
|
159
|
+
const platform = getPlatform();
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
autoInstall: missing.filter((t) => {
|
|
163
|
+
if (t.autoInstall === false) return false;
|
|
164
|
+
const cmd = t.install ? t.install[platform] || t.install.all : null;
|
|
165
|
+
return !!cmd;
|
|
166
|
+
}),
|
|
167
|
+
manual: missing.filter((t) => {
|
|
168
|
+
if (t.autoInstall === false) return true;
|
|
169
|
+
const cmd = t.install ? t.install[platform] || t.install.all : null;
|
|
170
|
+
return !cmd;
|
|
171
|
+
}),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ─── Auth checking ───────────────────────────────────────────────────────────
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Check if a tool is authenticated.
|
|
179
|
+
* Supports both legacy format (auth as string) and new format (auth as object).
|
|
180
|
+
* @param {object} tool
|
|
181
|
+
* @returns {{ authenticated: boolean, needsAuth: boolean }}
|
|
182
|
+
*/
|
|
183
|
+
function checkAuth(tool) {
|
|
184
|
+
const authCheck =
|
|
185
|
+
typeof tool.auth === "object" && tool.auth !== null
|
|
186
|
+
? tool.auth.check
|
|
187
|
+
: typeof tool.auth === "string"
|
|
188
|
+
? tool.auth
|
|
189
|
+
: null;
|
|
190
|
+
|
|
191
|
+
if (!authCheck) {
|
|
192
|
+
return { authenticated: true, needsAuth: false };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
try {
|
|
196
|
+
const result = spawnSync("bash", ["-c", authCheck], {
|
|
197
|
+
timeout: 10000,
|
|
198
|
+
stdio: "pipe",
|
|
199
|
+
encoding: "utf8",
|
|
200
|
+
});
|
|
201
|
+
return {
|
|
202
|
+
authenticated: result.status === 0,
|
|
203
|
+
needsAuth: true,
|
|
204
|
+
};
|
|
205
|
+
} catch (_) {
|
|
206
|
+
return { authenticated: false, needsAuth: true };
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Get the auth setup command for a tool.
|
|
212
|
+
* @param {object} tool
|
|
213
|
+
* @returns {string|null}
|
|
214
|
+
*/
|
|
215
|
+
function getAuthSetup(tool) {
|
|
216
|
+
if (typeof tool.auth === "object" && tool.auth !== null) {
|
|
217
|
+
return tool.auth.setup || null;
|
|
218
|
+
}
|
|
219
|
+
return tool.authSetup || null;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Get the auth URL for a tool (for creating tokens).
|
|
224
|
+
* @param {object} tool
|
|
225
|
+
* @returns {string|null}
|
|
226
|
+
*/
|
|
227
|
+
function getAuthUrl(tool) {
|
|
228
|
+
if (typeof tool.auth === "object" && tool.auth !== null) {
|
|
229
|
+
return tool.auth.url || null;
|
|
230
|
+
}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Check auth status for all installed tools that need auth.
|
|
236
|
+
* @param {Array<object>} tools - tools with `installed` status
|
|
237
|
+
* @returns {Array<object>} - tools with auth status added
|
|
238
|
+
*/
|
|
239
|
+
function checkAllAuth(tools) {
|
|
240
|
+
return tools
|
|
241
|
+
.filter((t) => t.installed)
|
|
242
|
+
.map((tool) => {
|
|
243
|
+
const authResult = checkAuth(tool);
|
|
244
|
+
return {
|
|
245
|
+
...tool,
|
|
246
|
+
...authResult,
|
|
247
|
+
authSetupCmd: getAuthSetup(tool),
|
|
248
|
+
authUrl: getAuthUrl(tool),
|
|
249
|
+
};
|
|
250
|
+
})
|
|
251
|
+
.filter((t) => t.needsAuth);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ─── Formatting helpers ──────────────────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Generate a human-readable tools status summary.
|
|
258
|
+
* @param {Array<object>} tools - tool check results
|
|
259
|
+
* @returns {string}
|
|
260
|
+
*/
|
|
261
|
+
function formatToolStatus(tools) {
|
|
262
|
+
return tools
|
|
263
|
+
.map((t) => {
|
|
264
|
+
const icon = t.installed ? "\u2705" : "\u274C";
|
|
265
|
+
const name = t.displayName || t.key;
|
|
266
|
+
return ` ${icon} ${name} — ${t.why}`;
|
|
267
|
+
})
|
|
268
|
+
.join("\n");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Generate install instructions for missing tools.
|
|
273
|
+
* @param {Array<object>} missing - missing tools
|
|
274
|
+
* @returns {string}
|
|
275
|
+
*/
|
|
276
|
+
function formatInstallInstructions(missing) {
|
|
277
|
+
const platform = getPlatform();
|
|
278
|
+
return missing
|
|
279
|
+
.map((t) => {
|
|
280
|
+
const cmd = t.install
|
|
281
|
+
? t.install[platform] || t.install.all || "N/A"
|
|
282
|
+
: "N/A";
|
|
283
|
+
const name = t.displayName || t.key;
|
|
284
|
+
return ` ${name}: ${cmd}`;
|
|
285
|
+
})
|
|
286
|
+
.join("\n");
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Format a consolidated installation plan.
|
|
291
|
+
* @param {{ autoInstall: Array<object>, manual: Array<object> }} plan
|
|
292
|
+
* @returns {string}
|
|
293
|
+
*/
|
|
294
|
+
function formatInstallPlan(plan) {
|
|
295
|
+
const lines = [];
|
|
296
|
+
|
|
297
|
+
if (plan.autoInstall.length > 0) {
|
|
298
|
+
lines.push("Will install:");
|
|
299
|
+
for (const tool of plan.autoInstall) {
|
|
300
|
+
const name = tool.displayName || tool.key;
|
|
301
|
+
const cmd = getInstallCommand(tool);
|
|
302
|
+
lines.push(` ${name} — ${cmd}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (plan.manual.length > 0) {
|
|
307
|
+
if (lines.length > 0) lines.push("");
|
|
308
|
+
lines.push("Manual setup needed:");
|
|
309
|
+
for (const tool of plan.manual) {
|
|
310
|
+
const name = tool.displayName || tool.key;
|
|
311
|
+
const url = tool.manualUrl || getInstallCommand(tool) || "see docs";
|
|
312
|
+
lines.push(` ${name} — ${url}`);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return lines.join("\n");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Format auth status for display.
|
|
321
|
+
* @param {Array<object>} authResults
|
|
322
|
+
* @returns {string}
|
|
323
|
+
*/
|
|
324
|
+
function formatAuthStatus(authResults) {
|
|
325
|
+
return authResults
|
|
326
|
+
.map((t) => {
|
|
327
|
+
const icon = t.authenticated ? "\u2705" : "\u274C";
|
|
328
|
+
const name = t.displayName || t.key;
|
|
329
|
+
let line = ` ${icon} ${name}`;
|
|
330
|
+
if (!t.authenticated && t.authSetupCmd) {
|
|
331
|
+
line += ` — run: ${t.authSetupCmd}`;
|
|
332
|
+
if (t.authUrl) line += ` (${t.authUrl})`;
|
|
333
|
+
}
|
|
334
|
+
return line;
|
|
335
|
+
})
|
|
336
|
+
.join("\n");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Build the AVAILABLE_TOOLS section content for CLAUDE.md.
|
|
341
|
+
* @param {Array<object>} tools - tool check results
|
|
342
|
+
* @returns {string}
|
|
343
|
+
*/
|
|
344
|
+
function buildAvailableToolsSection(tools) {
|
|
345
|
+
const lines = tools.map((t) => {
|
|
346
|
+
const status = t.installed ? "installed" : "not installed";
|
|
347
|
+
const name = t.displayName || t.key;
|
|
348
|
+
return `- **${name}** (${status}): ${t.why}`;
|
|
349
|
+
});
|
|
350
|
+
return lines.join("\n");
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
module.exports = {
|
|
354
|
+
checkTool,
|
|
355
|
+
getToolsForStack,
|
|
356
|
+
checkAllTools,
|
|
357
|
+
checkSystemBasics,
|
|
358
|
+
getPlatform,
|
|
359
|
+
getInstallCommand,
|
|
360
|
+
installTool,
|
|
361
|
+
categorizeForInstall,
|
|
362
|
+
checkAuth,
|
|
363
|
+
getAuthSetup,
|
|
364
|
+
getAuthUrl,
|
|
365
|
+
checkAllAuth,
|
|
366
|
+
formatToolStatus,
|
|
367
|
+
formatInstallInstructions,
|
|
368
|
+
formatInstallPlan,
|
|
369
|
+
formatAuthStatus,
|
|
370
|
+
buildAvailableToolsSection,
|
|
371
|
+
};
|
|
@@ -94,6 +94,16 @@ const SUBAGENT_SPECS = [
|
|
|
94
94
|
label: "Code Reviewer",
|
|
95
95
|
tags: ["testing-heavy", "docs-needed"],
|
|
96
96
|
},
|
|
97
|
+
{
|
|
98
|
+
key: "mobile-developer",
|
|
99
|
+
label: "Mobile Developer",
|
|
100
|
+
tags: ["native-ui", "frontend-heavy", "swift"],
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
key: "data-engineer",
|
|
104
|
+
label: "Data Engineer",
|
|
105
|
+
tags: ["data-pipeline", "compute-heavy", "analytics"],
|
|
106
|
+
},
|
|
97
107
|
];
|
|
98
108
|
|
|
99
109
|
/**
|
|
@@ -116,7 +126,7 @@ const STACK_SUBAGENTS = {
|
|
|
116
126
|
"test-automator",
|
|
117
127
|
"api-designer",
|
|
118
128
|
],
|
|
119
|
-
"swift-ios": ["ui-designer", "test-automator"],
|
|
129
|
+
"swift-ios": ["ui-designer", "test-automator", "mobile-developer"],
|
|
120
130
|
generic: ["debugger", "test-automator"],
|
|
121
131
|
};
|
|
122
132
|
|
package/bin/lib/template.js
CHANGED
|
@@ -9,6 +9,7 @@ const fs = require("fs");
|
|
|
9
9
|
const path = require("path");
|
|
10
10
|
const { FORMATTER_MAP } = require("./constants");
|
|
11
11
|
const { LANGUAGE_INSTRUCTIONS } = require("./languages");
|
|
12
|
+
const { getToolsForStack, checkTool } = require("./cli-tools");
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Build a substitution map from user config and parsed stack sections.
|
|
@@ -23,6 +24,18 @@ function buildSubstitutionMap(config, stackSections) {
|
|
|
23
24
|
config.customLanguage ||
|
|
24
25
|
LANGUAGE_INSTRUCTIONS.english;
|
|
25
26
|
|
|
27
|
+
// Build AVAILABLE_TOOLS section from detected CLI tools
|
|
28
|
+
const tools = getToolsForStack(config.stack);
|
|
29
|
+
const toolLines = tools.map((t) => {
|
|
30
|
+
const installed = checkTool(t.bin);
|
|
31
|
+
const status = installed ? "installed" : "not installed";
|
|
32
|
+
return `- **${t.key}** (${status}): ${t.why}`;
|
|
33
|
+
});
|
|
34
|
+
const availableTools =
|
|
35
|
+
toolLines.length > 0
|
|
36
|
+
? toolLines.join("\n")
|
|
37
|
+
: "No CLI tools configured. Run the installer to detect and configure tools.";
|
|
38
|
+
|
|
26
39
|
return {
|
|
27
40
|
PROJECT_NAME: config.projectName,
|
|
28
41
|
LANGUAGE: langInstruction,
|
|
@@ -39,6 +52,7 @@ function buildSubstitutionMap(config, stackSections) {
|
|
|
39
52
|
PACKAGE_MANAGER: config.packageManager,
|
|
40
53
|
TOOL_SPECIFIC_GUARDRAILS:
|
|
41
54
|
stackSections.TOOL_SPECIFIC_GUARDRAILS || "[Not configured]",
|
|
55
|
+
AVAILABLE_TOOLS: availableTools,
|
|
42
56
|
};
|
|
43
57
|
}
|
|
44
58
|
|