oh-langfuse 0.1.52 → 0.1.54
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 +142 -142
- package/bin/cli.js +425 -425
- package/codex_langfuse_notify.py +517 -517
- package/langfuse_hook.py +580 -580
- package/package.json +1 -1
- package/scripts/auto-update-runtime.mjs +190 -190
- package/scripts/codex-langfuse-check.mjs +81 -81
- package/scripts/codex-langfuse-setup.mjs +331 -331
- package/scripts/langfuse-check.mjs +180 -180
- package/scripts/langfuse-setup.mjs +341 -341
- package/scripts/log-filter-utils.mjs +26 -26
- package/scripts/metrics-utils.mjs +377 -377
- package/scripts/opencode-langfuse-setup.mjs +969 -939
- package/scripts/real-self-verify.mjs +621 -578
- package/scripts/runtime-state-utils.mjs +53 -53
- package/scripts/update-langfuse-runtime.mjs +260 -260
- package/scripts/update-utils.mjs +73 -73
package/bin/cli.js
CHANGED
|
@@ -1,49 +1,49 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import fs from "fs";
|
|
3
|
-
import path from "path";
|
|
4
|
-
import readline from "readline";
|
|
5
|
-
import { fileURLToPath } from "url";
|
|
6
|
-
import { spawnSync } from "child_process";
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import readline from "readline";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { spawnSync } from "child_process";
|
|
7
7
|
|
|
8
8
|
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
9
9
|
const scriptsDir = path.join(rootDir, "scripts");
|
|
10
10
|
|
|
11
|
-
const DEFAULT_LANGFUSE_BASE_URL = "http://120.46.221.227:3000";
|
|
12
|
-
const DEFAULT_LANGFUSE_PUBLIC_KEY = "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
|
|
13
|
-
const DEFAULT_LANGFUSE_SECRET_KEY = "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
|
|
14
|
-
const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
|
|
15
|
-
const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
|
|
16
|
-
|
|
17
|
-
function nodeMajorVersion() {
|
|
18
|
-
const raw = process.versions && process.versions.node ? process.versions.node : "0.0.0";
|
|
19
|
-
return Number.parseInt(raw.split(".")[0], 10) || 0;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function assertSupportedNode() {
|
|
23
|
-
if (nodeMajorVersion() >= 16) return;
|
|
24
|
-
console.error("oh-langfuse requires Node.js >= 16.");
|
|
25
|
-
console.error(`Current Node.js: ${process.version}`);
|
|
26
|
-
console.error("Please upgrade Node.js, then run: npx oh-langfuse@latest");
|
|
27
|
-
process.exit(1);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function createPromptInterface(options) {
|
|
31
|
-
const rl = readline.createInterface(options);
|
|
32
|
-
return {
|
|
33
|
-
question(query) {
|
|
34
|
-
return new Promise((resolve) => rl.question(query, resolve));
|
|
35
|
-
},
|
|
36
|
-
pause() {
|
|
37
|
-
rl.pause();
|
|
38
|
-
},
|
|
39
|
-
resume() {
|
|
40
|
-
rl.resume();
|
|
41
|
-
},
|
|
42
|
-
close() {
|
|
43
|
-
rl.close();
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
11
|
+
const DEFAULT_LANGFUSE_BASE_URL = "http://120.46.221.227:3000";
|
|
12
|
+
const DEFAULT_LANGFUSE_PUBLIC_KEY = "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
|
|
13
|
+
const DEFAULT_LANGFUSE_SECRET_KEY = "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
|
|
14
|
+
const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
|
|
15
|
+
const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
|
|
16
|
+
|
|
17
|
+
function nodeMajorVersion() {
|
|
18
|
+
const raw = process.versions && process.versions.node ? process.versions.node : "0.0.0";
|
|
19
|
+
return Number.parseInt(raw.split(".")[0], 10) || 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function assertSupportedNode() {
|
|
23
|
+
if (nodeMajorVersion() >= 16) return;
|
|
24
|
+
console.error("oh-langfuse requires Node.js >= 16.");
|
|
25
|
+
console.error(`Current Node.js: ${process.version}`);
|
|
26
|
+
console.error("Please upgrade Node.js, then run: npx oh-langfuse@latest");
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function createPromptInterface(options) {
|
|
31
|
+
const rl = readline.createInterface(options);
|
|
32
|
+
return {
|
|
33
|
+
question(query) {
|
|
34
|
+
return new Promise((resolve) => rl.question(query, resolve));
|
|
35
|
+
},
|
|
36
|
+
pause() {
|
|
37
|
+
rl.pause();
|
|
38
|
+
},
|
|
39
|
+
resume() {
|
|
40
|
+
rl.resume();
|
|
41
|
+
},
|
|
42
|
+
close() {
|
|
43
|
+
rl.close();
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
47
|
|
|
48
48
|
const colorEnabled = process.stdout.isTTY && process.env.NO_COLOR !== "1";
|
|
49
49
|
const ansi = (code) => (colorEnabled ? `\x1b[${code}m` : "");
|
|
@@ -114,21 +114,21 @@ function mask(v) {
|
|
|
114
114
|
return `${v.slice(0, 4)}...${v.slice(-4)}`;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
function hasValue(v) {
|
|
118
|
-
return typeof v === "string" && v.trim().length > 0;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
function normalizeUserId(v) {
|
|
122
|
-
return String(v || "").trim();
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function isValidUserId(v) {
|
|
126
|
-
return USER_ID_PATTERN.test(normalizeUserId(v));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function userIdValidationMessage() {
|
|
130
|
-
return `User ID must match ${USER_ID_PATTERN_TEXT}, for example h00613222 or hwx1234567.`;
|
|
131
|
-
}
|
|
117
|
+
function hasValue(v) {
|
|
118
|
+
return typeof v === "string" && v.trim().length > 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function normalizeUserId(v) {
|
|
122
|
+
return String(v || "").trim();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function isValidUserId(v) {
|
|
126
|
+
return USER_ID_PATTERN.test(normalizeUserId(v));
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function userIdValidationMessage() {
|
|
130
|
+
return `User ID must match ${USER_ID_PATTERN_TEXT}, for example h00613222 or hwx1234567.`;
|
|
131
|
+
}
|
|
132
132
|
|
|
133
133
|
function scriptPath(name) {
|
|
134
134
|
return path.join(scriptsDir, name);
|
|
@@ -244,7 +244,7 @@ async function ensureEnvironment(rl, target, options) {
|
|
|
244
244
|
"Action Needed",
|
|
245
245
|
missing.map(([name, hint]) => `${paint(name, t.red)} ${paint(hint, t.muted)}`)
|
|
246
246
|
);
|
|
247
|
-
console.log("");
|
|
247
|
+
console.log("");
|
|
248
248
|
await rl.question(`${paint("Press Enter after installing the missing dependency, or Ctrl+C to exit.", t.gold)} `);
|
|
249
249
|
return false;
|
|
250
250
|
}
|
|
@@ -280,7 +280,7 @@ function labelValue(label, value, valueStyle = t.reset) {
|
|
|
280
280
|
return `${paint(label.padEnd(22), t.muted)} ${paint(value, valueStyle)}`;
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
-
function runNodeScript(name, args = [], { dryRun = false, quiet = false } = {}) {
|
|
283
|
+
function runNodeScript(name, args = [], { dryRun = false, quiet = false } = {}) {
|
|
284
284
|
const target = scriptPath(name);
|
|
285
285
|
const cmd = `${process.execPath} ${target} ${args.join(" ")}`.trim();
|
|
286
286
|
if (dryRun) {
|
|
@@ -288,31 +288,31 @@ function runNodeScript(name, args = [], { dryRun = false, quiet = false } = {})
|
|
|
288
288
|
return 0;
|
|
289
289
|
}
|
|
290
290
|
console.log("");
|
|
291
|
-
if (!quiet) console.log(paint("Running installer...", t.bold, t.teal));
|
|
292
|
-
if (!quiet) console.log(paint("─".repeat(Math.min(terminalWidth(), 64)), t.panel));
|
|
291
|
+
if (!quiet) console.log(paint("Running installer...", t.bold, t.teal));
|
|
292
|
+
if (!quiet) console.log(paint("─".repeat(Math.min(terminalWidth(), 64)), t.panel));
|
|
293
293
|
const r = spawnSync(process.execPath, [target, ...args], { stdio: "inherit" });
|
|
294
|
-
return r.status != null ? r.status : r.error ? 1 : 0;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
async function askText(rl, label, { defaultValue = "", required = false, validate = null, invalidMessage = "" } = {}) {
|
|
298
|
-
while (true) {
|
|
299
|
-
const suffix = defaultValue ? paint(` ${defaultValue}`, t.muted) : "";
|
|
300
|
-
const answer = (await rl.question(`${paint(label, t.cyan)}${suffix}\n${paint(">", t.teal)} `)).trim();
|
|
301
|
-
const value = answer || defaultValue;
|
|
302
|
-
if (required && !hasValue(value)) {
|
|
303
|
-
console.log(paint("This value is required.", t.red));
|
|
304
|
-
continue;
|
|
305
|
-
}
|
|
306
|
-
if (validate && hasValue(value) && !validate(value)) {
|
|
307
|
-
console.log(paint(invalidMessage || "Invalid value.", t.red));
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
if (!required || hasValue(value)) return value;
|
|
311
|
-
console.log(paint("This value is required.", t.red));
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
async function askYesNo(rl, label, { defaultValue = false } = {}) {
|
|
294
|
+
return r.status != null ? r.status : r.error ? 1 : 0;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function askText(rl, label, { defaultValue = "", required = false, validate = null, invalidMessage = "" } = {}) {
|
|
298
|
+
while (true) {
|
|
299
|
+
const suffix = defaultValue ? paint(` ${defaultValue}`, t.muted) : "";
|
|
300
|
+
const answer = (await rl.question(`${paint(label, t.cyan)}${suffix}\n${paint(">", t.teal)} `)).trim();
|
|
301
|
+
const value = answer || defaultValue;
|
|
302
|
+
if (required && !hasValue(value)) {
|
|
303
|
+
console.log(paint("This value is required.", t.red));
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (validate && hasValue(value) && !validate(value)) {
|
|
307
|
+
console.log(paint(invalidMessage || "Invalid value.", t.red));
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
if (!required || hasValue(value)) return value;
|
|
311
|
+
console.log(paint("This value is required.", t.red));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function askYesNo(rl, label, { defaultValue = false } = {}) {
|
|
316
316
|
const hint = defaultValue ? "Y/n" : "y/N";
|
|
317
317
|
while (true) {
|
|
318
318
|
const answer = (await rl.question(`${paint(label, t.cyan)} ${paint(`(${hint})`, t.muted)} `)).trim().toLowerCase();
|
|
@@ -320,28 +320,28 @@ async function askYesNo(rl, label, { defaultValue = false } = {}) {
|
|
|
320
320
|
if (["y", "yes"].includes(answer)) return true;
|
|
321
321
|
if (["n", "no"].includes(answer)) return false;
|
|
322
322
|
console.log(paint("Please answer y or n.", t.red));
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function rawKeySeq(raw) {
|
|
327
|
-
if (Buffer.isBuffer(raw)) return raw.toString("latin1");
|
|
328
|
-
return String(raw == null ? "" : raw);
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function parseRawKey(raw) {
|
|
332
|
-
const seq = rawKeySeq(raw);
|
|
333
|
-
if (seq === "\x03") return { name: "ctrl-c", sequence: seq };
|
|
334
|
-
if (seq === "\x1b[A" || seq === "\x1bOA" || seq === "\x00H" || seq === "\xe0H") return { name: "up", sequence: seq };
|
|
335
|
-
if (seq === "\x1b[B" || seq === "\x1bOB" || seq === "\x00P" || seq === "\xe0P") return { name: "down", sequence: seq };
|
|
336
|
-
if (seq === "\r" || seq === "\n" || seq === "\r\n") return { name: "enter", sequence: seq };
|
|
337
|
-
if (seq === " ") return { name: "space", sequence: seq };
|
|
338
|
-
if (seq === "\x1b") return { name: "escape", sequence: seq };
|
|
339
|
-
if (/^[1-9]$/.test(seq)) return { name: "number", number: Number.parseInt(seq, 10), sequence: seq };
|
|
340
|
-
if (seq.length === 1) return { name: seq.toLowerCase(), sequence: seq };
|
|
341
|
-
return { name: "", sequence: seq };
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
function renderChoiceScreen(label, choices, index, options = {}) {
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function rawKeySeq(raw) {
|
|
327
|
+
if (Buffer.isBuffer(raw)) return raw.toString("latin1");
|
|
328
|
+
return String(raw == null ? "" : raw);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function parseRawKey(raw) {
|
|
332
|
+
const seq = rawKeySeq(raw);
|
|
333
|
+
if (seq === "\x03") return { name: "ctrl-c", sequence: seq };
|
|
334
|
+
if (seq === "\x1b[A" || seq === "\x1bOA" || seq === "\x00H" || seq === "\xe0H") return { name: "up", sequence: seq };
|
|
335
|
+
if (seq === "\x1b[B" || seq === "\x1bOB" || seq === "\x00P" || seq === "\xe0P") return { name: "down", sequence: seq };
|
|
336
|
+
if (seq === "\r" || seq === "\n" || seq === "\r\n") return { name: "enter", sequence: seq };
|
|
337
|
+
if (seq === " ") return { name: "space", sequence: seq };
|
|
338
|
+
if (seq === "\x1b") return { name: "escape", sequence: seq };
|
|
339
|
+
if (/^[1-9]$/.test(seq)) return { name: "number", number: Number.parseInt(seq, 10), sequence: seq };
|
|
340
|
+
if (seq.length === 1) return { name: seq.toLowerCase(), sequence: seq };
|
|
341
|
+
return { name: "", sequence: seq };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function renderChoiceScreen(label, choices, index, options = {}) {
|
|
345
345
|
clearScreen();
|
|
346
346
|
renderBrand(options);
|
|
347
347
|
console.log("");
|
|
@@ -362,47 +362,47 @@ function renderChoiceScreen(label, choices, index, options = {}) {
|
|
|
362
362
|
});
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
-
async function askChoice(rl, label, choices, options = {}) {
|
|
366
|
-
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
367
|
-
rl.pause();
|
|
368
|
-
return await new Promise((resolve) => {
|
|
369
|
-
let index = 0;
|
|
365
|
+
async function askChoice(rl, label, choices, options = {}) {
|
|
366
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
367
|
+
rl.pause();
|
|
368
|
+
return await new Promise((resolve) => {
|
|
369
|
+
let index = 0;
|
|
370
370
|
const stdin = process.stdin;
|
|
371
|
-
|
|
372
|
-
function cleanup(value) {
|
|
373
|
-
stdin.off("data", onData);
|
|
374
|
-
if (stdin.isTTY) stdin.setRawMode(false);
|
|
375
|
-
stdin.pause();
|
|
376
|
-
rl.resume();
|
|
377
|
-
clearScreen();
|
|
378
|
-
resolve(value);
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
function onData(raw) {
|
|
382
|
-
const key = parseRawKey(raw);
|
|
383
|
-
if (key.name === "ctrl-c") return cleanup("exit");
|
|
384
|
-
if (key.name === "up") {
|
|
385
|
-
index = (index - 1 + choices.length) % choices.length;
|
|
386
|
-
renderChoiceScreen(label, choices, index, options);
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
if (key.name === "down") {
|
|
390
|
-
index = (index + 1) % choices.length;
|
|
391
|
-
renderChoiceScreen(label, choices, index, options);
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
if (key.name === "q" || key.name === "escape") return cleanup("exit");
|
|
395
|
-
if (key.name === "enter") return cleanup(choices[index].value);
|
|
396
|
-
const num = key.name === "number" ? key.number : Number.NaN;
|
|
397
|
-
if (Number.isInteger(num) && choices[num - 1]) return cleanup(choices[num - 1].value);
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
if (stdin.isTTY) stdin.setRawMode(true);
|
|
401
|
-
stdin.on("data", onData);
|
|
402
|
-
stdin.resume();
|
|
403
|
-
renderChoiceScreen(label, choices, index, options);
|
|
404
|
-
});
|
|
405
|
-
}
|
|
371
|
+
|
|
372
|
+
function cleanup(value) {
|
|
373
|
+
stdin.off("data", onData);
|
|
374
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
375
|
+
stdin.pause();
|
|
376
|
+
rl.resume();
|
|
377
|
+
clearScreen();
|
|
378
|
+
resolve(value);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function onData(raw) {
|
|
382
|
+
const key = parseRawKey(raw);
|
|
383
|
+
if (key.name === "ctrl-c") return cleanup("exit");
|
|
384
|
+
if (key.name === "up") {
|
|
385
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
386
|
+
renderChoiceScreen(label, choices, index, options);
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (key.name === "down") {
|
|
390
|
+
index = (index + 1) % choices.length;
|
|
391
|
+
renderChoiceScreen(label, choices, index, options);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (key.name === "q" || key.name === "escape") return cleanup("exit");
|
|
395
|
+
if (key.name === "enter") return cleanup(choices[index].value);
|
|
396
|
+
const num = key.name === "number" ? key.number : Number.NaN;
|
|
397
|
+
if (Number.isInteger(num) && choices[num - 1]) return cleanup(choices[num - 1].value);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
401
|
+
stdin.on("data", onData);
|
|
402
|
+
stdin.resume();
|
|
403
|
+
renderChoiceScreen(label, choices, index, options);
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
406
|
|
|
407
407
|
console.log("");
|
|
408
408
|
console.log(label);
|
|
@@ -446,52 +446,52 @@ async function askMultiChoice(rl, label, choices, options = {}) {
|
|
|
446
446
|
let index = 0;
|
|
447
447
|
const selected = new Set(choices.filter((choice) => choice.selected).map((choice) => choice.value));
|
|
448
448
|
const stdin = process.stdin;
|
|
449
|
-
|
|
450
|
-
function cleanup(value) {
|
|
451
|
-
stdin.off("data", onData);
|
|
452
|
-
if (stdin.isTTY) stdin.setRawMode(false);
|
|
453
|
-
stdin.pause();
|
|
454
|
-
rl.resume();
|
|
455
|
-
clearScreen();
|
|
456
|
-
resolve(value);
|
|
449
|
+
|
|
450
|
+
function cleanup(value) {
|
|
451
|
+
stdin.off("data", onData);
|
|
452
|
+
if (stdin.isTTY) stdin.setRawMode(false);
|
|
453
|
+
stdin.pause();
|
|
454
|
+
rl.resume();
|
|
455
|
+
clearScreen();
|
|
456
|
+
resolve(value);
|
|
457
457
|
}
|
|
458
458
|
|
|
459
459
|
function toggle() {
|
|
460
460
|
const value = choices[index].value;
|
|
461
461
|
if (selected.has(value)) selected.delete(value);
|
|
462
462
|
else selected.add(value);
|
|
463
|
-
renderMultiChoiceScreen(label, choices, index, selected, options);
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
function onData(raw) {
|
|
467
|
-
const key = parseRawKey(raw);
|
|
468
|
-
if (key.name === "ctrl-c") return cleanup([]);
|
|
469
|
-
if (key.name === "up") {
|
|
470
|
-
index = (index - 1 + choices.length) % choices.length;
|
|
471
|
-
renderMultiChoiceScreen(label, choices, index, selected, options);
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
if (key.name === "down") {
|
|
475
|
-
index = (index + 1) % choices.length;
|
|
476
|
-
renderMultiChoiceScreen(label, choices, index, selected, options);
|
|
477
|
-
return;
|
|
478
|
-
}
|
|
479
|
-
if (key.name === "q" || key.name === "escape") return cleanup([]);
|
|
480
|
-
if (key.name === "space") return toggle();
|
|
481
|
-
if (key.name === "enter") return cleanup([...selected]);
|
|
482
|
-
const num = key.name === "number" ? key.number : Number.NaN;
|
|
483
|
-
if (Number.isInteger(num) && choices[num - 1]) {
|
|
484
|
-
index = num - 1;
|
|
485
|
-
toggle();
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (stdin.isTTY) stdin.setRawMode(true);
|
|
490
|
-
stdin.on("data", onData);
|
|
491
|
-
stdin.resume();
|
|
492
|
-
renderMultiChoiceScreen(label, choices, index, selected, options);
|
|
493
|
-
});
|
|
494
|
-
}
|
|
463
|
+
renderMultiChoiceScreen(label, choices, index, selected, options);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function onData(raw) {
|
|
467
|
+
const key = parseRawKey(raw);
|
|
468
|
+
if (key.name === "ctrl-c") return cleanup([]);
|
|
469
|
+
if (key.name === "up") {
|
|
470
|
+
index = (index - 1 + choices.length) % choices.length;
|
|
471
|
+
renderMultiChoiceScreen(label, choices, index, selected, options);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (key.name === "down") {
|
|
475
|
+
index = (index + 1) % choices.length;
|
|
476
|
+
renderMultiChoiceScreen(label, choices, index, selected, options);
|
|
477
|
+
return;
|
|
478
|
+
}
|
|
479
|
+
if (key.name === "q" || key.name === "escape") return cleanup([]);
|
|
480
|
+
if (key.name === "space") return toggle();
|
|
481
|
+
if (key.name === "enter") return cleanup([...selected]);
|
|
482
|
+
const num = key.name === "number" ? key.number : Number.NaN;
|
|
483
|
+
if (Number.isInteger(num) && choices[num - 1]) {
|
|
484
|
+
index = num - 1;
|
|
485
|
+
toggle();
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (stdin.isTTY) stdin.setRawMode(true);
|
|
490
|
+
stdin.on("data", onData);
|
|
491
|
+
stdin.resume();
|
|
492
|
+
renderMultiChoiceScreen(label, choices, index, selected, options);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
495
|
|
|
496
496
|
console.log("");
|
|
497
497
|
console.log(label);
|
|
@@ -504,81 +504,81 @@ async function askMultiChoice(rl, label, choices, options = {}) {
|
|
|
504
504
|
.map((idx) => choices[idx].value);
|
|
505
505
|
}
|
|
506
506
|
|
|
507
|
-
function langfuseConfig(overrides = {}) {
|
|
508
|
-
const config = {
|
|
509
|
-
baseUrl: envDefault("LANGFUSE_BASEURL", envDefault("LANGFUSE_HOST", DEFAULT_LANGFUSE_BASE_URL)),
|
|
510
|
-
publicKey: envDefault("LANGFUSE_PUBLIC_KEY", DEFAULT_LANGFUSE_PUBLIC_KEY),
|
|
511
|
-
secretKey: envDefault("LANGFUSE_SECRET_KEY", DEFAULT_LANGFUSE_SECRET_KEY),
|
|
512
|
-
userId: ""
|
|
513
|
-
};
|
|
514
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
515
|
-
if (hasValue(value)) config[key] = value;
|
|
516
|
-
}
|
|
517
|
-
return config;
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
async function collectLangfuseConfig(rl, { requireUserId = false, overrides = {} } = {}) {
|
|
521
|
-
const config = langfuseConfig(overrides);
|
|
522
|
-
renderSection("Langfuse Target", [
|
|
523
|
-
labelValue("Base URL", config.baseUrl, t.teal),
|
|
524
|
-
labelValue("Public Key", config.publicKey, t.blue),
|
|
525
|
-
labelValue("Secret Key", "configured", t.teal)
|
|
526
|
-
]);
|
|
527
|
-
if (hasValue(config.userId)) {
|
|
528
|
-
config.userId = normalizeUserId(config.userId);
|
|
529
|
-
if (!isValidUserId(config.userId)) {
|
|
530
|
-
throw new Error(userIdValidationMessage());
|
|
531
|
-
}
|
|
532
|
-
if (requireUserId) return config;
|
|
533
|
-
}
|
|
534
|
-
config.userId = await askText(rl, "User ID / employee number, for example h00613222", {
|
|
535
|
-
defaultValue: "",
|
|
536
|
-
required: requireUserId,
|
|
537
|
-
validate: isValidUserId,
|
|
538
|
-
invalidMessage: userIdValidationMessage()
|
|
539
|
-
});
|
|
507
|
+
function langfuseConfig(overrides = {}) {
|
|
508
|
+
const config = {
|
|
509
|
+
baseUrl: envDefault("LANGFUSE_BASEURL", envDefault("LANGFUSE_HOST", DEFAULT_LANGFUSE_BASE_URL)),
|
|
510
|
+
publicKey: envDefault("LANGFUSE_PUBLIC_KEY", DEFAULT_LANGFUSE_PUBLIC_KEY),
|
|
511
|
+
secretKey: envDefault("LANGFUSE_SECRET_KEY", DEFAULT_LANGFUSE_SECRET_KEY),
|
|
512
|
+
userId: ""
|
|
513
|
+
};
|
|
514
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
515
|
+
if (hasValue(value)) config[key] = value;
|
|
516
|
+
}
|
|
517
|
+
return config;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async function collectLangfuseConfig(rl, { requireUserId = false, overrides = {} } = {}) {
|
|
521
|
+
const config = langfuseConfig(overrides);
|
|
522
|
+
renderSection("Langfuse Target", [
|
|
523
|
+
labelValue("Base URL", config.baseUrl, t.teal),
|
|
524
|
+
labelValue("Public Key", config.publicKey, t.blue),
|
|
525
|
+
labelValue("Secret Key", "configured", t.teal)
|
|
526
|
+
]);
|
|
527
|
+
if (hasValue(config.userId)) {
|
|
528
|
+
config.userId = normalizeUserId(config.userId);
|
|
529
|
+
if (!isValidUserId(config.userId)) {
|
|
530
|
+
throw new Error(userIdValidationMessage());
|
|
531
|
+
}
|
|
532
|
+
if (requireUserId) return config;
|
|
533
|
+
}
|
|
534
|
+
config.userId = await askText(rl, "User ID / employee number, for example h00613222", {
|
|
535
|
+
defaultValue: "",
|
|
536
|
+
required: requireUserId,
|
|
537
|
+
validate: isValidUserId,
|
|
538
|
+
invalidMessage: userIdValidationMessage()
|
|
539
|
+
});
|
|
540
540
|
return config;
|
|
541
541
|
}
|
|
542
542
|
|
|
543
|
-
async function collectSharedConfig(rl, options) {
|
|
544
|
-
clearScreen();
|
|
545
|
-
renderBrand(options);
|
|
546
|
-
return await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides });
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
function commonLangfuseArgs(config) {
|
|
550
|
-
return [
|
|
551
|
-
`--langfuseBaseUrl=${config.baseUrl}`,
|
|
552
|
-
`--publicKey=${config.publicKey}`,
|
|
553
|
-
`--secretKey=${config.secretKey}`,
|
|
554
|
-
...(hasValue(config.userId) ? [`--userId=${config.userId}`] : [])
|
|
555
|
-
];
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
function optionalInstallerArgs(options) {
|
|
559
|
-
return [...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : [])];
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
async function confirmAction(rl, title, rows, options) {
|
|
543
|
+
async function collectSharedConfig(rl, options) {
|
|
544
|
+
clearScreen();
|
|
545
|
+
renderBrand(options);
|
|
546
|
+
return await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides });
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function commonLangfuseArgs(config) {
|
|
550
|
+
return [
|
|
551
|
+
`--langfuseBaseUrl=${config.baseUrl}`,
|
|
552
|
+
`--publicKey=${config.publicKey}`,
|
|
553
|
+
`--secretKey=${config.secretKey}`,
|
|
554
|
+
...(hasValue(config.userId) ? [`--userId=${config.userId}`] : [])
|
|
555
|
+
];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function optionalInstallerArgs(options) {
|
|
559
|
+
return [...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : [])];
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
async function confirmAction(rl, title, rows, options) {
|
|
563
563
|
clearScreen();
|
|
564
564
|
renderBrand(options);
|
|
565
565
|
renderSection(title, rows);
|
|
566
|
-
if (options.dryRun || options.yes) return true;
|
|
566
|
+
if (options.dryRun || options.yes) return true;
|
|
567
567
|
console.log("");
|
|
568
|
-
return await askYesNo(rl, "Continue with these changes", { defaultValue: false });
|
|
568
|
+
return await askYesNo(rl, "Continue with these changes", { defaultValue: false });
|
|
569
569
|
}
|
|
570
570
|
|
|
571
|
-
async function setupClaude(rl, options) {
|
|
572
|
-
if (!options.dryRun) {
|
|
573
|
-
while (!(await ensureEnvironment(rl, "claude", options))) {}
|
|
574
|
-
}
|
|
575
|
-
clearScreen();
|
|
576
|
-
renderBrand(options);
|
|
577
|
-
const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
|
|
571
|
+
async function setupClaude(rl, options) {
|
|
572
|
+
if (!options.dryRun) {
|
|
573
|
+
while (!(await ensureEnvironment(rl, "claude", options))) {}
|
|
574
|
+
}
|
|
575
|
+
clearScreen();
|
|
576
|
+
renderBrand(options);
|
|
577
|
+
const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
|
|
578
578
|
const defaultHook = path.join(rootDir, "langfuse_hook.py");
|
|
579
579
|
const pyPath = fs.existsSync(defaultHook) ? defaultHook : path.join(rootDir, "langfuse_hook.py");
|
|
580
580
|
|
|
581
|
-
const args = [...commonLangfuseArgs(config), ...optionalInstallerArgs(options), `--pyPath=${pyPath}`];
|
|
581
|
+
const args = [...commonLangfuseArgs(config), ...optionalInstallerArgs(options), `--pyPath=${pyPath}`];
|
|
582
582
|
const ok = await confirmAction(
|
|
583
583
|
rl,
|
|
584
584
|
"Claude Code Langfuse Setup",
|
|
@@ -589,29 +589,29 @@ async function setupClaude(rl, options) {
|
|
|
589
589
|
labelValue("Python package", "langfuse", t.gold)
|
|
590
590
|
],
|
|
591
591
|
options
|
|
592
|
-
);
|
|
593
|
-
if (!ok) return 0;
|
|
594
|
-
const code = runNodeScript("langfuse-setup.mjs", args, options);
|
|
595
|
-
if (code === 0 && !options.dryRun && !options.skipCheck) return checkClaude(options, { clear: false });
|
|
596
|
-
return code;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
async function setupOpenCode(rl, options) {
|
|
600
|
-
if (!options.dryRun) {
|
|
601
|
-
while (!(await ensureEnvironment(rl, "opencode", options))) {}
|
|
602
|
-
}
|
|
603
|
-
clearScreen();
|
|
604
|
-
renderBrand(options);
|
|
605
|
-
const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
|
|
606
|
-
const setEnv = !options.noSetEnv;
|
|
607
|
-
const installPlugin = !options.skipPluginInstall;
|
|
608
|
-
const cliPath = options.cmd || "";
|
|
609
|
-
|
|
610
|
-
const args = [
|
|
611
|
-
...commonLangfuseArgs(config),
|
|
612
|
-
...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
|
|
613
|
-
...(!setEnv ? ["--no-set-env"] : []),
|
|
614
|
-
...(!installPlugin ? ["--skip-plugin-install"] : []),
|
|
592
|
+
);
|
|
593
|
+
if (!ok) return 0;
|
|
594
|
+
const code = runNodeScript("langfuse-setup.mjs", args, options);
|
|
595
|
+
if (code === 0 && !options.dryRun && !options.skipCheck) return checkClaude(options, { clear: false });
|
|
596
|
+
return code;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
async function setupOpenCode(rl, options) {
|
|
600
|
+
if (!options.dryRun) {
|
|
601
|
+
while (!(await ensureEnvironment(rl, "opencode", options))) {}
|
|
602
|
+
}
|
|
603
|
+
clearScreen();
|
|
604
|
+
renderBrand(options);
|
|
605
|
+
const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
|
|
606
|
+
const setEnv = !options.noSetEnv;
|
|
607
|
+
const installPlugin = !options.skipPluginInstall;
|
|
608
|
+
const cliPath = options.cmd || "";
|
|
609
|
+
|
|
610
|
+
const args = [
|
|
611
|
+
...commonLangfuseArgs(config),
|
|
612
|
+
...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
|
|
613
|
+
...(!setEnv ? ["--no-set-env"] : []),
|
|
614
|
+
...(!installPlugin ? ["--skip-plugin-install"] : []),
|
|
615
615
|
...(hasValue(cliPath) ? [`--cmd=${cliPath}`] : [])
|
|
616
616
|
];
|
|
617
617
|
const ok = await confirmAction(
|
|
@@ -619,31 +619,31 @@ async function setupOpenCode(rl, options) {
|
|
|
619
619
|
"OpenCode Langfuse Setup",
|
|
620
620
|
[
|
|
621
621
|
labelValue("User ID", config.userId || "<none>", config.userId ? t.teal : t.muted),
|
|
622
|
-
labelValue("User env vars", setEnv ? "write" : "skip", setEnv ? t.teal : t.gold),
|
|
623
|
-
labelValue("Plugin install", installPlugin ? "install/update" : "skip", installPlugin ? t.teal : t.gold),
|
|
624
|
-
labelValue("CLI path", cliPath || "auto-detect", t.blue),
|
|
625
|
-
labelValue("Target runtime", "current shell", t.violet),
|
|
626
|
-
labelValue("Config file", "~/.config/opencode/opencode.json", t.violet)
|
|
622
|
+
labelValue("User env vars", setEnv ? "write" : "skip", setEnv ? t.teal : t.gold),
|
|
623
|
+
labelValue("Plugin install", installPlugin ? "install/update" : "skip", installPlugin ? t.teal : t.gold),
|
|
624
|
+
labelValue("CLI path", cliPath || "auto-detect", t.blue),
|
|
625
|
+
labelValue("Target runtime", "current shell", t.violet),
|
|
626
|
+
labelValue("Config file", "~/.config/opencode/opencode.json", t.violet)
|
|
627
627
|
],
|
|
628
628
|
options
|
|
629
|
-
);
|
|
630
|
-
if (!ok) return 0;
|
|
631
|
-
const code = runNodeScript("opencode-langfuse-setup.mjs", args, options);
|
|
632
|
-
if (code === 0 && !options.dryRun && !options.skipCheck) return checkOpenCode(options, { clear: false });
|
|
633
|
-
return code;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
async function setupCodex(rl, options) {
|
|
637
|
-
if (!options.dryRun) {
|
|
638
|
-
while (!(await ensureEnvironment(rl, "codex", options))) {}
|
|
639
|
-
}
|
|
640
|
-
clearScreen();
|
|
641
|
-
renderBrand(options);
|
|
642
|
-
const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
|
|
629
|
+
);
|
|
630
|
+
if (!ok) return 0;
|
|
631
|
+
const code = runNodeScript("opencode-langfuse-setup.mjs", args, options);
|
|
632
|
+
if (code === 0 && !options.dryRun && !options.skipCheck) return checkOpenCode(options, { clear: false });
|
|
633
|
+
return code;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
async function setupCodex(rl, options) {
|
|
637
|
+
if (!options.dryRun) {
|
|
638
|
+
while (!(await ensureEnvironment(rl, "codex", options))) {}
|
|
639
|
+
}
|
|
640
|
+
clearScreen();
|
|
641
|
+
renderBrand(options);
|
|
642
|
+
const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
|
|
643
643
|
const defaultHook = path.join(rootDir, "codex_langfuse_notify.py");
|
|
644
644
|
const pyPath = fs.existsSync(defaultHook) ? defaultHook : path.join(rootDir, "codex_langfuse_notify.py");
|
|
645
645
|
|
|
646
|
-
const args = [...commonLangfuseArgs(config), ...optionalInstallerArgs(options), `--pyPath=${pyPath}`];
|
|
646
|
+
const args = [...commonLangfuseArgs(config), ...optionalInstallerArgs(options), `--pyPath=${pyPath}`];
|
|
647
647
|
const ok = await confirmAction(
|
|
648
648
|
rl,
|
|
649
649
|
"Codex Langfuse Setup",
|
|
@@ -654,12 +654,12 @@ async function setupCodex(rl, options) {
|
|
|
654
654
|
labelValue("Python package", "langfuse", t.gold)
|
|
655
655
|
],
|
|
656
656
|
options
|
|
657
|
-
);
|
|
658
|
-
if (!ok) return 0;
|
|
659
|
-
const code = runNodeScript("codex-langfuse-setup.mjs", args, options);
|
|
660
|
-
if (code === 0 && !options.dryRun && !options.skipCheck) return checkCodex(options, { clear: false });
|
|
661
|
-
return code;
|
|
662
|
-
}
|
|
657
|
+
);
|
|
658
|
+
if (!ok) return 0;
|
|
659
|
+
const code = runNodeScript("codex-langfuse-setup.mjs", args, options);
|
|
660
|
+
if (code === 0 && !options.dryRun && !options.skipCheck) return checkCodex(options, { clear: false });
|
|
661
|
+
return code;
|
|
662
|
+
}
|
|
663
663
|
|
|
664
664
|
function checkClaude(options, { clear = true } = {}) {
|
|
665
665
|
if (clear) clearScreen();
|
|
@@ -667,11 +667,11 @@ function checkClaude(options, { clear = true } = {}) {
|
|
|
667
667
|
return runNodeScript("langfuse-check.mjs", [], options);
|
|
668
668
|
}
|
|
669
669
|
|
|
670
|
-
function checkOpenCode(options, { clear = true } = {}) {
|
|
671
|
-
if (clear) clearScreen();
|
|
672
|
-
renderBrand(options);
|
|
673
|
-
return runNodeScript("opencode-langfuse-check.mjs", [], options);
|
|
674
|
-
}
|
|
670
|
+
function checkOpenCode(options, { clear = true } = {}) {
|
|
671
|
+
if (clear) clearScreen();
|
|
672
|
+
renderBrand(options);
|
|
673
|
+
return runNodeScript("opencode-langfuse-check.mjs", [], options);
|
|
674
|
+
}
|
|
675
675
|
|
|
676
676
|
function checkCodex(options, { clear = true } = {}) {
|
|
677
677
|
if (clear) clearScreen();
|
|
@@ -711,34 +711,34 @@ async function checkMenu(rl, options) {
|
|
|
711
711
|
return claude || opencode || codex;
|
|
712
712
|
}
|
|
713
713
|
|
|
714
|
-
async function interactiveMain(options) {
|
|
715
|
-
const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
|
|
714
|
+
async function interactiveMain(options) {
|
|
715
|
+
const rl = createPromptInterface({ input: process.stdin, output: process.stdout });
|
|
716
716
|
try {
|
|
717
717
|
const action = await askChoice(
|
|
718
718
|
rl,
|
|
719
719
|
"What would you like to configure?",
|
|
720
720
|
[
|
|
721
|
-
{ label: "Setup Langfuse", value: "setup-langfuse", description: "Select one or more targets: Claude Code, OpenCode, Codex." },
|
|
722
|
-
{ label: "Update Installed Runtimes", value: "update", description: "Refresh installed Claude, OpenCode, and Codex Langfuse hooks/plugins." },
|
|
723
|
-
{ label: "Check Environment", value: "check-environment", description: "Verify required local tools before setup." },
|
|
724
|
-
{ label: "Check Configuration", value: "check", description: "Inspect current setup without changing local files." },
|
|
725
|
-
{ label: "Exit", value: "exit", description: "Close the setup console." }
|
|
721
|
+
{ label: "Setup Langfuse", value: "setup-langfuse", description: "Select one or more targets: Claude Code, OpenCode, Codex." },
|
|
722
|
+
{ label: "Update Installed Runtimes", value: "update", description: "Refresh installed Claude, OpenCode, and Codex Langfuse hooks/plugins." },
|
|
723
|
+
{ label: "Check Environment", value: "check-environment", description: "Verify required local tools before setup." },
|
|
724
|
+
{ label: "Check Configuration", value: "check", description: "Inspect current setup without changing local files." },
|
|
725
|
+
{ label: "Exit", value: "exit", description: "Close the setup console." }
|
|
726
726
|
],
|
|
727
727
|
options
|
|
728
728
|
);
|
|
729
|
-
|
|
730
|
-
if (action === "setup-langfuse") return await setupLangfuseMenu(rl, options);
|
|
731
|
-
if (action === "update") {
|
|
732
|
-
const config = langfuseConfig(options.configOverrides);
|
|
733
|
-
return runNodeScript("update-langfuse-runtime.mjs", [
|
|
734
|
-
"all",
|
|
735
|
-
...commonLangfuseArgs(config),
|
|
736
|
-
...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
|
|
737
|
-
...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
|
|
738
|
-
...(options.skipCheck ? ["--skip-check"] : []),
|
|
739
|
-
...(options.skipPluginInstall ? ["--skip-plugin-install"] : []),
|
|
740
|
-
], options);
|
|
741
|
-
}
|
|
729
|
+
|
|
730
|
+
if (action === "setup-langfuse") return await setupLangfuseMenu(rl, options);
|
|
731
|
+
if (action === "update") {
|
|
732
|
+
const config = langfuseConfig(options.configOverrides);
|
|
733
|
+
return runNodeScript("update-langfuse-runtime.mjs", [
|
|
734
|
+
"all",
|
|
735
|
+
...commonLangfuseArgs(config),
|
|
736
|
+
...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
|
|
737
|
+
...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
|
|
738
|
+
...(options.skipCheck ? ["--skip-check"] : []),
|
|
739
|
+
...(options.skipPluginInstall ? ["--skip-plugin-install"] : []),
|
|
740
|
+
], options);
|
|
741
|
+
}
|
|
742
742
|
if (action === "setup-claude") return await setupClaude(rl, options);
|
|
743
743
|
if (action === "setup-opencode") return await setupOpenCode(rl, options);
|
|
744
744
|
if (action === "setup-codex") return await setupCodex(rl, options);
|
|
@@ -760,7 +760,7 @@ async function setupLangfuseMenu(rl, options) {
|
|
|
760
760
|
rl,
|
|
761
761
|
"Select Langfuse setup targets",
|
|
762
762
|
[
|
|
763
|
-
{ label: "Claude Code Langfuse", value: "claude", selected: false, description: "Install the Langfuse Stop hook and connect Claude transcripts." },
|
|
763
|
+
{ label: "Claude Code Langfuse", value: "claude", selected: false, description: "Install the Langfuse Stop hook and connect Claude transcripts." },
|
|
764
764
|
{ label: "OpenCode Langfuse", value: "opencode", selected: false, description: "Install the Langfuse plugin and enable OpenTelemetry." },
|
|
765
765
|
{ label: "Codex Langfuse", value: "codex", selected: false, description: "Install the Codex notify hook and connect session JSONL events." }
|
|
766
766
|
],
|
|
@@ -770,75 +770,75 @@ async function setupLangfuseMenu(rl, options) {
|
|
|
770
770
|
if (!targets.length) return 0;
|
|
771
771
|
const config = await collectSharedConfig(rl, options);
|
|
772
772
|
let code = 0;
|
|
773
|
-
if (targets.includes("claude")) code = code || await setupClaude(rl, { ...options, config });
|
|
774
|
-
if (targets.includes("opencode")) code = code || await setupOpenCode(rl, { ...options, config });
|
|
775
|
-
if (targets.includes("codex")) code = code || await setupCodex(rl, { ...options, config });
|
|
773
|
+
if (targets.includes("claude")) code = code || await setupClaude(rl, { ...options, config });
|
|
774
|
+
if (targets.includes("opencode")) code = code || await setupOpenCode(rl, { ...options, config });
|
|
775
|
+
if (targets.includes("codex")) code = code || await setupCodex(rl, { ...options, config });
|
|
776
776
|
return code;
|
|
777
777
|
}
|
|
778
778
|
|
|
779
|
-
function printHelp() {
|
|
780
|
-
renderBrand({ dryRun: false });
|
|
781
|
-
console.log("");
|
|
782
|
-
renderSection("Usage", [
|
|
783
|
-
"npx oh-langfuse@latest",
|
|
784
|
-
"npx oh-langfuse@latest check opencode",
|
|
785
|
-
"oh-langfuse",
|
|
786
|
-
"oh-langfuse setup",
|
|
787
|
-
"oh-langfuse setup claude",
|
|
788
|
-
"oh-langfuse setup opencode",
|
|
789
|
-
"oh-langfuse setup codex",
|
|
790
|
-
"oh-langfuse check",
|
|
791
|
-
"oh-langfuse check environment",
|
|
792
|
-
"oh-langfuse check claude",
|
|
793
|
-
"oh-langfuse check opencode",
|
|
794
|
-
"oh-langfuse check codex",
|
|
795
|
-
"oh-langfuse update",
|
|
796
|
-
"oh-langfuse update all",
|
|
797
|
-
"oh-langfuse update claude",
|
|
798
|
-
"oh-langfuse update opencode",
|
|
799
|
-
"oh-langfuse update codex",
|
|
800
|
-
"oh-langfuse auto-update opencode"
|
|
779
|
+
function printHelp() {
|
|
780
|
+
renderBrand({ dryRun: false });
|
|
781
|
+
console.log("");
|
|
782
|
+
renderSection("Usage", [
|
|
783
|
+
"npx oh-langfuse@latest",
|
|
784
|
+
"npx oh-langfuse@latest check opencode",
|
|
785
|
+
"oh-langfuse",
|
|
786
|
+
"oh-langfuse setup",
|
|
787
|
+
"oh-langfuse setup claude",
|
|
788
|
+
"oh-langfuse setup opencode",
|
|
789
|
+
"oh-langfuse setup codex",
|
|
790
|
+
"oh-langfuse check",
|
|
791
|
+
"oh-langfuse check environment",
|
|
792
|
+
"oh-langfuse check claude",
|
|
793
|
+
"oh-langfuse check opencode",
|
|
794
|
+
"oh-langfuse check codex",
|
|
795
|
+
"oh-langfuse update",
|
|
796
|
+
"oh-langfuse update all",
|
|
797
|
+
"oh-langfuse update claude",
|
|
798
|
+
"oh-langfuse update opencode",
|
|
799
|
+
"oh-langfuse update codex",
|
|
800
|
+
"oh-langfuse auto-update opencode"
|
|
801
|
+
]);
|
|
802
|
+
renderSection("Options", [
|
|
803
|
+
`${paint("--dry-run", t.gold)} Preview actions without writing files or installing packages.`,
|
|
804
|
+
`${paint("--userId=ID", t.gold)} Provide the Langfuse user id without prompting.`,
|
|
805
|
+
`${paint("--langfuseBaseUrl=URL", t.gold)} Override the Langfuse base URL.`,
|
|
806
|
+
`${paint("--publicKey=KEY", t.gold)} Override the Langfuse public key.`,
|
|
807
|
+
`${paint("--secretKey=KEY", t.gold)} Override the Langfuse secret key.`,
|
|
808
|
+
`${paint("--yes", t.gold)} Accept setup changes without the confirmation prompt.`,
|
|
809
|
+
`${paint("--npmRegistry=URL", t.gold)} Use a specific npm registry when installing the OpenCode plugin.`,
|
|
810
|
+
`${paint("--pipIndexUrl=URL", t.gold)} Use a specific pip index for Python Langfuse installs.`,
|
|
811
|
+
`${paint("--skip-check", t.gold)} Do not run the target check after a successful setup.`,
|
|
812
|
+
`${paint("--help", t.gold)} Show this help.`
|
|
801
813
|
]);
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
noSetEnv: !!args["no-set-env"],
|
|
831
|
-
skipPluginInstall: !!(args["skip-plugin-install"] || args.skipNpmInstall),
|
|
832
|
-
skipCheck: !!args["skip-check"],
|
|
833
|
-
startupStatus: !!(args["startup-status"] || args.startupStatus),
|
|
834
|
-
cmd: args.cmd || "",
|
|
835
|
-
configOverrides: {
|
|
836
|
-
baseUrl: args.langfuseBaseUrl || args.langfuseHost || args.host || "",
|
|
837
|
-
publicKey: args.publicKey || "",
|
|
838
|
-
secretKey: args.secretKey || "",
|
|
839
|
-
userId: args.userId || args.userid || ""
|
|
840
|
-
}
|
|
841
|
-
};
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function commandNeedsPrompt(cmd, target) {
|
|
817
|
+
if (!cmd) return true;
|
|
818
|
+
if (cmd === "setup") return true;
|
|
819
|
+
if (cmd === "check" && !["claude", "opencode", "codex", "environment"].includes(target || "")) return true;
|
|
820
|
+
return false;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
async function main() {
|
|
824
|
+
const args = parseArgs(process.argv.slice(2));
|
|
825
|
+
const options = {
|
|
826
|
+
dryRun: !!args["dry-run"],
|
|
827
|
+
npmRegistry: args.npmRegistry || "",
|
|
828
|
+
pipIndexUrl: args.pipIndexUrl || "",
|
|
829
|
+
yes: !!(args.yes || args.y),
|
|
830
|
+
noSetEnv: !!args["no-set-env"],
|
|
831
|
+
skipPluginInstall: !!(args["skip-plugin-install"] || args.skipNpmInstall),
|
|
832
|
+
skipCheck: !!args["skip-check"],
|
|
833
|
+
startupStatus: !!(args["startup-status"] || args.startupStatus),
|
|
834
|
+
cmd: args.cmd || "",
|
|
835
|
+
configOverrides: {
|
|
836
|
+
baseUrl: args.langfuseBaseUrl || args.langfuseHost || args.host || "",
|
|
837
|
+
publicKey: args.publicKey || "",
|
|
838
|
+
secretKey: args.secretKey || "",
|
|
839
|
+
userId: args.userId || args.userid || ""
|
|
840
|
+
}
|
|
841
|
+
};
|
|
842
842
|
const [cmd, target] = args._;
|
|
843
843
|
|
|
844
844
|
if (args.help || args.h) {
|
|
@@ -848,34 +848,34 @@ async function main() {
|
|
|
848
848
|
|
|
849
849
|
if (!cmd) return await interactiveMain(options);
|
|
850
850
|
|
|
851
|
-
const rl = commandNeedsPrompt(cmd, target) ? createPromptInterface({ input: process.stdin, output: process.stdout }) : null;
|
|
852
|
-
try {
|
|
853
|
-
if (cmd === "setup" && target === "claude") return await setupClaude(rl, options);
|
|
854
|
-
if (cmd === "setup" && target === "opencode") return await setupOpenCode(rl, options);
|
|
851
|
+
const rl = commandNeedsPrompt(cmd, target) ? createPromptInterface({ input: process.stdin, output: process.stdout }) : null;
|
|
852
|
+
try {
|
|
853
|
+
if (cmd === "setup" && target === "claude") return await setupClaude(rl, options);
|
|
854
|
+
if (cmd === "setup" && target === "opencode") return await setupOpenCode(rl, options);
|
|
855
855
|
if (cmd === "setup" && target === "codex") return await setupCodex(rl, options);
|
|
856
|
-
if (cmd === "setup") return await setupLangfuseMenu(rl, options);
|
|
857
|
-
if (cmd === "update") {
|
|
858
|
-
const updateArgs = [
|
|
859
|
-
target || "all",
|
|
860
|
-
...commonLangfuseArgs(langfuseConfig(options.configOverrides)),
|
|
861
|
-
...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
|
|
862
|
-
...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
|
|
863
|
-
...(options.skipCheck ? ["--skip-check"] : []),
|
|
864
|
-
...(options.skipPluginInstall ? ["--skip-plugin-install"] : []),
|
|
865
|
-
];
|
|
866
|
-
return runNodeScript("update-langfuse-runtime.mjs", updateArgs, options);
|
|
867
|
-
}
|
|
868
|
-
if (cmd === "auto-update") {
|
|
869
|
-
return runNodeScript("auto-update-runtime.mjs", [
|
|
870
|
-
target || "all",
|
|
871
|
-
...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
|
|
872
|
-
...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
|
|
873
|
-
...(options.skipCheck ? ["--skip-check"] : []),
|
|
874
|
-
...(options.startupStatus ? ["--startup-status"] : []),
|
|
875
|
-
...(options.yes ? ["--yes"] : []),
|
|
876
|
-
], { ...options, quiet: true });
|
|
877
|
-
}
|
|
878
|
-
if (cmd === "check" && target === "claude") return checkClaude(options);
|
|
856
|
+
if (cmd === "setup") return await setupLangfuseMenu(rl, options);
|
|
857
|
+
if (cmd === "update") {
|
|
858
|
+
const updateArgs = [
|
|
859
|
+
target || "all",
|
|
860
|
+
...commonLangfuseArgs(langfuseConfig(options.configOverrides)),
|
|
861
|
+
...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
|
|
862
|
+
...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
|
|
863
|
+
...(options.skipCheck ? ["--skip-check"] : []),
|
|
864
|
+
...(options.skipPluginInstall ? ["--skip-plugin-install"] : []),
|
|
865
|
+
];
|
|
866
|
+
return runNodeScript("update-langfuse-runtime.mjs", updateArgs, options);
|
|
867
|
+
}
|
|
868
|
+
if (cmd === "auto-update") {
|
|
869
|
+
return runNodeScript("auto-update-runtime.mjs", [
|
|
870
|
+
target || "all",
|
|
871
|
+
...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
|
|
872
|
+
...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : []),
|
|
873
|
+
...(options.skipCheck ? ["--skip-check"] : []),
|
|
874
|
+
...(options.startupStatus ? ["--startup-status"] : []),
|
|
875
|
+
...(options.yes ? ["--yes"] : []),
|
|
876
|
+
], { ...options, quiet: true });
|
|
877
|
+
}
|
|
878
|
+
if (cmd === "check" && target === "claude") return checkClaude(options);
|
|
879
879
|
if (cmd === "check" && target === "opencode") return checkOpenCode(options);
|
|
880
880
|
if (cmd === "check" && target === "codex") return checkCodex(options);
|
|
881
881
|
if (cmd === "check" && target === "environment") {
|
|
@@ -885,19 +885,19 @@ async function main() {
|
|
|
885
885
|
return 0;
|
|
886
886
|
}
|
|
887
887
|
if (cmd === "check") return await checkMenu(rl, options);
|
|
888
|
-
} finally {
|
|
889
|
-
rl?.close();
|
|
890
|
-
}
|
|
888
|
+
} finally {
|
|
889
|
+
rl?.close();
|
|
890
|
+
}
|
|
891
891
|
|
|
892
892
|
printHelp();
|
|
893
893
|
return 1;
|
|
894
894
|
}
|
|
895
895
|
|
|
896
|
-
assertSupportedNode();
|
|
897
|
-
|
|
898
|
-
main()
|
|
899
|
-
.then((code) => process.exit(code))
|
|
900
|
-
.catch((err) => {
|
|
901
|
-
console.error(paint((err && err.message) || String(err), t.red));
|
|
902
|
-
process.exit(1);
|
|
903
|
-
});
|
|
896
|
+
assertSupportedNode();
|
|
897
|
+
|
|
898
|
+
main()
|
|
899
|
+
.then((code) => process.exit(code))
|
|
900
|
+
.catch((err) => {
|
|
901
|
+
console.error(paint((err && err.message) || String(err), t.red));
|
|
902
|
+
process.exit(1);
|
|
903
|
+
});
|