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.
Files changed (2) hide show
  1. package/bin/cli.js +162 -81
  2. 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 langfuse [setup|check] [target]");
55
- console.log(" oh-aicoding-tool report install opencode [--email user@company.com]");
56
- console.log(" oh-aicoding-tool report install claude");
57
- console.log(" oh-aicoding-tool report install both");
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 === "\x1b[" || buffer === "\x1bO" || buffer === "\x00" || buffer === "\xe0") {
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
- async function askMultiChoice(rl, title, choices, subtitle) {
136
- if (process.stdin.isTTY && process.stdout.isTTY) {
137
- rl.pause();
138
- return await new Promise((resolve) => {
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
- rl.resume();
149
- clearScreen();
150
- resolve(value);
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 answer = await rl.question("Select numbers separated by comma > ");
202
- return answer
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 rl = createInterface({ input: process.stdin, output: process.stdout });
234
- try {
235
- const tools = await askMultiChoice(
236
- rl,
237
- "Select tools to install or configure",
238
- [
239
- { label: "Langfuse tracing", value: "langfuse", selected: true, description: "Configure Claude Code, OpenCode, or Codex tracing." },
240
- { label: "Issue report plugin", value: "report", selected: true, description: "Install /report-ai-issue and configure Company email." },
241
- ],
242
- "Install menu"
243
- );
244
-
245
- let reportTarget = "";
246
- let email = "";
247
- if (tools.includes("report")) {
248
- const targets = await askMultiChoice(
249
- rl,
250
- "Select issue report install targets",
251
- [
252
- { label: "OpenCode", value: "opencode", selected: true, description: "Install OpenCode plugin and command." },
253
- { label: "Claude Code", value: "claude", selected: false, description: "Install Claude command and hook integration." },
254
- ],
255
- "Issue report setup"
256
- );
257
- reportTarget = targets.length === 2 ? "both" : targets[0] || "";
258
- if (reportTarget) email = await askCompanyEmail(rl);
259
- }
260
-
261
- rl.close();
262
- let code = 0;
263
- if (tools.includes("langfuse")) code ||= runPackageBin("oh-langfuse", "oh-langfuse", ["setup"]);
264
- if (tools.includes("report") && reportTarget) {
265
- const args = ["install", reportTarget];
266
- if (email) args.push("--email", email);
267
- else args.push("--skip-email");
268
- code ||= runPackageBin("oh-aireport", "oh-aireport", args);
269
- }
270
- return code;
271
- } finally {
272
- rl.close();
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 === "--help" || cmd === "-h" || cmd === "help") {
280
- printHelp();
281
- return 0;
282
- }
283
- if (!cmd) return await interactive();
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-aicoding-tool",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Interactive installer for AI coding tools: Langfuse tracing and oh-ai-report.",