oh-aicoding-tool 0.1.6 → 0.1.7

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 +52 -72
  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,39 @@ 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 parseRawKey(raw) {
112
+ const seq = rawKeySeq(raw);
113
+ if (seq === "\x03") return { name: "ctrl-c", sequence: seq };
114
+ if (seq === "\x1b[A" || seq === "\x1bOA" || seq === "\x00H" || seq === "\xe0H") return { name: "up", sequence: seq };
115
+ if (seq === "\x1b[B" || seq === "\x1bOB" || seq === "\x00P" || seq === "\xe0P") return { name: "down", sequence: seq };
116
+ if (seq === "\r" || seq === "\n" || seq === "\r\n") return { name: "enter", sequence: seq };
117
+ if (seq === " ") return { name: "space", sequence: seq };
118
+ if (seq === "\x1b") return { name: "escape", sequence: seq };
119
+ if (/^[1-9]$/.test(seq)) return { name: "number", number: Number.parseInt(seq, 10), sequence: seq };
120
+ if (seq.length === 1) return { name: seq.toLowerCase(), sequence: seq };
121
+ return { name: "", sequence: seq };
122
+ }
123
+
124
+ async function askMultiChoice(rl, title, choices, subtitle) {
145
125
  if (process.stdin.isTTY && process.stdout.isTTY) {
146
126
  rl.pause();
147
127
  return await new Promise((resolve) => {
148
128
  let index = 0;
149
129
  const selected = new Set(choices.filter((choice) => choice.selected).map((choice) => choice.value));
150
130
  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();
131
+
132
+ function cleanup(value) {
133
+ stdin.off("data", onData);
134
+ if (stdin.isTTY) stdin.setRawMode(false);
135
+ stdin.pause();
136
+ rl.resume();
157
137
  clearScreen();
158
138
  resolve(value);
159
139
  }
@@ -162,37 +142,37 @@ async function askMultiChoice(rl, title, choices, subtitle) {
162
142
  const value = choices[index].value;
163
143
  if (selected.has(value)) selected.delete(value);
164
144
  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)) {
145
+ renderMultiChoice(title, choices, index, selected, subtitle);
146
+ }
147
+
148
+ function onData(raw) {
149
+ const key = parseRawKey(raw);
150
+ if (key.name === "ctrl-c") return cleanup([]);
151
+ if (key.name === "up") {
171
152
  index = (index - 1 + choices.length) % choices.length;
172
153
  renderMultiChoice(title, choices, index, selected, subtitle);
173
154
  return;
174
155
  }
175
- if (isDownKey(raw, key)) {
156
+ if (key.name === "down") {
176
157
  index = (index + 1) % choices.length;
177
158
  renderMultiChoice(title, choices, index, selected, subtitle);
178
159
  return;
179
160
  }
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);
161
+ if (key.name === "q" || key.name === "escape") return cleanup([]);
162
+ if (key.name === "space") return toggle();
163
+ if (key.name === "enter") return cleanup([...selected]);
164
+ const number = key.name === "number" ? key.number : Number.NaN;
184
165
  if (Number.isInteger(number) && choices[number - 1]) {
185
166
  index = number - 1;
186
167
  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
- });
168
+ }
169
+ }
170
+
171
+ if (stdin.isTTY) stdin.setRawMode(true);
172
+ stdin.on("data", onData);
173
+ stdin.resume();
174
+ renderMultiChoice(title, choices, index, selected, subtitle);
175
+ });
196
176
  }
197
177
 
198
178
  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.7",
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
  }