@kisev/skills-opencode 2.2.3 → 2.3.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.
@@ -3,22 +3,41 @@ function parseKey(buffer) {
3
3
  const seq = buffer.toString();
4
4
  if (seq === "\r" || seq === "\n")
5
5
  return { name: "return", sequence: seq };
6
+ if (seq === " ")
7
+ return { name: "space", sequence: seq };
8
+ if (seq === "a" || seq === "A")
9
+ return { name: "all", sequence: seq };
10
+ if (seq === "n" || seq === "N")
11
+ return { name: "none", sequence: seq };
6
12
  if (seq === "\x03")
7
13
  return { name: "ctrl+c", sequence: seq };
8
14
  if (seq === "\x04")
9
15
  return { name: "ctrl+d", sequence: seq };
10
16
  if (seq === "\x1b" || seq === "\x1b\x1b" || seq === "\x1b\x1b\x1b")
11
17
  return { name: "escape", sequence: seq };
12
- if (seq === "\x1b[A" || seq === "\x1b[OA")
18
+ if (seq === "\x1b[A" || seq === "\x1bOA" || seq === "\x1b[OA")
13
19
  return { name: "up", sequence: seq };
14
- if (seq === "\x1b[B" || seq === "\x1b[OB")
20
+ if (seq === "\x1b[B" || seq === "\x1bOB" || seq === "\x1b[OB")
15
21
  return { name: "down", sequence: seq };
16
- if (seq === "\x1b[C" || seq === "\x1b[OC")
22
+ if (seq === "\x1b[C" || seq === "\x1bOC" || seq === "\x1b[OC")
17
23
  return { name: "right", sequence: seq };
18
- if (seq === "\x1b[D" || seq === "\x1b[OD")
24
+ if (seq === "\x1b[D" || seq === "\x1bOD" || seq === "\x1b[OD")
19
25
  return { name: "left", sequence: seq };
20
26
  return { name: "unknown", sequence: seq };
21
27
  }
28
+ function nextSequence(remaining) {
29
+ if (remaining.startsWith("\x1b[O"))
30
+ return remaining.slice(0, 4);
31
+ if (remaining.startsWith("\x1b[") || remaining.startsWith("\x1bO"))
32
+ return remaining.slice(0, 3);
33
+ return remaining[0];
34
+ }
35
+ function validateOptions(options) {
36
+ if (options.length === 0)
37
+ throw new InstallerError("invalid_input", "Interactive selector requires at least one option");
38
+ if (new Set(options).size !== options.length)
39
+ throw new InstallerError("invalid_input", "Interactive selector options must be unique");
40
+ }
22
41
  function clearLines(stderr, count) {
23
42
  for (let i = 0; i < count; i++) {
24
43
  stderr.write("\x1b[A\x1b[K");
@@ -28,10 +47,12 @@ export async function selectOption(label, options, stdin = process.stdin, stderr
28
47
  if (!stdin.isTTY || !stderr.isTTY) {
29
48
  throw new InstallerError("terminal_required", "This operation requires an interactive terminal");
30
49
  }
50
+ validateOptions(options);
31
51
  let selected = Math.max(0, Math.min(initial, options.length - 1));
32
- const lineCount = options.length + 1; // label + options
52
+ const lineCount = options.length + 2;
33
53
  function render() {
34
- stderr.write(`${label}\n`);
54
+ stderr.write(`? ${label}\n`);
55
+ stderr.write(" Up/Down move | Enter select | Esc cancel\n");
35
56
  for (let i = 0; i < options.length; i++) {
36
57
  const cursor = i === selected ? "> " : " ";
37
58
  stderr.write(`${cursor}${options[i]}\n`);
@@ -49,7 +70,7 @@ export async function selectOption(label, options, stdin = process.stdin, stderr
49
70
  function onData(data) {
50
71
  let remaining = data.toString();
51
72
  while (remaining) {
52
- const sequence = remaining.startsWith("\x1b[") ? remaining.slice(0, 3) : remaining[0];
73
+ const sequence = nextSequence(remaining);
53
74
  remaining = remaining.slice(sequence.length);
54
75
  const key = parseKey(Buffer.from(sequence));
55
76
  if (key.name === "ctrl+c" || key.name === "ctrl+d" || key.name === "escape") {
@@ -79,11 +100,102 @@ export async function selectOption(label, options, stdin = process.stdin, stderr
79
100
  stdin.on("data", onData);
80
101
  });
81
102
  }
103
+ export async function selectOptions(label, options, initialSelected = [], stdin = process.stdin, stderr = process.stderr) {
104
+ if (!stdin.isTTY || !stderr.isTTY) {
105
+ throw new InstallerError("terminal_required", "This operation requires an interactive terminal");
106
+ }
107
+ validateOptions(options);
108
+ const allowed = new Set(options);
109
+ for (const value of initialSelected) {
110
+ if (!allowed.has(value))
111
+ throw new InstallerError("invalid_input", `Unknown initial selector option: ${value}`);
112
+ }
113
+ const selected = new Set(initialSelected);
114
+ let active = 0;
115
+ const lineCount = options.length + 3;
116
+ function render() {
117
+ stderr.write(`? ${label}\n`);
118
+ stderr.write(" Up/Down move | Space toggle | A all | N none | Enter confirm | Esc cancel\n");
119
+ for (let index = 0; index < options.length; index += 1) {
120
+ const cursor = index === active ? "> " : " ";
121
+ const marker = selected.has(options[index]) ? "[x]" : "[ ]";
122
+ stderr.write(`${cursor}${marker} ${options[index]}\n`);
123
+ }
124
+ stderr.write(` Selected ${selected.size}/${options.length}\n`);
125
+ }
126
+ render();
127
+ return new Promise((resolve) => {
128
+ stdin.setRawMode(true);
129
+ stdin.resume();
130
+ function cleanup() {
131
+ stdin.setRawMode(false);
132
+ stdin.pause();
133
+ stdin.removeListener("data", onData);
134
+ }
135
+ function finish(value) {
136
+ cleanup();
137
+ clearLines(stderr, lineCount);
138
+ resolve(value);
139
+ }
140
+ function redraw(update) {
141
+ clearLines(stderr, lineCount);
142
+ update();
143
+ render();
144
+ }
145
+ function onData(data) {
146
+ let remaining = data.toString();
147
+ while (remaining) {
148
+ const sequence = nextSequence(remaining);
149
+ remaining = remaining.slice(sequence.length);
150
+ const key = parseKey(Buffer.from(sequence));
151
+ if (key.name === "ctrl+c" || key.name === "ctrl+d" || key.name === "escape") {
152
+ finish(null);
153
+ return;
154
+ }
155
+ if (key.name === "return") {
156
+ finish(options.filter((option) => selected.has(option)));
157
+ return;
158
+ }
159
+ if (key.name === "up") {
160
+ redraw(() => {
161
+ active = (active - 1 + options.length) % options.length;
162
+ });
163
+ }
164
+ else if (key.name === "down") {
165
+ redraw(() => {
166
+ active = (active + 1) % options.length;
167
+ });
168
+ }
169
+ else if (key.name === "space") {
170
+ redraw(() => {
171
+ const option = options[active];
172
+ if (selected.has(option))
173
+ selected.delete(option);
174
+ else
175
+ selected.add(option);
176
+ });
177
+ }
178
+ else if (key.name === "all") {
179
+ redraw(() => {
180
+ for (const option of options)
181
+ selected.add(option);
182
+ });
183
+ }
184
+ else if (key.name === "none") {
185
+ redraw(() => selected.clear());
186
+ }
187
+ }
188
+ }
189
+ stdin.on("data", onData);
190
+ });
191
+ }
82
192
  export async function promptText(label, stdin = process.stdin, stderr = process.stderr) {
83
193
  if (!stdin.isTTY || !stderr.isTTY) {
84
194
  throw new InstallerError("terminal_required", "This operation requires an interactive terminal");
85
195
  }
86
- stderr.write(`${label} `);
196
+ stderr.write(`? ${label}\n`);
197
+ stderr.write(" Type a value | Enter confirm | Esc cancel\n");
198
+ stderr.write("> ");
87
199
  return new Promise((resolve) => {
88
200
  let buffer = "";
89
201
  stdin.setRawMode(true);
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@kisev/skills-opencode",
3
- "version": "2.2.3",
3
+ "version": "2.3.0",
4
+ "skillsInstallerVersion": "1.5.23",
4
5
  "description": "OpenCode integration and opt-in installer for portable Agent Skills.",
5
6
  "license": "MIT",
6
7
  "repository": {