oh-skillhub 0.1.11 → 0.1.13
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/README.md +12 -4
- package/package.json +1 -1
- package/src/cli.js +264 -31
package/README.md
CHANGED
|
@@ -12,16 +12,23 @@ npx oh-skillhub install --domain arkui --agent all --scope user
|
|
|
12
12
|
npx oh-skillhub clean --agent claude --scope user --dry-run
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
Running without arguments starts a TUI selector. Choose
|
|
15
|
+
Running without arguments starts a TUI selector. Choose an action first (`Install skills` or `Clean installed skills`). Install then asks for the target (`Codex`, `Claude`, `OpenCode`, or `All`) and skill groups. Clean asks for the target and installed skills to remove.
|
|
16
16
|
|
|
17
17
|
In an interactive terminal:
|
|
18
18
|
|
|
19
19
|
- Use `Up` / `Down` or `j` / `k` to move.
|
|
20
|
-
- Press `Space` to select or unselect
|
|
21
|
-
- Press `Enter` to
|
|
20
|
+
- Press `Space` to select or unselect items.
|
|
21
|
+
- Press `Enter` to confirm the current action.
|
|
22
22
|
|
|
23
23
|
When input is piped or the terminal does not support raw key input, enter numbers separated by spaces:
|
|
24
24
|
|
|
25
|
+
```bash
|
|
26
|
+
printf "1\n1\n3 9\n" | npx oh-skillhub@latest
|
|
27
|
+
printf "2\n2\n1\n\n" | npx oh-skillhub@latest
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The old piped installer format is still accepted:
|
|
31
|
+
|
|
25
32
|
```bash
|
|
26
33
|
printf "1\n3 9\n" | npx oh-skillhub@latest
|
|
27
34
|
```
|
|
@@ -37,6 +44,7 @@ printf "1\n3 9\n" | npx oh-skillhub@latest
|
|
|
37
44
|
- Support `--scope user|project`.
|
|
38
45
|
- Support `--dry-run` install plans.
|
|
39
46
|
- Scan installed skills and clean selected skills with `clean`.
|
|
47
|
+
- Show clean as a first-class option in the default TUI.
|
|
40
48
|
- Move cleaned skills to `.oh-skillhub/trash` by default, with `--purge` for permanent deletion.
|
|
41
49
|
- Run a TUI matching the `skills/common/*` and `skills/domain/*` repository layout.
|
|
42
50
|
- Keep anonymous telemetry events in a local retry queue.
|
|
@@ -119,7 +127,7 @@ node bin/oh-skillhub.js install --domain arkui --agent all --scope user --dry-ru
|
|
|
119
127
|
|
|
120
128
|
- Uses a bundled manifest derived from the GitCode `release` branch examples.
|
|
121
129
|
- Does not yet refresh the remote GitCode manifest at runtime.
|
|
122
|
-
-
|
|
130
|
+
- Downloads and installs full skill directories for manifest entries, but does not yet support arbitrary remote skill discovery outside the bundled manifest.
|
|
123
131
|
- Telemetry collector upload is designed but not yet wired; events are queued locally.
|
|
124
132
|
|
|
125
133
|
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -37,10 +37,14 @@ const AGENT_CHOICES = [
|
|
|
37
37
|
{ agent: "opencode", label: "OpenCode", hint: "~/.config/opencode/skill" },
|
|
38
38
|
{ agent: "all", label: "All", hint: "Codex + Claude + OpenCode" },
|
|
39
39
|
];
|
|
40
|
+
const ACTION_CHOICES = [
|
|
41
|
+
{ action: "install", label: "Install skills", hint: "Choose OpenHarmony skill groups to install" },
|
|
42
|
+
{ action: "clean", label: "Clean installed skills", hint: "Scan an agent target and remove selected skills" },
|
|
43
|
+
];
|
|
40
44
|
|
|
41
45
|
async function main(argv = []) {
|
|
42
46
|
if (argv.length === 0) {
|
|
43
|
-
await
|
|
47
|
+
await runHome();
|
|
44
48
|
return;
|
|
45
49
|
}
|
|
46
50
|
if (argv[0] === "--help" || argv[0] === "-h") {
|
|
@@ -101,18 +105,52 @@ function parseArgs(args) {
|
|
|
101
105
|
return options;
|
|
102
106
|
}
|
|
103
107
|
|
|
108
|
+
async function runHome(input = process.stdin, output = process.stdout) {
|
|
109
|
+
if (input.isTTY && output.isTTY) {
|
|
110
|
+
const action = await runRawActionSelection(input, output);
|
|
111
|
+
if (action === "clean") {
|
|
112
|
+
await runClean({}, input, output);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await runInteractiveInstaller(input, output);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const answers = splitPromptAnswers(await readAll(input));
|
|
120
|
+
const hasActionAnswer = answers.length >= 3 && isSingleSelection(answers[0], ACTION_CHOICES.length);
|
|
121
|
+
if (!hasActionAnswer) {
|
|
122
|
+
await runInteractiveInstallerFromAnswers(answers, output);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
output.write(renderActionMenu());
|
|
127
|
+
output.write("Select action [1]: \n");
|
|
128
|
+
const action = ACTION_CHOICES[parseSingleSelection(answers[0], ACTION_CHOICES.length, 1)].action;
|
|
129
|
+
if (action === "clean") {
|
|
130
|
+
await runCleanWithAnswers({}, input, output, answers.slice(1));
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
await runInteractiveInstallerFromAnswers(answers.slice(1), output);
|
|
134
|
+
}
|
|
135
|
+
|
|
104
136
|
async function runClean(options, input = process.stdin, output = process.stdout) {
|
|
105
137
|
const scriptedAnswers = input.isTTY ? null : splitPromptAnswers(await readAll(input));
|
|
138
|
+
await runCleanWithAnswers(options, input, output, scriptedAnswers);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function runCleanWithAnswers(options, input, output, scriptedAnswers = null) {
|
|
142
|
+
const cleanOptions = { names: [], ...options };
|
|
143
|
+
const hasScriptedAnswers = Array.isArray(scriptedAnswers);
|
|
106
144
|
let answerIndex = 0;
|
|
107
145
|
const takeAnswer = () => {
|
|
108
|
-
if (!
|
|
146
|
+
if (!hasScriptedAnswers) return null;
|
|
109
147
|
const answer = scriptedAnswers[answerIndex] || "";
|
|
110
148
|
answerIndex += 1;
|
|
111
149
|
return answer;
|
|
112
150
|
};
|
|
113
|
-
let agent =
|
|
114
|
-
if (!agent && !
|
|
115
|
-
if (input.isTTY && output.isTTY) {
|
|
151
|
+
let agent = cleanOptions.agent;
|
|
152
|
+
if (!agent && !cleanOptions.target) {
|
|
153
|
+
if (!hasScriptedAnswers && input.isTTY && output.isTTY) {
|
|
116
154
|
agent = await runRawAgentSelection(input, output);
|
|
117
155
|
} else {
|
|
118
156
|
output.write(renderAgentMenu());
|
|
@@ -122,8 +160,8 @@ async function runClean(options, input = process.stdin, output = process.stdout)
|
|
|
122
160
|
}
|
|
123
161
|
const targets = resolveAgentTargets({
|
|
124
162
|
agent: agent || "codex",
|
|
125
|
-
scope:
|
|
126
|
-
target:
|
|
163
|
+
scope: cleanOptions.scope || "user",
|
|
164
|
+
target: cleanOptions.target,
|
|
127
165
|
cwd: process.cwd(),
|
|
128
166
|
homeDir: os.homedir(),
|
|
129
167
|
env: process.env,
|
|
@@ -135,8 +173,8 @@ async function runClean(options, input = process.stdin, output = process.stdout)
|
|
|
135
173
|
}
|
|
136
174
|
let selected;
|
|
137
175
|
let prefilledConfirmation = null;
|
|
138
|
-
if (!
|
|
139
|
-
if (input.isTTY) {
|
|
176
|
+
if (!cleanOptions.names.length && !cleanOptions.dryRun) {
|
|
177
|
+
if (!hasScriptedAnswers && input.isTTY && output.isTTY) {
|
|
140
178
|
selected = await promptCleanSelection(input, output, discovered);
|
|
141
179
|
} else {
|
|
142
180
|
output.write(renderCleanSelectionMenu(discovered));
|
|
@@ -145,16 +183,16 @@ async function runClean(options, input = process.stdin, output = process.stdout)
|
|
|
145
183
|
prefilledConfirmation = takeAnswer() || "";
|
|
146
184
|
}
|
|
147
185
|
} else {
|
|
148
|
-
selected = selectCleanSkills(discovered,
|
|
149
|
-
if (!input.isTTY) {
|
|
186
|
+
selected = selectCleanSkills(discovered, cleanOptions.names);
|
|
187
|
+
if (hasScriptedAnswers || !input.isTTY) {
|
|
150
188
|
prefilledConfirmation = takeAnswer();
|
|
151
189
|
}
|
|
152
190
|
}
|
|
153
191
|
if (!selected.length) {
|
|
154
|
-
throw new Error(`No installed skills matched: ${
|
|
192
|
+
throw new Error(`No installed skills matched: ${cleanOptions.names.join(", ")}`);
|
|
155
193
|
}
|
|
156
|
-
const plan = planClean(selected, { mode:
|
|
157
|
-
if (
|
|
194
|
+
const plan = planClean(selected, { mode: cleanOptions.purge ? "purge" : "trash" });
|
|
195
|
+
if (cleanOptions.dryRun) {
|
|
158
196
|
output.write(`${renderCleanPlan("Clean dry run", plan)}\n`);
|
|
159
197
|
return;
|
|
160
198
|
}
|
|
@@ -165,6 +203,10 @@ async function runClean(options, input = process.stdin, output = process.stdout)
|
|
|
165
203
|
}
|
|
166
204
|
|
|
167
205
|
async function promptCleanSelection(input, output, skills) {
|
|
206
|
+
if (input.isTTY && output.isTTY) {
|
|
207
|
+
const selectedIndexes = await runRawCleanSelection(input, output, skills);
|
|
208
|
+
return selectedIndexes.map((index) => skills[index]);
|
|
209
|
+
}
|
|
168
210
|
output.write(renderCleanSelectionMenu(skills));
|
|
169
211
|
const rl = readlinePromises.createInterface({ input, output });
|
|
170
212
|
try {
|
|
@@ -318,23 +360,32 @@ function renderInstall(options) {
|
|
|
318
360
|
}
|
|
319
361
|
|
|
320
362
|
async function runInteractiveInstaller(input = process.stdin, output = process.stdout) {
|
|
363
|
+
if (!(input.isTTY && output.isTTY)) {
|
|
364
|
+
await runInteractiveInstallerFromAnswers(splitPromptAnswers(await readAll(input)), output);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
321
368
|
const manifest = loadLocalManifest();
|
|
322
369
|
const choices = buildRepositoryChoices(manifest);
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
370
|
+
const agent = await runRawAgentSelection(input, output);
|
|
371
|
+
const selectedIndexes = await runRawTuiSelection(input, output, choices, agent);
|
|
372
|
+
await installInteractiveSelection(manifest, choices, agent, selectedIndexes, output);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function runInteractiveInstallerFromAnswers(answers, output = process.stdout) {
|
|
376
|
+
const manifest = loadLocalManifest();
|
|
377
|
+
const choices = buildRepositoryChoices(manifest);
|
|
378
|
+
const [agentAnswer, groupAnswer] = answers.length > 1 ? [answers[0], answers.slice(1).join(" ")] : ["", answers[0] || ""];
|
|
379
|
+
output.write(renderAgentMenu());
|
|
380
|
+
output.write("Select target [1]: \n");
|
|
381
|
+
const agent = AGENT_CHOICES[parseSingleSelection(agentAnswer, AGENT_CHOICES.length, 1)].agent;
|
|
382
|
+
output.write(renderTuiMenu(choices, agent));
|
|
383
|
+
output.write("Select groups [9]: \n");
|
|
384
|
+
const selectedIndexes = parseSelection(groupAnswer, choices.length, 9);
|
|
385
|
+
await installInteractiveSelection(manifest, choices, agent, selectedIndexes, output);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function installInteractiveSelection(manifest, choices, agent, selectedIndexes, output) {
|
|
338
389
|
const selectedChoices = selectedIndexes.map((index) => choices[index]);
|
|
339
390
|
const skills = selectSkillsForChoices(manifest, selectedChoices);
|
|
340
391
|
if (!skills.length) {
|
|
@@ -426,6 +477,26 @@ function renderAgentMenu() {
|
|
|
426
477
|
return `${lines.join("\n")}\n`;
|
|
427
478
|
}
|
|
428
479
|
|
|
480
|
+
function renderActionMenu() {
|
|
481
|
+
const width = 72;
|
|
482
|
+
const line = `+${"-".repeat(width - 2)}+`;
|
|
483
|
+
const lines = [
|
|
484
|
+
line,
|
|
485
|
+
`| ${padRight("OH SkillHub", width - 4)} |`,
|
|
486
|
+
`| ${padRight("OpenHarmony Skills Manager", width - 4)} |`,
|
|
487
|
+
line,
|
|
488
|
+
"",
|
|
489
|
+
"Choose action",
|
|
490
|
+
" Install new skills or clean existing skills from an agent target.",
|
|
491
|
+
"",
|
|
492
|
+
];
|
|
493
|
+
ACTION_CHOICES.forEach((choice, index) => {
|
|
494
|
+
lines.push(` ${index + 1}. [ ] ${choice.label.padEnd(22, " ")} ${choice.hint}`);
|
|
495
|
+
});
|
|
496
|
+
lines.push("");
|
|
497
|
+
return `${lines.join("\n")}\n`;
|
|
498
|
+
}
|
|
499
|
+
|
|
429
500
|
async function runPromptAgentSelection(input, output, existingInterface) {
|
|
430
501
|
output.write(renderAgentMenu());
|
|
431
502
|
const rl = existingInterface || readlinePromises.createInterface({ input, output });
|
|
@@ -549,6 +620,76 @@ function runRawAgentSelection(input, output) {
|
|
|
549
620
|
});
|
|
550
621
|
}
|
|
551
622
|
|
|
623
|
+
function runRawActionSelection(input, output) {
|
|
624
|
+
return new Promise((resolve, reject) => {
|
|
625
|
+
let cursor = 0;
|
|
626
|
+
const wasRaw = input.isRaw;
|
|
627
|
+
|
|
628
|
+
readline.emitKeypressEvents(input);
|
|
629
|
+
input.setRawMode(true);
|
|
630
|
+
|
|
631
|
+
function render() {
|
|
632
|
+
output.write("\x1b[2J\x1b[H");
|
|
633
|
+
output.write(renderRawActionMenu(cursor));
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function cleanup() {
|
|
637
|
+
input.removeListener("keypress", onKeypress);
|
|
638
|
+
input.setRawMode(Boolean(wasRaw));
|
|
639
|
+
output.write("\x1b[?25h");
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
function onKeypress(_str, key) {
|
|
643
|
+
if (key && key.ctrl && key.name === "c") {
|
|
644
|
+
cleanup();
|
|
645
|
+
reject(new Error("Cancelled."));
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
if (key && (key.name === "down" || key.name === "j")) {
|
|
649
|
+
cursor = (cursor + 1) % ACTION_CHOICES.length;
|
|
650
|
+
render();
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
if (key && (key.name === "up" || key.name === "k")) {
|
|
654
|
+
cursor = (cursor - 1 + ACTION_CHOICES.length) % ACTION_CHOICES.length;
|
|
655
|
+
render();
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
if (key && (key.name === "space" || key.name === "return")) {
|
|
659
|
+
cleanup();
|
|
660
|
+
resolve(ACTION_CHOICES[cursor].action);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
output.write("\x1b[?25l");
|
|
665
|
+
render();
|
|
666
|
+
input.on("keypress", onKeypress);
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function renderRawActionMenu(cursor) {
|
|
671
|
+
const width = 76;
|
|
672
|
+
const line = `+${"-".repeat(width - 2)}+`;
|
|
673
|
+
const lines = [
|
|
674
|
+
line,
|
|
675
|
+
rawHeaderLine("OH SkillHub", width, ANSI.cyan, ANSI.bold),
|
|
676
|
+
rawHeaderLine("OpenHarmony Skills Manager", width, ANSI.dim),
|
|
677
|
+
line,
|
|
678
|
+
"",
|
|
679
|
+
colorize("Choose action", ANSI.bold),
|
|
680
|
+
colorize(" Up/Down or j/k: move Space/Enter: select Ctrl+C: cancel", ANSI.dim),
|
|
681
|
+
"",
|
|
682
|
+
];
|
|
683
|
+
ACTION_CHOICES.forEach((choice, index) => {
|
|
684
|
+
const pointer = index === cursor ? ">" : " ";
|
|
685
|
+
const highlighted = index === cursor;
|
|
686
|
+
const row = `${pointer} ${rawCheckbox(highlighted, highlighted)} ${choice.label.padEnd(22, " ")} ${choice.hint}`;
|
|
687
|
+
lines.push(index === cursor ? colorize(row, ANSI.reverse, ANSI.bold) : row);
|
|
688
|
+
});
|
|
689
|
+
lines.push("");
|
|
690
|
+
return `${lines.join("\n")}\n`;
|
|
691
|
+
}
|
|
692
|
+
|
|
552
693
|
function renderRawAgentMenu(cursor) {
|
|
553
694
|
const width = 76;
|
|
554
695
|
const line = `+${"-".repeat(width - 2)}+`;
|
|
@@ -564,7 +705,8 @@ function renderRawAgentMenu(cursor) {
|
|
|
564
705
|
];
|
|
565
706
|
AGENT_CHOICES.forEach((choice, index) => {
|
|
566
707
|
const pointer = index === cursor ? ">" : " ";
|
|
567
|
-
const
|
|
708
|
+
const highlighted = index === cursor;
|
|
709
|
+
const row = `${pointer} ${rawCheckbox(highlighted, highlighted)} ${choice.label.padEnd(10, " ")} ${choice.hint}`;
|
|
568
710
|
lines.push(index === cursor ? colorize(row, ANSI.reverse, ANSI.bold) : row);
|
|
569
711
|
});
|
|
570
712
|
lines.push("");
|
|
@@ -602,6 +744,85 @@ function renderRawTuiMenu(choices, cursor, selected, agent = "codex") {
|
|
|
602
744
|
return `${lines.join("\n")}\n`;
|
|
603
745
|
}
|
|
604
746
|
|
|
747
|
+
function runRawCleanSelection(input, output, skills) {
|
|
748
|
+
return new Promise((resolve, reject) => {
|
|
749
|
+
let cursor = 0;
|
|
750
|
+
const selected = new Set([cursor]);
|
|
751
|
+
const wasRaw = input.isRaw;
|
|
752
|
+
|
|
753
|
+
readline.emitKeypressEvents(input);
|
|
754
|
+
input.setRawMode(true);
|
|
755
|
+
|
|
756
|
+
function render() {
|
|
757
|
+
output.write("\x1b[2J\x1b[H");
|
|
758
|
+
output.write(renderRawCleanSelectionMenu(skills, cursor, selected));
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
function cleanup() {
|
|
762
|
+
input.removeListener("keypress", onKeypress);
|
|
763
|
+
input.setRawMode(Boolean(wasRaw));
|
|
764
|
+
output.write("\x1b[?25h");
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
function onKeypress(_str, key) {
|
|
768
|
+
if (key && key.ctrl && key.name === "c") {
|
|
769
|
+
cleanup();
|
|
770
|
+
reject(new Error("Cancelled."));
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
if (key && (key.name === "down" || key.name === "j")) {
|
|
774
|
+
cursor = (cursor + 1) % skills.length;
|
|
775
|
+
render();
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
if (key && (key.name === "up" || key.name === "k")) {
|
|
779
|
+
cursor = (cursor - 1 + skills.length) % skills.length;
|
|
780
|
+
render();
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
if (key && key.name === "space") {
|
|
784
|
+
if (selected.has(cursor)) selected.delete(cursor);
|
|
785
|
+
else selected.add(cursor);
|
|
786
|
+
render();
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (key && key.name === "return") {
|
|
790
|
+
cleanup();
|
|
791
|
+
resolve(selected.size ? Array.from(selected).sort((a, b) => a - b) : [cursor]);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
output.write("\x1b[?25l");
|
|
796
|
+
render();
|
|
797
|
+
input.on("keypress", onKeypress);
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
function renderRawCleanSelectionMenu(skills, cursor, selected) {
|
|
802
|
+
const width = 92;
|
|
803
|
+
const line = `+${"-".repeat(width - 2)}+`;
|
|
804
|
+
const lines = [
|
|
805
|
+
line,
|
|
806
|
+
rawHeaderLine("OH SkillHub", width, ANSI.cyan, ANSI.bold),
|
|
807
|
+
rawHeaderLine("OpenHarmony Skills Cleaner", width, ANSI.dim),
|
|
808
|
+
line,
|
|
809
|
+
"",
|
|
810
|
+
colorize("Detected skills", ANSI.bold),
|
|
811
|
+
colorize(" Up/Down or j/k: move Space: select Enter: clean Ctrl+C: cancel", ANSI.dim),
|
|
812
|
+
"",
|
|
813
|
+
];
|
|
814
|
+
skills.forEach((skill, index) => {
|
|
815
|
+
const pointer = index === cursor ? ">" : " ";
|
|
816
|
+
const highlighted = index === cursor;
|
|
817
|
+
const checkbox = rawCheckbox(selected.has(index), highlighted);
|
|
818
|
+
const group = `${skill.domain || "unknown"}/${skill.stage || "unknown"}`;
|
|
819
|
+
const row = `${pointer} ${checkbox} ${skill.name.padEnd(36, " ")} ${skill.status.padEnd(9, " ")} ${group}`;
|
|
820
|
+
lines.push(highlighted ? colorize(row, ANSI.reverse, ANSI.bold) : row);
|
|
821
|
+
});
|
|
822
|
+
lines.push("");
|
|
823
|
+
return `${lines.join("\n")}\n`;
|
|
824
|
+
}
|
|
825
|
+
|
|
605
826
|
function colorize(value, ...codes) {
|
|
606
827
|
return `${codes.join("")}${value}${ANSI.reset}`;
|
|
607
828
|
}
|
|
@@ -661,6 +882,15 @@ function parseSingleSelection(answer, max, defaultNumber) {
|
|
|
661
882
|
return indexes[0];
|
|
662
883
|
}
|
|
663
884
|
|
|
885
|
+
function isSingleSelection(answer, max) {
|
|
886
|
+
try {
|
|
887
|
+
parseSingleSelection(answer, max, 1);
|
|
888
|
+
return true;
|
|
889
|
+
} catch {
|
|
890
|
+
return false;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
664
894
|
function selectSkillsForChoices(manifest, choices) {
|
|
665
895
|
const selected = new Map();
|
|
666
896
|
for (const choice of choices) {
|
|
@@ -774,7 +1004,7 @@ function helpText() {
|
|
|
774
1004
|
return [
|
|
775
1005
|
"oh-skillhub",
|
|
776
1006
|
"",
|
|
777
|
-
"Run without arguments to choose
|
|
1007
|
+
"Run without arguments to choose install or clean interactively.",
|
|
778
1008
|
"",
|
|
779
1009
|
"Commands:",
|
|
780
1010
|
" list [--domain <name>] [--stage <name>]",
|
|
@@ -798,8 +1028,11 @@ module.exports = {
|
|
|
798
1028
|
renderTelemetry,
|
|
799
1029
|
buildRepositoryChoices,
|
|
800
1030
|
renderAgentMenu,
|
|
1031
|
+
renderActionMenu,
|
|
801
1032
|
renderCleanPlan,
|
|
1033
|
+
renderRawActionMenu,
|
|
802
1034
|
renderRawAgentMenu,
|
|
1035
|
+
renderRawCleanSelectionMenu,
|
|
803
1036
|
parseSelection,
|
|
804
1037
|
renderRawTuiMenu,
|
|
805
1038
|
renderTuiMenu,
|