ai-otel-setup 1.0.8 → 1.0.10
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/LICENSE +17 -16
- package/cli.js +85 -63
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2026
|
|
3
|
+
Copyright (c) 2026 decent-yu
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
on machines authorized to send observability data to iFlyTek-internal
|
|
13
|
-
OTel collectors.
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/cli.js
CHANGED
|
@@ -28,43 +28,39 @@ const { execFileSync } = require("child_process");
|
|
|
28
28
|
|
|
29
29
|
const PKG_VERSION = require("./package.json").version;
|
|
30
30
|
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
// "C:\Program Files\..." 被切成 "C:\Program" + "Files\...",hook 进程起不来,
|
|
35
|
-
// exit code 1。用 8.3 短路径就根本没空格,cmd.exe 和 PowerShell 都能正确解析。
|
|
36
|
-
// 拿不到短路径(NTFS 卷禁用了 8.3 名)就回退原路径——比起死掉,至少 cmd.exe 还能跑。
|
|
37
|
-
function toWindowsShortPath(p) {
|
|
38
|
-
if (process.platform !== "win32" || !p) return p;
|
|
39
|
-
try {
|
|
40
|
-
const out = execFileSync(
|
|
41
|
-
"cmd.exe",
|
|
42
|
-
["/c", `for %A in ("${p}") do @echo %~sA`],
|
|
43
|
-
{ encoding: "utf8", windowsHide: true, timeout: 3000 },
|
|
44
|
-
);
|
|
45
|
-
const short = out.trim();
|
|
46
|
-
if (short && short.length > 0) return short;
|
|
47
|
-
} catch (_) {
|
|
48
|
-
/* 卷禁用了 8.3 / cmd.exe 不可用:吞掉,回退原路径 */
|
|
49
|
-
}
|
|
50
|
-
return p;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 安装时这台机器的 node 绝对路径,给 hook 命令做兜底(见 buildHookCommand)。
|
|
54
|
-
// Windows 上立刻转 8.3 短路径,规避 PowerShell 解析空格切 token 的问题。
|
|
55
|
-
const NODE_BIN = toWindowsShortPath(process.execPath);
|
|
31
|
+
// 安装时这台机器的 node 绝对路径,POSIX 上拿来构造 hook command 用。
|
|
32
|
+
// Windows 上不再写 node 绝对路径(见 buildHookCommand 注释)。
|
|
33
|
+
const NODE_BIN = process.execPath;
|
|
56
34
|
|
|
57
|
-
// 跨平台 hook
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
35
|
+
// 跨平台 hook 命令构造。
|
|
36
|
+
//
|
|
37
|
+
// **Windows 上的关键决策(v1.0.9 重构)**:不再写 node 绝对路径,也不再加引号,
|
|
38
|
+
// 让 shell 自己按空白 split + 用 PATH lookup node。原因:
|
|
39
|
+
// - 写绝对路径如 "C:\Program Files\nodejs\node.exe",PowerShell 5.1(cc/gemini
|
|
40
|
+
// 在 Windows 默认 shell)解析时会把外层引号脱掉,按空白把 "C:\Program" 切成
|
|
41
|
+
// 一个 token,"Files\nodejs\..." 切成另一个,hook 进程起不来,exit code 1。
|
|
42
|
+
// - 用 8.3 短路径 (C:\Progra~1\...) 在 NTFS 8dot3name 禁用的卷上失效,
|
|
43
|
+
// 更糟的是 cmd /c for 在某些 locale 下输出会带额外引号,被 installer
|
|
44
|
+
// 二次拼装成 ""\"C:\\\"C:\\Program Files\\nodejs\\node.exe\\\"\"" 这种
|
|
45
|
+
// 非法嵌套,比原 bug 更难修。
|
|
46
|
+
// - 改写 wrapper .cmd / .sh 让用户用别的 shell 接管 → 整体复杂度+30 行,
|
|
47
|
+
// 而且 wrapper 路径本身仍可能带空格,治标不治本。
|
|
48
|
+
//
|
|
49
|
+
// 务实最稳的解:让 shell 自己 PATH 找 node,命令字符串本身不含空格路径段。
|
|
50
|
+
// 假设 ~/.codex/ai-otel/ 这条路径不含空格(取决于用户名):
|
|
51
|
+
// - 99% 用户名是 ASCII 无空格 → 完美工作
|
|
52
|
+
// - 用户名带空格的极少数(如 "John Smith")→ 文档化处理(见
|
|
53
|
+
// docs/codex-windows-troubleshooting.md "用户名含空格" 一节)
|
|
54
|
+
//
|
|
55
|
+
// launch-hook.js 内部还会再做一次 PATH 上探 node、失败时 fallback baked
|
|
56
|
+
// execPath 的兜底,所以 PATH 上 node 临时失踪也能起来。
|
|
57
|
+
//
|
|
58
|
+
// POSIX:保持 quoted 三段格式,shell 行为统一,路径有空格也安全。
|
|
63
59
|
function buildHookCommand(launcherPath, scriptPath) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return `"${
|
|
60
|
+
if (process.platform === "win32") {
|
|
61
|
+
return `node ${launcherPath} ${scriptPath}`;
|
|
62
|
+
}
|
|
63
|
+
return `"${NODE_BIN}" "${launcherPath}" "${scriptPath}"`;
|
|
68
64
|
}
|
|
69
65
|
|
|
70
66
|
// 把 launcher 模板拷到 hook 同目录,返回 launcher 的绝对路径
|
|
@@ -191,25 +187,19 @@ function readGlobalGitUser() {
|
|
|
191
187
|
};
|
|
192
188
|
}
|
|
193
189
|
|
|
194
|
-
// ----------
|
|
190
|
+
// ---------- 装机上报:走同一条 OTel 管线 ----------
|
|
191
|
+
//
|
|
192
|
+
// 历史:原 POST 到 cc-view-server :8081/api/installer/report 直写 Doris。
|
|
193
|
+
// otel-prod 部署到公网、cc-view-web 留在内网后,installer 只能跟 OTel collector
|
|
194
|
+
// 说话;改成发一条 OTLP/HTTP log(event.name = installer_register),由 forwarder
|
|
195
|
+
// 走同一条管线落到 iData,cc-view-server 端从事件表 reduce 出装机记录。
|
|
196
|
+
// 这样 installer 命令依旧只暴露一个 url,跟数据上报完全同源。
|
|
195
197
|
//
|
|
196
|
-
// 安装完成时打一发 POST 到 cc-view-server,让运营侧能看到"谁/在哪台机/装了哪个版本"。
|
|
197
198
|
// 设计原则:
|
|
198
|
-
// - fire-and-forget:
|
|
199
|
-
// -
|
|
200
|
-
// - URL 本身是公司内网地址,自带隐式凭据,不带 SSO header
|
|
199
|
+
// - fire-and-forget:2.5s 超时、不重试、任何失败绝不让安装本身退出非 0
|
|
200
|
+
// - 复用 logsEndpointFromGrpc:4317 → 4318,path 自动补 /v1/logs
|
|
201
201
|
// - debug 模式下才打错误,正常运行不污染 stdout
|
|
202
202
|
|
|
203
|
-
function buildReportUrl(otelEndpoint) {
|
|
204
|
-
try {
|
|
205
|
-
const u = new URL(otelEndpoint);
|
|
206
|
-
// cc-view-server 跑在同机 :8081(与 collector 4317/4318 同主机)
|
|
207
|
-
return `http://${u.hostname}:8081/api/installer/report`;
|
|
208
|
-
} catch (_) {
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
203
|
function postJsonWithTimeout(targetUrl, payload, timeoutMs) {
|
|
214
204
|
return new Promise((resolve, reject) => {
|
|
215
205
|
let u;
|
|
@@ -254,24 +244,56 @@ async function reportInstall(otelEndpoint, gitUser, allResults, debug) {
|
|
|
254
244
|
if (debug) console.error("[ai-otel-setup] 跳过装机上报:无 git user.email");
|
|
255
245
|
return;
|
|
256
246
|
}
|
|
257
|
-
const
|
|
258
|
-
if (!
|
|
247
|
+
const logsUrl = logsEndpointFromGrpc(otelEndpoint);
|
|
248
|
+
if (!logsUrl) return;
|
|
259
249
|
const findOk = (tool) =>
|
|
260
250
|
allResults.find((r) => r.tool === tool)?.status === "installed";
|
|
251
|
+
|
|
252
|
+
// OTLP/HTTP log record。translator (lib/translate/installer_register.js) 按
|
|
253
|
+
// event.name = "installer_register" 路由到对应 eid,把 git.user.* / hostname
|
|
254
|
+
// 经 contextBlocksFromAttrs 落进 user 块,installer_* / os_* / node_version /
|
|
255
|
+
// *_cli_detected 走 phase1UdmapFields 白名单进 udmap。
|
|
256
|
+
const sessionId = `installer-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
257
|
+
const attrs = {
|
|
258
|
+
"tool_kind": "installer",
|
|
259
|
+
"event.name": "installer_register",
|
|
260
|
+
"event.timestamp": new Date().toISOString(),
|
|
261
|
+
"session.id": sessionId,
|
|
262
|
+
"git.user.email": gitUser.email,
|
|
263
|
+
"git.user.name": gitUser.name || "",
|
|
264
|
+
"hostname": os.hostname() || "",
|
|
265
|
+
"installer_version": PKG_VERSION,
|
|
266
|
+
"os_platform": os.platform(),
|
|
267
|
+
"os_arch": os.arch(),
|
|
268
|
+
"node_version": process.version,
|
|
269
|
+
"cc_cli_detected": findOk("claude") ? "1" : "0",
|
|
270
|
+
"codex_cli_detected": findOk("codex") ? "1" : "0",
|
|
271
|
+
};
|
|
261
272
|
const payload = {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
273
|
+
resourceLogs: [
|
|
274
|
+
{
|
|
275
|
+
resource: { attributes: [] },
|
|
276
|
+
scopeLogs: [
|
|
277
|
+
{
|
|
278
|
+
logRecords: [
|
|
279
|
+
{
|
|
280
|
+
timeUnixNano: `${Date.now()}000000`,
|
|
281
|
+
body: { stringValue: "installer_register" },
|
|
282
|
+
attributes: Object.entries(attrs).map(([k, v]) => ({
|
|
283
|
+
key: k,
|
|
284
|
+
value: { stringValue: String(v ?? "") },
|
|
285
|
+
})),
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
},
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
],
|
|
271
292
|
};
|
|
293
|
+
|
|
272
294
|
try {
|
|
273
|
-
await postJsonWithTimeout(
|
|
274
|
-
if (debug) console.error("[ai-otel-setup] 装机上报已发送");
|
|
295
|
+
await postJsonWithTimeout(logsUrl, payload, 2500);
|
|
296
|
+
if (debug) console.error("[ai-otel-setup] 装机上报已发送 →", logsUrl);
|
|
275
297
|
} catch (e) {
|
|
276
298
|
if (debug) {
|
|
277
299
|
console.error("[ai-otel-setup] 装机上报失败(不影响安装):", e.message || e);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-otel-setup",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.10",
|
|
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",
|