oh-aicoding-tool 0.1.7 → 0.1.9
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/bin/cli.js +157 -53
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -4,9 +4,11 @@ import path from "node:path";
|
|
|
4
4
|
import { spawnSync } from "node:child_process";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
6
|
import { createInterface } from "node:readline/promises";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
8
|
-
const require = createRequire(import.meta.url);
|
|
9
|
-
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
const packageJsonPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
11
|
+
|
|
10
12
|
const colorEnabled = process.stdout.isTTY && process.env.NO_COLOR !== "1";
|
|
11
13
|
const ansi = (code) => (colorEnabled ? `\x1b[${code}m` : "");
|
|
12
14
|
const rgb = (r, g, b) => (colorEnabled ? `\x1b[38;2;${r};${g};${b}m` : "");
|
|
@@ -46,16 +48,22 @@ function renderHeader(subtitle = "Interactive setup") {
|
|
|
46
48
|
console.log(`${paint("Space", t.gold)} toggle ${paint("Enter", t.gold)} continue ${paint("Up/Down", t.gold)} move ${paint("q", t.gold)} exit`);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
function printHelp() {
|
|
50
|
-
console.log("oh-aicoding-tool");
|
|
51
|
-
console.log("");
|
|
52
|
-
console.log("Usage:");
|
|
53
|
-
console.log(" oh-aicoding-tool");
|
|
54
|
-
console.log(" oh-aicoding-tool
|
|
55
|
-
console.log(" oh-aicoding-tool
|
|
56
|
-
console.log(" oh-aicoding-tool
|
|
57
|
-
console.log(" oh-aicoding-tool report install
|
|
58
|
-
|
|
51
|
+
function printHelp() {
|
|
52
|
+
console.log("oh-aicoding-tool");
|
|
53
|
+
console.log("");
|
|
54
|
+
console.log("Usage:");
|
|
55
|
+
console.log(" oh-aicoding-tool");
|
|
56
|
+
console.log(" oh-aicoding-tool --version");
|
|
57
|
+
console.log(" oh-aicoding-tool debug-keys");
|
|
58
|
+
console.log(" oh-aicoding-tool langfuse [setup|check] [target]");
|
|
59
|
+
console.log(" oh-aicoding-tool report install opencode [--email user@company.com]");
|
|
60
|
+
console.log(" oh-aicoding-tool report install claude");
|
|
61
|
+
console.log(" oh-aicoding-tool report install both");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function readOwnPackage() {
|
|
65
|
+
return JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
66
|
+
}
|
|
59
67
|
|
|
60
68
|
function packageBinScript(packageName, preferredBin) {
|
|
61
69
|
const pkgPath = require.resolve(`${packageName}/package.json`);
|
|
@@ -108,26 +116,107 @@ function rawKeySeq(raw) {
|
|
|
108
116
|
return String(raw ?? "");
|
|
109
117
|
}
|
|
110
118
|
|
|
111
|
-
function
|
|
112
|
-
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (
|
|
119
|
-
if (
|
|
120
|
-
if (
|
|
121
|
-
|
|
119
|
+
function readKeyFromBuffer(buffer) {
|
|
120
|
+
if (!buffer) return { pending: true, rest: "" };
|
|
121
|
+
if (buffer === "\x1b" || buffer === "\x00" || buffer === "\xe0" || /^\x1b\[[0-9;?]*$/.test(buffer) || buffer === "\x1bO") {
|
|
122
|
+
return { pending: true, rest: buffer };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const csi = buffer.match(/^\x1b\[[0-9;?]*([A-Za-z~])/);
|
|
126
|
+
if (csi?.[1] === "A") return { key: { name: "up", sequence: csi[0] }, rest: buffer.slice(csi[0].length) };
|
|
127
|
+
if (csi?.[1] === "B") return { key: { name: "down", sequence: csi[0] }, rest: buffer.slice(csi[0].length) };
|
|
128
|
+
if (csi) return { key: { name: "", sequence: csi[0] }, rest: buffer.slice(csi[0].length) };
|
|
129
|
+
|
|
130
|
+
const ss3 = buffer.match(/^\x1bO([A-Za-z])/);
|
|
131
|
+
if (ss3?.[1] === "A") return { key: { name: "up", sequence: ss3[0] }, rest: buffer.slice(ss3[0].length) };
|
|
132
|
+
if (ss3?.[1] === "B") return { key: { name: "down", sequence: ss3[0] }, rest: buffer.slice(ss3[0].length) };
|
|
133
|
+
if (ss3) return { key: { name: "", sequence: ss3[0] }, rest: buffer.slice(ss3[0].length) };
|
|
134
|
+
|
|
135
|
+
const three = buffer.slice(0, 3);
|
|
136
|
+
if (three === "\x1b[A" || three === "\x1bOA") return { key: { name: "up", sequence: three }, rest: buffer.slice(3) };
|
|
137
|
+
if (three === "\x1b[B" || three === "\x1bOB") return { key: { name: "down", sequence: three }, rest: buffer.slice(3) };
|
|
138
|
+
|
|
139
|
+
const two = buffer.slice(0, 2);
|
|
140
|
+
if (two === "\x00H" || two === "\xe0H") return { key: { name: "up", sequence: two }, rest: buffer.slice(2) };
|
|
141
|
+
if (two === "\x00P" || two === "\xe0P") return { key: { name: "down", sequence: two }, rest: buffer.slice(2) };
|
|
142
|
+
if (two === "\r\n") return { key: { name: "enter", sequence: two }, rest: buffer.slice(2) };
|
|
143
|
+
|
|
144
|
+
const seq = buffer[0];
|
|
145
|
+
if (seq === "\x03") return { key: { name: "ctrl-c", sequence: seq }, rest: buffer.slice(1) };
|
|
146
|
+
if (seq === "\r" || seq === "\n") return { key: { name: "enter", sequence: seq }, rest: buffer.slice(1) };
|
|
147
|
+
if (seq === " ") return { key: { name: "space", sequence: seq }, rest: buffer.slice(1) };
|
|
148
|
+
if (seq === "\x1b") return { key: { name: "escape", sequence: seq }, rest: buffer.slice(1) };
|
|
149
|
+
if (/^[1-9]$/.test(seq)) return { key: { name: "number", number: Number.parseInt(seq, 10), sequence: seq }, rest: buffer.slice(1) };
|
|
150
|
+
return { key: { name: seq.toLowerCase(), sequence: seq }, rest: buffer.slice(1) };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function showControlChars(value) {
|
|
154
|
+
return value
|
|
155
|
+
.replaceAll("\x1b", "\\x1b")
|
|
156
|
+
.replaceAll("\x00", "\\x00")
|
|
157
|
+
.replaceAll("\xe0", "\\xe0")
|
|
158
|
+
.replaceAll("\r", "\\r")
|
|
159
|
+
.replaceAll("\n", "\\n")
|
|
160
|
+
.replaceAll("\x03", "\\x03");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function keyHex(value) {
|
|
164
|
+
return [...Buffer.from(value, "latin1")].map((byte) => byte.toString(16).padStart(2, "0")).join(" ");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function debugKeys() {
|
|
168
|
+
const pkg = readOwnPackage();
|
|
169
|
+
console.log(`oh-aicoding-tool ${pkg.version}`);
|
|
170
|
+
console.log(`node ${process.version} ${process.platform} ${process.arch}`);
|
|
171
|
+
console.log(`stdin.isTTY=${process.stdin.isTTY} stdout.isTTY=${process.stdout.isTTY} rawMode=${process.stdin.isRaw}`);
|
|
172
|
+
console.log(`TERM=${process.env.TERM || ""} WT_SESSION=${process.env.WT_SESSION || ""} TERM_PROGRAM=${process.env.TERM_PROGRAM || ""}`);
|
|
173
|
+
console.log("Press Up, Down, Space, Enter, then q to exit this debug view.");
|
|
174
|
+
|
|
175
|
+
if (!process.stdin.isTTY) return 1;
|
|
176
|
+
|
|
177
|
+
return await new Promise((resolve) => {
|
|
178
|
+
let keyBuffer = "";
|
|
179
|
+
const stdin = process.stdin;
|
|
180
|
+
|
|
181
|
+
function cleanup(code) {
|
|
182
|
+
stdin.off("data", onData);
|
|
183
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
184
|
+
stdin.pause();
|
|
185
|
+
resolve(code);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function onData(raw) {
|
|
189
|
+
const seq = rawKeySeq(raw);
|
|
190
|
+
console.log(`raw hex=[${keyHex(seq)}] text=${showControlChars(seq)}`);
|
|
191
|
+
keyBuffer += seq;
|
|
192
|
+
while (keyBuffer) {
|
|
193
|
+
const result = readKeyFromBuffer(keyBuffer);
|
|
194
|
+
if (result.pending) {
|
|
195
|
+
console.log(`pending buffer hex=[${keyHex(result.rest)}] text=${showControlChars(result.rest)}`);
|
|
196
|
+
keyBuffer = result.rest;
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
keyBuffer = result.rest;
|
|
200
|
+
const key = result.key;
|
|
201
|
+
console.log(`parsed name=${key.name || "<unknown>"} sequence=${showControlChars(key.sequence || "")}`);
|
|
202
|
+
if (key.name === "q" || key.name === "ctrl-c") return cleanup(0);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
stdin.setRawMode(true);
|
|
207
|
+
stdin.on("data", onData);
|
|
208
|
+
stdin.resume();
|
|
209
|
+
});
|
|
122
210
|
}
|
|
123
211
|
|
|
124
212
|
async function askMultiChoice(rl, title, choices, subtitle) {
|
|
125
213
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
126
214
|
rl.pause();
|
|
127
215
|
return await new Promise((resolve) => {
|
|
128
|
-
let index = 0;
|
|
129
|
-
|
|
130
|
-
const
|
|
216
|
+
let index = 0;
|
|
217
|
+
let keyBuffer = "";
|
|
218
|
+
const selected = new Set(choices.filter((choice) => choice.selected).map((choice) => choice.value));
|
|
219
|
+
const stdin = process.stdin;
|
|
131
220
|
|
|
132
221
|
function cleanup(value) {
|
|
133
222
|
stdin.off("data", onData);
|
|
@@ -146,25 +235,35 @@ async function askMultiChoice(rl, title, choices, subtitle) {
|
|
|
146
235
|
}
|
|
147
236
|
|
|
148
237
|
function onData(raw) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return;
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
238
|
+
keyBuffer += rawKeySeq(raw);
|
|
239
|
+
while (keyBuffer) {
|
|
240
|
+
const result = readKeyFromBuffer(keyBuffer);
|
|
241
|
+
if (result.pending) {
|
|
242
|
+
keyBuffer = result.rest;
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
keyBuffer = result.rest;
|
|
246
|
+
const key = result.key;
|
|
247
|
+
|
|
248
|
+
if (key.name === "ctrl-c") return cleanup([]);
|
|
249
|
+
if (key.name === "up") {
|
|
250
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
251
|
+
renderMultiChoice(title, choices, index, selected, subtitle);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (key.name === "down") {
|
|
255
|
+
index = (index + 1) % choices.length;
|
|
256
|
+
renderMultiChoice(title, choices, index, selected, subtitle);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (key.name === "q" || key.name === "escape") return cleanup([]);
|
|
260
|
+
if (key.name === "space") return toggle();
|
|
261
|
+
if (key.name === "enter") return cleanup([...selected]);
|
|
262
|
+
const number = key.name === "number" ? key.number : Number.NaN;
|
|
263
|
+
if (Number.isInteger(number) && choices[number - 1]) {
|
|
264
|
+
index = number - 1;
|
|
265
|
+
toggle();
|
|
266
|
+
}
|
|
168
267
|
}
|
|
169
268
|
}
|
|
170
269
|
|
|
@@ -251,14 +350,19 @@ async function interactive() {
|
|
|
251
350
|
}
|
|
252
351
|
}
|
|
253
352
|
|
|
254
|
-
async function main() {
|
|
255
|
-
const argv = process.argv.slice(2);
|
|
256
|
-
const [cmd, ...rest] = argv;
|
|
257
|
-
if (cmd === "--
|
|
258
|
-
|
|
259
|
-
return 0;
|
|
260
|
-
}
|
|
261
|
-
if (
|
|
353
|
+
async function main() {
|
|
354
|
+
const argv = process.argv.slice(2);
|
|
355
|
+
const [cmd, ...rest] = argv;
|
|
356
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
357
|
+
console.log(readOwnPackage().version);
|
|
358
|
+
return 0;
|
|
359
|
+
}
|
|
360
|
+
if (cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
361
|
+
printHelp();
|
|
362
|
+
return 0;
|
|
363
|
+
}
|
|
364
|
+
if (cmd === "debug-keys") return await debugKeys();
|
|
365
|
+
if (!cmd) return await interactive();
|
|
262
366
|
if (cmd === "langfuse") return runPackageBin("oh-langfuse", "oh-langfuse", rest);
|
|
263
367
|
if (cmd === "report") return runPackageBin("oh-aireport", "oh-aireport", rest);
|
|
264
368
|
if (cmd === "setup" || cmd === "check") return runPackageBin("oh-langfuse", "oh-langfuse", argv);
|