@aslomon/effectum 0.4.0 → 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 +46 -7
- package/bin/lib/cli-tools.js +201 -118
- package/bin/lib/tool-loader.js +243 -0
- package/bin/lib/ui.js +138 -75
- package/package.json +1 -1
- 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,7 +53,9 @@ const {
|
|
|
53
53
|
askLanguage,
|
|
54
54
|
askAutonomy,
|
|
55
55
|
showRecommendation,
|
|
56
|
-
|
|
56
|
+
showSystemCheck,
|
|
57
|
+
showInstallPlan,
|
|
58
|
+
showAuthCheck,
|
|
57
59
|
askSetupMode,
|
|
58
60
|
askCustomize,
|
|
59
61
|
askManual,
|
|
@@ -62,7 +64,15 @@ const {
|
|
|
62
64
|
showSummary,
|
|
63
65
|
showOutro,
|
|
64
66
|
} = require("./lib/ui");
|
|
65
|
-
const {
|
|
67
|
+
const {
|
|
68
|
+
checkAllTools,
|
|
69
|
+
checkSystemBasics,
|
|
70
|
+
formatToolStatus,
|
|
71
|
+
categorizeForInstall,
|
|
72
|
+
formatInstallPlan,
|
|
73
|
+
checkAllAuth,
|
|
74
|
+
formatAuthStatus,
|
|
75
|
+
} = require("./lib/cli-tools");
|
|
66
76
|
|
|
67
77
|
// ─── File helpers ─────────────────────────────────────────────────────────────
|
|
68
78
|
|
|
@@ -598,18 +608,41 @@ Options:
|
|
|
598
608
|
process.exit(0);
|
|
599
609
|
}
|
|
600
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
|
+
|
|
601
621
|
// CLI tool check (report only, no install in non-interactive mode)
|
|
602
622
|
const toolCheck = checkAllTools(config.stack);
|
|
603
623
|
if (toolCheck.missing.length > 0) {
|
|
604
|
-
|
|
624
|
+
const plan = categorizeForInstall(toolCheck.tools);
|
|
625
|
+
console.log("\n Stack Tools:");
|
|
605
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
|
+
}
|
|
606
631
|
console.log(
|
|
607
632
|
`\n ${toolCheck.missing.length} tool(s) not installed. Run installer interactively to install.`,
|
|
608
633
|
);
|
|
609
634
|
}
|
|
610
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
|
+
|
|
611
644
|
// Store tool status in config
|
|
612
|
-
config.detectedTools = toolCheck.tools.map((t) => ({
|
|
645
|
+
config.detectedTools = [...sysCheck.tools, ...toolCheck.tools].map((t) => ({
|
|
613
646
|
key: t.key,
|
|
614
647
|
installed: t.installed,
|
|
615
648
|
}));
|
|
@@ -699,6 +732,9 @@ Options:
|
|
|
699
732
|
process.exit(0);
|
|
700
733
|
}
|
|
701
734
|
|
|
735
|
+
// ── Phase 1: System Basics Check ──────────────────────────────────────────
|
|
736
|
+
await showSystemCheck();
|
|
737
|
+
|
|
702
738
|
// ── Step 2: Project Basics ────────────────────────────────────────────────
|
|
703
739
|
const projectName = await askProjectName(detected.projectName);
|
|
704
740
|
const stack = await askStack(detected.stack);
|
|
@@ -726,8 +762,8 @@ Options:
|
|
|
726
762
|
|
|
727
763
|
showRecommendation(rec);
|
|
728
764
|
|
|
729
|
-
// ──
|
|
730
|
-
const cliToolResult = await
|
|
765
|
+
// ── Phase 3: Consolidated Tool Plan ───────────────────────────────────────
|
|
766
|
+
const cliToolResult = await showInstallPlan(stack, installTargetDir);
|
|
731
767
|
|
|
732
768
|
// ── Step 8: Decision ──────────────────────────────────────────────────────
|
|
733
769
|
const setupMode = await askSetupMode();
|
|
@@ -875,7 +911,10 @@ Options:
|
|
|
875
911
|
);
|
|
876
912
|
}
|
|
877
913
|
|
|
878
|
-
// 9e:
|
|
914
|
+
// 9e: Auth check
|
|
915
|
+
await showAuthCheck(cliToolResult.tools);
|
|
916
|
+
|
|
917
|
+
// 9f: Save config
|
|
879
918
|
const configPath = writeConfig(installTargetDir, config);
|
|
880
919
|
configSteps.push({ status: "created", dest: configPath });
|
|
881
920
|
|
package/bin/lib/cli-tools.js
CHANGED
|
@@ -1,113 +1,49 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI tool
|
|
2
|
+
* CLI tool detection, installation, and auth checking.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* - auth/authSetup: commands to check and configure authentication
|
|
8
|
-
* - why: human-readable reason for the tool
|
|
9
|
-
* - foundation: true if always recommended regardless of stack
|
|
10
|
-
* - stacks: array of stack keys where this tool is relevant
|
|
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.
|
|
11
7
|
*/
|
|
12
8
|
"use strict";
|
|
13
9
|
|
|
14
10
|
const { spawnSync } = require("child_process");
|
|
15
11
|
const os = require("os");
|
|
12
|
+
const { loadToolDefinitions, getSystemBasics } = require("./tool-loader");
|
|
16
13
|
|
|
17
|
-
// ───
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
},
|
|
28
|
-
auth: "git config user.name && git config user.email",
|
|
29
|
-
authSetup:
|
|
30
|
-
'git config --global user.name "Your Name" && git config --global user.email "you@example.com"',
|
|
31
|
-
why: "Version control — required for all projects",
|
|
32
|
-
foundation: true,
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
key: "gh",
|
|
36
|
-
bin: "gh",
|
|
37
|
-
install: { darwin: "brew install gh", linux: "sudo apt install -y gh" },
|
|
38
|
-
auth: "gh auth status",
|
|
39
|
-
authSetup: "gh auth login",
|
|
40
|
-
why: "GitHub: Issues, PRs, Code Search, CI status",
|
|
41
|
-
foundation: true,
|
|
42
|
-
},
|
|
43
|
-
// Stack-specific
|
|
44
|
-
{
|
|
45
|
-
key: "supabase",
|
|
46
|
-
bin: "supabase",
|
|
47
|
-
install: {
|
|
48
|
-
darwin: "brew install supabase/tap/supabase",
|
|
49
|
-
linux: "npm i -g supabase",
|
|
50
|
-
},
|
|
51
|
-
auth: "supabase projects list",
|
|
52
|
-
authSetup: "supabase login",
|
|
53
|
-
why: "Database migrations, type generation, edge functions",
|
|
54
|
-
stacks: ["nextjs-supabase"],
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
key: "vercel",
|
|
58
|
-
bin: "vercel",
|
|
59
|
-
install: { all: "npm i -g vercel" },
|
|
60
|
-
auth: "vercel whoami",
|
|
61
|
-
authSetup: "vercel login",
|
|
62
|
-
why: "Deployment to Vercel",
|
|
63
|
-
stacks: ["nextjs-supabase"],
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
key: "docker",
|
|
67
|
-
bin: "docker",
|
|
68
|
-
install: {
|
|
69
|
-
darwin: "brew install --cask docker",
|
|
70
|
-
linux: "sudo apt install -y docker.io",
|
|
71
|
-
},
|
|
72
|
-
auth: null,
|
|
73
|
-
why: "Container management, local dev environment",
|
|
74
|
-
stacks: ["python-fastapi", "generic"],
|
|
75
|
-
},
|
|
76
|
-
{
|
|
77
|
-
key: "uv",
|
|
78
|
-
bin: "uv",
|
|
79
|
-
install: { all: "curl -LsSf https://astral.sh/uv/install.sh | sh" },
|
|
80
|
-
auth: null,
|
|
81
|
-
why: "Fast Python package management",
|
|
82
|
-
stacks: ["python-fastapi"],
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
key: "ruff",
|
|
86
|
-
bin: "ruff",
|
|
87
|
-
install: { all: "pip install ruff" },
|
|
88
|
-
auth: null,
|
|
89
|
-
why: "Python linting and formatting",
|
|
90
|
-
stacks: ["python-fastapi"],
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
key: "xcodebuild",
|
|
94
|
-
bin: "xcodebuild",
|
|
95
|
-
install: { darwin: "xcode-select --install" },
|
|
96
|
-
auth: null,
|
|
97
|
-
why: "iOS/macOS build toolchain",
|
|
98
|
-
stacks: ["swift-ios"],
|
|
99
|
-
},
|
|
100
|
-
];
|
|
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
|
+
}
|
|
101
24
|
|
|
102
25
|
// ─── Tool check ──────────────────────────────────────────────────────────────
|
|
103
26
|
|
|
104
27
|
/**
|
|
105
28
|
* Check if a CLI tool is installed by looking up its binary.
|
|
106
|
-
*
|
|
29
|
+
* Uses the tool's `check` command if available, otherwise falls back to `which`.
|
|
30
|
+
* @param {object|string} toolOrBin - tool object or binary name
|
|
107
31
|
* @returns {boolean}
|
|
108
32
|
*/
|
|
109
|
-
function checkTool(
|
|
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
|
+
|
|
110
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
|
+
}
|
|
111
47
|
const result = spawnSync("which", [bin], {
|
|
112
48
|
timeout: 5000,
|
|
113
49
|
stdio: "pipe",
|
|
@@ -119,28 +55,30 @@ function checkTool(bin) {
|
|
|
119
55
|
}
|
|
120
56
|
}
|
|
121
57
|
|
|
58
|
+
// ─── Tool retrieval ──────────────────────────────────────────────────────────
|
|
59
|
+
|
|
122
60
|
/**
|
|
123
|
-
* Get the relevant tools for a given stack.
|
|
61
|
+
* Get the relevant tools for a given stack, loaded from JSON definitions.
|
|
124
62
|
* Foundation tools are always included.
|
|
125
|
-
* @param {string} stack - stack key (e.g
|
|
63
|
+
* @param {string} stack - stack key (e.g., "nextjs-supabase")
|
|
64
|
+
* @param {string} [targetDir] - project directory for community overrides
|
|
126
65
|
* @returns {Array<object>}
|
|
127
66
|
*/
|
|
128
|
-
function getToolsForStack(stack) {
|
|
129
|
-
return
|
|
130
|
-
(tool) => tool.foundation || (tool.stacks && tool.stacks.includes(stack)),
|
|
131
|
-
);
|
|
67
|
+
function getToolsForStack(stack, targetDir) {
|
|
68
|
+
return loadToolDefinitions(stack, targetDir);
|
|
132
69
|
}
|
|
133
70
|
|
|
134
71
|
/**
|
|
135
72
|
* Check all relevant tools for a stack and return status.
|
|
136
73
|
* @param {string} stack
|
|
137
|
-
* @
|
|
74
|
+
* @param {string} [targetDir] - project directory for community overrides
|
|
75
|
+
* @returns {{ tools: Array<object>, missing: Array<object>, installed: Array<object> }}
|
|
138
76
|
*/
|
|
139
|
-
function checkAllTools(stack) {
|
|
140
|
-
const relevant = getToolsForStack(stack);
|
|
77
|
+
function checkAllTools(stack, targetDir) {
|
|
78
|
+
const relevant = getToolsForStack(stack, targetDir);
|
|
141
79
|
const results = relevant.map((tool) => ({
|
|
142
80
|
...tool,
|
|
143
|
-
installed: checkTool(tool
|
|
81
|
+
installed: checkTool(tool),
|
|
144
82
|
}));
|
|
145
83
|
|
|
146
84
|
return {
|
|
@@ -150,17 +88,26 @@ function checkAllTools(stack) {
|
|
|
150
88
|
};
|
|
151
89
|
}
|
|
152
90
|
|
|
153
|
-
// ─── Tool installation ───────────────────────────────────────────────────────
|
|
154
|
-
|
|
155
91
|
/**
|
|
156
|
-
*
|
|
157
|
-
* @returns {
|
|
92
|
+
* Check system basics (Homebrew, Git, Node.js, Claude Code).
|
|
93
|
+
* @returns {{ tools: Array<object>, missing: Array<object>, installed: Array<object> }}
|
|
158
94
|
*/
|
|
159
|
-
function
|
|
160
|
-
const
|
|
161
|
-
|
|
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
|
+
};
|
|
162
107
|
}
|
|
163
108
|
|
|
109
|
+
// ─── Tool installation ───────────────────────────────────────────────────────
|
|
110
|
+
|
|
164
111
|
/**
|
|
165
112
|
* Get the install command for a tool on the current platform.
|
|
166
113
|
* @param {object} tool
|
|
@@ -202,20 +149,51 @@ function installTool(tool) {
|
|
|
202
149
|
}
|
|
203
150
|
}
|
|
204
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
|
+
|
|
205
175
|
// ─── Auth checking ───────────────────────────────────────────────────────────
|
|
206
176
|
|
|
207
177
|
/**
|
|
208
178
|
* Check if a tool is authenticated.
|
|
179
|
+
* Supports both legacy format (auth as string) and new format (auth as object).
|
|
209
180
|
* @param {object} tool
|
|
210
181
|
* @returns {{ authenticated: boolean, needsAuth: boolean }}
|
|
211
182
|
*/
|
|
212
183
|
function checkAuth(tool) {
|
|
213
|
-
|
|
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) {
|
|
214
192
|
return { authenticated: true, needsAuth: false };
|
|
215
193
|
}
|
|
216
194
|
|
|
217
195
|
try {
|
|
218
|
-
const result = spawnSync("bash", ["-c",
|
|
196
|
+
const result = spawnSync("bash", ["-c", authCheck], {
|
|
219
197
|
timeout: 10000,
|
|
220
198
|
stdio: "pipe",
|
|
221
199
|
encoding: "utf8",
|
|
@@ -229,6 +207,50 @@ function checkAuth(tool) {
|
|
|
229
207
|
}
|
|
230
208
|
}
|
|
231
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
|
+
|
|
232
254
|
// ─── Formatting helpers ──────────────────────────────────────────────────────
|
|
233
255
|
|
|
234
256
|
/**
|
|
@@ -240,7 +262,8 @@ function formatToolStatus(tools) {
|
|
|
240
262
|
return tools
|
|
241
263
|
.map((t) => {
|
|
242
264
|
const icon = t.installed ? "\u2705" : "\u274C";
|
|
243
|
-
|
|
265
|
+
const name = t.displayName || t.key;
|
|
266
|
+
return ` ${icon} ${name} — ${t.why}`;
|
|
244
267
|
})
|
|
245
268
|
.join("\n");
|
|
246
269
|
}
|
|
@@ -254,8 +277,61 @@ function formatInstallInstructions(missing) {
|
|
|
254
277
|
const platform = getPlatform();
|
|
255
278
|
return missing
|
|
256
279
|
.map((t) => {
|
|
257
|
-
const cmd = t.install
|
|
258
|
-
|
|
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;
|
|
259
335
|
})
|
|
260
336
|
.join("\n");
|
|
261
337
|
}
|
|
@@ -268,21 +344,28 @@ function formatInstallInstructions(missing) {
|
|
|
268
344
|
function buildAvailableToolsSection(tools) {
|
|
269
345
|
const lines = tools.map((t) => {
|
|
270
346
|
const status = t.installed ? "installed" : "not installed";
|
|
271
|
-
|
|
347
|
+
const name = t.displayName || t.key;
|
|
348
|
+
return `- **${name}** (${status}): ${t.why}`;
|
|
272
349
|
});
|
|
273
350
|
return lines.join("\n");
|
|
274
351
|
}
|
|
275
352
|
|
|
276
353
|
module.exports = {
|
|
277
|
-
CLI_TOOLS,
|
|
278
354
|
checkTool,
|
|
279
355
|
getToolsForStack,
|
|
280
356
|
checkAllTools,
|
|
357
|
+
checkSystemBasics,
|
|
281
358
|
getPlatform,
|
|
282
359
|
getInstallCommand,
|
|
283
360
|
installTool,
|
|
361
|
+
categorizeForInstall,
|
|
284
362
|
checkAuth,
|
|
363
|
+
getAuthSetup,
|
|
364
|
+
getAuthUrl,
|
|
365
|
+
checkAllAuth,
|
|
285
366
|
formatToolStatus,
|
|
286
367
|
formatInstallInstructions,
|
|
368
|
+
formatInstallPlan,
|
|
369
|
+
formatAuthStatus,
|
|
287
370
|
buildAvailableToolsSection,
|
|
288
371
|
};
|