oh-langfuse 0.1.79 → 0.1.80
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/SELF_VERIFY.md +25 -25
- package/bin/cli.js +41 -41
- package/codex_langfuse_notify.py +162 -2
- package/langfuse_hook.py +165 -2
- package/package.json +2 -1
- package/scripts/auto-update-runtime.mjs +14 -14
- package/scripts/cli-detection-utils.mjs +114 -114
- package/scripts/codex-langfuse-check.mjs +65 -65
- package/scripts/codex-langfuse-setup.mjs +140 -140
- package/scripts/json-utils.mjs +99 -99
- package/scripts/langfuse-check.mjs +50 -50
- package/scripts/langfuse-setup.mjs +154 -154
- package/scripts/opencode-langfuse-check.mjs +208 -199
- package/scripts/opencode-langfuse-run.mjs +21 -21
- package/scripts/opencode-langfuse-setup.mjs +106 -50
- package/scripts/opencode-skills-discover.mjs +125 -0
- package/scripts/real-self-verify.mjs +103 -103
- package/scripts/resolve-opencode-cli.mjs +53 -53
- package/scripts/update-langfuse-runtime.mjs +49 -49
- package/setup-langfuse.bat +19 -19
- package/setup-langfuse.sh +19 -19
|
@@ -2,28 +2,28 @@ import fs from "node:fs";
|
|
|
2
2
|
import fsp from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import os from "node:os";
|
|
5
|
-
import { execFileSync, spawnSync } from "node:child_process";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
import { defaultWindowsNpmCliCandidates, listSystemCliCandidates } from "./cli-detection-utils.mjs";
|
|
8
|
-
import { writeRuntimeInstallRecord } from "./runtime-state-utils.mjs";
|
|
9
|
-
|
|
10
|
-
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
-
const DEFAULT_LANGFUSE_BASE_URL = "https://metrics.openharmonyinsight.cn";
|
|
12
|
-
const LEGACY_LANGFUSE_BASE_URLS = new Set([
|
|
13
|
-
"http://120.46.221.227:3000",
|
|
14
|
-
"https://120.46.221.227:3000",
|
|
15
|
-
]);
|
|
16
|
-
const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
|
|
17
|
-
const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
|
|
18
|
-
|
|
19
|
-
function normalizeLegacyBaseUrl(baseUrl) {
|
|
20
|
-
const value = String(baseUrl || "").trim().replace(/\/+$/, "");
|
|
21
|
-
return LEGACY_LANGFUSE_BASE_URLS.has(value) ? DEFAULT_LANGFUSE_BASE_URL : baseUrl;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function normalizeUserId(v) {
|
|
25
|
-
return String(v || "").trim();
|
|
26
|
-
}
|
|
5
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { defaultWindowsNpmCliCandidates, listSystemCliCandidates } from "./cli-detection-utils.mjs";
|
|
8
|
+
import { writeRuntimeInstallRecord } from "./runtime-state-utils.mjs";
|
|
9
|
+
|
|
10
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
const DEFAULT_LANGFUSE_BASE_URL = "https://metrics.openharmonyinsight.cn";
|
|
12
|
+
const LEGACY_LANGFUSE_BASE_URLS = new Set([
|
|
13
|
+
"http://120.46.221.227:3000",
|
|
14
|
+
"https://120.46.221.227:3000",
|
|
15
|
+
]);
|
|
16
|
+
const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
|
|
17
|
+
const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
|
|
18
|
+
|
|
19
|
+
function normalizeLegacyBaseUrl(baseUrl) {
|
|
20
|
+
const value = String(baseUrl || "").trim().replace(/\/+$/, "");
|
|
21
|
+
return LEGACY_LANGFUSE_BASE_URLS.has(value) ? DEFAULT_LANGFUSE_BASE_URL : baseUrl;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function normalizeUserId(v) {
|
|
25
|
+
return String(v || "").trim();
|
|
26
|
+
}
|
|
27
27
|
|
|
28
28
|
function assertValidUserId(userId) {
|
|
29
29
|
if (!USER_ID_PATTERN.test(normalizeUserId(userId))) {
|
|
@@ -78,63 +78,63 @@ function pythonExecutableInVenv(venvDir) {
|
|
|
78
78
|
: path.join(venvDir, "bin", "python");
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
function pythonEnv(extraPythonPath = "") {
|
|
82
|
-
const env = { ...process.env, PYTHONUTF8: "1" };
|
|
83
|
-
if (extraPythonPath) {
|
|
84
|
-
env.PYTHONPATH = process.env.PYTHONPATH ? `${extraPythonPath}${path.delimiter}${process.env.PYTHONPATH}` : extraPythonPath;
|
|
85
|
-
}
|
|
86
|
-
return env;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function pythonCanImport(pythonCmd, moduleName, extraPythonPath = "") {
|
|
90
|
-
try {
|
|
91
|
-
execFileSync(pythonCmd, ["-c", `import ${moduleName}`], { stdio: "ignore", encoding: "utf8", env: pythonEnv(extraPythonPath) });
|
|
92
|
-
return true;
|
|
93
|
-
} catch {
|
|
94
|
-
return false;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function installLangfuseIntoTargetDir({ pythonCmd, baseDir, pipIndexUrl }) {
|
|
99
|
-
const targetDir = path.join(baseDir, "langfuse-python");
|
|
100
|
-
ensureDir(targetDir);
|
|
101
|
-
console.log(`Installing Python package into isolated target: ${targetDir}`);
|
|
102
|
-
execFileSync(
|
|
103
|
-
pythonCmd,
|
|
104
|
-
["-m", "pip", "install", "--target", targetDir, "--upgrade", "langfuse", "-i", pipIndexUrl],
|
|
105
|
-
{ stdio: "inherit", encoding: "utf8", env: pythonEnv() }
|
|
106
|
-
);
|
|
107
|
-
if (!pythonCanImport(pythonCmd, "langfuse", targetDir)) {
|
|
108
|
-
throw new Error(`langfuse was installed into ${targetDir}, but ${pythonCmd} still cannot import it with PYTHONPATH.`);
|
|
109
|
-
}
|
|
110
|
-
return { python: pythonCmd, pythonPath: targetDir };
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function runPipInstallWithFallback({ pythonCmd, baseDir, pipIndexUrl }) {
|
|
114
|
-
const attempts = [
|
|
115
|
-
() => installLangfuseIntoTargetDir({ pythonCmd, baseDir, pipIndexUrl }),
|
|
116
|
-
() => installLangfuseIntoTargetDir({ pythonCmd: "python3", baseDir, pipIndexUrl }),
|
|
117
|
-
() => installLangfuseIntoTargetDir({ pythonCmd: "python", baseDir, pipIndexUrl })
|
|
118
|
-
];
|
|
119
|
-
|
|
120
|
-
const errors = [];
|
|
121
|
-
for (const attempt of attempts) {
|
|
122
|
-
try {
|
|
123
|
-
return attempt();
|
|
124
|
-
} catch (error) {
|
|
125
|
-
errors.push(error?.message || String(error));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
throw new Error(
|
|
130
|
-
`Failed to install langfuse into isolated Python target. Last errors: ${errors.join(" | ")}`
|
|
131
|
-
);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl }) {
|
|
135
|
-
console.log("Python venv is unavailable; falling back to isolated PYTHONPATH install for langfuse.");
|
|
136
|
-
return runPipInstallWithFallback({ pythonCmd, baseDir, pipIndexUrl });
|
|
137
|
-
}
|
|
81
|
+
function pythonEnv(extraPythonPath = "") {
|
|
82
|
+
const env = { ...process.env, PYTHONUTF8: "1" };
|
|
83
|
+
if (extraPythonPath) {
|
|
84
|
+
env.PYTHONPATH = process.env.PYTHONPATH ? `${extraPythonPath}${path.delimiter}${process.env.PYTHONPATH}` : extraPythonPath;
|
|
85
|
+
}
|
|
86
|
+
return env;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function pythonCanImport(pythonCmd, moduleName, extraPythonPath = "") {
|
|
90
|
+
try {
|
|
91
|
+
execFileSync(pythonCmd, ["-c", `import ${moduleName}`], { stdio: "ignore", encoding: "utf8", env: pythonEnv(extraPythonPath) });
|
|
92
|
+
return true;
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function installLangfuseIntoTargetDir({ pythonCmd, baseDir, pipIndexUrl }) {
|
|
99
|
+
const targetDir = path.join(baseDir, "langfuse-python");
|
|
100
|
+
ensureDir(targetDir);
|
|
101
|
+
console.log(`Installing Python package into isolated target: ${targetDir}`);
|
|
102
|
+
execFileSync(
|
|
103
|
+
pythonCmd,
|
|
104
|
+
["-m", "pip", "install", "--target", targetDir, "--upgrade", "langfuse", "-i", pipIndexUrl],
|
|
105
|
+
{ stdio: "inherit", encoding: "utf8", env: pythonEnv() }
|
|
106
|
+
);
|
|
107
|
+
if (!pythonCanImport(pythonCmd, "langfuse", targetDir)) {
|
|
108
|
+
throw new Error(`langfuse was installed into ${targetDir}, but ${pythonCmd} still cannot import it with PYTHONPATH.`);
|
|
109
|
+
}
|
|
110
|
+
return { python: pythonCmd, pythonPath: targetDir };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function runPipInstallWithFallback({ pythonCmd, baseDir, pipIndexUrl }) {
|
|
114
|
+
const attempts = [
|
|
115
|
+
() => installLangfuseIntoTargetDir({ pythonCmd, baseDir, pipIndexUrl }),
|
|
116
|
+
() => installLangfuseIntoTargetDir({ pythonCmd: "python3", baseDir, pipIndexUrl }),
|
|
117
|
+
() => installLangfuseIntoTargetDir({ pythonCmd: "python", baseDir, pipIndexUrl })
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
const errors = [];
|
|
121
|
+
for (const attempt of attempts) {
|
|
122
|
+
try {
|
|
123
|
+
return attempt();
|
|
124
|
+
} catch (error) {
|
|
125
|
+
errors.push(error?.message || String(error));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Failed to install langfuse into isolated Python target. Last errors: ${errors.join(" | ")}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl }) {
|
|
135
|
+
console.log("Python venv is unavailable; falling back to isolated PYTHONPATH install for langfuse.");
|
|
136
|
+
return runPipInstallWithFallback({ pythonCmd, baseDir, pipIndexUrl });
|
|
137
|
+
}
|
|
138
138
|
|
|
139
139
|
function shQuote(s) {
|
|
140
140
|
return `'${String(s).replace(/'/g, "'\"'\"'")}'`;
|
|
@@ -160,7 +160,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
160
160
|
const helperDir = autoUpdateHelperDir();
|
|
161
161
|
ensureDir(helperDir);
|
|
162
162
|
const runtimePath = autoUpdateRuntimePath();
|
|
163
|
-
const fallbackArgs = ["-y", "--registry=https://registry.npmjs.org/", "oh-langfuse@latest", "auto-update", target, "--skip-check", "--startup-status"];
|
|
163
|
+
const fallbackArgs = ["-y", "--registry=https://registry.npmjs.org/", "oh-langfuse@latest", "auto-update", target, "--skip-check", "--startup-status"];
|
|
164
164
|
|
|
165
165
|
if (process.platform === "win32") {
|
|
166
166
|
const helper = path.join(helperDir, `oh-langfuse-auto-update-${target}.cmd`);
|
|
@@ -175,7 +175,7 @@ function writeAutoUpdateHelper(target) {
|
|
|
175
175
|
"exit /b 0",
|
|
176
176
|
""
|
|
177
177
|
];
|
|
178
|
-
fs.writeFileSync(helper, lines.join(os.EOL), "utf8");
|
|
178
|
+
fs.writeFileSync(helper, lines.join(os.EOL), "utf8");
|
|
179
179
|
return helper;
|
|
180
180
|
}
|
|
181
181
|
|
|
@@ -245,11 +245,11 @@ function listLocalCodexCliCandidates() {
|
|
|
245
245
|
return candidates;
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
-
function listCliCandidatesFromPath(target) {
|
|
249
|
-
return listSystemCliCandidates(target);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function sortWindowsCliCandidates(candidates) {
|
|
248
|
+
function listCliCandidatesFromPath(target) {
|
|
249
|
+
return listSystemCliCandidates(target);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function sortWindowsCliCandidates(candidates) {
|
|
253
253
|
const extPriority = (candidate) => {
|
|
254
254
|
const ext = path.extname(String(candidate || "")).toLowerCase();
|
|
255
255
|
if (ext === ".cmd" || ext === ".bat" || ext === ".exe") return 0;
|
|
@@ -258,13 +258,13 @@ function sortWindowsCliCandidates(candidates) {
|
|
|
258
258
|
return [...candidates].sort((a, b) => extPriority(a) - extPriority(b));
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
function resolveAgentCli({ target, preferred = "", shimDir = "" }) {
|
|
262
|
-
const candidates = [
|
|
263
|
-
preferred,
|
|
264
|
-
...(target === "codex" ? listLocalCodexCliCandidates() : []),
|
|
265
|
-
...defaultWindowsNpmCliCandidates(target),
|
|
266
|
-
...listCliCandidatesFromPath(target)
|
|
267
|
-
];
|
|
261
|
+
function resolveAgentCli({ target, preferred = "", shimDir = "" }) {
|
|
262
|
+
const candidates = [
|
|
263
|
+
preferred,
|
|
264
|
+
...(target === "codex" ? listLocalCodexCliCandidates() : []),
|
|
265
|
+
...defaultWindowsNpmCliCandidates(target),
|
|
266
|
+
...listCliCandidatesFromPath(target)
|
|
267
|
+
];
|
|
268
268
|
for (const candidate of candidates) {
|
|
269
269
|
const found = existingCliCandidate(candidate, shimDir);
|
|
270
270
|
if (found) return found;
|
|
@@ -318,7 +318,7 @@ function writeAgentCommandShim({ baseDir, target, executable, realCli, publicKey
|
|
|
318
318
|
"exit /b %ERRORLEVEL%",
|
|
319
319
|
""
|
|
320
320
|
].filter(Boolean).join(os.EOL);
|
|
321
|
-
fs.writeFileSync(shim, content, "utf8");
|
|
321
|
+
fs.writeFileSync(shim, content, "utf8");
|
|
322
322
|
return { shim, shimDir };
|
|
323
323
|
}
|
|
324
324
|
|
|
@@ -350,7 +350,7 @@ function createAgentLauncher({ baseDir, target, executable }) {
|
|
|
350
350
|
`${executable} %*`,
|
|
351
351
|
""
|
|
352
352
|
].join(os.EOL);
|
|
353
|
-
fs.writeFileSync(launcher, content, "utf8");
|
|
353
|
+
fs.writeFileSync(launcher, content, "utf8");
|
|
354
354
|
return launcher;
|
|
355
355
|
}
|
|
356
356
|
|
|
@@ -375,13 +375,13 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
|
|
|
375
375
|
if (!fs.existsSync(venvPython)) {
|
|
376
376
|
console.log(`Creating Python virtual environment: ${venvDir}`);
|
|
377
377
|
try {
|
|
378
|
-
execFileSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } });
|
|
379
|
-
} catch (e) {
|
|
380
|
-
if (process.platform !== "win32") {
|
|
381
|
-
return installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl });
|
|
382
|
-
}
|
|
383
|
-
throw new Error("Failed to create Python venv. Please confirm Python venv support is available.");
|
|
384
|
-
}
|
|
378
|
+
execFileSync(pythonCmd, ["-m", "venv", venvDir], { stdio: "inherit", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } });
|
|
379
|
+
} catch (e) {
|
|
380
|
+
if (process.platform !== "win32") {
|
|
381
|
+
return installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl });
|
|
382
|
+
}
|
|
383
|
+
throw new Error("Failed to create Python venv. Please confirm Python venv support is available.");
|
|
384
|
+
}
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
console.log("Installing/updating Python package in venv: langfuse");
|
|
@@ -390,31 +390,31 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
|
|
|
390
390
|
venvPython,
|
|
391
391
|
["-m", "pip", "install", "-U", "langfuse", "-i", pipIndexUrl],
|
|
392
392
|
{ stdio: "inherit", encoding: "utf8", env: { ...process.env, PYTHONUTF8: "1" } }
|
|
393
|
-
);
|
|
394
|
-
} catch (e) {
|
|
395
|
-
if (process.platform !== "win32") {
|
|
396
|
-
return installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl });
|
|
397
|
-
}
|
|
398
|
-
throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i ${pipIndexUrl}`);
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
if (!pythonCanImport(venvPython, "langfuse")) {
|
|
402
|
-
if (process.platform !== "win32") {
|
|
403
|
-
return installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl });
|
|
404
|
-
}
|
|
405
|
-
throw new Error(`langfuse was installed in venv, but cannot be imported by ${venvPython}`);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return { python: venvPython, pythonPath: "" };
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
function codexNotifyCommand({ python, pythonPath = "", hookPath }) {
|
|
412
|
-
if (pythonPath && process.platform !== "win32") {
|
|
413
|
-
const script = `if [ -n "\${PYTHONPATH:-}" ]; then export PYTHONPATH=${shQuote(pythonPath)}:"$PYTHONPATH"; else export PYTHONPATH=${shQuote(pythonPath)}; fi; exec ${shQuote(python)} ${shQuote(hookPath)}`;
|
|
414
|
-
return ["sh", "-c", script];
|
|
415
|
-
}
|
|
416
|
-
return [python, hookPath];
|
|
417
|
-
}
|
|
393
|
+
);
|
|
394
|
+
} catch (e) {
|
|
395
|
+
if (process.platform !== "win32") {
|
|
396
|
+
return installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl });
|
|
397
|
+
}
|
|
398
|
+
throw new Error(`Failed to install langfuse in venv: ${venvPython} -m pip install -U langfuse -i ${pipIndexUrl}`);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
if (!pythonCanImport(venvPython, "langfuse")) {
|
|
402
|
+
if (process.platform !== "win32") {
|
|
403
|
+
return installLangfuseWithSystemPython({ pythonCmd, baseDir, pipIndexUrl });
|
|
404
|
+
}
|
|
405
|
+
throw new Error(`langfuse was installed in venv, but cannot be imported by ${venvPython}`);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
return { python: venvPython, pythonPath: "" };
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function codexNotifyCommand({ python, pythonPath = "", hookPath }) {
|
|
412
|
+
if (pythonPath && process.platform !== "win32") {
|
|
413
|
+
const script = `if [ -n "\${PYTHONPATH:-}" ]; then export PYTHONPATH=${shQuote(pythonPath)}:"$PYTHONPATH"; else export PYTHONPATH=${shQuote(pythonPath)}; fi; exec ${shQuote(python)} ${shQuote(hookPath)}`;
|
|
414
|
+
return ["sh", "-c", script];
|
|
415
|
+
}
|
|
416
|
+
return [python, hookPath];
|
|
417
|
+
}
|
|
418
418
|
|
|
419
419
|
function updateCodexNotify(configPath, notifyCommand) {
|
|
420
420
|
let content = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
|
|
@@ -473,14 +473,14 @@ async function main() {
|
|
|
473
473
|
args.publicKey || process.env.LANGFUSE_PUBLIC_KEY || "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
|
|
474
474
|
const secretKey =
|
|
475
475
|
args.secretKey || process.env.LANGFUSE_SECRET_KEY || "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
|
|
476
|
-
const baseUrl = normalizeLegacyBaseUrl(
|
|
477
|
-
args.langfuseBaseUrl ||
|
|
478
|
-
args.langfuseHost ||
|
|
479
|
-
args.host ||
|
|
480
|
-
process.env.LANGFUSE_BASEURL ||
|
|
481
|
-
process.env.LANGFUSE_HOST ||
|
|
482
|
-
DEFAULT_LANGFUSE_BASE_URL
|
|
483
|
-
);
|
|
476
|
+
const baseUrl = normalizeLegacyBaseUrl(
|
|
477
|
+
args.langfuseBaseUrl ||
|
|
478
|
+
args.langfuseHost ||
|
|
479
|
+
args.host ||
|
|
480
|
+
process.env.LANGFUSE_BASEURL ||
|
|
481
|
+
process.env.LANGFUSE_HOST ||
|
|
482
|
+
DEFAULT_LANGFUSE_BASE_URL
|
|
483
|
+
);
|
|
484
484
|
const userId = normalizeUserId(args.userId || args.userid || "");
|
|
485
485
|
if (!userId || typeof userId !== "string") {
|
|
486
486
|
throw new Error("缺少参数:--userId=你的工号");
|
|
@@ -516,11 +516,11 @@ async function main() {
|
|
|
516
516
|
userId,
|
|
517
517
|
});
|
|
518
518
|
|
|
519
|
-
const pythonCmd = process.platform === "win32" ? "python" : "python3";
|
|
520
|
-
const notifyRuntime = args["skip-pip-install"]
|
|
521
|
-
? { python: pythonCmd, pythonPath: "" }
|
|
522
|
-
: createOrUpdateLangfuseVenv({ baseDir: codexHome, pipIndexUrl });
|
|
523
|
-
updateCodexNotify(configPath, codexNotifyCommand({ python: notifyRuntime.python, pythonPath: notifyRuntime.pythonPath, hookPath: destHook }));
|
|
519
|
+
const pythonCmd = process.platform === "win32" ? "python" : "python3";
|
|
520
|
+
const notifyRuntime = args["skip-pip-install"]
|
|
521
|
+
? { python: pythonCmd, pythonPath: "" }
|
|
522
|
+
: createOrUpdateLangfuseVenv({ baseDir: codexHome, pipIndexUrl });
|
|
523
|
+
updateCodexNotify(configPath, codexNotifyCommand({ python: notifyRuntime.python, pythonPath: notifyRuntime.pythonPath, hookPath: destHook }));
|
|
524
524
|
const codexShimDir = path.join(codexHome, "bin");
|
|
525
525
|
const realCodexCli = resolveAgentCli({ target: "codex", preferred: args.cmd || "", shimDir: codexShimDir });
|
|
526
526
|
const directShim = writeAgentCommandShim({
|
package/scripts/json-utils.mjs
CHANGED
|
@@ -1,99 +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
|
+
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
|
+
}
|