oh-skillhub 0.1.17 → 0.1.19
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/package.json +1 -1
- package/src/agents.js +12 -3
- package/src/cli.js +105 -22
package/package.json
CHANGED
package/src/agents.js
CHANGED
|
@@ -13,11 +13,10 @@ function resolveAgentTargets(options = {}) {
|
|
|
13
13
|
if (!["user", "project"].includes(scope)) {
|
|
14
14
|
throw new Error(`Unsupported scope "${scope}". Use user or project.`);
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
const agents = normalizeAgentSelection(agent);
|
|
17
|
+
if (agents.length > 1 && options.target) {
|
|
17
18
|
throw new Error("--agent all cannot be combined with --target");
|
|
18
19
|
}
|
|
19
|
-
|
|
20
|
-
const agents = agent === "all" ? SUPPORTED_AGENTS : [agent];
|
|
21
20
|
for (const item of agents) {
|
|
22
21
|
if (!SUPPORTED_AGENTS.includes(item)) {
|
|
23
22
|
throw new Error(`Unsupported agent "${item}". Use codex, claude, opencode, or all.`);
|
|
@@ -31,6 +30,16 @@ function resolveAgentTargets(options = {}) {
|
|
|
31
30
|
}));
|
|
32
31
|
}
|
|
33
32
|
|
|
33
|
+
function normalizeAgentSelection(agent) {
|
|
34
|
+
if (agent === "all") {
|
|
35
|
+
return SUPPORTED_AGENTS;
|
|
36
|
+
}
|
|
37
|
+
if (Array.isArray(agent)) {
|
|
38
|
+
return Array.from(new Set(agent));
|
|
39
|
+
}
|
|
40
|
+
return [agent];
|
|
41
|
+
}
|
|
42
|
+
|
|
34
43
|
function defaultDirFor(agent, scope, cwd, homeDir, env) {
|
|
35
44
|
if (scope === "project") {
|
|
36
45
|
if (agent === "codex") return path.join(cwd, ".codex", "skills");
|
package/src/cli.js
CHANGED
|
@@ -38,6 +38,8 @@ const AGENT_CHOICES = [
|
|
|
38
38
|
{ agent: "opencode", label: "OpenCode", hint: "~/.config/opencode/skill" },
|
|
39
39
|
{ agent: "all", label: "All", hint: "Codex + Claude + OpenCode" },
|
|
40
40
|
];
|
|
41
|
+
const INDIVIDUAL_AGENT_INDEXES = [0, 1, 2];
|
|
42
|
+
const ALL_AGENT_INDEX = 3;
|
|
41
43
|
const ACTION_CHOICES = [
|
|
42
44
|
{ action: "install", label: "Install skills", hint: "Choose OpenHarmony skill groups to install" },
|
|
43
45
|
{ action: "clean", label: "Clean installed skills", hint: "Scan an agent target and remove selected skills" },
|
|
@@ -156,7 +158,7 @@ async function runCleanWithAnswers(options, input, output, scriptedAnswers = nul
|
|
|
156
158
|
} else {
|
|
157
159
|
output.write(renderAgentMenu());
|
|
158
160
|
output.write("Select target [1]: \n");
|
|
159
|
-
agent =
|
|
161
|
+
agent = parseAgentSelection(takeAnswer() || "");
|
|
160
162
|
}
|
|
161
163
|
}
|
|
162
164
|
const targets = resolveAgentTargets({
|
|
@@ -379,7 +381,7 @@ async function runInteractiveInstallerFromAnswers(answers, output = process.stdo
|
|
|
379
381
|
const [agentAnswer, groupAnswer] = answers.length > 1 ? [answers[0], answers.slice(1).join(" ")] : ["", answers[0] || ""];
|
|
380
382
|
output.write(renderAgentMenu());
|
|
381
383
|
output.write("Select target [1]: \n");
|
|
382
|
-
const agent =
|
|
384
|
+
const agent = parseAgentSelection(agentAnswer);
|
|
383
385
|
output.write(renderTuiMenu(choices, agent));
|
|
384
386
|
output.write("Select groups [9]: \n");
|
|
385
387
|
const selectedIndexes = parseSelection(groupAnswer, choices.length, 9);
|
|
@@ -392,7 +394,7 @@ async function installInteractiveSelection(manifest, choices, agent, selectedInd
|
|
|
392
394
|
if (!skills.length) {
|
|
393
395
|
throw new Error("No skills matched the selected groups.");
|
|
394
396
|
}
|
|
395
|
-
output.write(`\nInstalling ${selectedChoices.map((choice) => choice.path).join(", ")} for ${agent}:user...\n`);
|
|
397
|
+
output.write(`\nInstalling ${selectedChoices.map((choice) => choice.path).join(", ")} for ${formatAgentSelection(agent)}:user...\n`);
|
|
396
398
|
const sourceRoot = await withSpinner(output, `Preparing skill source from ${manifest.source}#${manifest.ref}`, () =>
|
|
397
399
|
ensureSkillSourceRootAsync(manifest, { env: process.env, skills }),
|
|
398
400
|
);
|
|
@@ -469,7 +471,7 @@ function renderTuiMenu(choices, agent = "codex") {
|
|
|
469
471
|
line,
|
|
470
472
|
"",
|
|
471
473
|
"Target",
|
|
472
|
-
` Agent: ${agent}`,
|
|
474
|
+
` Agent: ${formatAgentSelection(agent)}`,
|
|
473
475
|
" Scope: user",
|
|
474
476
|
"",
|
|
475
477
|
"Choose skill groups",
|
|
@@ -556,8 +558,7 @@ function runRawTuiSelection(input, output, choices, agent = "codex") {
|
|
|
556
558
|
const selected = new Set([cursor]);
|
|
557
559
|
const wasRaw = input.isRaw;
|
|
558
560
|
|
|
559
|
-
|
|
560
|
-
input.setRawMode(true);
|
|
561
|
+
prepareRawInput(input);
|
|
561
562
|
|
|
562
563
|
function render() {
|
|
563
564
|
output.write("\x1b[2J\x1b[H");
|
|
@@ -566,7 +567,7 @@ function runRawTuiSelection(input, output, choices, agent = "codex") {
|
|
|
566
567
|
|
|
567
568
|
function cleanup() {
|
|
568
569
|
input.removeListener("keypress", onKeypress);
|
|
569
|
-
input
|
|
570
|
+
releaseRawInput(input, wasRaw);
|
|
570
571
|
output.write("\x1b[?25h");
|
|
571
572
|
}
|
|
572
573
|
|
|
@@ -607,11 +608,10 @@ function runRawTuiSelection(input, output, choices, agent = "codex") {
|
|
|
607
608
|
function runRawAgentSelection(input, output) {
|
|
608
609
|
return new Promise((resolve, reject) => {
|
|
609
610
|
let cursor = 0;
|
|
610
|
-
|
|
611
|
+
const selected = new Set([0]);
|
|
611
612
|
const wasRaw = input.isRaw;
|
|
612
613
|
|
|
613
|
-
|
|
614
|
-
input.setRawMode(true);
|
|
614
|
+
prepareRawInput(input);
|
|
615
615
|
|
|
616
616
|
function render() {
|
|
617
617
|
output.write("\x1b[2J\x1b[H");
|
|
@@ -620,7 +620,7 @@ function runRawAgentSelection(input, output) {
|
|
|
620
620
|
|
|
621
621
|
function cleanup() {
|
|
622
622
|
input.removeListener("keypress", onKeypress);
|
|
623
|
-
input
|
|
623
|
+
releaseRawInput(input, wasRaw);
|
|
624
624
|
output.write("\x1b[?25h");
|
|
625
625
|
}
|
|
626
626
|
|
|
@@ -641,13 +641,13 @@ function runRawAgentSelection(input, output) {
|
|
|
641
641
|
return;
|
|
642
642
|
}
|
|
643
643
|
if (key && key.name === "space") {
|
|
644
|
-
selected
|
|
644
|
+
toggleAgentSelection(selected, cursor);
|
|
645
645
|
render();
|
|
646
646
|
return;
|
|
647
647
|
}
|
|
648
648
|
if (key && key.name === "return") {
|
|
649
649
|
cleanup();
|
|
650
|
-
resolve(
|
|
650
|
+
resolve(agentSelectionFromIndexes(selected, cursor));
|
|
651
651
|
}
|
|
652
652
|
}
|
|
653
653
|
|
|
@@ -663,8 +663,7 @@ function runRawActionSelection(input, output) {
|
|
|
663
663
|
let selected = 0;
|
|
664
664
|
const wasRaw = input.isRaw;
|
|
665
665
|
|
|
666
|
-
|
|
667
|
-
input.setRawMode(true);
|
|
666
|
+
prepareRawInput(input);
|
|
668
667
|
|
|
669
668
|
function render() {
|
|
670
669
|
output.write("\x1b[2J\x1b[H");
|
|
@@ -673,7 +672,7 @@ function runRawActionSelection(input, output) {
|
|
|
673
672
|
|
|
674
673
|
function cleanup() {
|
|
675
674
|
input.removeListener("keypress", onKeypress);
|
|
676
|
-
input
|
|
675
|
+
releaseRawInput(input, wasRaw);
|
|
677
676
|
output.write("\x1b[?25h");
|
|
678
677
|
}
|
|
679
678
|
|
|
@@ -733,9 +732,27 @@ function renderRawActionMenu(cursor, selected = cursor) {
|
|
|
733
732
|
return `${lines.join("\n")}\n`;
|
|
734
733
|
}
|
|
735
734
|
|
|
736
|
-
function
|
|
735
|
+
function prepareRawInput(input) {
|
|
736
|
+
if (typeof input.on === "function" && typeof input.listenerCount === "function") {
|
|
737
|
+
readline.emitKeypressEvents(input);
|
|
738
|
+
}
|
|
739
|
+
input.setRawMode(true);
|
|
740
|
+
if (typeof input.resume === "function") {
|
|
741
|
+
input.resume();
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
function releaseRawInput(input, wasRaw) {
|
|
746
|
+
input.setRawMode(Boolean(wasRaw));
|
|
747
|
+
if (!wasRaw && typeof input.pause === "function") {
|
|
748
|
+
input.pause();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function renderRawAgentMenu(cursor, selected = new Set([cursor])) {
|
|
737
753
|
const width = 76;
|
|
738
754
|
const line = `+${"-".repeat(width - 2)}+`;
|
|
755
|
+
const selectedIndexes = normalizeAgentIndexes(selected);
|
|
739
756
|
const lines = [
|
|
740
757
|
line,
|
|
741
758
|
rawHeaderLine("OH SkillHub", width, ANSI.cyan, ANSI.bold),
|
|
@@ -749,7 +766,7 @@ function renderRawAgentMenu(cursor, selected = cursor) {
|
|
|
749
766
|
AGENT_CHOICES.forEach((choice, index) => {
|
|
750
767
|
const pointer = index === cursor ? ">" : " ";
|
|
751
768
|
const highlighted = index === cursor;
|
|
752
|
-
const row = `${pointer} ${rawCheckbox(index
|
|
769
|
+
const row = `${pointer} ${rawCheckbox(isAgentIndexSelected(selectedIndexes, index), highlighted)} ${choice.label.padEnd(10, " ")} ${choice.hint}`;
|
|
753
770
|
lines.push(index === cursor ? colorize(row, ANSI.reverse, ANSI.bold) : row);
|
|
754
771
|
});
|
|
755
772
|
lines.push("");
|
|
@@ -793,8 +810,7 @@ function runRawCleanSelection(input, output, skills) {
|
|
|
793
810
|
const selected = new Set([cursor]);
|
|
794
811
|
const wasRaw = input.isRaw;
|
|
795
812
|
|
|
796
|
-
|
|
797
|
-
input.setRawMode(true);
|
|
813
|
+
prepareRawInput(input);
|
|
798
814
|
|
|
799
815
|
function render() {
|
|
800
816
|
output.write("\x1b[2J\x1b[H");
|
|
@@ -803,7 +819,7 @@ function runRawCleanSelection(input, output, skills) {
|
|
|
803
819
|
|
|
804
820
|
function cleanup() {
|
|
805
821
|
input.removeListener("keypress", onKeypress);
|
|
806
|
-
input
|
|
822
|
+
releaseRawInput(input, wasRaw);
|
|
807
823
|
output.write("\x1b[?25h");
|
|
808
824
|
}
|
|
809
825
|
|
|
@@ -925,6 +941,15 @@ function parseSingleSelection(answer, max, defaultNumber) {
|
|
|
925
941
|
return indexes[0];
|
|
926
942
|
}
|
|
927
943
|
|
|
944
|
+
function parseAgentSelection(answer) {
|
|
945
|
+
const indexes = parseSelection(answer, AGENT_CHOICES.length, 1);
|
|
946
|
+
if (indexes.includes(ALL_AGENT_INDEX)) {
|
|
947
|
+
return "all";
|
|
948
|
+
}
|
|
949
|
+
const agents = indexes.map((index) => AGENT_CHOICES[index].agent);
|
|
950
|
+
return agents.length === 1 ? agents[0] : agents;
|
|
951
|
+
}
|
|
952
|
+
|
|
928
953
|
function isSingleSelection(answer, max) {
|
|
929
954
|
try {
|
|
930
955
|
parseSingleSelection(answer, max, 1);
|
|
@@ -934,6 +959,62 @@ function isSingleSelection(answer, max) {
|
|
|
934
959
|
}
|
|
935
960
|
}
|
|
936
961
|
|
|
962
|
+
function normalizeAgentIndexes(selected) {
|
|
963
|
+
const indexes = selected instanceof Set ? Array.from(selected) : [selected];
|
|
964
|
+
if (indexes.includes(ALL_AGENT_INDEX)) {
|
|
965
|
+
return new Set(INDIVIDUAL_AGENT_INDEXES);
|
|
966
|
+
}
|
|
967
|
+
return new Set(indexes.filter((index) => INDIVIDUAL_AGENT_INDEXES.includes(index)));
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function isAgentIndexSelected(selected, index) {
|
|
971
|
+
if (index === ALL_AGENT_INDEX) {
|
|
972
|
+
return INDIVIDUAL_AGENT_INDEXES.every((item) => selected.has(item));
|
|
973
|
+
}
|
|
974
|
+
return selected.has(index);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
function toggleAgentSelection(selected, cursor) {
|
|
978
|
+
if (cursor === ALL_AGENT_INDEX) {
|
|
979
|
+
if (INDIVIDUAL_AGENT_INDEXES.every((index) => selected.has(index))) {
|
|
980
|
+
selected.clear();
|
|
981
|
+
selected.add(0);
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
selected.clear();
|
|
985
|
+
for (const index of INDIVIDUAL_AGENT_INDEXES) {
|
|
986
|
+
selected.add(index);
|
|
987
|
+
}
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
if (selected.has(cursor) && selected.size > 1) {
|
|
991
|
+
selected.delete(cursor);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
selected.add(cursor);
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
function agentSelectionFromIndexes(selected, cursor = 0) {
|
|
998
|
+
const normalized = normalizeAgentIndexes(selected);
|
|
999
|
+
if (!normalized.size) {
|
|
1000
|
+
normalized.add(INDIVIDUAL_AGENT_INDEXES.includes(cursor) ? cursor : 0);
|
|
1001
|
+
}
|
|
1002
|
+
if (INDIVIDUAL_AGENT_INDEXES.every((index) => normalized.has(index))) {
|
|
1003
|
+
return "all";
|
|
1004
|
+
}
|
|
1005
|
+
const agents = Array.from(normalized)
|
|
1006
|
+
.sort((left, right) => left - right)
|
|
1007
|
+
.map((index) => AGENT_CHOICES[index].agent);
|
|
1008
|
+
return agents.length === 1 ? agents[0] : agents;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function formatAgentSelection(agent) {
|
|
1012
|
+
if (Array.isArray(agent)) {
|
|
1013
|
+
return agent.join(", ");
|
|
1014
|
+
}
|
|
1015
|
+
return agent;
|
|
1016
|
+
}
|
|
1017
|
+
|
|
937
1018
|
function selectSkillsForChoices(manifest, choices) {
|
|
938
1019
|
const selected = new Map();
|
|
939
1020
|
for (const choice of choices) {
|
|
@@ -987,7 +1068,7 @@ function renderInteractiveCompletion(skills, targetOptions, output = process.std
|
|
|
987
1068
|
renderStatusLine(
|
|
988
1069
|
"success",
|
|
989
1070
|
"INSTALL SUCCESS",
|
|
990
|
-
`Installed ${skills.length} skill(s) for ${targetOptions.agent}:${targetOptions.scope}.`,
|
|
1071
|
+
`Installed ${skills.length} skill(s) for ${formatAgentSelection(targetOptions.agent)}:${targetOptions.scope}.`,
|
|
991
1072
|
output,
|
|
992
1073
|
),
|
|
993
1074
|
].join("\n");
|
|
@@ -1088,6 +1169,8 @@ module.exports = {
|
|
|
1088
1169
|
renderRawAgentMenu,
|
|
1089
1170
|
renderRawCleanSelectionMenu,
|
|
1090
1171
|
parseSelection,
|
|
1172
|
+
prepareRawInput,
|
|
1173
|
+
releaseRawInput,
|
|
1091
1174
|
renderRawTuiMenu,
|
|
1092
1175
|
renderTuiMenu,
|
|
1093
1176
|
withSpinner,
|