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