oh-langfuse 0.1.32 → 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 +22 -1
- package/codex_langfuse_notify.py +2 -1
- package/langfuse_hook.py +2 -1
- package/package.json +1 -1
- package/scripts/opencode-langfuse-setup.mjs +147 -22
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
|
|
|
@@ -51,6 +53,25 @@ npx oh-langfuse@latest auto-update claude
|
|
|
51
53
|
npx oh-langfuse@latest auto-update codex
|
|
52
54
|
```
|
|
53
55
|
|
|
56
|
+
## Skill 使用量统计
|
|
57
|
+
|
|
58
|
+
工具会为每次识别到的 skill 使用额外写入一条 observation:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
Skill Use / <skill_name>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Dashboard 中统计各 skill 使用量可配置:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
View: Observations
|
|
68
|
+
Metric: Count
|
|
69
|
+
Filter: Observation Name contains Skill Use /
|
|
70
|
+
Breakdown Dimension: Observation Name
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`AI Interaction` 仍然保留 `skill_use_count`、`skill_names`、`skill_names_json` 和 `skill_names_csv`,用于查看单次交互的效率汇总。为避免极端情况下产生过多 observation,OpenCode 每次交互最多记录 20 个去重后的 skill 使用事件。
|
|
74
|
+
|
|
54
75
|
本地开发运行:
|
|
55
76
|
|
|
56
77
|
```bash
|
package/codex_langfuse_notify.py
CHANGED
|
@@ -655,13 +655,14 @@ def emit_codex_turn(
|
|
|
655
655
|
|
|
656
656
|
for skill in skill_usages:
|
|
657
657
|
with langfuse.start_as_current_observation(
|
|
658
|
-
name=f"Skill Use
|
|
658
|
+
name=f"Skill Use / {skill['name']}",
|
|
659
659
|
metadata={
|
|
660
660
|
"source": "codex",
|
|
661
661
|
"user_id": user_id or "",
|
|
662
662
|
"session_id": session_id,
|
|
663
663
|
"interaction_id": interaction_meta["interaction_id"],
|
|
664
664
|
"skill_name": skill["name"],
|
|
665
|
+
"skill_use_count": 1,
|
|
665
666
|
"skill_namespace": skill["skill_namespace"],
|
|
666
667
|
"detected_by": skill["detected_by"],
|
|
667
668
|
"turn_number": turn_num,
|
package/langfuse_hook.py
CHANGED
|
@@ -689,13 +689,14 @@ def emit_turn(
|
|
|
689
689
|
|
|
690
690
|
for skill in skill_usages:
|
|
691
691
|
with langfuse.start_as_current_observation(
|
|
692
|
-
name=f"Skill Use
|
|
692
|
+
name=f"Skill Use / {skill['name']}",
|
|
693
693
|
metadata={
|
|
694
694
|
"source": "claude",
|
|
695
695
|
"user_id": user_id or "",
|
|
696
696
|
"session_id": session_id,
|
|
697
697
|
"interaction_id": interaction_meta["interaction_id"],
|
|
698
698
|
"skill_name": skill["name"],
|
|
699
|
+
"skill_use_count": 1,
|
|
699
700
|
"skill_namespace": skill["skill_namespace"],
|
|
700
701
|
"detected_by": skill["detected_by"],
|
|
701
702
|
"turn_number": turn_num,
|
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();
|
|
@@ -300,6 +305,7 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
300
305
|
" }",
|
|
301
306
|
" return out;",
|
|
302
307
|
"};",
|
|
308
|
+
"const MAX_SKILL_USE_SPANS_PER_INTERACTION = 20;",
|
|
303
309
|
"",
|
|
304
310
|
"const escapeRegExp = (value) => String(value).replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');",
|
|
305
311
|
"",
|
|
@@ -460,6 +466,23 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
460
466
|
" }",
|
|
461
467
|
" set.add(activity.toolCallId || `${kind}:${set.size + 1}`);",
|
|
462
468
|
" };",
|
|
469
|
+
" const emitSkillUseSpans = ({ skillNames, sessionId, messageId, interactionId }) => {",
|
|
470
|
+
" for (const skillName of skillNames.slice(0, MAX_SKILL_USE_SPANS_PER_INTERACTION)) {",
|
|
471
|
+
" const span = metricsTracer.startSpan(`Skill Use / ${skillName}`);",
|
|
472
|
+
' span.setAttribute("oh.langfuse.source", "opencode");',
|
|
473
|
+
' span.setAttribute("oh.langfuse.user_id", userId || "");',
|
|
474
|
+
' span.setAttribute("oh.langfuse.metrics_schema_version", "1.0");',
|
|
475
|
+
' span.setAttribute("langfuse.observation.metadata.source", "opencode");',
|
|
476
|
+
' span.setAttribute("langfuse.observation.metadata.user_id", userId || "");',
|
|
477
|
+
' span.setAttribute("langfuse.observation.metadata.session_id", sessionId || "unknown");',
|
|
478
|
+
' span.setAttribute("langfuse.observation.metadata.message_id", messageId || "unknown");',
|
|
479
|
+
' span.setAttribute("langfuse.observation.metadata.interaction_id", interactionId);',
|
|
480
|
+
' span.setAttribute("langfuse.observation.metadata.metrics_schema_version", "1.0");',
|
|
481
|
+
' span.setAttribute("langfuse.observation.metadata.skill_name", skillName);',
|
|
482
|
+
' span.setAttribute("langfuse.observation.metadata.skill_use_count", 1);',
|
|
483
|
+
" span.end();",
|
|
484
|
+
" }",
|
|
485
|
+
" };",
|
|
463
486
|
"",
|
|
464
487
|
" const recordInteractionMetric = (event) => {",
|
|
465
488
|
" const payload = eventPayload(event);",
|
|
@@ -496,7 +519,8 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
496
519
|
' span.setAttribute("langfuse.observation.metadata.source", "opencode");',
|
|
497
520
|
' span.setAttribute("langfuse.observation.metadata.user_id", userId || "");',
|
|
498
521
|
' span.setAttribute("langfuse.observation.metadata.session_id", sessionId || "unknown");',
|
|
499
|
-
'
|
|
522
|
+
' const interactionId = `opencode:${userId || "unknown"}:${sessionId || "unknown"}:${messageId}`;',
|
|
523
|
+
' span.setAttribute("langfuse.observation.metadata.interaction_id", interactionId);',
|
|
500
524
|
' span.setAttribute("langfuse.observation.metadata.metrics_schema_version", "1.0");',
|
|
501
525
|
' span.setAttribute("langfuse.observation.metadata.interaction_count", 1);',
|
|
502
526
|
' span.setAttribute("langfuse.observation.metadata.user_message_count", 1);',
|
|
@@ -515,6 +539,7 @@ function getPatchedLangfuseDistIndexJs() {
|
|
|
515
539
|
' if (tokenMetrics.reasoning !== undefined) span.setAttribute("langfuse.observation.metadata.reasoning_tokens", tokenMetrics.reasoning);',
|
|
516
540
|
' if (text) span.setAttribute("langfuse.observation.metadata.output_text_preview", text.slice(0, 512));',
|
|
517
541
|
" span.end();",
|
|
542
|
+
" emitSkillUseSpans({ skillNames, sessionId, messageId, interactionId });",
|
|
518
543
|
" messageTextById.delete(messageId);",
|
|
519
544
|
" skillNamesByMessageId.delete(messageId);",
|
|
520
545
|
" skillNamesBySessionId.delete(sessionId);",
|
|
@@ -585,6 +610,48 @@ function shQuote(s) {
|
|
|
585
610
|
return `'${String(s).replace(/'/g, "'\"'\"'")}'`;
|
|
586
611
|
}
|
|
587
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
|
+
|
|
588
655
|
function writeUnixLauncherSh(opencodeDir, { publicKey, secretKey, baseUrl, userId }) {
|
|
589
656
|
if (process.platform === "win32") return null;
|
|
590
657
|
const p = path.join(opencodeDir, "launch-opencode-langfuse.sh");
|
|
@@ -713,13 +780,29 @@ function runNpmInstallCapture(npmArgs) {
|
|
|
713
780
|
});
|
|
714
781
|
}
|
|
715
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
|
+
|
|
716
792
|
async function runNpmInstallOrThrow({ opencodeDir, pkgName = "opencode-plugin-langfuse", npmRegistry = "" }) {
|
|
717
|
-
const npmArgs = ["install", pkgName, "--prefix", opencodeDir];
|
|
793
|
+
const npmArgs = ["install", pkgName, "--prefix", opencodeDir, "--package-lock=false", "--no-save"];
|
|
718
794
|
if (npmRegistry) npmArgs.push("--registry", npmRegistry);
|
|
719
795
|
const cliJs = getNpmCliJsPath();
|
|
720
796
|
console.log(`使用 npm:${fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable()}`);
|
|
721
797
|
console.log("Installing OpenCode Langfuse plugin. This can take a few minutes on slow networks...");
|
|
722
|
-
|
|
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
|
+
}
|
|
723
806
|
if (!r.error && r.status === 0) return;
|
|
724
807
|
printNpmDiagnostics();
|
|
725
808
|
const npmLabel = fs.existsSync(cliJs) ? `node ${cliJs}` : getNpmExecutable();
|
|
@@ -729,18 +812,45 @@ async function runNpmInstallOrThrow({ opencodeDir, pkgName = "opencode-plugin-la
|
|
|
729
812
|
);
|
|
730
813
|
}
|
|
731
814
|
|
|
732
|
-
function setWindowsUserEnv({ publicKey, secretKey, baseUrl }) {
|
|
733
|
-
const cmd = [
|
|
815
|
+
function setWindowsUserEnv({ publicKey, secretKey, baseUrl }) {
|
|
816
|
+
const cmd = [
|
|
734
817
|
"$ErrorActionPreference = 'Stop';",
|
|
735
818
|
`[Environment]::SetEnvironmentVariable('LANGFUSE_PUBLIC_KEY', ${psQuote(publicKey)}, 'User');`,
|
|
736
819
|
`[Environment]::SetEnvironmentVariable('LANGFUSE_SECRET_KEY', ${psQuote(secretKey)}, 'User');`,
|
|
737
820
|
`[Environment]::SetEnvironmentVariable('LANGFUSE_BASEURL', ${psQuote(baseUrl)}, 'User');`
|
|
738
821
|
].join(" ");
|
|
739
822
|
const r = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
|
|
740
|
-
if (r.status !== 0) {
|
|
741
|
-
throw new Error("写入用户级环境变量失败(PowerShell SetEnvironmentVariable)。");
|
|
742
|
-
}
|
|
743
|
-
}
|
|
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
|
+
}
|
|
744
854
|
|
|
745
855
|
function getShellConfigPath() {
|
|
746
856
|
const shell = process.env.SHELL || "/bin/bash";
|
|
@@ -750,7 +860,7 @@ function getShellConfigPath() {
|
|
|
750
860
|
return path.join(os.homedir(), ".bashrc");
|
|
751
861
|
}
|
|
752
862
|
|
|
753
|
-
function updateShellConfig({ publicKey, secretKey, baseUrl, userId }) {
|
|
863
|
+
function updateShellConfig({ publicKey, secretKey, baseUrl, userId, pathPrepend = "" }) {
|
|
754
864
|
const configPath = getShellConfigPath();
|
|
755
865
|
const marker = "# === Langfuse OpenCode Setup ===";
|
|
756
866
|
const endMarker = "# === End Langfuse OpenCode Setup ===";
|
|
@@ -773,11 +883,12 @@ function updateShellConfig({ publicKey, secretKey, baseUrl, userId }) {
|
|
|
773
883
|
const envBlock = [
|
|
774
884
|
"",
|
|
775
885
|
marker,
|
|
776
|
-
`export LANGFUSE_PUBLIC_KEY="${publicKey}"`,
|
|
777
|
-
`export LANGFUSE_SECRET_KEY="${secretKey}"`,
|
|
778
|
-
`export LANGFUSE_BASEURL="${baseUrl}"`,
|
|
779
|
-
userId ? `export LANGFUSE_USER_ID="${userId}"` : null,
|
|
780
|
-
|
|
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,
|
|
781
892
|
""
|
|
782
893
|
].filter(Boolean).join("\n");
|
|
783
894
|
|
|
@@ -868,7 +979,8 @@ async function main() {
|
|
|
868
979
|
plugin: mergePluginList(basePlugins, langfusePluginPath)
|
|
869
980
|
};
|
|
870
981
|
const merged = deepMerge(existing, desired);
|
|
871
|
-
writeJsonPretty(opencodeJsonPath, merged);
|
|
982
|
+
writeJsonPretty(opencodeJsonPath, merged);
|
|
983
|
+
const realOpencodeCli = resolveOpencodeCli(args.cmd);
|
|
872
984
|
console.log(`已更新:${opencodeJsonPath}`);
|
|
873
985
|
|
|
874
986
|
const launcher = writeWindowsLauncherCmd(opencodeDir, { publicKey, secretKey, baseUrl, userId });
|
|
@@ -882,6 +994,15 @@ async function main() {
|
|
|
882
994
|
printCommandHint("如果新终端仍读不到环境变量,可运行:", unixLauncher);
|
|
883
995
|
}
|
|
884
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
|
+
|
|
885
1006
|
let shellConfigPathWritten = "";
|
|
886
1007
|
if (setEnv) {
|
|
887
1008
|
if (process.platform === "win32") {
|
|
@@ -892,12 +1013,16 @@ async function main() {
|
|
|
892
1013
|
"$ErrorActionPreference = 'Stop';",
|
|
893
1014
|
`[Environment]::SetEnvironmentVariable('LANGFUSE_USER_ID', ${psQuote(userId)}, 'User');`
|
|
894
1015
|
].join(" ");
|
|
895
|
-
spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
|
|
896
|
-
}
|
|
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
|
+
}
|
|
897
1022
|
console.log("提示:新开一个终端后环境变量才会对应用生效。");
|
|
898
1023
|
} else {
|
|
899
1024
|
console.log("正在写入环境变量到 shell 配置文件 …");
|
|
900
|
-
const configPath = updateShellConfig({ publicKey, secretKey, baseUrl, userId });
|
|
1025
|
+
const configPath = updateShellConfig({ publicKey, secretKey, baseUrl, userId, pathPrepend: opencodeShim?.shimDir || "" });
|
|
901
1026
|
shellConfigPathWritten = configPath;
|
|
902
1027
|
console.log(`已更新:${configPath}`);
|
|
903
1028
|
|