oh-aicoding-tool 0.1.8 → 0.1.10
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 +162 -81
- 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`);
|
|
@@ -110,10 +118,20 @@ function rawKeySeq(raw) {
|
|
|
110
118
|
|
|
111
119
|
function readKeyFromBuffer(buffer) {
|
|
112
120
|
if (!buffer) return { pending: true, rest: "" };
|
|
113
|
-
if (buffer === "\x1b" || buffer === "\
|
|
121
|
+
if (buffer === "\x1b" || buffer === "\x00" || buffer === "\xe0" || /^\x1b\[[0-9;?]*$/.test(buffer) || buffer === "\x1bO") {
|
|
114
122
|
return { pending: true, rest: buffer };
|
|
115
123
|
}
|
|
116
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
|
+
|
|
117
135
|
const three = buffer.slice(0, 3);
|
|
118
136
|
if (three === "\x1b[A" || three === "\x1bOA") return { key: { name: "up", sequence: three }, rest: buffer.slice(3) };
|
|
119
137
|
if (three === "\x1b[B" || three === "\x1bOB") return { key: { name: "down", sequence: three }, rest: buffer.slice(3) };
|
|
@@ -132,10 +150,68 @@ function readKeyFromBuffer(buffer) {
|
|
|
132
150
|
return { key: { name: seq.toLowerCase(), sequence: seq }, rest: buffer.slice(1) };
|
|
133
151
|
}
|
|
134
152
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function askMultiChoice(title, choices, subtitle) {
|
|
213
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
214
|
+
return await new Promise((resolve) => {
|
|
139
215
|
let index = 0;
|
|
140
216
|
let keyBuffer = "";
|
|
141
217
|
const selected = new Set(choices.filter((choice) => choice.selected).map((choice) => choice.value));
|
|
@@ -145,10 +221,9 @@ async function askMultiChoice(rl, title, choices, subtitle) {
|
|
|
145
221
|
stdin.off("data", onData);
|
|
146
222
|
if (stdin.isTTY) stdin.setRawMode(false);
|
|
147
223
|
stdin.pause();
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
224
|
+
clearScreen();
|
|
225
|
+
resolve(value);
|
|
226
|
+
}
|
|
152
227
|
|
|
153
228
|
function toggle() {
|
|
154
229
|
const value = choices[index].value;
|
|
@@ -196,10 +271,12 @@ async function askMultiChoice(rl, title, choices, subtitle) {
|
|
|
196
271
|
renderMultiChoice(title, choices, index, selected, subtitle);
|
|
197
272
|
});
|
|
198
273
|
}
|
|
199
|
-
|
|
200
|
-
choices.forEach((choice, index) => console.log(` ${index + 1}. ${choice.label}`));
|
|
201
|
-
const
|
|
202
|
-
|
|
274
|
+
|
|
275
|
+
choices.forEach((choice, index) => console.log(` ${index + 1}. ${choice.label}`));
|
|
276
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
277
|
+
const answer = await rl.question("Select numbers separated by comma > ");
|
|
278
|
+
rl.close();
|
|
279
|
+
return answer
|
|
203
280
|
.split(/[, ]+/)
|
|
204
281
|
.map((item) => Number.parseInt(item, 10) - 1)
|
|
205
282
|
.filter((index) => Number.isInteger(index) && choices[index])
|
|
@@ -224,63 +301,67 @@ async function askCompanyEmail(rl) {
|
|
|
224
301
|
}
|
|
225
302
|
}
|
|
226
303
|
|
|
227
|
-
async function interactive() {
|
|
228
|
-
if (!process.stdin.isTTY) {
|
|
229
|
-
printHelp();
|
|
230
|
-
return 0;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
304
|
+
async function interactive() {
|
|
305
|
+
if (!process.stdin.isTTY) {
|
|
306
|
+
printHelp();
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const tools = await askMultiChoice(
|
|
311
|
+
"Select tools to install or configure",
|
|
312
|
+
[
|
|
313
|
+
{ label: "Langfuse tracing", value: "langfuse", selected: true, description: "Configure Claude Code, OpenCode, or Codex tracing." },
|
|
314
|
+
{ label: "Issue report plugin", value: "report", selected: true, description: "Install /report-ai-issue and configure Company email." },
|
|
315
|
+
],
|
|
316
|
+
"Install menu"
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
let reportTarget = "";
|
|
320
|
+
let email = "";
|
|
321
|
+
if (tools.includes("report")) {
|
|
322
|
+
const targets = await askMultiChoice(
|
|
323
|
+
"Select issue report install targets",
|
|
324
|
+
[
|
|
325
|
+
{ label: "OpenCode", value: "opencode", selected: true, description: "Install OpenCode plugin and command." },
|
|
326
|
+
{ label: "Claude Code", value: "claude", selected: false, description: "Install Claude command and hook integration." },
|
|
327
|
+
],
|
|
328
|
+
"Issue report setup"
|
|
329
|
+
);
|
|
330
|
+
reportTarget = targets.length === 2 ? "both" : targets[0] || "";
|
|
331
|
+
if (reportTarget) {
|
|
332
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
333
|
+
try {
|
|
334
|
+
email = await askCompanyEmail(rl);
|
|
335
|
+
} finally {
|
|
336
|
+
rl.close();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
let code = 0;
|
|
342
|
+
if (tools.includes("langfuse")) code ||= runPackageBin("oh-langfuse", "oh-langfuse", ["setup"]);
|
|
343
|
+
if (tools.includes("report") && reportTarget) {
|
|
344
|
+
const args = ["install", reportTarget];
|
|
345
|
+
if (email) args.push("--email", email);
|
|
346
|
+
else args.push("--skip-email");
|
|
347
|
+
code ||= runPackageBin("oh-aireport", "oh-aireport", args);
|
|
348
|
+
}
|
|
349
|
+
return code;
|
|
350
|
+
}
|
|
275
351
|
|
|
276
|
-
async function main() {
|
|
277
|
-
const argv = process.argv.slice(2);
|
|
278
|
-
const [cmd, ...rest] = argv;
|
|
279
|
-
if (cmd === "--
|
|
280
|
-
|
|
281
|
-
return 0;
|
|
282
|
-
}
|
|
283
|
-
if (
|
|
352
|
+
async function main() {
|
|
353
|
+
const argv = process.argv.slice(2);
|
|
354
|
+
const [cmd, ...rest] = argv;
|
|
355
|
+
if (cmd === "--version" || cmd === "-v" || cmd === "version") {
|
|
356
|
+
console.log(readOwnPackage().version);
|
|
357
|
+
return 0;
|
|
358
|
+
}
|
|
359
|
+
if (cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
360
|
+
printHelp();
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
if (cmd === "debug-keys") return await debugKeys();
|
|
364
|
+
if (!cmd) return await interactive();
|
|
284
365
|
if (cmd === "langfuse") return runPackageBin("oh-langfuse", "oh-langfuse", rest);
|
|
285
366
|
if (cmd === "report") return runPackageBin("oh-aireport", "oh-aireport", rest);
|
|
286
367
|
if (cmd === "setup" || cmd === "check") return runPackageBin("oh-langfuse", "oh-langfuse", argv);
|