oh-langfuse 0.1.13 → 0.1.15

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
@@ -102,7 +102,25 @@ npx oh-langfuse@latest check codex
102
102
  并可写入用户级 `LANGFUSE_*` 环境变量。Windows 下会生成
103
103
  `launch-opencode-langfuse.cmd`,Linux/macOS 下会生成
104
104
  `launch-opencode-langfuse.sh`,用于在 shell 环境变量未生效时显式带
105
- Langfuse 配置启动 OpenCode
105
+ Langfuse 配置启动 OpenCode。安装器可以读取带注释或尾随逗号的
106
+ `opencode.json`,合并配置后会写回标准 JSON。
107
+
108
+ 如果你在 WSL 里运行 OpenCode,就在 WSL shell 里直接执行安装和检查命令。
109
+ 此时配置会写入 WSL 用户自己的 `$HOME/.config/opencode`:
110
+
111
+ ```bash
112
+ npx oh-langfuse@latest setup opencode --userId=h00613222
113
+ npx oh-langfuse@latest check opencode
114
+ ```
115
+
116
+ 如果 `opencode.json` 原本是 JSONC 风格(例如带注释或尾随逗号),安装器会先兼容读取,
117
+ 再写回标准 JSON。
118
+
119
+ 如果你确实是在 Windows PowerShell 里远程配置 WSL,才需要使用 `--wsl`:
120
+
121
+ ```bash
122
+ npx oh-langfuse@latest setup opencode --wsl=Ubuntu --userId=h00613222
123
+ ```
106
124
 
107
125
  ### Codex
108
126
 
package/bin/cli.js CHANGED
@@ -492,6 +492,13 @@ function commonLangfuseArgs(config) {
492
492
  function optionalInstallerArgs(options) {
493
493
  return [...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : [])];
494
494
  }
495
+
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
+ }
495
502
 
496
503
  async function confirmAction(rl, title, rows, options) {
497
504
  clearScreen();
@@ -538,6 +545,7 @@ async function setupOpenCode(rl, options) {
538
545
  const args = [
539
546
  ...commonLangfuseArgs(config),
540
547
  ...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
548
+ ...opencodePlatformArgs(options),
541
549
  ...(!setEnv ? ["--no-set-env"] : []),
542
550
  ...(!installPlugin ? ["--skip-plugin-install"] : []),
543
551
  ...(hasValue(cliPath) ? [`--cmd=${cliPath}`] : [])
@@ -548,9 +556,10 @@ async function setupOpenCode(rl, options) {
548
556
  [
549
557
  labelValue("User ID", config.userId || "<none>", config.userId ? t.teal : t.muted),
550
558
  labelValue("User env vars", setEnv ? "write" : "skip", setEnv ? t.teal : t.gold),
551
- labelValue("Plugin install", installPlugin ? "install/update" : "skip", installPlugin ? t.teal : t.gold),
552
- labelValue("CLI path", cliPath || "auto-detect", t.blue),
553
- labelValue("Config file", "~/.config/opencode/opencode.json", t.violet)
559
+ labelValue("Plugin install", installPlugin ? "install/update" : "skip", installPlugin ? t.teal : t.gold),
560
+ 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),
562
+ labelValue("Config file", "~/.config/opencode/opencode.json", t.violet)
554
563
  ],
555
564
  options
556
565
  );
@@ -588,11 +597,11 @@ function checkClaude(options, { clear = true } = {}) {
588
597
  return runNodeScript("langfuse-check.mjs", [], options);
589
598
  }
590
599
 
591
- function checkOpenCode(options, { clear = true } = {}) {
592
- if (clear) clearScreen();
593
- renderBrand(options);
594
- return runNodeScript("opencode-langfuse-check.mjs", [], options);
595
- }
600
+ function checkOpenCode(options, { clear = true } = {}) {
601
+ if (clear) clearScreen();
602
+ renderBrand(options);
603
+ return runNodeScript("opencode-langfuse-check.mjs", opencodePlatformArgs(options), options);
604
+ }
596
605
 
597
606
  function checkCodex(options, { clear = true } = {}) {
598
607
  if (clear) clearScreen();
@@ -704,6 +713,7 @@ function printHelp() {
704
713
  ]);
705
714
  renderSection("Options", [
706
715
  `${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.`,
707
717
  `${paint("--npmRegistry=URL", t.gold)} Use a specific npm registry when installing the OpenCode plugin.`,
708
718
  `${paint("--pipIndexUrl=URL", t.gold)} Use a specific pip index for Python Langfuse installs.`,
709
719
  `${paint("--help", t.gold)} Show this help.`
@@ -714,6 +724,8 @@ async function main() {
714
724
  const args = parseArgs(process.argv.slice(2));
715
725
  const options = {
716
726
  dryRun: !!args["dry-run"],
727
+ wsl: args.wsl || !!(args.wslDistro || args["wsl-distro"]),
728
+ wslDistro: args.wslDistro || args["wsl-distro"] || "",
717
729
  npmRegistry: args.npmRegistry || "",
718
730
  pipIndexUrl: args.pipIndexUrl || ""
719
731
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-langfuse",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Use npm scripts to configure Claude Code / OpenCode / Codex with Langfuse tracing.",
@@ -0,0 +1,99 @@
1
+ export function stripBom(s) {
2
+ if (typeof s !== "string" || s.length === 0) return s;
3
+ return s.charCodeAt(0) === 0xfeff ? s.slice(1) : s;
4
+ }
5
+
6
+ function stripJsonComments(text) {
7
+ let out = "";
8
+ let inString = false;
9
+ let escaped = false;
10
+
11
+ for (let i = 0; i < text.length; i += 1) {
12
+ const c = text[i];
13
+ const n = text[i + 1];
14
+
15
+ if (inString) {
16
+ out += c;
17
+ if (escaped) escaped = false;
18
+ else if (c === "\\") escaped = true;
19
+ else if (c === '"') inString = false;
20
+ continue;
21
+ }
22
+
23
+ if (c === '"') {
24
+ inString = true;
25
+ out += c;
26
+ continue;
27
+ }
28
+
29
+ if (c === "/" && n === "/") {
30
+ while (i < text.length && text[i] !== "\n" && text[i] !== "\r") i += 1;
31
+ if (i < text.length) out += text[i];
32
+ continue;
33
+ }
34
+
35
+ if (c === "/" && n === "*") {
36
+ i += 2;
37
+ while (i < text.length - 1 && !(text[i] === "*" && text[i + 1] === "/")) {
38
+ out += text[i] === "\n" || text[i] === "\r" ? text[i] : " ";
39
+ i += 1;
40
+ }
41
+ i += 1;
42
+ continue;
43
+ }
44
+
45
+ out += c;
46
+ }
47
+
48
+ return out;
49
+ }
50
+
51
+ function removeTrailingCommas(text) {
52
+ let out = "";
53
+ let inString = false;
54
+ let escaped = false;
55
+
56
+ for (let i = 0; i < text.length; i += 1) {
57
+ const c = text[i];
58
+
59
+ if (inString) {
60
+ out += c;
61
+ if (escaped) escaped = false;
62
+ else if (c === "\\") escaped = true;
63
+ else if (c === '"') inString = false;
64
+ continue;
65
+ }
66
+
67
+ if (c === '"') {
68
+ inString = true;
69
+ out += c;
70
+ continue;
71
+ }
72
+
73
+ if (c === ",") {
74
+ let j = i + 1;
75
+ while (j < text.length && /\s/.test(text[j])) j += 1;
76
+ if (text[j] === "}" || text[j] === "]") continue;
77
+ }
78
+
79
+ out += c;
80
+ }
81
+
82
+ return out;
83
+ }
84
+
85
+ export function parseJsonRelaxed(text, filePath = "JSON file") {
86
+ const raw = stripBom(text);
87
+ try {
88
+ return JSON.parse(raw);
89
+ } catch (strictError) {
90
+ const relaxed = removeTrailingCommas(stripJsonComments(raw));
91
+ try {
92
+ return JSON.parse(relaxed);
93
+ } catch (relaxedError) {
94
+ throw new Error(
95
+ `Cannot parse ${filePath}: ${relaxedError.message}. Original JSON.parse error: ${strictError.message}`
96
+ );
97
+ }
98
+ }
99
+ }
@@ -1,10 +1,18 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import os from "node:os";
4
-
5
- function stripBom(s) {
6
- if (typeof s !== "string" || s.length === 0) return s;
7
- return s.charCodeAt(0) === 0xfeff ? s.slice(1) : s;
4
+ import { parseJsonRelaxed, stripBom } from "./json-utils.mjs";
5
+ import { runOhLangfuseInWsl, shouldRunInWsl } from "./wsl-utils.mjs";
6
+
7
+ function parseArgs(argv) {
8
+ const args = {};
9
+ for (const raw of argv) {
10
+ if (!raw.startsWith("--")) continue;
11
+ const eq = raw.indexOf("=");
12
+ if (eq === -1) args[raw.slice(2)] = true;
13
+ else args[raw.slice(2, eq)] = raw.slice(eq + 1);
14
+ }
15
+ return args;
8
16
  }
9
17
 
10
18
  function readTextIfExists(p) {
@@ -15,7 +23,7 @@ function readTextIfExists(p) {
15
23
  function readJsonIfExists(p) {
16
24
  const txt = readTextIfExists(p);
17
25
  if (!txt.trim()) return null;
18
- return JSON.parse(txt);
26
+ return parseJsonRelaxed(txt, p);
19
27
  }
20
28
 
21
29
  function hasPlugin(pluginField, name) {
@@ -190,6 +198,10 @@ function main() {
190
198
  }
191
199
 
192
200
  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
+ }
193
205
  main();
194
206
  } catch (e) {
195
207
  console.error(e?.message || String(e));
@@ -1,7 +1,9 @@
1
1
  import fs from "node:fs";
2
- import path from "node:path";
3
- import os from "node:os";
2
+ import path from "node:path";
3
+ import os from "node:os";
4
4
  import { spawn, spawnSync } from "node:child_process";
5
+ import { parseJsonRelaxed, stripBom } from "./json-utils.mjs";
6
+ import { runOhLangfuseInWsl, shouldRunInWsl } from "./wsl-utils.mjs";
5
7
 
6
8
  function parseArgs(argv) {
7
9
  const args = {};
@@ -21,18 +23,12 @@ function ensureDir(p) {
21
23
  fs.mkdirSync(p, { recursive: true });
22
24
  }
23
25
 
24
- function stripBom(s) {
25
- if (typeof s !== "string" || s.length === 0) return s;
26
- // Handle UTF-8 BOM (U+FEFF) which breaks JSON.parse on some files.
27
- return s.charCodeAt(0) === 0xfeff ? s.slice(1) : s;
28
- }
29
-
30
- function readJsonIfExists(p) {
31
- if (!fs.existsSync(p)) return null;
32
- const txt = stripBom(fs.readFileSync(p, "utf8"));
33
- if (!txt.trim()) return null;
34
- return JSON.parse(txt);
35
- }
26
+ function readJsonIfExists(p) {
27
+ if (!fs.existsSync(p)) return null;
28
+ const txt = stripBom(fs.readFileSync(p, "utf8"));
29
+ if (!txt.trim()) return null;
30
+ return parseJsonRelaxed(txt, p);
31
+ }
36
32
 
37
33
  function writeJsonPretty(p, obj) {
38
34
  fs.writeFileSync(p, JSON.stringify(obj, null, 2) + os.EOL, "utf8");
@@ -420,8 +416,26 @@ function updateShellConfig({ publicKey, secretKey, baseUrl, userId }) {
420
416
  }
421
417
 
422
418
  async function main() {
423
- const args = parseArgs(process.argv.slice(2));
424
- const setEnv = !args["no-set-env"];
419
+ 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
+
438
+ const setEnv = !args["no-set-env"];
425
439
  const skipPluginInstall =
426
440
  !!(args["skip-plugin-install"] || args.skipNpmInstall || process.env.OPENCODE_SKIP_PLUGIN_INSTALL);
427
441
 
@@ -0,0 +1,39 @@
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
+ }