oh-langfuse 0.1.60 → 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 +56 -54
- 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,12 +39,12 @@ function parseArgs(argv) {
|
|
|
39
39
|
return args;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
async function latestVersion(packageName, registry = "https://registry.npmjs.org") {
|
|
43
|
-
const base = registry.replace(/\/+$/, "");
|
|
44
|
-
const response = await fetch(`${base}/${packageName}`, { headers: { accept: "application/json" } });
|
|
45
|
-
if (!response.ok) throw new Error(`npm registry ${response.status} ${response.statusText}`);
|
|
46
|
-
return extractVersionFromNpmMetadata(await response.json());
|
|
47
|
-
}
|
|
42
|
+
async function latestVersion(packageName, registry = "https://registry.npmjs.org") {
|
|
43
|
+
const base = registry.replace(/\/+$/, "");
|
|
44
|
+
const response = await fetch(`${base}/${packageName}`, { headers: { accept: "application/json" } });
|
|
45
|
+
if (!response.ok) throw new Error(`npm registry ${response.status} ${response.statusText}`);
|
|
46
|
+
return extractVersionFromNpmMetadata(await response.json());
|
|
47
|
+
}
|
|
48
48
|
|
|
49
49
|
function question(text) {
|
|
50
50
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -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,26 +100,26 @@ 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, packageName, version) {
|
|
102
|
-
const label = targetLabel(target);
|
|
103
|
-
console.log(paint(`[OK] ${label} Langfuse \u5df2\u662f\u6700\u65b0\uff1a${packageName}@${version}`, t.bold, t.green));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function updateCommandForPackage(packageName, target) {
|
|
107
|
-
if (packageName === "oh-aicoding-tool") return `npx oh-aicoding-tool@latest langfuse update ${target}`;
|
|
108
|
-
return `npx ${packageName}@latest update ${target}`;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function updateArgsForPackage(packageName, target) {
|
|
112
|
-
if (packageName === "oh-aicoding-tool") return ["-y", "oh-aicoding-tool@latest", "langfuse", "update", target];
|
|
113
|
-
return ["-y", `${packageName}@latest`, "update", target];
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function runUpdate(target, packageName, args) {
|
|
117
|
-
const updateArgs = updateArgsForPackage(packageName, target);
|
|
118
|
-
if (args["skip-check"]) updateArgs.push("--skip-check");
|
|
119
|
-
if (args.npmRegistry) updateArgs.push(`--npmRegistry=${args.npmRegistry}`);
|
|
120
|
-
if (args.pipIndexUrl) updateArgs.push(`--pipIndexUrl=${args.pipIndexUrl}`);
|
|
103
|
+
function printAlreadyCurrent(target, packageName, version) {
|
|
104
|
+
const label = targetLabel(target);
|
|
105
|
+
console.log(paint(`[OK] ${label} Langfuse \u5df2\u662f\u6700\u65b0\uff1a${packageName}@${version}`, t.bold, t.green));
|
|
106
|
+
}
|
|
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);
|
|
120
|
+
if (args["skip-check"]) updateArgs.push("--skip-check");
|
|
121
|
+
if (args.npmRegistry) updateArgs.push(`--npmRegistry=${args.npmRegistry}`);
|
|
122
|
+
if (args.pipIndexUrl) updateArgs.push(`--pipIndexUrl=${args.pipIndexUrl}`);
|
|
121
123
|
const result = runNpx(updateArgs, {
|
|
122
124
|
stdio: "inherit",
|
|
123
125
|
timeout: 900000,
|
|
@@ -143,27 +145,27 @@ async function main() {
|
|
|
143
145
|
throw new Error("Usage: oh-langfuse auto-update <claude|opencode|codex>");
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
const record = getRuntimeInstallRecord(target);
|
|
147
|
-
const packageName = record?.packageName || packageJson.name;
|
|
148
|
-
let latest;
|
|
149
|
-
try {
|
|
150
|
-
latest = await latestVersion(packageName, args.npmRegistry);
|
|
151
|
-
} catch (error) {
|
|
152
|
-
if (args.verbose) console.log(`[INFO] ${packageName} update check skipped: ${error.message}`);
|
|
153
|
-
return 0;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const installedVersion = record?.packageVersion || record?.version || "";
|
|
157
|
-
const needsUpdate = installedVersion ? isNewerVersion(latest, installedVersion) : isNewerVersion(latest, packageJson.version);
|
|
158
|
-
if (!needsUpdate) {
|
|
159
|
-
if (shouldPrintStartupStatus(args)) printAlreadyCurrent(target, packageName, installedVersion || packageJson.version);
|
|
160
|
-
return 0;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const message = installedVersion
|
|
164
|
-
? `${packageName} ${target} runtime update available: ${installedVersion} -> ${latest}.`
|
|
165
|
-
: `${packageName} ${target} runtime may need update. Latest package: ${latest}.`;
|
|
166
|
-
const updateCommand = updateCommandForPackage(packageName, target);
|
|
148
|
+
const record = getRuntimeInstallRecord(target);
|
|
149
|
+
const packageName = record?.packageName || packageJson.name;
|
|
150
|
+
let latest;
|
|
151
|
+
try {
|
|
152
|
+
latest = await latestVersion(packageName, args.npmRegistry);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (args.verbose) console.log(`[INFO] ${packageName} update check skipped: ${error.message}`);
|
|
155
|
+
return 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const installedVersion = record?.packageVersion || record?.version || "";
|
|
159
|
+
const needsUpdate = installedVersion ? isNewerVersion(latest, installedVersion) : isNewerVersion(latest, packageJson.version);
|
|
160
|
+
if (!needsUpdate) {
|
|
161
|
+
if (shouldPrintStartupStatus(args)) printAlreadyCurrent(target, packageName, installedVersion || packageJson.version);
|
|
162
|
+
return 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const message = installedVersion
|
|
166
|
+
? `${packageName} ${target} runtime update available: ${installedVersion} -> ${latest}.`
|
|
167
|
+
: `${packageName} ${target} runtime may need update. Latest package: ${latest}.`;
|
|
168
|
+
const updateCommand = updateCommandForPackage(packageName, target);
|
|
167
169
|
|
|
168
170
|
if (mode === "notify") {
|
|
169
171
|
printUpdateAvailable(target, message, updateCommand);
|
|
@@ -172,9 +174,9 @@ async function main() {
|
|
|
172
174
|
}
|
|
173
175
|
|
|
174
176
|
if (mode === "auto") {
|
|
175
|
-
printUpdateAvailable(target, message, updateCommand);
|
|
176
|
-
printUpdateCommand(target, updateCommand);
|
|
177
|
-
const code = runUpdate(target, packageName, args);
|
|
177
|
+
printUpdateAvailable(target, message, updateCommand);
|
|
178
|
+
printUpdateCommand(target, updateCommand);
|
|
179
|
+
const code = runUpdate(target, packageName, args);
|
|
178
180
|
return args.strict ? code : 0;
|
|
179
181
|
}
|
|
180
182
|
|
|
@@ -184,10 +186,10 @@ async function main() {
|
|
|
184
186
|
}
|
|
185
187
|
|
|
186
188
|
printUpdateAvailable(target, message, updateCommand);
|
|
187
|
-
const answer = await question(`${paint("[CONFIRM]", t.bold, t.gold)} Update now? (y/N) `);
|
|
188
|
-
if (!/^(y|yes)$/i.test(answer)) return 0;
|
|
189
|
-
printUpdateCommand(target, updateCommand);
|
|
190
|
-
const code = runUpdate(target, packageName, args);
|
|
189
|
+
const answer = await question(`${paint("[CONFIRM]", t.bold, t.gold)} Update now? (y/N) `);
|
|
190
|
+
if (!/^(y|yes)$/i.test(answer)) return 0;
|
|
191
|
+
printUpdateCommand(target, updateCommand);
|
|
192
|
+
const code = runUpdate(target, packageName, args);
|
|
191
193
|
return args.strict ? code : 0;
|
|
192
194
|
}
|
|
193
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() {
|