oh-langfuse 0.1.11 → 0.1.12

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
@@ -43,6 +43,8 @@ npm 发布后运行:
43
43
  npx oh-langfuse@latest
44
44
  ```
45
45
 
46
+ 建议始终带 `@latest` 运行,避免 npx 使用本地缓存里的旧版本。
47
+
46
48
  交互界面支持:
47
49
 
48
50
  - `Up` / `Down`:移动选项;
@@ -74,15 +76,15 @@ oh-langfuse check codex
74
76
  oh-langfuse setup --dry-run
75
77
  ```
76
78
 
77
- 本仓内也可以直接运行脚本:
79
+ 本仓内也可以直接运行脚本。面向用户排查时,推荐使用 `npx oh-langfuse@latest check ...`,这样不依赖源码目录:
78
80
 
79
81
  ```bash
80
82
  npm run claude:setup
81
83
  npm run opencode:setup
82
84
  npm run codex:setup
83
- npm run claude:check
84
- npm run opencode:check
85
- npm run codex:check
85
+ npx oh-langfuse@latest check claude
86
+ npx oh-langfuse@latest check opencode
87
+ npx oh-langfuse@latest check codex
86
88
  ```
87
89
 
88
90
  ## 各目标工具写入的配置
@@ -97,8 +99,10 @@ npm run codex:check
97
99
 
98
100
  安装并 patch `opencode-plugin-langfuse`,在
99
101
  `~/.config/opencode/opencode.json` 中启用 OpenTelemetry,写入插件用户配置,
100
- 并可写入用户级 `LANGFUSE_*` 环境变量。Windows 下还会生成
101
- `launch-opencode-langfuse.cmd`,用于带环境变量启动 OpenCode。
102
+ 并可写入用户级 `LANGFUSE_*` 环境变量。Windows 下会生成
103
+ `launch-opencode-langfuse.cmd`,Linux/macOS 下会生成
104
+ `launch-opencode-langfuse.sh`,用于在 shell 环境变量未生效时显式带
105
+ Langfuse 配置启动 OpenCode。
102
106
 
103
107
  ### Codex
104
108
 
@@ -122,6 +126,7 @@ Langfuse observation,并把读取偏移记录到 `~/.codex/langfuse/state.json
122
126
  | `LANGFUSE_USER_ID` / `CC_USER_ID` | 用户标识或员工号 |
123
127
  | `CODEX_HOME` | 自定义 Codex home 目录 |
124
128
  | `OPENCODE_SKIP_PLUGIN_INSTALL` | OpenCode 插件已准备好时跳过 npm 安装 |
129
+ | `OPENCODE_NPM_REGISTRY` | OpenCode 插件 npm 安装源,例如 `https://registry.npmmirror.com` |
125
130
 
126
131
  交互界面不会明文打印 secret key。
127
132
 
package/bin/cli.js CHANGED
@@ -467,10 +467,10 @@ async function collectLangfuseConfig(rl, { requireUserId = false } = {}) {
467
467
  labelValue("Public Key", config.publicKey, t.blue),
468
468
  labelValue("Secret Key", "configured", t.teal)
469
469
  ]);
470
- config.userId = await askText(rl, "User ID / employee number, for example h00613222", {
471
- defaultValue: config.userId,
472
- required: requireUserId
473
- });
470
+ config.userId = await askText(rl, "User ID / employee number, for example h00613222", {
471
+ defaultValue: requireUserId ? "" : config.userId,
472
+ required: requireUserId
473
+ });
474
474
  return config;
475
475
  }
476
476
 
@@ -480,14 +480,18 @@ async function collectSharedConfig(rl, options) {
480
480
  return await collectLangfuseConfig(rl, { requireUserId: true });
481
481
  }
482
482
 
483
- function commonLangfuseArgs(config) {
484
- return [
485
- `--langfuseBaseUrl=${config.baseUrl}`,
486
- `--publicKey=${config.publicKey}`,
487
- `--secretKey=${config.secretKey}`,
488
- ...(hasValue(config.userId) ? [`--userId=${config.userId}`] : [])
489
- ];
490
- }
483
+ function commonLangfuseArgs(config) {
484
+ return [
485
+ `--langfuseBaseUrl=${config.baseUrl}`,
486
+ `--publicKey=${config.publicKey}`,
487
+ `--secretKey=${config.secretKey}`,
488
+ ...(hasValue(config.userId) ? [`--userId=${config.userId}`] : [])
489
+ ];
490
+ }
491
+
492
+ function optionalInstallerArgs(options) {
493
+ return [...(hasValue(options.pipIndexUrl) ? [`--pipIndexUrl=${options.pipIndexUrl}`] : [])];
494
+ }
491
495
 
492
496
  async function confirmAction(rl, title, rows, options) {
493
497
  clearScreen();
@@ -506,7 +510,7 @@ async function setupClaude(rl, options) {
506
510
  const defaultHook = path.join(rootDir, "langfuse_hook.py");
507
511
  const pyPath = fs.existsSync(defaultHook) ? defaultHook : path.join(rootDir, "langfuse_hook.py");
508
512
 
509
- const args = [...commonLangfuseArgs(config), `--pyPath=${pyPath}`];
513
+ const args = [...commonLangfuseArgs(config), ...optionalInstallerArgs(options), `--pyPath=${pyPath}`];
510
514
  const ok = await confirmAction(
511
515
  rl,
512
516
  "Claude Code Langfuse Setup",
@@ -522,7 +526,7 @@ async function setupClaude(rl, options) {
522
526
  return runNodeScript("langfuse-setup.mjs", args, options);
523
527
  }
524
528
 
525
- async function setupOpenCode(rl, options) {
529
+ async function setupOpenCode(rl, options) {
526
530
  while (!(await ensureEnvironment(rl, "opencode", options))) {}
527
531
  clearScreen();
528
532
  renderBrand(options);
@@ -531,9 +535,10 @@ async function setupOpenCode(rl, options) {
531
535
  const installPlugin = true;
532
536
  const cliPath = "";
533
537
 
534
- const args = [
535
- ...commonLangfuseArgs(config),
536
- ...(!setEnv ? ["--no-set-env"] : []),
538
+ const args = [
539
+ ...commonLangfuseArgs(config),
540
+ ...(hasValue(options.npmRegistry) ? [`--npmRegistry=${options.npmRegistry}`] : []),
541
+ ...(!setEnv ? ["--no-set-env"] : []),
537
542
  ...(!installPlugin ? ["--skip-plugin-install"] : []),
538
543
  ...(hasValue(cliPath) ? [`--cmd=${cliPath}`] : [])
539
544
  ];
@@ -561,7 +566,7 @@ async function setupCodex(rl, options) {
561
566
  const defaultHook = path.join(rootDir, "codex_langfuse_notify.py");
562
567
  const pyPath = fs.existsSync(defaultHook) ? defaultHook : path.join(rootDir, "codex_langfuse_notify.py");
563
568
 
564
- const args = [...commonLangfuseArgs(config), `--pyPath=${pyPath}`];
569
+ const args = [...commonLangfuseArgs(config), ...optionalInstallerArgs(options), `--pyPath=${pyPath}`];
565
570
  const ok = await confirmAction(
566
571
  rl,
567
572
  "Codex Langfuse Setup",
@@ -682,8 +687,10 @@ async function setupLangfuseMenu(rl, options) {
682
687
 
683
688
  function printHelp() {
684
689
  renderBrand({ dryRun: false });
685
- console.log("");
686
- renderSection("Usage", [
690
+ console.log("");
691
+ renderSection("Usage", [
692
+ "npx oh-langfuse@latest",
693
+ "npx oh-langfuse@latest check opencode",
687
694
  "oh-langfuse",
688
695
  "oh-langfuse setup",
689
696
  "oh-langfuse setup claude",
@@ -695,15 +702,21 @@ function printHelp() {
695
702
  "oh-langfuse check opencode",
696
703
  "oh-langfuse check codex"
697
704
  ]);
698
- renderSection("Options", [
699
- `${paint("--dry-run", t.gold)} Preview actions without writing files or installing packages.`,
700
- `${paint("--help", t.gold)} Show this help.`
701
- ]);
702
- }
705
+ renderSection("Options", [
706
+ `${paint("--dry-run", t.gold)} Preview actions without writing files or installing packages.`,
707
+ `${paint("--npmRegistry=URL", t.gold)} Use a specific npm registry when installing the OpenCode plugin.`,
708
+ `${paint("--pipIndexUrl=URL", t.gold)} Use a specific pip index for Python Langfuse installs.`,
709
+ `${paint("--help", t.gold)} Show this help.`
710
+ ]);
711
+ }
703
712
 
704
- async function main() {
705
- const args = parseArgs(process.argv.slice(2));
706
- const options = { dryRun: !!args["dry-run"] };
713
+ async function main() {
714
+ const args = parseArgs(process.argv.slice(2));
715
+ const options = {
716
+ dryRun: !!args["dry-run"],
717
+ npmRegistry: args.npmRegistry || "",
718
+ pipIndexUrl: args.pipIndexUrl || ""
719
+ };
707
720
  const [cmd, target] = args._;
708
721
 
709
722
  if (args.help || args.h) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-langfuse",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Use npm scripts to configure Claude Code / OpenCode / Codex with Langfuse tracing.",
@@ -49,9 +49,13 @@ function venvPython(codexHome) {
49
49
  : path.join(codexHome, "langfuse-venv", "bin", "python");
50
50
  }
51
51
 
52
- function configHasNotify(configText) {
53
- return /^\s*notify\s*=.*codex_langfuse_notify\.py/m.test(configText);
54
- }
52
+ function configHasNotify(configText) {
53
+ return /^\s*notify\s*=.*codex_langfuse_notify\.py/m.test(configText);
54
+ }
55
+
56
+ function addResult(results, item, ok, detail, fix = "") {
57
+ results.push({ item, ok, detail, fix });
58
+ }
55
59
 
56
60
  function main() {
57
61
  const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), ".codex");
@@ -66,36 +70,66 @@ function main() {
66
70
  const python = commandOk(process.platform === "win32" ? "python" : "python3", ["--version"]);
67
71
  const langfuseImport = commandOk(hookPython, ["-c", "import langfuse; print('langfuse ok')"]);
68
72
 
69
- const results = [
70
- { item: "Codex home", ok: fs.existsSync(codexHome), detail: codexHome },
71
- { item: "config.toml", ok: fs.existsSync(configPath), detail: configPath },
72
- {
73
- item: "notify hook configured",
74
- ok: configHasNotify(configText),
75
- detail: configHasNotify(configText) ? "OK" : "missing notify entry for codex_langfuse_notify.py",
76
- },
77
- { item: "hook script", ok: fs.existsSync(hookPath), detail: hookPath },
78
- { item: "Langfuse config", ok: !!langfuseConfig, detail: langfuseConfigPath },
79
- {
80
- item: "Langfuse keys",
81
- ok: !!(langfuseConfig?.publicKey && langfuseConfig?.secretKey),
82
- detail: langfuseConfig?.publicKey ? "configured" : "missing",
83
- },
84
- { item: "sessions directory", ok: fs.existsSync(sessionsDir), detail: sessionsDir },
85
- { item: "latest session JSONL", ok: !!latestSession, detail: latestSession || "not found" },
86
- { item: "Python", ok: python.ok, detail: python.detail || "not found" },
87
- { item: "Langfuse venv Python", ok: fs.existsSync(hookPython), detail: hookPython },
88
- { item: "Python langfuse package", ok: langfuseImport.ok, detail: langfuseImport.detail || "not importable from venv" },
89
- ];
73
+ const logPath = path.join(codexHome, "langfuse", "codex_langfuse_notify.log");
74
+ const logText = fs.existsSync(logPath) ? stripBom(fs.readFileSync(logPath, "utf8")) : "";
75
+ const recentLogHasError = /Traceback|ERROR|Exception|Failed/i.test(logText.slice(-4000));
76
+
77
+ const results = [];
78
+ addResult(results, "Codex home", fs.existsSync(codexHome), codexHome, "Run: npx oh-langfuse@latest setup codex");
79
+ addResult(results, "config.toml", fs.existsSync(configPath), configPath, "Run setup again to update Codex config.");
80
+ addResult(
81
+ results,
82
+ "notify hook configured",
83
+ configHasNotify(configText),
84
+ configHasNotify(configText) ? "OK" : "missing notify entry for codex_langfuse_notify.py",
85
+ "Run setup again, then restart Codex."
86
+ );
87
+ addResult(results, "hook script", fs.existsSync(hookPath), hookPath, "Run setup again to install the notify hook.");
88
+ addResult(results, "Langfuse config", !!langfuseConfig, langfuseConfigPath, "Run setup again to write Langfuse credentials.");
89
+ addResult(
90
+ results,
91
+ "Langfuse keys",
92
+ !!(langfuseConfig?.publicKey && langfuseConfig?.secretKey),
93
+ langfuseConfig?.publicKey ? "configured" : "missing",
94
+ "Provide publicKey/secretKey or use the defaults from the installer."
95
+ );
96
+ addResult(results, "sessions directory", fs.existsSync(sessionsDir), sessionsDir, "Start Codex once so session JSONL files are created.");
97
+ addResult(results, "latest session JSONL", !!latestSession, latestSession || "not found", "Start a Codex conversation, then check again.");
98
+ addResult(results, "Python", python.ok, python.detail || "not found", "Install Python and pip, then rerun setup.");
99
+ addResult(results, "Langfuse venv Python", fs.existsSync(hookPython), hookPython, "Run setup again; on Linux install python3-venv if venv creation fails.");
100
+ addResult(
101
+ results,
102
+ "Python langfuse package",
103
+ langfuseImport.ok,
104
+ langfuseImport.detail || "not importable from venv",
105
+ "Run setup again, or pass --pipIndexUrl=https://pypi.tuna.tsinghua.edu.cn/simple."
106
+ );
107
+ addResult(
108
+ results,
109
+ "notify log recent errors",
110
+ !recentLogHasError,
111
+ fs.existsSync(logPath) ? logPath : "log not created yet",
112
+ "Open the log path above and inspect the newest error after running Codex."
113
+ );
90
114
 
91
115
  const w = Math.max(...results.map((r) => r.item.length)) + 2;
92
116
  const pad = (s, n) => (s.length >= n ? s : s + " ".repeat(n - s.length));
93
- for (const r of results) {
94
- const status = r.ok ? "OK " : "BAD";
95
- console.log(`${status} ${pad(r.item, w)} ${r.detail}`);
96
- }
97
-
98
- process.exit(results.some((r) => !r.ok) ? 2 : 0);
99
- }
117
+ const failed = [];
118
+ for (const r of results) {
119
+ const status = r.ok ? "OK " : "BAD";
120
+ console.log(`${status} ${pad(r.item, w)} ${r.detail}`);
121
+ if (!r.ok) failed.push(r);
122
+ }
123
+
124
+ if (failed.length) {
125
+ console.log("");
126
+ console.log("Fix suggestions:");
127
+ for (const r of failed) {
128
+ if (r.fix) console.log(`- ${r.item}: ${r.fix}`);
129
+ }
130
+ }
131
+
132
+ process.exit(failed.length ? 2 : 0);
133
+ }
100
134
 
101
135
  main();
@@ -50,7 +50,7 @@ function pythonExecutableInVenv(venvDir) {
50
50
  : path.join(venvDir, "bin", "python");
51
51
  }
52
52
 
53
- function createOrUpdateLangfuseVenv({ baseDir }) {
53
+ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.tsinghua.edu.cn/simple" }) {
54
54
  const venvDir = path.join(baseDir, "langfuse-venv");
55
55
  const pythonCmd = process.platform === "win32" ? "python" : "python3";
56
56
  const venvPython = pythonExecutableInVenv(venvDir);
@@ -71,12 +71,12 @@ function createOrUpdateLangfuseVenv({ baseDir }) {
71
71
  try {
72
72
  execFileSync(
73
73
  venvPython,
74
- ["-m", "pip", "install", "-U", "langfuse", "-i", "https://pypi.tuna.tsinghua.edu.cn/simple"],
75
- { stdio: "inherit" }
76
- );
77
- } catch (e) {
78
- throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i https://pypi.tuna.tsinghua.edu.cn/simple`);
79
- }
74
+ ["-m", "pip", "install", "-U", "langfuse", "-i", pipIndexUrl],
75
+ { stdio: "inherit" }
76
+ );
77
+ } catch (e) {
78
+ throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i ${pipIndexUrl}`);
79
+ }
80
80
 
81
81
  return venvPython;
82
82
  }
@@ -128,7 +128,11 @@ async function main() {
128
128
  process.env.LANGFUSE_BASEURL ||
129
129
  process.env.LANGFUSE_HOST ||
130
130
  "http://120.46.221.227:3000";
131
- const userId = args.userId || args.userid || process.env.LANGFUSE_USER_ID || process.env.CC_USER_ID || "";
131
+ const userId = args.userId || args.userid || "";
132
+ if (!userId || typeof userId !== "string") {
133
+ throw new Error("缺少参数:--userId=你的工号");
134
+ }
135
+ const pipIndexUrl = args.pipIndexUrl || process.env.LANGFUSE_PIP_INDEX_URL || "https://pypi.tuna.tsinghua.edu.cn/simple";
132
136
 
133
137
  if (!publicKey || !secretKey) {
134
138
  throw new Error("Missing Langfuse keys: provide --publicKey and --secretKey or set LANGFUSE_PUBLIC_KEY/LANGFUSE_SECRET_KEY.");
@@ -159,9 +163,9 @@ async function main() {
159
163
  });
160
164
 
161
165
  const pythonCmd = process.platform === "win32" ? "python" : "python3";
162
- const notifyPython = args["skip-pip-install"]
163
- ? pythonCmd
164
- : createOrUpdateLangfuseVenv({ baseDir: codexHome });
166
+ const notifyPython = args["skip-pip-install"]
167
+ ? pythonCmd
168
+ : createOrUpdateLangfuseVenv({ baseDir: codexHome, pipIndexUrl });
165
169
  updateCodexNotify(configPath, [notifyPython, destHook]);
166
170
 
167
171
  console.log(`Updated Codex notify hook: ${configPath}`);
@@ -124,7 +124,7 @@ function pythonExecutableInVenv(venvDir) {
124
124
  : path.join(venvDir, "bin", "python");
125
125
  }
126
126
 
127
- function createOrUpdateLangfuseVenv({ baseDir }) {
127
+ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.tsinghua.edu.cn/simple" }) {
128
128
  const venvDir = path.join(baseDir, "langfuse-venv");
129
129
  const pythonCmd = process.platform === "win32" ? "python" : "python3";
130
130
  const venvPython = pythonExecutableInVenv(venvDir);
@@ -145,12 +145,12 @@ function createOrUpdateLangfuseVenv({ baseDir }) {
145
145
  try {
146
146
  execFileSync(
147
147
  venvPython,
148
- ["-m", "pip", "install", "-U", "langfuse", "-i", "https://pypi.tuna.tsinghua.edu.cn/simple"],
149
- { stdio: "inherit" }
150
- );
151
- } catch (e) {
152
- throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i https://pypi.tuna.tsinghua.edu.cn/simple`);
153
- }
148
+ ["-m", "pip", "install", "-U", "langfuse", "-i", pipIndexUrl],
149
+ { stdio: "inherit" }
150
+ );
151
+ } catch (e) {
152
+ throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i ${pipIndexUrl}`);
153
+ }
154
154
 
155
155
  return venvPython;
156
156
  }
@@ -158,10 +158,10 @@ function createOrUpdateLangfuseVenv({ baseDir }) {
158
158
  async function main() {
159
159
  const args = parseArgs(process.argv.slice(2));
160
160
 
161
- const userId = args.userId || args.userid || process.env.CC_USER_ID;
162
- if (!userId || typeof userId !== "string") {
163
- throw new Error("缺少参数:--userId=你的工号(或设置环境变量 CC_USER_ID)");
164
- }
161
+ const userId = args.userId || args.userid;
162
+ if (!userId || typeof userId !== "string") {
163
+ throw new Error("缺少参数:--userId=你的工号");
164
+ }
165
165
 
166
166
  const langfuseHost =
167
167
  args.langfuseHost ||
@@ -171,8 +171,9 @@ async function main() {
171
171
 
172
172
  const publicKey =
173
173
  args.publicKey || process.env.LANGFUSE_PUBLIC_KEY || "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
174
- const secretKey =
175
- args.secretKey || process.env.LANGFUSE_SECRET_KEY || "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
174
+ const secretKey =
175
+ args.secretKey || process.env.LANGFUSE_SECRET_KEY || "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
176
+ const pipIndexUrl = args.pipIndexUrl || process.env.LANGFUSE_PIP_INDEX_URL || "https://pypi.tuna.tsinghua.edu.cn/simple";
176
177
  if (!publicKey || !secretKey) {
177
178
  throw new Error("缺少 Langfuse Key:请提供 --publicKey=... --secretKey=...(或设置环境变量 LANGFUSE_PUBLIC_KEY/LANGFUSE_SECRET_KEY)");
178
179
  }
@@ -225,7 +226,7 @@ async function main() {
225
226
  if (nextPyText !== pyText) {
226
227
  await fsp.writeFile(pyPath, nextPyText, "utf8");
227
228
  }
228
- const hookPython = createOrUpdateLangfuseVenv({ baseDir: claudeDir });
229
+ const hookPython = createOrUpdateLangfuseVenv({ baseDir: claudeDir, pipIndexUrl });
229
230
 
230
231
  // 4) 合并写入 settings.json
231
232
  const settingsPath = path.join(claudeDir, "settings.json");
@@ -1,95 +1,197 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
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;
8
- }
9
-
10
- function readJsonIfExists(p) {
11
- if (!fs.existsSync(p)) return null;
12
- const txt = stripBom(fs.readFileSync(p, "utf8"));
13
- if (!txt.trim()) return null;
14
- return JSON.parse(txt);
15
- }
16
-
17
- function hasPlugin(pluginField, name) {
18
- if (Array.isArray(pluginField)) return pluginField.includes(name);
19
- if (typeof pluginField === "string") return pluginField === name;
20
- return false;
21
- }
22
-
23
- function main() {
24
- const home = os.homedir();
25
- const opencodeDir = path.join(home, ".config", "opencode");
26
- const pkgDir = path.join(opencodeDir, "node_modules", "opencode-plugin-langfuse");
27
- const pluginDest = path.join(opencodeDir, "plugins", "opencode-plugin-langfuse");
28
- const langfusePluginPath = "./plugins/opencode-plugin-langfuse";
29
- const opencodeJsonPath = path.join(opencodeDir, "opencode.json");
30
-
31
- const results = [];
32
-
33
- results.push({ item: "OpenCode 配置目录", ok: fs.existsSync(opencodeDir), detail: opencodeDir });
34
-
35
- const settings = readJsonIfExists(opencodeJsonPath);
36
- results.push({
37
- item: "opencode.json",
38
- ok: !!settings,
39
- detail: opencodeJsonPath
40
- });
41
-
42
- const otOk = !!(settings?.experimental?.openTelemetry === true);
43
- results.push({
44
- item: "experimental.openTelemetry",
45
- ok: otOk,
46
- detail: otOk ? "true" : String(settings?.experimental?.openTelemetry ?? "缺失")
47
- });
48
-
49
- const plOk = hasPlugin(settings?.plugin, langfusePluginPath);
50
- results.push({
51
- item: `plugin 包含 ${langfusePluginPath}`,
52
- ok: plOk,
53
- detail: JSON.stringify(settings?.plugin ?? null)
54
- });
55
-
56
- results.push({
57
- item: "npm 包目录 node_modules/opencode-plugin-langfuse",
58
- ok: fs.existsSync(pkgDir),
59
- detail: pkgDir
60
- });
61
-
62
- results.push({
63
- item: "plugins 目录本地插件",
64
- ok: fs.existsSync(path.join(pluginDest, "package.json")),
65
- detail: pluginDest
66
- });
67
-
68
- const launcherPath = path.join(opencodeDir, "launch-opencode-langfuse.cmd");
1
+ import fs from "node:fs";
2
+ import path from "node:path";
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;
8
+ }
9
+
10
+ function readTextIfExists(p) {
11
+ if (!fs.existsSync(p)) return "";
12
+ return stripBom(fs.readFileSync(p, "utf8"));
13
+ }
14
+
15
+ function readJsonIfExists(p) {
16
+ const txt = readTextIfExists(p);
17
+ if (!txt.trim()) return null;
18
+ return JSON.parse(txt);
19
+ }
20
+
21
+ function hasPlugin(pluginField, name) {
22
+ if (Array.isArray(pluginField)) return pluginField.includes(name);
23
+ if (typeof pluginField === "string") return pluginField === name;
24
+ return false;
25
+ }
26
+
27
+ function shellConfigPath() {
28
+ const shell = process.env.SHELL || "/bin/bash";
29
+ return path.join(os.homedir(), shell.includes("zsh") ? ".zshrc" : ".bashrc");
30
+ }
31
+
32
+ function isPatchedPlugin(distIndexPath) {
33
+ const code = readTextIfExists(distIndexPath);
34
+ return (
35
+ code.includes("LangfuseSpanProcessor") &&
36
+ code.includes("LANGFUSE_PUBLIC_KEY") &&
37
+ code.includes("createUserIdSpanProcessor")
38
+ );
39
+ }
40
+
41
+ function addResult(results, item, ok, detail, fix = "") {
42
+ results.push({ item, ok, detail, fix });
43
+ }
44
+
45
+ function main() {
46
+ const home = os.homedir();
47
+ const opencodeDir = path.join(home, ".config", "opencode");
48
+ const pkgDir = path.join(opencodeDir, "node_modules", "opencode-plugin-langfuse");
49
+ const pluginDest = path.join(opencodeDir, "plugins", "opencode-plugin-langfuse");
50
+ const langfusePluginPath = "./plugins/opencode-plugin-langfuse";
51
+ const opencodeJsonPath = path.join(opencodeDir, "opencode.json");
52
+ const pluginDistIndex = path.join(pluginDest, "dist", "index.js");
53
+ const nodeModulesDistIndex = path.join(pkgDir, "dist", "index.js");
54
+ const userConfigPath = path.join(home, ".config", "opencode-plugin-langfuse", "config.json");
55
+ const windowsLauncherPath = path.join(opencodeDir, "launch-opencode-langfuse.cmd");
56
+ const unixLauncherPath = path.join(opencodeDir, "launch-opencode-langfuse.sh");
57
+
58
+ const results = [];
59
+
60
+ addResult(
61
+ results,
62
+ "OpenCode config dir",
63
+ fs.existsSync(opencodeDir),
64
+ opencodeDir,
65
+ "Run: npx oh-langfuse@latest setup opencode"
66
+ );
67
+
68
+ const settings = readJsonIfExists(opencodeJsonPath);
69
+ addResult(results, "opencode.json", !!settings, opencodeJsonPath, "Run setup again to create OpenCode config.");
70
+
71
+ const otOk = !!(settings?.experimental?.openTelemetry === true);
72
+ addResult(
73
+ results,
74
+ "experimental.openTelemetry",
75
+ otOk,
76
+ otOk ? "true" : String(settings?.experimental?.openTelemetry ?? "missing"),
77
+ "Run: npx oh-langfuse@latest setup opencode"
78
+ );
79
+
80
+ const plOk = hasPlugin(settings?.plugin, langfusePluginPath);
81
+ addResult(
82
+ results,
83
+ `plugin contains ${langfusePluginPath}`,
84
+ plOk,
85
+ JSON.stringify(settings?.plugin ?? null),
86
+ "Run setup again so opencode.json points at the local plugin copy."
87
+ );
88
+
89
+ addResult(
90
+ results,
91
+ "npm package",
92
+ fs.existsSync(path.join(pkgDir, "package.json")),
93
+ pkgDir,
94
+ "Install may have failed. Try: npx oh-langfuse@latest setup opencode --npmRegistry=https://registry.npmmirror.com"
95
+ );
96
+
97
+ addResult(
98
+ results,
99
+ "local plugin copy",
100
+ fs.existsSync(path.join(pluginDest, "package.json")),
101
+ pluginDest,
102
+ "Run setup again; OpenCode loads ./plugins/opencode-plugin-langfuse from opencode.json."
103
+ );
104
+
105
+ addResult(
106
+ results,
107
+ "local plugin patch",
108
+ isPatchedPlugin(pluginDistIndex),
109
+ pluginDistIndex,
110
+ "Run setup again to patch Langfuse credentials/userId injection into the plugin."
111
+ );
112
+
113
+ addResult(
114
+ results,
115
+ "node_modules plugin patch",
116
+ isPatchedPlugin(nodeModulesDistIndex),
117
+ nodeModulesDistIndex,
118
+ "Run setup again to patch the installed npm package before copying it."
119
+ );
120
+
121
+ const userConfig = readJsonIfExists(userConfigPath);
122
+ addResult(
123
+ results,
124
+ "OpenCode Langfuse user config",
125
+ !!(userConfig?.userId || userConfig?.usrid),
126
+ userConfigPath,
127
+ "Run setup again and enter userId when prompted."
128
+ );
129
+
130
+ const hasPublicKey = !!process.env.LANGFUSE_PUBLIC_KEY;
131
+ const hasSecretKey = !!process.env.LANGFUSE_SECRET_KEY;
132
+ const hasBaseUrl = !!process.env.LANGFUSE_BASEURL;
133
+ addResult(
134
+ results,
135
+ "current terminal LANGFUSE_*",
136
+ hasPublicKey && hasSecretKey && hasBaseUrl,
137
+ `publicKey=${hasPublicKey ? "set" : "missing"}, secretKey=${hasSecretKey ? "set" : "missing"}, baseUrl=${
138
+ hasBaseUrl ? process.env.LANGFUSE_BASEURL : "missing"
139
+ }`,
140
+ process.platform === "win32"
141
+ ? "Open a new terminal or launch with ~/.config/opencode/launch-opencode-langfuse.cmd."
142
+ : "Open a new terminal, source your shell rc file, or launch with ~/.config/opencode/launch-opencode-langfuse.sh."
143
+ );
144
+
69
145
  if (process.platform === "win32") {
70
- results.push({
71
- item: "启动脚本 launch-opencode-langfuse.cmd",
72
- ok: fs.existsSync(launcherPath),
73
- detail: launcherPath
74
- });
75
- }
76
-
77
- const hasPk = !!process.env.LANGFUSE_PUBLIC_KEY;
78
- results.push({
79
- item: "当前进程 LANGFUSE_PUBLIC_KEY(任选终端验证)",
80
- ok: hasPk,
81
- detail: hasPk ? "已设置(本终端可见)" : "未设置:若你只从桌面打开 OpenCode,请先运行 npm run opencode:langfuse:setup 或使用 launch-opencode-langfuse.cmd"
82
- });
83
-
84
- const w = Math.max(...results.map((r) => r.item.length)) + 2;
85
- for (const r of results) {
86
- const status = r.ok ? "OK " : "BAD";
87
- const pad = (s, n) => (s.length >= n ? s : s + " ".repeat(n - s.length));
88
- console.log(`${status} ${pad(r.item, w)} ${r.detail}`);
89
- }
90
-
91
- const failed = results.filter((r) => !r.ok);
92
- process.exit(failed.length ? 2 : 0);
93
- }
94
-
95
- main();
146
+ addResult(
147
+ results,
148
+ "launcher cmd",
149
+ fs.existsSync(windowsLauncherPath),
150
+ windowsLauncherPath,
151
+ "Run setup again to create a launcher with LANGFUSE_* variables."
152
+ );
153
+ } else {
154
+ const shellRc = shellConfigPath();
155
+ const shellRcText = readTextIfExists(shellRc);
156
+ addResult(
157
+ results,
158
+ "shell rc LANGFUSE_* block",
159
+ shellRcText.includes("# === Langfuse OpenCode Setup ===") && shellRcText.includes("LANGFUSE_PUBLIC_KEY"),
160
+ shellRc,
161
+ "Run setup again, then open a new terminal or source the shown shell rc file."
162
+ );
163
+ addResult(
164
+ results,
165
+ "launcher sh",
166
+ fs.existsSync(unixLauncherPath),
167
+ unixLauncherPath,
168
+ "Run setup again to create a launcher with LANGFUSE_* variables."
169
+ );
170
+ }
171
+
172
+ const w = Math.max(...results.map((r) => r.item.length)) + 2;
173
+ const failed = [];
174
+ for (const r of results) {
175
+ const status = r.ok ? "OK " : "BAD";
176
+ const pad = (s, n) => (s.length >= n ? s : s + " ".repeat(n - s.length));
177
+ console.log(`${status} ${pad(r.item, w)} ${r.detail}`);
178
+ if (!r.ok) failed.push(r);
179
+ }
180
+
181
+ if (failed.length) {
182
+ console.log("");
183
+ console.log("Fix suggestions:");
184
+ for (const r of failed) {
185
+ if (r.fix) console.log(`- ${r.item}: ${r.fix}`);
186
+ }
187
+ }
188
+
189
+ process.exit(failed.length ? 2 : 0);
190
+ }
191
+
192
+ try {
193
+ main();
194
+ } catch (e) {
195
+ console.error(e?.message || String(e));
196
+ process.exit(1);
197
+ }
@@ -29,8 +29,11 @@ function main() {
29
29
  args.publicKey || process.env.LANGFUSE_PUBLIC_KEY || "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
30
30
  const secretKey =
31
31
  args.secretKey || process.env.LANGFUSE_SECRET_KEY || "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
32
- const baseUrl = args.langfuseBaseUrl || process.env.LANGFUSE_BASEURL || "http://120.46.221.227:3000";
33
- const userId = args.userId || args.userid || process.env.LANGFUSE_USER_ID || "";
32
+ const baseUrl = args.langfuseBaseUrl || process.env.LANGFUSE_BASEURL || "http://120.46.221.227:3000";
33
+ const userId = args.userId || args.userid || "";
34
+ if (!userId) {
35
+ throw new Error("Missing userId. Run with --userId=your-id.");
36
+ }
34
37
 
35
38
  // 1) 先执行 setup:默认会写入 Windows 用户级 LANGFUSE_*(从桌面/开始菜单启动 OpenCode 也能读到)
36
39
  const scriptsDir = path.dirname(fileURLToPath(import.meta.url));
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import os from "node:os";
4
- import { spawnSync } from "node:child_process";
4
+ import { spawn, spawnSync } from "node:child_process";
5
5
 
6
6
  function parseArgs(argv) {
7
7
  const args = {};
@@ -202,9 +202,9 @@ function syncPluginPackageToLocalPlugins({ pkgDir, pluginDest, pluginsDir }) {
202
202
  fs.cpSync(pkgDir, pluginDest, { recursive: true });
203
203
  }
204
204
 
205
- function writeWindowsLauncherCmd(opencodeDir, { publicKey, secretKey, baseUrl, userId }) {
206
- if (process.platform !== "win32") return null;
207
- const p = path.join(opencodeDir, "launch-opencode-langfuse.cmd");
205
+ function writeWindowsLauncherCmd(opencodeDir, { publicKey, secretKey, baseUrl, userId }) {
206
+ if (process.platform !== "win32") return null;
207
+ const p = path.join(opencodeDir, "launch-opencode-langfuse.cmd");
208
208
  const cmd = ["@echo off", "REM Auto-generated by scripts/opencode-langfuse-setup.mjs"];
209
209
  cmd.push(`set LANGFUSE_PUBLIC_KEY=${publicKey}`);
210
210
  cmd.push(`set LANGFUSE_SECRET_KEY=${secretKey}`);
@@ -215,9 +215,35 @@ function writeWindowsLauncherCmd(opencodeDir, { publicKey, secretKey, baseUrl, u
215
215
  cmd.push(" exit /b %ERRORLEVEL%");
216
216
  cmd.push(")");
217
217
  cmd.push("opencode %*");
218
- fs.writeFileSync(p, cmd.join("\r\n") + "\r\n", "utf8");
219
- return p;
220
- }
218
+ fs.writeFileSync(p, cmd.join("\r\n") + "\r\n", "utf8");
219
+ return p;
220
+ }
221
+
222
+ function shQuote(s) {
223
+ return `'${String(s).replace(/'/g, "'\"'\"'")}'`;
224
+ }
225
+
226
+ function writeUnixLauncherSh(opencodeDir, { publicKey, secretKey, baseUrl, userId }) {
227
+ if (process.platform === "win32") return null;
228
+ const p = path.join(opencodeDir, "launch-opencode-langfuse.sh");
229
+ const lines = [
230
+ "#!/usr/bin/env sh",
231
+ "# Auto-generated by scripts/opencode-langfuse-setup.mjs",
232
+ "set -eu",
233
+ `export LANGFUSE_PUBLIC_KEY=${shQuote(publicKey)}`,
234
+ `export LANGFUSE_SECRET_KEY=${shQuote(secretKey)}`,
235
+ `export LANGFUSE_BASEURL=${shQuote(baseUrl)}`,
236
+ userId ? `export LANGFUSE_USER_ID=${shQuote(userId)}` : null,
237
+ 'if [ -x "$HOME/.opencode/bin/opencode" ]; then',
238
+ ' exec "$HOME/.opencode/bin/opencode" "$@"',
239
+ "fi",
240
+ 'exec opencode "$@"',
241
+ ""
242
+ ].filter(Boolean);
243
+ fs.writeFileSync(p, lines.join("\n"), "utf8");
244
+ fs.chmodSync(p, 0o755);
245
+ return p;
246
+ }
221
247
 
222
248
  function getNpmExecutable() {
223
249
  if (process.platform === "win32") {
@@ -278,14 +304,54 @@ function printNpmDiagnostics() {
278
304
  } catch (_) {}
279
305
  }
280
306
 
281
- function runNpmInstallOrThrow({ opencodeDir, pkgName = "opencode-plugin-langfuse" }) {
282
- const npmArgs = ["install", pkgName, "--prefix", opencodeDir];
283
- const cliJs = getNpmCliJsPath();
284
- console.log(`使用 npm:${fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable()}`);
285
- const r = runNpmCapture(npmArgs);
286
- if (r.stdout) process.stdout.write(r.stdout);
287
- if (r.stderr) process.stderr.write(r.stderr);
288
- if (!r.error && r.status === 0) return;
307
+ function runNpmInstallCapture(npmArgs) {
308
+ return new Promise((resolve) => {
309
+ const cliJs = getNpmCliJsPath();
310
+ const npmExe = getNpmExecutable();
311
+ const command = fs.existsSync(cliJs) ? process.execPath : npmExe;
312
+ const args = fs.existsSync(cliJs) ? [cliJs, ...npmArgs] : npmArgs;
313
+ const useShell = process.platform === "win32" && !fs.existsSync(cliJs) && /\.(cmd|bat)$/i.test(npmExe);
314
+ const child = spawn(command, args, {
315
+ encoding: "utf8",
316
+ shell: useShell,
317
+ windowsHide: true
318
+ });
319
+ let stdout = "";
320
+ let stderr = "";
321
+ const started = Date.now();
322
+ const timer = setInterval(() => {
323
+ const secs = Math.round((Date.now() - started) / 1000);
324
+ console.error(`npm install still running (${secs}s). Slow networks can take a few minutes...`);
325
+ }, 15000);
326
+ child.stdout?.on("data", (chunk) => {
327
+ const text = String(chunk);
328
+ stdout += text;
329
+ process.stdout.write(text);
330
+ });
331
+ child.stderr?.on("data", (chunk) => {
332
+ const text = String(chunk);
333
+ stderr += text;
334
+ process.stderr.write(text);
335
+ });
336
+ child.on("error", (error) => {
337
+ clearInterval(timer);
338
+ resolve({ status: null, stdout, stderr, error });
339
+ });
340
+ child.on("close", (status) => {
341
+ clearInterval(timer);
342
+ resolve({ status, stdout, stderr });
343
+ });
344
+ });
345
+ }
346
+
347
+ async function runNpmInstallOrThrow({ opencodeDir, pkgName = "opencode-plugin-langfuse", npmRegistry = "" }) {
348
+ const npmArgs = ["install", pkgName, "--prefix", opencodeDir];
349
+ if (npmRegistry) npmArgs.push("--registry", npmRegistry);
350
+ const cliJs = getNpmCliJsPath();
351
+ console.log(`使用 npm:${fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable()}`);
352
+ console.log("Installing OpenCode Langfuse plugin. This can take a few minutes on slow networks...");
353
+ const r = await runNpmInstallCapture(npmArgs);
354
+ if (!r.error && r.status === 0) return;
289
355
  printNpmDiagnostics();
290
356
  const npmLabel = fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable();
291
357
  throw new Error(
@@ -353,7 +419,7 @@ function updateShellConfig({ publicKey, secretKey, baseUrl, userId }) {
353
419
  return configPath;
354
420
  }
355
421
 
356
- function main() {
422
+ async function main() {
357
423
  const args = parseArgs(process.argv.slice(2));
358
424
  const setEnv = !args["no-set-env"];
359
425
  const skipPluginInstall =
@@ -363,32 +429,37 @@ function main() {
363
429
  args.publicKey || process.env.LANGFUSE_PUBLIC_KEY || "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
364
430
  const secretKey =
365
431
  args.secretKey || process.env.LANGFUSE_SECRET_KEY || "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
366
- const baseUrl =
367
- args.langfuseBaseUrl || process.env.LANGFUSE_BASEURL || "http://120.46.221.227:3000";
368
- const userId = args.userId || args.userid || process.env.LANGFUSE_USER_ID || process.env.OPENCODE_USER_ID || "";
432
+ const baseUrl =
433
+ args.langfuseBaseUrl || process.env.LANGFUSE_BASEURL || "http://120.46.221.227:3000";
434
+ const userId = args.userId || args.userid || "";
435
+ if (!userId || typeof userId !== "string") {
436
+ throw new Error("缺少参数:--userId=你的工号");
437
+ }
438
+ const npmRegistry = args.npmRegistry || process.env.OPENCODE_NPM_REGISTRY || process.env.NPM_CONFIG_REGISTRY || "";
369
439
 
370
440
  const home = os.homedir();
371
- const opencodeDir = path.join(home, ".config", "opencode");
372
- const pluginsDir = path.join(opencodeDir, "plugins");
373
- const pkgDir = path.join(opencodeDir, "node_modules", "opencode-plugin-langfuse");
374
- const pluginDest = path.join(pluginsDir, "opencode-plugin-langfuse");
441
+ const opencodeDir = path.join(home, ".config", "opencode");
442
+ const pluginsDir = path.join(opencodeDir, "plugins");
443
+ const pkgDir = path.join(opencodeDir, "node_modules", "opencode-plugin-langfuse");
444
+ const pkgJsonPath = path.join(pkgDir, "package.json");
445
+ const pluginDest = path.join(pluginsDir, "opencode-plugin-langfuse");
375
446
  const opencodeJsonPath = path.join(opencodeDir, "opencode.json");
376
447
 
377
448
  ensureDir(opencodeDir);
378
449
 
379
450
  console.log(`OpenCode 配置目录:${opencodeDir}`);
380
451
 
381
- if (skipPluginInstall && fs.existsSync(pkgDir)) {
382
- console.log(`已跳过 npm install(--skip-plugin-install 且已存在):${pkgDir}`);
383
- } else {
384
- console.log("安装 npm 包:opencode-plugin-langfuse …");
385
- runNpmInstallOrThrow({ opencodeDir });
386
- }
452
+ if (skipPluginInstall && fs.existsSync(pkgJsonPath)) {
453
+ console.log(`已跳过 npm install(--skip-plugin-install 且已存在):${pkgDir}`);
454
+ } else {
455
+ console.log("安装 npm 包:opencode-plugin-langfuse …");
456
+ await runNpmInstallOrThrow({ opencodeDir, npmRegistry });
457
+ }
387
458
 
388
- if (!fs.existsSync(pkgDir)) {
389
- throw new Error(
390
- `未找到已安装包目录:${pkgDir}。请先解决上方 npm 报错,或手动在此目录执行 npm install opencode-plugin-langfuse。`
391
- );
459
+ if (!fs.existsSync(pkgJsonPath)) {
460
+ throw new Error(
461
+ `未找到已安装包目录:${pkgDir}。请先解决上方 npm 报错,或手动在此目录执行 npm install opencode-plugin-langfuse。`
462
+ );
392
463
  }
393
464
 
394
465
  const existing = readJsonIfExists(opencodeJsonPath) ?? {};
@@ -429,11 +500,16 @@ function main() {
429
500
  writeJsonPretty(opencodeJsonPath, merged);
430
501
  console.log(`已更新:${opencodeJsonPath}`);
431
502
 
432
- const launcher = writeWindowsLauncherCmd(opencodeDir, { publicKey, secretKey, baseUrl, userId });
433
- if (launcher) {
503
+ const launcher = writeWindowsLauncherCmd(opencodeDir, { publicKey, secretKey, baseUrl, userId });
504
+ if (launcher) {
434
505
  console.log(`已生成快捷启动脚本(含 LANGFUSE 环境变量):${launcher}`);
435
506
  console.log("若不从当前终端启动,也可双击或用该脚本启动 OpenCode(避免插件读不到密钥)。");
436
- }
507
+ }
508
+ const unixLauncher = writeUnixLauncherSh(opencodeDir, { publicKey, secretKey, baseUrl, userId });
509
+ if (unixLauncher) {
510
+ console.log(`已生成启动脚本(含 LANGFUSE 环境变量):${unixLauncher}`);
511
+ console.log(`如果新终端仍读不到环境变量,可运行:${unixLauncher}`);
512
+ }
437
513
 
438
514
  if (setEnv) {
439
515
  if (process.platform === "win32") {
@@ -468,12 +544,12 @@ function main() {
468
544
  }
469
545
  }
470
546
 
471
- console.log("完成。可运行:`npm run opencode:langfuse:check` 校验。");
472
- }
547
+ console.log("完成。可运行:`npx oh-langfuse@latest check opencode` 校验。");
548
+ }
473
549
 
474
550
  try {
475
- main();
476
- } catch (e) {
551
+ await main();
552
+ } catch (e) {
477
553
  console.error(e?.message || String(e));
478
554
  process.exit(1);
479
555
  }
@@ -57,9 +57,9 @@ echo.
57
57
  call npm run claude:setup -- --userId=%USER_ID% --pyPath=%PY_PATH%
58
58
  if errorlevel 1 exit /b 1
59
59
 
60
- echo.
61
- echo Check:
62
- call npm run claude:check
60
+ echo.
61
+ echo Check:
62
+ call npx oh-langfuse@latest check claude
63
63
  exit /b %errorlevel%
64
64
 
65
65
  :OPENCODE
@@ -72,7 +72,11 @@ echo - NO auto-launch: open opencode in a NEW terminal afterwards
72
72
  echo.
73
73
 
74
74
  set "OC_USER_ID="
75
- set /p OC_USER_ID=Enter userId (optional; press Enter to skip) ^>
75
+ set /p OC_USER_ID=Enter userId (required) ^>
76
+ if "%OC_USER_ID%"=="" (
77
+ echo [ERROR] userId is required.
78
+ exit /b 1
79
+ )
76
80
 
77
81
  echo.
78
82
  echo Write LANGFUSE_* to Windows user env vars (HKCU)?
@@ -94,7 +98,7 @@ set "OC_CMD="
94
98
  set /p OC_CMD=OpenCode CLI path (optional; press Enter to auto-detect) ^>
95
99
 
96
100
  set "SETUP_ARGS="
97
- if not "%OC_USER_ID%"=="" set "SETUP_ARGS=%SETUP_ARGS% --userId=%OC_USER_ID%"
101
+ set "SETUP_ARGS=%SETUP_ARGS% --userId=%OC_USER_ID%"
98
102
  if /i "%OC_SET_ENV%"=="n" set "SETUP_ARGS=%SETUP_ARGS% --no-set-env"
99
103
  if /i "%OC_SET_ENV%"=="no" set "SETUP_ARGS=%SETUP_ARGS% --no-set-env"
100
104
  if /i "%OC_SKIP_INSTALL%"=="y" set "SETUP_ARGS=%SETUP_ARGS% --skip-plugin-install"
@@ -116,12 +120,13 @@ if errorlevel 1 goto :OPENCODE_FAIL
116
120
  echo.
117
121
  echo ============================================
118
122
  echo OpenCode setup finished.
119
- echo Next:
120
- echo 1) Open a NEW terminal (HKCU env vars take effect).
121
- echo 2) Run: opencode
122
- echo Optional launcher:
123
- echo %USERPROFILE%\.config\opencode\launch-opencode-langfuse.cmd
124
- echo ============================================
123
+ echo Next:
124
+ echo 1) Open a NEW terminal (HKCU env vars take effect).
125
+ echo 2) Run: opencode
126
+ echo Optional launcher:
127
+ echo %USERPROFILE%\.config\opencode\launch-opencode-langfuse.cmd
128
+ echo 3) Check: npx oh-langfuse@latest check opencode
129
+ echo ============================================
125
130
  exit /b 0
126
131
 
127
132
  :CODEX
@@ -145,9 +150,9 @@ echo.
145
150
  call npm run codex:setup -- --userId=%CODEX_USER_ID% --pyPath=%CODEX_PY_PATH%
146
151
  if errorlevel 1 exit /b 1
147
152
 
148
- echo.
149
- echo Check:
150
- call npm run codex:check
153
+ echo.
154
+ echo Check:
155
+ call npx oh-langfuse@latest check codex
151
156
  exit /b %errorlevel%
152
157
 
153
158
  :NO_NPM
package/setup-langfuse.sh CHANGED
@@ -44,8 +44,8 @@ case "$choice" in
44
44
  echo "Running: npm run claude:setup -- --userId=${user_id} --pyPath=${py_path}"
45
45
  npm run claude:setup -- --userId="${user_id}" --pyPath="${py_path}"
46
46
  echo
47
- echo "Check: npm run claude:check"
48
- npm run claude:check
47
+ echo "Check: npx oh-langfuse@latest check claude"
48
+ npx oh-langfuse@latest check claude
49
49
  ;;
50
50
 
51
51
  2)
@@ -56,8 +56,12 @@ case "$choice" in
56
56
  echo " - NO auto-launch: you will open opencode in a NEW terminal afterwards"
57
57
  echo
58
58
 
59
- printf "Enter userId (optional; press Enter to skip) > "
60
- IFS= read -r user_id || true
59
+ printf "Enter userId (required) > "
60
+ IFS= read -r user_id || true
61
+ if [ -z "${user_id:-}" ]; then
62
+ echo "[ERROR] userId is required."
63
+ exit 1
64
+ fi
61
65
 
62
66
  echo
63
67
  echo "Skip plugin install (npm install opencode-plugin-langfuse)?"
@@ -71,9 +75,7 @@ case "$choice" in
71
75
  IFS= read -r oc_cmd || true
72
76
 
73
77
  run_args=""
74
- if [ -n "${user_id:-}" ]; then
75
- run_args="${run_args} --userId=${user_id}"
76
- fi
78
+ run_args="${run_args} --userId=${user_id}"
77
79
  case "${skip_install:-}" in
78
80
  y|Y|yes|YES|Yes)
79
81
  run_args="${run_args} --skip-plugin-install"
@@ -94,11 +96,14 @@ case "$choice" in
94
96
 
95
97
  echo
96
98
  echo "============================================"
97
- echo "OpenCode setup finished."
98
- echo "Next:"
99
- echo " 1) Open a NEW terminal."
100
- echo " 2) Run: opencode"
101
- echo "============================================"
99
+ echo "OpenCode setup finished."
100
+ echo "Next:"
101
+ echo " 1) Open a NEW terminal."
102
+ echo " 2) Run: opencode"
103
+ echo " 3) Check: npx oh-langfuse@latest check opencode"
104
+ echo " If LANGFUSE_* is not visible in the new terminal, run:"
105
+ echo " ~/.config/opencode/launch-opencode-langfuse.sh"
106
+ echo "============================================"
102
107
  ;;
103
108
 
104
109
  3)
@@ -119,8 +124,8 @@ case "$choice" in
119
124
  echo "Running: npm run codex:setup -- --userId=${user_id} --pyPath=${py_path}"
120
125
  npm run codex:setup -- --userId="${user_id}" --pyPath="${py_path}"
121
126
  echo
122
- echo "Check: npm run codex:check"
123
- npm run codex:check
127
+ echo "Check: npx oh-langfuse@latest check codex"
128
+ npx oh-langfuse@latest check codex
124
129
  ;;
125
130
 
126
131
  *)