ai-otel-setup 1.0.4 → 1.0.6

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/cli.js CHANGED
@@ -280,10 +280,32 @@ function stripLegacyCodexHook(text) {
280
280
 
281
281
  function stripLegacyCodexHooksFlag(text) {
282
282
  // Codex 把 [features].codex_hooks 重命名为 [features].hooks,旧 key 启动时触发 deprecation 警告
283
- // 删 = true 这行,由 managed 块统一写 hooks = true;= false 是显式 opt-out,保留
283
+ // 删 = true 这行,由 ensureFeaturesHooksTrue 统一写 hooks = true;= false 是显式 opt-out,保留
284
284
  return text.replace(/^[ \t]*codex_hooks[ \t]*=[ \t]*true[ \t]*\r?\n/gm, "");
285
285
  }
286
286
 
287
+ function ensureFeaturesHooksTrue(text) {
288
+ // 在用户已有的 [features] 块原地插入 hooks = true(如缺失);没有 [features] 就新建。
289
+ // 不能写在 managed 块里——TOML 1.0 禁止同名 table 重复声明,会被严格解析器拒绝。
290
+ const lines = text.split(/\r?\n/);
291
+ let featuresIdx = -1;
292
+ let hooksKeyExists = false;
293
+ for (let i = 0; i < lines.length; i++) {
294
+ if (featuresIdx === -1) {
295
+ if (/^\s*\[features\]\s*$/.test(lines[i])) featuresIdx = i;
296
+ continue;
297
+ }
298
+ if (/^\s*\[/.test(lines[i])) break; // 下一个 section,结束 [features] 主块扫描
299
+ if (/^[ \t]*hooks[ \t]*=/.test(lines[i])) hooksKeyExists = true;
300
+ }
301
+ if (featuresIdx >= 0) {
302
+ if (hooksKeyExists) return text; // 任何 hooks = ... 都尊重,不覆盖用户显式选择
303
+ lines.splice(featuresIdx + 1, 0, "hooks = true");
304
+ return lines.join("\n");
305
+ }
306
+ return text.trimEnd() + "\n\n[features]\nhooks = true\n";
307
+ }
308
+
287
309
  function buildCodexManagedBlock(endpoint, hookDest, launcherDest) {
288
310
  // exporter / trace_exporter / metrics_exporter 是 externally-tagged enum:
289
311
  // - 写 scalar `exporter = "otlp-grpc"`:codex 解析为 unit variant,因为
@@ -293,11 +315,9 @@ function buildCodexManagedBlock(endpoint, hookDest, launcherDest) {
293
315
  // - 只写 table `[otel.exporter."otlp-grpc"]`:✓ codex 把它解析为
294
316
  // OtlpGrpc { endpoint },tag 来自 key 名。
295
317
  // 官方 sample 之所以能 `exporter = "none"`,是因为 None 本身就是 unit variant。
318
+ // [features].hooks = true 由 ensureFeaturesHooksTrue 写到用户块里,避免重复声明 [features]
296
319
  return [
297
320
  CODEX_MANAGED_BEGIN,
298
- "[features]",
299
- "hooks = true",
300
- "",
301
321
  "[otel]",
302
322
  'environment = "prod"',
303
323
  "log_user_prompt = false",
@@ -336,11 +356,12 @@ function installCodex(home, endpoint) {
336
356
  const bak = backup(configPath);
337
357
  let existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
338
358
 
339
- // 四步去重:先剥离上一次的 managed 块,再清掉旧 schema 残留
359
+ // 先剥离上一次的 managed 块和旧 schema 残留,再保证用户块里有 hooks = true
340
360
  existing = stripCodexManagedBlock(existing);
341
361
  existing = stripLegacyCodexOtel(existing);
342
362
  existing = stripLegacyCodexHook(existing);
343
363
  existing = stripLegacyCodexHooksFlag(existing);
364
+ existing = ensureFeaturesHooksTrue(existing);
344
365
 
345
366
  // hook 同目录的 endpoint.json:hook 脚本运行时读它拿 logs endpoint,避免依赖
346
367
  // shell 前缀注入 env(cmd.exe 不认那种语法,跨平台必须改成走文件)。
@@ -454,7 +475,7 @@ function main() {
454
475
  };
455
476
 
456
477
  // UserPromptSubmit 兜底 hook:复用同一脚本,由 stdin.hook_event_name 在脚本内部
457
- // 分流。客户端做 5 分钟节流,服务端见 entry 已存在则仅补空。用于救 SessionStart
478
+ // 分流。客户端做 2 分钟节流,服务端见 entry 已存在则仅补空。用于救 SessionStart
458
479
  // 因网络/超时丢失的场景(线上观测约 60% 事件因此空 git/hostname)。
459
480
  const promptHookEntry = {
460
481
  matcher: "*",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-otel-setup",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "One-shot installer for AI CLI OpenTelemetry forwarding. Writes Claude Code, Codex CLI, and Gemini CLI telemetry config in a single npx command.",
5
5
  "bin": {
6
6
  "ai-otel-setup": "cli.js",
@@ -20,8 +20,8 @@
20
20
  *
21
21
  * 节流(仅对 UserPromptSubmit):
22
22
  * - 在 ~/.claude/cc-otel-state/sent-<sid>.flag 写 marker
23
- * - 5 分钟内同 sid 跳过 OTLP 上报,避免高频敲键狂发
24
- * - 5 分钟后过期允许重试,给丢包/瞬时故障留救命窗口
23
+ * - 2 分钟内同 sid 跳过 OTLP 上报,避免高频敲键狂发
24
+ * - 2 分钟后过期允许重试,给丢包/瞬时故障留救命窗口
25
25
  */
26
26
 
27
27
  "use strict";
@@ -34,8 +34,8 @@ const http = require("http");
34
34
  const https = require("https");
35
35
  const { URL } = require("url");
36
36
 
37
- // UserPromptSubmit 节流窗口:5 分钟
38
- const PROMPT_THROTTLE_MS = 5 * 60 * 1000;
37
+ // UserPromptSubmit 节流窗口:2 分钟
38
+ const PROMPT_THROTTLE_MS = 2 * 60 * 1000;
39
39
 
40
40
  // -------- 环境变量读取 ----------
41
41
 
@@ -120,7 +120,7 @@ function safeGit(args) {
120
120
  // CC 在 stdin 里告诉脚本是哪个 hook 触发的;UserPromptSubmit 走"兜底"分支
121
121
  const isPromptFallback = input.hook_event_name === "UserPromptSubmit";
122
122
 
123
- // 兜底路径节流:sid 维度 5 分钟最多一次(marker 文件 mtime 判断)。
123
+ // 兜底路径节流:sid 维度 2 分钟最多一次(marker 文件 mtime 判断)。
124
124
  // 失败重试窗口同时由此控制:marker 过期后允许下次 prompt 再发一次。
125
125
  const stateDir = path.join(os.homedir(), ".claude", "cc-otel-state");
126
126
  const markerPath = sessionId ? path.join(stateDir, `sent-${sessionId}.flag`) : null;
@@ -217,8 +217,8 @@ function safeGit(args) {
217
217
  req.on("timeout", () => { req.destroy(); done(); });
218
218
 
219
219
  // 在真正发包前 touch marker 文件——把"已尝试上报"持久化下来,
220
- // 让后续 5 分钟内的 UserPromptSubmit 跳过重复 POST。失败也照写,
221
- // 因为 5 分钟后 marker 会过期允许重试,不会永久卡住。
220
+ // 让后续 2 分钟内的 UserPromptSubmit 跳过重复 POST。失败也照写,
221
+ // 因为 2 分钟后 marker 会过期允许重试,不会永久卡住。
222
222
  if (markerPath) {
223
223
  try {
224
224
  fs.mkdirSync(stateDir, { recursive: true });
@@ -5,8 +5,8 @@
5
5
  "OTEL_LOGS_EXPORTER": "otlp",
6
6
  "OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
7
7
  "OTEL_EXPORTER_OTLP_ENDPOINT": "PLACEHOLDER_ENDPOINT",
8
- "OTEL_LOGS_EXPORT_INTERVAL": "300000",
9
- "OTEL_METRIC_EXPORT_INTERVAL": "600000",
8
+ "OTEL_LOGS_EXPORT_INTERVAL": "120000",
9
+ "OTEL_METRIC_EXPORT_INTERVAL": "300000",
10
10
  "OTEL_METRICS_INCLUDE_VERSION": "true",
11
11
  "OTEL_LOG_USER_PROMPTS": "0",
12
12
  "OTEL_LOG_TOOL_DETAILS": "1",