@skilly-hand/skilly-hand 0.3.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/CHANGELOG.md +33 -0
- package/README.md +10 -0
- package/package.json +4 -1
- package/packages/cli/src/bin.js +383 -137
- package/packages/core/src/index.js +42 -8
- package/packages/core/src/terminal.js +76 -69
- package/packages/core/src/ui/brand.js +31 -0
- package/packages/core/src/ui/index.js +3 -0
- package/packages/core/src/ui/layout.js +289 -0
- package/packages/core/src/ui/theme.js +120 -0
package/packages/cli/src/bin.js
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { checkbox as inquirerCheckbox, confirm as inquirerConfirm, select as inquirerSelect } from "@inquirer/prompts";
|
|
3
6
|
import { loadAllSkills } from "../../catalog/src/index.js";
|
|
4
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
DEFAULT_AGENTS,
|
|
9
|
+
installProject,
|
|
10
|
+
resolveSkillSelection,
|
|
11
|
+
runDoctor,
|
|
12
|
+
uninstallProject
|
|
13
|
+
} from "../../core/src/index.js";
|
|
5
14
|
import { createTerminalRenderer } from "../../core/src/terminal.js";
|
|
6
15
|
import { detectProject } from "../../detectors/src/index.js";
|
|
7
16
|
|
|
8
|
-
const
|
|
17
|
+
const require = createRequire(import.meta.url);
|
|
18
|
+
const { version } = require("../../../package.json");
|
|
9
19
|
|
|
10
|
-
function
|
|
20
|
+
function isExecutedDirectly(metaUrl, argv1) {
|
|
21
|
+
if (!argv1) return false;
|
|
22
|
+
return metaUrl === pathToFileURL(argv1).href;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function parseArgs(argv) {
|
|
11
26
|
const args = [...argv];
|
|
12
27
|
const positional = [];
|
|
13
28
|
const flags = {
|
|
@@ -50,9 +65,10 @@ function parseArgs(argv) {
|
|
|
50
65
|
return { command: positional[0], flags };
|
|
51
66
|
}
|
|
52
67
|
|
|
53
|
-
function buildHelpText() {
|
|
68
|
+
function buildHelpText(renderer, appVersion) {
|
|
54
69
|
const usage = renderer.section("Usage", renderer.list([
|
|
55
|
-
"npx skilly-hand
|
|
70
|
+
"npx skilly-hand # interactive launcher when running in a TTY",
|
|
71
|
+
"npx skilly-hand install",
|
|
56
72
|
"npx skilly-hand detect",
|
|
57
73
|
"npx skilly-hand list",
|
|
58
74
|
"npx skilly-hand doctor",
|
|
@@ -62,7 +78,7 @@ function buildHelpText() {
|
|
|
62
78
|
const flags = renderer.section("Flags", renderer.list([
|
|
63
79
|
"--dry-run Show install plan without writing files",
|
|
64
80
|
"--json Emit stable JSON output for automation",
|
|
65
|
-
"--yes, -y
|
|
81
|
+
"--yes, -y Skip install/uninstall confirmations",
|
|
66
82
|
"--verbose, -v Reserved for future debug detail",
|
|
67
83
|
"--agent, -a <name> codex|claude|cursor|gemini|copilot (repeatable)",
|
|
68
84
|
"--cwd <path> Project root (defaults to current directory)",
|
|
@@ -72,65 +88,22 @@ function buildHelpText() {
|
|
|
72
88
|
], { bullet: "-" }));
|
|
73
89
|
|
|
74
90
|
const examples = renderer.section("Examples", renderer.list([
|
|
91
|
+
"npx skilly-hand",
|
|
75
92
|
"npx skilly-hand install --dry-run",
|
|
76
93
|
"npx skilly-hand detect --json",
|
|
77
94
|
"npx skilly-hand install --agent codex --agent claude",
|
|
78
|
-
"npx skilly-hand
|
|
95
|
+
"npx skilly-hand uninstall --yes"
|
|
79
96
|
], { bullet: "-" }));
|
|
80
97
|
|
|
81
98
|
return renderer.joinBlocks([
|
|
82
|
-
renderer.
|
|
99
|
+
renderer.banner(appVersion),
|
|
83
100
|
usage,
|
|
84
101
|
flags,
|
|
85
102
|
examples
|
|
86
103
|
]);
|
|
87
104
|
}
|
|
88
105
|
|
|
89
|
-
function
|
|
90
|
-
return detections.map((item) => ({
|
|
91
|
-
technology: item.technology,
|
|
92
|
-
confidence: item.confidence.toFixed(2),
|
|
93
|
-
reasons: item.reasons.join("; "),
|
|
94
|
-
recommended: item.recommendedSkillIds.join(", ")
|
|
95
|
-
}));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function renderDetections(detections) {
|
|
99
|
-
if (detections.length === 0) {
|
|
100
|
-
return renderer.status("warn", "No technology signals were detected.", "Only core skills will be selected.");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return renderer.table(
|
|
104
|
-
[
|
|
105
|
-
{ key: "technology", header: "Technology" },
|
|
106
|
-
{ key: "confidence", header: "Confidence" },
|
|
107
|
-
{ key: "reasons", header: "Reasons" },
|
|
108
|
-
{ key: "recommended", header: "Recommended Skills" }
|
|
109
|
-
],
|
|
110
|
-
detectionRows(detections)
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function renderSkillTable(skills) {
|
|
115
|
-
if (skills.length === 0) {
|
|
116
|
-
return renderer.status("warn", "No skills selected.");
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return renderer.table(
|
|
120
|
-
[
|
|
121
|
-
{ key: "id", header: "Skill ID" },
|
|
122
|
-
{ key: "title", header: "Title" },
|
|
123
|
-
{ key: "tags", header: "Tags" }
|
|
124
|
-
],
|
|
125
|
-
skills.map((skill) => ({
|
|
126
|
-
id: skill.id,
|
|
127
|
-
title: skill.title,
|
|
128
|
-
tags: skill.tags.join(", ")
|
|
129
|
-
}))
|
|
130
|
-
);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function printInstallResult(result, flags) {
|
|
106
|
+
function printInstallResult(renderer, appVersion, result, flags) {
|
|
134
107
|
const mode = flags.dryRun ? "dry-run" : "apply";
|
|
135
108
|
const preflight = renderer.section(
|
|
136
109
|
"Install Preflight",
|
|
@@ -144,8 +117,30 @@ function printInstallResult(result, flags) {
|
|
|
144
117
|
])
|
|
145
118
|
);
|
|
146
119
|
|
|
147
|
-
const detections = renderer.section(
|
|
148
|
-
|
|
120
|
+
const detections = renderer.section(
|
|
121
|
+
"Detected Technologies",
|
|
122
|
+
result.plan.detections.length > 0
|
|
123
|
+
? renderer.detectionGrid(result.plan.detections)
|
|
124
|
+
: renderer.status("warn", "No technology signals were detected.", "Only core skills will be selected.")
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
const skills = renderer.section(
|
|
128
|
+
"Skill Plan",
|
|
129
|
+
result.plan.skills.length > 0
|
|
130
|
+
? renderer.table(
|
|
131
|
+
[
|
|
132
|
+
{ key: "id", header: "Skill ID" },
|
|
133
|
+
{ key: "title", header: "Title" },
|
|
134
|
+
{ key: "tags", header: "Tags" }
|
|
135
|
+
],
|
|
136
|
+
result.plan.skills.map((skill) => ({
|
|
137
|
+
id: skill.id,
|
|
138
|
+
title: skill.title,
|
|
139
|
+
tags: skill.tags.join(", ")
|
|
140
|
+
}))
|
|
141
|
+
)
|
|
142
|
+
: renderer.status("warn", "No skills selected.")
|
|
143
|
+
);
|
|
149
144
|
|
|
150
145
|
const status = result.applied
|
|
151
146
|
? renderer.status("success", "Installation completed.", "Managed files and symlinks are in place.")
|
|
@@ -153,19 +148,19 @@ function printInstallResult(result, flags) {
|
|
|
153
148
|
|
|
154
149
|
const nextSteps = result.applied
|
|
155
150
|
? renderer.nextSteps([
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
151
|
+
"Review generated AGENTS and assistant instruction files.",
|
|
152
|
+
"Run `npx skilly-hand doctor` to validate installation health.",
|
|
153
|
+
"Use `npx skilly-hand uninstall` to restore backed-up files if needed."
|
|
154
|
+
])
|
|
160
155
|
: renderer.nextSteps([
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
156
|
+
"Run `npx skilly-hand install` to apply this plan.",
|
|
157
|
+
"Adjust `--include` and `--exclude` tags to tune skill selection."
|
|
158
|
+
]);
|
|
164
159
|
|
|
165
|
-
renderer.write(renderer.joinBlocks([preflight, detections, skills, status, nextSteps]));
|
|
160
|
+
renderer.write(renderer.joinBlocks([renderer.banner(appVersion), preflight, detections, skills, status, nextSteps]));
|
|
166
161
|
}
|
|
167
162
|
|
|
168
|
-
function printDetectResult(cwd, detections) {
|
|
163
|
+
function printDetectResult(renderer, cwd, detections) {
|
|
169
164
|
const summary = renderer.section(
|
|
170
165
|
"Detection Summary",
|
|
171
166
|
renderer.kv([
|
|
@@ -174,11 +169,17 @@ function printDetectResult(cwd, detections) {
|
|
|
174
169
|
])
|
|
175
170
|
);
|
|
176
171
|
|
|
177
|
-
const
|
|
178
|
-
|
|
172
|
+
const findings = renderer.section(
|
|
173
|
+
"Findings",
|
|
174
|
+
detections.length > 0
|
|
175
|
+
? renderer.detectionGrid(detections)
|
|
176
|
+
: renderer.status("warn", "No technology signals were detected.", "Only core skills will be selected.")
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
renderer.write(renderer.joinBlocks([summary, findings]));
|
|
179
180
|
}
|
|
180
181
|
|
|
181
|
-
function printListResult(skills) {
|
|
182
|
+
function printListResult(renderer, skills) {
|
|
182
183
|
const summary = renderer.section(
|
|
183
184
|
"Catalog Summary",
|
|
184
185
|
renderer.kv([["Skills available", String(skills.length)]])
|
|
@@ -205,10 +206,8 @@ function printListResult(skills) {
|
|
|
205
206
|
renderer.write(renderer.joinBlocks([summary, table]));
|
|
206
207
|
}
|
|
207
208
|
|
|
208
|
-
function printDoctorResult(result) {
|
|
209
|
-
const
|
|
210
|
-
? renderer.status("success", "Installation detected.")
|
|
211
|
-
: renderer.status("warn", "No installation detected.");
|
|
209
|
+
function printDoctorResult(renderer, result) {
|
|
210
|
+
const badge = renderer.healthBadge(result.installed);
|
|
212
211
|
|
|
213
212
|
const summary = renderer.section(
|
|
214
213
|
"Doctor Summary",
|
|
@@ -250,10 +249,10 @@ function printDoctorResult(result) {
|
|
|
250
249
|
)
|
|
251
250
|
);
|
|
252
251
|
|
|
253
|
-
renderer.write(renderer.joinBlocks([
|
|
252
|
+
renderer.write(renderer.joinBlocks([badge, summary, lock, issues, probes]));
|
|
254
253
|
}
|
|
255
254
|
|
|
256
|
-
function printUninstallResult(result) {
|
|
255
|
+
function printUninstallResult(renderer, result) {
|
|
257
256
|
if (result.removed) {
|
|
258
257
|
renderer.write(
|
|
259
258
|
renderer.joinBlocks([
|
|
@@ -275,34 +274,193 @@ function printUninstallResult(result) {
|
|
|
275
274
|
);
|
|
276
275
|
}
|
|
277
276
|
|
|
278
|
-
|
|
279
|
-
|
|
277
|
+
export function buildErrorHint(message) {
|
|
278
|
+
if (message.startsWith("Unknown command:")) {
|
|
279
|
+
return "Run `npx skilly-hand --help` to see available commands.";
|
|
280
|
+
}
|
|
281
|
+
if (message.startsWith("Unknown flag:") || message.startsWith("Missing value")) {
|
|
282
|
+
return "Check command flags with `npx skilly-hand --help`.";
|
|
283
|
+
}
|
|
284
|
+
return "Retry with `--verbose` for expanded context if needed.";
|
|
285
|
+
}
|
|
280
286
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
287
|
+
export function createPromptAdapter({ selectImpl, checkboxImpl, confirmImpl } = {}) {
|
|
288
|
+
return {
|
|
289
|
+
select: selectImpl || inquirerSelect,
|
|
290
|
+
checkbox: checkboxImpl || inquirerCheckbox,
|
|
291
|
+
confirm: confirmImpl || inquirerConfirm
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function createServices(overrides = {}) {
|
|
296
|
+
return {
|
|
297
|
+
loadAllSkills,
|
|
298
|
+
installProject,
|
|
299
|
+
resolveSkillSelection,
|
|
300
|
+
runDoctor,
|
|
301
|
+
uninstallProject,
|
|
302
|
+
detectProject,
|
|
303
|
+
defaultAgents: DEFAULT_AGENTS,
|
|
304
|
+
...overrides
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function isInteractiveLauncherMode({ command, flags, stdout }) {
|
|
309
|
+
return !command && !flags.json && Boolean(stdout?.isTTY);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async function runInteractiveInstall({
|
|
313
|
+
cwd,
|
|
314
|
+
renderer,
|
|
315
|
+
prompt,
|
|
316
|
+
services,
|
|
317
|
+
appVersion
|
|
318
|
+
}) {
|
|
319
|
+
const [catalog, detections] = await Promise.all([
|
|
320
|
+
services.loadAllSkills(),
|
|
321
|
+
services.detectProject(cwd)
|
|
322
|
+
]);
|
|
323
|
+
const portableCatalog = catalog.filter((skill) => skill.portable).sort((a, b) => a.id.localeCompare(b.id));
|
|
324
|
+
const preselected = new Set(
|
|
325
|
+
services
|
|
326
|
+
.resolveSkillSelection({ catalog, detections, includeTags: [], excludeTags: [] })
|
|
327
|
+
.map((skill) => skill.id)
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const selectedSkillIds = await prompt.checkbox({
|
|
331
|
+
message: "Select skills to install",
|
|
332
|
+
choices: portableCatalog.map((skill) => ({
|
|
333
|
+
value: skill.id,
|
|
334
|
+
name: `${skill.id} - ${skill.title}`,
|
|
335
|
+
checked: preselected.has(skill.id)
|
|
336
|
+
}))
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
const selectedAgents = await prompt.checkbox({
|
|
340
|
+
message: "Select AI assistants to configure",
|
|
341
|
+
choices: services.defaultAgents.map((agent) => ({
|
|
342
|
+
value: agent,
|
|
343
|
+
name: agent,
|
|
344
|
+
checked: true
|
|
345
|
+
}))
|
|
346
|
+
});
|
|
296
347
|
|
|
297
|
-
|
|
348
|
+
const preview = await services.installProject({
|
|
349
|
+
cwd,
|
|
350
|
+
agents: selectedAgents,
|
|
351
|
+
dryRun: true,
|
|
352
|
+
selectedSkillIds
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
printInstallResult(renderer, appVersion, preview, {
|
|
356
|
+
dryRun: true,
|
|
357
|
+
include: [],
|
|
358
|
+
exclude: []
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
const shouldApply = await prompt.confirm({
|
|
362
|
+
message: "Apply installation changes now?",
|
|
363
|
+
default: true
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
if (!shouldApply) {
|
|
367
|
+
renderer.write(renderer.status("info", "Installation cancelled.", "No files were written."));
|
|
298
368
|
return;
|
|
299
369
|
}
|
|
300
370
|
|
|
301
|
-
const
|
|
302
|
-
|
|
371
|
+
const applied = await services.installProject({
|
|
372
|
+
cwd,
|
|
373
|
+
agents: selectedAgents,
|
|
374
|
+
dryRun: false,
|
|
375
|
+
selectedSkillIds
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
printInstallResult(renderer, appVersion, applied, {
|
|
379
|
+
dryRun: false,
|
|
380
|
+
include: [],
|
|
381
|
+
exclude: []
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function runInteractiveSession({
|
|
386
|
+
cwd,
|
|
387
|
+
renderer,
|
|
388
|
+
prompt,
|
|
389
|
+
services,
|
|
390
|
+
appVersion
|
|
391
|
+
}) {
|
|
392
|
+
renderer.write(renderer.banner(appVersion));
|
|
393
|
+
|
|
394
|
+
while (true) {
|
|
395
|
+
const selection = await prompt.select({
|
|
396
|
+
message: "Select a command",
|
|
397
|
+
choices: [
|
|
398
|
+
{ value: "install", name: "Install" },
|
|
399
|
+
{ value: "detect", name: "Detect" },
|
|
400
|
+
{ value: "list", name: "List" },
|
|
401
|
+
{ value: "doctor", name: "Doctor" },
|
|
402
|
+
{ value: "uninstall", name: "Uninstall" },
|
|
403
|
+
{ value: "exit", name: "Exit" }
|
|
404
|
+
]
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
if (selection === "exit") {
|
|
408
|
+
renderer.write(renderer.status("info", "Exited skilly-hand interactive mode."));
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
303
411
|
|
|
304
|
-
|
|
305
|
-
|
|
412
|
+
if (selection === "install") {
|
|
413
|
+
await runInteractiveInstall({ cwd, renderer, prompt, services, appVersion });
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (selection === "detect") {
|
|
418
|
+
const detections = await services.detectProject(cwd);
|
|
419
|
+
printDetectResult(renderer, cwd, detections);
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (selection === "list") {
|
|
424
|
+
const skills = await services.loadAllSkills();
|
|
425
|
+
printListResult(renderer, skills);
|
|
426
|
+
continue;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (selection === "doctor") {
|
|
430
|
+
const result = await services.runDoctor(cwd);
|
|
431
|
+
printDoctorResult(renderer, result);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (selection === "uninstall") {
|
|
436
|
+
const confirmed = await prompt.confirm({
|
|
437
|
+
message: "Remove the skilly-hand installation from this project?",
|
|
438
|
+
default: false
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
if (!confirmed) {
|
|
442
|
+
renderer.write(renderer.status("info", "Uninstall cancelled."));
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
const result = await services.uninstallProject(cwd);
|
|
447
|
+
printUninstallResult(renderer, result);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
async function runCommand({
|
|
453
|
+
command,
|
|
454
|
+
flags,
|
|
455
|
+
cwd,
|
|
456
|
+
stdout,
|
|
457
|
+
renderer,
|
|
458
|
+
prompt,
|
|
459
|
+
services,
|
|
460
|
+
appVersion
|
|
461
|
+
}) {
|
|
462
|
+
if (command === "detect") {
|
|
463
|
+
const detections = await services.detectProject(cwd);
|
|
306
464
|
if (flags.json) {
|
|
307
465
|
renderer.writeJson({
|
|
308
466
|
command: "detect",
|
|
@@ -312,12 +470,12 @@ async function main() {
|
|
|
312
470
|
});
|
|
313
471
|
return;
|
|
314
472
|
}
|
|
315
|
-
printDetectResult(cwd, detections);
|
|
473
|
+
printDetectResult(renderer, cwd, detections);
|
|
316
474
|
return;
|
|
317
475
|
}
|
|
318
476
|
|
|
319
|
-
if (
|
|
320
|
-
const skills = await loadAllSkills();
|
|
477
|
+
if (command === "list") {
|
|
478
|
+
const skills = await services.loadAllSkills();
|
|
321
479
|
if (flags.json) {
|
|
322
480
|
renderer.writeJson({
|
|
323
481
|
command: "list",
|
|
@@ -326,12 +484,12 @@ async function main() {
|
|
|
326
484
|
});
|
|
327
485
|
return;
|
|
328
486
|
}
|
|
329
|
-
printListResult(skills);
|
|
487
|
+
printListResult(renderer, skills);
|
|
330
488
|
return;
|
|
331
489
|
}
|
|
332
490
|
|
|
333
|
-
if (
|
|
334
|
-
const result = await runDoctor(cwd);
|
|
491
|
+
if (command === "doctor") {
|
|
492
|
+
const result = await services.runDoctor(cwd);
|
|
335
493
|
if (flags.json) {
|
|
336
494
|
renderer.writeJson({
|
|
337
495
|
command: "doctor",
|
|
@@ -339,12 +497,23 @@ async function main() {
|
|
|
339
497
|
});
|
|
340
498
|
return;
|
|
341
499
|
}
|
|
342
|
-
printDoctorResult(result);
|
|
500
|
+
printDoctorResult(renderer, result);
|
|
343
501
|
return;
|
|
344
502
|
}
|
|
345
503
|
|
|
346
|
-
if (
|
|
347
|
-
|
|
504
|
+
if (command === "uninstall") {
|
|
505
|
+
if (!flags.json && !flags.yes && Boolean(stdout?.isTTY)) {
|
|
506
|
+
const confirmed = await prompt.confirm({
|
|
507
|
+
message: "Remove the skilly-hand installation from this project?",
|
|
508
|
+
default: false
|
|
509
|
+
});
|
|
510
|
+
if (!confirmed) {
|
|
511
|
+
renderer.write(renderer.status("info", "Uninstall cancelled."));
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const result = await services.uninstallProject(cwd);
|
|
348
517
|
if (flags.json) {
|
|
349
518
|
renderer.writeJson({
|
|
350
519
|
command: "uninstall",
|
|
@@ -352,12 +521,23 @@ async function main() {
|
|
|
352
521
|
});
|
|
353
522
|
return;
|
|
354
523
|
}
|
|
355
|
-
printUninstallResult(result);
|
|
524
|
+
printUninstallResult(renderer, result);
|
|
356
525
|
return;
|
|
357
526
|
}
|
|
358
527
|
|
|
359
|
-
if (
|
|
360
|
-
|
|
528
|
+
if (command === "install") {
|
|
529
|
+
if (!flags.dryRun && !flags.json && !flags.yes && Boolean(stdout?.isTTY)) {
|
|
530
|
+
const confirmed = await prompt.confirm({
|
|
531
|
+
message: "Apply installation changes to this project?",
|
|
532
|
+
default: true
|
|
533
|
+
});
|
|
534
|
+
if (!confirmed) {
|
|
535
|
+
renderer.write(renderer.status("info", "Installation cancelled.", "No files were written."));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const result = await services.installProject({
|
|
361
541
|
cwd,
|
|
362
542
|
agents: flags.agents,
|
|
363
543
|
dryRun: flags.dryRun,
|
|
@@ -375,43 +555,109 @@ async function main() {
|
|
|
375
555
|
return;
|
|
376
556
|
}
|
|
377
557
|
|
|
378
|
-
printInstallResult(result, flags);
|
|
558
|
+
printInstallResult(renderer, appVersion, result, flags);
|
|
379
559
|
return;
|
|
380
560
|
}
|
|
381
561
|
|
|
382
|
-
throw new Error(`Unknown command: ${
|
|
562
|
+
throw new Error(`Unknown command: ${command}`);
|
|
383
563
|
}
|
|
384
564
|
|
|
385
|
-
|
|
565
|
+
export async function runCli({
|
|
566
|
+
argv = process.argv.slice(2),
|
|
567
|
+
stdout = process.stdout,
|
|
568
|
+
stderr = process.stderr,
|
|
569
|
+
env = process.env,
|
|
570
|
+
platform = process.platform,
|
|
571
|
+
prompt = createPromptAdapter(),
|
|
572
|
+
services: providedServices = {},
|
|
573
|
+
appVersion = version,
|
|
574
|
+
cwdResolver = process.cwd
|
|
575
|
+
} = {}) {
|
|
576
|
+
const renderer = createTerminalRenderer({ stdout, stderr, env, platform });
|
|
577
|
+
const services = createServices(providedServices);
|
|
578
|
+
const { command, flags } = parseArgs(argv);
|
|
386
579
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
580
|
+
if (flags.help) {
|
|
581
|
+
if (flags.json) {
|
|
582
|
+
renderer.writeJson({
|
|
583
|
+
command: command || "install",
|
|
584
|
+
help: true,
|
|
585
|
+
usage: [
|
|
586
|
+
"npx skilly-hand",
|
|
587
|
+
"npx skilly-hand install",
|
|
588
|
+
"npx skilly-hand detect",
|
|
589
|
+
"npx skilly-hand list",
|
|
590
|
+
"npx skilly-hand doctor",
|
|
591
|
+
"npx skilly-hand uninstall"
|
|
592
|
+
]
|
|
593
|
+
});
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
394
596
|
|
|
395
|
-
|
|
396
|
-
renderer.writeErrorJson({
|
|
397
|
-
ok: false,
|
|
398
|
-
error: {
|
|
399
|
-
what: "skilly-hand command failed",
|
|
400
|
-
why: error.message,
|
|
401
|
-
hint
|
|
402
|
-
}
|
|
403
|
-
});
|
|
404
|
-
process.exitCode = 1;
|
|
597
|
+
renderer.write(buildHelpText(renderer, appVersion));
|
|
405
598
|
return;
|
|
406
599
|
}
|
|
407
600
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
601
|
+
const cwd = path.resolve(flags.cwd || cwdResolver());
|
|
602
|
+
|
|
603
|
+
if (isInteractiveLauncherMode({ command, flags, stdout })) {
|
|
604
|
+
try {
|
|
605
|
+
await runInteractiveSession({
|
|
606
|
+
cwd,
|
|
607
|
+
renderer,
|
|
608
|
+
prompt,
|
|
609
|
+
services,
|
|
610
|
+
appVersion
|
|
611
|
+
});
|
|
612
|
+
return;
|
|
613
|
+
} catch (error) {
|
|
614
|
+
if (error?.name === "ExitPromptError") {
|
|
615
|
+
renderer.write(renderer.status("info", "Interactive session cancelled."));
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
throw error;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const effectiveCommand = command || "install";
|
|
623
|
+
await runCommand({
|
|
624
|
+
command: effectiveCommand,
|
|
625
|
+
flags,
|
|
626
|
+
cwd,
|
|
627
|
+
stdout,
|
|
628
|
+
renderer,
|
|
629
|
+
prompt,
|
|
630
|
+
services,
|
|
631
|
+
appVersion
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
if (isExecutedDirectly(import.meta.url, process.argv[1])) {
|
|
636
|
+
const jsonRequested = process.argv.includes("--json");
|
|
637
|
+
const renderer = createTerminalRenderer();
|
|
638
|
+
|
|
639
|
+
runCli().catch((error) => {
|
|
640
|
+
if (jsonRequested) {
|
|
641
|
+
renderer.writeErrorJson({
|
|
642
|
+
ok: false,
|
|
643
|
+
error: {
|
|
644
|
+
what: "skilly-hand command failed",
|
|
645
|
+
why: error.message,
|
|
646
|
+
hint: buildErrorHint(error.message)
|
|
647
|
+
}
|
|
648
|
+
});
|
|
649
|
+
process.exitCode = 1;
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
renderer.writeError(
|
|
654
|
+
renderer.error({
|
|
655
|
+
what: "skilly-hand command failed",
|
|
656
|
+
why: error.message,
|
|
657
|
+
hint: buildErrorHint(error.message),
|
|
658
|
+
exitCode: 1
|
|
659
|
+
})
|
|
660
|
+
);
|
|
661
|
+
process.exitCode = 1;
|
|
662
|
+
});
|
|
663
|
+
}
|