oh-langfuse 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -102,18 +102,21 @@ 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。
|
|
106
107
|
|
|
107
|
-
如果你在
|
|
108
|
-
|
|
109
|
-
`C:\Users\<you>\.config\opencode`:
|
|
108
|
+
如果你在 WSL 里运行 OpenCode,就在 WSL shell 里直接执行安装和检查命令。
|
|
109
|
+
此时配置会写入 WSL 用户自己的 `$HOME/.config/opencode`:
|
|
110
110
|
|
|
111
111
|
```bash
|
|
112
|
-
npx oh-langfuse@latest setup opencode --
|
|
113
|
-
npx oh-langfuse@latest check opencode
|
|
112
|
+
npx oh-langfuse@latest setup opencode --userId=h00613222
|
|
113
|
+
npx oh-langfuse@latest check opencode
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
-
|
|
116
|
+
如果 `opencode.json` 原本是 JSONC 风格(例如带注释或尾随逗号),安装器会先兼容读取,
|
|
117
|
+
再写回标准 JSON。
|
|
118
|
+
|
|
119
|
+
如果你确实是在 Windows PowerShell 里远程配置 WSL,才需要使用 `--wsl`:
|
|
117
120
|
|
|
118
121
|
```bash
|
|
119
122
|
npx oh-langfuse@latest setup opencode --wsl=Ubuntu --userId=h00613222
|
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,6 +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 { parseJsonRelaxed, stripBom } from "./json-utils.mjs";
|
|
4
5
|
import { runOhLangfuseInWsl, shouldRunInWsl } from "./wsl-utils.mjs";
|
|
5
6
|
|
|
6
7
|
function parseArgs(argv) {
|
|
@@ -14,11 +15,6 @@ function parseArgs(argv) {
|
|
|
14
15
|
return args;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
function stripBom(s) {
|
|
18
|
-
if (typeof s !== "string" || s.length === 0) return s;
|
|
19
|
-
return s.charCodeAt(0) === 0xfeff ? s.slice(1) : s;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
18
|
function readTextIfExists(p) {
|
|
23
19
|
if (!fs.existsSync(p)) return "";
|
|
24
20
|
return stripBom(fs.readFileSync(p, "utf8"));
|
|
@@ -27,7 +23,7 @@ function readTextIfExists(p) {
|
|
|
27
23
|
function readJsonIfExists(p) {
|
|
28
24
|
const txt = readTextIfExists(p);
|
|
29
25
|
if (!txt.trim()) return null;
|
|
30
|
-
return
|
|
26
|
+
return parseJsonRelaxed(txt, p);
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
function hasPlugin(pluginField, name) {
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import { spawn, spawnSync } from "node:child_process";
|
|
5
|
+
import { parseJsonRelaxed, stripBom } from "./json-utils.mjs";
|
|
5
6
|
import { runOhLangfuseInWsl, shouldRunInWsl } from "./wsl-utils.mjs";
|
|
6
7
|
|
|
7
8
|
function parseArgs(argv) {
|
|
@@ -22,26 +23,41 @@ function ensureDir(p) {
|
|
|
22
23
|
fs.mkdirSync(p, { recursive: true });
|
|
23
24
|
}
|
|
24
25
|
|
|
25
|
-
function
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
function readJsonIfExists(p) {
|
|
32
|
-
if (!fs.existsSync(p)) return null;
|
|
33
|
-
const txt = stripBom(fs.readFileSync(p, "utf8"));
|
|
34
|
-
if (!txt.trim()) return null;
|
|
35
|
-
return JSON.parse(txt);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function writeJsonPretty(p, obj) {
|
|
39
|
-
fs.writeFileSync(p, JSON.stringify(obj, null, 2) + os.EOL, "utf8");
|
|
40
|
-
}
|
|
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
|
+
}
|
|
41
32
|
|
|
42
|
-
function
|
|
43
|
-
|
|
44
|
-
}
|
|
33
|
+
function writeJsonPretty(p, obj) {
|
|
34
|
+
fs.writeFileSync(p, JSON.stringify(obj, null, 2) + os.EOL, "utf8");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const colorEnabled = process.stdout.isTTY && process.env.NO_COLOR !== "1";
|
|
38
|
+
const ansi = (code) => (colorEnabled ? `\x1b[${code}m` : "");
|
|
39
|
+
const colors = {
|
|
40
|
+
reset: ansi(0),
|
|
41
|
+
bold: ansi(1),
|
|
42
|
+
cyan: ansi(36),
|
|
43
|
+
green: ansi(32),
|
|
44
|
+
yellow: ansi(33),
|
|
45
|
+
dim: ansi(2)
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function paint(text, ...styles) {
|
|
49
|
+
if (!colorEnabled) return String(text);
|
|
50
|
+
return `${styles.join("")}${text}${colors.reset}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function printCommandHint(label, command) {
|
|
54
|
+
console.log(paint(label, colors.yellow, colors.bold));
|
|
55
|
+
console.log(`${paint(">", colors.green, colors.bold)} ${paint(command, colors.cyan, colors.bold)}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isObject(x) {
|
|
59
|
+
return x && typeof x === "object" && !Array.isArray(x);
|
|
60
|
+
}
|
|
45
61
|
|
|
46
62
|
function deepMerge(target, src) {
|
|
47
63
|
if (!isObject(target) || !isObject(src)) return src;
|
|
@@ -527,7 +543,7 @@ async function main() {
|
|
|
527
543
|
const unixLauncher = writeUnixLauncherSh(opencodeDir, { publicKey, secretKey, baseUrl, userId });
|
|
528
544
|
if (unixLauncher) {
|
|
529
545
|
console.log(`已生成启动脚本(含 LANGFUSE 环境变量):${unixLauncher}`);
|
|
530
|
-
|
|
546
|
+
printCommandHint("如果新终端仍读不到环境变量,可运行:", unixLauncher);
|
|
531
547
|
}
|
|
532
548
|
|
|
533
549
|
if (setEnv) {
|
|
@@ -556,14 +572,14 @@ async function main() {
|
|
|
556
572
|
if (r.status === 0) {
|
|
557
573
|
console.log("环境变量已生效:");
|
|
558
574
|
console.log(r.stdout);
|
|
559
|
-
} else {
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
575
|
+
} else {
|
|
576
|
+
printCommandHint("提示:环境变量已写入配置文件,请运行以下命令使其生效:", `source ${configPath}`);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
console.log(paint("完成。", colors.green, colors.bold));
|
|
582
|
+
printCommandHint("可运行以下命令校验:", "npx oh-langfuse@latest check opencode");
|
|
567
583
|
}
|
|
568
584
|
|
|
569
585
|
try {
|