oh-aicoding-tool 0.1.6 → 0.1.8

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.
Files changed (2) hide show
  1. package/bin/cli.js +87 -85
  2. package/package.json +2 -2
package/bin/cli.js CHANGED
@@ -1,10 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import readline from "node:readline";
5
- import { spawnSync } from "node:child_process";
6
- import { createRequire } from "node:module";
7
- import { createInterface } from "node:readline/promises";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { createRequire } from "node:module";
6
+ import { createInterface } from "node:readline/promises";
8
7
 
9
8
  const require = createRequire(import.meta.url);
10
9
 
@@ -87,43 +86,6 @@ function emailIsValid(value) {
87
86
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value || "").trim());
88
87
  }
89
88
 
90
- function keySeq(raw, key = {}) {
91
- return String(key.sequence ?? raw ?? "");
92
- }
93
-
94
- function isCtrlC(raw, key = {}) {
95
- return Boolean(key.ctrl && key.name === "c") || keySeq(raw, key) === "\x03";
96
- }
97
-
98
- function isEscape(raw, key = {}) {
99
- const seq = keySeq(raw, key);
100
- return key.name === "escape" || seq === "\x1b";
101
- }
102
-
103
- function isUpKey(raw, key = {}) {
104
- const seq = keySeq(raw, key);
105
- return key.name === "up" || seq === "\x1b[A" || seq === "\x1bOA";
106
- }
107
-
108
- function isDownKey(raw, key = {}) {
109
- const seq = keySeq(raw, key);
110
- return key.name === "down" || seq === "\x1b[B" || seq === "\x1bOB";
111
- }
112
-
113
- function isEnterKey(raw, key = {}) {
114
- const seq = keySeq(raw, key);
115
- return key.name === "return" || key.name === "enter" || seq === "\r" || seq === "\n";
116
- }
117
-
118
- function isSpaceKey(raw, key = {}) {
119
- return key.name === "space" || keySeq(raw, key) === " ";
120
- }
121
-
122
- function numberKey(raw, key = {}) {
123
- const seq = keySeq(raw, key);
124
- return /^[1-9]$/.test(seq) ? Number.parseInt(seq, 10) : Number.NaN;
125
- }
126
-
127
89
  function renderMultiChoice(title, choices, index, selected, subtitle) {
128
90
  renderHeader(subtitle);
129
91
  console.log("");
@@ -139,21 +101,51 @@ function renderMultiChoice(title, choices, index, selected, subtitle) {
139
101
  if (choice.description) console.log(` ${paint(choice.description, focused ? t.blue : t.muted)}`);
140
102
  if (idx < choices.length - 1) console.log("");
141
103
  });
142
- }
143
-
144
- async function askMultiChoice(rl, title, choices, subtitle) {
104
+ }
105
+
106
+ function rawKeySeq(raw) {
107
+ if (Buffer.isBuffer(raw)) return raw.toString("latin1");
108
+ return String(raw ?? "");
109
+ }
110
+
111
+ function readKeyFromBuffer(buffer) {
112
+ if (!buffer) return { pending: true, rest: "" };
113
+ if (buffer === "\x1b" || buffer === "\x1b[" || buffer === "\x1bO" || buffer === "\x00" || buffer === "\xe0") {
114
+ return { pending: true, rest: buffer };
115
+ }
116
+
117
+ const three = buffer.slice(0, 3);
118
+ if (three === "\x1b[A" || three === "\x1bOA") return { key: { name: "up", sequence: three }, rest: buffer.slice(3) };
119
+ if (three === "\x1b[B" || three === "\x1bOB") return { key: { name: "down", sequence: three }, rest: buffer.slice(3) };
120
+
121
+ const two = buffer.slice(0, 2);
122
+ if (two === "\x00H" || two === "\xe0H") return { key: { name: "up", sequence: two }, rest: buffer.slice(2) };
123
+ if (two === "\x00P" || two === "\xe0P") return { key: { name: "down", sequence: two }, rest: buffer.slice(2) };
124
+ if (two === "\r\n") return { key: { name: "enter", sequence: two }, rest: buffer.slice(2) };
125
+
126
+ const seq = buffer[0];
127
+ if (seq === "\x03") return { key: { name: "ctrl-c", sequence: seq }, rest: buffer.slice(1) };
128
+ if (seq === "\r" || seq === "\n") return { key: { name: "enter", sequence: seq }, rest: buffer.slice(1) };
129
+ if (seq === " ") return { key: { name: "space", sequence: seq }, rest: buffer.slice(1) };
130
+ if (seq === "\x1b") return { key: { name: "escape", sequence: seq }, rest: buffer.slice(1) };
131
+ if (/^[1-9]$/.test(seq)) return { key: { name: "number", number: Number.parseInt(seq, 10), sequence: seq }, rest: buffer.slice(1) };
132
+ return { key: { name: seq.toLowerCase(), sequence: seq }, rest: buffer.slice(1) };
133
+ }
134
+
135
+ async function askMultiChoice(rl, title, choices, subtitle) {
145
136
  if (process.stdin.isTTY && process.stdout.isTTY) {
146
137
  rl.pause();
147
138
  return await new Promise((resolve) => {
148
- let index = 0;
149
- const selected = new Set(choices.filter((choice) => choice.selected).map((choice) => choice.value));
150
- const stdin = process.stdin;
151
-
152
- function cleanup(value) {
153
- stdin.off("keypress", onKeypress);
154
- if (stdin.isTTY) stdin.setRawMode(false);
155
- stdin.pause();
156
- rl.resume();
139
+ let index = 0;
140
+ let keyBuffer = "";
141
+ const selected = new Set(choices.filter((choice) => choice.selected).map((choice) => choice.value));
142
+ const stdin = process.stdin;
143
+
144
+ function cleanup(value) {
145
+ stdin.off("data", onData);
146
+ if (stdin.isTTY) stdin.setRawMode(false);
147
+ stdin.pause();
148
+ rl.resume();
157
149
  clearScreen();
158
150
  resolve(value);
159
151
  }
@@ -162,37 +154,47 @@ async function askMultiChoice(rl, title, choices, subtitle) {
162
154
  const value = choices[index].value;
163
155
  if (selected.has(value)) selected.delete(value);
164
156
  else selected.add(value);
165
- renderMultiChoice(title, choices, index, selected, subtitle);
166
- }
167
-
168
- function onKeypress(raw, key = {}) {
169
- if (isCtrlC(raw, key)) return cleanup([]);
170
- if (isUpKey(raw, key)) {
171
- index = (index - 1 + choices.length) % choices.length;
172
- renderMultiChoice(title, choices, index, selected, subtitle);
173
- return;
174
- }
175
- if (isDownKey(raw, key)) {
176
- index = (index + 1) % choices.length;
177
- renderMultiChoice(title, choices, index, selected, subtitle);
178
- return;
157
+ renderMultiChoice(title, choices, index, selected, subtitle);
158
+ }
159
+
160
+ function onData(raw) {
161
+ keyBuffer += rawKeySeq(raw);
162
+ while (keyBuffer) {
163
+ const result = readKeyFromBuffer(keyBuffer);
164
+ if (result.pending) {
165
+ keyBuffer = result.rest;
166
+ return;
167
+ }
168
+ keyBuffer = result.rest;
169
+ const key = result.key;
170
+
171
+ if (key.name === "ctrl-c") return cleanup([]);
172
+ if (key.name === "up") {
173
+ index = (index - 1 + choices.length) % choices.length;
174
+ renderMultiChoice(title, choices, index, selected, subtitle);
175
+ return;
176
+ }
177
+ if (key.name === "down") {
178
+ index = (index + 1) % choices.length;
179
+ renderMultiChoice(title, choices, index, selected, subtitle);
180
+ return;
181
+ }
182
+ if (key.name === "q" || key.name === "escape") return cleanup([]);
183
+ if (key.name === "space") return toggle();
184
+ if (key.name === "enter") return cleanup([...selected]);
185
+ const number = key.name === "number" ? key.number : Number.NaN;
186
+ if (Number.isInteger(number) && choices[number - 1]) {
187
+ index = number - 1;
188
+ toggle();
189
+ }
179
190
  }
180
- if (keySeq(raw, key).toLowerCase() === "q" || isEscape(raw, key)) return cleanup([]);
181
- if (isSpaceKey(raw, key)) return toggle();
182
- if (isEnterKey(raw, key)) return cleanup([...selected]);
183
- const number = numberKey(raw, key);
184
- if (Number.isInteger(number) && choices[number - 1]) {
185
- index = number - 1;
186
- toggle();
187
- }
188
- }
189
-
190
- readline.emitKeypressEvents(stdin);
191
- if (stdin.isTTY) stdin.setRawMode(true);
192
- stdin.on("keypress", onKeypress);
193
- stdin.resume();
194
- renderMultiChoice(title, choices, index, selected, subtitle);
195
- });
191
+ }
192
+
193
+ if (stdin.isTTY) stdin.setRawMode(true);
194
+ stdin.on("data", onData);
195
+ stdin.resume();
196
+ renderMultiChoice(title, choices, index, selected, subtitle);
197
+ });
196
198
  }
197
199
 
198
200
  choices.forEach((choice, index) => console.log(` ${index + 1}. ${choice.label}`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-aicoding-tool",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Interactive installer for AI coding tools: Langfuse tracing and oh-ai-report.",
@@ -25,7 +25,7 @@
25
25
  ],
26
26
  "license": "UNLICENSED",
27
27
  "dependencies": {
28
- "oh-langfuse": "^0.1.9",
28
+ "oh-langfuse": "^0.1.10",
29
29
  "oh-aireport": "^0.1.1"
30
30
  }
31
31
  }