oh-langfuse 0.1.15 → 0.1.17

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 CHANGED
@@ -109,19 +109,18 @@ Langfuse 配置启动 OpenCode。安装器可以读取带注释或尾随逗号
109
109
  此时配置会写入 WSL 用户自己的 `$HOME/.config/opencode`:
110
110
 
111
111
  ```bash
112
- npx oh-langfuse@latest setup opencode --userId=h00613222
112
+ npx oh-langfuse@latest setup opencode --userId=h00613222 --yes
113
113
  npx oh-langfuse@latest check opencode
114
114
  ```
115
115
 
116
+ 安装完成后会自动执行一次 `check opencode`。如果检查提示当前终端缺少
117
+ `LANGFUSE_*`,请新开一个 WSL 终端,或执行安装器提示的 `source ~/.bashrc`
118
+ / `source ~/.zshrc`,也可以直接使用生成的
119
+ `~/.config/opencode/launch-opencode-langfuse.sh` 启动 OpenCode。
120
+
116
121
  如果 `opencode.json` 原本是 JSONC 风格(例如带注释或尾随逗号),安装器会先兼容读取,
117
122
  再写回标准 JSON。
118
123
 
119
- 如果你确实是在 Windows PowerShell 里远程配置 WSL,才需要使用 `--wsl`:
120
-
121
- ```bash
122
- npx oh-langfuse@latest setup opencode --wsl=Ubuntu --userId=h00613222
123
- ```
124
-
125
124
  ### Codex
126
125
 
127
126
  安装 `codex_langfuse_notify.py`,创建 `~/.codex/langfuse-venv`,安装 Python
package/bin/cli.js CHANGED
@@ -451,22 +451,27 @@ async function askMultiChoice(rl, label, choices, options = {}) {
451
451
  .map((idx) => choices[idx].value);
452
452
  }
453
453
 
454
- function langfuseConfig() {
455
- return {
456
- baseUrl: envDefault("LANGFUSE_BASEURL", envDefault("LANGFUSE_HOST", DEFAULT_LANGFUSE_BASE_URL)),
457
- publicKey: envDefault("LANGFUSE_PUBLIC_KEY", DEFAULT_LANGFUSE_PUBLIC_KEY),
458
- secretKey: envDefault("LANGFUSE_SECRET_KEY", DEFAULT_LANGFUSE_SECRET_KEY),
459
- userId: envDefault("LANGFUSE_USER_ID", envDefault("CC_USER_ID", ""))
460
- };
461
- }
462
-
463
- async function collectLangfuseConfig(rl, { requireUserId = false } = {}) {
464
- const config = langfuseConfig();
465
- renderSection("Langfuse Target", [
466
- labelValue("Base URL", config.baseUrl, t.teal),
467
- labelValue("Public Key", config.publicKey, t.blue),
468
- labelValue("Secret Key", "configured", t.teal)
469
- ]);
454
+ function langfuseConfig(overrides = {}) {
455
+ const config = {
456
+ baseUrl: envDefault("LANGFUSE_BASEURL", envDefault("LANGFUSE_HOST", DEFAULT_LANGFUSE_BASE_URL)),
457
+ publicKey: envDefault("LANGFUSE_PUBLIC_KEY", DEFAULT_LANGFUSE_PUBLIC_KEY),
458
+ secretKey: envDefault("LANGFUSE_SECRET_KEY", DEFAULT_LANGFUSE_SECRET_KEY),
459
+ userId: envDefault("LANGFUSE_USER_ID", envDefault("CC_USER_ID", ""))
460
+ };
461
+ for (const [key, value] of Object.entries(overrides)) {
462
+ if (hasValue(value)) config[key] = value;
463
+ }
464
+ return config;
465
+ }
466
+
467
+ async function collectLangfuseConfig(rl, { requireUserId = false, overrides = {} } = {}) {
468
+ const config = langfuseConfig(overrides);
469
+ renderSection("Langfuse Target", [
470
+ labelValue("Base URL", config.baseUrl, t.teal),
471
+ labelValue("Public Key", config.publicKey, t.blue),
472
+ labelValue("Secret Key", "configured", t.teal)
473
+ ]);
474
+ if (requireUserId && hasValue(config.userId)) return config;
470
475
  config.userId = await askText(rl, "User ID / employee number, for example h00613222", {
471
476
  defaultValue: requireUserId ? "" : config.userId,
472
477
  required: requireUserId
@@ -474,11 +479,11 @@ async function collectLangfuseConfig(rl, { requireUserId = false } = {}) {
474
479
  return config;
475
480
  }
476
481
 
477
- async function collectSharedConfig(rl, options) {
478
- clearScreen();
479
- renderBrand(options);
480
- return await collectLangfuseConfig(rl, { requireUserId: true });
481
- }
482
+ async function collectSharedConfig(rl, options) {
483
+ clearScreen();
484
+ renderBrand(options);
485
+ return await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides });
486
+ }
482
487
 
483
488
  function commonLangfuseArgs(config) {
484
489
  return [
@@ -493,27 +498,22 @@ function optionalInstallerArgs(options) {
493
498
  return [...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : [])];
494
499
  }
495
500
 
496
- function opencodePlatformArgs(options) {
497
- return [
498
- ...(options.wsl ? [typeof options.wsl === "string" ? `--wsl=${options.wsl}` : "--wsl"] : []),
499
- ...(hasValue(options.wslDistro) ? [`--wslDistro=${options.wslDistro}`] : [])
500
- ];
501
- }
502
-
503
- async function confirmAction(rl, title, rows, options) {
501
+ async function confirmAction(rl, title, rows, options) {
504
502
  clearScreen();
505
503
  renderBrand(options);
506
504
  renderSection(title, rows);
507
- if (options.dryRun) return true;
505
+ if (options.dryRun || options.yes) return true;
508
506
  console.log("");
509
507
  return await askYesNo(rl, "Continue with these changes", { defaultValue: true });
510
508
  }
511
509
 
512
- async function setupClaude(rl, options) {
513
- while (!(await ensureEnvironment(rl, "claude", options))) {}
514
- clearScreen();
515
- renderBrand(options);
516
- const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true }));
510
+ async function setupClaude(rl, options) {
511
+ if (!options.dryRun) {
512
+ while (!(await ensureEnvironment(rl, "claude", options))) {}
513
+ }
514
+ clearScreen();
515
+ renderBrand(options);
516
+ const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
517
517
  const defaultHook = path.join(rootDir, "langfuse_hook.py");
518
518
  const pyPath = fs.existsSync(defaultHook) ? defaultHook : path.join(rootDir, "langfuse_hook.py");
519
519
 
@@ -528,26 +528,29 @@ async function setupClaude(rl, options) {
528
528
  labelValue("Python package", "langfuse", t.gold)
529
529
  ],
530
530
  options
531
- );
532
- if (!ok) return 0;
533
- return runNodeScript("langfuse-setup.mjs", args, options);
534
- }
535
-
531
+ );
532
+ if (!ok) return 0;
533
+ const code = runNodeScript("langfuse-setup.mjs", args, options);
534
+ if (code === 0 && !options.dryRun && !options.skipCheck) return checkClaude(options, { clear: false });
535
+ return code;
536
+ }
537
+
536
538
  async function setupOpenCode(rl, options) {
537
- while (!(await ensureEnvironment(rl, "opencode", options))) {}
538
- clearScreen();
539
- renderBrand(options);
540
- const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true }));
541
- const setEnv = true;
542
- const installPlugin = true;
543
- const cliPath = "";
539
+ if (!options.dryRun) {
540
+ while (!(await ensureEnvironment(rl, "opencode", options))) {}
541
+ }
542
+ clearScreen();
543
+ renderBrand(options);
544
+ const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
545
+ const setEnv = !options.noSetEnv;
546
+ const installPlugin = !options.skipPluginInstall;
547
+ const cliPath = options.cmd || "";
544
548
 
545
549
  const args = [
546
550
  ...commonLangfuseArgs(config),
547
551
  ...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
548
- ...opencodePlatformArgs(options),
549
552
  ...(!setEnv ? ["--no-set-env"] : []),
550
- ...(!installPlugin ? ["--skip-plugin-install"] : []),
553
+ ...(!installPlugin ? ["--skip-plugin-install"] : []),
551
554
  ...(hasValue(cliPath) ? [`--cmd=${cliPath}`] : [])
552
555
  ];
553
556
  const ok = await confirmAction(
@@ -555,23 +558,27 @@ async function setupOpenCode(rl, options) {
555
558
  "OpenCode Langfuse Setup",
556
559
  [
557
560
  labelValue("User ID", config.userId || "<none>", config.userId ? t.teal : t.muted),
558
- labelValue("User env vars", setEnv ? "write" : "skip", setEnv ? t.teal : t.gold),
561
+ labelValue("User env vars", setEnv ? "write" : "skip", setEnv ? t.teal : t.gold),
559
562
  labelValue("Plugin install", installPlugin ? "install/update" : "skip", installPlugin ? t.teal : t.gold),
560
563
  labelValue("CLI path", cliPath || "auto-detect", t.blue),
561
- labelValue("Target runtime", options.wsl ? `WSL${typeof options.wsl === "string" ? ` (${options.wsl})` : ""}` : "current OS", t.violet),
564
+ labelValue("Target runtime", "current shell", t.violet),
562
565
  labelValue("Config file", "~/.config/opencode/opencode.json", t.violet)
563
566
  ],
564
567
  options
565
- );
566
- if (!ok) return 0;
567
- return runNodeScript("opencode-langfuse-setup.mjs", args, options);
568
- }
569
-
570
- async function setupCodex(rl, options) {
571
- while (!(await ensureEnvironment(rl, "codex", options))) {}
572
- clearScreen();
573
- renderBrand(options);
574
- const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true }));
568
+ );
569
+ if (!ok) return 0;
570
+ const code = runNodeScript("opencode-langfuse-setup.mjs", args, options);
571
+ if (code === 0 && !options.dryRun && !options.skipCheck) return checkOpenCode(options, { clear: false });
572
+ return code;
573
+ }
574
+
575
+ async function setupCodex(rl, options) {
576
+ if (!options.dryRun) {
577
+ while (!(await ensureEnvironment(rl, "codex", options))) {}
578
+ }
579
+ clearScreen();
580
+ renderBrand(options);
581
+ const config = options.config || (await collectLangfuseConfig(rl, { requireUserId: true, overrides: options.configOverrides }));
575
582
  const defaultHook = path.join(rootDir, "codex_langfuse_notify.py");
576
583
  const pyPath = fs.existsSync(defaultHook) ? defaultHook : path.join(rootDir, "codex_langfuse_notify.py");
577
584
 
@@ -586,10 +593,12 @@ async function setupCodex(rl, options) {
586
593
  labelValue("Python package", "langfuse", t.gold)
587
594
  ],
588
595
  options
589
- );
590
- if (!ok) return 0;
591
- return runNodeScript("codex-langfuse-setup.mjs", args, options);
592
- }
596
+ );
597
+ if (!ok) return 0;
598
+ const code = runNodeScript("codex-langfuse-setup.mjs", args, options);
599
+ if (code === 0 && !options.dryRun && !options.skipCheck) return checkCodex(options, { clear: false });
600
+ return code;
601
+ }
593
602
 
594
603
  function checkClaude(options, { clear = true } = {}) {
595
604
  if (clear) clearScreen();
@@ -600,7 +609,7 @@ function checkClaude(options, { clear = true } = {}) {
600
609
  function checkOpenCode(options, { clear = true } = {}) {
601
610
  if (clear) clearScreen();
602
611
  renderBrand(options);
603
- return runNodeScript("opencode-langfuse-check.mjs", opencodePlatformArgs(options), options);
612
+ return runNodeScript("opencode-langfuse-check.mjs", [], options);
604
613
  }
605
614
 
606
615
  function checkCodex(options, { clear = true } = {}) {
@@ -713,9 +722,14 @@ function printHelp() {
713
722
  ]);
714
723
  renderSection("Options", [
715
724
  `${paint("--dry-run", t.gold)} Preview actions without writing files or installing packages.`,
716
- `${paint("--wsl[=DISTRO]", t.gold)} Configure/check OpenCode inside WSL from Windows.`,
725
+ `${paint("--userId=ID", t.gold)} Provide the Langfuse user id without prompting.`,
726
+ `${paint("--langfuseBaseUrl=URL", t.gold)} Override the Langfuse base URL.`,
727
+ `${paint("--publicKey=KEY", t.gold)} Override the Langfuse public key.`,
728
+ `${paint("--secretKey=KEY", t.gold)} Override the Langfuse secret key.`,
729
+ `${paint("--yes", t.gold)} Accept setup changes without the confirmation prompt.`,
717
730
  `${paint("--npmRegistry=URL", t.gold)} Use a specific npm registry when installing the OpenCode plugin.`,
718
731
  `${paint("--pipIndexUrl=URL", t.gold)} Use a specific pip index for Python Langfuse installs.`,
732
+ `${paint("--skip-check", t.gold)} Do not run the target check after a successful setup.`,
719
733
  `${paint("--help", t.gold)} Show this help.`
720
734
  ]);
721
735
  }
@@ -724,10 +738,19 @@ async function main() {
724
738
  const args = parseArgs(process.argv.slice(2));
725
739
  const options = {
726
740
  dryRun: !!args["dry-run"],
727
- wsl: args.wsl || !!(args.wslDistro || args["wsl-distro"]),
728
- wslDistro: args.wslDistro || args["wsl-distro"] || "",
729
741
  npmRegistry: args.npmRegistry || "",
730
- pipIndexUrl: args.pipIndexUrl || ""
742
+ pipIndexUrl: args.pipIndexUrl || "",
743
+ yes: !!(args.yes || args.y),
744
+ noSetEnv: !!args["no-set-env"],
745
+ skipPluginInstall: !!(args["skip-plugin-install"] || args.skipNpmInstall),
746
+ skipCheck: !!args["skip-check"],
747
+ cmd: args.cmd || "",
748
+ configOverrides: {
749
+ baseUrl: args.langfuseBaseUrl || args.langfuseHost || args.host || "",
750
+ publicKey: args.publicKey || "",
751
+ secretKey: args.secretKey || "",
752
+ userId: args.userId || args.userid || ""
753
+ }
731
754
  };
732
755
  const [cmd, target] = args._;
733
756
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-langfuse",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Use npm scripts to configure Claude Code / OpenCode / Codex with Langfuse tracing.",
@@ -2,7 +2,6 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import os from "node:os";
4
4
  import { parseJsonRelaxed, stripBom } from "./json-utils.mjs";
5
- import { runOhLangfuseInWsl, shouldRunInWsl } from "./wsl-utils.mjs";
6
5
 
7
6
  function parseArgs(argv) {
8
7
  const args = {};
@@ -198,10 +197,7 @@ function main() {
198
197
  }
199
198
 
200
199
  try {
201
- const args = parseArgs(process.argv.slice(2));
202
- if (shouldRunInWsl(args)) {
203
- process.exit(runOhLangfuseInWsl(args, ["npx", "-y", "oh-langfuse@latest", "check", "opencode"]));
204
- }
200
+ parseArgs(process.argv.slice(2));
205
201
  main();
206
202
  } catch (e) {
207
203
  console.error(e?.message || String(e));
@@ -3,7 +3,6 @@ import path from "node:path";
3
3
  import os from "node:os";
4
4
  import { spawn, spawnSync } from "node:child_process";
5
5
  import { parseJsonRelaxed, stripBom } from "./json-utils.mjs";
6
- import { runOhLangfuseInWsl, shouldRunInWsl } from "./wsl-utils.mjs";
7
6
 
8
7
  function parseArgs(argv) {
9
8
  const args = {};
@@ -30,13 +29,34 @@ function readJsonIfExists(p) {
30
29
  return parseJsonRelaxed(txt, p);
31
30
  }
32
31
 
33
- function writeJsonPretty(p, obj) {
34
- fs.writeFileSync(p, JSON.stringify(obj, null, 2) + os.EOL, "utf8");
35
- }
36
-
37
- function isObject(x) {
38
- return x && typeof x === "object" && !Array.isArray(x);
39
- }
32
+ function writeJsonPretty(p, obj) {
33
+ fs.writeFileSync(p, JSON.stringify(obj, null, 2) + os.EOL, "utf8");
34
+ }
35
+
36
+ const colorEnabled = process.stdout.isTTY && process.env.NO_COLOR !== "1";
37
+ const ansi = (code) => (colorEnabled ? `\x1b[${code}m` : "");
38
+ const colors = {
39
+ reset: ansi(0),
40
+ bold: ansi(1),
41
+ cyan: ansi(36),
42
+ green: ansi(32),
43
+ yellow: ansi(33),
44
+ dim: ansi(2)
45
+ };
46
+
47
+ function paint(text, ...styles) {
48
+ if (!colorEnabled) return String(text);
49
+ return `${styles.join("")}${text}${colors.reset}`;
50
+ }
51
+
52
+ function printCommandHint(label, command) {
53
+ console.log(paint(label, colors.yellow, colors.bold));
54
+ console.log(`${paint(">", colors.green, colors.bold)} ${paint(command, colors.cyan, colors.bold)}`);
55
+ }
56
+
57
+ function isObject(x) {
58
+ return x && typeof x === "object" && !Array.isArray(x);
59
+ }
40
60
 
41
61
  function deepMerge(target, src) {
42
62
  if (!isObject(target) || !isObject(src)) return src;
@@ -417,23 +437,6 @@ function updateShellConfig({ publicKey, secretKey, baseUrl, userId }) {
417
437
 
418
438
  async function main() {
419
439
  const args = parseArgs(process.argv.slice(2));
420
- if (shouldRunInWsl(args)) {
421
- const forwarded = [
422
- "npx",
423
- "-y",
424
- "oh-langfuse@latest",
425
- "setup",
426
- "opencode",
427
- ...(args.langfuseBaseUrl ? [`--langfuseBaseUrl=${args.langfuseBaseUrl}`] : []),
428
- ...(args.publicKey ? [`--publicKey=${args.publicKey}`] : []),
429
- ...(args.secretKey ? [`--secretKey=${args.secretKey}`] : []),
430
- ...(args.userId || args.userid ? [`--userId=${args.userId || args.userid}`] : []),
431
- ...(args.npmRegistry ? [`--npmRegistry=${args.npmRegistry}`] : []),
432
- ...(args["no-set-env"] ? ["--no-set-env"] : []),
433
- ...(args["skip-plugin-install"] ? ["--skip-plugin-install"] : [])
434
- ];
435
- process.exit(runOhLangfuseInWsl(args, forwarded));
436
- }
437
440
 
438
441
  const setEnv = !args["no-set-env"];
439
442
  const skipPluginInstall =
@@ -522,10 +525,11 @@ async function main() {
522
525
  const unixLauncher = writeUnixLauncherSh(opencodeDir, { publicKey, secretKey, baseUrl, userId });
523
526
  if (unixLauncher) {
524
527
  console.log(`已生成启动脚本(含 LANGFUSE 环境变量):${unixLauncher}`);
525
- console.log(`如果新终端仍读不到环境变量,可运行:${unixLauncher}`);
528
+ printCommandHint("如果新终端仍读不到环境变量,可运行:", unixLauncher);
526
529
  }
527
530
 
528
- if (setEnv) {
531
+ let shellConfigPathWritten = "";
532
+ if (setEnv) {
529
533
  if (process.platform === "win32") {
530
534
  console.log("写入用户级环境变量:LANGFUSE_PUBLIC_KEY / LANGFUSE_SECRET_KEY / LANGFUSE_BASEURL …");
531
535
  setWindowsUserEnv({ publicKey, secretKey, baseUrl: baseUrl });
@@ -539,7 +543,8 @@ async function main() {
539
543
  console.log("提示:新开一个终端后环境变量才会对应用生效。");
540
544
  } else {
541
545
  console.log("正在写入环境变量到 shell 配置文件 …");
542
- const configPath = updateShellConfig({ publicKey, secretKey, baseUrl, userId });
546
+ const configPath = updateShellConfig({ publicKey, secretKey, baseUrl, userId });
547
+ shellConfigPathWritten = configPath;
543
548
  console.log(`已更新:${configPath}`);
544
549
 
545
550
  // 自动 source 配置文件
@@ -551,17 +556,29 @@ async function main() {
551
556
  if (r.status === 0) {
552
557
  console.log("环境变量已生效:");
553
558
  console.log(r.stdout);
554
- } else {
555
- console.log("提示:环境变量已写入配置文件,请运行以下命令使其生效:");
556
- console.log(`source ${configPath}`);
557
- }
558
- }
559
- }
560
-
561
- console.log("完成。可运行:`npx oh-langfuse@latest check opencode` 校验。");
559
+ } else {
560
+ printCommandHint("提示:环境变量已写入配置文件,请运行以下命令使其生效:", `source ${configPath}`);
561
+ }
562
+ }
563
+ }
564
+
565
+ console.log(paint("完成。", colors.green, colors.bold));
566
+ printCommandHint("可运行以下命令校验:", "npx oh-langfuse@latest check opencode");
567
+ if (process.platform !== "win32") {
568
+ console.log("");
569
+ console.log("Important for WSL/Linux:");
570
+ console.log("This installer wrote LANGFUSE_* to your shell rc file, but it cannot update an already-open parent shell.");
571
+ if (shellConfigPathWritten) {
572
+ printCommandHint("Before starting OpenCode from this same terminal, run:", `source ${shellConfigPathWritten}`);
573
+ }
574
+ if (unixLauncher) {
575
+ printCommandHint("Or start OpenCode with the generated launcher:", unixLauncher);
576
+ }
577
+ console.log("Opening a new WSL terminal also loads the variables for normal shell setups.");
578
+ }
562
579
  }
563
-
564
- try {
580
+
581
+ try {
565
582
  await main();
566
583
  } catch (e) {
567
584
  console.error(e?.message || String(e));
@@ -1,39 +0,0 @@
1
- import { spawnSync } from "node:child_process";
2
-
3
- export function shouldRunInWsl(args) {
4
- return process.platform === "win32" && !!(args.wsl || args.wslDistro || args["wsl-distro"]);
5
- }
6
-
7
- function wslDistro(args) {
8
- if (typeof args.wsl === "string" && args.wsl.trim() && args.wsl.trim().toLowerCase() !== "true") {
9
- return args.wsl.trim();
10
- }
11
- if (typeof args.wslDistro === "string" && args.wslDistro.trim()) return args.wslDistro.trim();
12
- if (typeof args["wsl-distro"] === "string" && args["wsl-distro"].trim()) return args["wsl-distro"].trim();
13
- return "";
14
- }
15
-
16
- function shQuote(value) {
17
- return `'${String(value).replace(/'/g, "'\"'\"'")}'`;
18
- }
19
-
20
- export function runOhLangfuseInWsl(args, ohLangfuseArgs) {
21
- const distro = wslDistro(args);
22
- const wslArgs = [];
23
- if (distro) wslArgs.push("-d", distro);
24
- wslArgs.push("--", "sh", "-lc", ohLangfuseArgs.map(shQuote).join(" "));
25
-
26
- console.log(
27
- distro
28
- ? `Forwarding OpenCode Langfuse command to WSL distro: ${distro}`
29
- : "Forwarding OpenCode Langfuse command to the default WSL distro"
30
- );
31
-
32
- const r = spawnSync("wsl.exe", wslArgs, { stdio: "inherit", windowsHide: true });
33
- if (r.error) {
34
- console.error(r.error.message);
35
- console.error("WSL is not available from this Windows session. Run the same oh-langfuse command inside WSL instead.");
36
- return 1;
37
- }
38
- return r.status ?? 1;
39
- }