oh-aicoding-tool 0.1.1 → 0.1.4
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/README.md +79 -80
- package/bin/cli.js +257 -383
- package/package.json +28 -56
- package/CODEX_LANGFUSE_PLAN.md +0 -62
- package/bin/langfuse-cli.js +0 -718
- package/codex_langfuse_notify.py +0 -591
- package/langfuse_hook.py +0 -603
- package/opencode-ohai-report/.claude/commands/report-ai-issue.md +0 -60
- package/opencode-ohai-report/.opencode/commands/report-ai-issue.md +0 -30
- package/opencode-ohai-report/.opencode/plugins/oh-ai-report.ts +0 -569
- package/opencode-ohai-report/README.md +0 -45
- package/opencode-ohai-report/bin/cli.js +0 -421
- package/opencode-ohai-report/docs/opencode-ai-issue-collection-architecture.md +0 -313
- package/opencode-ohai-report/docs/opencode-ai-issue-collection-best-practices.md +0 -476
- package/opencode-ohai-report/docs/opencode-ai-issue-collection-phase1-summary.md +0 -405
- package/opencode-ohai-report/examples/issue_output.json +0 -4
- package/opencode-ohai-report/package.json +0 -40
- package/opencode-ohai-report/scripts/claude_report_hook.py +0 -257
- package/opencode-ohai-report/scripts/create_issue.py +0 -34
- package/opencode-ohai-report/scripts/install-claude-plugin.ps1 +0 -254
- package/opencode-ohai-report/scripts/install-opencode-plugin.ps1 +0 -264
- package/opencode-ohai-report/scripts/install-opencode-plugin.sh +0 -218
- package/opencode-ohai-report/scripts/merge-claude-settings.py +0 -99
- package/opencode-ohai-report/tools/ohai-report/README.md +0 -151
- package/opencode-ohai-report/tools/ohai-report/examples/issue-input.json +0 -26
- package/opencode-ohai-report/tools/ohai-report/ohai_report/__init__.py +0 -5
- package/opencode-ohai-report/tools/ohai-report/ohai_report/__main__.py +0 -9
- package/opencode-ohai-report/tools/ohai-report/ohai_report/cli.py +0 -319
- package/opencode-ohai-report/tools/ohai-report/ohai_report/git_context.py +0 -32
- package/opencode-ohai-report/tools/ohai-report/ohai_report/gitcode_defaults.py +0 -14
- package/opencode-ohai-report/tools/ohai-report/ohai_report/issue_markdown.py +0 -313
- package/opencode-ohai-report/tools/ohai-report/ohai_report/metadata.py +0 -360
- package/opencode-ohai-report/tools/ohai-report/ohai_report/observability/__init__.py +0 -1
- package/opencode-ohai-report/tools/ohai-report/ohai_report/observability/langfuse.py +0 -38
- package/opencode-ohai-report/tools/ohai-report/ohai_report/payload.py +0 -64
- package/opencode-ohai-report/tools/ohai-report/ohai_report/schema.py +0 -80
- package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/__init__.py +0 -1
- package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/base.py +0 -15
- package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/gitcode.py +0 -405
- package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/local.py +0 -21
- package/opencode-ohai-report/tools/ohai-report/ohai_report/sinks/webhook.py +0 -354
- package/opencode-ohai-report/tools/ohai-report/ohai_report/webhook_defaults.py +0 -9
- package/opencode-ohai-report/tools/ohai-report/ohai_report/workspace.py +0 -61
- package/opencode-ohai-report/tools/ohai-report/ohai_report.py +0 -10
- package/opencode-ohai-report/tools/ohai-report/schemas/report_issue.schema.json +0 -166
- package/scripts/codex-langfuse-check.mjs +0 -101
- package/scripts/codex-langfuse-setup.mjs +0 -181
- package/scripts/langfuse-check.mjs +0 -90
- package/scripts/langfuse-setup.mjs +0 -278
- package/scripts/opencode-langfuse-check.mjs +0 -94
- package/scripts/opencode-langfuse-run.mjs +0 -96
- package/scripts/opencode-langfuse-setup.mjs +0 -478
- package/scripts/resolve-opencode-cli.mjs +0 -58
- package/setup-langfuse.bat +0 -163
- package/setup-langfuse.sh +0 -130
package/bin/cli.js
CHANGED
|
@@ -1,383 +1,257 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
console.log(
|
|
52
|
-
console.log(
|
|
53
|
-
console.log(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
);
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
console.log("");
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
if (idx < choices.length - 1) console.log("");
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
async function
|
|
108
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
109
|
-
rl.pause();
|
|
110
|
-
return await new Promise((resolve) => {
|
|
111
|
-
let index = 0;
|
|
112
|
-
const
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
stdin.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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
|
-
const answer = (await rl.question(`${paint("Email", t.cyan)}${paint(hint, t.muted)} > `)).trim();
|
|
259
|
-
const value = answer || (emailIsValid(envEmail) ? envEmail.trim() : "");
|
|
260
|
-
if (!value) return "";
|
|
261
|
-
if (emailIsValid(value)) return value;
|
|
262
|
-
console.log(paint("Invalid email. Example: name@company.com", t.red));
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async function collectInstallPlan(rl) {
|
|
267
|
-
const tools = await askMultiChoice(
|
|
268
|
-
rl,
|
|
269
|
-
"Select tools to install or configure",
|
|
270
|
-
[
|
|
271
|
-
{
|
|
272
|
-
label: "Langfuse tracing",
|
|
273
|
-
value: "langfuse",
|
|
274
|
-
selected: true,
|
|
275
|
-
description: "Configure tracing for Claude Code, OpenCode, or Codex.",
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
label: "Issue report plugin",
|
|
279
|
-
value: "report",
|
|
280
|
-
selected: true,
|
|
281
|
-
description: "Install /report-ai-issue and configure company email.",
|
|
282
|
-
},
|
|
283
|
-
],
|
|
284
|
-
"Install menu"
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
if (!tools.length) return { tools: [], reportTarget: "", email: "" };
|
|
288
|
-
|
|
289
|
-
let reportTarget = "";
|
|
290
|
-
let email = "";
|
|
291
|
-
if (tools.includes("report")) {
|
|
292
|
-
const targets = await askMultiChoice(
|
|
293
|
-
rl,
|
|
294
|
-
"Select issue report install targets",
|
|
295
|
-
[
|
|
296
|
-
{
|
|
297
|
-
label: "OpenCode",
|
|
298
|
-
value: "opencode",
|
|
299
|
-
selected: true,
|
|
300
|
-
description: "Install the OpenCode plugin and /report-ai-issue command.",
|
|
301
|
-
},
|
|
302
|
-
{
|
|
303
|
-
label: "Claude Code",
|
|
304
|
-
value: "claude",
|
|
305
|
-
selected: false,
|
|
306
|
-
description: "Install the command and optional hook integration.",
|
|
307
|
-
},
|
|
308
|
-
],
|
|
309
|
-
"Issue report setup"
|
|
310
|
-
);
|
|
311
|
-
if (!targets.length) {
|
|
312
|
-
const withoutReport = tools.filter((item) => item !== "report");
|
|
313
|
-
return { tools: withoutReport, reportTarget: "", email: "" };
|
|
314
|
-
}
|
|
315
|
-
reportTarget = targets.length === 2 ? "both" : targets[0];
|
|
316
|
-
email = await askEmail(rl);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
return { tools, reportTarget, email };
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async function runInteractiveInstall() {
|
|
323
|
-
if (!process.stdin.isTTY) {
|
|
324
|
-
printHelp();
|
|
325
|
-
return 0;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
329
|
-
try {
|
|
330
|
-
const plan = await collectInstallPlan(rl);
|
|
331
|
-
rl.close();
|
|
332
|
-
|
|
333
|
-
let code = 0;
|
|
334
|
-
if (plan.tools.includes("langfuse")) {
|
|
335
|
-
code ||= runNodeScript(langfuseCli, ["setup"]);
|
|
336
|
-
}
|
|
337
|
-
if (plan.tools.includes("report") && plan.reportTarget) {
|
|
338
|
-
const args = ["install", plan.reportTarget];
|
|
339
|
-
if (plan.email) args.push("--email", plan.email);
|
|
340
|
-
else args.push("--skip-email");
|
|
341
|
-
code ||= runNodeScript(reportCli, args);
|
|
342
|
-
}
|
|
343
|
-
return code;
|
|
344
|
-
} finally {
|
|
345
|
-
rl.close();
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
async function main() {
|
|
350
|
-
const argv = process.argv.slice(2);
|
|
351
|
-
const [cmd, ...rest] = argv;
|
|
352
|
-
|
|
353
|
-
if (cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
354
|
-
printHelp();
|
|
355
|
-
return 0;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (!cmd) return await runInteractiveInstall();
|
|
359
|
-
|
|
360
|
-
if (cmd === "langfuse" || cmd === "trace" || cmd === "tracing") {
|
|
361
|
-
return runNodeScript(langfuseCli, rest);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (cmd === "report" || cmd === "ohai" || cmd === "issue") {
|
|
365
|
-
return runNodeScript(reportCli, rest);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
if (cmd === "setup" || cmd === "check") {
|
|
369
|
-
return runNodeScript(langfuseCli, argv);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
console.error(`Unknown command: ${cmd}`);
|
|
373
|
-
console.error("");
|
|
374
|
-
printHelp();
|
|
375
|
-
return 1;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
main()
|
|
379
|
-
.then((code) => process.exit(code))
|
|
380
|
-
.catch((err) => {
|
|
381
|
-
console.error(paint(err?.message || String(err), t.red));
|
|
382
|
-
process.exit(1);
|
|
383
|
-
});
|
|
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";
|
|
8
|
+
|
|
9
|
+
const require = createRequire(import.meta.url);
|
|
10
|
+
|
|
11
|
+
const colorEnabled = process.stdout.isTTY && process.env.NO_COLOR !== "1";
|
|
12
|
+
const ansi = (code) => (colorEnabled ? `\x1b[${code}m` : "");
|
|
13
|
+
const rgb = (r, g, b) => (colorEnabled ? `\x1b[38;2;${r};${g};${b}m` : "");
|
|
14
|
+
const bg = (r, g, b) => (colorEnabled ? `\x1b[48;2;${r};${g};${b}m` : "");
|
|
15
|
+
const t = {
|
|
16
|
+
reset: ansi(0),
|
|
17
|
+
bold: ansi(1),
|
|
18
|
+
cyan: rgb(92, 214, 255),
|
|
19
|
+
teal: rgb(92, 232, 188),
|
|
20
|
+
blue: rgb(132, 167, 255),
|
|
21
|
+
gold: rgb(245, 202, 116),
|
|
22
|
+
red: rgb(255, 117, 117),
|
|
23
|
+
muted: rgb(140, 151, 166),
|
|
24
|
+
panel: rgb(75, 86, 105),
|
|
25
|
+
selectedBg: bg(31, 53, 68),
|
|
26
|
+
selectedFg: rgb(218, 250, 255),
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function paint(value, ...styles) {
|
|
30
|
+
if (!colorEnabled) return String(value);
|
|
31
|
+
return `${styles.join("")}${value}${t.reset}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function clearScreen() {
|
|
35
|
+
if (process.stdout.isTTY) process.stdout.write("\x1b[2J\x1b[H");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function terminalWidth() {
|
|
39
|
+
return Math.max(72, Math.min(process.stdout.columns || 88, 104));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function renderHeader(subtitle = "Interactive setup") {
|
|
43
|
+
clearScreen();
|
|
44
|
+
console.log("");
|
|
45
|
+
console.log(`${paint("oh-aicoding-tool", t.bold, t.teal)} ${paint(subtitle, t.cyan)}`);
|
|
46
|
+
console.log(paint("-".repeat(terminalWidth()), t.panel));
|
|
47
|
+
console.log(`${paint("Space", t.gold)} toggle ${paint("Enter", t.gold)} continue ${paint("Up/Down", t.gold)} move ${paint("q", t.gold)} exit`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function printHelp() {
|
|
51
|
+
console.log("oh-aicoding-tool");
|
|
52
|
+
console.log("");
|
|
53
|
+
console.log("Usage:");
|
|
54
|
+
console.log(" oh-aicoding-tool");
|
|
55
|
+
console.log(" oh-aicoding-tool langfuse [setup|check] [target]");
|
|
56
|
+
console.log(" oh-aicoding-tool report install opencode [--email user@company.com]");
|
|
57
|
+
console.log(" oh-aicoding-tool report install claude");
|
|
58
|
+
console.log(" oh-aicoding-tool report install both");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function packageBinScript(packageName, preferredBin) {
|
|
62
|
+
const pkgPath = require.resolve(`${packageName}/package.json`);
|
|
63
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
64
|
+
const bin = typeof pkg.bin === "string" ? pkg.bin : pkg.bin?.[preferredBin] || Object.values(pkg.bin || {})[0];
|
|
65
|
+
if (!bin) throw new Error(`${packageName} does not expose a bin script.`);
|
|
66
|
+
return path.resolve(path.dirname(pkgPath), bin);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function runPackageBin(packageName, binName, args) {
|
|
70
|
+
let script;
|
|
71
|
+
try {
|
|
72
|
+
script = packageBinScript(packageName, binName);
|
|
73
|
+
} catch (err) {
|
|
74
|
+
console.error(paint(`Missing dependency: ${packageName}`, t.red));
|
|
75
|
+
console.error(paint("Run npm install, or publish/install the dependency package first.", t.muted));
|
|
76
|
+
console.error(paint(err?.message || String(err), t.muted));
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
const result = spawnSync(process.execPath, [script, ...args], {
|
|
80
|
+
stdio: "inherit",
|
|
81
|
+
windowsHide: true,
|
|
82
|
+
});
|
|
83
|
+
return result.status ?? (result.error ? 1 : 0);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function emailIsValid(value) {
|
|
87
|
+
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value || "").trim());
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function renderMultiChoice(title, choices, index, selected, subtitle) {
|
|
91
|
+
renderHeader(subtitle);
|
|
92
|
+
console.log("");
|
|
93
|
+
console.log(paint(title, t.bold, t.blue));
|
|
94
|
+
console.log("");
|
|
95
|
+
choices.forEach((choice, idx) => {
|
|
96
|
+
const focused = idx === index;
|
|
97
|
+
const checked = selected.has(choice.value);
|
|
98
|
+
const box = checked ? paint("[✓]", t.teal, t.bold) : paint("[ ]", t.muted);
|
|
99
|
+
const cursor = focused ? paint(">", t.teal, t.bold) : " ";
|
|
100
|
+
const label = focused ? paint(` ${choice.label} `, t.bold, t.selectedFg, t.selectedBg) : paint(choice.label, t.bold);
|
|
101
|
+
console.log(` ${cursor} ${box} ${label}`);
|
|
102
|
+
if (choice.description) console.log(` ${paint(choice.description, focused ? t.blue : t.muted)}`);
|
|
103
|
+
if (idx < choices.length - 1) console.log("");
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function askMultiChoice(rl, title, choices, subtitle) {
|
|
108
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
109
|
+
rl.pause();
|
|
110
|
+
return await new Promise((resolve) => {
|
|
111
|
+
let index = 0;
|
|
112
|
+
const selected = new Set(choices.filter((choice) => choice.selected).map((choice) => choice.value));
|
|
113
|
+
const stdin = process.stdin;
|
|
114
|
+
|
|
115
|
+
function cleanup(value) {
|
|
116
|
+
stdin.off("keypress", onKeypress);
|
|
117
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
118
|
+
stdin.pause();
|
|
119
|
+
rl.resume();
|
|
120
|
+
clearScreen();
|
|
121
|
+
resolve(value);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function toggle() {
|
|
125
|
+
const value = choices[index].value;
|
|
126
|
+
if (selected.has(value)) selected.delete(value);
|
|
127
|
+
else selected.add(value);
|
|
128
|
+
renderMultiChoice(title, choices, index, selected, subtitle);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function onKeypress(_, key = {}) {
|
|
132
|
+
if (key.ctrl && key.name === "c") return cleanup([]);
|
|
133
|
+
if (key.name === "q" || key.name === "escape") return cleanup([]);
|
|
134
|
+
if (key.name === "up") {
|
|
135
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
136
|
+
renderMultiChoice(title, choices, index, selected, subtitle);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (key.name === "down") {
|
|
140
|
+
index = (index + 1) % choices.length;
|
|
141
|
+
renderMultiChoice(title, choices, index, selected, subtitle);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (key.name === "space") return toggle();
|
|
145
|
+
if (key.name === "return" || key.name === "enter") return cleanup([...selected]);
|
|
146
|
+
const number = Number.parseInt(key.sequence, 10);
|
|
147
|
+
if (Number.isInteger(number) && choices[number - 1]) {
|
|
148
|
+
index = number - 1;
|
|
149
|
+
toggle();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
readline.emitKeypressEvents(stdin);
|
|
154
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
155
|
+
stdin.on("keypress", onKeypress);
|
|
156
|
+
stdin.resume();
|
|
157
|
+
renderMultiChoice(title, choices, index, selected, subtitle);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
choices.forEach((choice, index) => console.log(` ${index + 1}. ${choice.label}`));
|
|
162
|
+
const answer = await rl.question("Select numbers separated by comma > ");
|
|
163
|
+
return answer
|
|
164
|
+
.split(/[, ]+/)
|
|
165
|
+
.map((item) => Number.parseInt(item, 10) - 1)
|
|
166
|
+
.filter((index) => Number.isInteger(index) && choices[index])
|
|
167
|
+
.map((index) => choices[index].value);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async function askCompanyEmail(rl) {
|
|
171
|
+
const envEmail = process.env.OHAI_INSTALL_USER_EMAIL || "";
|
|
172
|
+
const hint = emailIsValid(envEmail) ? ` [${envEmail}]` : "";
|
|
173
|
+
renderHeader("Issue report setup");
|
|
174
|
+
console.log("");
|
|
175
|
+
console.log(paint("Company email", t.bold, t.blue));
|
|
176
|
+
console.log(paint("Examples: xxxxx@huawei.com or xxxxx@h-partners.com", t.muted));
|
|
177
|
+
console.log(paint("Press Enter to skip Company email setup for now.", t.muted));
|
|
178
|
+
console.log("");
|
|
179
|
+
while (true) {
|
|
180
|
+
const answer = (await rl.question(`${paint("Company email", t.cyan)}${paint(hint, t.muted)} > `)).trim();
|
|
181
|
+
const value = answer || (emailIsValid(envEmail) ? envEmail.trim() : "");
|
|
182
|
+
if (!value) return "";
|
|
183
|
+
if (emailIsValid(value)) return value;
|
|
184
|
+
console.log(paint("Invalid Company email. Examples: xxxxx@huawei.com or xxxxx@h-partners.com", t.red));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async function interactive() {
|
|
189
|
+
if (!process.stdin.isTTY) {
|
|
190
|
+
printHelp();
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
195
|
+
try {
|
|
196
|
+
const tools = await askMultiChoice(
|
|
197
|
+
rl,
|
|
198
|
+
"Select tools to install or configure",
|
|
199
|
+
[
|
|
200
|
+
{ label: "Langfuse tracing", value: "langfuse", selected: true, description: "Configure Claude Code, OpenCode, or Codex tracing." },
|
|
201
|
+
{ label: "Issue report plugin", value: "report", selected: true, description: "Install /report-ai-issue and configure Company email." },
|
|
202
|
+
],
|
|
203
|
+
"Install menu"
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
let reportTarget = "";
|
|
207
|
+
let email = "";
|
|
208
|
+
if (tools.includes("report")) {
|
|
209
|
+
const targets = await askMultiChoice(
|
|
210
|
+
rl,
|
|
211
|
+
"Select issue report install targets",
|
|
212
|
+
[
|
|
213
|
+
{ label: "OpenCode", value: "opencode", selected: true, description: "Install OpenCode plugin and command." },
|
|
214
|
+
{ label: "Claude Code", value: "claude", selected: false, description: "Install Claude command and hook integration." },
|
|
215
|
+
],
|
|
216
|
+
"Issue report setup"
|
|
217
|
+
);
|
|
218
|
+
reportTarget = targets.length === 2 ? "both" : targets[0] || "";
|
|
219
|
+
if (reportTarget) email = await askCompanyEmail(rl);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
rl.close();
|
|
223
|
+
let code = 0;
|
|
224
|
+
if (tools.includes("langfuse")) code ||= runPackageBin("oh-langfuse", "oh-langfuse", ["setup"]);
|
|
225
|
+
if (tools.includes("report") && reportTarget) {
|
|
226
|
+
const args = ["install", reportTarget];
|
|
227
|
+
if (email) args.push("--email", email);
|
|
228
|
+
else args.push("--skip-email");
|
|
229
|
+
code ||= runPackageBin("oh-aireport", "oh-aireport", args);
|
|
230
|
+
}
|
|
231
|
+
return code;
|
|
232
|
+
} finally {
|
|
233
|
+
rl.close();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function main() {
|
|
238
|
+
const argv = process.argv.slice(2);
|
|
239
|
+
const [cmd, ...rest] = argv;
|
|
240
|
+
if (cmd === "--help" || cmd === "-h" || cmd === "help") {
|
|
241
|
+
printHelp();
|
|
242
|
+
return 0;
|
|
243
|
+
}
|
|
244
|
+
if (!cmd) return await interactive();
|
|
245
|
+
if (cmd === "langfuse") return runPackageBin("oh-langfuse", "oh-langfuse", rest);
|
|
246
|
+
if (cmd === "report") return runPackageBin("oh-aireport", "oh-aireport", rest);
|
|
247
|
+
if (cmd === "setup" || cmd === "check") return runPackageBin("oh-langfuse", "oh-langfuse", argv);
|
|
248
|
+
printHelp();
|
|
249
|
+
return 1;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
main()
|
|
253
|
+
.then((code) => process.exit(code))
|
|
254
|
+
.catch((err) => {
|
|
255
|
+
console.error(paint(err?.message || String(err), t.red));
|
|
256
|
+
process.exit(1);
|
|
257
|
+
});
|