oh-langfuse 0.1.10 → 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 +11 -6
- package/bin/cli.js +41 -28
- package/package.json +1 -1
- package/scripts/codex-langfuse-check.mjs +65 -31
- package/scripts/codex-langfuse-setup.mjs +15 -11
- package/scripts/langfuse-setup.mjs +15 -14
- package/scripts/opencode-langfuse-check.mjs +197 -94
- package/scripts/opencode-langfuse-run.mjs +5 -2
- package/scripts/opencode-langfuse-setup.mjs +146 -69
- package/setup-langfuse.bat +19 -14
- package/setup-langfuse.sh +19 -14
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
|
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
|
-
...(
|
|
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("--
|
|
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 = {
|
|
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
|
@@ -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
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
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",
|
|
75
|
-
{ stdio: "inherit" }
|
|
76
|
-
);
|
|
77
|
-
} catch (e) {
|
|
78
|
-
throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i
|
|
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 ||
|
|
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",
|
|
149
|
-
{ stdio: "inherit" }
|
|
150
|
-
);
|
|
151
|
-
} catch (e) {
|
|
152
|
-
throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i
|
|
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
|
|
162
|
-
if (!userId || typeof userId !== "string") {
|
|
163
|
-
throw new Error("缺少参数:--userId
|
|
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,94 +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
|
|
11
|
-
if (!fs.existsSync(p)) return
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
+
|
|
145
|
+
if (process.platform === "win32") {
|
|
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 ||
|
|
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 = {};
|
|
@@ -188,15 +188,23 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
188
188
|
].join(os.EOL);
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
function patchDistIndexJs(distIndexPath) {
|
|
192
|
-
const code = getPatchedLangfuseDistIndexJs();
|
|
193
|
-
ensureDir(path.dirname(distIndexPath));
|
|
194
|
-
fs.writeFileSync(distIndexPath, code + os.EOL, "utf8");
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function
|
|
198
|
-
|
|
199
|
-
|
|
191
|
+
function patchDistIndexJs(distIndexPath) {
|
|
192
|
+
const code = getPatchedLangfuseDistIndexJs();
|
|
193
|
+
ensureDir(path.dirname(distIndexPath));
|
|
194
|
+
fs.writeFileSync(distIndexPath, code + os.EOL, "utf8");
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function syncPluginPackageToLocalPlugins({ pkgDir, pluginDest, pluginsDir }) {
|
|
198
|
+
ensureDir(pluginsDir);
|
|
199
|
+
if (fs.existsSync(pluginDest)) {
|
|
200
|
+
fs.rmSync(pluginDest, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
fs.cpSync(pkgDir, pluginDest, { recursive: true });
|
|
203
|
+
}
|
|
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");
|
|
200
208
|
const cmd = ["@echo off", "REM Auto-generated by scripts/opencode-langfuse-setup.mjs"];
|
|
201
209
|
cmd.push(`set LANGFUSE_PUBLIC_KEY=${publicKey}`);
|
|
202
210
|
cmd.push(`set LANGFUSE_SECRET_KEY=${secretKey}`);
|
|
@@ -207,9 +215,35 @@ function writeWindowsLauncherCmd(opencodeDir, { publicKey, secretKey, baseUrl, u
|
|
|
207
215
|
cmd.push(" exit /b %ERRORLEVEL%");
|
|
208
216
|
cmd.push(")");
|
|
209
217
|
cmd.push("opencode %*");
|
|
210
|
-
fs.writeFileSync(p, cmd.join("\r\n") + "\r\n", "utf8");
|
|
211
|
-
return p;
|
|
212
|
-
}
|
|
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
|
+
}
|
|
213
247
|
|
|
214
248
|
function getNpmExecutable() {
|
|
215
249
|
if (process.platform === "win32") {
|
|
@@ -270,14 +304,54 @@ function printNpmDiagnostics() {
|
|
|
270
304
|
} catch (_) {}
|
|
271
305
|
}
|
|
272
306
|
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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;
|
|
281
355
|
printNpmDiagnostics();
|
|
282
356
|
const npmLabel = fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable();
|
|
283
357
|
throw new Error(
|
|
@@ -345,7 +419,7 @@ function updateShellConfig({ publicKey, secretKey, baseUrl, userId }) {
|
|
|
345
419
|
return configPath;
|
|
346
420
|
}
|
|
347
421
|
|
|
348
|
-
function main() {
|
|
422
|
+
async function main() {
|
|
349
423
|
const args = parseArgs(process.argv.slice(2));
|
|
350
424
|
const setEnv = !args["no-set-env"];
|
|
351
425
|
const skipPluginInstall =
|
|
@@ -355,56 +429,54 @@ function main() {
|
|
|
355
429
|
args.publicKey || process.env.LANGFUSE_PUBLIC_KEY || "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
|
|
356
430
|
const secretKey =
|
|
357
431
|
args.secretKey || process.env.LANGFUSE_SECRET_KEY || "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
|
|
358
|
-
const baseUrl =
|
|
359
|
-
args.langfuseBaseUrl || process.env.LANGFUSE_BASEURL || "http://120.46.221.227:3000";
|
|
360
|
-
const userId = args.userId || args.userid ||
|
|
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 || "";
|
|
361
439
|
|
|
362
440
|
const home = os.homedir();
|
|
363
|
-
const opencodeDir = path.join(home, ".config", "opencode");
|
|
364
|
-
const pluginsDir = path.join(opencodeDir, "plugins");
|
|
365
|
-
const pkgDir = path.join(opencodeDir, "node_modules", "opencode-plugin-langfuse");
|
|
366
|
-
const
|
|
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");
|
|
367
446
|
const opencodeJsonPath = path.join(opencodeDir, "opencode.json");
|
|
368
447
|
|
|
369
448
|
ensureDir(opencodeDir);
|
|
370
449
|
|
|
371
450
|
console.log(`OpenCode 配置目录:${opencodeDir}`);
|
|
372
451
|
|
|
373
|
-
if (skipPluginInstall && fs.existsSync(
|
|
374
|
-
console.log(`已跳过 npm install(--skip-plugin-install 且已存在):${pkgDir}`);
|
|
375
|
-
} else {
|
|
376
|
-
console.log("安装 npm 包:opencode-plugin-langfuse …");
|
|
377
|
-
runNpmInstallOrThrow({ opencodeDir });
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (!fs.existsSync(
|
|
381
|
-
throw new Error(
|
|
382
|
-
`未找到已安装包目录:${pkgDir}。请先解决上方 npm 报错,或手动在此目录执行 npm install opencode-plugin-langfuse。`
|
|
383
|
-
);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (process.platform === "win32") {
|
|
387
|
-
console.log("Windows:将插件复制到 ~/.config/opencode/plugins/ …");
|
|
388
|
-
ensureDir(pluginsDir);
|
|
389
|
-
if (fs.existsSync(pluginDest)) {
|
|
390
|
-
fs.rmSync(pluginDest, { recursive: true, force: true });
|
|
391
|
-
}
|
|
392
|
-
fs.cpSync(pkgDir, pluginDest, { recursive: true });
|
|
393
|
-
console.log(`已复制到:${pluginDest}`);
|
|
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
|
+
}
|
|
458
|
+
|
|
459
|
+
if (!fs.existsSync(pkgJsonPath)) {
|
|
460
|
+
throw new Error(
|
|
461
|
+
`未找到已安装包目录:${pkgDir}。请先解决上方 npm 报错,或手动在此目录执行 npm install opencode-plugin-langfuse。`
|
|
462
|
+
);
|
|
394
463
|
}
|
|
395
464
|
|
|
396
|
-
const existing = readJsonIfExists(opencodeJsonPath) ?? {};
|
|
397
|
-
// Patch the official plugin to inject userId and make behavior consistent.
|
|
398
|
-
const distIndexInNodeModules = path.join(pkgDir, "dist", "index.js");
|
|
399
|
-
if (fs.existsSync(distIndexInNodeModules)) {
|
|
400
|
-
patchDistIndexJs(distIndexInNodeModules);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
465
|
+
const existing = readJsonIfExists(opencodeJsonPath) ?? {};
|
|
466
|
+
// Patch the official plugin to inject userId and make behavior consistent.
|
|
467
|
+
const distIndexInNodeModules = path.join(pkgDir, "dist", "index.js");
|
|
468
|
+
if (fs.existsSync(distIndexInNodeModules)) {
|
|
469
|
+
patchDistIndexJs(distIndexInNodeModules);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
console.log("Syncing plugin into ~/.config/opencode/plugins/ ...");
|
|
473
|
+
syncPluginPackageToLocalPlugins({ pkgDir, pluginDest, pluginsDir });
|
|
474
|
+
console.log(`Plugin ready at: ${pluginDest}`);
|
|
475
|
+
|
|
476
|
+
const distIndexInPlugins = path.join(pluginDest, "dist", "index.js");
|
|
477
|
+
if (fs.existsSync(distIndexInPlugins)) {
|
|
478
|
+
patchDistIndexJs(distIndexInPlugins);
|
|
479
|
+
}
|
|
408
480
|
|
|
409
481
|
// Write plugin user config to support userId injection.
|
|
410
482
|
if (userId) {
|
|
@@ -428,11 +500,16 @@ function main() {
|
|
|
428
500
|
writeJsonPretty(opencodeJsonPath, merged);
|
|
429
501
|
console.log(`已更新:${opencodeJsonPath}`);
|
|
430
502
|
|
|
431
|
-
const launcher = writeWindowsLauncherCmd(opencodeDir, { publicKey, secretKey, baseUrl, userId });
|
|
432
|
-
if (launcher) {
|
|
503
|
+
const launcher = writeWindowsLauncherCmd(opencodeDir, { publicKey, secretKey, baseUrl, userId });
|
|
504
|
+
if (launcher) {
|
|
433
505
|
console.log(`已生成快捷启动脚本(含 LANGFUSE 环境变量):${launcher}`);
|
|
434
506
|
console.log("若不从当前终端启动,也可双击或用该脚本启动 OpenCode(避免插件读不到密钥)。");
|
|
435
|
-
}
|
|
507
|
+
}
|
|
508
|
+
const unixLauncher = writeUnixLauncherSh(opencodeDir, { publicKey, secretKey, baseUrl, userId });
|
|
509
|
+
if (unixLauncher) {
|
|
510
|
+
console.log(`已生成启动脚本(含 LANGFUSE 环境变量):${unixLauncher}`);
|
|
511
|
+
console.log(`如果新终端仍读不到环境变量,可运行:${unixLauncher}`);
|
|
512
|
+
}
|
|
436
513
|
|
|
437
514
|
if (setEnv) {
|
|
438
515
|
if (process.platform === "win32") {
|
|
@@ -467,12 +544,12 @@ function main() {
|
|
|
467
544
|
}
|
|
468
545
|
}
|
|
469
546
|
|
|
470
|
-
console.log("完成。可运行:`
|
|
471
|
-
}
|
|
547
|
+
console.log("完成。可运行:`npx oh-langfuse@latest check opencode` 校验。");
|
|
548
|
+
}
|
|
472
549
|
|
|
473
550
|
try {
|
|
474
|
-
main();
|
|
475
|
-
} catch (e) {
|
|
551
|
+
await main();
|
|
552
|
+
} catch (e) {
|
|
476
553
|
console.error(e?.message || String(e));
|
|
477
554
|
process.exit(1);
|
|
478
555
|
}
|
package/setup-langfuse.bat
CHANGED
|
@@ -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
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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:
|
|
48
|
-
|
|
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 (
|
|
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
|
-
|
|
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:
|
|
123
|
-
|
|
127
|
+
echo "Check: npx oh-langfuse@latest check codex"
|
|
128
|
+
npx oh-langfuse@latest check codex
|
|
124
129
|
;;
|
|
125
130
|
|
|
126
131
|
*)
|