@skilly-hand/skilly-hand 0.18.0 → 0.20.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 +40 -0
- package/README.md +13 -3
- package/catalog/skills/project-security/assets/generic-ci-security-gate.sh +1 -28
- package/catalog/skills/project-security/assets/github-actions-security-gate.yml +38 -0
- package/catalog/skills/project-security/assets/pre-commit.sample.sh +1 -1
- package/catalog/skills/project-security/assets/pre-push.sample.sh +1 -30
- package/catalog/skills/project-security/assets/run-security-check.shared.sh +33 -0
- package/package.json +8 -4
- package/packages/catalog/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/cli/src/bin.js +126 -161
- package/packages/cli/src/ink-ui.js +692 -0
- package/packages/core/package.json +1 -1
- package/packages/core/src/terminal.js +16 -5
- package/packages/core/src/ui/layout.js +193 -42
- package/packages/detectors/package.json +1 -1
package/packages/cli/src/bin.js
CHANGED
|
@@ -3,7 +3,6 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { checkbox as inquirerCheckbox, confirm as inquirerConfirm, select as inquirerSelect } from "@inquirer/prompts";
|
|
7
6
|
import { loadAllSkills } from "../../catalog/src/index.js";
|
|
8
7
|
import {
|
|
9
8
|
DEFAULT_AGENTS,
|
|
@@ -14,6 +13,7 @@ import {
|
|
|
14
13
|
} from "../../core/src/index.js";
|
|
15
14
|
import { createTerminalRenderer } from "../../core/src/terminal.js";
|
|
16
15
|
import { detectProject } from "../../detectors/src/index.js";
|
|
16
|
+
import { confirmWithInk, launchInkApp } from "./ink-ui.js";
|
|
17
17
|
|
|
18
18
|
const require = createRequire(import.meta.url);
|
|
19
19
|
const { version } = require("../../../package.json");
|
|
@@ -41,6 +41,7 @@ export function parseArgs(argv) {
|
|
|
41
41
|
yes: false,
|
|
42
42
|
verbose: false,
|
|
43
43
|
json: false,
|
|
44
|
+
classic: false,
|
|
44
45
|
agents: [],
|
|
45
46
|
include: [],
|
|
46
47
|
exclude: []
|
|
@@ -65,6 +66,7 @@ export function parseArgs(argv) {
|
|
|
65
66
|
else if (token === "--yes" || token === "-y") flags.yes = true;
|
|
66
67
|
else if (token === "--verbose" || token === "-v") flags.verbose = true;
|
|
67
68
|
else if (token === "--json") flags.json = true;
|
|
69
|
+
else if (token === "--classic") flags.classic = true;
|
|
68
70
|
else if (token === "--agent" || token === "-a") flags.agents.push(takeFlagValue(token));
|
|
69
71
|
else if (token === "--cwd") flags.cwd = takeFlagValue(token);
|
|
70
72
|
else if (token === "--include") flags.include.push(takeFlagValue(token));
|
|
@@ -89,6 +91,7 @@ function buildHelpText(renderer, appVersion) {
|
|
|
89
91
|
const flags = renderer.section("Flags", renderer.list([
|
|
90
92
|
"--dry-run Show install plan without writing files",
|
|
91
93
|
"--json Emit stable JSON output for automation",
|
|
94
|
+
"--classic Force plain text command mode (skip full-screen TUI)",
|
|
92
95
|
"--yes, -y Skip install/uninstall confirmations",
|
|
93
96
|
"--verbose, -v Reserved for future debug detail",
|
|
94
97
|
"--agent, -a <name> standard|codex|claude|cursor|gemini|copilot|antigravity|windsurf|trae (repeatable)",
|
|
@@ -114,7 +117,7 @@ function buildHelpText(renderer, appVersion) {
|
|
|
114
117
|
]);
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
function
|
|
120
|
+
function buildInstallResultBlock(renderer, appVersion, result, flags) {
|
|
118
121
|
const mode = flags.dryRun ? "dry-run" : "apply";
|
|
119
122
|
const preflight = renderer.section(
|
|
120
123
|
"Install Preflight",
|
|
@@ -168,10 +171,14 @@ function printInstallResult(renderer, appVersion, result, flags) {
|
|
|
168
171
|
"Adjust `--include` and `--exclude` tags to tune skill selection."
|
|
169
172
|
]);
|
|
170
173
|
|
|
171
|
-
renderer.
|
|
174
|
+
return renderer.joinBlocks([renderer.banner(appVersion), preflight, detections, skills, status, nextSteps]);
|
|
172
175
|
}
|
|
173
176
|
|
|
174
|
-
function
|
|
177
|
+
function printInstallResult(renderer, appVersion, result, flags) {
|
|
178
|
+
renderer.write(buildInstallResultBlock(renderer, appVersion, result, flags));
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function buildDetectResultBlock(renderer, cwd, detections) {
|
|
175
182
|
const summary = renderer.section(
|
|
176
183
|
"Detection Summary",
|
|
177
184
|
renderer.kv([
|
|
@@ -187,10 +194,14 @@ function printDetectResult(renderer, cwd, detections) {
|
|
|
187
194
|
: renderer.status("warn", "No technology signals were detected.", "Only core skills will be selected.")
|
|
188
195
|
);
|
|
189
196
|
|
|
190
|
-
renderer.
|
|
197
|
+
return renderer.joinBlocks([summary, findings]);
|
|
191
198
|
}
|
|
192
199
|
|
|
193
|
-
function
|
|
200
|
+
function printDetectResult(renderer, cwd, detections) {
|
|
201
|
+
renderer.write(buildDetectResultBlock(renderer, cwd, detections));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function buildListResultBlock(renderer, skills) {
|
|
194
205
|
const summary = renderer.section(
|
|
195
206
|
"Catalog Summary",
|
|
196
207
|
renderer.kv([["Skills available", String(skills.length)]])
|
|
@@ -214,10 +225,14 @@ function printListResult(renderer, skills) {
|
|
|
214
225
|
)
|
|
215
226
|
);
|
|
216
227
|
|
|
217
|
-
renderer.
|
|
228
|
+
return renderer.joinBlocks([summary, table]);
|
|
218
229
|
}
|
|
219
230
|
|
|
220
|
-
function
|
|
231
|
+
function printListResult(renderer, skills) {
|
|
232
|
+
renderer.write(buildListResultBlock(renderer, skills));
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function buildDoctorResultBlock(renderer, result) {
|
|
221
236
|
const badge = renderer.healthBadge(result.installed);
|
|
222
237
|
|
|
223
238
|
const summary = renderer.section(
|
|
@@ -260,29 +275,32 @@ function printDoctorResult(renderer, result) {
|
|
|
260
275
|
)
|
|
261
276
|
);
|
|
262
277
|
|
|
263
|
-
renderer.
|
|
278
|
+
return renderer.joinBlocks([badge, summary, lock, issues, probes]);
|
|
264
279
|
}
|
|
265
280
|
|
|
266
|
-
function
|
|
281
|
+
function printDoctorResult(renderer, result) {
|
|
282
|
+
renderer.write(buildDoctorResultBlock(renderer, result));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function buildUninstallResultBlock(renderer, result) {
|
|
267
286
|
if (result.removed) {
|
|
268
|
-
renderer.
|
|
269
|
-
renderer.
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
"Run `npx skilly-hand doctor` to confirm the project state."
|
|
274
|
-
])
|
|
287
|
+
return renderer.joinBlocks([
|
|
288
|
+
renderer.status("success", "skilly-hand installation removed."),
|
|
289
|
+
renderer.nextSteps([
|
|
290
|
+
"Run `npx skilly-hand install` if you want to reinstall managed files.",
|
|
291
|
+
"Run `npx skilly-hand doctor` to confirm the project state."
|
|
275
292
|
])
|
|
276
|
-
);
|
|
277
|
-
return;
|
|
293
|
+
]);
|
|
278
294
|
}
|
|
279
295
|
|
|
280
|
-
renderer.
|
|
281
|
-
renderer.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
296
|
+
return renderer.joinBlocks([
|
|
297
|
+
renderer.status("warn", "Nothing to uninstall.", result.reason),
|
|
298
|
+
renderer.nextSteps(["Run `npx skilly-hand install` to create a managed installation first."])
|
|
299
|
+
]);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function printUninstallResult(renderer, result) {
|
|
303
|
+
renderer.write(buildUninstallResultBlock(renderer, result));
|
|
286
304
|
}
|
|
287
305
|
|
|
288
306
|
export function buildErrorHint(message) {
|
|
@@ -295,14 +313,6 @@ export function buildErrorHint(message) {
|
|
|
295
313
|
return "Retry with `--verbose` for expanded context if needed.";
|
|
296
314
|
}
|
|
297
315
|
|
|
298
|
-
export function createPromptAdapter({ selectImpl, checkboxImpl, confirmImpl } = {}) {
|
|
299
|
-
return {
|
|
300
|
-
select: selectImpl || inquirerSelect,
|
|
301
|
-
checkbox: checkboxImpl || inquirerCheckbox,
|
|
302
|
-
confirm: confirmImpl || inquirerConfirm
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
|
|
306
316
|
function createServices(overrides = {}) {
|
|
307
317
|
return {
|
|
308
318
|
loadAllSkills,
|
|
@@ -316,16 +326,13 @@ function createServices(overrides = {}) {
|
|
|
316
326
|
};
|
|
317
327
|
}
|
|
318
328
|
|
|
319
|
-
function isInteractiveLauncherMode({ command, flags, stdout }) {
|
|
320
|
-
return !command && !flags.json && Boolean(stdout?.isTTY);
|
|
329
|
+
function isInteractiveLauncherMode({ command, flags, stdout, stdin }) {
|
|
330
|
+
return !command && !flags.json && !flags.classic && Boolean(stdout?.isTTY) && Boolean(stdin?.isTTY);
|
|
321
331
|
}
|
|
322
332
|
|
|
323
|
-
async function
|
|
333
|
+
async function getInteractiveInstallContext({
|
|
324
334
|
cwd,
|
|
325
|
-
|
|
326
|
-
prompt,
|
|
327
|
-
services,
|
|
328
|
-
appVersion
|
|
335
|
+
services
|
|
329
336
|
}) {
|
|
330
337
|
const [catalog, detections] = await Promise.all([
|
|
331
338
|
services.loadAllSkills(),
|
|
@@ -338,137 +345,90 @@ async function runInteractiveInstall({
|
|
|
338
345
|
.map((skill) => skill.id)
|
|
339
346
|
);
|
|
340
347
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
value: skill.id,
|
|
345
|
-
name: `${skill.id} - ${skill.title}`,
|
|
348
|
+
return {
|
|
349
|
+
skills: portableCatalog.map((skill) => ({
|
|
350
|
+
...skill,
|
|
346
351
|
checked: preselected.has(skill.id)
|
|
347
|
-
}))
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
const selectedAgents = await prompt.checkbox({
|
|
351
|
-
message: "Select AI assistants to configure",
|
|
352
|
-
choices: services.defaultAgents.map((agent) => ({
|
|
352
|
+
})),
|
|
353
|
+
agents: services.defaultAgents.map((agent) => ({
|
|
353
354
|
value: agent,
|
|
354
|
-
name: agent,
|
|
355
355
|
checked: agent === "standard"
|
|
356
356
|
}))
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
const preview = await services.installProject({
|
|
360
|
-
cwd,
|
|
361
|
-
agents: selectedAgents,
|
|
362
|
-
dryRun: true,
|
|
363
|
-
selectedSkillIds
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
printInstallResult(renderer, appVersion, preview, {
|
|
367
|
-
dryRun: true,
|
|
368
|
-
include: [],
|
|
369
|
-
exclude: []
|
|
370
|
-
});
|
|
371
|
-
|
|
372
|
-
const shouldApply = await prompt.confirm({
|
|
373
|
-
message: "Apply installation changes now?",
|
|
374
|
-
default: true
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
if (!shouldApply) {
|
|
378
|
-
renderer.write(renderer.status("info", "Installation cancelled.", "No files were written."));
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
const applied = await services.installProject({
|
|
383
|
-
cwd,
|
|
384
|
-
agents: selectedAgents,
|
|
385
|
-
dryRun: false,
|
|
386
|
-
selectedSkillIds
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
printInstallResult(renderer, appVersion, applied, {
|
|
390
|
-
dryRun: false,
|
|
391
|
-
include: [],
|
|
392
|
-
exclude: []
|
|
393
|
-
});
|
|
357
|
+
};
|
|
394
358
|
}
|
|
395
359
|
|
|
396
360
|
async function runInteractiveSession({
|
|
397
361
|
cwd,
|
|
398
362
|
renderer,
|
|
399
|
-
prompt,
|
|
400
363
|
services,
|
|
401
|
-
appVersion
|
|
364
|
+
appVersion,
|
|
365
|
+
interactiveUi
|
|
402
366
|
}) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
renderer.write(renderer.status("info", "Uninstall cancelled."));
|
|
454
|
-
continue;
|
|
367
|
+
await interactiveUi.launch({
|
|
368
|
+
appVersion,
|
|
369
|
+
actions: {
|
|
370
|
+
async runCommand(command) {
|
|
371
|
+
if (command === "detect") {
|
|
372
|
+
const detections = await services.detectProject(cwd);
|
|
373
|
+
return buildDetectResultBlock(renderer, cwd, detections);
|
|
374
|
+
}
|
|
375
|
+
if (command === "list") {
|
|
376
|
+
const skills = await services.loadAllSkills();
|
|
377
|
+
return buildListResultBlock(renderer, skills);
|
|
378
|
+
}
|
|
379
|
+
if (command === "doctor") {
|
|
380
|
+
const result = await services.runDoctor(cwd);
|
|
381
|
+
return buildDoctorResultBlock(renderer, result);
|
|
382
|
+
}
|
|
383
|
+
if (command === "uninstall") {
|
|
384
|
+
const result = await services.uninstallProject(cwd);
|
|
385
|
+
return buildUninstallResultBlock(renderer, result);
|
|
386
|
+
}
|
|
387
|
+
return renderer.status("warn", `Unknown command: ${command}`);
|
|
388
|
+
},
|
|
389
|
+
async prepareInstall() {
|
|
390
|
+
return getInteractiveInstallContext({ cwd, services });
|
|
391
|
+
},
|
|
392
|
+
async previewInstall({ selectedSkillIds, selectedAgents }) {
|
|
393
|
+
const preview = await services.installProject({
|
|
394
|
+
cwd,
|
|
395
|
+
agents: selectedAgents,
|
|
396
|
+
dryRun: true,
|
|
397
|
+
selectedSkillIds
|
|
398
|
+
});
|
|
399
|
+
return buildInstallResultBlock(renderer, appVersion, preview, {
|
|
400
|
+
dryRun: true,
|
|
401
|
+
include: [],
|
|
402
|
+
exclude: []
|
|
403
|
+
});
|
|
404
|
+
},
|
|
405
|
+
async applyInstall({ selectedSkillIds, selectedAgents }) {
|
|
406
|
+
const applied = await services.installProject({
|
|
407
|
+
cwd,
|
|
408
|
+
agents: selectedAgents,
|
|
409
|
+
dryRun: false,
|
|
410
|
+
selectedSkillIds
|
|
411
|
+
});
|
|
412
|
+
return buildInstallResultBlock(renderer, appVersion, applied, {
|
|
413
|
+
dryRun: false,
|
|
414
|
+
include: [],
|
|
415
|
+
exclude: []
|
|
416
|
+
});
|
|
455
417
|
}
|
|
456
|
-
|
|
457
|
-
const result = await services.uninstallProject(cwd);
|
|
458
|
-
printUninstallResult(renderer, result);
|
|
459
418
|
}
|
|
460
|
-
}
|
|
419
|
+
});
|
|
461
420
|
}
|
|
462
421
|
|
|
463
422
|
async function runCommand({
|
|
464
423
|
command,
|
|
465
424
|
flags,
|
|
466
425
|
cwd,
|
|
426
|
+
stdin,
|
|
467
427
|
stdout,
|
|
468
428
|
renderer,
|
|
469
|
-
prompt,
|
|
470
429
|
services,
|
|
471
|
-
appVersion
|
|
430
|
+
appVersion,
|
|
431
|
+
interactiveUi
|
|
472
432
|
}) {
|
|
473
433
|
if (command === "detect") {
|
|
474
434
|
const detections = await services.detectProject(cwd);
|
|
@@ -513,10 +473,10 @@ async function runCommand({
|
|
|
513
473
|
}
|
|
514
474
|
|
|
515
475
|
if (command === "uninstall") {
|
|
516
|
-
if (!flags.json && !flags.yes && Boolean(stdout?.isTTY)) {
|
|
517
|
-
const confirmed = await
|
|
476
|
+
if (!flags.json && !flags.yes && Boolean(stdout?.isTTY) && Boolean(stdin?.isTTY)) {
|
|
477
|
+
const confirmed = await interactiveUi.confirm({
|
|
518
478
|
message: "Remove the skilly-hand installation from this project?",
|
|
519
|
-
|
|
479
|
+
defaultValue: false
|
|
520
480
|
});
|
|
521
481
|
if (!confirmed) {
|
|
522
482
|
renderer.write(renderer.status("info", "Uninstall cancelled."));
|
|
@@ -537,10 +497,10 @@ async function runCommand({
|
|
|
537
497
|
}
|
|
538
498
|
|
|
539
499
|
if (command === "install") {
|
|
540
|
-
if (!flags.dryRun && !flags.json && !flags.yes && Boolean(stdout?.isTTY)) {
|
|
541
|
-
const confirmed = await
|
|
500
|
+
if (!flags.dryRun && !flags.json && !flags.yes && Boolean(stdout?.isTTY) && Boolean(stdin?.isTTY)) {
|
|
501
|
+
const confirmed = await interactiveUi.confirm({
|
|
542
502
|
message: "Apply installation changes to this project?",
|
|
543
|
-
|
|
503
|
+
defaultValue: true
|
|
544
504
|
});
|
|
545
505
|
if (!confirmed) {
|
|
546
506
|
renderer.write(renderer.status("info", "Installation cancelled.", "No files were written."));
|
|
@@ -575,12 +535,16 @@ async function runCommand({
|
|
|
575
535
|
|
|
576
536
|
export async function runCli({
|
|
577
537
|
argv = process.argv.slice(2),
|
|
538
|
+
stdin = process.stdin,
|
|
578
539
|
stdout = process.stdout,
|
|
579
540
|
stderr = process.stderr,
|
|
580
541
|
env = process.env,
|
|
581
542
|
platform = process.platform,
|
|
582
|
-
prompt = createPromptAdapter(),
|
|
583
543
|
services: providedServices = {},
|
|
544
|
+
interactiveUi = {
|
|
545
|
+
launch: ({ appVersion, actions }) => launchInkApp({ appVersion, actions }),
|
|
546
|
+
confirm: ({ message, defaultValue }) => confirmWithInk({ message, defaultValue })
|
|
547
|
+
},
|
|
584
548
|
appVersion = version,
|
|
585
549
|
cwdResolver = process.cwd
|
|
586
550
|
} = {}) {
|
|
@@ -611,14 +575,14 @@ export async function runCli({
|
|
|
611
575
|
|
|
612
576
|
const cwd = path.resolve(flags.cwd || cwdResolver());
|
|
613
577
|
|
|
614
|
-
if (isInteractiveLauncherMode({ command, flags, stdout })) {
|
|
578
|
+
if (isInteractiveLauncherMode({ command, flags, stdout, stdin })) {
|
|
615
579
|
try {
|
|
616
580
|
await runInteractiveSession({
|
|
617
581
|
cwd,
|
|
618
582
|
renderer,
|
|
619
|
-
prompt,
|
|
620
583
|
services,
|
|
621
|
-
appVersion
|
|
584
|
+
appVersion,
|
|
585
|
+
interactiveUi
|
|
622
586
|
});
|
|
623
587
|
return;
|
|
624
588
|
} catch (error) {
|
|
@@ -635,11 +599,12 @@ export async function runCli({
|
|
|
635
599
|
command: effectiveCommand,
|
|
636
600
|
flags,
|
|
637
601
|
cwd,
|
|
602
|
+
stdin,
|
|
638
603
|
stdout,
|
|
639
604
|
renderer,
|
|
640
|
-
prompt,
|
|
641
605
|
services,
|
|
642
|
-
appVersion
|
|
606
|
+
appVersion,
|
|
607
|
+
interactiveUi
|
|
643
608
|
});
|
|
644
609
|
}
|
|
645
610
|
|