oh-langfuse 0.1.33 → 0.1.34
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 +3 -1
- package/package.json +1 -1
- package/scripts/opencode-langfuse-setup.mjs +126 -21
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
`oh-langfuse` 是用于给 Claude Code、OpenCode 和 Codex 配置 Langfuse 追踪的命令行工具。它提供交互式安装向导,也支持 `setup` / `check` 直接命令,方便在用户机器上安装、修复和校验配置。
|
|
4
4
|
|
|
5
|
-
当前 npm 版本:`0.1.
|
|
5
|
+
当前 npm 版本:`0.1.34`
|
|
6
6
|
|
|
7
7
|
## 能做什么
|
|
8
8
|
|
|
@@ -40,6 +40,8 @@ npx oh-langfuse@latest check codex
|
|
|
40
40
|
|
|
41
41
|
OpenCode 生成的 `launch-opencode-langfuse.*` 会在启动 agent 前执行一次更新检测:如果 npm 上的 `oh-langfuse@latest` 高于本机 runtime 版本,会提示是否更新;用户确认后才会运行 `npx oh-langfuse@latest update opencode`。Claude Code 和 Codex 也会生成对应 launcher:
|
|
42
42
|
|
|
43
|
+
OpenCode setup 还会生成 `~/.config/opencode/bin/opencode` / `opencode.cmd` 命令包装器,并把该目录写入用户 PATH。新开终端后直接运行 `opencode`,也会先检测 `oh-langfuse` runtime 是否需要更新,再转发到真实 OpenCode CLI。
|
|
44
|
+
|
|
43
45
|
- `~/.claude/launch-claude-langfuse.cmd` / `~/.claude/launch-claude-langfuse.sh`
|
|
44
46
|
- `~/.codex/launch-codex-langfuse.cmd` / `~/.codex/launch-codex-langfuse.sh`
|
|
45
47
|
|
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ import { fileURLToPath } from "node:url";
|
|
|
6
6
|
import { parseJsonRelaxed, stripBom } from "./json-utils.mjs";
|
|
7
7
|
import { shouldSuppressAgentLogLine } from "./log-filter-utils.mjs";
|
|
8
8
|
import { writeRuntimeInstallRecord } from "./runtime-state-utils.mjs";
|
|
9
|
+
import { resolveOpencodeCli } from "./resolve-opencode-cli.mjs";
|
|
9
10
|
|
|
10
11
|
const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
|
|
11
12
|
const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
|
|
@@ -101,9 +102,13 @@ function removePlugins(existing, names) {
|
|
|
101
102
|
return [];
|
|
102
103
|
}
|
|
103
104
|
|
|
104
|
-
function psQuote(s) {
|
|
105
|
-
return `'${String(s).replace(/'/g, "''")}'`;
|
|
106
|
-
}
|
|
105
|
+
function psQuote(s) {
|
|
106
|
+
return `'${String(s).replace(/'/g, "''")}'`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function cmdQuote(s) {
|
|
110
|
+
return `"${String(s).replace(/"/g, '""')}"`;
|
|
111
|
+
}
|
|
107
112
|
|
|
108
113
|
function writeLangfusePluginUserConfig({ userId }) {
|
|
109
114
|
const home = os.homedir();
|
|
@@ -605,6 +610,48 @@ function shQuote(s) {
|
|
|
605
610
|
return `'${String(s).replace(/'/g, "'\"'\"'")}'`;
|
|
606
611
|
}
|
|
607
612
|
|
|
613
|
+
function writeOpencodeCommandShim(opencodeDir, { publicKey, secretKey, baseUrl, userId, realOpencodeCli }) {
|
|
614
|
+
const shimDir = path.join(opencodeDir, "bin");
|
|
615
|
+
ensureDir(shimDir);
|
|
616
|
+
if (process.platform === "win32") {
|
|
617
|
+
const shim = path.join(shimDir, "opencode.cmd");
|
|
618
|
+
const cmd = ["@echo off", "REM Auto-generated by scripts/opencode-langfuse-setup.mjs"];
|
|
619
|
+
cmd.push("set OH_LANGFUSE_OPENCODE_SHIM=1");
|
|
620
|
+
cmd.push(`set LANGFUSE_PUBLIC_KEY=${publicKey}`);
|
|
621
|
+
cmd.push(`set LANGFUSE_SECRET_KEY=${secretKey}`);
|
|
622
|
+
cmd.push(`set LANGFUSE_BASEURL=${baseUrl}`);
|
|
623
|
+
if (userId) cmd.push(`set LANGFUSE_USER_ID=${userId}`);
|
|
624
|
+
cmd.push("where npx >nul 2>nul");
|
|
625
|
+
cmd.push("if %ERRORLEVEL% EQU 0 (");
|
|
626
|
+
cmd.push(" call npx -y oh-langfuse@latest auto-update opencode --skip-check");
|
|
627
|
+
cmd.push(")");
|
|
628
|
+
cmd.push(`call ${cmdQuote(realOpencodeCli)} %*`);
|
|
629
|
+
cmd.push("exit /b %ERRORLEVEL%");
|
|
630
|
+
fs.writeFileSync(shim, cmd.join("\r\n") + "\r\n", "utf8");
|
|
631
|
+
return { shim, shimDir };
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
const shim = path.join(shimDir, "opencode");
|
|
635
|
+
const lines = [
|
|
636
|
+
"#!/usr/bin/env sh",
|
|
637
|
+
"# Auto-generated by scripts/opencode-langfuse-setup.mjs",
|
|
638
|
+
"set -eu",
|
|
639
|
+
"export OH_LANGFUSE_OPENCODE_SHIM=1",
|
|
640
|
+
`export LANGFUSE_PUBLIC_KEY=${shQuote(publicKey)}`,
|
|
641
|
+
`export LANGFUSE_SECRET_KEY=${shQuote(secretKey)}`,
|
|
642
|
+
`export LANGFUSE_BASEURL=${shQuote(baseUrl)}`,
|
|
643
|
+
userId ? `export LANGFUSE_USER_ID=${shQuote(userId)}` : null,
|
|
644
|
+
'if command -v npx >/dev/null 2>&1; then',
|
|
645
|
+
' npx -y oh-langfuse@latest auto-update opencode --skip-check || true',
|
|
646
|
+
"fi",
|
|
647
|
+
`exec ${shQuote(realOpencodeCli)} "$@"`,
|
|
648
|
+
""
|
|
649
|
+
].filter(Boolean);
|
|
650
|
+
fs.writeFileSync(shim, lines.join("\n"), "utf8");
|
|
651
|
+
fs.chmodSync(shim, 0o755);
|
|
652
|
+
return { shim, shimDir };
|
|
653
|
+
}
|
|
654
|
+
|
|
608
655
|
function writeUnixLauncherSh(opencodeDir, { publicKey, secretKey, baseUrl, userId }) {
|
|
609
656
|
if (process.platform === "win32") return null;
|
|
610
657
|
const p = path.join(opencodeDir, "launch-opencode-langfuse.sh");
|
|
@@ -733,13 +780,29 @@ function runNpmInstallCapture(npmArgs) {
|
|
|
733
780
|
});
|
|
734
781
|
}
|
|
735
782
|
|
|
783
|
+
function npmInstallLooksLikeMissingVersion(result) {
|
|
784
|
+
const text = `${result?.stdout || ""}\n${result?.stderr || ""}\n${result?.error?.message || ""}`.toLowerCase();
|
|
785
|
+
return text.includes("etarget") || text.includes("notarget") || text.includes("no matching version found");
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
function isOfficialNpmRegistry(registry) {
|
|
789
|
+
return /^https?:\/\/registry\.npmjs\.org\/?$/i.test(String(registry || "").trim());
|
|
790
|
+
}
|
|
791
|
+
|
|
736
792
|
async function runNpmInstallOrThrow({ opencodeDir, pkgName = "opencode-plugin-langfuse", npmRegistry = "" }) {
|
|
737
|
-
const npmArgs = ["install", pkgName, "--prefix", opencodeDir];
|
|
793
|
+
const npmArgs = ["install", pkgName, "--prefix", opencodeDir, "--package-lock=false", "--no-save"];
|
|
738
794
|
if (npmRegistry) npmArgs.push("--registry", npmRegistry);
|
|
739
795
|
const cliJs = getNpmCliJsPath();
|
|
740
796
|
console.log(`使用 npm:${fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable()}`);
|
|
741
797
|
console.log("Installing OpenCode Langfuse plugin. This can take a few minutes on slow networks...");
|
|
742
|
-
|
|
798
|
+
let r = await runNpmInstallCapture(npmArgs);
|
|
799
|
+
if (!r.error && r.status !== 0 && npmInstallLooksLikeMissingVersion(r) && !isOfficialNpmRegistry(npmRegistry)) {
|
|
800
|
+
console.error("");
|
|
801
|
+
console.error("npm registry appears to be missing a package version. Retrying with https://registry.npmjs.org/ ...");
|
|
802
|
+
const retryArgs = ["install", pkgName, "--prefix", opencodeDir, "--package-lock=false", "--no-save", "--registry", "https://registry.npmjs.org/"];
|
|
803
|
+
r = await runNpmInstallCapture(retryArgs);
|
|
804
|
+
if (!r.error && r.status === 0) return;
|
|
805
|
+
}
|
|
743
806
|
if (!r.error && r.status === 0) return;
|
|
744
807
|
printNpmDiagnostics();
|
|
745
808
|
const npmLabel = fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable();
|
|
@@ -749,18 +812,45 @@ async function runNpmInstallOrThrow({ opencodeDir, pkgName = "opencode-plugin-la
|
|
|
749
812
|
);
|
|
750
813
|
}
|
|
751
814
|
|
|
752
|
-
function setWindowsUserEnv({ publicKey, secretKey, baseUrl }) {
|
|
753
|
-
const cmd = [
|
|
815
|
+
function setWindowsUserEnv({ publicKey, secretKey, baseUrl }) {
|
|
816
|
+
const cmd = [
|
|
754
817
|
"$ErrorActionPreference = 'Stop';",
|
|
755
818
|
`[Environment]::SetEnvironmentVariable('LANGFUSE_PUBLIC_KEY', ${psQuote(publicKey)}, 'User');`,
|
|
756
819
|
`[Environment]::SetEnvironmentVariable('LANGFUSE_SECRET_KEY', ${psQuote(secretKey)}, 'User');`,
|
|
757
820
|
`[Environment]::SetEnvironmentVariable('LANGFUSE_BASEURL', ${psQuote(baseUrl)}, 'User');`
|
|
758
821
|
].join(" ");
|
|
759
822
|
const r = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
|
|
760
|
-
if (r.status !== 0) {
|
|
761
|
-
throw new Error("写入用户级环境变量失败(PowerShell SetEnvironmentVariable)。");
|
|
762
|
-
}
|
|
763
|
-
}
|
|
823
|
+
if (r.status !== 0) {
|
|
824
|
+
throw new Error("写入用户级环境变量失败(PowerShell SetEnvironmentVariable)。");
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function prependWindowsUserPath(dir) {
|
|
829
|
+
if (process.platform !== "win32") return false;
|
|
830
|
+
const cmd = [
|
|
831
|
+
"$ErrorActionPreference = 'Stop';",
|
|
832
|
+
`$dir = ${psQuote(dir)};`,
|
|
833
|
+
"$current = [Environment]::GetEnvironmentVariable('Path', 'User');",
|
|
834
|
+
"$parts = @();",
|
|
835
|
+
"if ($current) { $parts = $current -split ';' | Where-Object { $_ -and $_.Trim() } }",
|
|
836
|
+
"$exists = $false;",
|
|
837
|
+
"foreach ($part in $parts) { if ([string]::Equals($part.TrimEnd('\\'), $dir.TrimEnd('\\'), [StringComparison]::OrdinalIgnoreCase)) { $exists = $true } }",
|
|
838
|
+
"if (-not $exists) {",
|
|
839
|
+
" $next = (@($dir) + $parts) -join ';';",
|
|
840
|
+
" [Environment]::SetEnvironmentVariable('Path', $next, 'User');",
|
|
841
|
+
"}"
|
|
842
|
+
].join(" ");
|
|
843
|
+
const r = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
|
|
844
|
+
if (r.status !== 0) {
|
|
845
|
+
throw new Error("写入用户 PATH 失败:无法把 OpenCode Langfuse shim 加入 PATH。");
|
|
846
|
+
}
|
|
847
|
+
const currentParts = String(process.env.PATH || "").split(path.delimiter);
|
|
848
|
+
const normalized = dir.replace(/[\\/]+$/, "").toLowerCase();
|
|
849
|
+
if (!currentParts.some((part) => part && part.replace(/[\\/]+$/, "").toLowerCase() === normalized)) {
|
|
850
|
+
process.env.PATH = `${dir}${path.delimiter}${process.env.PATH || ""}`;
|
|
851
|
+
}
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
764
854
|
|
|
765
855
|
function getShellConfigPath() {
|
|
766
856
|
const shell = process.env.SHELL || "/bin/bash";
|
|
@@ -770,7 +860,7 @@ function getShellConfigPath() {
|
|
|
770
860
|
return path.join(os.homedir(), ".bashrc");
|
|
771
861
|
}
|
|
772
862
|
|
|
773
|
-
function updateShellConfig({ publicKey, secretKey, baseUrl, userId }) {
|
|
863
|
+
function updateShellConfig({ publicKey, secretKey, baseUrl, userId, pathPrepend = "" }) {
|
|
774
864
|
const configPath = getShellConfigPath();
|
|
775
865
|
const marker = "# === Langfuse OpenCode Setup ===";
|
|
776
866
|
const endMarker = "# === End Langfuse OpenCode Setup ===";
|
|
@@ -793,11 +883,12 @@ function updateShellConfig({ publicKey, secretKey, baseUrl, userId }) {
|
|
|
793
883
|
const envBlock = [
|
|
794
884
|
"",
|
|
795
885
|
marker,
|
|
796
|
-
`export LANGFUSE_PUBLIC_KEY="${publicKey}"`,
|
|
797
|
-
`export LANGFUSE_SECRET_KEY="${secretKey}"`,
|
|
798
|
-
`export LANGFUSE_BASEURL="${baseUrl}"`,
|
|
799
|
-
userId ? `export LANGFUSE_USER_ID="${userId}"` : null,
|
|
800
|
-
|
|
886
|
+
`export LANGFUSE_PUBLIC_KEY="${publicKey}"`,
|
|
887
|
+
`export LANGFUSE_SECRET_KEY="${secretKey}"`,
|
|
888
|
+
`export LANGFUSE_BASEURL="${baseUrl}"`,
|
|
889
|
+
userId ? `export LANGFUSE_USER_ID="${userId}"` : null,
|
|
890
|
+
pathPrepend ? `export PATH="${pathPrepend}:$PATH"` : null,
|
|
891
|
+
endMarker,
|
|
801
892
|
""
|
|
802
893
|
].filter(Boolean).join("\n");
|
|
803
894
|
|
|
@@ -888,7 +979,8 @@ async function main() {
|
|
|
888
979
|
plugin: mergePluginList(basePlugins, langfusePluginPath)
|
|
889
980
|
};
|
|
890
981
|
const merged = deepMerge(existing, desired);
|
|
891
|
-
writeJsonPretty(opencodeJsonPath, merged);
|
|
982
|
+
writeJsonPretty(opencodeJsonPath, merged);
|
|
983
|
+
const realOpencodeCli = resolveOpencodeCli(args.cmd);
|
|
892
984
|
console.log(`已更新:${opencodeJsonPath}`);
|
|
893
985
|
|
|
894
986
|
const launcher = writeWindowsLauncherCmd(opencodeDir, { publicKey, secretKey, baseUrl, userId });
|
|
@@ -902,6 +994,15 @@ async function main() {
|
|
|
902
994
|
printCommandHint("如果新终端仍读不到环境变量,可运行:", unixLauncher);
|
|
903
995
|
}
|
|
904
996
|
|
|
997
|
+
let opencodeShim = null;
|
|
998
|
+
if (realOpencodeCli) {
|
|
999
|
+
opencodeShim = writeOpencodeCommandShim(opencodeDir, { publicKey, secretKey, baseUrl, userId, realOpencodeCli });
|
|
1000
|
+
console.log(`OpenCode command shim ready: ${opencodeShim.shim}`);
|
|
1001
|
+
console.log(`Real OpenCode CLI: ${realOpencodeCli}`);
|
|
1002
|
+
} else {
|
|
1003
|
+
console.log("OpenCode CLI not found; skipped opencode command shim.");
|
|
1004
|
+
}
|
|
1005
|
+
|
|
905
1006
|
let shellConfigPathWritten = "";
|
|
906
1007
|
if (setEnv) {
|
|
907
1008
|
if (process.platform === "win32") {
|
|
@@ -912,12 +1013,16 @@ async function main() {
|
|
|
912
1013
|
"$ErrorActionPreference = 'Stop';",
|
|
913
1014
|
`[Environment]::SetEnvironmentVariable('LANGFUSE_USER_ID', ${psQuote(userId)}, 'User');`
|
|
914
1015
|
].join(" ");
|
|
915
|
-
spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
|
|
916
|
-
}
|
|
1016
|
+
spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
|
|
1017
|
+
}
|
|
1018
|
+
if (opencodeShim?.shimDir) {
|
|
1019
|
+
prependWindowsUserPath(opencodeShim.shimDir);
|
|
1020
|
+
console.log(`已将 OpenCode Langfuse shim 加入用户 PATH:${opencodeShim.shimDir}`);
|
|
1021
|
+
}
|
|
917
1022
|
console.log("提示:新开一个终端后环境变量才会对应用生效。");
|
|
918
1023
|
} else {
|
|
919
1024
|
console.log("正在写入环境变量到 shell 配置文件 …");
|
|
920
|
-
const configPath = updateShellConfig({ publicKey, secretKey, baseUrl, userId });
|
|
1025
|
+
const configPath = updateShellConfig({ publicKey, secretKey, baseUrl, userId, pathPrepend: opencodeShim?.shimDir || "" });
|
|
921
1026
|
shellConfigPathWritten = configPath;
|
|
922
1027
|
console.log(`已更新:${configPath}`);
|
|
923
1028
|
|