oh-langfuse 0.1.17 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@ Claude Code、OpenCode 和 Codex。它提供终端安装向导、直接 setup/ch
9
9
  本仓是 AI Coding 工具链中的 Langfuse 能力包,负责:
10
10
 
11
11
  - 检查 Node.js、npm、Python、pip、OpenCode CLI 等本地依赖;
12
- - 收集 Langfuse 用户标识,通常是员工号,例如 `h00613222`;
12
+ - 收集 Langfuse 用户标识,必须匹配 `^[a-z](?:\d{8}|wx\d{7})$`,例如 `h00613222` 或 `hwx1234567`;
13
13
  - 安装或更新 hook 脚本、插件文件、Python 虚拟环境和用户级配置;
14
14
  - 校验 Claude Code、OpenCode、Codex 的 Langfuse 配置是否生效。
15
15
 
package/bin/cli.js CHANGED
@@ -8,9 +8,11 @@ import { spawnSync } from "node:child_process";
8
8
  const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
9
9
  const scriptsDir = path.join(rootDir, "scripts");
10
10
 
11
- const DEFAULT_LANGFUSE_BASE_URL = "http://120.46.221.227:3000";
12
- const DEFAULT_LANGFUSE_PUBLIC_KEY = "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
13
- const DEFAULT_LANGFUSE_SECRET_KEY = "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
11
+ const DEFAULT_LANGFUSE_BASE_URL = "http://120.46.221.227:3000";
12
+ const DEFAULT_LANGFUSE_PUBLIC_KEY = "pk-lf-da0c90a7-6e93-4eb7-bb86-c1047c8d187d";
13
+ const DEFAULT_LANGFUSE_SECRET_KEY = "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
14
+ const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
15
+ const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
14
16
 
15
17
  const colorEnabled = process.stdout.isTTY && process.env.NO_COLOR !== "1";
16
18
  const ansi = (code) => (colorEnabled ? `\x1b[${code}m` : "");
@@ -81,9 +83,21 @@ function mask(v) {
81
83
  return `${v.slice(0, 4)}...${v.slice(-4)}`;
82
84
  }
83
85
 
84
- function hasValue(v) {
85
- return typeof v === "string" && v.trim().length > 0;
86
- }
86
+ function hasValue(v) {
87
+ return typeof v === "string" && v.trim().length > 0;
88
+ }
89
+
90
+ function normalizeUserId(v) {
91
+ return String(v || "").trim();
92
+ }
93
+
94
+ function isValidUserId(v) {
95
+ return USER_ID_PATTERN.test(normalizeUserId(v));
96
+ }
97
+
98
+ function userIdValidationMessage() {
99
+ return `User ID must match ${USER_ID_PATTERN_TEXT}, for example h00613222 or hwx1234567.`;
100
+ }
87
101
 
88
102
  function scriptPath(name) {
89
103
  return path.join(scriptsDir, name);
@@ -249,17 +263,25 @@ function runNodeScript(name, args = [], { dryRun = false } = {}) {
249
263
  return r.status ?? (r.error ? 1 : 0);
250
264
  }
251
265
 
252
- async function askText(rl, label, { defaultValue = "", required = false } = {}) {
253
- while (true) {
254
- const suffix = defaultValue ? paint(` ${defaultValue}`, t.muted) : "";
255
- const answer = (await rl.question(`${paint(label, t.cyan)}${suffix}\n${paint(">", t.teal)} `)).trim();
256
- const value = answer || defaultValue;
257
- if (!required || hasValue(value)) return value;
258
- console.log(paint("This value is required.", t.red));
259
- }
260
- }
261
-
262
- async function askYesNo(rl, label, { defaultValue = true } = {}) {
266
+ async function askText(rl, label, { defaultValue = "", required = false, validate = null, invalidMessage = "" } = {}) {
267
+ while (true) {
268
+ const suffix = defaultValue ? paint(` ${defaultValue}`, t.muted) : "";
269
+ const answer = (await rl.question(`${paint(label, t.cyan)}${suffix}\n${paint(">", t.teal)} `)).trim();
270
+ const value = answer || defaultValue;
271
+ if (required && !hasValue(value)) {
272
+ console.log(paint("This value is required.", t.red));
273
+ continue;
274
+ }
275
+ if (validate && hasValue(value) && !validate(value)) {
276
+ console.log(paint(invalidMessage || "Invalid value.", t.red));
277
+ continue;
278
+ }
279
+ if (!required || hasValue(value)) return value;
280
+ console.log(paint("This value is required.", t.red));
281
+ }
282
+ }
283
+
284
+ async function askYesNo(rl, label, { defaultValue = false } = {}) {
263
285
  const hint = defaultValue ? "Y/n" : "y/N";
264
286
  while (true) {
265
287
  const answer = (await rl.question(`${paint(label, t.cyan)} ${paint(`(${hint})`, t.muted)} `)).trim().toLowerCase();
@@ -456,7 +478,7 @@ function langfuseConfig(overrides = {}) {
456
478
  baseUrl: envDefault("LANGFUSE_BASEURL", envDefault("LANGFUSE_HOST", DEFAULT_LANGFUSE_BASE_URL)),
457
479
  publicKey: envDefault("LANGFUSE_PUBLIC_KEY", DEFAULT_LANGFUSE_PUBLIC_KEY),
458
480
  secretKey: envDefault("LANGFUSE_SECRET_KEY", DEFAULT_LANGFUSE_SECRET_KEY),
459
- userId: envDefault("LANGFUSE_USER_ID", envDefault("CC_USER_ID", ""))
481
+ userId: ""
460
482
  };
461
483
  for (const [key, value] of Object.entries(overrides)) {
462
484
  if (hasValue(value)) config[key] = value;
@@ -471,10 +493,18 @@ async function collectLangfuseConfig(rl, { requireUserId = false, overrides = {}
471
493
  labelValue("Public Key", config.publicKey, t.blue),
472
494
  labelValue("Secret Key", "configured", t.teal)
473
495
  ]);
474
- if (requireUserId && hasValue(config.userId)) return config;
496
+ if (hasValue(config.userId)) {
497
+ config.userId = normalizeUserId(config.userId);
498
+ if (!isValidUserId(config.userId)) {
499
+ throw new Error(userIdValidationMessage());
500
+ }
501
+ if (requireUserId) return config;
502
+ }
475
503
  config.userId = await askText(rl, "User ID / employee number, for example h00613222", {
476
- defaultValue: requireUserId ? "" : config.userId,
477
- required: requireUserId
504
+ defaultValue: "",
505
+ required: requireUserId,
506
+ validate: isValidUserId,
507
+ invalidMessage: userIdValidationMessage()
478
508
  });
479
509
  return config;
480
510
  }
@@ -504,7 +534,7 @@ async function confirmAction(rl, title, rows, options) {
504
534
  renderSection(title, rows);
505
535
  if (options.dryRun || options.yes) return true;
506
536
  console.log("");
507
- return await askYesNo(rl, "Continue with these changes", { defaultValue: true });
537
+ return await askYesNo(rl, "Continue with these changes", { defaultValue: false });
508
538
  }
509
539
 
510
540
  async function setupClaude(rl, options) {
@@ -687,7 +717,7 @@ async function setupLangfuseMenu(rl, options) {
687
717
  rl,
688
718
  "Select Langfuse setup targets",
689
719
  [
690
- { label: "Claude Code Langfuse", value: "claude", selected: true, description: "Install the Langfuse Stop hook and connect Claude transcripts." },
720
+ { label: "Claude Code Langfuse", value: "claude", selected: false, description: "Install the Langfuse Stop hook and connect Claude transcripts." },
691
721
  { label: "OpenCode Langfuse", value: "opencode", selected: false, description: "Install the Langfuse plugin and enable OpenTelemetry." },
692
722
  { label: "Codex Langfuse", value: "codex", selected: false, description: "Install the Codex notify hook and connect session JSONL events." }
693
723
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-langfuse",
3
- "version": "0.1.17",
3
+ "version": "0.1.18",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Use npm scripts to configure Claude Code / OpenCode / Codex with Langfuse tracing.",
@@ -2,8 +2,21 @@ 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 } from "node:child_process";
6
- import { fileURLToPath } from "node:url";
5
+ import { execFileSync } from "node:child_process";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
9
+ const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
10
+
11
+ function normalizeUserId(v) {
12
+ return String(v || "").trim();
13
+ }
14
+
15
+ function assertValidUserId(userId) {
16
+ if (!USER_ID_PATTERN.test(normalizeUserId(userId))) {
17
+ throw new Error(`工号格式不正确:--userId 必须匹配 ${USER_ID_PATTERN_TEXT},例如 h00613222 或 hwx1234567`);
18
+ }
19
+ }
7
20
 
8
21
  function parseArgs(argv) {
9
22
  const args = {};
@@ -136,10 +149,11 @@ async function main() {
136
149
  process.env.LANGFUSE_BASEURL ||
137
150
  process.env.LANGFUSE_HOST ||
138
151
  "http://120.46.221.227:3000";
139
- const userId = args.userId || args.userid || "";
152
+ const userId = normalizeUserId(args.userId || args.userid || "");
140
153
  if (!userId || typeof userId !== "string") {
141
154
  throw new Error("缺少参数:--userId=你的工号");
142
155
  }
156
+ assertValidUserId(userId);
143
157
  const pipIndexUrl = args.pipIndexUrl || process.env.LANGFUSE_PIP_INDEX_URL || "https://pypi.tuna.tsinghua.edu.cn/simple";
144
158
 
145
159
  if (!publicKey || !secretKey) {
@@ -2,7 +2,20 @@ 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 } from "node:child_process";
5
+ import { execFileSync } from "node:child_process";
6
+
7
+ const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
8
+ const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
9
+
10
+ function normalizeUserId(v) {
11
+ return String(v || "").trim();
12
+ }
13
+
14
+ function assertValidUserId(userId) {
15
+ if (!USER_ID_PATTERN.test(normalizeUserId(userId))) {
16
+ throw new Error(`工号格式不正确:--userId 必须匹配 ${USER_ID_PATTERN_TEXT},例如 h00613222 或 hwx1234567`);
17
+ }
18
+ }
6
19
 
7
20
  function parseArgs(argv) {
8
21
  const args = {};
@@ -158,10 +171,11 @@ function createOrUpdateLangfuseVenv({ baseDir, pipIndexUrl = "https://pypi.tuna.
158
171
  async function main() {
159
172
  const args = parseArgs(process.argv.slice(2));
160
173
 
161
- const userId = args.userId || args.userid;
174
+ const userId = normalizeUserId(args.userId || args.userid);
162
175
  if (!userId || typeof userId !== "string") {
163
176
  throw new Error("缺少参数:--userId=你的工号");
164
177
  }
178
+ assertValidUserId(userId);
165
179
 
166
180
  const langfuseHost =
167
181
  args.langfuseHost ||
@@ -1,7 +1,20 @@
1
1
  import path from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
- import { spawnSync } from "node:child_process";
4
- import { resolveOpencodeCli } from "./resolve-opencode-cli.mjs";
3
+ import { spawnSync } from "node:child_process";
4
+ import { resolveOpencodeCli } from "./resolve-opencode-cli.mjs";
5
+
6
+ const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
7
+ const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
8
+
9
+ function normalizeUserId(v) {
10
+ return String(v || "").trim();
11
+ }
12
+
13
+ function assertValidUserId(userId) {
14
+ if (!USER_ID_PATTERN.test(normalizeUserId(userId))) {
15
+ throw new Error(`User ID must match ${USER_ID_PATTERN_TEXT}, for example h00613222 or hwx1234567.`);
16
+ }
17
+ }
5
18
 
6
19
  function parseArgs(argv) {
7
20
  const args = {};
@@ -30,10 +43,11 @@ function main() {
30
43
  const secretKey =
31
44
  args.secretKey || process.env.LANGFUSE_SECRET_KEY || "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
32
45
  const baseUrl = args.langfuseBaseUrl || process.env.LANGFUSE_BASEURL || "http://120.46.221.227:3000";
33
- const userId = args.userId || args.userid || "";
46
+ const userId = normalizeUserId(args.userId || args.userid || "");
34
47
  if (!userId) {
35
48
  throw new Error("Missing userId. Run with --userId=your-id.");
36
49
  }
50
+ assertValidUserId(userId);
37
51
 
38
52
  // 1) 先执行 setup:默认会写入 Windows 用户级 LANGFUSE_*(从桌面/开始菜单启动 OpenCode 也能读到)
39
53
  const scriptsDir = path.dirname(fileURLToPath(import.meta.url));
@@ -3,8 +3,21 @@ import path from "node:path";
3
3
  import os from "node:os";
4
4
  import { spawn, spawnSync } from "node:child_process";
5
5
  import { parseJsonRelaxed, stripBom } from "./json-utils.mjs";
6
-
7
- function parseArgs(argv) {
6
+
7
+ const USER_ID_PATTERN = /^[a-z](?:\d{8}|wx\d{7})$/;
8
+ const USER_ID_PATTERN_TEXT = "^[a-z](?:\\d{8}|wx\\d{7})$";
9
+
10
+ function normalizeUserId(v) {
11
+ return String(v || "").trim();
12
+ }
13
+
14
+ function assertValidUserId(userId) {
15
+ if (!USER_ID_PATTERN.test(normalizeUserId(userId))) {
16
+ throw new Error(`工号格式不正确:--userId 必须匹配 ${USER_ID_PATTERN_TEXT},例如 h00613222 或 hwx1234567`);
17
+ }
18
+ }
19
+
20
+ function parseArgs(argv) {
8
21
  const args = {};
9
22
  for (const raw of argv) {
10
23
  if (!raw.startsWith("--")) continue;
@@ -448,10 +461,11 @@ async function main() {
448
461
  args.secretKey || process.env.LANGFUSE_SECRET_KEY || "sk-lf-0269b85d-bfdc-442c-bfa3-e737954e3315";
449
462
  const baseUrl =
450
463
  args.langfuseBaseUrl || process.env.LANGFUSE_BASEURL || "http://120.46.221.227:3000";
451
- const userId = args.userId || args.userid || "";
464
+ const userId = normalizeUserId(args.userId || args.userid || "");
452
465
  if (!userId || typeof userId !== "string") {
453
466
  throw new Error("缺少参数:--userId=你的工号");
454
467
  }
468
+ assertValidUserId(userId);
455
469
  const npmRegistry = args.npmRegistry || process.env.OPENCODE_NPM_REGISTRY || process.env.NPM_CONFIG_REGISTRY || "";
456
470
 
457
471
  const home = os.homedir();