oh-langfuse 0.1.71 → 0.1.72
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oh-langfuse",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.72",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Use npm scripts to configure Claude Code / OpenCode / Codex with Langfuse tracing.",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"scripts/auto-update-runtime.mjs",
|
|
17
17
|
"scripts/codex-langfuse-check.mjs",
|
|
18
18
|
"scripts/codex-langfuse-setup.mjs",
|
|
19
|
+
"scripts/cli-detection-utils.mjs",
|
|
19
20
|
"scripts/json-utils.mjs",
|
|
20
21
|
"scripts/langfuse-check.mjs",
|
|
21
22
|
"scripts/langfuse-setup.mjs",
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
|
|
6
|
+
function unique(values) {
|
|
7
|
+
return [...new Set(values.filter(Boolean))];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function stripExecutableExtension(name) {
|
|
11
|
+
return String(name || "").replace(/\.(cmd|bat|exe)$/i, "");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function isTargetBinary(target, candidate) {
|
|
15
|
+
const base = stripExecutableExtension(path.basename(String(candidate || ""))).toLowerCase();
|
|
16
|
+
return base === String(target || "").toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function sortWindowsCliCandidates(candidates) {
|
|
20
|
+
const extPriority = (candidate) => {
|
|
21
|
+
const ext = path.extname(String(candidate || "")).toLowerCase();
|
|
22
|
+
if (ext === ".cmd" || ext === ".bat" || ext === ".exe") return 0;
|
|
23
|
+
return 1;
|
|
24
|
+
};
|
|
25
|
+
return [...candidates].sort((a, b) => extPriority(a) - extPriority(b));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function parseWhereisBinaryOutput(target, stdout) {
|
|
29
|
+
const candidates = [];
|
|
30
|
+
for (const line of String(stdout || "").split(/\r?\n/)) {
|
|
31
|
+
const colon = line.indexOf(":");
|
|
32
|
+
const body = colon === -1 ? line : line.slice(colon + 1);
|
|
33
|
+
for (const token of body.split(/\s+/)) {
|
|
34
|
+
const candidate = token.trim();
|
|
35
|
+
if (candidate && path.isAbsolute(candidate) && isTargetBinary(target, candidate)) {
|
|
36
|
+
candidates.push(candidate);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return unique(candidates);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isLikelyNodePrefix(name) {
|
|
44
|
+
return /^(node|nodejs)(?:[-_.].*)?$/i.test(String(name || ""));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function listCommonUnixCliCandidates(target, roots = ["/usr/local", "/opt"]) {
|
|
48
|
+
if (process.platform === "win32") return [];
|
|
49
|
+
const candidates = [
|
|
50
|
+
path.join("/usr/local", "bin", target),
|
|
51
|
+
path.join("/usr", "bin", target),
|
|
52
|
+
path.join("/bin", target),
|
|
53
|
+
];
|
|
54
|
+
for (const root of roots) {
|
|
55
|
+
try {
|
|
56
|
+
if (!fs.existsSync(root)) continue;
|
|
57
|
+
for (const ent of fs.readdirSync(root, { withFileTypes: true })) {
|
|
58
|
+
if (ent.isDirectory() && isLikelyNodePrefix(ent.name)) {
|
|
59
|
+
candidates.push(path.join(root, ent.name, "bin", target));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
// Best effort only; command lookups and other candidates may still work.
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return unique(candidates);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function listSystemCliCandidates(target) {
|
|
70
|
+
if (!target) return [];
|
|
71
|
+
const candidates = [];
|
|
72
|
+
if (process.platform === "win32") {
|
|
73
|
+
const result = spawnSync("where.exe", [target], { encoding: "utf8", windowsHide: true });
|
|
74
|
+
if (result.status === 0) {
|
|
75
|
+
candidates.push(...String(result.stdout || "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
76
|
+
}
|
|
77
|
+
return sortWindowsCliCandidates(unique(candidates));
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const which = spawnSync("which", ["-a", target], { encoding: "utf8", windowsHide: true });
|
|
81
|
+
if (which.status === 0) {
|
|
82
|
+
candidates.push(...String(which.stdout || "").split(/\r?\n/).map((line) => line.trim()).filter(Boolean));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const whereis = spawnSync("whereis", ["-b", target], { encoding: "utf8", windowsHide: true });
|
|
86
|
+
if (whereis.status === 0) {
|
|
87
|
+
candidates.push(...parseWhereisBinaryOutput(target, whereis.stdout));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
candidates.push(...listCommonUnixCliCandidates(target));
|
|
91
|
+
return unique(candidates);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function candidateExists(candidate) {
|
|
95
|
+
try {
|
|
96
|
+
return Boolean(candidate) && fs.existsSync(candidate);
|
|
97
|
+
} catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function hasSystemCli(target) {
|
|
103
|
+
return listSystemCliCandidates(target).some(candidateExists);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function defaultWindowsNpmCliCandidates(target) {
|
|
107
|
+
const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
108
|
+
if (process.platform !== "win32") return [];
|
|
109
|
+
return [
|
|
110
|
+
path.join(appData, "npm", `${target}.cmd`),
|
|
111
|
+
path.join(appData, "npm", `${target}.exe`),
|
|
112
|
+
path.join(appData, "npm", target),
|
|
113
|
+
];
|
|
114
|
+
}
|
|
@@ -2,9 +2,10 @@ 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 {
|
|
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";
|
|
8
9
|
|
|
9
10
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
11
|
const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
|
|
@@ -217,19 +218,11 @@ function listLocalCodexCliCandidates() {
|
|
|
217
218
|
return candidates;
|
|
218
219
|
}
|
|
219
220
|
|
|
220
|
-
function listCliCandidatesFromPath(target) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const candidates = String(result.stdout || "")
|
|
226
|
-
.split(/\r?\n/)
|
|
227
|
-
.map((line) => line.trim())
|
|
228
|
-
.filter(Boolean);
|
|
229
|
-
return process.platform === "win32" ? sortWindowsCliCandidates(candidates) : candidates;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function sortWindowsCliCandidates(candidates) {
|
|
221
|
+
function listCliCandidatesFromPath(target) {
|
|
222
|
+
return listSystemCliCandidates(target);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function sortWindowsCliCandidates(candidates) {
|
|
233
226
|
const extPriority = (candidate) => {
|
|
234
227
|
const ext = path.extname(String(candidate || "")).toLowerCase();
|
|
235
228
|
if (ext === ".cmd" || ext === ".bat" || ext === ".exe") return 0;
|
|
@@ -238,16 +231,13 @@ function sortWindowsCliCandidates(candidates) {
|
|
|
238
231
|
return [...candidates].sort((a, b) => extPriority(a) - extPriority(b));
|
|
239
232
|
}
|
|
240
233
|
|
|
241
|
-
function resolveAgentCli({ target, preferred = "", shimDir = "" }) {
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
...(target
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
process.platform === "win32" ? path.join(appData, "npm", target) : "",
|
|
249
|
-
...listCliCandidatesFromPath(target)
|
|
250
|
-
];
|
|
234
|
+
function resolveAgentCli({ target, preferred = "", shimDir = "" }) {
|
|
235
|
+
const candidates = [
|
|
236
|
+
preferred,
|
|
237
|
+
...(target === "codex" ? listLocalCodexCliCandidates() : []),
|
|
238
|
+
...defaultWindowsNpmCliCandidates(target),
|
|
239
|
+
...listCliCandidatesFromPath(target)
|
|
240
|
+
];
|
|
251
241
|
for (const candidate of candidates) {
|
|
252
242
|
const found = existingCliCandidate(candidate, shimDir);
|
|
253
243
|
if (found) return found;
|
|
@@ -2,9 +2,10 @@ 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 {
|
|
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";
|
|
8
9
|
|
|
9
10
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
11
|
const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
@@ -221,26 +222,16 @@ function existingCliCandidate(candidate, shimDir) {
|
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
|
|
224
|
-
function listCliCandidatesFromPath(target) {
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
function resolveAgentCli({ target, preferred = "", shimDir = "" }) {
|
|
236
|
-
const appData = process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming");
|
|
237
|
-
const candidates = [
|
|
238
|
-
preferred,
|
|
239
|
-
process.platform === "win32" ? path.join(appData, "npm", `${target}.cmd`) : "",
|
|
240
|
-
process.platform === "win32" ? path.join(appData, "npm", `${target}.exe`) : "",
|
|
241
|
-
process.platform === "win32" ? path.join(appData, "npm", target) : "",
|
|
242
|
-
...listCliCandidatesFromPath(target)
|
|
243
|
-
];
|
|
225
|
+
function listCliCandidatesFromPath(target) {
|
|
226
|
+
return listSystemCliCandidates(target);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function resolveAgentCli({ target, preferred = "", shimDir = "" }) {
|
|
230
|
+
const candidates = [
|
|
231
|
+
preferred,
|
|
232
|
+
...defaultWindowsNpmCliCandidates(target),
|
|
233
|
+
...listCliCandidatesFromPath(target)
|
|
234
|
+
];
|
|
244
235
|
for (const candidate of candidates) {
|
|
245
236
|
const found = existingCliCandidate(candidate, shimDir);
|
|
246
237
|
if (found) return found;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { listSystemCliCandidates } from "./cli-detection-utils.mjs";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* OpenCode CLI 的官方安装脚本默认会把二进制放到 ~/.opencode/bin;
|
|
@@ -36,10 +37,11 @@ export function resolveOpencodeCli(preferred) {
|
|
|
36
37
|
candidates.push(path.join(process.env.APPDATA, "npm", "node_modules", "opencode-windows-x64-baseline", "bin", "opencode.exe"));
|
|
37
38
|
}
|
|
38
39
|
candidates.push(path.join(home, "scoop", "shims", "opencode.exe"));
|
|
39
|
-
} else {
|
|
40
|
-
candidates.push(path.join(home, ".opencode", "bin", "opencode"));
|
|
41
|
-
candidates.push(path.join(home, ".local", "bin", "opencode"));
|
|
42
|
-
|
|
40
|
+
} else {
|
|
41
|
+
candidates.push(path.join(home, ".opencode", "bin", "opencode"));
|
|
42
|
+
candidates.push(path.join(home, ".local", "bin", "opencode"));
|
|
43
|
+
candidates.push(...listSystemCliCandidates("opencode"));
|
|
44
|
+
}
|
|
43
45
|
|
|
44
46
|
for (const c of candidates) {
|
|
45
47
|
if (c && fs.existsSync(c)) return c;
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { spawnSync } from "node:child_process";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { hasSystemCli } from "./cli-detection-utils.mjs";
|
|
7
|
+
import { buildUpdatePlan, extractVersionFromNpmMetadata } from "./update-utils.mjs";
|
|
8
|
+
import { writeRuntimeInstallRecord } from "./runtime-state-utils.mjs";
|
|
8
9
|
|
|
9
10
|
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
10
11
|
const packageJson = JSON.parse(fs.readFileSync(path.join(rootDir, "package.json"), "utf8"));
|
|
@@ -59,16 +60,21 @@ function readJsonIfExists(p) {
|
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
function detectInstalledTargets(home = os.homedir()) {
|
|
63
|
-
const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
|
|
64
|
-
return {
|
|
65
|
-
claude:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
function detectInstalledTargets(home = os.homedir()) {
|
|
64
|
+
const codexHome = process.env.CODEX_HOME || path.join(home, ".codex");
|
|
65
|
+
return {
|
|
66
|
+
claude:
|
|
67
|
+
fs.existsSync(path.join(home, ".claude", "hooks", "langfuse_hook.py")) ||
|
|
68
|
+
hasSystemCli("claude"),
|
|
69
|
+
opencode:
|
|
70
|
+
fs.existsSync(path.join(home, ".config", "opencode", "opencode.json")) ||
|
|
71
|
+
fs.existsSync(path.join(home, ".config", "opencode", "plugins", "opencode-plugin-langfuse")) ||
|
|
72
|
+
hasSystemCli("opencode"),
|
|
73
|
+
codex:
|
|
74
|
+
fs.existsSync(path.join(codexHome, "hooks", "codex_langfuse_notify.py")) ||
|
|
75
|
+
hasSystemCli("codex"),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
72
78
|
|
|
73
79
|
function claudeConfig(home) {
|
|
74
80
|
const settings = readJsonIfExists(path.join(home, ".claude", "settings.json")) || {};
|