autotel-cli 0.11.2 → 0.12.0
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/dist/index.js +529 -68
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import * as fs from "node:fs";
|
|
4
|
-
import
|
|
4
|
+
import { readFileSync } from "node:fs";
|
|
5
|
+
import * as path$1 from "node:path";
|
|
6
|
+
import path from "node:path";
|
|
5
7
|
import { execSync } from "node:child_process";
|
|
6
|
-
import { randomBytes } from "node:crypto";
|
|
8
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
7
9
|
import { glob } from "glob";
|
|
8
10
|
import chalk from "chalk";
|
|
9
11
|
import { checkbox, confirm, select } from "@inquirer/prompts";
|
|
@@ -12,20 +14,23 @@ import * as semver from "semver";
|
|
|
12
14
|
import { Project, SyntaxKind } from "ts-morph";
|
|
13
15
|
import { fileURLToPath } from "node:url";
|
|
14
16
|
import { buildCollectorGuide, buildInstrumentationGuide, clearSemanticConventionCache, collectUsage, detectAnomalies, discoverLogFields, discoverServices, discoverTraceFields, findRootCause, getCollectorComponentReadme, getCollectorComponentSchema, getModelStats, getSemanticConventionNamespace, listCollectorComponents, listCollectorVersions, listModels, listSemanticConventionNamespaces, listToolUsage, pickErrorMessage, rankExpensiveTraces, rankSlowTraces, refreshCollectorCatalog, resolveCollectorVersion, scoreSpan, suggestCollectorConfig, suggestInstrumentationFixes, toLogSearchQuery, toMetricSearchQuery, toSpanSearchQuery, toTraceSearchQuery, validateCollectorComponentConfig, validateOtlpReceiverConfig } from "autotel-mcp";
|
|
17
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
|
+
import { appendFile, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
19
|
+
import { homedir } from "node:os";
|
|
15
20
|
//#region src/lib/fs.ts
|
|
16
21
|
/**
|
|
17
22
|
* Check if a path is within the allowed root directory
|
|
18
23
|
*/
|
|
19
24
|
function isPathWithinRoot(targetPath, rootPath) {
|
|
20
|
-
const resolvedTarget = path.resolve(targetPath);
|
|
21
|
-
const resolvedRoot = path.resolve(rootPath);
|
|
22
|
-
return resolvedTarget.startsWith(resolvedRoot + path.sep) || resolvedTarget === resolvedRoot;
|
|
25
|
+
const resolvedTarget = path$1.resolve(targetPath);
|
|
26
|
+
const resolvedRoot = path$1.resolve(rootPath);
|
|
27
|
+
return resolvedTarget.startsWith(resolvedRoot + path$1.sep) || resolvedTarget === resolvedRoot;
|
|
23
28
|
}
|
|
24
29
|
/**
|
|
25
30
|
* Ensure parent directory exists
|
|
26
31
|
*/
|
|
27
32
|
function ensureDir(filePath) {
|
|
28
|
-
const dir = path.dirname(filePath);
|
|
33
|
+
const dir = path$1.dirname(filePath);
|
|
29
34
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
30
35
|
}
|
|
31
36
|
/**
|
|
@@ -71,7 +76,7 @@ function createBackup(filePath) {
|
|
|
71
76
|
* Atomic write - write to temp file then rename
|
|
72
77
|
*/
|
|
73
78
|
function atomicWrite(filePath, content, options) {
|
|
74
|
-
const resolvedPath = path.resolve(filePath);
|
|
79
|
+
const resolvedPath = path$1.resolve(filePath);
|
|
75
80
|
if (!isPathWithinRoot(resolvedPath, options.root)) throw new Error(`Path traversal detected: ${filePath} resolves outside root ${options.root}`);
|
|
76
81
|
let backupPath = null;
|
|
77
82
|
if (options.backup && fileExists(resolvedPath)) backupPath = createBackup(resolvedPath);
|
|
@@ -104,17 +109,17 @@ function readJsonSafe(filePath) {
|
|
|
104
109
|
* Find a file by walking up the directory tree
|
|
105
110
|
*/
|
|
106
111
|
function findUpward(startDir, filename, stopAtRoot = true) {
|
|
107
|
-
let currentDir = path.resolve(startDir);
|
|
108
|
-
const root = path.parse(currentDir).root;
|
|
112
|
+
let currentDir = path$1.resolve(startDir);
|
|
113
|
+
const root = path$1.parse(currentDir).root;
|
|
109
114
|
while (currentDir !== root) {
|
|
110
|
-
const filePath = path.join(currentDir, filename);
|
|
115
|
+
const filePath = path$1.join(currentDir, filename);
|
|
111
116
|
if (fileExists(filePath)) return filePath;
|
|
112
|
-
const parentDir = path.dirname(currentDir);
|
|
117
|
+
const parentDir = path$1.dirname(currentDir);
|
|
113
118
|
if (parentDir === currentDir) break;
|
|
114
119
|
currentDir = parentDir;
|
|
115
120
|
if (stopAtRoot && currentDir === root) break;
|
|
116
121
|
}
|
|
117
|
-
const rootFilePath = path.join(root, filename);
|
|
122
|
+
const rootFilePath = path$1.join(root, filename);
|
|
118
123
|
if (fileExists(rootFilePath)) return rootFilePath;
|
|
119
124
|
return null;
|
|
120
125
|
}
|
|
@@ -123,14 +128,14 @@ function findUpward(startDir, filename, stopAtRoot = true) {
|
|
|
123
128
|
*/
|
|
124
129
|
function findAllUpward(startDir, filenames) {
|
|
125
130
|
const found = /* @__PURE__ */ new Map();
|
|
126
|
-
let currentDir = path.resolve(startDir);
|
|
127
|
-
const root = path.parse(currentDir).root;
|
|
131
|
+
let currentDir = path$1.resolve(startDir);
|
|
132
|
+
const root = path$1.parse(currentDir).root;
|
|
128
133
|
while (currentDir !== root) {
|
|
129
134
|
for (const filename of filenames) if (!found.has(filename)) {
|
|
130
|
-
const filePath = path.join(currentDir, filename);
|
|
135
|
+
const filePath = path$1.join(currentDir, filename);
|
|
131
136
|
if (fileExists(filePath)) found.set(filename, filePath);
|
|
132
137
|
}
|
|
133
|
-
const parentDir = path.dirname(currentDir);
|
|
138
|
+
const parentDir = path$1.dirname(currentDir);
|
|
134
139
|
if (parentDir === currentDir) break;
|
|
135
140
|
currentDir = parentDir;
|
|
136
141
|
}
|
|
@@ -179,7 +184,7 @@ function detectPackageManager(startDir) {
|
|
|
179
184
|
let closestDepth = -1;
|
|
180
185
|
let closestPM = "npm";
|
|
181
186
|
for (const [lockfileName, lockfilePath] of foundLockfiles) {
|
|
182
|
-
const depth = lockfilePath.split(path.sep).length;
|
|
187
|
+
const depth = lockfilePath.split(path$1.sep).length;
|
|
183
188
|
const pm = LOCKFILE_MAP[lockfileName];
|
|
184
189
|
if (pm === void 0) continue;
|
|
185
190
|
if (depth > closestDepth || depth === closestDepth && PM_PRIORITY.indexOf(pm) < PM_PRIORITY.indexOf(closestPM)) {
|
|
@@ -210,28 +215,28 @@ function getInstallCommand(pm, packages, options = {}) {
|
|
|
210
215
|
* Detect workspace root markers
|
|
211
216
|
*/
|
|
212
217
|
function detectWorkspaceRoot(startDir) {
|
|
213
|
-
let currentDir = path.resolve(startDir);
|
|
214
|
-
const root = path.parse(currentDir).root;
|
|
218
|
+
let currentDir = path$1.resolve(startDir);
|
|
219
|
+
const root = path$1.parse(currentDir).root;
|
|
215
220
|
while (currentDir !== root) {
|
|
216
|
-
if (fileExists(path.join(currentDir, "pnpm-workspace.yaml"))) return {
|
|
221
|
+
if (fileExists(path$1.join(currentDir, "pnpm-workspace.yaml"))) return {
|
|
217
222
|
workspaceRoot: currentDir,
|
|
218
223
|
workspaceType: "pnpm"
|
|
219
224
|
};
|
|
220
|
-
if (fileExists(path.join(currentDir, "lerna.json"))) return {
|
|
225
|
+
if (fileExists(path$1.join(currentDir, "lerna.json"))) return {
|
|
221
226
|
workspaceRoot: currentDir,
|
|
222
227
|
workspaceType: "lerna"
|
|
223
228
|
};
|
|
224
|
-
const pkgJsonContent = readFileSafe(path.join(currentDir, "package.json"));
|
|
229
|
+
const pkgJsonContent = readFileSafe(path$1.join(currentDir, "package.json"));
|
|
225
230
|
if (pkgJsonContent) try {
|
|
226
231
|
if (JSON.parse(pkgJsonContent).workspaces) {
|
|
227
|
-
const hasYarnLock = fileExists(path.join(currentDir, "yarn.lock"));
|
|
232
|
+
const hasYarnLock = fileExists(path$1.join(currentDir, "yarn.lock"));
|
|
228
233
|
return {
|
|
229
234
|
workspaceRoot: currentDir,
|
|
230
235
|
workspaceType: hasYarnLock ? "yarn" : "npm"
|
|
231
236
|
};
|
|
232
237
|
}
|
|
233
238
|
} catch {}
|
|
234
|
-
const parentDir = path.dirname(currentDir);
|
|
239
|
+
const parentDir = path$1.dirname(currentDir);
|
|
235
240
|
if (parentDir === currentDir) break;
|
|
236
241
|
currentDir = parentDir;
|
|
237
242
|
}
|
|
@@ -250,15 +255,15 @@ function findPackageJson(startDir) {
|
|
|
250
255
|
if (!packageJsonPath) return null;
|
|
251
256
|
return {
|
|
252
257
|
packageJsonPath,
|
|
253
|
-
packageRoot: path.dirname(packageJsonPath)
|
|
258
|
+
packageRoot: path$1.dirname(packageJsonPath)
|
|
254
259
|
};
|
|
255
260
|
}
|
|
256
261
|
/**
|
|
257
262
|
* Detect if project uses TypeScript
|
|
258
263
|
*/
|
|
259
264
|
function detectTypeScript(packageRoot) {
|
|
260
|
-
if (fileExists(path.join(packageRoot, "tsconfig.json"))) return true;
|
|
261
|
-
const pkgJson = readJsonSafe(path.join(packageRoot, "package.json"));
|
|
265
|
+
if (fileExists(path$1.join(packageRoot, "tsconfig.json"))) return true;
|
|
266
|
+
const pkgJson = readJsonSafe(path$1.join(packageRoot, "package.json"));
|
|
262
267
|
if (pkgJson) {
|
|
263
268
|
if ("typescript" in {
|
|
264
269
|
...pkgJson.dependencies,
|
|
@@ -289,7 +294,7 @@ function buildWorkspaceInfo(packageRoot, cwd) {
|
|
|
289
294
|
* Discover project context from a directory
|
|
290
295
|
*/
|
|
291
296
|
function discoverProject(cwd) {
|
|
292
|
-
const resolvedCwd = path.resolve(cwd);
|
|
297
|
+
const resolvedCwd = path$1.resolve(cwd);
|
|
293
298
|
const pkgResult = findPackageJson(resolvedCwd);
|
|
294
299
|
if (!pkgResult) return null;
|
|
295
300
|
const { packageJsonPath, packageRoot } = pkgResult;
|
|
@@ -325,15 +330,15 @@ function getEntrypointCandidates(packageRoot) {
|
|
|
325
330
|
"server.js",
|
|
326
331
|
"app.js",
|
|
327
332
|
"index.js"
|
|
328
|
-
].map((c) => path.join(packageRoot, c)).filter((p) => fileExists(p));
|
|
333
|
+
].map((c) => path$1.join(packageRoot, c)).filter((p) => fileExists(p));
|
|
329
334
|
}
|
|
330
335
|
/**
|
|
331
336
|
* Get instrumentation file path based on project conventions
|
|
332
337
|
*/
|
|
333
338
|
function getInstrumentationPath(packageRoot, hasTypeScript) {
|
|
334
|
-
const dir = dirExists(path.join(packageRoot, "src")) || fileExists(path.join(packageRoot, "src", "index.ts")) || fileExists(path.join(packageRoot, "src", "index.js")) ? path.join(packageRoot, "src") : packageRoot;
|
|
339
|
+
const dir = dirExists(path$1.join(packageRoot, "src")) || fileExists(path$1.join(packageRoot, "src", "index.ts")) || fileExists(path$1.join(packageRoot, "src", "index.js")) ? path$1.join(packageRoot, "src") : packageRoot;
|
|
335
340
|
const ext = hasTypeScript ? "mts" : "mjs";
|
|
336
|
-
return path.join(dir, `instrumentation.${ext}`);
|
|
341
|
+
return path$1.join(dir, `instrumentation.${ext}`);
|
|
337
342
|
}
|
|
338
343
|
//#endregion
|
|
339
344
|
//#region src/lib/code-builder.ts
|
|
@@ -559,7 +564,7 @@ const INSTRUMENTATION_LOCATIONS = [
|
|
|
559
564
|
*/
|
|
560
565
|
function findInstrumentationFile(packageRoot) {
|
|
561
566
|
for (const location of INSTRUMENTATION_LOCATIONS) {
|
|
562
|
-
const filePath = path.join(packageRoot, location);
|
|
567
|
+
const filePath = path$1.join(packageRoot, location);
|
|
563
568
|
if (fileExists(filePath)) {
|
|
564
569
|
const content = readFileSafe(filePath);
|
|
565
570
|
if (content === null) continue;
|
|
@@ -589,7 +594,7 @@ function findAutotelYaml(packageRoot) {
|
|
|
589
594
|
".autotelrc.yaml",
|
|
590
595
|
".autotelrc.yml"
|
|
591
596
|
]) {
|
|
592
|
-
const filePath = path.join(packageRoot, candidate);
|
|
597
|
+
const filePath = path$1.join(packageRoot, candidate);
|
|
593
598
|
if (fileExists(filePath)) return filePath;
|
|
594
599
|
}
|
|
595
600
|
return null;
|
|
@@ -700,7 +705,7 @@ async function findEnvFiles(packageRoot) {
|
|
|
700
705
|
* Check if env var is present in any env file
|
|
701
706
|
*/
|
|
702
707
|
async function checkEnvVarPresent(packageRoot, varName, specificFile) {
|
|
703
|
-
const files = specificFile ? [path.resolve(packageRoot, specificFile)] : await findEnvFiles(packageRoot);
|
|
708
|
+
const files = specificFile ? [path$1.resolve(packageRoot, specificFile)] : await findEnvFiles(packageRoot);
|
|
704
709
|
for (const file of files) {
|
|
705
710
|
const content = readFileSafe(file);
|
|
706
711
|
if (content === null) continue;
|
|
@@ -741,6 +746,10 @@ const DEP_TO_PRESET = {
|
|
|
741
746
|
"@tanstack/start": "tanstack",
|
|
742
747
|
"@tanstack/start-server": "tanstack",
|
|
743
748
|
"@tanstack/start-client": "tanstack",
|
|
749
|
+
"@nestjs/core": "nestjs",
|
|
750
|
+
"@sveltejs/kit": "sveltekit",
|
|
751
|
+
elysia: "elysia",
|
|
752
|
+
nuxt: "nuxt",
|
|
744
753
|
"posthog-node": "posthog",
|
|
745
754
|
"@posthog/node": "posthog",
|
|
746
755
|
mixpanel: "mixpanel",
|
|
@@ -752,7 +761,6 @@ const DEP_TO_PRESET = {
|
|
|
752
761
|
"drizzle-orm": "drizzle",
|
|
753
762
|
express: "auto-instr",
|
|
754
763
|
fastify: "auto-instr",
|
|
755
|
-
"@nestjs/core": "auto-instr",
|
|
756
764
|
next: "auto-instr",
|
|
757
765
|
pg: "auto-instr",
|
|
758
766
|
mysql: "auto-instr",
|
|
@@ -858,7 +866,7 @@ function collectEnvKeys(opts) {
|
|
|
858
866
|
const sources = [];
|
|
859
867
|
const tryRead = (name, requiresConsent) => {
|
|
860
868
|
if (requiresConsent && !opts.envConsent) return;
|
|
861
|
-
const content = readFileSafe(path.join(opts.packageRoot, name));
|
|
869
|
+
const content = readFileSafe(path$1.join(opts.packageRoot, name));
|
|
862
870
|
if (content === null) return;
|
|
863
871
|
sources.push(name);
|
|
864
872
|
for (const k of parseEnvKeys(content)) keys.add(k);
|
|
@@ -892,7 +900,7 @@ function detectBackend(opts) {
|
|
|
892
900
|
};
|
|
893
901
|
}
|
|
894
902
|
function detectPlatform(packageRoot) {
|
|
895
|
-
if (fileExists(path.join(packageRoot, "wrangler.toml")) || fileExists(path.join(packageRoot, "wrangler.jsonc")) || fileExists(path.join(packageRoot, "wrangler.json"))) return "cloudflare";
|
|
903
|
+
if (fileExists(path$1.join(packageRoot, "wrangler.toml")) || fileExists(path$1.join(packageRoot, "wrangler.jsonc")) || fileExists(path$1.join(packageRoot, "wrangler.json"))) return "cloudflare";
|
|
896
904
|
return null;
|
|
897
905
|
}
|
|
898
906
|
/**
|
|
@@ -901,7 +909,7 @@ function detectPlatform(packageRoot) {
|
|
|
901
909
|
function detectInProject(opts) {
|
|
902
910
|
const { project } = opts;
|
|
903
911
|
let rootPkg = null;
|
|
904
|
-
if (project.workspace.workspaceRoot !== null && project.workspace.workspaceRoot !== project.packageRoot) rootPkg = readJsonSafe(path.join(project.workspace.workspaceRoot, "package.json"));
|
|
912
|
+
if (project.workspace.workspaceRoot !== null && project.workspace.workspaceRoot !== project.packageRoot) rootPkg = readJsonSafe(path$1.join(project.workspace.workspaceRoot, "package.json"));
|
|
905
913
|
const deps = mergeDeps(project.packageJson, rootPkg);
|
|
906
914
|
const detectedPackages = [];
|
|
907
915
|
const presetSet = /* @__PURE__ */ new Set();
|
|
@@ -944,7 +952,7 @@ function detectInProject(opts) {
|
|
|
944
952
|
* to read it). .env.example is committed and doesn't trigger this.
|
|
945
953
|
*/
|
|
946
954
|
function envFilesRequireConsent(packageRoot) {
|
|
947
|
-
return fileExists(path.join(packageRoot, ".env")) || fileExists(path.join(packageRoot, ".env.local"));
|
|
955
|
+
return fileExists(path$1.join(packageRoot, ".env")) || fileExists(path$1.join(packageRoot, ".env.local"));
|
|
948
956
|
}
|
|
949
957
|
//#endregion
|
|
950
958
|
//#region src/presets/backends/datadog.ts
|
|
@@ -1802,6 +1810,120 @@ const tanstack = {
|
|
|
1802
1810
|
]
|
|
1803
1811
|
};
|
|
1804
1812
|
//#endregion
|
|
1813
|
+
//#region src/presets/plugins/nestjs.ts
|
|
1814
|
+
const nestjs = {
|
|
1815
|
+
name: "NestJS",
|
|
1816
|
+
slug: "nestjs",
|
|
1817
|
+
type: "plugin",
|
|
1818
|
+
description: "Request-scoped logging via AutotelInterceptor",
|
|
1819
|
+
packages: {
|
|
1820
|
+
required: ["autotel-adapters"],
|
|
1821
|
+
optional: [],
|
|
1822
|
+
devOnly: []
|
|
1823
|
+
},
|
|
1824
|
+
env: {
|
|
1825
|
+
required: [],
|
|
1826
|
+
optional: []
|
|
1827
|
+
},
|
|
1828
|
+
imports: [{
|
|
1829
|
+
source: "autotel-adapters/nestjs",
|
|
1830
|
+
specifiers: ["AutotelInterceptor"]
|
|
1831
|
+
}, {
|
|
1832
|
+
source: "@nestjs/core",
|
|
1833
|
+
specifiers: ["APP_INTERCEPTOR"]
|
|
1834
|
+
}],
|
|
1835
|
+
configBlock: {
|
|
1836
|
+
type: "plugin",
|
|
1837
|
+
code: `// Register globally in AppModule providers:
|
|
1838
|
+
// { provide: APP_INTERCEPTOR, useClass: AutotelInterceptor }`,
|
|
1839
|
+
section: "PLUGIN_INIT"
|
|
1840
|
+
},
|
|
1841
|
+
nextSteps: ["Register AutotelInterceptor via APP_INTERCEPTOR in AppModule", "Call useLogger() from controllers/services inside requests"]
|
|
1842
|
+
};
|
|
1843
|
+
//#endregion
|
|
1844
|
+
//#region src/presets/plugins/sveltekit.ts
|
|
1845
|
+
const sveltekit = {
|
|
1846
|
+
name: "SvelteKit",
|
|
1847
|
+
slug: "sveltekit",
|
|
1848
|
+
type: "plugin",
|
|
1849
|
+
description: "Server handle hook with request-scoped Autotel logger",
|
|
1850
|
+
packages: {
|
|
1851
|
+
required: ["autotel-adapters"],
|
|
1852
|
+
optional: [],
|
|
1853
|
+
devOnly: []
|
|
1854
|
+
},
|
|
1855
|
+
env: {
|
|
1856
|
+
required: [],
|
|
1857
|
+
optional: []
|
|
1858
|
+
},
|
|
1859
|
+
imports: [{
|
|
1860
|
+
source: "autotel-adapters/sveltekit",
|
|
1861
|
+
specifiers: ["autotelHandle"]
|
|
1862
|
+
}],
|
|
1863
|
+
configBlock: {
|
|
1864
|
+
type: "plugin",
|
|
1865
|
+
code: `// src/hooks.server.ts
|
|
1866
|
+
// export const handle = autotelHandle();`,
|
|
1867
|
+
section: "PLUGIN_INIT"
|
|
1868
|
+
},
|
|
1869
|
+
nextSteps: ["Export autotelHandle() from src/hooks.server.ts", "Use useLogger() in +server.ts routes and server loads"]
|
|
1870
|
+
};
|
|
1871
|
+
//#endregion
|
|
1872
|
+
//#region src/presets/plugins/elysia.ts
|
|
1873
|
+
const elysia = {
|
|
1874
|
+
name: "Elysia",
|
|
1875
|
+
slug: "elysia",
|
|
1876
|
+
type: "plugin",
|
|
1877
|
+
description: "Route handler wrapper with request-scoped logger",
|
|
1878
|
+
packages: {
|
|
1879
|
+
required: ["autotel-adapters"],
|
|
1880
|
+
optional: [],
|
|
1881
|
+
devOnly: []
|
|
1882
|
+
},
|
|
1883
|
+
env: {
|
|
1884
|
+
required: [],
|
|
1885
|
+
optional: []
|
|
1886
|
+
},
|
|
1887
|
+
imports: [{
|
|
1888
|
+
source: "autotel-adapters/elysia",
|
|
1889
|
+
specifiers: ["withAutotelHandler", "useLogger"]
|
|
1890
|
+
}],
|
|
1891
|
+
configBlock: {
|
|
1892
|
+
type: "plugin",
|
|
1893
|
+
code: `// app.get('/health', withAutotelHandler(async ({ path }) => {
|
|
1894
|
+
// useLogger().set({ route: path });
|
|
1895
|
+
// return { ok: true };
|
|
1896
|
+
// }));`,
|
|
1897
|
+
section: "PLUGIN_INIT"
|
|
1898
|
+
},
|
|
1899
|
+
nextSteps: ["Wrap route handlers with withAutotelHandler()", "Call useLogger() inside handlers for request-scoped context"]
|
|
1900
|
+
};
|
|
1901
|
+
//#endregion
|
|
1902
|
+
//#region src/presets/plugins/nuxt.ts
|
|
1903
|
+
const nuxt = {
|
|
1904
|
+
name: "Nuxt",
|
|
1905
|
+
slug: "nuxt",
|
|
1906
|
+
type: "plugin",
|
|
1907
|
+
description: "Nuxt module exposing Nitro Autotel adapters",
|
|
1908
|
+
packages: {
|
|
1909
|
+
required: ["autotel-nuxt", "autotel-adapters"],
|
|
1910
|
+
optional: [],
|
|
1911
|
+
devOnly: []
|
|
1912
|
+
},
|
|
1913
|
+
env: {
|
|
1914
|
+
required: [],
|
|
1915
|
+
optional: []
|
|
1916
|
+
},
|
|
1917
|
+
imports: [],
|
|
1918
|
+
configBlock: {
|
|
1919
|
+
type: "plugin",
|
|
1920
|
+
code: `// nuxt.config.ts
|
|
1921
|
+
// export default defineNuxtConfig({ modules: ['autotel-nuxt'] });`,
|
|
1922
|
+
section: "PLUGIN_INIT"
|
|
1923
|
+
},
|
|
1924
|
+
nextSteps: ["Add modules: ['autotel-nuxt'] to nuxt.config.ts", "Wrap server/api handlers with withAutotelEventHandler from autotel-nuxt/runtime/nitro"]
|
|
1925
|
+
};
|
|
1926
|
+
//#endregion
|
|
1805
1927
|
//#region src/presets/platforms/aws.ts
|
|
1806
1928
|
/**
|
|
1807
1929
|
* AWS Lambda platform preset
|
|
@@ -1926,7 +2048,11 @@ const plugins = /* @__PURE__ */ new Map([
|
|
|
1926
2048
|
["sentry", sentry],
|
|
1927
2049
|
["hono", hono],
|
|
1928
2050
|
["mcp", mcp],
|
|
1929
|
-
["tanstack", tanstack]
|
|
2051
|
+
["tanstack", tanstack],
|
|
2052
|
+
["nestjs", nestjs],
|
|
2053
|
+
["sveltekit", sveltekit],
|
|
2054
|
+
["elysia", elysia],
|
|
2055
|
+
["nuxt", nuxt]
|
|
1930
2056
|
]);
|
|
1931
2057
|
/**
|
|
1932
2058
|
* Platform presets registry
|
|
@@ -2061,12 +2187,12 @@ function buildPlanFromDetection(opts) {
|
|
|
2061
2187
|
}
|
|
2062
2188
|
const instrumentationPath = getInstrumentationPath(project.packageRoot, project.hasTypeScript);
|
|
2063
2189
|
const filesToWrite = [{
|
|
2064
|
-
path: path.relative(project.cwd, instrumentationPath),
|
|
2190
|
+
path: path$1.relative(project.cwd, instrumentationPath),
|
|
2065
2191
|
action: fileExists(instrumentationPath) ? "merge" : "create"
|
|
2066
2192
|
}];
|
|
2067
|
-
const envExamplePath = path.join(project.packageRoot, ".env.example");
|
|
2193
|
+
const envExamplePath = path$1.join(project.packageRoot, ".env.example");
|
|
2068
2194
|
if (envVars.length > 0 && !fileExists(envExamplePath)) filesToWrite.push({
|
|
2069
|
-
path: path.relative(project.cwd, envExamplePath),
|
|
2195
|
+
path: path$1.relative(project.cwd, envExamplePath),
|
|
2070
2196
|
action: "create"
|
|
2071
2197
|
});
|
|
2072
2198
|
const nextSteps = presets.flatMap((p) => p.nextSteps);
|
|
@@ -2425,10 +2551,10 @@ function printJson(data) {
|
|
|
2425
2551
|
}
|
|
2426
2552
|
}
|
|
2427
2553
|
function writeArtifactFile(filePath, content) {
|
|
2428
|
-
const resolved = path.resolve(filePath);
|
|
2554
|
+
const resolved = path$1.resolve(filePath);
|
|
2429
2555
|
if (outputRoot !== null) atomicWrite(resolved, content, { root: outputRoot });
|
|
2430
2556
|
else {
|
|
2431
|
-
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
2557
|
+
fs.mkdirSync(path$1.dirname(resolved), { recursive: true });
|
|
2432
2558
|
fs.writeFileSync(resolved, content, "utf8");
|
|
2433
2559
|
}
|
|
2434
2560
|
}
|
|
@@ -2789,7 +2915,7 @@ function applyPlan(args) {
|
|
|
2789
2915
|
if (codeFile.backendConfig === null) setBackendConfig(codeFile, "// Local/console mode - no backend configured");
|
|
2790
2916
|
const newContent = renderCodeFile(codeFile);
|
|
2791
2917
|
const instrumentationPath = resolveInstrumentationPath(project);
|
|
2792
|
-
const envExamplePath = path.join(project.packageRoot, ".env.example");
|
|
2918
|
+
const envExamplePath = path$1.join(project.packageRoot, ".env.example");
|
|
2793
2919
|
const existing = readFileSafe(instrumentationPath);
|
|
2794
2920
|
if (existing !== null) {
|
|
2795
2921
|
const parsed = parseInstrumentation(existing);
|
|
@@ -2809,24 +2935,24 @@ function applyPlan(args) {
|
|
|
2809
2935
|
].map((i) => i.source));
|
|
2810
2936
|
const addedAuto = diffAutoInstrumentations(parsed, codeFile.autoInstrumentations);
|
|
2811
2937
|
const contentChanged = existing !== newContent;
|
|
2812
|
-
if (addedImports.length === 0 && addedAuto.length === 0 && parsed.cliOwned && !contentChanged) result.wroteFiles.push(`${path.relative(project.cwd, instrumentationPath)} (no changes)`);
|
|
2938
|
+
if (addedImports.length === 0 && addedAuto.length === 0 && parsed.cliOwned && !contentChanged) result.wroteFiles.push(`${path$1.relative(project.cwd, instrumentationPath)} (no changes)`);
|
|
2813
2939
|
else {
|
|
2814
2940
|
atomicWrite(instrumentationPath, newContent, {
|
|
2815
2941
|
root: project.packageRoot,
|
|
2816
2942
|
backup: true
|
|
2817
2943
|
});
|
|
2818
|
-
result.wroteFiles.push(path.relative(project.cwd, instrumentationPath));
|
|
2944
|
+
result.wroteFiles.push(path$1.relative(project.cwd, instrumentationPath));
|
|
2819
2945
|
}
|
|
2820
2946
|
} else {
|
|
2821
2947
|
atomicWrite(instrumentationPath, newContent, { root: project.packageRoot });
|
|
2822
|
-
result.wroteFiles.push(path.relative(project.cwd, instrumentationPath));
|
|
2948
|
+
result.wroteFiles.push(path$1.relative(project.cwd, instrumentationPath));
|
|
2823
2949
|
}
|
|
2824
2950
|
const envVars = [];
|
|
2825
2951
|
for (const p of presets) envVars.push(...p.env.required, ...p.env.optional);
|
|
2826
2952
|
const envExampleContent = generateEnvExample(envVars);
|
|
2827
2953
|
if (envExampleContent.length > 0 && !fileExists(envExamplePath)) {
|
|
2828
2954
|
atomicWrite(envExamplePath, envExampleContent, { root: project.packageRoot });
|
|
2829
|
-
result.wroteFiles.push(path.relative(project.cwd, envExamplePath));
|
|
2955
|
+
result.wroteFiles.push(path$1.relative(project.cwd, envExamplePath));
|
|
2830
2956
|
}
|
|
2831
2957
|
const prod = plan.packagesToInstall.prod;
|
|
2832
2958
|
const dev = plan.packagesToInstall.dev;
|
|
@@ -2862,10 +2988,10 @@ function applyPlan(args) {
|
|
|
2862
2988
|
return result;
|
|
2863
2989
|
}
|
|
2864
2990
|
function resolveInstrumentationPath(project) {
|
|
2865
|
-
const srcDir = path.join(project.packageRoot, "src");
|
|
2866
|
-
const dir = fs.existsSync(srcDir) || fileExists(path.join(project.packageRoot, "src", "index.ts")) || fileExists(path.join(project.packageRoot, "src", "index.js")) ? srcDir : project.packageRoot;
|
|
2991
|
+
const srcDir = path$1.join(project.packageRoot, "src");
|
|
2992
|
+
const dir = fs.existsSync(srcDir) || fileExists(path$1.join(project.packageRoot, "src", "index.ts")) || fileExists(path$1.join(project.packageRoot, "src", "index.js")) ? srcDir : project.packageRoot;
|
|
2867
2993
|
const ext = project.hasTypeScript ? "mts" : "mjs";
|
|
2868
|
-
return path.join(dir, `instrumentation.${ext}`);
|
|
2994
|
+
return path$1.join(dir, `instrumentation.${ext}`);
|
|
2869
2995
|
}
|
|
2870
2996
|
function resolvePreset(slug) {
|
|
2871
2997
|
for (const type of [
|
|
@@ -3007,12 +3133,12 @@ function checkEsmHook(project) {
|
|
|
3007
3133
|
const result = checkRegisterImportOrder(content);
|
|
3008
3134
|
if (result.found && !result.isFirst) return {
|
|
3009
3135
|
status: "warn",
|
|
3010
|
-
message: `autotel/register import found but not first in ${path.basename(entrypoint)}:${result.lineNumber}`,
|
|
3136
|
+
message: `autotel/register import found but not first in ${path$1.basename(entrypoint)}:${result.lineNumber}`,
|
|
3011
3137
|
details: ["autotel/register must be the first import for instrumentation to work", "Move it to the top of the file, before any other imports"]
|
|
3012
3138
|
};
|
|
3013
3139
|
if (result.found && result.isFirst) return {
|
|
3014
3140
|
status: "ok",
|
|
3015
|
-
message: `autotel/register correctly imported first in ${path.basename(entrypoint)}`
|
|
3141
|
+
message: `autotel/register correctly imported first in ${path$1.basename(entrypoint)}`
|
|
3016
3142
|
};
|
|
3017
3143
|
}
|
|
3018
3144
|
return {
|
|
@@ -3059,12 +3185,12 @@ function extractAutoInstrumentations(content) {
|
|
|
3059
3185
|
*/
|
|
3060
3186
|
function findSourceFiles(packageRoot) {
|
|
3061
3187
|
const sourceFiles = [];
|
|
3062
|
-
const dirsToCheck = [packageRoot, path.join(packageRoot, "src")].filter((dir) => fs.existsSync(dir) && fs.statSync(dir).isDirectory());
|
|
3188
|
+
const dirsToCheck = [packageRoot, path$1.join(packageRoot, "src")].filter((dir) => fs.existsSync(dir) && fs.statSync(dir).isDirectory());
|
|
3063
3189
|
for (const dir of dirsToCheck) {
|
|
3064
3190
|
const files = fs.readdirSync(dir, { recursive: true });
|
|
3065
3191
|
for (const file of files) {
|
|
3066
3192
|
if (typeof file !== "string") continue;
|
|
3067
|
-
const filePath = path.join(dir, file);
|
|
3193
|
+
const filePath = path$1.join(dir, file);
|
|
3068
3194
|
try {
|
|
3069
3195
|
if (fs.statSync(filePath).isFile() && /\.(ts|js|mts|mjs|tsx|jsx)$/.test(file)) sourceFiles.push(filePath);
|
|
3070
3196
|
} catch {}
|
|
@@ -3746,8 +3872,8 @@ function addTraceImport(sourceFile) {
|
|
|
3746
3872
|
});
|
|
3747
3873
|
}
|
|
3748
3874
|
function expandNamePattern(pattern, name, filePath, cwd) {
|
|
3749
|
-
const file = path.basename(filePath, path.extname(filePath));
|
|
3750
|
-
const relPath = path.relative(cwd, filePath).replaceAll("\\", "/");
|
|
3875
|
+
const file = path$1.basename(filePath, path$1.extname(filePath));
|
|
3876
|
+
const relPath = path$1.relative(cwd, filePath).replaceAll("\\", "/");
|
|
3751
3877
|
return pattern.replaceAll("{name}", name).replaceAll("{file}", file).replaceAll("{path}", relPath);
|
|
3752
3878
|
}
|
|
3753
3879
|
function getSpanName(name, filePath, options) {
|
|
@@ -3979,19 +4105,19 @@ const GLOB_META = /[*?[\]]/;
|
|
|
3979
4105
|
*/
|
|
3980
4106
|
async function resolveCodemodFiles(pathArg, cwd) {
|
|
3981
4107
|
if (!GLOB_META.test(pathArg)) {
|
|
3982
|
-
const absolute = path.isAbsolute(pathArg) ? pathArg : path.resolve(cwd, pathArg);
|
|
4108
|
+
const absolute = path$1.isAbsolute(pathArg) ? pathArg : path$1.resolve(cwd, pathArg);
|
|
3983
4109
|
if (fileExists(absolute)) {
|
|
3984
|
-
const ext = path.extname(absolute);
|
|
4110
|
+
const ext = path$1.extname(absolute);
|
|
3985
4111
|
if (CODEMOD_EXTENSIONS.has(ext) && !absolute.endsWith(".d.ts")) return [absolute];
|
|
3986
4112
|
return [];
|
|
3987
4113
|
}
|
|
3988
4114
|
}
|
|
3989
|
-
return (await glob(path.isAbsolute(pathArg) ? pathArg : path.join(cwd, pathArg), {
|
|
4115
|
+
return (await glob(path$1.isAbsolute(pathArg) ? pathArg : path$1.join(cwd, pathArg), {
|
|
3990
4116
|
cwd,
|
|
3991
4117
|
absolute: true,
|
|
3992
4118
|
ignore: ["**/node_modules/**", "**/*.d.ts"]
|
|
3993
4119
|
})).filter((f) => {
|
|
3994
|
-
const ext = path.extname(f);
|
|
4120
|
+
const ext = path$1.extname(f);
|
|
3995
4121
|
return CODEMOD_EXTENSIONS.has(ext) && !f.endsWith(".d.ts");
|
|
3996
4122
|
});
|
|
3997
4123
|
}
|
|
@@ -4028,7 +4154,7 @@ async function runCodemodTrace(options) {
|
|
|
4028
4154
|
process.exitCode = 1;
|
|
4029
4155
|
continue;
|
|
4030
4156
|
}
|
|
4031
|
-
const relativePath = path.relative(cwd, filePath);
|
|
4157
|
+
const relativePath = path$1.relative(cwd, filePath);
|
|
4032
4158
|
if (result.changed) {
|
|
4033
4159
|
totalWrapped += result.wrappedCount;
|
|
4034
4160
|
totalChanged += 1;
|
|
@@ -4633,6 +4759,38 @@ const INVESTIGATE_COMMANDS = [
|
|
|
4633
4759
|
})
|
|
4634
4760
|
];
|
|
4635
4761
|
COMMANDS.push(...INVESTIGATE_COMMANDS);
|
|
4762
|
+
COMMANDS.push(...[
|
|
4763
|
+
{
|
|
4764
|
+
name: "telemetry",
|
|
4765
|
+
description: "Manage opt-in CLI usage telemetry (parent)",
|
|
4766
|
+
mutating: false,
|
|
4767
|
+
flags: []
|
|
4768
|
+
},
|
|
4769
|
+
{
|
|
4770
|
+
name: "telemetry status",
|
|
4771
|
+
description: "Show telemetry consent status",
|
|
4772
|
+
mutating: false,
|
|
4773
|
+
flags: []
|
|
4774
|
+
},
|
|
4775
|
+
{
|
|
4776
|
+
name: "telemetry enable",
|
|
4777
|
+
description: "Enable telemetry for this tool",
|
|
4778
|
+
mutating: true,
|
|
4779
|
+
flags: []
|
|
4780
|
+
},
|
|
4781
|
+
{
|
|
4782
|
+
name: "telemetry disable",
|
|
4783
|
+
description: "Disable telemetry and purge undelivered events",
|
|
4784
|
+
mutating: true,
|
|
4785
|
+
flags: []
|
|
4786
|
+
},
|
|
4787
|
+
{
|
|
4788
|
+
name: "telemetry disclosure",
|
|
4789
|
+
description: "Print telemetry disclosure markdown",
|
|
4790
|
+
mutating: false,
|
|
4791
|
+
flags: []
|
|
4792
|
+
}
|
|
4793
|
+
]);
|
|
4636
4794
|
function getCommand(name) {
|
|
4637
4795
|
return COMMANDS.find((c) => c.name === name);
|
|
4638
4796
|
}
|
|
@@ -4714,14 +4872,14 @@ function configure(opts) {
|
|
|
4714
4872
|
}
|
|
4715
4873
|
function readSelfVersion() {
|
|
4716
4874
|
try {
|
|
4717
|
-
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
4875
|
+
let dir = path$1.dirname(fileURLToPath(import.meta.url));
|
|
4718
4876
|
for (let i = 0; i < 5; i++) {
|
|
4719
|
-
const candidate = path.join(dir, "package.json");
|
|
4877
|
+
const candidate = path$1.join(dir, "package.json");
|
|
4720
4878
|
if (fs.existsSync(candidate)) {
|
|
4721
4879
|
const pkg = JSON.parse(fs.readFileSync(candidate, "utf8"));
|
|
4722
4880
|
if (pkg.name === "autotel-cli") return String(pkg.version ?? "0.0.0");
|
|
4723
4881
|
}
|
|
4724
|
-
dir = path.dirname(dir);
|
|
4882
|
+
dir = path$1.dirname(dir);
|
|
4725
4883
|
}
|
|
4726
4884
|
} catch {}
|
|
4727
4885
|
return "0.0.0";
|
|
@@ -6187,6 +6345,291 @@ function registerSecurityCommands(program) {
|
|
|
6187
6345
|
program.addCommand(securityCmd);
|
|
6188
6346
|
}
|
|
6189
6347
|
//#endregion
|
|
6348
|
+
//#region ../autotel-telemetry/dist/index.js
|
|
6349
|
+
function getTelemetryDir(toolName) {
|
|
6350
|
+
const safe = toolName.replaceAll(/[^a-zA-Z0-9._-]/g, "-");
|
|
6351
|
+
return path.join(homedir(), ".autotel", "telemetry", safe);
|
|
6352
|
+
}
|
|
6353
|
+
var TelemetryOutbox = class {
|
|
6354
|
+
dir;
|
|
6355
|
+
filePath;
|
|
6356
|
+
constructor(options) {
|
|
6357
|
+
this.dir = getTelemetryDir(options.toolName);
|
|
6358
|
+
this.filePath = path.join(this.dir, "outbox.ndjson");
|
|
6359
|
+
}
|
|
6360
|
+
async append(event) {
|
|
6361
|
+
await mkdir(this.dir, { recursive: true });
|
|
6362
|
+
await appendFile(this.filePath, `${JSON.stringify(event)}\n`, "utf8");
|
|
6363
|
+
}
|
|
6364
|
+
async readAll() {
|
|
6365
|
+
try {
|
|
6366
|
+
return (await readFile(this.filePath, "utf8")).split("\n").filter(Boolean).map((line) => JSON.parse(line));
|
|
6367
|
+
} catch {
|
|
6368
|
+
return [];
|
|
6369
|
+
}
|
|
6370
|
+
}
|
|
6371
|
+
async purge() {
|
|
6372
|
+
try {
|
|
6373
|
+
await unlink(this.filePath);
|
|
6374
|
+
} catch {}
|
|
6375
|
+
}
|
|
6376
|
+
};
|
|
6377
|
+
function resolveConsent(toolName) {
|
|
6378
|
+
if (process.env.DO_NOT_TRACK === "1") return false;
|
|
6379
|
+
if (process.env.AUTOTEL_TELEMETRY === "0") return false;
|
|
6380
|
+
if (process.env.AUTOTEL_TELEMETRY === "1") return true;
|
|
6381
|
+
const pref = readPreferenceSync(toolName);
|
|
6382
|
+
if (pref === "disabled") return false;
|
|
6383
|
+
if (pref === "enabled") return true;
|
|
6384
|
+
return false;
|
|
6385
|
+
}
|
|
6386
|
+
function readPreferenceSync(toolName) {
|
|
6387
|
+
try {
|
|
6388
|
+
const raw = readFileSync(path.join(getTelemetryDir(toolName), "preference.json"), "utf8");
|
|
6389
|
+
return JSON.parse(raw).preference ?? "unset";
|
|
6390
|
+
} catch {
|
|
6391
|
+
return "unset";
|
|
6392
|
+
}
|
|
6393
|
+
}
|
|
6394
|
+
async function writePreference(toolName, preference) {
|
|
6395
|
+
const dir = getTelemetryDir(toolName);
|
|
6396
|
+
await mkdir(dir, { recursive: true });
|
|
6397
|
+
await writeFile(path.join(dir, "preference.json"), JSON.stringify({ preference }), "utf8");
|
|
6398
|
+
}
|
|
6399
|
+
async function purgeOutbox(toolName) {
|
|
6400
|
+
await new TelemetryOutbox({ toolName }).purge();
|
|
6401
|
+
}
|
|
6402
|
+
function createNoopDrain() {
|
|
6403
|
+
return async () => {};
|
|
6404
|
+
}
|
|
6405
|
+
function createDebugDrain() {
|
|
6406
|
+
return async (events) => {
|
|
6407
|
+
if (process.env.AUTOTEL_TELEMETRY_DEBUG === "1") for (const event of events) console.error("[autotel-telemetry]", JSON.stringify(event));
|
|
6408
|
+
};
|
|
6409
|
+
}
|
|
6410
|
+
function createHttpDrain(endpoint) {
|
|
6411
|
+
return async (events) => {
|
|
6412
|
+
if (events.length === 0) return;
|
|
6413
|
+
const response = await fetch(endpoint, {
|
|
6414
|
+
method: "POST",
|
|
6415
|
+
headers: { "content-type": "application/json" },
|
|
6416
|
+
body: JSON.stringify({ events })
|
|
6417
|
+
});
|
|
6418
|
+
if (!response.ok) throw new Error(`Telemetry delivery failed with HTTP ${response.status}`);
|
|
6419
|
+
};
|
|
6420
|
+
}
|
|
6421
|
+
function composeDrains(...drains) {
|
|
6422
|
+
return async (events) => {
|
|
6423
|
+
await Promise.all(drains.map((drain) => drain(events)));
|
|
6424
|
+
};
|
|
6425
|
+
}
|
|
6426
|
+
function resolveEndpoint(explicit) {
|
|
6427
|
+
return explicit ?? process.env.AUTOTEL_TELEMETRY_ENDPOINT;
|
|
6428
|
+
}
|
|
6429
|
+
const SENSITIVE_PATTERN = /(?:token|secret|password|key|auth|credential|api[_-]?key|private)/i;
|
|
6430
|
+
function sanitizeFlags(argv, allowlistedStringFlags = []) {
|
|
6431
|
+
const out = {};
|
|
6432
|
+
for (let i = 0; i < argv.length; i++) {
|
|
6433
|
+
const arg = argv[i];
|
|
6434
|
+
if (arg === void 0 || !arg.startsWith("-")) continue;
|
|
6435
|
+
const key = arg.replace(/^-+/, "").split("=")[0] ?? arg;
|
|
6436
|
+
if (SENSITIVE_PATTERN.test(key)) {
|
|
6437
|
+
out[key] = { present: true };
|
|
6438
|
+
continue;
|
|
6439
|
+
}
|
|
6440
|
+
if (arg.includes("=")) {
|
|
6441
|
+
const [, rawValue] = arg.split("=");
|
|
6442
|
+
if (rawValue === void 0) continue;
|
|
6443
|
+
if (/^\d+$/.test(rawValue)) out[key] = Number(rawValue);
|
|
6444
|
+
else if (allowlistedStringFlags.includes(key)) out[key] = rawValue;
|
|
6445
|
+
else out[key] = { present: true };
|
|
6446
|
+
continue;
|
|
6447
|
+
}
|
|
6448
|
+
const next = argv[i + 1];
|
|
6449
|
+
if (next && !next.startsWith("-")) if (/^\d+$/.test(next)) {
|
|
6450
|
+
out[key] = Number(next);
|
|
6451
|
+
i++;
|
|
6452
|
+
} else if (allowlistedStringFlags.includes(key)) {
|
|
6453
|
+
out[key] = next;
|
|
6454
|
+
i++;
|
|
6455
|
+
} else {
|
|
6456
|
+
out[key] = { present: true };
|
|
6457
|
+
i++;
|
|
6458
|
+
}
|
|
6459
|
+
else out[key] = true;
|
|
6460
|
+
}
|
|
6461
|
+
return out;
|
|
6462
|
+
}
|
|
6463
|
+
function sanitizeCustom(fields) {
|
|
6464
|
+
const out = {};
|
|
6465
|
+
for (const [key, value] of Object.entries(fields)) if (typeof value === "boolean" || typeof value === "number") out[key] = value;
|
|
6466
|
+
else if (value !== null && value !== void 0) out[key] = { present: true };
|
|
6467
|
+
return out;
|
|
6468
|
+
}
|
|
6469
|
+
const runStorage = new AsyncLocalStorage();
|
|
6470
|
+
let activeHandle = null;
|
|
6471
|
+
var InternalTelemetry = class {
|
|
6472
|
+
enabled;
|
|
6473
|
+
outbox;
|
|
6474
|
+
drain;
|
|
6475
|
+
options;
|
|
6476
|
+
constructor(options, enabled) {
|
|
6477
|
+
this.options = options;
|
|
6478
|
+
this.enabled = enabled;
|
|
6479
|
+
this.outbox = new TelemetryOutbox({ toolName: options.name });
|
|
6480
|
+
const endpoint = resolveEndpoint(options.endpoint);
|
|
6481
|
+
const drains = [createDebugDrain(), endpoint ? createHttpDrain(endpoint) : createNoopDrain()];
|
|
6482
|
+
this.drain = composeDrains(...drains);
|
|
6483
|
+
}
|
|
6484
|
+
set(fields) {
|
|
6485
|
+
const ctx = runStorage.getStore();
|
|
6486
|
+
if (!ctx || !this.enabled) return;
|
|
6487
|
+
ctx.custom = sanitizeCustom({
|
|
6488
|
+
...ctx.custom,
|
|
6489
|
+
...fields
|
|
6490
|
+
});
|
|
6491
|
+
}
|
|
6492
|
+
async finish(outcome) {
|
|
6493
|
+
if (!this.enabled || !resolveConsent(this.options.name)) return;
|
|
6494
|
+
const ctx = runStorage.getStore();
|
|
6495
|
+
if (!ctx) return;
|
|
6496
|
+
const event = {
|
|
6497
|
+
tool: this.options.name,
|
|
6498
|
+
version: this.options.version,
|
|
6499
|
+
command: ctx.command,
|
|
6500
|
+
outcome,
|
|
6501
|
+
durationMs: Date.now() - ctx.startedAt,
|
|
6502
|
+
flags: ctx.flags,
|
|
6503
|
+
custom: Object.keys(ctx.custom).length > 0 ? ctx.custom : void 0,
|
|
6504
|
+
ci: Boolean(process.env.CI),
|
|
6505
|
+
machineId: hashMachineId()
|
|
6506
|
+
};
|
|
6507
|
+
try {
|
|
6508
|
+
await this.outbox.append(event);
|
|
6509
|
+
const pending = await this.outbox.readAll();
|
|
6510
|
+
await this.drain(pending);
|
|
6511
|
+
await this.outbox.purge();
|
|
6512
|
+
} catch {}
|
|
6513
|
+
}
|
|
6514
|
+
async flush() {
|
|
6515
|
+
if (!this.enabled || !resolveConsent(this.options.name)) return;
|
|
6516
|
+
await Promise.race([(async () => {
|
|
6517
|
+
const pending = await this.outbox.readAll();
|
|
6518
|
+
await this.drain(pending);
|
|
6519
|
+
await this.outbox.purge();
|
|
6520
|
+
})(), new Promise((resolve) => setTimeout(resolve, 500))]);
|
|
6521
|
+
}
|
|
6522
|
+
};
|
|
6523
|
+
function hashMachineId() {
|
|
6524
|
+
const seed = `${process.platform}:${process.arch}:${process.env.USER ?? "unknown"}`;
|
|
6525
|
+
return createHash("sha256").update(seed).digest("hex").slice(0, 16);
|
|
6526
|
+
}
|
|
6527
|
+
function createTelemetry(options) {
|
|
6528
|
+
activeHandle = new InternalTelemetry(options, resolveConsent(options.name));
|
|
6529
|
+
return activeHandle;
|
|
6530
|
+
}
|
|
6531
|
+
async function runWithTelemetry(options, command, argv, fn) {
|
|
6532
|
+
const handle = createTelemetry(options);
|
|
6533
|
+
const ctx = {
|
|
6534
|
+
command,
|
|
6535
|
+
startedAt: Date.now(),
|
|
6536
|
+
custom: {},
|
|
6537
|
+
flags: sanitizeFlags(argv, options.allowlistedStringFlags)
|
|
6538
|
+
};
|
|
6539
|
+
return runStorage.run(ctx, async () => {
|
|
6540
|
+
let outcome = "success";
|
|
6541
|
+
try {
|
|
6542
|
+
return await fn();
|
|
6543
|
+
} catch (error) {
|
|
6544
|
+
outcome = "failure";
|
|
6545
|
+
throw error;
|
|
6546
|
+
} finally {
|
|
6547
|
+
if (handle.enabled) await handle.finish(outcome);
|
|
6548
|
+
}
|
|
6549
|
+
});
|
|
6550
|
+
}
|
|
6551
|
+
function withCommanderTelemetry(program, options) {
|
|
6552
|
+
const originalParse = program.parseAsync.bind(program);
|
|
6553
|
+
program.parseAsync = async (argv) => {
|
|
6554
|
+
const command = argv[2] ?? "help";
|
|
6555
|
+
return runWithTelemetry({
|
|
6556
|
+
name: options?.name ?? program.name(),
|
|
6557
|
+
version: options?.version ?? program.version(),
|
|
6558
|
+
...options
|
|
6559
|
+
}, command, argv.slice(3), () => originalParse(argv));
|
|
6560
|
+
};
|
|
6561
|
+
return program;
|
|
6562
|
+
}
|
|
6563
|
+
function getTelemetryStatus(toolName) {
|
|
6564
|
+
return {
|
|
6565
|
+
enabled: resolveConsent(toolName),
|
|
6566
|
+
preference: readPreferenceSync(toolName)
|
|
6567
|
+
};
|
|
6568
|
+
}
|
|
6569
|
+
async function enableTelemetry(toolName) {
|
|
6570
|
+
await writePreference(toolName, "enabled");
|
|
6571
|
+
}
|
|
6572
|
+
async function disableTelemetry(toolName) {
|
|
6573
|
+
await writePreference(toolName, "disabled");
|
|
6574
|
+
await purgeOutbox(toolName);
|
|
6575
|
+
}
|
|
6576
|
+
function generateDisclosure(options) {
|
|
6577
|
+
const json = {
|
|
6578
|
+
tool: options.name,
|
|
6579
|
+
version: options.version,
|
|
6580
|
+
collected: [
|
|
6581
|
+
"command",
|
|
6582
|
+
"outcome",
|
|
6583
|
+
"durationMs",
|
|
6584
|
+
"sanitized flags",
|
|
6585
|
+
"optional custom counters"
|
|
6586
|
+
],
|
|
6587
|
+
notCollected: [
|
|
6588
|
+
"raw argv strings",
|
|
6589
|
+
"paths",
|
|
6590
|
+
"tokens",
|
|
6591
|
+
"secrets"
|
|
6592
|
+
],
|
|
6593
|
+
optOut: [
|
|
6594
|
+
"DO_NOT_TRACK=1",
|
|
6595
|
+
"AUTOTEL_TELEMETRY=0",
|
|
6596
|
+
"autotel telemetry disable"
|
|
6597
|
+
]
|
|
6598
|
+
};
|
|
6599
|
+
return {
|
|
6600
|
+
markdown: `# ${options.name} telemetry disclosure
|
|
6601
|
+
|
|
6602
|
+
Collects one structured event per command: command name, outcome, duration, sanitized flags, and optional counters via \`telemetry.set()\`.
|
|
6603
|
+
|
|
6604
|
+
Does not collect raw paths, tokens, or secret-shaped flag values.
|
|
6605
|
+
|
|
6606
|
+
Opt out with \`DO_NOT_TRACK=1\`, \`AUTOTEL_TELEMETRY=0\`, or \`autotel telemetry disable\`.
|
|
6607
|
+
`,
|
|
6608
|
+
json
|
|
6609
|
+
};
|
|
6610
|
+
}
|
|
6611
|
+
//#endregion
|
|
6612
|
+
//#region src/commands/telemetry.ts
|
|
6613
|
+
async function runTelemetryStatus(toolName) {
|
|
6614
|
+
const status = getTelemetryStatus(toolName);
|
|
6615
|
+
console.log(JSON.stringify(status, null, 2));
|
|
6616
|
+
}
|
|
6617
|
+
async function runTelemetryEnable(toolName) {
|
|
6618
|
+
await enableTelemetry(toolName);
|
|
6619
|
+
console.log(`Telemetry enabled for ${toolName}`);
|
|
6620
|
+
}
|
|
6621
|
+
async function runTelemetryDisable(toolName) {
|
|
6622
|
+
await disableTelemetry(toolName);
|
|
6623
|
+
console.log(`Telemetry disabled for ${toolName}`);
|
|
6624
|
+
}
|
|
6625
|
+
function runTelemetryDisclosure(toolName, version) {
|
|
6626
|
+
const disclosure = generateDisclosure({
|
|
6627
|
+
name: toolName,
|
|
6628
|
+
version
|
|
6629
|
+
});
|
|
6630
|
+
console.log(disclosure.markdown);
|
|
6631
|
+
}
|
|
6632
|
+
//#endregion
|
|
6190
6633
|
//#region src/cli.ts
|
|
6191
6634
|
/**
|
|
6192
6635
|
* Create the CLI program
|
|
@@ -6325,6 +6768,20 @@ function createProgram() {
|
|
|
6325
6768
|
registerScoreCommands(program);
|
|
6326
6769
|
registerCollectorCommands(program);
|
|
6327
6770
|
registerSecurityCommands(program);
|
|
6771
|
+
const telemetryCmd = new Command("telemetry").description("Manage opt-in CLI usage telemetry");
|
|
6772
|
+
telemetryCmd.command("status").description("Show telemetry consent status").action(async () => {
|
|
6773
|
+
await runTelemetryStatus("autotel");
|
|
6774
|
+
});
|
|
6775
|
+
telemetryCmd.command("enable").description("Enable telemetry for this tool").action(async () => {
|
|
6776
|
+
await runTelemetryEnable("autotel");
|
|
6777
|
+
});
|
|
6778
|
+
telemetryCmd.command("disable").description("Disable telemetry and purge undelivered events").action(async () => {
|
|
6779
|
+
await runTelemetryDisable("autotel");
|
|
6780
|
+
});
|
|
6781
|
+
telemetryCmd.command("disclosure").description("Print telemetry disclosure markdown").action(() => {
|
|
6782
|
+
runTelemetryDisclosure("autotel", program.version());
|
|
6783
|
+
});
|
|
6784
|
+
program.addCommand(telemetryCmd);
|
|
6328
6785
|
return program;
|
|
6329
6786
|
}
|
|
6330
6787
|
/**
|
|
@@ -6337,7 +6794,11 @@ function createProgram() {
|
|
|
6337
6794
|
* which the top-level handler in `index.ts` converts to an envelope.
|
|
6338
6795
|
*/
|
|
6339
6796
|
async function run() {
|
|
6340
|
-
const
|
|
6797
|
+
const base = createProgram();
|
|
6798
|
+
const program = withCommanderTelemetry(base, {
|
|
6799
|
+
name: "autotel",
|
|
6800
|
+
version: base.version()
|
|
6801
|
+
});
|
|
6341
6802
|
program.exitOverride();
|
|
6342
6803
|
const argvJoined = process.argv.slice(2).join(" ");
|
|
6343
6804
|
const isJsonOnly = process.argv.includes("--json") || /^(schema|commands|examples|version|health|capabilities|discover|query|trace|diagnose|topology|correlate|llm|semconv|score|collector)\b/.test(argvJoined);
|