oh-langfuse 0.1.59 → 0.1.61
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 +245 -245
- package/bin/cli.js +22 -22
- package/codex_langfuse_notify.py +422 -56
- package/package.json +6 -6
- package/scripts/auto-update-runtime.mjs +28 -15
- package/scripts/codex-langfuse-setup.mjs +127 -117
- package/scripts/langfuse-setup.mjs +71 -71
- package/scripts/opencode-langfuse-run.mjs +23 -23
- package/scripts/opencode-langfuse-setup.mjs +140 -64
- package/scripts/real-self-verify.mjs +114 -104
- package/scripts/update-langfuse-runtime.mjs +7 -6
- package/scripts/verify-update-runtime.mjs +162 -162
- package/scripts/verify-update-utils.mjs +60 -60
|
@@ -39,9 +39,9 @@ function parseArgs(argv) {
|
|
|
39
39
|
return args;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
async function latestVersion(registry = "https://registry.npmjs.org") {
|
|
42
|
+
async function latestVersion(packageName, registry = "https://registry.npmjs.org") {
|
|
43
43
|
const base = registry.replace(/\/+$/, "");
|
|
44
|
-
const response = await fetch(`${base}
|
|
44
|
+
const response = await fetch(`${base}/${packageName}`, { headers: { accept: "application/json" } });
|
|
45
45
|
if (!response.ok) throw new Error(`npm registry ${response.status} ${response.statusText}`);
|
|
46
46
|
return extractVersionFromNpmMetadata(await response.json());
|
|
47
47
|
}
|
|
@@ -65,11 +65,13 @@ function runNpx(args, options = {}) {
|
|
|
65
65
|
return spawnSync("cmd.exe", ["/d", "/s", "/c", npxCommand(), ...args], {
|
|
66
66
|
...options,
|
|
67
67
|
windowsHide: true,
|
|
68
|
+
encoding: "utf8",
|
|
68
69
|
});
|
|
69
70
|
}
|
|
70
71
|
return spawnSync(npxCommand(), args, {
|
|
71
72
|
...options,
|
|
72
73
|
windowsHide: true,
|
|
74
|
+
encoding: "utf8",
|
|
73
75
|
});
|
|
74
76
|
}
|
|
75
77
|
|
|
@@ -98,13 +100,23 @@ function shouldPrintStartupStatus(args, env = process.env) {
|
|
|
98
100
|
return !!(args["startup-status"] || args.startupStatus || /^(1|true|yes|on)$/i.test(raw));
|
|
99
101
|
}
|
|
100
102
|
|
|
101
|
-
function printAlreadyCurrent(target, version) {
|
|
103
|
+
function printAlreadyCurrent(target, packageName, version) {
|
|
102
104
|
const label = targetLabel(target);
|
|
103
|
-
console.log(paint(`[OK] ${label} Langfuse \u5df2\u662f\u6700\u65b0\uff1a${
|
|
105
|
+
console.log(paint(`[OK] ${label} Langfuse \u5df2\u662f\u6700\u65b0\uff1a${packageName}@${version}`, t.bold, t.green));
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
+
function updateCommandForPackage(packageName, target) {
|
|
109
|
+
if (packageName === "oh-aicoding-tool") return `npx oh-aicoding-tool@latest langfuse update ${target}`;
|
|
110
|
+
return `npx ${packageName}@latest update ${target}`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function updateArgsForPackage(packageName, target) {
|
|
114
|
+
if (packageName === "oh-aicoding-tool") return ["-y", "oh-aicoding-tool@latest", "langfuse", "update", target];
|
|
115
|
+
return ["-y", `${packageName}@latest`, "update", target];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function runUpdate(target, packageName, args) {
|
|
119
|
+
const updateArgs = updateArgsForPackage(packageName, target);
|
|
108
120
|
if (args["skip-check"]) updateArgs.push("--skip-check");
|
|
109
121
|
if (args.npmRegistry) updateArgs.push(`--npmRegistry=${args.npmRegistry}`);
|
|
110
122
|
if (args.pipIndexUrl) updateArgs.push(`--pipIndexUrl=${args.pipIndexUrl}`);
|
|
@@ -133,26 +145,27 @@ async function main() {
|
|
|
133
145
|
throw new Error("Usage: oh-langfuse auto-update <claude|opencode|codex>");
|
|
134
146
|
}
|
|
135
147
|
|
|
148
|
+
const record = getRuntimeInstallRecord(target);
|
|
149
|
+
const packageName = record?.packageName || packageJson.name;
|
|
136
150
|
let latest;
|
|
137
151
|
try {
|
|
138
|
-
latest = await latestVersion(args.npmRegistry);
|
|
152
|
+
latest = await latestVersion(packageName, args.npmRegistry);
|
|
139
153
|
} catch (error) {
|
|
140
|
-
if (args.verbose) console.log(`[INFO]
|
|
154
|
+
if (args.verbose) console.log(`[INFO] ${packageName} update check skipped: ${error.message}`);
|
|
141
155
|
return 0;
|
|
142
156
|
}
|
|
143
157
|
|
|
144
|
-
const record = getRuntimeInstallRecord(target);
|
|
145
158
|
const installedVersion = record?.packageVersion || record?.version || "";
|
|
146
159
|
const needsUpdate = installedVersion ? isNewerVersion(latest, installedVersion) : isNewerVersion(latest, packageJson.version);
|
|
147
160
|
if (!needsUpdate) {
|
|
148
|
-
if (shouldPrintStartupStatus(args)) printAlreadyCurrent(target, installedVersion || packageJson.version);
|
|
161
|
+
if (shouldPrintStartupStatus(args)) printAlreadyCurrent(target, packageName, installedVersion || packageJson.version);
|
|
149
162
|
return 0;
|
|
150
163
|
}
|
|
151
164
|
|
|
152
165
|
const message = installedVersion
|
|
153
|
-
?
|
|
154
|
-
:
|
|
155
|
-
const updateCommand =
|
|
166
|
+
? `${packageName} ${target} runtime update available: ${installedVersion} -> ${latest}.`
|
|
167
|
+
: `${packageName} ${target} runtime may need update. Latest package: ${latest}.`;
|
|
168
|
+
const updateCommand = updateCommandForPackage(packageName, target);
|
|
156
169
|
|
|
157
170
|
if (mode === "notify") {
|
|
158
171
|
printUpdateAvailable(target, message, updateCommand);
|
|
@@ -163,7 +176,7 @@ async function main() {
|
|
|
163
176
|
if (mode === "auto") {
|
|
164
177
|
printUpdateAvailable(target, message, updateCommand);
|
|
165
178
|
printUpdateCommand(target, updateCommand);
|
|
166
|
-
const code = runUpdate(target, args);
|
|
179
|
+
const code = runUpdate(target, packageName, args);
|
|
167
180
|
return args.strict ? code : 0;
|
|
168
181
|
}
|
|
169
182
|
|
|
@@ -176,7 +189,7 @@ async function main() {
|
|
|
176
189
|
const answer = await question(`${paint("[CONFIRM]", t.bold, t.gold)} Update now? (y/N) `);
|
|
177
190
|
if (!/^(y|yes)$/i.test(answer)) return 0;
|
|
178
191
|
printUpdateCommand(target, updateCommand);
|
|
179
|
-
const code = runUpdate(target, args);
|
|
192
|
+
const code = runUpdate(target, packageName, args);
|
|
180
193
|
return args.strict ? code : 0;
|
|
181
194
|
}
|
|
182
195
|
|
|
@@ -52,61 +52,63 @@ function writeJsonPretty(p, obj) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function tomlString(s) {
|
|
55
|
-
|
|
55
|
+
const str = String(s);
|
|
56
|
+
const escaped = str.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
|
|
57
|
+
return `"${escaped}"`;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
60
|
function tomlArray(items) {
|
|
59
61
|
return `[${items.map(tomlString).join(", ")}]`;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
|
-
function pythonExecutableInVenv(venvDir) {
|
|
63
|
-
return process.platform === "win32"
|
|
64
|
-
? path.join(venvDir, "Scripts", "python.exe")
|
|
65
|
-
: path.join(venvDir, "bin", "python");
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function pythonCanImport(pythonCmd, moduleName) {
|
|
69
|
-
try {
|
|
70
|
-
execFileSync(pythonCmd, ["-c", `import ${moduleName}`], { stdio: "ignore" });
|
|
71
|
-
return true;
|
|
72
|
-
} catch {
|
|
73
|
-
return false;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function runPipInstallWithFallback({ pythonCmd, pipIndexUrl }) {
|
|
78
|
-
const attempts = [
|
|
79
|
-
{ command: pythonCmd, args: ["-m", "pip", "install", "--user", "-U", "langfuse", "-i", pipIndexUrl] },
|
|
80
|
-
{ command: "pip3", args: ["install", "--user", "-U", "langfuse", "-i", pipIndexUrl] },
|
|
81
|
-
{ command: "pip", args: ["install", "--user", "-U", "langfuse", "-i", pipIndexUrl] }
|
|
82
|
-
];
|
|
83
|
-
|
|
84
|
-
const errors = [];
|
|
85
|
-
for (const attempt of attempts) {
|
|
86
|
-
try {
|
|
87
|
-
console.log(`Trying Python package install: ${attempt.command} ${attempt.args.join(" ")}`);
|
|
88
|
-
execFileSync(attempt.command, attempt.args, { stdio: "inherit" });
|
|
89
|
-
return;
|
|
90
|
-
} catch (error) {
|
|
91
|
-
errors.push(`${attempt.command}: ${error?.message || error}`);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
throw new Error(
|
|
96
|
-
`Failed to install langfuse with system Python/pip. Tried python -m pip, pip3, and pip. Last errors: ${errors.join(" | ")}`
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl }) {
|
|
101
|
-
console.log("Python venv is unavailable; falling back to system Python user install for langfuse.");
|
|
102
|
-
runPipInstallWithFallback({ pythonCmd, pipIndexUrl });
|
|
103
|
-
if (!pythonCanImport(pythonCmd, "langfuse")) {
|
|
104
|
-
throw new Error("langfuse was installed with pip, but python3 still cannot import it. Install python3-venv and rerun setup, for example: sudo apt install python3-venv");
|
|
105
|
-
}
|
|
106
|
-
return pythonCmd;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function shQuote(s) {
|
|
64
|
+
function pythonExecutableInVenv(venvDir) {
|
|
65
|
+
return process.platform === "win32"
|
|
66
|
+
? path.join(venvDir, "Scripts", "python.exe")
|
|
67
|
+
: path.join(venvDir, "bin", "python");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function pythonCanImport(pythonCmd, moduleName) {
|
|
71
|
+
try {
|
|
72
|
+
execFileSync(pythonCmd, ["-c", `import ${moduleName}`], { stdio: "ignore", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } });
|
|
73
|
+
return true;
|
|
74
|
+
} catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function runPipInstallWithFallback({ pythonCmd, pipIndexUrl }) {
|
|
80
|
+
const attempts = [
|
|
81
|
+
{ command: pythonCmd, args: ["-m", "pip", "install", "--user", "-U", "langfuse", "-i", pipIndexUrl] },
|
|
82
|
+
{ command: "pip3", args: ["install", "--user", "-U", "langfuse", "-i", pipIndexUrl] },
|
|
83
|
+
{ command: "pip", args: ["install", "--user", "-U", "langfuse", "-i", pipIndexUrl] }
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
const errors = [];
|
|
87
|
+
for (const attempt of attempts) {
|
|
88
|
+
try {
|
|
89
|
+
console.log(`Trying Python package install: ${attempt.command} ${attempt.args.join(" ")}`);
|
|
90
|
+
execFileSync(attempt.command, attempt.args, { stdio: "inherit", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } });
|
|
91
|
+
return;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
errors.push(`${attempt.command}: ${error?.message || error}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
throw new Error(
|
|
98
|
+
`Failed to install langfuse with system Python/pip. Tried python -m pip, pip3, and pip. Last errors: ${errors.join(" | ")}`
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl }) {
|
|
103
|
+
console.log("Python venv is unavailable; falling back to system Python user install for langfuse.");
|
|
104
|
+
runPipInstallWithFallback({ pythonCmd, pipIndexUrl });
|
|
105
|
+
if (!pythonCanImport(pythonCmd, "langfuse")) {
|
|
106
|
+
throw new Error("langfuse was installed with pip, but python3 still cannot import it. Install python3-venv and rerun setup, for example: sudo apt install python3-venv");
|
|
107
|
+
}
|
|
108
|
+
return pythonCmd;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function shQuote(s) {
|
|
110
112
|
return `'${String(s).replace(/'/g, "'\"'\"'")}'`;
|
|
111
113
|
}
|
|
112
114
|
|
|
@@ -145,7 +147,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
145
147
|
"exit /b 0",
|
|
146
148
|
""
|
|
147
149
|
];
|
|
148
|
-
fs.writeFileSync(helper, lines.join(os.EOL), "utf8");
|
|
150
|
+
fs.writeFileSync(helper, "\ufeff" + lines.join(os.EOL), "utf8");
|
|
149
151
|
return helper;
|
|
150
152
|
}
|
|
151
153
|
|
|
@@ -181,44 +183,44 @@ function isPathInsideDir(candidate, dir) {
|
|
|
181
183
|
return relative === "" || (relative && !relative.startsWith("..") && !path.isAbsolute(relative));
|
|
182
184
|
}
|
|
183
185
|
|
|
184
|
-
function existingCliCandidate(candidate, shimDir) {
|
|
185
|
-
if (!candidate || isPathInsideDir(candidate, shimDir)) return "";
|
|
186
|
-
if (process.platform === "win32" && isWindowsAppsPath(candidate)) return "";
|
|
187
|
-
try {
|
|
188
|
-
return fs.existsSync(candidate) ? candidate : "";
|
|
189
|
-
} catch {
|
|
190
|
-
return "";
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function isWindowsAppsPath(candidate) {
|
|
195
|
-
return String(candidate || "").toLowerCase().includes("\\program files\\windowsapps\\");
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function listLocalCodexCliCandidates() {
|
|
199
|
-
if (process.platform !== "win32") return [];
|
|
200
|
-
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
201
|
-
const codexBin = path.join(localAppData, "OpenAI", "Codex", "bin");
|
|
202
|
-
const candidates = [
|
|
203
|
-
process.env.CODEX_CLI_PATH || "",
|
|
204
|
-
path.join(codexBin, "codex.exe"),
|
|
205
|
-
];
|
|
206
|
-
try {
|
|
207
|
-
if (fs.existsSync(codexBin)) {
|
|
208
|
-
for (const ent of fs.readdirSync(codexBin, { withFileTypes: true })) {
|
|
209
|
-
if (ent.isDirectory()) candidates.push(path.join(codexBin, ent.name, "codex.exe"));
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
} catch {
|
|
213
|
-
// Best effort only; PATH candidates are still checked below.
|
|
214
|
-
}
|
|
215
|
-
return candidates;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function listCliCandidatesFromPath(target) {
|
|
219
|
-
const cmd = process.platform === "win32" ? "where.exe" : "which";
|
|
220
|
-
const args = process.platform === "win32" ? [target] : ["-a", target];
|
|
221
|
-
const result = spawnSync(cmd, args, { encoding: "utf8", windowsHide: true });
|
|
186
|
+
function existingCliCandidate(candidate, shimDir) {
|
|
187
|
+
if (!candidate || isPathInsideDir(candidate, shimDir)) return "";
|
|
188
|
+
if (process.platform === "win32" && isWindowsAppsPath(candidate)) return "";
|
|
189
|
+
try {
|
|
190
|
+
return fs.existsSync(candidate) ? candidate : "";
|
|
191
|
+
} catch {
|
|
192
|
+
return "";
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function isWindowsAppsPath(candidate) {
|
|
197
|
+
return String(candidate || "").toLowerCase().includes("\\program files\\windowsapps\\");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function listLocalCodexCliCandidates() {
|
|
201
|
+
if (process.platform !== "win32") return [];
|
|
202
|
+
const localAppData = process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
|
|
203
|
+
const codexBin = path.join(localAppData, "OpenAI", "Codex", "bin");
|
|
204
|
+
const candidates = [
|
|
205
|
+
process.env.CODEX_CLI_PATH || "",
|
|
206
|
+
path.join(codexBin, "codex.exe"),
|
|
207
|
+
];
|
|
208
|
+
try {
|
|
209
|
+
if (fs.existsSync(codexBin)) {
|
|
210
|
+
for (const ent of fs.readdirSync(codexBin, { withFileTypes: true })) {
|
|
211
|
+
if (ent.isDirectory()) candidates.push(path.join(codexBin, ent.name, "codex.exe"));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
} catch {
|
|
215
|
+
// Best effort only; PATH candidates are still checked below.
|
|
216
|
+
}
|
|
217
|
+
return candidates;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function listCliCandidatesFromPath(target) {
|
|
221
|
+
const cmd = process.platform === "win32" ? "where.exe" : "which";
|
|
222
|
+
const args = process.platform === "win32" ? [target] : ["-a", target];
|
|
223
|
+
const result = spawnSync(cmd, args, { encoding: "utf8", windowsHide: true });
|
|
222
224
|
if (result.status !== 0) return [];
|
|
223
225
|
const candidates = String(result.stdout || "")
|
|
224
226
|
.split(/\r?\n/)
|
|
@@ -236,14 +238,14 @@ function sortWindowsCliCandidates(candidates) {
|
|
|
236
238
|
return [...candidates].sort((a, b) => extPriority(a) - extPriority(b));
|
|
237
239
|
}
|
|
238
240
|
|
|
239
|
-
function resolveAgentCli({ target, preferred = "", shimDir = "" }) {
|
|
240
|
-
const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
241
|
-
const candidates = [
|
|
242
|
-
preferred,
|
|
243
|
-
...(target === "codex" ? listLocalCodexCliCandidates() : []),
|
|
244
|
-
process.platform === "win32" ? path.join(appData, "npm", `${target}.cmd`) : "",
|
|
245
|
-
process.platform === "win32" ? path.join(appData, "npm", `${target}.exe`) : "",
|
|
246
|
-
process.platform === "win32" ? path.join(appData, "npm", target) : "",
|
|
241
|
+
function resolveAgentCli({ target, preferred = "", shimDir = "" }) {
|
|
242
|
+
const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
243
|
+
const candidates = [
|
|
244
|
+
preferred,
|
|
245
|
+
...(target === "codex" ? listLocalCodexCliCandidates() : []),
|
|
246
|
+
process.platform === "win32" ? path.join(appData, "npm", `${target}.cmd`) : "",
|
|
247
|
+
process.platform === "win32" ? path.join(appData, "npm", `${target}.exe`) : "",
|
|
248
|
+
process.platform === "win32" ? path.join(appData, "npm", target) : "",
|
|
247
249
|
...listCliCandidatesFromPath(target)
|
|
248
250
|
];
|
|
249
251
|
for (const candidate of candidates) {
|
|
@@ -268,7 +270,7 @@ function prependWindowsUserPath(dir) {
|
|
|
268
270
|
" [Environment]::SetEnvironmentVariable('Path', $next, 'User');",
|
|
269
271
|
"}"
|
|
270
272
|
].join(" ");
|
|
271
|
-
const result = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit", windowsHide: true });
|
|
273
|
+
const result = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit", windowsHide: true, encoding: "utf8" });
|
|
272
274
|
if (result.status !== 0) throw new Error("Failed to prepend Codex Langfuse shim to the user PATH.");
|
|
273
275
|
|
|
274
276
|
const currentParts = String(process.env.PATH || "").split(path.delimiter);
|
|
@@ -299,7 +301,7 @@ function writeAgentCommandShim({ baseDir, target, executable, realCli, publicKey
|
|
|
299
301
|
"exit /b %ERRORLEVEL%",
|
|
300
302
|
""
|
|
301
303
|
].filter(Boolean).join(os.EOL);
|
|
302
|
-
fs.writeFileSync(shim, content, "utf8");
|
|
304
|
+
fs.writeFileSync(shim, "\ufeff" + content, "utf8");
|
|
303
305
|
return { shim, shimDir };
|
|
304
306
|
}
|
|
305
307
|
|
|
@@ -331,7 +333,7 @@ function createAgentLauncher({ baseDir, target, executable }) {
|
|
|
331
333
|
`${executable} %*`,
|
|
332
334
|
""
|
|
333
335
|
].join(os.EOL);
|
|
334
|
-
fs.writeFileSync(launcher, content, "utf8");
|
|
336
|
+
fs.writeFileSync(launcher, "\ufeff" + content, "utf8");
|
|
335
337
|
return launcher;
|
|
336
338
|
}
|
|
337
339
|
|
|
@@ -355,14 +357,14 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
|
|
|
355
357
|
|
|
356
358
|
if (!fs.existsSync(venvPython)) {
|
|
357
359
|
console.log(`Creating Python virtual environment: ${venvDir}`);
|
|
358
|
-
try {
|
|
359
|
-
execFileSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit" });
|
|
360
|
-
} catch (e) {
|
|
361
|
-
if (process.platform !== "win32") {
|
|
362
|
-
return installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl });
|
|
363
|
-
}
|
|
364
|
-
throw new Error("Failed to create Python venv. Please confirm Python venv support is available.");
|
|
365
|
-
}
|
|
360
|
+
try {
|
|
361
|
+
execFileSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } });
|
|
362
|
+
} catch (e) {
|
|
363
|
+
if (process.platform !== "win32") {
|
|
364
|
+
return installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl });
|
|
365
|
+
}
|
|
366
|
+
throw new Error("Failed to create Python venv. Please confirm Python venv support is available.");
|
|
367
|
+
}
|
|
366
368
|
}
|
|
367
369
|
|
|
368
370
|
console.log("Installing/updating Python package in venv: langfuse");
|
|
@@ -370,14 +372,14 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
|
|
|
370
372
|
execFileSync(
|
|
371
373
|
venvPython,
|
|
372
374
|
["-m", "pip", "install", "-U", "langfuse", "-i", pipIndexUrl],
|
|
373
|
-
{ stdio: "inherit" }
|
|
374
|
-
);
|
|
375
|
-
} catch (e) {
|
|
376
|
-
if (process.platform !== "win32") {
|
|
377
|
-
return installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl });
|
|
378
|
-
}
|
|
379
|
-
throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i ${pipIndexUrl}`);
|
|
380
|
-
}
|
|
375
|
+
{ stdio: "inherit", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } }
|
|
376
|
+
);
|
|
377
|
+
} catch (e) {
|
|
378
|
+
if (process.platform !== "win32") {
|
|
379
|
+
return installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl });
|
|
380
|
+
}
|
|
381
|
+
throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i ${pipIndexUrl}`);
|
|
382
|
+
}
|
|
381
383
|
|
|
382
384
|
return venvPython;
|
|
383
385
|
}
|
|
@@ -398,7 +400,8 @@ function updateCodexNotify(configPath, notifyCommand) {
|
|
|
398
400
|
const next = [];
|
|
399
401
|
|
|
400
402
|
for (const line of lines) {
|
|
401
|
-
|
|
403
|
+
const trimmed = line.trim();
|
|
404
|
+
if (/^\[/.test(trimmed) && !trimmed.startsWith("[[")) {
|
|
402
405
|
if (!replaced && !inserted) {
|
|
403
406
|
while (next.length && next[next.length - 1].trim() === "") next.pop();
|
|
404
407
|
next.push(notifyLine, "");
|
|
@@ -419,7 +422,14 @@ function updateCodexNotify(configPath, notifyCommand) {
|
|
|
419
422
|
next.push(notifyLine, "");
|
|
420
423
|
}
|
|
421
424
|
|
|
422
|
-
|
|
425
|
+
const result = next.join("\n");
|
|
426
|
+
fs.writeFileSync(configPath, result, "utf8");
|
|
427
|
+
|
|
428
|
+
const verifyContent = fs.readFileSync(configPath, "utf8");
|
|
429
|
+
const notifyMatch = verifyContent.match(/^notify\s*=\s*\[[\s\S]*?\]/m);
|
|
430
|
+
if (!notifyMatch) {
|
|
431
|
+
throw new Error(`Failed to write valid notify config to ${configPath}. Generated content:\n${result}`);
|
|
432
|
+
}
|
|
423
433
|
}
|
|
424
434
|
|
|
425
435
|
async function main() {
|
|
@@ -81,7 +81,7 @@ function extractZipWithPowerShell({ zipPath, destDir }) {
|
|
|
81
81
|
const cmd = cmdParts.join(" ");
|
|
82
82
|
|
|
83
83
|
try {
|
|
84
|
-
execFileSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
|
|
84
|
+
execFileSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit", encoding: "utf8" });
|
|
85
85
|
} catch {
|
|
86
86
|
throw new Error("解压 hooks zip 失败:请确认 zip 文件存在,且 PowerShell 支持 Expand-Archive。");
|
|
87
87
|
}
|
|
@@ -97,7 +97,7 @@ function downloadAndExtractZipWithPowerShell({ zipUrl, destDir }) {
|
|
|
97
97
|
].join(" ");
|
|
98
98
|
|
|
99
99
|
try {
|
|
100
|
-
execFileSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit" });
|
|
100
|
+
execFileSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit", encoding: "utf8" });
|
|
101
101
|
} catch (e) {
|
|
102
102
|
const msg = e?.message ? String(e.message) : "";
|
|
103
103
|
if (msg.includes("(401)") || msg.toLowerCase().includes("unauthorized")) {
|
|
@@ -176,7 +176,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
176
176
|
"exit /b 0",
|
|
177
177
|
""
|
|
178
178
|
];
|
|
179
|
-
fs.writeFileSync(helper, lines.join(os.EOL), "utf8");
|
|
179
|
+
fs.writeFileSync(helper, "\ufeff" + lines.join(os.EOL), "utf8");
|
|
180
180
|
return helper;
|
|
181
181
|
}
|
|
182
182
|
|
|
@@ -263,7 +263,7 @@ function prependWindowsUserPath(dir) {
|
|
|
263
263
|
" [Environment]::SetEnvironmentVariable('Path', $next, 'User');",
|
|
264
264
|
"}"
|
|
265
265
|
].join(" ");
|
|
266
|
-
const result = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit", windowsHide: true });
|
|
266
|
+
const result = spawnSync("powershell", ["-NoProfile", "-Command", cmd], { stdio: "inherit", windowsHide: true, encoding: "utf8" });
|
|
267
267
|
if (result.status !== 0) throw new Error("Failed to prepend Claude Langfuse shim to the user PATH.");
|
|
268
268
|
|
|
269
269
|
const currentParts = String(process.env.PATH || "").split(path.delimiter);
|
|
@@ -294,7 +294,7 @@ function writeAgentCommandShim({ baseDir, target, executable, realCli, publicKey
|
|
|
294
294
|
"exit /b %ERRORLEVEL%",
|
|
295
295
|
""
|
|
296
296
|
].filter(Boolean).join(os.EOL);
|
|
297
|
-
fs.writeFileSync(shim, content, "utf8");
|
|
297
|
+
fs.writeFileSync(shim, "\ufeff" + content, "utf8");
|
|
298
298
|
return { shim, shimDir };
|
|
299
299
|
}
|
|
300
300
|
|
|
@@ -326,7 +326,7 @@ function createAgentLauncher({ baseDir, target, executable }) {
|
|
|
326
326
|
`${executable} %*`,
|
|
327
327
|
""
|
|
328
328
|
].join(os.EOL);
|
|
329
|
-
fs.writeFileSync(launcher, content, "utf8");
|
|
329
|
+
fs.writeFileSync(launcher, "\ufeff" + content, "utf8");
|
|
330
330
|
return launcher;
|
|
331
331
|
}
|
|
332
332
|
|
|
@@ -343,54 +343,54 @@ function createAgentLauncher({ baseDir, target, executable }) {
|
|
|
343
343
|
return launcher;
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
-
function pythonExecutableInVenv(venvDir) {
|
|
347
|
-
return process.platform === "win32"
|
|
348
|
-
? path.join(venvDir, "Scripts", "python.exe")
|
|
349
|
-
: path.join(venvDir, "bin", "python");
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
function pythonCanImport(pythonCmd, moduleName) {
|
|
353
|
-
try {
|
|
354
|
-
execFileSync(pythonCmd, ["-c", `import ${moduleName}`], { stdio: "ignore" });
|
|
355
|
-
return true;
|
|
356
|
-
} catch {
|
|
357
|
-
return false;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function runPipInstallWithFallback({ pythonCmd, pipIndexUrl }) {
|
|
362
|
-
const attempts = [
|
|
363
|
-
{ command: pythonCmd, args: ["-m", "pip", "install", "--user", "-U", "langfuse", "-i", pipIndexUrl] },
|
|
364
|
-
{ command: "pip3", args: ["install", "--user", "-U", "langfuse", "-i", pipIndexUrl] },
|
|
365
|
-
{ command: "pip", args: ["install", "--user", "-U", "langfuse", "-i", pipIndexUrl] }
|
|
366
|
-
];
|
|
367
|
-
|
|
368
|
-
const errors = [];
|
|
369
|
-
for (const attempt of attempts) {
|
|
370
|
-
try {
|
|
371
|
-
console.log(`Trying Python package install: ${attempt.command} ${attempt.args.join(" ")}`);
|
|
372
|
-
execFileSync(attempt.command, attempt.args, { stdio: "inherit" });
|
|
373
|
-
return;
|
|
374
|
-
} catch (error) {
|
|
375
|
-
errors.push(`${attempt.command}: ${error?.message || error}`);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
throw new Error(
|
|
380
|
-
`Failed to install langfuse with system Python/pip. Tried python -m pip, pip3, and pip. Last errors: ${errors.join(" | ")}`
|
|
381
|
-
);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl }) {
|
|
385
|
-
console.log("Python venv is unavailable; falling back to system Python user install for langfuse.");
|
|
386
|
-
runPipInstallWithFallback({ pythonCmd, pipIndexUrl });
|
|
387
|
-
if (!pythonCanImport(pythonCmd, "langfuse")) {
|
|
388
|
-
throw new Error("langfuse was installed with pip, but python3 still cannot import it. Install python3-venv and rerun setup, for example: sudo apt install python3-venv");
|
|
389
|
-
}
|
|
390
|
-
return pythonCmd;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
async function createHookLauncher({ hooksDir, hookPython, pyPath }) {
|
|
346
|
+
function pythonExecutableInVenv(venvDir) {
|
|
347
|
+
return process.platform === "win32"
|
|
348
|
+
? path.join(venvDir, "Scripts", "python.exe")
|
|
349
|
+
: path.join(venvDir, "bin", "python");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function pythonCanImport(pythonCmd, moduleName) {
|
|
353
|
+
try {
|
|
354
|
+
execFileSync(pythonCmd, ["-c", `import ${moduleName}`], { stdio: "ignore", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } });
|
|
355
|
+
return true;
|
|
356
|
+
} catch {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function runPipInstallWithFallback({ pythonCmd, pipIndexUrl }) {
|
|
362
|
+
const attempts = [
|
|
363
|
+
{ command: pythonCmd, args: ["-m", "pip", "install", "--user", "-U", "langfuse", "-i", pipIndexUrl] },
|
|
364
|
+
{ command: "pip3", args: ["install", "--user", "-U", "langfuse", "-i", pipIndexUrl] },
|
|
365
|
+
{ command: "pip", args: ["install", "--user", "-U", "langfuse", "-i", pipIndexUrl] }
|
|
366
|
+
];
|
|
367
|
+
|
|
368
|
+
const errors = [];
|
|
369
|
+
for (const attempt of attempts) {
|
|
370
|
+
try {
|
|
371
|
+
console.log(`Trying Python package install: ${attempt.command} ${attempt.args.join(" ")}`);
|
|
372
|
+
execFileSync(attempt.command, attempt.args, { stdio: "inherit", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } });
|
|
373
|
+
return;
|
|
374
|
+
} catch (error) {
|
|
375
|
+
errors.push(`${attempt.command}: ${error?.message || error}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
throw new Error(
|
|
380
|
+
`Failed to install langfuse with system Python/pip. Tried python -m pip, pip3, and pip. Last errors: ${errors.join(" | ")}`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl }) {
|
|
385
|
+
console.log("Python venv is unavailable; falling back to system Python user install for langfuse.");
|
|
386
|
+
runPipInstallWithFallback({ pythonCmd, pipIndexUrl });
|
|
387
|
+
if (!pythonCanImport(pythonCmd, "langfuse")) {
|
|
388
|
+
throw new Error("langfuse was installed with pip, but python3 still cannot import it. Install python3-venv and rerun setup, for example: sudo apt install python3-venv");
|
|
389
|
+
}
|
|
390
|
+
return pythonCmd;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function createHookLauncher({ hooksDir, hookPython, pyPath }) {
|
|
394
394
|
if (process.platform === "win32") {
|
|
395
395
|
const launcher = path.join(hooksDir, "run-langfuse-hook.cmd");
|
|
396
396
|
const content = [
|
|
@@ -398,7 +398,7 @@ async function createHookLauncher({ hooksDir, hookPython, pyPath }) {
|
|
|
398
398
|
`${cmdQuote(hookPython)} ${cmdQuote(pyPath)}`,
|
|
399
399
|
""
|
|
400
400
|
].join(os.EOL);
|
|
401
|
-
await fsp.writeFile(launcher, content, "utf8");
|
|
401
|
+
await fsp.writeFile(launcher, "\ufeff" + content, "utf8");
|
|
402
402
|
return launcher;
|
|
403
403
|
}
|
|
404
404
|
|
|
@@ -420,14 +420,14 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
|
|
|
420
420
|
|
|
421
421
|
if (!fs.existsSync(venvPython)) {
|
|
422
422
|
console.log(`Creating Python virtual environment: ${venvDir}`);
|
|
423
|
-
try {
|
|
424
|
-
execFileSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit" });
|
|
425
|
-
} catch (e) {
|
|
426
|
-
if (process.platform !== "win32") {
|
|
427
|
-
return installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl });
|
|
428
|
-
}
|
|
429
|
-
throw new Error("Failed to create Python venv. Please confirm Python venv support is available.");
|
|
430
|
-
}
|
|
423
|
+
try {
|
|
424
|
+
execFileSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } });
|
|
425
|
+
} catch (e) {
|
|
426
|
+
if (process.platform !== "win32") {
|
|
427
|
+
return installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl });
|
|
428
|
+
}
|
|
429
|
+
throw new Error("Failed to create Python venv. Please confirm Python venv support is available.");
|
|
430
|
+
}
|
|
431
431
|
}
|
|
432
432
|
|
|
433
433
|
console.log("Installing/updating Python package in venv: langfuse");
|
|
@@ -435,14 +435,14 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
|
|
|
435
435
|
execFileSync(
|
|
436
436
|
venvPython,
|
|
437
437
|
["-m", "pip", "install", "-U", "langfuse", "-i", pipIndexUrl],
|
|
438
|
-
{ stdio: "inherit" }
|
|
439
|
-
);
|
|
440
|
-
} catch (e) {
|
|
441
|
-
if (process.platform !== "win32") {
|
|
442
|
-
return installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl });
|
|
443
|
-
}
|
|
444
|
-
throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i ${pipIndexUrl}`);
|
|
445
|
-
}
|
|
438
|
+
{ stdio: "inherit", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } }
|
|
439
|
+
);
|
|
440
|
+
} catch (e) {
|
|
441
|
+
if (process.platform !== "win32") {
|
|
442
|
+
return installLangfuseWithSystemPython({ pythonCmd, pipIndexUrl });
|
|
443
|
+
}
|
|
444
|
+
throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i ${pipIndexUrl}`);
|
|
445
|
+
}
|
|
446
446
|
|
|
447
447
|
return venvPython;
|
|
448
448
|
}
|