autotel-cli 0.8.7 → 0.8.9
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 +72 -6
- package/dist/index.js +2898 -387
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { Command } from "commander";
|
|
4
|
+
import { Command as Command11 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/commands/init.ts
|
|
7
|
-
import * as
|
|
7
|
+
import * as fs4 from "fs";
|
|
8
|
+
import * as path9 from "path";
|
|
8
9
|
import { execSync } from "child_process";
|
|
9
10
|
|
|
10
11
|
// src/lib/project.ts
|
|
@@ -330,11 +331,25 @@ function createCodeFile() {
|
|
|
330
331
|
backendImports: [],
|
|
331
332
|
pluginImports: [],
|
|
332
333
|
subscriberImports: [],
|
|
334
|
+
loggerImports: [],
|
|
335
|
+
loggerSetup: null,
|
|
336
|
+
loggerExpr: null,
|
|
337
|
+
autoInstrumentations: [],
|
|
333
338
|
backendConfig: null,
|
|
334
339
|
subscribersConfig: [],
|
|
335
340
|
pluginInit: []
|
|
336
341
|
};
|
|
337
342
|
}
|
|
343
|
+
function setPinoLogger(file) {
|
|
344
|
+
file.loggerImports.push({ source: "pino", default: "pino" });
|
|
345
|
+
file.loggerSetup = `const logger = pino({ name: 'app', level: process.env.LOG_LEVEL ?? 'info' });`;
|
|
346
|
+
file.loggerExpr = "logger";
|
|
347
|
+
}
|
|
348
|
+
function addAutoInstrumentationLogger(file, name) {
|
|
349
|
+
if (!file.autoInstrumentations.includes(name)) {
|
|
350
|
+
file.autoInstrumentations.push(name);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
338
353
|
function addImport(file, imp, section) {
|
|
339
354
|
switch (section) {
|
|
340
355
|
case "backend":
|
|
@@ -362,7 +377,7 @@ function addPluginInit(file, init) {
|
|
|
362
377
|
function sortImports(imports) {
|
|
363
378
|
const sideEffect = [];
|
|
364
379
|
const external = [];
|
|
365
|
-
const
|
|
380
|
+
const relative6 = [];
|
|
366
381
|
for (const imp of imports) {
|
|
367
382
|
if (imp.sideEffect) {
|
|
368
383
|
if (imp.source === "autotel/register") {
|
|
@@ -371,15 +386,15 @@ function sortImports(imports) {
|
|
|
371
386
|
sideEffect.push(imp);
|
|
372
387
|
}
|
|
373
388
|
} else if (imp.source.startsWith(".") || imp.source.startsWith("/")) {
|
|
374
|
-
|
|
389
|
+
relative6.push(imp);
|
|
375
390
|
} else {
|
|
376
391
|
external.push(imp);
|
|
377
392
|
}
|
|
378
393
|
}
|
|
379
394
|
const sortBySource = (a, b) => a.source.localeCompare(b.source);
|
|
380
395
|
external.sort(sortBySource);
|
|
381
|
-
|
|
382
|
-
return [...sideEffect, ...external, ...
|
|
396
|
+
relative6.sort(sortBySource);
|
|
397
|
+
return [...sideEffect, ...external, ...relative6];
|
|
383
398
|
}
|
|
384
399
|
function renderImport(imp) {
|
|
385
400
|
if (imp.sideEffect) {
|
|
@@ -427,7 +442,23 @@ function renderCodeFile(file) {
|
|
|
427
442
|
lines.push(renderImports(file.subscriberImports));
|
|
428
443
|
lines.push("");
|
|
429
444
|
}
|
|
445
|
+
if (file.loggerImports.length > 0) {
|
|
446
|
+
lines.push(SECTION_MARKER("LOGGER"));
|
|
447
|
+
lines.push(renderImports(file.loggerImports));
|
|
448
|
+
lines.push("");
|
|
449
|
+
}
|
|
450
|
+
if (file.loggerSetup !== null) {
|
|
451
|
+
lines.push(file.loggerSetup);
|
|
452
|
+
lines.push("");
|
|
453
|
+
}
|
|
430
454
|
lines.push("init({");
|
|
455
|
+
if (file.loggerExpr !== null) {
|
|
456
|
+
lines.push(` logger: ${file.loggerExpr},`);
|
|
457
|
+
}
|
|
458
|
+
if (file.autoInstrumentations.length > 0) {
|
|
459
|
+
const list = file.autoInstrumentations.map((n) => `'${n}'`).join(", ");
|
|
460
|
+
lines.push(` autoInstrumentations: [${list}],`);
|
|
461
|
+
}
|
|
431
462
|
if (file.backendConfig) {
|
|
432
463
|
lines.push(" " + SECTION_MARKER("BACKEND_CONFIG"));
|
|
433
464
|
lines.push(` ${file.backendConfig}`);
|
|
@@ -638,60 +669,192 @@ async function checkEnvVarsPresent(packageRoot, varNames, specificFile) {
|
|
|
638
669
|
return results;
|
|
639
670
|
}
|
|
640
671
|
|
|
641
|
-
// src/lib/
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
672
|
+
// src/lib/dep-detector.ts
|
|
673
|
+
import * as fs2 from "fs";
|
|
674
|
+
import * as path6 from "path";
|
|
675
|
+
var DEP_TO_PRESET = {
|
|
676
|
+
// First-party autotel wrappers
|
|
677
|
+
"@sentry/node": "sentry",
|
|
678
|
+
"@sentry/bun": "sentry",
|
|
679
|
+
hono: "hono",
|
|
680
|
+
"@modelcontextprotocol/sdk": "mcp",
|
|
681
|
+
"@tanstack/start": "tanstack",
|
|
682
|
+
"@tanstack/start-server": "tanstack",
|
|
683
|
+
"@tanstack/start-client": "tanstack",
|
|
684
|
+
// Subscribers
|
|
685
|
+
"posthog-node": "posthog",
|
|
686
|
+
"@posthog/node": "posthog",
|
|
687
|
+
mixpanel: "mixpanel",
|
|
688
|
+
"@amplitude/analytics-node": "amplitude",
|
|
689
|
+
"@segment/analytics-node": "segment",
|
|
690
|
+
"@slack/web-api": "slack",
|
|
691
|
+
"@slack/webhook": "slack",
|
|
692
|
+
// Plugins (existing presets)
|
|
693
|
+
mongoose: "mongoose",
|
|
694
|
+
"drizzle-orm": "drizzle",
|
|
695
|
+
// Auto-instrumented (no first-party preset needed)
|
|
696
|
+
express: "auto-instr",
|
|
697
|
+
fastify: "auto-instr",
|
|
698
|
+
"@nestjs/core": "auto-instr",
|
|
699
|
+
next: "auto-instr",
|
|
700
|
+
pg: "auto-instr",
|
|
701
|
+
mysql: "auto-instr",
|
|
702
|
+
mysql2: "auto-instr",
|
|
703
|
+
redis: "auto-instr",
|
|
704
|
+
ioredis: "auto-instr",
|
|
705
|
+
"@aws-sdk/client-s3": "auto-instr",
|
|
706
|
+
graphql: "auto-instr"
|
|
707
|
+
};
|
|
708
|
+
var LOGGER_DEPS = {
|
|
709
|
+
pino: "pino",
|
|
710
|
+
winston: "winston",
|
|
711
|
+
bunyan: "bunyan"
|
|
712
|
+
};
|
|
713
|
+
var ENV_KEY_TO_BACKEND = [
|
|
714
|
+
{ key: "DD_API_KEY", slug: "datadog" },
|
|
715
|
+
{ key: "DATADOG_API_KEY", slug: "datadog" },
|
|
716
|
+
{ key: "HONEYCOMB_API_KEY", slug: "honeycomb" },
|
|
717
|
+
{ key: "HONEYCOMB_WRITE_KEY", slug: "honeycomb" },
|
|
718
|
+
{ key: "GOOGLE_CLOUD_PROJECT", slug: "google-cloud" },
|
|
719
|
+
// OTEL endpoint is generic enough that we pick otlp-http
|
|
720
|
+
{ key: "OTEL_EXPORTER_OTLP_ENDPOINT", slug: "otlp-http" }
|
|
721
|
+
];
|
|
722
|
+
function mergeDeps(target, root) {
|
|
723
|
+
const out = /* @__PURE__ */ new Map();
|
|
724
|
+
if (root) {
|
|
725
|
+
for (const [name, version] of Object.entries({
|
|
726
|
+
...root.dependencies,
|
|
727
|
+
...root.devDependencies
|
|
728
|
+
})) {
|
|
729
|
+
out.set(name, { version, resolution: "workspace-root" });
|
|
653
730
|
}
|
|
654
731
|
}
|
|
732
|
+
for (const [name, version] of Object.entries({
|
|
733
|
+
...target.dependencies,
|
|
734
|
+
...target.devDependencies
|
|
735
|
+
})) {
|
|
736
|
+
out.set(name, { version, resolution: "target" });
|
|
737
|
+
}
|
|
738
|
+
return out;
|
|
655
739
|
}
|
|
656
|
-
function
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
740
|
+
function detectLoggers(deps) {
|
|
741
|
+
const present = [];
|
|
742
|
+
for (const [name, kind] of Object.entries(LOGGER_DEPS)) {
|
|
743
|
+
if (deps.has(name)) present.push(kind);
|
|
744
|
+
}
|
|
745
|
+
if (present.length === 0) return { primary: null, autoInstrument: [] };
|
|
746
|
+
if (present.includes("pino")) {
|
|
747
|
+
return {
|
|
748
|
+
primary: "pino",
|
|
749
|
+
autoInstrument: present.filter((l) => l !== "pino")
|
|
750
|
+
};
|
|
664
751
|
}
|
|
752
|
+
return {
|
|
753
|
+
primary: present[0] ?? null,
|
|
754
|
+
autoInstrument: present.slice(1)
|
|
755
|
+
};
|
|
665
756
|
}
|
|
666
|
-
function
|
|
667
|
-
|
|
757
|
+
function parseEnvKeys(content) {
|
|
758
|
+
const out = /* @__PURE__ */ new Set();
|
|
759
|
+
for (const rawLine of content.split("\n")) {
|
|
760
|
+
const line = rawLine.trim();
|
|
761
|
+
if (line.length === 0 || line.startsWith("#")) continue;
|
|
762
|
+
const eq = line.indexOf("=");
|
|
763
|
+
if (eq === -1) continue;
|
|
764
|
+
const key = line.slice(0, eq).trim();
|
|
765
|
+
if (key.length > 0) out.add(key);
|
|
766
|
+
}
|
|
767
|
+
return out;
|
|
668
768
|
}
|
|
669
|
-
function
|
|
670
|
-
|
|
769
|
+
function collectEnvKeys(opts) {
|
|
770
|
+
const keys = /* @__PURE__ */ new Set();
|
|
771
|
+
const sources = [];
|
|
772
|
+
const tryRead = (name, requiresConsent) => {
|
|
773
|
+
if (requiresConsent && !opts.envConsent) return;
|
|
774
|
+
const p = path6.join(opts.packageRoot, name);
|
|
775
|
+
const content = readFileSafe(p);
|
|
776
|
+
if (content === null) return;
|
|
777
|
+
sources.push(name);
|
|
778
|
+
for (const k of parseEnvKeys(content)) keys.add(k);
|
|
779
|
+
};
|
|
780
|
+
tryRead(".env.example", false);
|
|
781
|
+
tryRead(".env.sample", false);
|
|
782
|
+
tryRead(".env", true);
|
|
783
|
+
tryRead(".env.local", true);
|
|
784
|
+
return { keys, sources };
|
|
671
785
|
}
|
|
672
|
-
function
|
|
673
|
-
|
|
786
|
+
function detectBackend(opts) {
|
|
787
|
+
for (const { key, slug } of ENV_KEY_TO_BACKEND) {
|
|
788
|
+
if (opts.envKeys.has(key)) {
|
|
789
|
+
return { slug, source: "env", detail: key };
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
if (opts.deps.has("dd-trace")) {
|
|
793
|
+
return { slug: "datadog", source: "deps", detail: "dd-trace" };
|
|
794
|
+
}
|
|
795
|
+
return { slug: "local", source: "default" };
|
|
674
796
|
}
|
|
675
|
-
function
|
|
676
|
-
if (
|
|
677
|
-
return;
|
|
797
|
+
function detectPlatform(packageRoot) {
|
|
798
|
+
if (fileExists(path6.join(packageRoot, "wrangler.toml")) || fileExists(path6.join(packageRoot, "wrangler.jsonc")) || fileExists(path6.join(packageRoot, "wrangler.json"))) {
|
|
799
|
+
return "cloudflare";
|
|
678
800
|
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
801
|
+
return null;
|
|
802
|
+
}
|
|
803
|
+
function detectInProject(opts) {
|
|
804
|
+
const { project } = opts;
|
|
805
|
+
let rootPkg = null;
|
|
806
|
+
if (project.workspace.workspaceRoot !== null && project.workspace.workspaceRoot !== project.packageRoot) {
|
|
807
|
+
rootPkg = readJsonSafe(
|
|
808
|
+
path6.join(project.workspace.workspaceRoot, "package.json")
|
|
809
|
+
);
|
|
682
810
|
}
|
|
683
|
-
|
|
684
|
-
|
|
811
|
+
const deps = mergeDeps(project.packageJson, rootPkg);
|
|
812
|
+
const detectedPackages = [];
|
|
813
|
+
const presetSet = /* @__PURE__ */ new Set();
|
|
814
|
+
const autoInstrDeps = [];
|
|
815
|
+
for (const [name, info2] of deps.entries()) {
|
|
816
|
+
const mapping = DEP_TO_PRESET[name];
|
|
817
|
+
if (mapping === void 0 && LOGGER_DEPS[name] === void 0) continue;
|
|
818
|
+
detectedPackages.push({
|
|
819
|
+
name,
|
|
820
|
+
version: info2.version,
|
|
821
|
+
resolution: info2.resolution
|
|
822
|
+
});
|
|
823
|
+
if (mapping === "auto-instr") {
|
|
824
|
+
autoInstrDeps.push(name);
|
|
825
|
+
} else if (mapping !== void 0) {
|
|
826
|
+
presetSet.add(mapping);
|
|
827
|
+
}
|
|
685
828
|
}
|
|
829
|
+
const loggers = detectLoggers(deps);
|
|
830
|
+
const platform = detectPlatform(project.packageRoot);
|
|
831
|
+
if (platform !== null) presetSet.add(platform);
|
|
832
|
+
const { keys: envKeys } = collectEnvKeys({
|
|
833
|
+
packageRoot: project.packageRoot,
|
|
834
|
+
envConsent: opts.envConsent
|
|
835
|
+
});
|
|
836
|
+
const backend = detectBackend({
|
|
837
|
+
envKeys,
|
|
838
|
+
packageRoot: project.packageRoot,
|
|
839
|
+
deps
|
|
840
|
+
});
|
|
841
|
+
return {
|
|
842
|
+
packages: detectedPackages,
|
|
843
|
+
presets: [...presetSet],
|
|
844
|
+
primaryLogger: loggers.primary,
|
|
845
|
+
autoInstrumentLoggers: loggers.autoInstrument,
|
|
846
|
+
autoInstrumentedDeps: autoInstrDeps,
|
|
847
|
+
backend,
|
|
848
|
+
platform
|
|
849
|
+
};
|
|
686
850
|
}
|
|
687
|
-
function
|
|
688
|
-
|
|
689
|
-
addCorePackages(plan);
|
|
690
|
-
addPresetsToPlan(plan, options.presets);
|
|
691
|
-
addAutoInstrumentationPackages(plan, options.autoInstrumentations);
|
|
692
|
-
return plan;
|
|
851
|
+
function envFilesRequireConsent(packageRoot) {
|
|
852
|
+
return fileExists(path6.join(packageRoot, ".env")) || fileExists(path6.join(packageRoot, ".env.local"));
|
|
693
853
|
}
|
|
694
854
|
|
|
855
|
+
// src/lib/plan-builder.ts
|
|
856
|
+
import * as path7 from "path";
|
|
857
|
+
|
|
695
858
|
// src/presets/backends/datadog.ts
|
|
696
859
|
var datadogDirect = {
|
|
697
860
|
name: "Datadog (Direct)",
|
|
@@ -1475,6 +1638,183 @@ var drizzle = {
|
|
|
1475
1638
|
]
|
|
1476
1639
|
};
|
|
1477
1640
|
|
|
1641
|
+
// src/presets/plugins/sentry.ts
|
|
1642
|
+
var sentry = {
|
|
1643
|
+
name: "Sentry",
|
|
1644
|
+
slug: "sentry",
|
|
1645
|
+
type: "plugin",
|
|
1646
|
+
description: "Send traces to Sentry via OTLP and link captured errors to active traces",
|
|
1647
|
+
packages: {
|
|
1648
|
+
required: ["autotel-sentry", "@sentry/node"],
|
|
1649
|
+
optional: [],
|
|
1650
|
+
devOnly: []
|
|
1651
|
+
},
|
|
1652
|
+
env: {
|
|
1653
|
+
required: [
|
|
1654
|
+
{
|
|
1655
|
+
name: "SENTRY_DSN",
|
|
1656
|
+
description: "Sentry Data Source Name (project ingest URL)",
|
|
1657
|
+
example: "https://<key>@o<org>.ingest.sentry.io/<project>",
|
|
1658
|
+
sensitive: true
|
|
1659
|
+
}
|
|
1660
|
+
],
|
|
1661
|
+
optional: []
|
|
1662
|
+
},
|
|
1663
|
+
imports: [
|
|
1664
|
+
{
|
|
1665
|
+
source: "@sentry/node",
|
|
1666
|
+
specifiers: ["*"],
|
|
1667
|
+
default: "* as Sentry"
|
|
1668
|
+
},
|
|
1669
|
+
{
|
|
1670
|
+
source: "autotel-sentry",
|
|
1671
|
+
specifiers: ["sentryOtlpConfig", "linkSentryErrors"]
|
|
1672
|
+
}
|
|
1673
|
+
],
|
|
1674
|
+
configBlock: {
|
|
1675
|
+
type: "plugin",
|
|
1676
|
+
code: `// Sentry OTLP setup
|
|
1677
|
+
const sentryConfig = sentryOtlpConfig(process.env.SENTRY_DSN!);
|
|
1678
|
+
Sentry.init({ dsn: sentryConfig.dsn, skipOpenTelemetrySetup: true });
|
|
1679
|
+
linkSentryErrors(Sentry);`,
|
|
1680
|
+
section: "PLUGIN_INIT"
|
|
1681
|
+
},
|
|
1682
|
+
nextSteps: [
|
|
1683
|
+
"Set SENTRY_DSN in .env (from Sentry \u2192 Settings \u2192 Projects \u2192 Client Keys)",
|
|
1684
|
+
"Captured Sentry errors will now be linked to active OTel traces",
|
|
1685
|
+
"Pass sentryConfig.endpoint and sentryConfig.headers to init() if you want Sentry as the trace backend"
|
|
1686
|
+
]
|
|
1687
|
+
};
|
|
1688
|
+
|
|
1689
|
+
// src/presets/plugins/hono.ts
|
|
1690
|
+
var hono = {
|
|
1691
|
+
name: "Hono",
|
|
1692
|
+
slug: "hono",
|
|
1693
|
+
type: "plugin",
|
|
1694
|
+
description: "HTTP tracing + metrics middleware for Hono apps",
|
|
1695
|
+
packages: {
|
|
1696
|
+
required: ["autotel-hono"],
|
|
1697
|
+
optional: ["autotel-adapters"],
|
|
1698
|
+
devOnly: []
|
|
1699
|
+
},
|
|
1700
|
+
env: {
|
|
1701
|
+
required: [],
|
|
1702
|
+
optional: []
|
|
1703
|
+
},
|
|
1704
|
+
imports: [
|
|
1705
|
+
{
|
|
1706
|
+
source: "autotel-hono",
|
|
1707
|
+
specifiers: ["otel"]
|
|
1708
|
+
}
|
|
1709
|
+
],
|
|
1710
|
+
configBlock: {
|
|
1711
|
+
type: "plugin",
|
|
1712
|
+
code: `// Register the otel() middleware on your Hono app:
|
|
1713
|
+
// app.use('*', otel({
|
|
1714
|
+
// serviceName: 'my-service',
|
|
1715
|
+
// captureRequestHeaders: ['user-agent'],
|
|
1716
|
+
// captureResponseHeaders: ['content-type'],
|
|
1717
|
+
// }));`,
|
|
1718
|
+
section: "PLUGIN_INIT"
|
|
1719
|
+
},
|
|
1720
|
+
nextSteps: [
|
|
1721
|
+
"Add otel() middleware to your Hono app: app.use('*', otel({ serviceName: '<name>' }))",
|
|
1722
|
+
"Optionally install autotel-adapters and use useLogger() to thread the request logger",
|
|
1723
|
+
"See packages/autotel-hono README for header/metric configuration"
|
|
1724
|
+
]
|
|
1725
|
+
};
|
|
1726
|
+
|
|
1727
|
+
// src/presets/plugins/mcp.ts
|
|
1728
|
+
var mcp = {
|
|
1729
|
+
name: "MCP (Model Context Protocol)",
|
|
1730
|
+
slug: "mcp",
|
|
1731
|
+
type: "plugin",
|
|
1732
|
+
description: "Distributed tracing for MCP servers and clients (W3C trace context via _meta)",
|
|
1733
|
+
packages: {
|
|
1734
|
+
required: ["autotel-mcp-instrumentation"],
|
|
1735
|
+
optional: [],
|
|
1736
|
+
devOnly: []
|
|
1737
|
+
},
|
|
1738
|
+
env: {
|
|
1739
|
+
required: [],
|
|
1740
|
+
optional: []
|
|
1741
|
+
},
|
|
1742
|
+
imports: [
|
|
1743
|
+
{
|
|
1744
|
+
source: "autotel-mcp-instrumentation",
|
|
1745
|
+
specifiers: ["instrumentMcpServer"]
|
|
1746
|
+
}
|
|
1747
|
+
],
|
|
1748
|
+
configBlock: {
|
|
1749
|
+
type: "plugin",
|
|
1750
|
+
code: `// Wrap your MCP server after constructing it:
|
|
1751
|
+
// const instrumented = instrumentMcpServer(server, {
|
|
1752
|
+
// networkTransport: 'pipe',
|
|
1753
|
+
// captureToolArgs: true,
|
|
1754
|
+
// captureToolResults: false,
|
|
1755
|
+
// captureErrors: true,
|
|
1756
|
+
// });
|
|
1757
|
+
//
|
|
1758
|
+
// For clients, import { instrumentMcpClient } and wrap the client.`,
|
|
1759
|
+
section: "PLUGIN_INIT"
|
|
1760
|
+
},
|
|
1761
|
+
nextSteps: [
|
|
1762
|
+
"Wrap your MCP server with instrumentMcpServer(server, { ... })",
|
|
1763
|
+
"For clients, use instrumentMcpClient from autotel-mcp-instrumentation",
|
|
1764
|
+
"Traces follow OTel MCP semantic conventions and propagate via the _meta field"
|
|
1765
|
+
]
|
|
1766
|
+
};
|
|
1767
|
+
|
|
1768
|
+
// src/presets/plugins/tanstack.ts
|
|
1769
|
+
var tanstack = {
|
|
1770
|
+
name: "TanStack Start",
|
|
1771
|
+
slug: "tanstack",
|
|
1772
|
+
type: "plugin",
|
|
1773
|
+
description: "Tracing middleware for TanStack Start (SSR requests + server functions)",
|
|
1774
|
+
packages: {
|
|
1775
|
+
required: ["autotel-tanstack"],
|
|
1776
|
+
optional: [],
|
|
1777
|
+
devOnly: []
|
|
1778
|
+
},
|
|
1779
|
+
env: {
|
|
1780
|
+
required: [],
|
|
1781
|
+
optional: []
|
|
1782
|
+
},
|
|
1783
|
+
imports: [
|
|
1784
|
+
{
|
|
1785
|
+
source: "autotel-tanstack/middleware",
|
|
1786
|
+
specifiers: ["createTracingServerHandler"]
|
|
1787
|
+
}
|
|
1788
|
+
],
|
|
1789
|
+
configBlock: {
|
|
1790
|
+
type: "plugin",
|
|
1791
|
+
code: `// In your start.ts, register tracing middleware on createStart():
|
|
1792
|
+
// import { createMiddleware, createStart } from '@tanstack/react-start';
|
|
1793
|
+
//
|
|
1794
|
+
// const requestTracing = createMiddleware().server(
|
|
1795
|
+
// createTracingServerHandler({
|
|
1796
|
+
// captureHeaders: ['x-request-id', 'user-agent'],
|
|
1797
|
+
// excludePaths: ['/health', '/metrics'],
|
|
1798
|
+
// }),
|
|
1799
|
+
// );
|
|
1800
|
+
//
|
|
1801
|
+
// const functionTracing = createMiddleware({ type: 'function' }).server(
|
|
1802
|
+
// createTracingServerHandler({ type: 'function', captureArgs: true }),
|
|
1803
|
+
// );
|
|
1804
|
+
//
|
|
1805
|
+
// export const startInstance = createStart(() => ({
|
|
1806
|
+
// requestMiddleware: [requestTracing],
|
|
1807
|
+
// functionMiddleware: [functionTracing],
|
|
1808
|
+
// }));`,
|
|
1809
|
+
section: "PLUGIN_INIT"
|
|
1810
|
+
},
|
|
1811
|
+
nextSteps: [
|
|
1812
|
+
"Add tracing middleware to your createStart() call in src/start.ts",
|
|
1813
|
+
'Alternative: import "autotel-tanstack/auto" for zero-config init via OTEL_* env vars',
|
|
1814
|
+
"See packages/autotel-tanstack README for loader/server-function tracing helpers"
|
|
1815
|
+
]
|
|
1816
|
+
};
|
|
1817
|
+
|
|
1478
1818
|
// src/presets/platforms/aws.ts
|
|
1479
1819
|
var awsLambda = {
|
|
1480
1820
|
name: "AWS Lambda",
|
|
@@ -1601,7 +1941,11 @@ var subscribers = /* @__PURE__ */ new Map([
|
|
|
1601
1941
|
]);
|
|
1602
1942
|
var plugins = /* @__PURE__ */ new Map([
|
|
1603
1943
|
["mongoose", mongoose],
|
|
1604
|
-
["drizzle", drizzle]
|
|
1944
|
+
["drizzle", drizzle],
|
|
1945
|
+
["sentry", sentry],
|
|
1946
|
+
["hono", hono],
|
|
1947
|
+
["mcp", mcp],
|
|
1948
|
+
["tanstack", tanstack]
|
|
1605
1949
|
]);
|
|
1606
1950
|
var platforms = /* @__PURE__ */ new Map([
|
|
1607
1951
|
["aws-lambda", awsLambda],
|
|
@@ -1693,120 +2037,399 @@ function listPresetSlugs(type) {
|
|
|
1693
2037
|
return [...getPresetsByType(type).keys()];
|
|
1694
2038
|
}
|
|
1695
2039
|
|
|
1696
|
-
// src/
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
2040
|
+
// src/lib/plan-builder.ts
|
|
2041
|
+
function buildPlanFromDetection(opts) {
|
|
2042
|
+
const { project, detection } = opts;
|
|
2043
|
+
const presets = [];
|
|
2044
|
+
const presetSlugs = [];
|
|
2045
|
+
const backendPreset = findPreset(detection.backend.slug);
|
|
2046
|
+
if (backendPreset !== null) {
|
|
2047
|
+
presets.push(backendPreset);
|
|
2048
|
+
presetSlugs.push(detection.backend.slug);
|
|
2049
|
+
}
|
|
2050
|
+
for (const slug of detection.presets) {
|
|
2051
|
+
if (slug === detection.backend.slug) continue;
|
|
2052
|
+
const p = findPreset(slug);
|
|
2053
|
+
if (p !== null) {
|
|
2054
|
+
presets.push(p);
|
|
2055
|
+
presetSlugs.push(slug);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
const prod = /* @__PURE__ */ new Set(["autotel"]);
|
|
2059
|
+
const dev = /* @__PURE__ */ new Set();
|
|
2060
|
+
for (const p of presets) {
|
|
2061
|
+
for (const pkg of p.packages.required) prod.add(pkg);
|
|
2062
|
+
for (const pkg of p.packages.optional) prod.add(pkg);
|
|
2063
|
+
for (const pkg of p.packages.devOnly) dev.add(pkg);
|
|
2064
|
+
}
|
|
2065
|
+
if (detection.primaryLogger === "pino") prod.add("pino");
|
|
2066
|
+
if (detection.autoInstrumentedDeps.length > 0 || detection.autoInstrumentLoggers.length > 0 || detection.primaryLogger === "winston" || detection.primaryLogger === "bunyan") {
|
|
2067
|
+
prod.add("@opentelemetry/auto-instrumentations-node");
|
|
2068
|
+
}
|
|
2069
|
+
const envVars = [];
|
|
2070
|
+
const seenEnv = /* @__PURE__ */ new Set();
|
|
2071
|
+
for (const p of presets) {
|
|
2072
|
+
for (const ev of [...p.env.required, ...p.env.optional]) {
|
|
2073
|
+
if (seenEnv.has(ev.name)) continue;
|
|
2074
|
+
seenEnv.add(ev.name);
|
|
2075
|
+
envVars.push({
|
|
2076
|
+
name: ev.name,
|
|
2077
|
+
sensitive: ev.sensitive,
|
|
2078
|
+
action: "add-to-.env.example"
|
|
2079
|
+
});
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
const instrumentationPath = getInstrumentationPath(
|
|
2083
|
+
project.packageRoot,
|
|
2084
|
+
project.hasTypeScript
|
|
2085
|
+
);
|
|
2086
|
+
const filesToWrite = [
|
|
2087
|
+
{
|
|
2088
|
+
path: path7.relative(project.cwd, instrumentationPath),
|
|
2089
|
+
action: fileExists(instrumentationPath) ? "merge" : "create"
|
|
2090
|
+
}
|
|
2091
|
+
];
|
|
2092
|
+
const envExamplePath = path7.join(project.packageRoot, ".env.example");
|
|
2093
|
+
if (envVars.length > 0 && !fileExists(envExamplePath)) {
|
|
2094
|
+
filesToWrite.push({
|
|
2095
|
+
path: path7.relative(project.cwd, envExamplePath),
|
|
2096
|
+
action: "create"
|
|
2097
|
+
});
|
|
2098
|
+
}
|
|
2099
|
+
const nextSteps = presets.flatMap((p) => p.nextSteps);
|
|
2100
|
+
const plan = {
|
|
2101
|
+
v: 1,
|
|
2102
|
+
presets: presetSlugs,
|
|
2103
|
+
packagesToInstall: { prod: [...prod], dev: [...dev] },
|
|
2104
|
+
filesToWrite,
|
|
2105
|
+
envVars,
|
|
2106
|
+
nextSteps,
|
|
2107
|
+
detected: {
|
|
2108
|
+
packages: detection.packages,
|
|
2109
|
+
primaryLogger: detection.primaryLogger,
|
|
2110
|
+
autoInstrumentLoggers: detection.autoInstrumentLoggers,
|
|
2111
|
+
autoInstrumentedDeps: detection.autoInstrumentedDeps,
|
|
2112
|
+
backend: detection.backend,
|
|
2113
|
+
platform: detection.platform
|
|
2114
|
+
}
|
|
2115
|
+
};
|
|
2116
|
+
return { plan, presets, instrumentationPath };
|
|
2117
|
+
}
|
|
2118
|
+
function findPreset(slug) {
|
|
2119
|
+
for (const type of ["backend", "subscriber", "plugin", "platform"]) {
|
|
2120
|
+
const p = getPreset(type, slug);
|
|
2121
|
+
if (p !== void 0) return p;
|
|
2122
|
+
}
|
|
2123
|
+
return null;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// src/lib/errors.ts
|
|
2127
|
+
var AutotelErrorCodes = {
|
|
2128
|
+
// validation
|
|
2129
|
+
E_NO_PACKAGE_JSON: "AUTOTEL_E_NO_PACKAGE_JSON",
|
|
2130
|
+
E_UNKNOWN_PRESET: "AUTOTEL_E_UNKNOWN_PRESET",
|
|
2131
|
+
E_INVALID_PLAN: "AUTOTEL_E_INVALID_PLAN",
|
|
2132
|
+
E_INVALID_INPUT: "AUTOTEL_E_INVALID_INPUT",
|
|
2133
|
+
E_INVALID_FLAG: "AUTOTEL_E_INVALID_FLAG",
|
|
2134
|
+
// environment
|
|
2135
|
+
E_NO_WORKSPACE_PACKAGES: "AUTOTEL_E_NO_WORKSPACE_PACKAGES",
|
|
2136
|
+
E_ENV_CONSENT_REQUIRED: "AUTOTEL_E_ENV_CONSENT_REQUIRED",
|
|
2137
|
+
// conflict
|
|
2138
|
+
E_EXISTING_CONFIG: "AUTOTEL_E_EXISTING_CONFIG",
|
|
2139
|
+
E_AMBIGUOUS_LOGGER: "AUTOTEL_E_AMBIGUOUS_LOGGER",
|
|
2140
|
+
// install
|
|
2141
|
+
E_INSTALL_FAILED: "AUTOTEL_E_INSTALL_FAILED",
|
|
2142
|
+
// io
|
|
2143
|
+
E_WRITE_FAILED: "AUTOTEL_E_WRITE_FAILED",
|
|
2144
|
+
E_READ_FAILED: "AUTOTEL_E_READ_FAILED",
|
|
2145
|
+
// runtime
|
|
2146
|
+
E_UNKNOWN: "AUTOTEL_E_UNKNOWN"
|
|
2147
|
+
};
|
|
2148
|
+
var AutotelError = class extends Error {
|
|
2149
|
+
type;
|
|
2150
|
+
code;
|
|
2151
|
+
retryable;
|
|
2152
|
+
fix;
|
|
2153
|
+
expected;
|
|
2154
|
+
suggestions;
|
|
2155
|
+
constructor(opts) {
|
|
2156
|
+
super(opts.message);
|
|
2157
|
+
this.name = "AutotelError";
|
|
2158
|
+
this.type = opts.type;
|
|
2159
|
+
this.code = opts.code;
|
|
2160
|
+
this.retryable = opts.retryable ?? false;
|
|
2161
|
+
this.fix = opts.fix;
|
|
2162
|
+
this.expected = opts.expected;
|
|
2163
|
+
this.suggestions = opts.suggestions;
|
|
2164
|
+
}
|
|
2165
|
+
toEnvelope(command) {
|
|
2166
|
+
return {
|
|
2167
|
+
ok: false,
|
|
2168
|
+
...command !== void 0 ? { command } : {},
|
|
2169
|
+
error: {
|
|
2170
|
+
type: this.type,
|
|
2171
|
+
code: this.code,
|
|
2172
|
+
message: this.message,
|
|
2173
|
+
retryable: this.retryable,
|
|
2174
|
+
...this.fix !== void 0 ? { fix: this.fix } : {},
|
|
2175
|
+
...this.expected !== void 0 ? { expected: this.expected } : {},
|
|
2176
|
+
...this.suggestions !== void 0 ? { suggestions: this.suggestions } : {}
|
|
2177
|
+
}
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
};
|
|
2181
|
+
function exitCodeForError(err) {
|
|
2182
|
+
if (err.type === "validation" || err.type === "conflict") {
|
|
2183
|
+
return 2;
|
|
2184
|
+
}
|
|
2185
|
+
return 1;
|
|
2186
|
+
}
|
|
2187
|
+
function toAutotelError(value) {
|
|
2188
|
+
if (value instanceof AutotelError) return value;
|
|
2189
|
+
const message = value instanceof Error ? value.message : String(value);
|
|
2190
|
+
return new AutotelError({
|
|
2191
|
+
type: "runtime",
|
|
2192
|
+
code: AutotelErrorCodes.E_UNKNOWN,
|
|
2193
|
+
message
|
|
1708
2194
|
});
|
|
1709
2195
|
}
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
2196
|
+
|
|
2197
|
+
// src/lib/plan.ts
|
|
2198
|
+
function parsePlan(input2) {
|
|
2199
|
+
if (typeof input2 !== "object" || input2 === null || Array.isArray(input2)) {
|
|
2200
|
+
throw new AutotelError({
|
|
2201
|
+
type: "validation",
|
|
2202
|
+
code: AutotelErrorCodes.E_INVALID_PLAN,
|
|
2203
|
+
message: "Plan must be a JSON object",
|
|
2204
|
+
expected: { v: 1, presets: "string[]" }
|
|
2205
|
+
});
|
|
2206
|
+
}
|
|
2207
|
+
const obj = input2;
|
|
2208
|
+
if (obj["v"] !== 1) {
|
|
2209
|
+
throw new AutotelError({
|
|
2210
|
+
type: "validation",
|
|
2211
|
+
code: AutotelErrorCodes.E_INVALID_PLAN,
|
|
2212
|
+
message: `Unsupported plan version: ${String(obj["v"])}`,
|
|
2213
|
+
fix: "Regenerate the plan with `autotel init --json --dry-run`",
|
|
2214
|
+
expected: { v: 1 }
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
if (!Array.isArray(obj["presets"])) {
|
|
2218
|
+
throw new AutotelError({
|
|
2219
|
+
type: "validation",
|
|
2220
|
+
code: AutotelErrorCodes.E_INVALID_PLAN,
|
|
2221
|
+
message: "plan.presets must be an array of preset slugs"
|
|
2222
|
+
});
|
|
2223
|
+
}
|
|
2224
|
+
const pkgs = obj["packagesToInstall"];
|
|
2225
|
+
if (typeof pkgs !== "object" || pkgs === null || !Array.isArray(pkgs["prod"]) || !Array.isArray(pkgs["dev"])) {
|
|
2226
|
+
throw new AutotelError({
|
|
2227
|
+
type: "validation",
|
|
2228
|
+
code: AutotelErrorCodes.E_INVALID_PLAN,
|
|
2229
|
+
message: "plan.packagesToInstall must be { prod: string[], dev: string[] }"
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
return obj;
|
|
2233
|
+
}
|
|
2234
|
+
|
|
2235
|
+
// src/lib/instrumentation-parser.ts
|
|
2236
|
+
function parseInstrumentation(content) {
|
|
2237
|
+
const cliOwned = hasCliOwnershipHeader(content);
|
|
2238
|
+
const importedSources = collectImportSources(content);
|
|
2239
|
+
const detectedLogger = pickLogger(importedSources);
|
|
2240
|
+
const autoInstrumentations = parseAutoInstrumentations(content);
|
|
2241
|
+
return { cliOwned, importedSources, detectedLogger, autoInstrumentations };
|
|
2242
|
+
}
|
|
2243
|
+
function collectImportSources(content) {
|
|
2244
|
+
const out = /* @__PURE__ */ new Set();
|
|
2245
|
+
const re = /import\s+(?:[^'"]*\s+from\s+)?['"]([^'"]+)['"]/g;
|
|
2246
|
+
let match;
|
|
2247
|
+
while ((match = re.exec(content)) !== null) {
|
|
2248
|
+
if (match[1] !== void 0) out.add(match[1]);
|
|
2249
|
+
}
|
|
2250
|
+
return out;
|
|
2251
|
+
}
|
|
2252
|
+
function pickLogger(imports) {
|
|
2253
|
+
if (imports.has("pino")) return "pino";
|
|
2254
|
+
if (imports.has("winston")) return "winston";
|
|
2255
|
+
if (imports.has("bunyan")) return "bunyan";
|
|
2256
|
+
return null;
|
|
2257
|
+
}
|
|
2258
|
+
function parseAutoInstrumentations(content) {
|
|
2259
|
+
const re = /autoInstrumentations\s*:\s*\[([^\]]*)\]/;
|
|
2260
|
+
const match = re.exec(content);
|
|
2261
|
+
if (!match || match[1] === void 0) return [];
|
|
2262
|
+
return [...match[1].matchAll(/['"]([^'"]+)['"]/g)].map((m) => m[1]).filter((s) => s !== void 0);
|
|
2263
|
+
}
|
|
2264
|
+
function diffImportSources(existing, planImportSources) {
|
|
2265
|
+
return planImportSources.filter((s) => !existing.importedSources.has(s));
|
|
2266
|
+
}
|
|
2267
|
+
function diffAutoInstrumentations(existing, planEntries) {
|
|
2268
|
+
return planEntries.filter((e) => !existing.autoInstrumentations.includes(e));
|
|
2269
|
+
}
|
|
2270
|
+
|
|
2271
|
+
// src/ui/preview.ts
|
|
2272
|
+
import chalk from "chalk";
|
|
2273
|
+
import { checkbox, select } from "@inquirer/prompts";
|
|
2274
|
+
function renderPlanPreview(plan) {
|
|
2275
|
+
const lines = [
|
|
2276
|
+
chalk.bold("autotel init \u2014 proposed plan"),
|
|
2277
|
+
""
|
|
1717
2278
|
];
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
2279
|
+
if (plan.detected) {
|
|
2280
|
+
const det = plan.detected;
|
|
2281
|
+
const pkgList = det.packages.map((p) => `${p.name}@${p.version}`).join(", ");
|
|
2282
|
+
lines.push(
|
|
2283
|
+
`${chalk.dim("Detected packages:")} ${pkgList || chalk.dim("(none)")}`
|
|
2284
|
+
);
|
|
2285
|
+
if (det.primaryLogger !== null) {
|
|
2286
|
+
lines.push(
|
|
2287
|
+
`${chalk.dim("Logger:")} ${chalk.bold(det.primaryLogger)} ${chalk.dim("(first-class)")}` + (det.autoInstrumentLoggers.length > 0 ? `, ${chalk.dim("+ auto-instrumented:")} ${det.autoInstrumentLoggers.join(", ")}` : "")
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
if (det.autoInstrumentedDeps.length > 0) {
|
|
2291
|
+
lines.push(
|
|
2292
|
+
`${chalk.dim("Covered by auto-instrumentations-node:")} ${det.autoInstrumentedDeps.join(", ")}`
|
|
2293
|
+
);
|
|
2294
|
+
}
|
|
2295
|
+
lines.push(
|
|
2296
|
+
`${chalk.dim("Backend:")} ${chalk.bold(det.backend.slug)} ${chalk.dim(`(${det.backend.source}${det.backend.detail ? `: ${det.backend.detail}` : ""})`)}`
|
|
2297
|
+
);
|
|
2298
|
+
if (det.platform !== null) {
|
|
2299
|
+
lines.push(`${chalk.dim("Platform:")} ${chalk.bold(det.platform)}`);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
lines.push("");
|
|
2303
|
+
lines.push(chalk.bold("Will wire:"));
|
|
2304
|
+
for (const slug of plan.presets) {
|
|
2305
|
+
lines.push(` ${chalk.green("+")} ${slug}`);
|
|
2306
|
+
}
|
|
2307
|
+
lines.push("");
|
|
2308
|
+
if (plan.packagesToInstall.prod.length > 0) {
|
|
2309
|
+
lines.push(
|
|
2310
|
+
`${chalk.bold("Install:")} ${plan.packagesToInstall.prod.join(", ")}`
|
|
2311
|
+
);
|
|
2312
|
+
}
|
|
2313
|
+
if (plan.packagesToInstall.dev.length > 0) {
|
|
2314
|
+
lines.push(
|
|
2315
|
+
`${chalk.bold("Install (dev):")} ${plan.packagesToInstall.dev.join(", ")}`
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
if (plan.envVars.length > 0) {
|
|
2319
|
+
lines.push("");
|
|
2320
|
+
lines.push(chalk.bold("Env vars:"));
|
|
2321
|
+
for (const ev of plan.envVars) {
|
|
2322
|
+
const marker = ev.sensitive ? chalk.yellow("[sensitive]") : "";
|
|
2323
|
+
lines.push(` ${ev.name} ${marker}`);
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
if (plan.filesToWrite.length > 0) {
|
|
2327
|
+
lines.push("");
|
|
2328
|
+
lines.push(chalk.bold("Files:"));
|
|
2329
|
+
for (const f of plan.filesToWrite) {
|
|
2330
|
+
lines.push(` ${chalk.dim(f.action)} ${f.path}`);
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
return lines.join("\n");
|
|
1723
2334
|
}
|
|
1724
|
-
async function
|
|
1725
|
-
|
|
1726
|
-
|
|
2335
|
+
async function confirmOrEditPlan(plan) {
|
|
2336
|
+
console.log(renderPlanPreview(plan));
|
|
2337
|
+
console.log("");
|
|
2338
|
+
const choice = await select({
|
|
2339
|
+
message: "Proceed?",
|
|
1727
2340
|
choices: [
|
|
1728
|
-
{ value:
|
|
1729
|
-
{ value: "
|
|
1730
|
-
{ value: "
|
|
2341
|
+
{ value: "apply", name: "Apply the plan above" },
|
|
2342
|
+
{ value: "edit", name: "Edit \u2014 deselect items I do not want" },
|
|
2343
|
+
{ value: "abort", name: "Abort, write nothing" }
|
|
1731
2344
|
],
|
|
1732
|
-
default:
|
|
2345
|
+
default: "apply"
|
|
1733
2346
|
});
|
|
2347
|
+
if (choice === "abort") return null;
|
|
2348
|
+
if (choice === "apply") return plan;
|
|
2349
|
+
const kept = await checkbox({
|
|
2350
|
+
message: "Keep which presets?",
|
|
2351
|
+
choices: plan.presets.map((slug) => ({
|
|
2352
|
+
value: slug,
|
|
2353
|
+
name: slug,
|
|
2354
|
+
checked: true
|
|
2355
|
+
}))
|
|
2356
|
+
});
|
|
2357
|
+
return { ...plan, presets: kept };
|
|
1734
2358
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
2359
|
+
|
|
2360
|
+
// src/lib/json-output.ts
|
|
2361
|
+
import * as fs3 from "fs";
|
|
2362
|
+
import * as path8 from "path";
|
|
2363
|
+
var outputFilePath = null;
|
|
2364
|
+
var outputFileWritten = false;
|
|
2365
|
+
var outputRoot = null;
|
|
2366
|
+
var redactSecrets = false;
|
|
2367
|
+
function configureJsonOutput(opts) {
|
|
2368
|
+
outputFilePath = opts.outputFile ?? null;
|
|
2369
|
+
outputFileWritten = false;
|
|
2370
|
+
outputRoot = opts.outputRoot ?? null;
|
|
2371
|
+
redactSecrets = opts.noSecrets === true || process.env["AUTOTEL_NO_SECRETS"] === "1" || process.env["AGENT_SANDBOX"] === "1";
|
|
2372
|
+
}
|
|
2373
|
+
var SECRET_KEY_PATTERN = /SECRET|TOKEN|PASSWORD|API[_-]?KEY|DSN/i;
|
|
2374
|
+
var SECRET_VALUE_PATTERN = /^[A-Za-z0-9_\-+/=]{40,}$/;
|
|
2375
|
+
var REDACTED = "[REDACTED]";
|
|
2376
|
+
function redact(value) {
|
|
2377
|
+
if (value === null || value === void 0) return value;
|
|
2378
|
+
if (Array.isArray(value)) {
|
|
2379
|
+
return value.map((v) => redact(v));
|
|
2380
|
+
}
|
|
2381
|
+
if (typeof value === "object") {
|
|
2382
|
+
const out = {};
|
|
2383
|
+
for (const [k, v] of Object.entries(value)) {
|
|
2384
|
+
if (typeof v === "string" && SECRET_KEY_PATTERN.test(k)) {
|
|
2385
|
+
out[k] = REDACTED;
|
|
2386
|
+
} else {
|
|
2387
|
+
out[k] = redact(v);
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2390
|
+
return out;
|
|
1742
2391
|
}
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
2392
|
+
if (typeof value === "string" && SECRET_VALUE_PATTERN.test(value)) {
|
|
2393
|
+
return REDACTED;
|
|
2394
|
+
}
|
|
2395
|
+
return value;
|
|
1747
2396
|
}
|
|
1748
|
-
|
|
1749
|
-
const
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
2397
|
+
function printJson(data) {
|
|
2398
|
+
const payload = redactSecrets ? redact(data) : data;
|
|
2399
|
+
const serialised = JSON.stringify(payload, null, 2);
|
|
2400
|
+
process.stdout.write(serialised + "\n");
|
|
2401
|
+
if (outputFilePath !== null && !outputFileWritten) {
|
|
2402
|
+
writeArtifactFile(outputFilePath, serialised);
|
|
2403
|
+
outputFileWritten = true;
|
|
1755
2404
|
}
|
|
1756
|
-
return await checkbox({
|
|
1757
|
-
message: "Which event destinations? (space to select, enter to continue)",
|
|
1758
|
-
choices
|
|
1759
|
-
});
|
|
1760
2405
|
}
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
default: "all"
|
|
1770
|
-
});
|
|
1771
|
-
}
|
|
1772
|
-
async function promptStartupStyle(hasTypeScript) {
|
|
1773
|
-
const choices = hasTypeScript ? [
|
|
1774
|
-
{ value: "node-esm", name: "Node ESM (node --import) - Recommended" },
|
|
1775
|
-
{ value: "tsx", name: "tsx (tsx --import) - For development" },
|
|
1776
|
-
{ value: "ts-node", name: "ts-node" },
|
|
1777
|
-
{ value: "nextjs", name: "Next.js" },
|
|
1778
|
-
{ value: "other", name: "Other / Manual" }
|
|
1779
|
-
] : [
|
|
1780
|
-
{ value: "node-esm", name: "Node ESM (node --import) - Recommended" },
|
|
1781
|
-
{ value: "nextjs", name: "Next.js" },
|
|
1782
|
-
{ value: "other", name: "Other / Manual" }
|
|
1783
|
-
];
|
|
1784
|
-
return await select({
|
|
1785
|
-
message: "How do you start your app?",
|
|
1786
|
-
choices,
|
|
1787
|
-
default: "node-esm"
|
|
1788
|
-
});
|
|
2406
|
+
function writeArtifactFile(filePath, content) {
|
|
2407
|
+
const resolved = path8.resolve(filePath);
|
|
2408
|
+
if (outputRoot !== null) {
|
|
2409
|
+
atomicWrite(resolved, content, { root: outputRoot });
|
|
2410
|
+
} else {
|
|
2411
|
+
fs3.mkdirSync(path8.dirname(resolved), { recursive: true });
|
|
2412
|
+
fs3.writeFileSync(resolved, content, "utf8");
|
|
2413
|
+
}
|
|
1789
2414
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
],
|
|
1798
|
-
default: "update"
|
|
2415
|
+
|
|
2416
|
+
// src/ui/prompts.ts
|
|
2417
|
+
import { select as select2, checkbox as checkbox2, confirm, input } from "@inquirer/prompts";
|
|
2418
|
+
async function promptConfirm(message, defaultValue = true) {
|
|
2419
|
+
return await confirm({
|
|
2420
|
+
message,
|
|
2421
|
+
default: defaultValue
|
|
1799
2422
|
});
|
|
1800
2423
|
}
|
|
1801
2424
|
|
|
1802
2425
|
// src/ui/output.ts
|
|
1803
|
-
import
|
|
2426
|
+
import chalk2 from "chalk";
|
|
1804
2427
|
var STATUS = {
|
|
1805
|
-
ok:
|
|
1806
|
-
warn:
|
|
1807
|
-
error:
|
|
1808
|
-
info:
|
|
1809
|
-
skip:
|
|
2428
|
+
ok: chalk2.green("[OK]"),
|
|
2429
|
+
warn: chalk2.yellow("[WARN]"),
|
|
2430
|
+
error: chalk2.red("[ERROR]"),
|
|
2431
|
+
info: chalk2.blue("[INFO]"),
|
|
2432
|
+
skip: chalk2.gray("[SKIP]")
|
|
1810
2433
|
};
|
|
1811
2434
|
function formatCheck(check) {
|
|
1812
2435
|
const lines = [];
|
|
@@ -1814,68 +2437,66 @@ function formatCheck(check) {
|
|
|
1814
2437
|
lines.push(` ${statusToken} ${check.message}`);
|
|
1815
2438
|
if (check.details && check.details.length > 0) {
|
|
1816
2439
|
for (const detail of check.details) {
|
|
1817
|
-
lines.push(` ${
|
|
2440
|
+
lines.push(` ${chalk2.dim(detail)}`);
|
|
1818
2441
|
}
|
|
1819
2442
|
}
|
|
1820
2443
|
if (check.fix) {
|
|
1821
|
-
lines.push(` ${
|
|
2444
|
+
lines.push(` ${chalk2.cyan("Fix:")} ${check.fix.cmd}`);
|
|
1822
2445
|
}
|
|
1823
2446
|
return lines;
|
|
1824
2447
|
}
|
|
1825
2448
|
function formatSummary(summary) {
|
|
1826
2449
|
const parts = [];
|
|
1827
2450
|
if (summary.ok > 0) {
|
|
1828
|
-
parts.push(
|
|
2451
|
+
parts.push(chalk2.green(`${summary.ok} passed`));
|
|
1829
2452
|
}
|
|
1830
2453
|
if (summary.warnings > 0) {
|
|
1831
|
-
parts.push(
|
|
2454
|
+
parts.push(chalk2.yellow(`${summary.warnings} warning${summary.warnings > 1 ? "s" : ""}`));
|
|
1832
2455
|
}
|
|
1833
2456
|
if (summary.errors > 0) {
|
|
1834
|
-
parts.push(
|
|
2457
|
+
parts.push(chalk2.red(`${summary.errors} error${summary.errors > 1 ? "s" : ""}`));
|
|
1835
2458
|
}
|
|
1836
2459
|
if (summary.skipped > 0) {
|
|
1837
|
-
parts.push(
|
|
2460
|
+
parts.push(chalk2.gray(`${summary.skipped} skipped`));
|
|
1838
2461
|
}
|
|
1839
2462
|
return `Summary: ${parts.join(", ")}`;
|
|
1840
2463
|
}
|
|
1841
2464
|
function formatFooter(options) {
|
|
1842
2465
|
const lines = [""];
|
|
1843
2466
|
if (options.detected) {
|
|
1844
|
-
lines.push(
|
|
2467
|
+
lines.push(chalk2.dim(`Detected: ${options.detected}`));
|
|
1845
2468
|
}
|
|
1846
2469
|
if (options.wrote && options.wrote.length > 0) {
|
|
1847
|
-
lines.push(
|
|
2470
|
+
lines.push(chalk2.dim(`Wrote: ${options.wrote.join(", ")}`));
|
|
1848
2471
|
}
|
|
1849
2472
|
if (options.next) {
|
|
1850
|
-
lines.push(
|
|
2473
|
+
lines.push(chalk2.cyan(`Next: ${options.next}`));
|
|
1851
2474
|
}
|
|
1852
2475
|
return lines.join("\n");
|
|
1853
2476
|
}
|
|
2477
|
+
function formatPackageManagerInfo(pm, lockfilePath) {
|
|
2478
|
+
if (lockfilePath) {
|
|
2479
|
+
return `${pm} (via ${chalk2.dim(lockfilePath)})`;
|
|
2480
|
+
}
|
|
2481
|
+
return `${pm} (default, no lockfile found)`;
|
|
2482
|
+
}
|
|
1854
2483
|
function heading(text) {
|
|
1855
|
-
console.log(
|
|
2484
|
+
console.log(chalk2.bold(text));
|
|
1856
2485
|
}
|
|
1857
2486
|
function info(text) {
|
|
1858
|
-
console.log(
|
|
2487
|
+
console.log(chalk2.blue(text));
|
|
1859
2488
|
}
|
|
1860
2489
|
function success(text) {
|
|
1861
|
-
console.log(
|
|
2490
|
+
console.log(chalk2.green(text));
|
|
1862
2491
|
}
|
|
1863
2492
|
function warn(text) {
|
|
1864
|
-
console.log(
|
|
2493
|
+
console.log(chalk2.yellow(text));
|
|
1865
2494
|
}
|
|
1866
2495
|
function error(text) {
|
|
1867
|
-
console.log(
|
|
2496
|
+
console.log(chalk2.red(text));
|
|
1868
2497
|
}
|
|
1869
2498
|
function dim(text) {
|
|
1870
|
-
console.log(
|
|
1871
|
-
}
|
|
1872
|
-
function isVerbose() {
|
|
1873
|
-
return process.env["AUTOTEL_VERBOSE"] === "true";
|
|
1874
|
-
}
|
|
1875
|
-
function verbose(text) {
|
|
1876
|
-
if (isVerbose()) {
|
|
1877
|
-
console.log(chalk.gray(`[verbose] ${text}`));
|
|
1878
|
-
}
|
|
2499
|
+
console.log(chalk2.dim(text));
|
|
1879
2500
|
}
|
|
1880
2501
|
|
|
1881
2502
|
// src/ui/spinner.ts
|
|
@@ -1906,104 +2527,200 @@ function isCI() {
|
|
|
1906
2527
|
|
|
1907
2528
|
// src/commands/init.ts
|
|
1908
2529
|
async function runInit(options) {
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
2530
|
+
if (options.json) {
|
|
2531
|
+
configureJsonOutput({
|
|
2532
|
+
outputFile: options.outputFile,
|
|
2533
|
+
noSecrets: options.noSecrets
|
|
2534
|
+
});
|
|
1912
2535
|
}
|
|
1913
|
-
if (options.
|
|
1914
|
-
|
|
1915
|
-
}
|
|
1916
|
-
spinner.start("Discovering project...");
|
|
2536
|
+
if (options.verbose) process.env["AUTOTEL_VERBOSE"] = "true";
|
|
2537
|
+
if (options.quiet) process.env["AUTOTEL_QUIET"] = "true";
|
|
1917
2538
|
const project = discoverProject(options.cwd);
|
|
1918
|
-
if (
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2539
|
+
if (project === null) {
|
|
2540
|
+
throw new AutotelError({
|
|
2541
|
+
type: "environment",
|
|
2542
|
+
code: AutotelErrorCodes.E_NO_PACKAGE_JSON,
|
|
2543
|
+
message: `No package.json found at or above ${options.cwd}`,
|
|
2544
|
+
fix: "cd into a directory with a package.json, or pass --cwd <path>",
|
|
2545
|
+
expected: { file: "package.json" }
|
|
2546
|
+
});
|
|
1922
2547
|
}
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
2548
|
+
if (options.json && options.outputFile !== void 0) {
|
|
2549
|
+
configureJsonOutput({
|
|
2550
|
+
outputFile: options.outputFile,
|
|
2551
|
+
outputRoot: project.packageRoot,
|
|
2552
|
+
noSecrets: options.noSecrets
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
let plan = null;
|
|
2556
|
+
if (options.plan !== void 0) {
|
|
2557
|
+
plan = readPlanFromFile(options.plan);
|
|
2558
|
+
} else if (options.input !== void 0) {
|
|
2559
|
+
plan = await readPlanFromInput(options.input);
|
|
2560
|
+
} else if (options.preset !== void 0) {
|
|
2561
|
+
plan = planFromQuickPreset(options.preset, project);
|
|
2562
|
+
} else if (!options.noDetect) {
|
|
2563
|
+
plan = await planFromDetection(project, options);
|
|
2564
|
+
}
|
|
2565
|
+
if (plan === null) {
|
|
2566
|
+
throw new AutotelError({
|
|
2567
|
+
type: "validation",
|
|
2568
|
+
code: AutotelErrorCodes.E_INVALID_FLAG,
|
|
2569
|
+
message: "No plan source available (--no-detect disables detection)",
|
|
2570
|
+
fix: "Drop --no-detect or pass --plan / --input / --preset"
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
const interactive = !options.yes && !options.noInteractive && !options.json && !isCI();
|
|
2574
|
+
if (interactive && options.plan === void 0 && options.input === void 0 && options.preset === void 0) {
|
|
2575
|
+
const confirmed = await confirmOrEditPlan(plan);
|
|
2576
|
+
if (confirmed === null) {
|
|
1935
2577
|
info("Aborted");
|
|
1936
|
-
|
|
1937
|
-
}
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
const quickPreset = getQuickPreset(options.preset);
|
|
1944
|
-
if (quickPreset) {
|
|
1945
|
-
info(`Using quick preset: ${quickPreset.name}`);
|
|
1946
|
-
const backendPreset = getPreset("backend", quickPreset.backend);
|
|
1947
|
-
if (backendPreset) {
|
|
1948
|
-
selectedPresets.push(backendPreset);
|
|
1949
|
-
}
|
|
1950
|
-
if (quickPreset.subscribers) {
|
|
1951
|
-
for (const sub of quickPreset.subscribers) {
|
|
1952
|
-
const subPreset = getPreset("subscriber", sub);
|
|
1953
|
-
if (subPreset) selectedPresets.push(subPreset);
|
|
1954
|
-
}
|
|
1955
|
-
}
|
|
1956
|
-
if (quickPreset.plugins) {
|
|
1957
|
-
for (const plug of quickPreset.plugins) {
|
|
1958
|
-
const plugPreset = getPreset("plugin", plug);
|
|
1959
|
-
if (plugPreset) selectedPresets.push(plugPreset);
|
|
1960
|
-
}
|
|
1961
|
-
}
|
|
1962
|
-
autoInstrumentations = quickPreset.autoInstrumentations;
|
|
2578
|
+
return;
|
|
2579
|
+
}
|
|
2580
|
+
plan = confirmed;
|
|
2581
|
+
}
|
|
2582
|
+
if (options.detectOnly) {
|
|
2583
|
+
if (options.json) {
|
|
2584
|
+
printJson({ ok: true, command: "autotel init", plan });
|
|
1963
2585
|
} else {
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
process.exit(1);
|
|
2586
|
+
info("Detection-only mode \u2014 no files written");
|
|
2587
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
1967
2588
|
}
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
if (options.json && options.dryRun) {
|
|
2592
|
+
printJson({ ok: true, command: "autotel init", plan, dryRun: true });
|
|
2593
|
+
return;
|
|
2594
|
+
}
|
|
2595
|
+
if (options.json && !options.dryRun) {
|
|
2596
|
+
const applied2 = applyPlan({ plan, project, options });
|
|
2597
|
+
printJson({ ok: true, command: "autotel init", plan, applied: applied2 });
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
if (options.dryRun) {
|
|
2601
|
+
heading("\nDry run \u2014 no files will be written\n");
|
|
2602
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
const applied = applyPlan({ plan, project, options });
|
|
2606
|
+
printApplySummary({ plan, applied, project, options });
|
|
2607
|
+
}
|
|
2608
|
+
function readPlanFromFile(filePath) {
|
|
2609
|
+
const content = readFileSafe(filePath);
|
|
2610
|
+
if (content === null) {
|
|
2611
|
+
throw new AutotelError({
|
|
2612
|
+
type: "io",
|
|
2613
|
+
code: AutotelErrorCodes.E_READ_FAILED,
|
|
2614
|
+
message: `Could not read plan file: ${filePath}`
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
try {
|
|
2618
|
+
return parsePlan(JSON.parse(content));
|
|
2619
|
+
} catch (error2) {
|
|
2620
|
+
if (error2 instanceof AutotelError) throw error2;
|
|
2621
|
+
throw new AutotelError({
|
|
2622
|
+
type: "validation",
|
|
2623
|
+
code: AutotelErrorCodes.E_INVALID_PLAN,
|
|
2624
|
+
message: `Plan file is not valid JSON: ${error2.message}`
|
|
2625
|
+
});
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
async function readPlanFromInput(input2) {
|
|
2629
|
+
if (input2 === "-") {
|
|
2630
|
+
const chunks = [];
|
|
2631
|
+
for await (const chunk of process.stdin) {
|
|
2632
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
1973
2633
|
}
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2634
|
+
const content = Buffer.concat(chunks).toString("utf8");
|
|
2635
|
+
try {
|
|
2636
|
+
return parsePlan(JSON.parse(content));
|
|
2637
|
+
} catch (error2) {
|
|
2638
|
+
if (error2 instanceof AutotelError) throw error2;
|
|
2639
|
+
throw new AutotelError({
|
|
2640
|
+
type: "validation",
|
|
2641
|
+
code: AutotelErrorCodes.E_INVALID_INPUT,
|
|
2642
|
+
message: `stdin did not contain valid JSON: ${error2.message}`
|
|
2643
|
+
});
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
return readPlanFromFile(input2);
|
|
2647
|
+
}
|
|
2648
|
+
function planFromQuickPreset(slug, project) {
|
|
2649
|
+
if (project === null) {
|
|
2650
|
+
throw new AutotelError({
|
|
2651
|
+
type: "environment",
|
|
2652
|
+
code: AutotelErrorCodes.E_NO_PACKAGE_JSON,
|
|
2653
|
+
message: "project required"
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
const quick = getQuickPreset(slug);
|
|
2657
|
+
if (quick === void 0) {
|
|
2658
|
+
throw new AutotelError({
|
|
2659
|
+
type: "validation",
|
|
2660
|
+
code: AutotelErrorCodes.E_UNKNOWN_PRESET,
|
|
2661
|
+
message: `Unknown preset: ${slug}`,
|
|
2662
|
+
fix: "Run `autotel commands --json` to see available presets"
|
|
2663
|
+
});
|
|
2664
|
+
}
|
|
2665
|
+
const presets = [quick.backend];
|
|
2666
|
+
if (quick.subscribers) presets.push(...quick.subscribers);
|
|
2667
|
+
if (quick.plugins) presets.push(...quick.plugins);
|
|
2668
|
+
const { plan } = buildPlanFromDetection({
|
|
2669
|
+
project,
|
|
2670
|
+
detection: {
|
|
2671
|
+
packages: [],
|
|
2672
|
+
presets,
|
|
2673
|
+
primaryLogger: quick.logging === "pino" ? "pino" : null,
|
|
2674
|
+
autoInstrumentLoggers: [],
|
|
2675
|
+
autoInstrumentedDeps: [],
|
|
2676
|
+
backend: { slug: quick.backend, source: "default" },
|
|
2677
|
+
platform: null
|
|
2001
2678
|
}
|
|
2679
|
+
});
|
|
2680
|
+
return plan;
|
|
2681
|
+
}
|
|
2682
|
+
async function planFromDetection(project, options) {
|
|
2683
|
+
if (project === null) {
|
|
2684
|
+
throw new AutotelError({
|
|
2685
|
+
type: "environment",
|
|
2686
|
+
code: AutotelErrorCodes.E_NO_PACKAGE_JSON,
|
|
2687
|
+
message: "project required"
|
|
2688
|
+
});
|
|
2689
|
+
}
|
|
2690
|
+
let envConsent = options.scanEnv;
|
|
2691
|
+
if (!envConsent && envFilesRequireConsent(project.packageRoot) && !options.yes && !options.noInteractive && !options.json && !isCI()) {
|
|
2692
|
+
envConsent = await promptConfirm(
|
|
2693
|
+
`Found a .env file. Read its keys to help detect the backend? (values are never read)`,
|
|
2694
|
+
false
|
|
2695
|
+
);
|
|
2696
|
+
}
|
|
2697
|
+
const detection = detectInProject({ project, envConsent });
|
|
2698
|
+
const { plan } = buildPlanFromDetection({ project, detection });
|
|
2699
|
+
return plan;
|
|
2700
|
+
}
|
|
2701
|
+
function applyPlan(args) {
|
|
2702
|
+
const { plan, project, options } = args;
|
|
2703
|
+
const result = {
|
|
2704
|
+
wroteFiles: [],
|
|
2705
|
+
ranInstalls: [],
|
|
2706
|
+
printedInstalls: [],
|
|
2707
|
+
installErrors: []
|
|
2708
|
+
};
|
|
2709
|
+
const presets = [];
|
|
2710
|
+
for (const slug of plan.presets) {
|
|
2711
|
+
const p = resolvePreset(slug);
|
|
2712
|
+
if (p !== null) presets.push(p);
|
|
2002
2713
|
}
|
|
2003
2714
|
const codeFile = createCodeFile();
|
|
2004
2715
|
addImport(codeFile, { source: "autotel/register", sideEffect: true });
|
|
2005
2716
|
addImport(codeFile, { source: "autotel", specifiers: ["init"] });
|
|
2006
|
-
|
|
2717
|
+
if (plan.detected?.primaryLogger === "pino") {
|
|
2718
|
+
setPinoLogger(codeFile);
|
|
2719
|
+
}
|
|
2720
|
+
for (const l of plan.detected?.autoInstrumentLoggers ?? []) {
|
|
2721
|
+
addAutoInstrumentationLogger(codeFile, l);
|
|
2722
|
+
}
|
|
2723
|
+
for (const preset of presets) {
|
|
2007
2724
|
for (const imp of preset.imports) {
|
|
2008
2725
|
const section = preset.type === "backend" || preset.type === "platform" ? "backend" : preset.type === "plugin" ? "plugin" : preset.type === "subscriber" ? "subscriber" : void 0;
|
|
2009
2726
|
addImport(codeFile, imp, section);
|
|
@@ -2016,130 +2733,143 @@ async function runInit(options) {
|
|
|
2016
2733
|
addPluginInit(codeFile, preset.configBlock.code);
|
|
2017
2734
|
}
|
|
2018
2735
|
}
|
|
2019
|
-
if (
|
|
2736
|
+
if (codeFile.backendConfig === null) {
|
|
2020
2737
|
setBackendConfig(codeFile, "// Local/console mode - no backend configured");
|
|
2021
2738
|
}
|
|
2022
|
-
const
|
|
2023
|
-
const
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2739
|
+
const newContent = renderCodeFile(codeFile);
|
|
2740
|
+
const instrumentationPath = resolveInstrumentationPath(project);
|
|
2741
|
+
const envExamplePath = path9.join(project.packageRoot, ".env.example");
|
|
2742
|
+
const existing = readFileSafe(instrumentationPath);
|
|
2743
|
+
if (existing !== null) {
|
|
2744
|
+
const parsed = parseInstrumentation(existing);
|
|
2745
|
+
if (!parsed.cliOwned && !options.force) {
|
|
2746
|
+
throw new AutotelError({
|
|
2747
|
+
type: "conflict",
|
|
2748
|
+
code: AutotelErrorCodes.E_EXISTING_CONFIG,
|
|
2749
|
+
message: `Hand-edited instrumentation file at ${instrumentationPath} (no CLI markers found)`,
|
|
2750
|
+
fix: "Pass --force to overwrite (creates a .bak backup) or remove the file",
|
|
2751
|
+
expected: { path: instrumentationPath }
|
|
2752
|
+
});
|
|
2753
|
+
}
|
|
2754
|
+
const addedImports = diffImportSources(
|
|
2755
|
+
parsed,
|
|
2756
|
+
[
|
|
2757
|
+
...codeFile.imports,
|
|
2758
|
+
...codeFile.backendImports,
|
|
2759
|
+
...codeFile.pluginImports,
|
|
2760
|
+
...codeFile.subscriberImports,
|
|
2761
|
+
...codeFile.loggerImports
|
|
2762
|
+
].map((i) => i.source)
|
|
2763
|
+
);
|
|
2764
|
+
const addedAuto = diffAutoInstrumentations(
|
|
2765
|
+
parsed,
|
|
2766
|
+
codeFile.autoInstrumentations
|
|
2767
|
+
);
|
|
2768
|
+
const contentChanged = existing !== newContent;
|
|
2769
|
+
if (addedImports.length === 0 && addedAuto.length === 0 && parsed.cliOwned && !contentChanged) {
|
|
2770
|
+
result.wroteFiles.push(
|
|
2771
|
+
`${path9.relative(project.cwd, instrumentationPath)} (no changes)`
|
|
2772
|
+
);
|
|
2773
|
+
} else {
|
|
2774
|
+
atomicWrite(instrumentationPath, newContent, {
|
|
2775
|
+
root: project.packageRoot,
|
|
2776
|
+
backup: true
|
|
2777
|
+
});
|
|
2778
|
+
result.wroteFiles.push(path9.relative(project.cwd, instrumentationPath));
|
|
2779
|
+
}
|
|
2780
|
+
} else {
|
|
2781
|
+
atomicWrite(instrumentationPath, newContent, {
|
|
2782
|
+
root: project.packageRoot
|
|
2783
|
+
});
|
|
2784
|
+
result.wroteFiles.push(path9.relative(project.cwd, instrumentationPath));
|
|
2785
|
+
}
|
|
2027
2786
|
const envVars = [];
|
|
2028
|
-
for (const
|
|
2029
|
-
envVars.push(...
|
|
2787
|
+
for (const p of presets) {
|
|
2788
|
+
envVars.push(...p.env.required, ...p.env.optional);
|
|
2030
2789
|
}
|
|
2031
2790
|
const envExampleContent = generateEnvExample(envVars);
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
}
|
|
2056
|
-
process.exit(0);
|
|
2057
|
-
}
|
|
2058
|
-
spinner.start("Writing instrumentation file...");
|
|
2059
|
-
const { backupPath: instrBackup } = atomicWrite(instrumentationPath, instrumentationContent, {
|
|
2060
|
-
root: project.packageRoot,
|
|
2061
|
-
backup: options.force
|
|
2062
|
-
});
|
|
2063
|
-
if (instrBackup) {
|
|
2064
|
-
verbose(`Backup created: ${instrBackup}`);
|
|
2065
|
-
}
|
|
2066
|
-
spinner.succeed(`Wrote ${path6.relative(project.cwd, instrumentationPath)}`);
|
|
2067
|
-
if (envExampleContent && !fileExists(envExamplePath)) {
|
|
2068
|
-
spinner.start("Writing .env.example...");
|
|
2069
|
-
atomicWrite(envExamplePath, envExampleContent, { root: project.packageRoot });
|
|
2070
|
-
spinner.succeed(`Wrote ${path6.relative(project.cwd, envExamplePath)}`);
|
|
2071
|
-
}
|
|
2072
|
-
const prodPkgs = getProdPackages(depPlan);
|
|
2073
|
-
const devPkgs = getDevPackages(depPlan);
|
|
2074
|
-
if (!options.noInstall && (prodPkgs.length > 0 || devPkgs.length > 0)) {
|
|
2075
|
-
if (prodPkgs.length > 0) {
|
|
2076
|
-
const cmd = getInstallCommand(project.packageManager, prodPkgs);
|
|
2077
|
-
if (options.printInstallCmd) {
|
|
2078
|
-
info(`Install command: ${cmd}`);
|
|
2079
|
-
} else {
|
|
2080
|
-
spinner.start("Installing dependencies...");
|
|
2791
|
+
if (envExampleContent.length > 0 && !fileExists(envExamplePath)) {
|
|
2792
|
+
atomicWrite(envExamplePath, envExampleContent, {
|
|
2793
|
+
root: project.packageRoot
|
|
2794
|
+
});
|
|
2795
|
+
result.wroteFiles.push(path9.relative(project.cwd, envExamplePath));
|
|
2796
|
+
}
|
|
2797
|
+
const prod = plan.packagesToInstall.prod;
|
|
2798
|
+
const dev = plan.packagesToInstall.dev;
|
|
2799
|
+
if (prod.length > 0 || dev.length > 0) {
|
|
2800
|
+
if (options.noInstall || options.printInstallCmd) {
|
|
2801
|
+
if (prod.length > 0) {
|
|
2802
|
+
result.printedInstalls.push(
|
|
2803
|
+
getInstallCommand(project.packageManager, prod)
|
|
2804
|
+
);
|
|
2805
|
+
}
|
|
2806
|
+
if (dev.length > 0) {
|
|
2807
|
+
result.printedInstalls.push(
|
|
2808
|
+
getInstallCommand(project.packageManager, dev, { dev: true })
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
} else {
|
|
2812
|
+
if (prod.length > 0) {
|
|
2813
|
+
const cmd = getInstallCommand(project.packageManager, prod);
|
|
2081
2814
|
try {
|
|
2082
2815
|
execSync(cmd, { cwd: project.packageRoot, stdio: "pipe" });
|
|
2083
|
-
|
|
2816
|
+
result.ranInstalls.push(cmd);
|
|
2084
2817
|
} catch {
|
|
2085
|
-
|
|
2086
|
-
error(`Run manually: ${cmd}`);
|
|
2818
|
+
result.installErrors.push(cmd);
|
|
2087
2819
|
}
|
|
2088
2820
|
}
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
info(`Install command (dev): ${cmd}`);
|
|
2094
|
-
} else {
|
|
2095
|
-
spinner.start("Installing dev dependencies...");
|
|
2821
|
+
if (dev.length > 0) {
|
|
2822
|
+
const cmd = getInstallCommand(project.packageManager, dev, {
|
|
2823
|
+
dev: true
|
|
2824
|
+
});
|
|
2096
2825
|
try {
|
|
2097
2826
|
execSync(cmd, { cwd: project.packageRoot, stdio: "pipe" });
|
|
2098
|
-
|
|
2827
|
+
result.ranInstalls.push(cmd);
|
|
2099
2828
|
} catch {
|
|
2100
|
-
|
|
2101
|
-
error(`Run manually: ${cmd}`);
|
|
2829
|
+
result.installErrors.push(cmd);
|
|
2102
2830
|
}
|
|
2103
2831
|
}
|
|
2104
2832
|
}
|
|
2105
|
-
} else if (options.noInstall && (prodPkgs.length > 0 || devPkgs.length > 0)) {
|
|
2106
|
-
info("Skipping installation (--no-install)");
|
|
2107
|
-
if (prodPkgs.length > 0) {
|
|
2108
|
-
const cmd = getInstallCommand(project.packageManager, prodPkgs);
|
|
2109
|
-
dim(`Run: ${cmd}`);
|
|
2110
|
-
}
|
|
2111
|
-
if (devPkgs.length > 0) {
|
|
2112
|
-
const cmd = getInstallCommand(project.packageManager, devPkgs, { dev: true });
|
|
2113
|
-
dim(`Run: ${cmd}`);
|
|
2114
|
-
}
|
|
2115
2833
|
}
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2834
|
+
return result;
|
|
2835
|
+
}
|
|
2836
|
+
function resolveInstrumentationPath(project) {
|
|
2837
|
+
const srcDir = path9.join(project.packageRoot, "src");
|
|
2838
|
+
const hasSrcDir = fs4.existsSync(srcDir) || fileExists(path9.join(project.packageRoot, "src", "index.ts")) || fileExists(path9.join(project.packageRoot, "src", "index.js"));
|
|
2839
|
+
const dir = hasSrcDir ? srcDir : project.packageRoot;
|
|
2840
|
+
const ext = project.hasTypeScript ? "mts" : "mjs";
|
|
2841
|
+
return path9.join(dir, `instrumentation.${ext}`);
|
|
2842
|
+
}
|
|
2843
|
+
function resolvePreset(slug) {
|
|
2844
|
+
for (const type of ["backend", "subscriber", "plugin", "platform"]) {
|
|
2845
|
+
const p = getPreset(type, slug);
|
|
2846
|
+
if (p !== void 0) return p;
|
|
2125
2847
|
}
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2848
|
+
return null;
|
|
2849
|
+
}
|
|
2850
|
+
function printApplySummary(args) {
|
|
2851
|
+
const { applied, plan, project } = args;
|
|
2852
|
+
if (applied.wroteFiles.length > 0) {
|
|
2853
|
+
success(`Wrote: ${applied.wroteFiles.join(", ")}`);
|
|
2130
2854
|
}
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2855
|
+
for (const cmd of applied.ranInstalls) {
|
|
2856
|
+
info(`Installed: ${cmd}`);
|
|
2857
|
+
}
|
|
2858
|
+
for (const cmd of applied.printedInstalls) {
|
|
2859
|
+
dim(`Run: ${cmd}`);
|
|
2860
|
+
}
|
|
2861
|
+
for (const cmd of applied.installErrors) {
|
|
2862
|
+
error(`Install failed \u2014 run manually: ${cmd}`);
|
|
2863
|
+
}
|
|
2864
|
+
if (plan.nextSteps.length > 0) {
|
|
2138
2865
|
console.log("\nNext steps:");
|
|
2139
|
-
for (const step of
|
|
2866
|
+
for (const step of plan.nextSteps) {
|
|
2140
2867
|
console.log(` - ${step}`);
|
|
2141
2868
|
}
|
|
2142
2869
|
}
|
|
2870
|
+
const pmInfo = project.workspace.isMonorepo ? `${project.packageManager} workspace, package root ${project.packageRoot}` : project.packageManager;
|
|
2871
|
+
console.log(`
|
|
2872
|
+
${formatPackageManagerInfo ? formatPackageManagerInfo(project.packageManager, project.lockfilePath) : pmInfo}`);
|
|
2143
2873
|
}
|
|
2144
2874
|
|
|
2145
2875
|
// src/lib/dependency-auditor.ts
|
|
@@ -2181,7 +2911,7 @@ function getAutotelInfo(packageJson) {
|
|
|
2181
2911
|
}
|
|
2182
2912
|
|
|
2183
2913
|
// src/lib/esm-checker.ts
|
|
2184
|
-
import * as
|
|
2914
|
+
import * as path10 from "path";
|
|
2185
2915
|
function checkRegisterImportOrder(content) {
|
|
2186
2916
|
const lines = content.split("\n");
|
|
2187
2917
|
let registerLine = null;
|
|
@@ -2238,7 +2968,7 @@ function checkEsmHook(project) {
|
|
|
2238
2968
|
if (result.found && !result.isFirst) {
|
|
2239
2969
|
return {
|
|
2240
2970
|
status: "warn",
|
|
2241
|
-
message: `autotel/register import found but not first in ${
|
|
2971
|
+
message: `autotel/register import found but not first in ${path10.basename(entrypoint)}:${result.lineNumber}`,
|
|
2242
2972
|
details: [
|
|
2243
2973
|
"autotel/register must be the first import for instrumentation to work",
|
|
2244
2974
|
"Move it to the top of the file, before any other imports"
|
|
@@ -2248,7 +2978,7 @@ function checkEsmHook(project) {
|
|
|
2248
2978
|
if (result.found && result.isFirst) {
|
|
2249
2979
|
return {
|
|
2250
2980
|
status: "ok",
|
|
2251
|
-
message: `autotel/register correctly imported first in ${
|
|
2981
|
+
message: `autotel/register correctly imported first in ${path10.basename(entrypoint)}`
|
|
2252
2982
|
};
|
|
2253
2983
|
}
|
|
2254
2984
|
}
|
|
@@ -2263,8 +2993,8 @@ function checkEsmHook(project) {
|
|
|
2263
2993
|
}
|
|
2264
2994
|
|
|
2265
2995
|
// src/lib/logger-checker.ts
|
|
2266
|
-
import * as
|
|
2267
|
-
import * as
|
|
2996
|
+
import * as path11 from "path";
|
|
2997
|
+
import * as fs5 from "fs";
|
|
2268
2998
|
var LOGGER_INSTRUMENTATION = {
|
|
2269
2999
|
winston: "@opentelemetry/instrumentation-winston",
|
|
2270
3000
|
bunyan: "@opentelemetry/instrumentation-bunyan",
|
|
@@ -2294,17 +3024,17 @@ function extractAutoInstrumentations(content) {
|
|
|
2294
3024
|
}
|
|
2295
3025
|
function findSourceFiles(packageRoot) {
|
|
2296
3026
|
const sourceFiles = [];
|
|
2297
|
-
const srcDir =
|
|
3027
|
+
const srcDir = path11.join(packageRoot, "src");
|
|
2298
3028
|
const dirsToCheck = [packageRoot, srcDir].filter(
|
|
2299
|
-
(dir) =>
|
|
3029
|
+
(dir) => fs5.existsSync(dir) && fs5.statSync(dir).isDirectory()
|
|
2300
3030
|
);
|
|
2301
3031
|
for (const dir of dirsToCheck) {
|
|
2302
|
-
const files =
|
|
3032
|
+
const files = fs5.readdirSync(dir, { recursive: true });
|
|
2303
3033
|
for (const file of files) {
|
|
2304
3034
|
if (typeof file !== "string") continue;
|
|
2305
|
-
const filePath =
|
|
3035
|
+
const filePath = path11.join(dir, file);
|
|
2306
3036
|
try {
|
|
2307
|
-
if (
|
|
3037
|
+
if (fs5.statSync(filePath).isFile() && /\.(ts|js|mts|mjs|tsx|jsx)$/.test(file)) {
|
|
2308
3038
|
sourceFiles.push(filePath);
|
|
2309
3039
|
}
|
|
2310
3040
|
} catch {
|
|
@@ -3028,11 +3758,11 @@ ${presetType} presets:
|
|
|
3028
3758
|
}
|
|
3029
3759
|
|
|
3030
3760
|
// src/commands/codemod-trace.ts
|
|
3031
|
-
import * as
|
|
3761
|
+
import * as path13 from "path";
|
|
3032
3762
|
import { glob as glob2 } from "glob";
|
|
3033
3763
|
|
|
3034
3764
|
// src/lib/codemod-trace.ts
|
|
3035
|
-
import * as
|
|
3765
|
+
import * as path12 from "path";
|
|
3036
3766
|
import { Project, SyntaxKind } from "ts-morph";
|
|
3037
3767
|
var TRACE_IMPORT_MODULE = "autotel";
|
|
3038
3768
|
function hasTraceImport(sourceFile) {
|
|
@@ -3055,8 +3785,8 @@ function addTraceImport(sourceFile) {
|
|
|
3055
3785
|
});
|
|
3056
3786
|
}
|
|
3057
3787
|
function expandNamePattern(pattern, name, filePath, cwd) {
|
|
3058
|
-
const file =
|
|
3059
|
-
const relPath =
|
|
3788
|
+
const file = path12.basename(filePath, path12.extname(filePath));
|
|
3789
|
+
const relPath = path12.relative(cwd, filePath).replaceAll("\\", "/");
|
|
3060
3790
|
return pattern.replaceAll("{name}", name).replaceAll("{file}", file).replaceAll("{path}", relPath);
|
|
3061
3791
|
}
|
|
3062
3792
|
function getSpanName(name, filePath, options) {
|
|
@@ -3292,28 +4022,28 @@ var GLOB_META = /[*?[\]]/;
|
|
|
3292
4022
|
async function resolveCodemodFiles(pathArg, cwd) {
|
|
3293
4023
|
const isGlob = GLOB_META.test(pathArg);
|
|
3294
4024
|
if (!isGlob) {
|
|
3295
|
-
const absolute =
|
|
4025
|
+
const absolute = path13.isAbsolute(pathArg) ? pathArg : path13.resolve(cwd, pathArg);
|
|
3296
4026
|
if (fileExists(absolute)) {
|
|
3297
|
-
const ext =
|
|
4027
|
+
const ext = path13.extname(absolute);
|
|
3298
4028
|
if (CODEMOD_EXTENSIONS.has(ext) && !absolute.endsWith(".d.ts")) {
|
|
3299
4029
|
return [absolute];
|
|
3300
4030
|
}
|
|
3301
4031
|
return [];
|
|
3302
4032
|
}
|
|
3303
4033
|
}
|
|
3304
|
-
const pattern =
|
|
4034
|
+
const pattern = path13.isAbsolute(pathArg) ? pathArg : path13.join(cwd, pathArg);
|
|
3305
4035
|
const matches = await glob2(pattern, {
|
|
3306
4036
|
cwd,
|
|
3307
4037
|
absolute: true,
|
|
3308
4038
|
ignore: ["**/node_modules/**", "**/*.d.ts"]
|
|
3309
4039
|
});
|
|
3310
4040
|
return matches.filter((f) => {
|
|
3311
|
-
const ext =
|
|
4041
|
+
const ext = path13.extname(f);
|
|
3312
4042
|
return CODEMOD_EXTENSIONS.has(ext) && !f.endsWith(".d.ts");
|
|
3313
4043
|
});
|
|
3314
4044
|
}
|
|
3315
4045
|
async function runCodemodTrace(options) {
|
|
3316
|
-
const { path: pathArg, cwd, dryRun, namePattern, skip, printFiles, verbose
|
|
4046
|
+
const { path: pathArg, cwd, dryRun, namePattern, skip, printFiles, verbose, quiet } = options;
|
|
3317
4047
|
const files = await resolveCodemodFiles(pathArg, cwd);
|
|
3318
4048
|
if (files.length === 0) {
|
|
3319
4049
|
if (!quiet) {
|
|
@@ -3329,7 +4059,7 @@ async function runCodemodTrace(options) {
|
|
|
3329
4059
|
for (const filePath of files) {
|
|
3330
4060
|
const content = readFileSafe(filePath);
|
|
3331
4061
|
if (content === null) {
|
|
3332
|
-
if (
|
|
4062
|
+
if (verbose) dim(`Skip ${filePath} (unreadable)`);
|
|
3333
4063
|
continue;
|
|
3334
4064
|
}
|
|
3335
4065
|
let result;
|
|
@@ -3339,17 +4069,17 @@ async function runCodemodTrace(options) {
|
|
|
3339
4069
|
if (!quiet) {
|
|
3340
4070
|
error(`Failed to transform ${filePath}: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
3341
4071
|
}
|
|
3342
|
-
if (
|
|
4072
|
+
if (verbose && error2 instanceof Error && error2.stack) dim(error2.stack);
|
|
3343
4073
|
process.exitCode = 1;
|
|
3344
4074
|
continue;
|
|
3345
4075
|
}
|
|
3346
|
-
const relativePath =
|
|
4076
|
+
const relativePath = path13.relative(cwd, filePath);
|
|
3347
4077
|
if (result.changed) {
|
|
3348
4078
|
totalWrapped += result.wrappedCount;
|
|
3349
4079
|
totalChanged += 1;
|
|
3350
4080
|
if (!dryRun) {
|
|
3351
|
-
const
|
|
3352
|
-
|
|
4081
|
+
const fs9 = await import("fs");
|
|
4082
|
+
fs9.writeFileSync(filePath, result.modified, "utf8");
|
|
3353
4083
|
}
|
|
3354
4084
|
}
|
|
3355
4085
|
const showSummary = printFiles || dryRun || result.changed;
|
|
@@ -3368,25 +4098,1718 @@ async function runCodemodTrace(options) {
|
|
|
3368
4098
|
}
|
|
3369
4099
|
}
|
|
3370
4100
|
|
|
4101
|
+
// src/commands/schema.ts
|
|
4102
|
+
import * as fs6 from "fs";
|
|
4103
|
+
import * as path14 from "path";
|
|
4104
|
+
import { fileURLToPath } from "url";
|
|
4105
|
+
|
|
4106
|
+
// src/lib/manifest.ts
|
|
4107
|
+
var GLOBAL_FLAGS = [
|
|
4108
|
+
{
|
|
4109
|
+
name: "--cwd",
|
|
4110
|
+
takesValue: true,
|
|
4111
|
+
description: "Target directory (default: current working directory)"
|
|
4112
|
+
},
|
|
4113
|
+
{ name: "--verbose", description: "Show detailed output" },
|
|
4114
|
+
{ name: "--quiet", description: "Only show warnings and errors" }
|
|
4115
|
+
];
|
|
4116
|
+
var AGENT_FLAGS = [
|
|
4117
|
+
{
|
|
4118
|
+
name: "--json",
|
|
4119
|
+
description: "Emit machine-readable JSON instead of human output"
|
|
4120
|
+
},
|
|
4121
|
+
{
|
|
4122
|
+
name: "--output-file",
|
|
4123
|
+
takesValue: true,
|
|
4124
|
+
description: "Persist the first JSON payload to this path"
|
|
4125
|
+
},
|
|
4126
|
+
{
|
|
4127
|
+
name: "--no-secrets-in-output",
|
|
4128
|
+
description: "Redact secret-shaped values (also via AUTOTEL_NO_SECRETS=1 / AGENT_SANDBOX=1)"
|
|
4129
|
+
},
|
|
4130
|
+
{
|
|
4131
|
+
name: "--no-interactive",
|
|
4132
|
+
description: "Never prompt; fail fast if input is required"
|
|
4133
|
+
}
|
|
4134
|
+
];
|
|
4135
|
+
var COMMANDS = [
|
|
4136
|
+
{
|
|
4137
|
+
name: "init",
|
|
4138
|
+
description: "Initialise autotel in your project",
|
|
4139
|
+
flags: [
|
|
4140
|
+
...GLOBAL_FLAGS,
|
|
4141
|
+
...AGENT_FLAGS,
|
|
4142
|
+
{ name: "--dry-run", description: "Print what would be done; write nothing" },
|
|
4143
|
+
{ name: "--no-install", description: "Generate files only, skip package installation" },
|
|
4144
|
+
{ name: "--print-install-cmd", description: "Output install command without running it" },
|
|
4145
|
+
{ name: "--yes", alias: "-y", description: "Auto-apply detected items; no prompts" },
|
|
4146
|
+
{ name: "--preset", takesValue: true, description: "Use a named quick preset" },
|
|
4147
|
+
{ name: "--force", description: "Overwrite existing config (creates backup)" },
|
|
4148
|
+
{ name: "--workspace-root", description: "Install at workspace root, not package root" },
|
|
4149
|
+
{ name: "--no-detect", description: "Skip auto-detection of installed deps" },
|
|
4150
|
+
{ name: "--detect-only", description: "Run detection and print the proposal; write nothing" },
|
|
4151
|
+
{ name: "--plan", takesValue: true, description: "Read a pre-built InitPlan JSON from this path and apply it" },
|
|
4152
|
+
{ name: "--input", takesValue: true, description: "Read InitPlan JSON from stdin (-) or a file" },
|
|
4153
|
+
{ name: "--scan-env", description: "Consent to reading uncommitted .env files for backend detection" }
|
|
4154
|
+
],
|
|
4155
|
+
mutating: true,
|
|
4156
|
+
network: true,
|
|
4157
|
+
writesFiles: true,
|
|
4158
|
+
supportsDryRun: true,
|
|
4159
|
+
requiresPackageJson: true,
|
|
4160
|
+
mayReadEnv: true,
|
|
4161
|
+
supportsJson: true,
|
|
4162
|
+
examples: [
|
|
4163
|
+
{ description: "Interactive setup with detection", command: "autotel init" },
|
|
4164
|
+
{ description: "Non-interactive, apply all detected items", command: "autotel init --yes" },
|
|
4165
|
+
{ description: "Preview as JSON without writing", command: "autotel init --json --dry-run" },
|
|
4166
|
+
{ description: "Detection only", command: "autotel init --detect-only --json" }
|
|
4167
|
+
]
|
|
4168
|
+
},
|
|
4169
|
+
{
|
|
4170
|
+
name: "doctor",
|
|
4171
|
+
description: "Run diagnostics on your autotel setup",
|
|
4172
|
+
flags: [
|
|
4173
|
+
...GLOBAL_FLAGS,
|
|
4174
|
+
...AGENT_FLAGS,
|
|
4175
|
+
{ name: "--fix", description: "Auto-fix resolvable issues" },
|
|
4176
|
+
{ name: "--list-checks", description: "List all available checks" },
|
|
4177
|
+
{ name: "--env-file", takesValue: true, description: "Path to env file to check" }
|
|
4178
|
+
],
|
|
4179
|
+
mutating: false,
|
|
4180
|
+
network: false,
|
|
4181
|
+
writesFiles: false,
|
|
4182
|
+
supportsDryRun: false,
|
|
4183
|
+
requiresPackageJson: true,
|
|
4184
|
+
mayReadEnv: true,
|
|
4185
|
+
supportsJson: true
|
|
4186
|
+
},
|
|
4187
|
+
{
|
|
4188
|
+
name: "add",
|
|
4189
|
+
description: "Add a backend, subscriber, plugin, or platform incrementally",
|
|
4190
|
+
args: [
|
|
4191
|
+
{ name: "type", required: false, description: "Preset type" },
|
|
4192
|
+
{ name: "name", required: false, description: "Preset name" }
|
|
4193
|
+
],
|
|
4194
|
+
flags: [
|
|
4195
|
+
...GLOBAL_FLAGS,
|
|
4196
|
+
...AGENT_FLAGS,
|
|
4197
|
+
{ name: "--list", description: "List available presets for the given type" },
|
|
4198
|
+
{ name: "--dry-run", description: "Print what would be done" },
|
|
4199
|
+
{ name: "--no-install", description: "Generate files only" },
|
|
4200
|
+
{ name: "--print-install-cmd", description: "Output install command without running" },
|
|
4201
|
+
{ name: "--yes", alias: "-y", description: "Accept defaults" },
|
|
4202
|
+
{ name: "--force", description: "Overwrite non-CLI-owned config" },
|
|
4203
|
+
{ name: "--workspace-root", description: "Install at workspace root" }
|
|
4204
|
+
],
|
|
4205
|
+
mutating: true,
|
|
4206
|
+
network: true,
|
|
4207
|
+
writesFiles: true,
|
|
4208
|
+
supportsDryRun: true,
|
|
4209
|
+
requiresPackageJson: true,
|
|
4210
|
+
mayReadEnv: false,
|
|
4211
|
+
supportsJson: true
|
|
4212
|
+
},
|
|
4213
|
+
{
|
|
4214
|
+
name: "codemod trace",
|
|
4215
|
+
description: "Wrap functions in trace() with span name from function/variable/method name",
|
|
4216
|
+
args: [{ name: "path", required: true, description: "File path or glob" }],
|
|
4217
|
+
flags: [
|
|
4218
|
+
...GLOBAL_FLAGS,
|
|
4219
|
+
{ name: "--dry-run", description: "Print changes without writing files" },
|
|
4220
|
+
{ name: "--name-pattern", takesValue: true, description: "Span name template" },
|
|
4221
|
+
{ name: "--skip", takesValue: true, description: "Skip functions whose name matches (repeatable)" },
|
|
4222
|
+
{ name: "--print-files", description: "Print per-file summary" }
|
|
4223
|
+
],
|
|
4224
|
+
mutating: true,
|
|
4225
|
+
network: false,
|
|
4226
|
+
writesFiles: true,
|
|
4227
|
+
supportsDryRun: true,
|
|
4228
|
+
requiresPackageJson: false,
|
|
4229
|
+
mayReadEnv: false,
|
|
4230
|
+
supportsJson: false
|
|
4231
|
+
},
|
|
4232
|
+
{
|
|
4233
|
+
name: "schema",
|
|
4234
|
+
description: "Print the CLI manifest as JSON (agent discovery)",
|
|
4235
|
+
flags: [
|
|
4236
|
+
{ name: "--json", description: "Always JSON (this command is JSON-only)" }
|
|
4237
|
+
],
|
|
4238
|
+
mutating: false,
|
|
4239
|
+
network: false,
|
|
4240
|
+
writesFiles: false,
|
|
4241
|
+
supportsDryRun: false,
|
|
4242
|
+
requiresPackageJson: false,
|
|
4243
|
+
mayReadEnv: false,
|
|
4244
|
+
supportsJson: true
|
|
4245
|
+
},
|
|
4246
|
+
{
|
|
4247
|
+
name: "schema errors",
|
|
4248
|
+
description: "Print error envelope shape + AUTOTEL_E_* enum",
|
|
4249
|
+
flags: [{ name: "--json", description: "Always JSON" }],
|
|
4250
|
+
mutating: false,
|
|
4251
|
+
network: false,
|
|
4252
|
+
writesFiles: false,
|
|
4253
|
+
supportsDryRun: false,
|
|
4254
|
+
requiresPackageJson: false,
|
|
4255
|
+
mayReadEnv: false,
|
|
4256
|
+
supportsJson: true
|
|
4257
|
+
},
|
|
4258
|
+
{
|
|
4259
|
+
name: "schema outputs",
|
|
4260
|
+
description: "Print JSON output shapes per command",
|
|
4261
|
+
flags: [{ name: "--json", description: "Always JSON" }],
|
|
4262
|
+
mutating: false,
|
|
4263
|
+
network: false,
|
|
4264
|
+
writesFiles: false,
|
|
4265
|
+
supportsDryRun: false,
|
|
4266
|
+
requiresPackageJson: false,
|
|
4267
|
+
mayReadEnv: false,
|
|
4268
|
+
supportsJson: true
|
|
4269
|
+
},
|
|
4270
|
+
{
|
|
4271
|
+
name: "commands",
|
|
4272
|
+
description: "Print compact tool-style command listing",
|
|
4273
|
+
flags: [{ name: "--json", description: "Always JSON" }],
|
|
4274
|
+
mutating: false,
|
|
4275
|
+
network: false,
|
|
4276
|
+
writesFiles: false,
|
|
4277
|
+
supportsDryRun: false,
|
|
4278
|
+
requiresPackageJson: false,
|
|
4279
|
+
mayReadEnv: false,
|
|
4280
|
+
supportsJson: true
|
|
4281
|
+
},
|
|
4282
|
+
{
|
|
4283
|
+
name: "examples",
|
|
4284
|
+
description: "Print copy-pasteable examples for a command",
|
|
4285
|
+
args: [
|
|
4286
|
+
{ name: "command", required: false, description: "Command name (omit for all)" }
|
|
4287
|
+
],
|
|
4288
|
+
flags: [{ name: "--json", description: "Emit JSON" }],
|
|
4289
|
+
mutating: false,
|
|
4290
|
+
network: false,
|
|
4291
|
+
writesFiles: false,
|
|
4292
|
+
supportsDryRun: false,
|
|
4293
|
+
requiresPackageJson: false,
|
|
4294
|
+
mayReadEnv: false,
|
|
4295
|
+
supportsJson: true
|
|
4296
|
+
},
|
|
4297
|
+
{
|
|
4298
|
+
name: "version",
|
|
4299
|
+
description: "Print version info",
|
|
4300
|
+
flags: [{ name: "--json", description: "Emit JSON" }],
|
|
4301
|
+
mutating: false,
|
|
4302
|
+
network: false,
|
|
4303
|
+
writesFiles: false,
|
|
4304
|
+
supportsDryRun: false,
|
|
4305
|
+
requiresPackageJson: false,
|
|
4306
|
+
mayReadEnv: false,
|
|
4307
|
+
supportsJson: true
|
|
4308
|
+
}
|
|
4309
|
+
];
|
|
4310
|
+
var INVESTIGATE_FLAGS = [
|
|
4311
|
+
{ name: "--backend", takesValue: true, description: "Backend kind (env: AUTOTEL_BACKEND)" },
|
|
4312
|
+
{ name: "--jaeger-base-url", takesValue: true, description: "Jaeger base URL" },
|
|
4313
|
+
{ name: "--tempo-base-url", takesValue: true, description: "Tempo base URL" },
|
|
4314
|
+
{ name: "--prometheus-base-url", takesValue: true, description: "Prometheus base URL" },
|
|
4315
|
+
{ name: "--loki-base-url", takesValue: true, description: "Loki base URL" },
|
|
4316
|
+
{ name: "--collector-port", takesValue: true, description: "OTLP receiver port" },
|
|
4317
|
+
{ name: "--fixture-path", takesValue: true, description: "Fixture JSON path" },
|
|
4318
|
+
{ name: "--output-file", takesValue: true, description: "Persist JSON output to this path" },
|
|
4319
|
+
{ name: "--no-secrets-in-output", description: "Redact secret-shaped values" }
|
|
4320
|
+
];
|
|
4321
|
+
var STATIC_FLAGS = [
|
|
4322
|
+
{ name: "--output-file", takesValue: true, description: "Persist JSON output to this path" },
|
|
4323
|
+
{ name: "--no-secrets-in-output", description: "Redact secret-shaped values" }
|
|
4324
|
+
];
|
|
4325
|
+
function investigateCmd(name, description, extras = {}) {
|
|
4326
|
+
return {
|
|
4327
|
+
name,
|
|
4328
|
+
description,
|
|
4329
|
+
...extras.args ? { args: extras.args } : {},
|
|
4330
|
+
flags: [...extras.static ? STATIC_FLAGS : INVESTIGATE_FLAGS, ...extras.flags ?? []],
|
|
4331
|
+
mutating: false,
|
|
4332
|
+
network: extras.network ?? !extras.static,
|
|
4333
|
+
writesFiles: false,
|
|
4334
|
+
supportsDryRun: false,
|
|
4335
|
+
requiresPackageJson: false,
|
|
4336
|
+
mayReadEnv: false,
|
|
4337
|
+
supportsJson: true
|
|
4338
|
+
};
|
|
4339
|
+
}
|
|
4340
|
+
var traceIdArg = [
|
|
4341
|
+
{ name: "traceId", required: true, description: "Trace ID" }
|
|
4342
|
+
];
|
|
4343
|
+
var serviceNameArg = [
|
|
4344
|
+
{ name: "serviceName", required: true, description: "Service name" }
|
|
4345
|
+
];
|
|
4346
|
+
var INVESTIGATE_COMMANDS = [
|
|
4347
|
+
investigateCmd("health", "Backend health + signal coverage"),
|
|
4348
|
+
investigateCmd("capabilities", "Which signals the active backend serves"),
|
|
4349
|
+
investigateCmd("discover", "Discover services and field shapes (parent)"),
|
|
4350
|
+
investigateCmd("discover services", "Services with cross-signal metadata"),
|
|
4351
|
+
investigateCmd("discover trace-fields", "Trace/span field names from sampled traces", {
|
|
4352
|
+
flags: [{ name: "--search", takesValue: true, description: "Substring filter" }]
|
|
4353
|
+
}),
|
|
4354
|
+
investigateCmd("discover log-fields", "Log field names from sampled logs", {
|
|
4355
|
+
flags: [{ name: "--search", takesValue: true, description: "Substring filter" }]
|
|
4356
|
+
}),
|
|
4357
|
+
investigateCmd("query", "Query traces/spans/metrics/logs (parent)"),
|
|
4358
|
+
investigateCmd("query traces", "Search traces by service/op/status/tags/time/error"),
|
|
4359
|
+
investigateCmd("query spans", "Search individual spans (extra duration filters)"),
|
|
4360
|
+
investigateCmd("query metrics", "List metric series"),
|
|
4361
|
+
investigateCmd("query logs", "Search logs"),
|
|
4362
|
+
investigateCmd("trace", "Trace lookup commands (parent)"),
|
|
4363
|
+
investigateCmd("trace get", "Get a trace by ID", { args: traceIdArg }),
|
|
4364
|
+
investigateCmd("trace summary", "Compact incident-friendly trace summary", { args: traceIdArg }),
|
|
4365
|
+
investigateCmd("topology", "Service topology commands (parent)"),
|
|
4366
|
+
investigateCmd("topology services", "List known services"),
|
|
4367
|
+
investigateCmd("topology operations", "List operations for a service", { args: serviceNameArg }),
|
|
4368
|
+
investigateCmd("topology map", "Service dependency map with node/edge health"),
|
|
4369
|
+
investigateCmd("diagnose", "Anomaly / root-cause / errors / SLO diagnosis (parent)"),
|
|
4370
|
+
investigateCmd("diagnose anomalies", "Latency / error-rate outliers"),
|
|
4371
|
+
investigateCmd("diagnose root-cause", "Bottleneck span in a trace", { args: traceIdArg }),
|
|
4372
|
+
investigateCmd("diagnose errors", "Error spans grouped by service/operation"),
|
|
4373
|
+
investigateCmd("diagnose slos", "SLO violations for a service"),
|
|
4374
|
+
investigateCmd("correlate", "Cross-signal correlation (parent)"),
|
|
4375
|
+
investigateCmd("correlate trace", "Trace + metrics + logs for a trace ID", { args: traceIdArg }),
|
|
4376
|
+
investigateCmd("correlate explain-slowdown", "Anomalies + root cause + correlated signals"),
|
|
4377
|
+
investigateCmd("llm", "LLM analytics (parent)"),
|
|
4378
|
+
investigateCmd("llm usage", "Token usage + USD by model and service"),
|
|
4379
|
+
investigateCmd("llm models", "Discover LLM models in use"),
|
|
4380
|
+
investigateCmd("llm model-stats", "Per-model latency/token/error stats"),
|
|
4381
|
+
investigateCmd("llm expensive", "Top token-spend traces"),
|
|
4382
|
+
investigateCmd("llm slow", "Slowest LLM traces"),
|
|
4383
|
+
investigateCmd("llm tools", "Tool/function spans grouped by tool name"),
|
|
4384
|
+
investigateCmd("semconv", "Semantic conventions lookup (parent)", { static: true }),
|
|
4385
|
+
investigateCmd("semconv list", "List semconv namespaces", { static: true, network: true }),
|
|
4386
|
+
investigateCmd("semconv get", "Groups for one namespace", {
|
|
4387
|
+
static: true,
|
|
4388
|
+
network: true,
|
|
4389
|
+
args: [{ name: "namespace", required: true, description: "Namespace (e.g. http)" }]
|
|
4390
|
+
}),
|
|
4391
|
+
investigateCmd("semconv refresh", "Clear semconv cache", { static: true }),
|
|
4392
|
+
investigateCmd("score", "Score a span for instrumentation quality (JSON on stdin)", {
|
|
4393
|
+
static: true,
|
|
4394
|
+
flags: [{ name: "--span-file", takesValue: true, description: "Read span JSON from file" }]
|
|
4395
|
+
}),
|
|
4396
|
+
investigateCmd("score explain", "Explain the instrumentation scoring rubric", { static: true }),
|
|
4397
|
+
investigateCmd("collector", "OpenTelemetry Collector config + schema commands (parent)", {
|
|
4398
|
+
static: true
|
|
4399
|
+
}),
|
|
4400
|
+
investigateCmd("collector validate", "Validate OTLP receiver config", {
|
|
4401
|
+
static: true,
|
|
4402
|
+
flags: [{ name: "--config-file", takesValue: true, description: "Read JSON config from file" }]
|
|
4403
|
+
}),
|
|
4404
|
+
investigateCmd("collector suggest", "Minimal OTLP receiver config", { static: true }),
|
|
4405
|
+
investigateCmd("collector explain", "Receiver config shape + defaults", { static: true }),
|
|
4406
|
+
investigateCmd("collector versions", "Supported collector schema versions", { static: true, network: true }),
|
|
4407
|
+
investigateCmd("collector components", "Components for a version", {
|
|
4408
|
+
static: true,
|
|
4409
|
+
network: true,
|
|
4410
|
+
flags: [
|
|
4411
|
+
{ name: "--version", takesValue: true, description: "Collector version" },
|
|
4412
|
+
{ name: "--kind", takesValue: true, description: "Component kind filter" }
|
|
4413
|
+
]
|
|
4414
|
+
}),
|
|
4415
|
+
investigateCmd("collector schema", "JSON schema for a component", {
|
|
4416
|
+
static: true,
|
|
4417
|
+
network: true,
|
|
4418
|
+
flags: [
|
|
4419
|
+
{ name: "--kind", takesValue: true, description: "Component kind" },
|
|
4420
|
+
{ name: "--name", takesValue: true, description: "Component name" },
|
|
4421
|
+
{ name: "--version", takesValue: true, description: "Collector version" }
|
|
4422
|
+
]
|
|
4423
|
+
}),
|
|
4424
|
+
investigateCmd("collector readme", "README for a component", {
|
|
4425
|
+
static: true,
|
|
4426
|
+
network: true,
|
|
4427
|
+
flags: [
|
|
4428
|
+
{ name: "--kind", takesValue: true, description: "Component kind" },
|
|
4429
|
+
{ name: "--name", takesValue: true, description: "Component name" },
|
|
4430
|
+
{ name: "--version", takesValue: true, description: "Collector version" }
|
|
4431
|
+
]
|
|
4432
|
+
}),
|
|
4433
|
+
investigateCmd("collector validate-component", "Validate component config against upstream schema", {
|
|
4434
|
+
static: true,
|
|
4435
|
+
network: true,
|
|
4436
|
+
flags: [
|
|
4437
|
+
{ name: "--kind", takesValue: true, description: "Component kind" },
|
|
4438
|
+
{ name: "--name", takesValue: true, description: "Component name" },
|
|
4439
|
+
{ name: "--version", takesValue: true, description: "Collector version" },
|
|
4440
|
+
{ name: "--config-file", takesValue: true, description: "Read JSON config from file" }
|
|
4441
|
+
]
|
|
4442
|
+
}),
|
|
4443
|
+
investigateCmd("collector refresh", "Refresh in-memory collector metadata cache", { static: true, network: true })
|
|
4444
|
+
];
|
|
4445
|
+
COMMANDS.push(...INVESTIGATE_COMMANDS);
|
|
4446
|
+
function getCommand(name) {
|
|
4447
|
+
return COMMANDS.find((c) => c.name === name);
|
|
4448
|
+
}
|
|
4449
|
+
var ERROR_CATALOGUE = [
|
|
4450
|
+
{ code: AutotelErrorCodes.E_NO_PACKAGE_JSON, type: "environment", description: "No package.json found in cwd or ancestors" },
|
|
4451
|
+
{ code: AutotelErrorCodes.E_UNKNOWN_PRESET, type: "validation", description: "Preset slug not in registry" },
|
|
4452
|
+
{ code: AutotelErrorCodes.E_INVALID_PLAN, type: "validation", description: "Plan file failed schema validation" },
|
|
4453
|
+
{ code: AutotelErrorCodes.E_INVALID_INPUT, type: "validation", description: "Stdin or --input payload invalid" },
|
|
4454
|
+
{ code: AutotelErrorCodes.E_INVALID_FLAG, type: "validation", description: "Conflicting or invalid flag combination" },
|
|
4455
|
+
{ code: AutotelErrorCodes.E_NO_WORKSPACE_PACKAGES, type: "environment", description: "Workspace root has no detectable packages" },
|
|
4456
|
+
{ code: AutotelErrorCodes.E_ENV_CONSENT_REQUIRED, type: "environment", description: ".env file present; consent required (pass --scan-env or run interactively)" },
|
|
4457
|
+
{ code: AutotelErrorCodes.E_EXISTING_CONFIG, type: "conflict", description: "Existing instrumentation config; use --force or run with merge" },
|
|
4458
|
+
{ code: AutotelErrorCodes.E_AMBIGUOUS_LOGGER, type: "conflict", description: "Multiple loggers detected and selection could not be inferred" },
|
|
4459
|
+
{ code: AutotelErrorCodes.E_INSTALL_FAILED, type: "install", description: "Package manager install command failed" },
|
|
4460
|
+
{ code: AutotelErrorCodes.E_WRITE_FAILED, type: "io", description: "Failed to write a file" },
|
|
4461
|
+
{ code: AutotelErrorCodes.E_READ_FAILED, type: "io", description: "Failed to read a file" },
|
|
4462
|
+
{ code: AutotelErrorCodes.E_UNKNOWN, type: "runtime", description: "Unexpected error" }
|
|
4463
|
+
];
|
|
4464
|
+
|
|
4465
|
+
// src/commands/schema.ts
|
|
4466
|
+
function configure(opts) {
|
|
4467
|
+
configureJsonOutput({
|
|
4468
|
+
outputFile: opts.outputFile,
|
|
4469
|
+
noSecrets: opts.noSecrets
|
|
4470
|
+
});
|
|
4471
|
+
}
|
|
4472
|
+
function readSelfVersion() {
|
|
4473
|
+
try {
|
|
4474
|
+
const here = path14.dirname(fileURLToPath(import.meta.url));
|
|
4475
|
+
let dir = here;
|
|
4476
|
+
for (let i = 0; i < 5; i++) {
|
|
4477
|
+
const candidate = path14.join(dir, "package.json");
|
|
4478
|
+
if (fs6.existsSync(candidate)) {
|
|
4479
|
+
const pkg = JSON.parse(fs6.readFileSync(candidate, "utf8"));
|
|
4480
|
+
if (pkg.name === "autotel-cli") return String(pkg.version ?? "0.0.0");
|
|
4481
|
+
}
|
|
4482
|
+
dir = path14.dirname(dir);
|
|
4483
|
+
}
|
|
4484
|
+
} catch {
|
|
4485
|
+
}
|
|
4486
|
+
return "0.0.0";
|
|
4487
|
+
}
|
|
4488
|
+
function runSchema(opts) {
|
|
4489
|
+
configure(opts);
|
|
4490
|
+
printJson({
|
|
4491
|
+
ok: true,
|
|
4492
|
+
command: "autotel schema",
|
|
4493
|
+
version: readSelfVersion(),
|
|
4494
|
+
commands: COMMANDS
|
|
4495
|
+
});
|
|
4496
|
+
}
|
|
4497
|
+
function runSchemaErrors(opts) {
|
|
4498
|
+
configure(opts);
|
|
4499
|
+
printJson({
|
|
4500
|
+
ok: true,
|
|
4501
|
+
command: "autotel schema errors",
|
|
4502
|
+
envelope: {
|
|
4503
|
+
ok: false,
|
|
4504
|
+
command: "<command>",
|
|
4505
|
+
error: {
|
|
4506
|
+
type: "<one of: validation, environment, auth, conflict, install, io, runtime>",
|
|
4507
|
+
code: "<AUTOTEL_E_*>",
|
|
4508
|
+
message: "human-readable message",
|
|
4509
|
+
retryable: false,
|
|
4510
|
+
fix: "optional remediation hint",
|
|
4511
|
+
expected: { "<key>": "<value>" },
|
|
4512
|
+
suggestions: ["optional follow-up commands"]
|
|
4513
|
+
}
|
|
4514
|
+
},
|
|
4515
|
+
codes: ERROR_CATALOGUE,
|
|
4516
|
+
exitCodes: {
|
|
4517
|
+
"0": "success",
|
|
4518
|
+
"1": "runtime / unexpected failure",
|
|
4519
|
+
"2": "validation / conflict / refusal"
|
|
4520
|
+
}
|
|
4521
|
+
});
|
|
4522
|
+
}
|
|
4523
|
+
function runSchemaOutputs(opts) {
|
|
4524
|
+
configure(opts);
|
|
4525
|
+
printJson({
|
|
4526
|
+
ok: true,
|
|
4527
|
+
command: "autotel schema outputs",
|
|
4528
|
+
outputs: {
|
|
4529
|
+
"autotel init --json": {
|
|
4530
|
+
ok: "boolean",
|
|
4531
|
+
command: "string",
|
|
4532
|
+
detected: {
|
|
4533
|
+
packages: 'array of { name, version, resolution: "target" | "workspace-root" }',
|
|
4534
|
+
logger: '"pino" | "winston" | "bunyan" | null',
|
|
4535
|
+
backend: '{ source: "env" | "wrangler" | "deps" | "prompt" | "default", value: string }',
|
|
4536
|
+
platform: '"cloudflare" | "aws-lambda" | "edge" | null'
|
|
4537
|
+
},
|
|
4538
|
+
plan: {
|
|
4539
|
+
presets: "string[] (slugs)",
|
|
4540
|
+
packagesToInstall: { prod: "string[]", dev: "string[]" },
|
|
4541
|
+
filesToWrite: 'array of { path, action: "create" | "merge" | "skip" }',
|
|
4542
|
+
envVars: "array of { name, sensitive, action }"
|
|
4543
|
+
},
|
|
4544
|
+
nextSteps: "string[]"
|
|
4545
|
+
}
|
|
4546
|
+
}
|
|
4547
|
+
});
|
|
4548
|
+
}
|
|
4549
|
+
function runCommandsListing(opts) {
|
|
4550
|
+
configure(opts);
|
|
4551
|
+
const compact = COMMANDS.map((c) => ({
|
|
4552
|
+
name: c.name,
|
|
4553
|
+
description: c.description,
|
|
4554
|
+
mutating: c.mutating,
|
|
4555
|
+
network: c.network,
|
|
4556
|
+
writesFiles: c.writesFiles,
|
|
4557
|
+
supportsDryRun: c.supportsDryRun,
|
|
4558
|
+
supportsJson: c.supportsJson
|
|
4559
|
+
}));
|
|
4560
|
+
printJson({ ok: true, command: "autotel commands", commands: compact });
|
|
4561
|
+
}
|
|
4562
|
+
function runExamples(name, opts) {
|
|
4563
|
+
configure(opts);
|
|
4564
|
+
if (name === void 0) {
|
|
4565
|
+
const all = COMMANDS.filter((c) => c.examples && c.examples.length > 0).map(
|
|
4566
|
+
(c) => ({ command: c.name, examples: c.examples })
|
|
4567
|
+
);
|
|
4568
|
+
printJson({ ok: true, command: "autotel examples", examples: all });
|
|
4569
|
+
return;
|
|
4570
|
+
}
|
|
4571
|
+
const cmd = getCommand(name);
|
|
4572
|
+
if (!cmd) {
|
|
4573
|
+
throw new AutotelError({
|
|
4574
|
+
type: "validation",
|
|
4575
|
+
code: AutotelErrorCodes.E_INVALID_INPUT,
|
|
4576
|
+
message: `Unknown command: ${name}`,
|
|
4577
|
+
expected: { command: COMMANDS.map((c) => c.name) },
|
|
4578
|
+
fix: "Run `autotel commands --json` to see available commands"
|
|
4579
|
+
});
|
|
4580
|
+
}
|
|
4581
|
+
printJson({
|
|
4582
|
+
ok: true,
|
|
4583
|
+
command: "autotel examples",
|
|
4584
|
+
target: cmd.name,
|
|
4585
|
+
examples: cmd.examples ?? []
|
|
4586
|
+
});
|
|
4587
|
+
}
|
|
4588
|
+
function runVersion(opts) {
|
|
4589
|
+
configure(opts);
|
|
4590
|
+
printJson({
|
|
4591
|
+
ok: true,
|
|
4592
|
+
command: "autotel version",
|
|
4593
|
+
version: readSelfVersion(),
|
|
4594
|
+
node: process.version,
|
|
4595
|
+
platform: process.platform,
|
|
4596
|
+
arch: process.arch
|
|
4597
|
+
});
|
|
4598
|
+
}
|
|
4599
|
+
|
|
4600
|
+
// src/commands/investigate/health.ts
|
|
4601
|
+
import { Command } from "commander";
|
|
4602
|
+
|
|
4603
|
+
// src/commands/investigate/runtime.ts
|
|
4604
|
+
function applyFlagsToEnv(flags) {
|
|
4605
|
+
if (flags.backend !== void 0) process.env.AUTOTEL_BACKEND = flags.backend;
|
|
4606
|
+
if (flags.jaegerBaseUrl !== void 0)
|
|
4607
|
+
process.env.JAEGER_BASE_URL = flags.jaegerBaseUrl;
|
|
4608
|
+
if (flags.tempoBaseUrl !== void 0)
|
|
4609
|
+
process.env.TEMPO_BASE_URL = flags.tempoBaseUrl;
|
|
4610
|
+
if (flags.prometheusBaseUrl !== void 0)
|
|
4611
|
+
process.env.PROMETHEUS_BASE_URL = flags.prometheusBaseUrl;
|
|
4612
|
+
if (flags.lokiBaseUrl !== void 0)
|
|
4613
|
+
process.env.LOKI_BASE_URL = flags.lokiBaseUrl;
|
|
4614
|
+
if (flags.collectorPort !== void 0)
|
|
4615
|
+
process.env.AUTOTEL_COLLECTOR_PORT = String(flags.collectorPort);
|
|
4616
|
+
if (flags.fixturePath !== void 0)
|
|
4617
|
+
process.env.AUTOTEL_FIXTURE_PATH = flags.fixturePath;
|
|
4618
|
+
}
|
|
4619
|
+
async function openBackend(flags) {
|
|
4620
|
+
applyFlagsToEnv(flags);
|
|
4621
|
+
const { loadConfig, createBackend } = await import("autotel-mcp");
|
|
4622
|
+
const config = loadConfig();
|
|
4623
|
+
return createBackend(config);
|
|
4624
|
+
}
|
|
4625
|
+
async function runStatic(command, flags, fn) {
|
|
4626
|
+
configureJsonOutput({
|
|
4627
|
+
outputFile: flags.outputFile,
|
|
4628
|
+
noSecrets: flags.noSecrets
|
|
4629
|
+
});
|
|
4630
|
+
try {
|
|
4631
|
+
const data = await fn();
|
|
4632
|
+
printJson({ ok: true, command, data });
|
|
4633
|
+
} catch (error2) {
|
|
4634
|
+
throw toInvestigateError(command, error2);
|
|
4635
|
+
}
|
|
4636
|
+
}
|
|
4637
|
+
async function runInvestigate(command, flags, fn) {
|
|
4638
|
+
configureJsonOutput({
|
|
4639
|
+
outputFile: flags.outputFile,
|
|
4640
|
+
noSecrets: flags.noSecrets
|
|
4641
|
+
});
|
|
4642
|
+
let handle = null;
|
|
4643
|
+
try {
|
|
4644
|
+
handle = await openBackend(flags);
|
|
4645
|
+
await handle.start();
|
|
4646
|
+
const data = await fn(handle.backend);
|
|
4647
|
+
printJson({ ok: true, command, data });
|
|
4648
|
+
} catch (error2) {
|
|
4649
|
+
throw toInvestigateError(command, error2);
|
|
4650
|
+
} finally {
|
|
4651
|
+
if (handle) {
|
|
4652
|
+
try {
|
|
4653
|
+
await handle.stop();
|
|
4654
|
+
} catch {
|
|
4655
|
+
}
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4659
|
+
function toInvestigateError(command, error2) {
|
|
4660
|
+
if (error2 instanceof AutotelError) return error2;
|
|
4661
|
+
if (error2 !== null && typeof error2 === "object" && "issues" in error2 && Array.isArray(error2.issues)) {
|
|
4662
|
+
const issues = error2.issues;
|
|
4663
|
+
const first = issues[0];
|
|
4664
|
+
const path15 = first?.path?.join(".") ?? "";
|
|
4665
|
+
return new AutotelError({
|
|
4666
|
+
type: "validation",
|
|
4667
|
+
code: "AUTOTEL_E_INVALID_INPUT",
|
|
4668
|
+
message: `autotel ${command}: invalid input${path15 ? ` for "${path15}"` : ""} \u2014 ${first?.message ?? "validation failed"}`,
|
|
4669
|
+
retryable: false,
|
|
4670
|
+
expected: { issues }
|
|
4671
|
+
});
|
|
4672
|
+
}
|
|
4673
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
4674
|
+
return new AutotelError({
|
|
4675
|
+
type: "runtime",
|
|
4676
|
+
code: "AUTOTEL_E_UNKNOWN",
|
|
4677
|
+
message: `autotel ${command} failed: ${message}`,
|
|
4678
|
+
retryable: false
|
|
4679
|
+
});
|
|
4680
|
+
}
|
|
4681
|
+
|
|
4682
|
+
// src/commands/investigate/cli-helpers.ts
|
|
4683
|
+
var intArg = (v) => Number.parseInt(v, 10);
|
|
4684
|
+
var floatArg = (v) => Number.parseFloat(v);
|
|
4685
|
+
function addBackendFlags(cmd) {
|
|
4686
|
+
return cmd.option(
|
|
4687
|
+
"--backend <kind>",
|
|
4688
|
+
"Backend: collector|jaeger|tempo|prometheus|loki|stack|auto|fixture (env: AUTOTEL_BACKEND)"
|
|
4689
|
+
).option("--jaeger-base-url <url>", "Jaeger base URL (env: JAEGER_BASE_URL)").option("--tempo-base-url <url>", "Tempo base URL (env: TEMPO_BASE_URL)").option(
|
|
4690
|
+
"--prometheus-base-url <url>",
|
|
4691
|
+
"Prometheus base URL (env: PROMETHEUS_BASE_URL)"
|
|
4692
|
+
).option("--loki-base-url <url>", "Loki base URL (env: LOKI_BASE_URL)").option(
|
|
4693
|
+
"--collector-port <n>",
|
|
4694
|
+
"OTLP receiver port for the collector backend",
|
|
4695
|
+
intArg
|
|
4696
|
+
).option("--fixture-path <path>", "Fixture JSON for the fixture backend").option("--output-file <path>", "Persist JSON output to this file").option("--no-secrets-in-output", "Redact secret-shaped values");
|
|
4697
|
+
}
|
|
4698
|
+
function addStaticFlags(cmd) {
|
|
4699
|
+
return cmd.option("--output-file <path>", "Persist JSON output to this file").option("--no-secrets-in-output", "Redact secret-shaped values");
|
|
4700
|
+
}
|
|
4701
|
+
function backendFlagsFromOpts(opts) {
|
|
4702
|
+
return {
|
|
4703
|
+
backend: opts.backend,
|
|
4704
|
+
jaegerBaseUrl: opts.jaegerBaseUrl,
|
|
4705
|
+
tempoBaseUrl: opts.tempoBaseUrl,
|
|
4706
|
+
prometheusBaseUrl: opts.prometheusBaseUrl,
|
|
4707
|
+
lokiBaseUrl: opts.lokiBaseUrl,
|
|
4708
|
+
collectorPort: opts.collectorPort,
|
|
4709
|
+
fixturePath: opts.fixturePath,
|
|
4710
|
+
outputFile: opts.outputFile,
|
|
4711
|
+
noSecrets: opts.secretsInOutput === false
|
|
4712
|
+
};
|
|
4713
|
+
}
|
|
4714
|
+
function staticFlagsFromOpts(opts) {
|
|
4715
|
+
return {
|
|
4716
|
+
outputFile: opts.outputFile,
|
|
4717
|
+
noSecrets: opts.secretsInOutput === false
|
|
4718
|
+
};
|
|
4719
|
+
}
|
|
4720
|
+
function addTimeWindowFlags(cmd) {
|
|
4721
|
+
return cmd.option("--service-name <name>", "Filter by service name").option("--operation-name <name>", "Filter by operation name").option("--lookback-minutes <n>", "Lookback window in minutes", intArg).option("--from <iso>", "Start time (ISO 8601)").option("--to <iso>", "End time (ISO 8601)").option("--limit <n>", "Max results", intArg);
|
|
4722
|
+
}
|
|
4723
|
+
|
|
4724
|
+
// src/commands/investigate/health.ts
|
|
4725
|
+
async function runHealth(flags) {
|
|
4726
|
+
await runInvestigate("health", flags, async (backend) => {
|
|
4727
|
+
const [health, capabilities] = await Promise.all([
|
|
4728
|
+
backend.healthCheck(),
|
|
4729
|
+
Promise.resolve(backend.capabilities())
|
|
4730
|
+
]);
|
|
4731
|
+
return { ...health, signals: capabilities };
|
|
4732
|
+
});
|
|
4733
|
+
}
|
|
4734
|
+
async function runCapabilities(flags) {
|
|
4735
|
+
await runInvestigate(
|
|
4736
|
+
"capabilities",
|
|
4737
|
+
flags,
|
|
4738
|
+
async (backend) => backend.capabilities()
|
|
4739
|
+
);
|
|
4740
|
+
}
|
|
4741
|
+
function registerHealthCommands(program) {
|
|
4742
|
+
const healthCmd = new Command("health").description("Backend health check + signal coverage (JSON)").action(async function() {
|
|
4743
|
+
await runHealth(backendFlagsFromOpts(this.opts()));
|
|
4744
|
+
});
|
|
4745
|
+
addBackendFlags(healthCmd);
|
|
4746
|
+
program.addCommand(healthCmd);
|
|
4747
|
+
const capabilitiesCmd = new Command("capabilities").description("Which telemetry signals the active backend can serve (JSON)").action(async function() {
|
|
4748
|
+
await runCapabilities(backendFlagsFromOpts(this.opts()));
|
|
4749
|
+
});
|
|
4750
|
+
addBackendFlags(capabilitiesCmd);
|
|
4751
|
+
program.addCommand(capabilitiesCmd);
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4754
|
+
// src/commands/investigate/discovery.ts
|
|
4755
|
+
import { Command as Command2 } from "commander";
|
|
4756
|
+
import {
|
|
4757
|
+
discoverServices,
|
|
4758
|
+
discoverTraceFields,
|
|
4759
|
+
discoverLogFields
|
|
4760
|
+
} from "autotel-mcp";
|
|
4761
|
+
async function runDiscoverServices(flags) {
|
|
4762
|
+
await runInvestigate("discover services", flags, async (backend) => {
|
|
4763
|
+
const limitServices = flags.limitServices ?? 100;
|
|
4764
|
+
const traceSample = flags.traceSample ?? 200;
|
|
4765
|
+
const logSample = flags.logSample ?? 200;
|
|
4766
|
+
const metricSample = flags.metricSample ?? 200;
|
|
4767
|
+
const caps = backend.capabilities();
|
|
4768
|
+
const servicesResult = await backend.listServices({ limit: limitServices });
|
|
4769
|
+
const services = servicesResult.services.slice(0, limitServices);
|
|
4770
|
+
const [traces, logs, metrics] = await Promise.all([
|
|
4771
|
+
caps.traces === "available" ? backend.searchTraces({ limit: traceSample }).then((r) => r.items) : Promise.resolve([]),
|
|
4772
|
+
caps.logs === "available" ? backend.searchLogs({ limit: logSample }).then((r) => r.items) : Promise.resolve([]),
|
|
4773
|
+
caps.metrics === "available" ? backend.listMetrics({ limit: metricSample }).then((r) => r.items) : Promise.resolve([])
|
|
4774
|
+
]);
|
|
4775
|
+
const discovered = discoverServices({ services, traces, logs, metrics });
|
|
4776
|
+
return { count: discovered.length, services: discovered };
|
|
4777
|
+
});
|
|
4778
|
+
}
|
|
4779
|
+
async function runDiscoverTraceFields(flags) {
|
|
4780
|
+
await runInvestigate("discover trace-fields", flags, async (backend) => {
|
|
4781
|
+
return discoverFields(backend, flags, "traces");
|
|
4782
|
+
});
|
|
4783
|
+
}
|
|
4784
|
+
async function runDiscoverLogFields(flags) {
|
|
4785
|
+
await runInvestigate("discover log-fields", flags, async (backend) => {
|
|
4786
|
+
return discoverFields(backend, flags, "logs");
|
|
4787
|
+
});
|
|
4788
|
+
}
|
|
4789
|
+
async function discoverFields(backend, flags, signal) {
|
|
4790
|
+
const sampleSize = flags.sampleSize ?? 200;
|
|
4791
|
+
if (signal === "traces") {
|
|
4792
|
+
const traces = await backend.searchTraces({ limit: sampleSize }).then((r) => r.items);
|
|
4793
|
+
return {
|
|
4794
|
+
search: flags.search ?? null,
|
|
4795
|
+
sampleSize: traces.length,
|
|
4796
|
+
...discoverTraceFields(traces, flags.search)
|
|
4797
|
+
};
|
|
4798
|
+
}
|
|
4799
|
+
const logs = await backend.searchLogs({ limit: sampleSize }).then((r) => r.items);
|
|
4800
|
+
return {
|
|
4801
|
+
search: flags.search ?? null,
|
|
4802
|
+
sampleSize: logs.length,
|
|
4803
|
+
...discoverLogFields(logs, flags.search)
|
|
4804
|
+
};
|
|
4805
|
+
}
|
|
4806
|
+
function registerDiscoveryCommands(program) {
|
|
4807
|
+
const discoverCmd = new Command2("discover").description(
|
|
4808
|
+
"Discover services and field shapes from the active backend (JSON)"
|
|
4809
|
+
);
|
|
4810
|
+
const servicesCmd = new Command2("services").description("Discover services with cross-signal metadata").option("--limit-services <n>", "Max services", intArg).option("--trace-sample <n>", "Trace sample size", intArg).option("--log-sample <n>", "Log sample size", intArg).option("--metric-sample <n>", "Metric sample size", intArg).action(async function() {
|
|
4811
|
+
const o = this.optsWithGlobals();
|
|
4812
|
+
await runDiscoverServices({
|
|
4813
|
+
...backendFlagsFromOpts(o),
|
|
4814
|
+
limitServices: o.limitServices,
|
|
4815
|
+
traceSample: o.traceSample,
|
|
4816
|
+
logSample: o.logSample,
|
|
4817
|
+
metricSample: o.metricSample
|
|
4818
|
+
});
|
|
4819
|
+
});
|
|
4820
|
+
const traceFieldsCmd = new Command2("trace-fields").description("Discover trace/span field names from sampled traces").option("--search <text>", "Filter field names by substring").option("--sample-size <n>", "Trace sample size", intArg).action(async function() {
|
|
4821
|
+
const o = this.optsWithGlobals();
|
|
4822
|
+
await runDiscoverTraceFields({
|
|
4823
|
+
...backendFlagsFromOpts(o),
|
|
4824
|
+
search: o.search,
|
|
4825
|
+
sampleSize: o.sampleSize
|
|
4826
|
+
});
|
|
4827
|
+
});
|
|
4828
|
+
const logFieldsCmd = new Command2("log-fields").description("Discover log field names from sampled logs").option("--search <text>", "Filter field names by substring").option("--sample-size <n>", "Log sample size", intArg).action(async function() {
|
|
4829
|
+
const o = this.optsWithGlobals();
|
|
4830
|
+
await runDiscoverLogFields({
|
|
4831
|
+
...backendFlagsFromOpts(o),
|
|
4832
|
+
search: o.search,
|
|
4833
|
+
sampleSize: o.sampleSize
|
|
4834
|
+
});
|
|
4835
|
+
});
|
|
4836
|
+
addBackendFlags(discoverCmd);
|
|
4837
|
+
discoverCmd.addCommand(servicesCmd);
|
|
4838
|
+
discoverCmd.addCommand(traceFieldsCmd);
|
|
4839
|
+
discoverCmd.addCommand(logFieldsCmd);
|
|
4840
|
+
program.addCommand(discoverCmd);
|
|
4841
|
+
}
|
|
4842
|
+
|
|
4843
|
+
// src/commands/investigate/investigation.ts
|
|
4844
|
+
import { Command as Command3 } from "commander";
|
|
4845
|
+
import {
|
|
4846
|
+
toTraceSearchQuery,
|
|
4847
|
+
toSpanSearchQuery
|
|
4848
|
+
} from "autotel-mcp";
|
|
4849
|
+
|
|
4850
|
+
// src/commands/investigate/signals.ts
|
|
4851
|
+
import {
|
|
4852
|
+
toMetricSearchQuery,
|
|
4853
|
+
toLogSearchQuery
|
|
4854
|
+
} from "autotel-mcp";
|
|
4855
|
+
async function runQueryMetrics(flags) {
|
|
4856
|
+
await runInvestigate(
|
|
4857
|
+
"query metrics",
|
|
4858
|
+
flags,
|
|
4859
|
+
async (backend) => backend.listMetrics(toMetricSearchQuery(flags))
|
|
4860
|
+
);
|
|
4861
|
+
}
|
|
4862
|
+
async function runQueryLogs(flags) {
|
|
4863
|
+
await runInvestigate(
|
|
4864
|
+
"query logs",
|
|
4865
|
+
flags,
|
|
4866
|
+
async (backend) => backend.searchLogs(toLogSearchQuery(flags))
|
|
4867
|
+
);
|
|
4868
|
+
}
|
|
4869
|
+
|
|
4870
|
+
// src/commands/investigate/investigation.ts
|
|
4871
|
+
async function runQueryTraces(flags) {
|
|
4872
|
+
await runInvestigate(
|
|
4873
|
+
"query traces",
|
|
4874
|
+
flags,
|
|
4875
|
+
async (backend) => backend.searchTraces(toTraceSearchQuery(flags))
|
|
4876
|
+
);
|
|
4877
|
+
}
|
|
4878
|
+
async function runQuerySpans(flags) {
|
|
4879
|
+
await runInvestigate(
|
|
4880
|
+
"query spans",
|
|
4881
|
+
flags,
|
|
4882
|
+
async (backend) => backend.searchSpans(toSpanSearchQuery(flags))
|
|
4883
|
+
);
|
|
4884
|
+
}
|
|
4885
|
+
async function runTraceGet(flags) {
|
|
4886
|
+
await runInvestigate(
|
|
4887
|
+
"trace get",
|
|
4888
|
+
flags,
|
|
4889
|
+
async (backend) => backend.getTrace(flags.traceId)
|
|
4890
|
+
);
|
|
4891
|
+
}
|
|
4892
|
+
async function runTraceSummary(flags) {
|
|
4893
|
+
await runInvestigate(
|
|
4894
|
+
"trace summary",
|
|
4895
|
+
flags,
|
|
4896
|
+
async (backend) => backend.summarizeTrace(flags.traceId)
|
|
4897
|
+
);
|
|
4898
|
+
}
|
|
4899
|
+
function registerQueryCommands(program) {
|
|
4900
|
+
const queryCmd = new Command3("query").description(
|
|
4901
|
+
"Query traces, spans, metrics, or logs (JSON)"
|
|
4902
|
+
);
|
|
4903
|
+
const tracesCmd = addTimeWindowFlags(new Command3("traces")).description("Search traces by service, op, status, tags, time, error").option("--error-only", "Only traces with errors").option("--status-code <code>", "OK | ERROR | UNSET").option("--min-duration-ms <n>", "Minimum duration", intArg).option("--max-duration-ms <n>", "Maximum duration", intArg).option("--gen-ai-system <name>", "gen_ai.system").option("--gen-ai-request-model <name>", "gen_ai.request.model").option("--gen-ai-response-model <name>", "gen_ai.response.model").action(async function() {
|
|
4904
|
+
const o = this.optsWithGlobals();
|
|
4905
|
+
await runQueryTraces({
|
|
4906
|
+
...backendFlagsFromOpts(o),
|
|
4907
|
+
serviceName: o.serviceName,
|
|
4908
|
+
operationName: o.operationName,
|
|
4909
|
+
lookbackMinutes: o.lookbackMinutes,
|
|
4910
|
+
from: o.from,
|
|
4911
|
+
to: o.to,
|
|
4912
|
+
limit: o.limit,
|
|
4913
|
+
errorOnly: o.errorOnly,
|
|
4914
|
+
statusCode: o.statusCode,
|
|
4915
|
+
minDurationMs: o.minDurationMs,
|
|
4916
|
+
maxDurationMs: o.maxDurationMs,
|
|
4917
|
+
genAiSystem: o.genAiSystem,
|
|
4918
|
+
genAiRequestModel: o.genAiRequestModel,
|
|
4919
|
+
genAiResponseModel: o.genAiResponseModel
|
|
4920
|
+
});
|
|
4921
|
+
});
|
|
4922
|
+
const spansCmd = addTimeWindowFlags(new Command3("spans")).description("Search individual spans by service/op/status/tags/duration").option("--error-only", "Only spans with errors").option("--status-code <code>", "OK | ERROR | UNSET").option("--min-duration-ms <n>", "Minimum span duration", intArg).option("--max-duration-ms <n>", "Maximum span duration", intArg).action(async function() {
|
|
4923
|
+
const o = this.optsWithGlobals();
|
|
4924
|
+
await runQuerySpans({
|
|
4925
|
+
...backendFlagsFromOpts(o),
|
|
4926
|
+
serviceName: o.serviceName,
|
|
4927
|
+
operationName: o.operationName,
|
|
4928
|
+
lookbackMinutes: o.lookbackMinutes,
|
|
4929
|
+
from: o.from,
|
|
4930
|
+
to: o.to,
|
|
4931
|
+
limit: o.limit,
|
|
4932
|
+
errorOnly: o.errorOnly,
|
|
4933
|
+
statusCode: o.statusCode,
|
|
4934
|
+
minDurationMs: o.minDurationMs,
|
|
4935
|
+
maxDurationMs: o.maxDurationMs
|
|
4936
|
+
});
|
|
4937
|
+
});
|
|
4938
|
+
const metricsCmd = addTimeWindowFlags(new Command3("metrics")).description("List metric series").option("--metric-name <name>", "Filter by metric name").action(async function() {
|
|
4939
|
+
const o = this.optsWithGlobals();
|
|
4940
|
+
await runQueryMetrics({
|
|
4941
|
+
...backendFlagsFromOpts(o),
|
|
4942
|
+
metricName: o.metricName,
|
|
4943
|
+
serviceName: o.serviceName,
|
|
4944
|
+
lookbackMinutes: o.lookbackMinutes,
|
|
4945
|
+
from: o.from,
|
|
4946
|
+
to: o.to,
|
|
4947
|
+
limit: o.limit
|
|
4948
|
+
});
|
|
4949
|
+
});
|
|
4950
|
+
const logsCmd = addTimeWindowFlags(new Command3("logs")).description("Search logs").option("--trace-id <id>", "Filter by trace id").option("--span-id <id>", "Filter by span id").option("--severity-text <text>", "Severity text").option("--text <text>", "Free-text search").action(async function() {
|
|
4951
|
+
const o = this.optsWithGlobals();
|
|
4952
|
+
await runQueryLogs({
|
|
4953
|
+
...backendFlagsFromOpts(o),
|
|
4954
|
+
serviceName: o.serviceName,
|
|
4955
|
+
traceId: o.traceId,
|
|
4956
|
+
spanId: o.spanId,
|
|
4957
|
+
severityText: o.severityText,
|
|
4958
|
+
text: o.text,
|
|
4959
|
+
lookbackMinutes: o.lookbackMinutes,
|
|
4960
|
+
from: o.from,
|
|
4961
|
+
to: o.to,
|
|
4962
|
+
limit: o.limit
|
|
4963
|
+
});
|
|
4964
|
+
});
|
|
4965
|
+
addBackendFlags(queryCmd);
|
|
4966
|
+
queryCmd.addCommand(tracesCmd);
|
|
4967
|
+
queryCmd.addCommand(spansCmd);
|
|
4968
|
+
queryCmd.addCommand(metricsCmd);
|
|
4969
|
+
queryCmd.addCommand(logsCmd);
|
|
4970
|
+
program.addCommand(queryCmd);
|
|
4971
|
+
}
|
|
4972
|
+
function registerTraceCommands(program) {
|
|
4973
|
+
const traceLookupCmd = new Command3("trace").description(
|
|
4974
|
+
"Trace lookup commands (JSON)"
|
|
4975
|
+
);
|
|
4976
|
+
const getCmd = new Command3("get").description("Get a trace by ID").argument("<traceId>", "Trace ID").action(async function(traceId) {
|
|
4977
|
+
await runTraceGet({
|
|
4978
|
+
...backendFlagsFromOpts(this.optsWithGlobals()),
|
|
4979
|
+
traceId
|
|
4980
|
+
});
|
|
4981
|
+
});
|
|
4982
|
+
const summaryCmd = new Command3("summary").description("Compact incident-friendly trace summary").argument("<traceId>", "Trace ID").action(async function(traceId) {
|
|
4983
|
+
await runTraceSummary({
|
|
4984
|
+
...backendFlagsFromOpts(this.optsWithGlobals()),
|
|
4985
|
+
traceId
|
|
4986
|
+
});
|
|
4987
|
+
});
|
|
4988
|
+
addBackendFlags(traceLookupCmd);
|
|
4989
|
+
traceLookupCmd.addCommand(getCmd);
|
|
4990
|
+
traceLookupCmd.addCommand(summaryCmd);
|
|
4991
|
+
program.addCommand(traceLookupCmd);
|
|
4992
|
+
}
|
|
4993
|
+
|
|
4994
|
+
// src/commands/investigate/topology.ts
|
|
4995
|
+
import { Command as Command4 } from "commander";
|
|
4996
|
+
async function runListServices(flags) {
|
|
4997
|
+
await runInvestigate(
|
|
4998
|
+
"topology services",
|
|
4999
|
+
flags,
|
|
5000
|
+
async (backend) => backend.listServices()
|
|
5001
|
+
);
|
|
5002
|
+
}
|
|
5003
|
+
async function runListOperations(flags) {
|
|
5004
|
+
await runInvestigate(
|
|
5005
|
+
"topology operations",
|
|
5006
|
+
flags,
|
|
5007
|
+
async (backend) => backend.listOperations(flags.serviceName)
|
|
5008
|
+
);
|
|
5009
|
+
}
|
|
5010
|
+
async function runServiceMap(flags) {
|
|
5011
|
+
await runInvestigate(
|
|
5012
|
+
"topology map",
|
|
5013
|
+
flags,
|
|
5014
|
+
async (backend) => backend.serviceMap(flags.lookbackMinutes ?? 60, flags.limit ?? 20)
|
|
5015
|
+
);
|
|
5016
|
+
}
|
|
5017
|
+
function registerTopologyCommands(program) {
|
|
5018
|
+
const topologyCmd = new Command4("topology").description(
|
|
5019
|
+
"Service topology commands (JSON)"
|
|
5020
|
+
);
|
|
5021
|
+
const servicesCmd = new Command4("services").description("List known services").action(async function() {
|
|
5022
|
+
await runListServices(backendFlagsFromOpts(this.optsWithGlobals()));
|
|
5023
|
+
});
|
|
5024
|
+
const operationsCmd = new Command4("operations").description("List operations for a service").argument("<serviceName>", "Service name").action(async function(serviceName) {
|
|
5025
|
+
await runListOperations({
|
|
5026
|
+
...backendFlagsFromOpts(this.optsWithGlobals()),
|
|
5027
|
+
serviceName
|
|
5028
|
+
});
|
|
5029
|
+
});
|
|
5030
|
+
const mapCmd = new Command4("map").description("Build a service dependency map").option("--lookback-minutes <n>", "Lookback in minutes", intArg).option("--limit <n>", "Max services", intArg).action(async function() {
|
|
5031
|
+
const o = this.optsWithGlobals();
|
|
5032
|
+
await runServiceMap({
|
|
5033
|
+
...backendFlagsFromOpts(o),
|
|
5034
|
+
lookbackMinutes: o.lookbackMinutes,
|
|
5035
|
+
limit: o.limit
|
|
5036
|
+
});
|
|
5037
|
+
});
|
|
5038
|
+
addBackendFlags(topologyCmd);
|
|
5039
|
+
topologyCmd.addCommand(servicesCmd);
|
|
5040
|
+
topologyCmd.addCommand(operationsCmd);
|
|
5041
|
+
topologyCmd.addCommand(mapCmd);
|
|
5042
|
+
program.addCommand(topologyCmd);
|
|
5043
|
+
}
|
|
5044
|
+
|
|
5045
|
+
// src/commands/investigate/diagnosis.ts
|
|
5046
|
+
import { Command as Command5 } from "commander";
|
|
5047
|
+
import {
|
|
5048
|
+
detectAnomalies,
|
|
5049
|
+
findRootCause,
|
|
5050
|
+
pickErrorMessage
|
|
5051
|
+
} from "autotel-mcp";
|
|
5052
|
+
async function runDiagnoseAnomalies(flags) {
|
|
5053
|
+
await runInvestigate("diagnose anomalies", flags, async (backend) => {
|
|
5054
|
+
const lookback = flags.lookbackMinutes ?? 60;
|
|
5055
|
+
const nowMs = Date.now();
|
|
5056
|
+
const result = await backend.searchTraces({
|
|
5057
|
+
service: flags.service,
|
|
5058
|
+
startTimeUnixMs: nowMs - lookback * 60 * 1e3,
|
|
5059
|
+
endTimeUnixMs: nowMs,
|
|
5060
|
+
limit: 100
|
|
5061
|
+
});
|
|
5062
|
+
return detectAnomalies(result.items, {
|
|
5063
|
+
service: flags.service,
|
|
5064
|
+
operation: flags.operation
|
|
5065
|
+
});
|
|
5066
|
+
});
|
|
5067
|
+
}
|
|
5068
|
+
async function runDiagnoseRootCause(flags) {
|
|
5069
|
+
await runInvestigate("diagnose root-cause", flags, async (backend) => {
|
|
5070
|
+
const trace = await backend.getTrace(flags.traceId);
|
|
5071
|
+
if (!trace) return { error: `Trace not found: ${flags.traceId}` };
|
|
5072
|
+
return findRootCause(trace);
|
|
5073
|
+
});
|
|
5074
|
+
}
|
|
5075
|
+
async function runDiagnoseErrors(flags) {
|
|
5076
|
+
await runInvestigate("diagnose errors", flags, async (backend) => {
|
|
5077
|
+
const lookback = flags.lookbackMinutes ?? 60;
|
|
5078
|
+
const limit = flags.limit ?? 20;
|
|
5079
|
+
const nowMs = Date.now();
|
|
5080
|
+
const result = await backend.searchTraces({
|
|
5081
|
+
service: flags.service,
|
|
5082
|
+
hasError: true,
|
|
5083
|
+
startTimeUnixMs: nowMs - lookback * 60 * 1e3,
|
|
5084
|
+
endTimeUnixMs: nowMs,
|
|
5085
|
+
limit
|
|
5086
|
+
});
|
|
5087
|
+
const groups = /* @__PURE__ */ new Map();
|
|
5088
|
+
for (const trace of result.items) {
|
|
5089
|
+
for (const span of trace.spans) {
|
|
5090
|
+
if (!span.hasError) continue;
|
|
5091
|
+
if (flags.service && span.serviceName !== flags.service) continue;
|
|
5092
|
+
const key = `${span.serviceName}::${span.operationName}`;
|
|
5093
|
+
if (!groups.has(key)) {
|
|
5094
|
+
groups.set(key, {
|
|
5095
|
+
service: span.serviceName,
|
|
5096
|
+
operation: span.operationName,
|
|
5097
|
+
count: 0,
|
|
5098
|
+
errorMessages: [],
|
|
5099
|
+
traceIds: []
|
|
5100
|
+
});
|
|
5101
|
+
}
|
|
5102
|
+
const group = groups.get(key);
|
|
5103
|
+
group.count++;
|
|
5104
|
+
const msg = pickErrorMessage(span.tags);
|
|
5105
|
+
if (msg && !group.errorMessages.includes(msg)) {
|
|
5106
|
+
group.errorMessages.push(msg);
|
|
5107
|
+
}
|
|
5108
|
+
if (!group.traceIds.includes(trace.traceId)) {
|
|
5109
|
+
group.traceIds.push(trace.traceId);
|
|
5110
|
+
}
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
return {
|
|
5114
|
+
totalTraces: result.totalCount,
|
|
5115
|
+
groups: [...groups.values()].toSorted((a, b) => b.count - a.count)
|
|
5116
|
+
};
|
|
5117
|
+
});
|
|
5118
|
+
}
|
|
5119
|
+
async function runDiagnoseSlos(flags) {
|
|
5120
|
+
await runInvestigate("diagnose slos", flags, async (backend) => {
|
|
5121
|
+
const lookback = flags.lookbackMinutes ?? 60;
|
|
5122
|
+
const nowMs = Date.now();
|
|
5123
|
+
const result = await backend.searchTraces({
|
|
5124
|
+
service: flags.service,
|
|
5125
|
+
startTimeUnixMs: nowMs - lookback * 60 * 1e3,
|
|
5126
|
+
endTimeUnixMs: nowMs,
|
|
5127
|
+
limit: 100
|
|
5128
|
+
});
|
|
5129
|
+
const spans = result.items.flatMap(
|
|
5130
|
+
(t) => t.spans.filter((s) => s.serviceName === flags.service)
|
|
5131
|
+
);
|
|
5132
|
+
const violations = [];
|
|
5133
|
+
if (spans.length === 0) {
|
|
5134
|
+
return {
|
|
5135
|
+
service: flags.service,
|
|
5136
|
+
totalSpans: 0,
|
|
5137
|
+
violations,
|
|
5138
|
+
message: "No spans found for the given service and time window."
|
|
5139
|
+
};
|
|
5140
|
+
}
|
|
5141
|
+
const durations = spans.map((s) => s.durationMs).toSorted((a, b) => a - b);
|
|
5142
|
+
const p99Index = Math.floor(durations.length * 0.99);
|
|
5143
|
+
const actualP99 = durations[Math.min(p99Index, durations.length - 1)];
|
|
5144
|
+
if (flags.p99LatencyMs !== void 0 && actualP99 > flags.p99LatencyMs) {
|
|
5145
|
+
violations.push({
|
|
5146
|
+
type: "p99_latency",
|
|
5147
|
+
target: flags.p99LatencyMs,
|
|
5148
|
+
actual: actualP99,
|
|
5149
|
+
description: `p99 latency ${actualP99.toFixed(1)}ms exceeds target ${flags.p99LatencyMs}ms`
|
|
5150
|
+
});
|
|
5151
|
+
}
|
|
5152
|
+
const errorCount = spans.filter((s) => s.hasError).length;
|
|
5153
|
+
const actualErrorRate = errorCount / spans.length;
|
|
5154
|
+
if (flags.maxErrorRate !== void 0 && actualErrorRate > flags.maxErrorRate) {
|
|
5155
|
+
violations.push({
|
|
5156
|
+
type: "error_rate",
|
|
5157
|
+
target: flags.maxErrorRate,
|
|
5158
|
+
actual: actualErrorRate,
|
|
5159
|
+
description: `Error rate ${(actualErrorRate * 100).toFixed(2)}% exceeds target ${(flags.maxErrorRate * 100).toFixed(2)}%`
|
|
5160
|
+
});
|
|
5161
|
+
}
|
|
5162
|
+
return {
|
|
5163
|
+
service: flags.service,
|
|
5164
|
+
totalSpans: spans.length,
|
|
5165
|
+
p99LatencyMs: actualP99,
|
|
5166
|
+
errorRate: actualErrorRate,
|
|
5167
|
+
violations
|
|
5168
|
+
};
|
|
5169
|
+
});
|
|
5170
|
+
}
|
|
5171
|
+
function registerDiagnoseCommands(program) {
|
|
5172
|
+
const diagnoseCmd = new Command5("diagnose").description(
|
|
5173
|
+
"Anomaly / root-cause / errors / SLO diagnosis (JSON)"
|
|
5174
|
+
);
|
|
5175
|
+
const anomaliesCmd = new Command5("anomalies").description("Detect latency/error-rate outliers").option("--service <name>", "Service filter").option("--operation <name>", "Operation filter").option("--lookback-minutes <n>", "Lookback in minutes", intArg).action(async function() {
|
|
5176
|
+
const o = this.optsWithGlobals();
|
|
5177
|
+
await runDiagnoseAnomalies({
|
|
5178
|
+
...backendFlagsFromOpts(o),
|
|
5179
|
+
service: o.service,
|
|
5180
|
+
operation: o.operation,
|
|
5181
|
+
lookbackMinutes: o.lookbackMinutes
|
|
5182
|
+
});
|
|
5183
|
+
});
|
|
5184
|
+
const rootCauseCmd = new Command5("root-cause").description("Walk a trace tree to identify the bottleneck span").argument("<traceId>", "Trace ID").action(async function(traceId) {
|
|
5185
|
+
await runDiagnoseRootCause({
|
|
5186
|
+
...backendFlagsFromOpts(this.optsWithGlobals()),
|
|
5187
|
+
traceId
|
|
5188
|
+
});
|
|
5189
|
+
});
|
|
5190
|
+
const errorsCmd = new Command5("errors").description("Aggregate error spans by service/operation").option("--service <name>", "Service filter").option("--lookback-minutes <n>", "Lookback in minutes", intArg).option("--limit <n>", "Max traces to scan", intArg).action(async function() {
|
|
5191
|
+
const o = this.optsWithGlobals();
|
|
5192
|
+
await runDiagnoseErrors({
|
|
5193
|
+
...backendFlagsFromOpts(o),
|
|
5194
|
+
service: o.service,
|
|
5195
|
+
lookbackMinutes: o.lookbackMinutes,
|
|
5196
|
+
limit: o.limit
|
|
5197
|
+
});
|
|
5198
|
+
});
|
|
5199
|
+
const slosCmd = new Command5("slos").description("Report SLO violations for a service").requiredOption("--service <name>", "Service to check").option("--p99-latency-ms <n>", "p99 latency target", floatArg).option("--max-error-rate <n>", "Error-rate target (0..1)", floatArg).option("--lookback-minutes <n>", "Lookback in minutes", intArg).action(async function() {
|
|
5200
|
+
const o = this.optsWithGlobals();
|
|
5201
|
+
await runDiagnoseSlos({
|
|
5202
|
+
...backendFlagsFromOpts(o),
|
|
5203
|
+
service: o.service,
|
|
5204
|
+
p99LatencyMs: o.p99LatencyMs,
|
|
5205
|
+
maxErrorRate: o.maxErrorRate,
|
|
5206
|
+
lookbackMinutes: o.lookbackMinutes
|
|
5207
|
+
});
|
|
5208
|
+
});
|
|
5209
|
+
addBackendFlags(diagnoseCmd);
|
|
5210
|
+
diagnoseCmd.addCommand(anomaliesCmd);
|
|
5211
|
+
diagnoseCmd.addCommand(rootCauseCmd);
|
|
5212
|
+
diagnoseCmd.addCommand(errorsCmd);
|
|
5213
|
+
diagnoseCmd.addCommand(slosCmd);
|
|
5214
|
+
program.addCommand(diagnoseCmd);
|
|
5215
|
+
}
|
|
5216
|
+
|
|
5217
|
+
// src/commands/investigate/correlation.ts
|
|
5218
|
+
import { Command as Command6 } from "commander";
|
|
5219
|
+
import { detectAnomalies as detectAnomalies2, findRootCause as findRootCause2 } from "autotel-mcp";
|
|
5220
|
+
async function runCorrelate(flags) {
|
|
5221
|
+
await runInvestigate(
|
|
5222
|
+
"correlate trace",
|
|
5223
|
+
flags,
|
|
5224
|
+
async (backend) => backend.getCorrelatedSignals(flags.traceId)
|
|
5225
|
+
);
|
|
5226
|
+
}
|
|
5227
|
+
async function runExplainSlowdown(flags) {
|
|
5228
|
+
await runInvestigate("correlate explain-slowdown", flags, async (backend) => {
|
|
5229
|
+
const lookback = flags.lookbackMinutes ?? 60;
|
|
5230
|
+
const nowMs = Date.now();
|
|
5231
|
+
const result = await backend.searchTraces({
|
|
5232
|
+
service: flags.service,
|
|
5233
|
+
startTimeUnixMs: nowMs - lookback * 60 * 1e3,
|
|
5234
|
+
endTimeUnixMs: nowMs,
|
|
5235
|
+
limit: 100
|
|
5236
|
+
});
|
|
5237
|
+
const anomalies = detectAnomalies2(result.items, { service: flags.service });
|
|
5238
|
+
const findings = await Promise.all(
|
|
5239
|
+
anomalies.map(async (anomaly) => {
|
|
5240
|
+
const sampleTraceId = anomaly.affectedTraceIds[0];
|
|
5241
|
+
if (!sampleTraceId) {
|
|
5242
|
+
return { anomaly, rootCause: null, correlatedSignals: null };
|
|
5243
|
+
}
|
|
5244
|
+
const trace = await backend.getTrace(sampleTraceId);
|
|
5245
|
+
const rootCause = trace ? findRootCause2(trace) : null;
|
|
5246
|
+
const correlatedSignals = await backend.getCorrelatedSignals(sampleTraceId);
|
|
5247
|
+
return { anomaly, rootCause, correlatedSignals };
|
|
5248
|
+
})
|
|
5249
|
+
);
|
|
5250
|
+
return {
|
|
5251
|
+
service: flags.service,
|
|
5252
|
+
lookbackMinutes: lookback,
|
|
5253
|
+
anomalyCount: anomalies.length,
|
|
5254
|
+
findings
|
|
5255
|
+
};
|
|
5256
|
+
});
|
|
5257
|
+
}
|
|
5258
|
+
function registerCorrelateCommands(program) {
|
|
5259
|
+
const correlateCmd = new Command6("correlate").description(
|
|
5260
|
+
"Cross-signal correlation (JSON)"
|
|
5261
|
+
);
|
|
5262
|
+
const traceCmd = new Command6("trace").description("Trace + metrics + correlated logs for a trace ID").argument("<traceId>", "Trace ID").action(async function(traceId) {
|
|
5263
|
+
await runCorrelate({
|
|
5264
|
+
...backendFlagsFromOpts(this.optsWithGlobals()),
|
|
5265
|
+
traceId
|
|
5266
|
+
});
|
|
5267
|
+
});
|
|
5268
|
+
const slowdownCmd = new Command6("explain-slowdown").description("Identify when/why a service degraded").requiredOption("--service <name>", "Service name").option("--lookback-minutes <n>", "Lookback in minutes", intArg).action(async function() {
|
|
5269
|
+
const o = this.optsWithGlobals();
|
|
5270
|
+
await runExplainSlowdown({
|
|
5271
|
+
...backendFlagsFromOpts(o),
|
|
5272
|
+
service: o.service,
|
|
5273
|
+
lookbackMinutes: o.lookbackMinutes
|
|
5274
|
+
});
|
|
5275
|
+
});
|
|
5276
|
+
addBackendFlags(correlateCmd);
|
|
5277
|
+
correlateCmd.addCommand(traceCmd);
|
|
5278
|
+
correlateCmd.addCommand(slowdownCmd);
|
|
5279
|
+
program.addCommand(correlateCmd);
|
|
5280
|
+
}
|
|
5281
|
+
|
|
5282
|
+
// src/commands/investigate/llm.ts
|
|
5283
|
+
import {
|
|
5284
|
+
collectUsage,
|
|
5285
|
+
listModels,
|
|
5286
|
+
getModelStats,
|
|
5287
|
+
rankExpensiveTraces,
|
|
5288
|
+
rankSlowTraces,
|
|
5289
|
+
listToolUsage,
|
|
5290
|
+
toTraceSearchQuery as toTraceSearchQuery2
|
|
5291
|
+
} from "autotel-mcp";
|
|
5292
|
+
import { Command as Command7 } from "commander";
|
|
5293
|
+
function parseDateToUnixMs(value) {
|
|
5294
|
+
if (!value) return void 0;
|
|
5295
|
+
const parsed = Date.parse(value);
|
|
5296
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
5297
|
+
}
|
|
5298
|
+
async function collectTracesForAnalytics(backend, input2) {
|
|
5299
|
+
const result = await backend.searchTraces(
|
|
5300
|
+
toTraceSearchQuery2({
|
|
5301
|
+
serviceName: input2.serviceName,
|
|
5302
|
+
genAiSystem: input2.genAiSystem,
|
|
5303
|
+
genAiRequestModel: input2.genAiRequestModel,
|
|
5304
|
+
genAiResponseModel: input2.genAiResponseModel,
|
|
5305
|
+
limit: input2.limit ?? 1e3
|
|
5306
|
+
})
|
|
5307
|
+
);
|
|
5308
|
+
let filtered = result.items;
|
|
5309
|
+
const startUnixMs = parseDateToUnixMs(input2.startTime);
|
|
5310
|
+
const endUnixMs = parseDateToUnixMs(input2.endTime);
|
|
5311
|
+
if (startUnixMs !== void 0) {
|
|
5312
|
+
filtered = filtered.filter(
|
|
5313
|
+
(trace) => trace.spans.some((span) => span.startTimeUnixMs >= startUnixMs)
|
|
5314
|
+
);
|
|
5315
|
+
}
|
|
5316
|
+
if (endUnixMs !== void 0) {
|
|
5317
|
+
filtered = filtered.filter(
|
|
5318
|
+
(trace) => trace.spans.some((span) => span.startTimeUnixMs <= endUnixMs)
|
|
5319
|
+
);
|
|
5320
|
+
}
|
|
5321
|
+
return filtered;
|
|
5322
|
+
}
|
|
5323
|
+
async function runLlmUsage(flags) {
|
|
5324
|
+
await runInvestigate("llm usage", flags, async (backend) => {
|
|
5325
|
+
const traces = await collectTracesForAnalytics(backend, flags);
|
|
5326
|
+
const report = collectUsage(traces);
|
|
5327
|
+
return {
|
|
5328
|
+
period: {
|
|
5329
|
+
startTime: flags.startTime ?? null,
|
|
5330
|
+
endTime: flags.endTime ?? null
|
|
5331
|
+
},
|
|
5332
|
+
filters: {
|
|
5333
|
+
serviceName: flags.serviceName ?? null,
|
|
5334
|
+
genAiSystem: flags.genAiSystem ?? null,
|
|
5335
|
+
genAiRequestModel: flags.genAiRequestModel ?? null,
|
|
5336
|
+
genAiResponseModel: flags.genAiResponseModel ?? null
|
|
5337
|
+
},
|
|
5338
|
+
summary: {
|
|
5339
|
+
totalRequests: report.totalRequests,
|
|
5340
|
+
totalPromptTokens: report.totalPromptTokens,
|
|
5341
|
+
totalCompletionTokens: report.totalCompletionTokens,
|
|
5342
|
+
totalTokens: report.totalTokens,
|
|
5343
|
+
totalCostUsd: report.totalCostUsd,
|
|
5344
|
+
unpricedRequests: report.unpricedRequests
|
|
5345
|
+
},
|
|
5346
|
+
byModel: report.byModel,
|
|
5347
|
+
byService: report.byService
|
|
5348
|
+
};
|
|
5349
|
+
});
|
|
5350
|
+
}
|
|
5351
|
+
async function runLlmModels(flags) {
|
|
5352
|
+
await runInvestigate("llm models", flags, async (backend) => {
|
|
5353
|
+
const traces = await collectTracesForAnalytics(backend, flags);
|
|
5354
|
+
const models = listModels(traces).slice(0, flags.limit ?? 1e3);
|
|
5355
|
+
return { count: models.length, models };
|
|
5356
|
+
});
|
|
5357
|
+
}
|
|
5358
|
+
async function runLlmModelStats(flags) {
|
|
5359
|
+
await runInvestigate("llm model-stats", flags, async (backend) => {
|
|
5360
|
+
const traces = await collectTracesForAnalytics(backend, flags);
|
|
5361
|
+
const stats = getModelStats(traces, flags.modelName);
|
|
5362
|
+
if (!stats) {
|
|
5363
|
+
return {
|
|
5364
|
+
error: `No traces found for model '${flags.modelName}' in the specified time range`
|
|
5365
|
+
};
|
|
5366
|
+
}
|
|
5367
|
+
return stats;
|
|
5368
|
+
});
|
|
5369
|
+
}
|
|
5370
|
+
async function runLlmExpensive(flags) {
|
|
5371
|
+
await runInvestigate("llm expensive", flags, async (backend) => {
|
|
5372
|
+
const traces = await collectTracesForAnalytics(backend, flags);
|
|
5373
|
+
let ranked = rankExpensiveTraces(traces);
|
|
5374
|
+
if (flags.minTokens !== void 0) {
|
|
5375
|
+
ranked = ranked.filter((t) => t.tokens.total >= flags.minTokens);
|
|
5376
|
+
}
|
|
5377
|
+
ranked = ranked.slice(0, flags.limit ?? 10);
|
|
5378
|
+
return { count: ranked.length, traces: ranked };
|
|
5379
|
+
});
|
|
5380
|
+
}
|
|
5381
|
+
async function runLlmSlow(flags) {
|
|
5382
|
+
await runInvestigate("llm slow", flags, async (backend) => {
|
|
5383
|
+
const traces = await collectTracesForAnalytics(backend, flags);
|
|
5384
|
+
let ranked = rankSlowTraces(traces);
|
|
5385
|
+
if (flags.minDurationMs !== void 0) {
|
|
5386
|
+
ranked = ranked.filter((t) => t.durationMs >= flags.minDurationMs);
|
|
5387
|
+
}
|
|
5388
|
+
ranked = ranked.slice(0, flags.limit ?? 10);
|
|
5389
|
+
return { count: ranked.length, traces: ranked };
|
|
5390
|
+
});
|
|
5391
|
+
}
|
|
5392
|
+
async function runLlmTools(flags) {
|
|
5393
|
+
await runInvestigate("llm tools", flags, async (backend) => {
|
|
5394
|
+
const traces = await collectTracesForAnalytics(backend, flags);
|
|
5395
|
+
const tools = listToolUsage(traces).slice(0, flags.limit ?? 1e3);
|
|
5396
|
+
return {
|
|
5397
|
+
count: tools.length,
|
|
5398
|
+
totalCalls: tools.reduce((sum, t) => sum + t.usageCount, 0),
|
|
5399
|
+
tools
|
|
5400
|
+
};
|
|
5401
|
+
});
|
|
5402
|
+
}
|
|
5403
|
+
function registerLlmCommands(program) {
|
|
5404
|
+
const llmCmd = new Command7("llm").description(
|
|
5405
|
+
"LLM analytics (cost, models, expensive/slow traces, tools)"
|
|
5406
|
+
);
|
|
5407
|
+
const commonOpts = (cmd) => cmd.option("--start-time <iso>", "Start of window (ISO 8601)").option("--end-time <iso>", "End of window (ISO 8601)").option("--service-name <name>", "Service filter").option("--gen-ai-system <name>", "gen_ai.system filter").option("--gen-ai-request-model <name>", "gen_ai.request.model filter").option("--gen-ai-response-model <name>", "gen_ai.response.model filter").option("--limit <n>", "Max results", intArg);
|
|
5408
|
+
const usageCmd = commonOpts(new Command7("usage")).description("Aggregate token usage by model and service").action(async function() {
|
|
5409
|
+
const o = this.optsWithGlobals();
|
|
5410
|
+
await runLlmUsage({
|
|
5411
|
+
...backendFlagsFromOpts(o),
|
|
5412
|
+
startTime: o.startTime,
|
|
5413
|
+
endTime: o.endTime,
|
|
5414
|
+
serviceName: o.serviceName,
|
|
5415
|
+
genAiSystem: o.genAiSystem,
|
|
5416
|
+
genAiRequestModel: o.genAiRequestModel,
|
|
5417
|
+
genAiResponseModel: o.genAiResponseModel,
|
|
5418
|
+
limit: o.limit
|
|
5419
|
+
});
|
|
5420
|
+
});
|
|
5421
|
+
const modelsCmd = commonOpts(new Command7("models")).description("Discover LLM models in use").action(async function() {
|
|
5422
|
+
const o = this.optsWithGlobals();
|
|
5423
|
+
await runLlmModels({
|
|
5424
|
+
...backendFlagsFromOpts(o),
|
|
5425
|
+
startTime: o.startTime,
|
|
5426
|
+
endTime: o.endTime,
|
|
5427
|
+
serviceName: o.serviceName,
|
|
5428
|
+
genAiSystem: o.genAiSystem,
|
|
5429
|
+
limit: o.limit
|
|
5430
|
+
});
|
|
5431
|
+
});
|
|
5432
|
+
const modelStatsCmd = commonOpts(new Command7("model-stats")).description("Latency/token/error stats for one LLM model").requiredOption("--model-name <name>", "Model to inspect").action(async function() {
|
|
5433
|
+
const o = this.optsWithGlobals();
|
|
5434
|
+
await runLlmModelStats({
|
|
5435
|
+
...backendFlagsFromOpts(o),
|
|
5436
|
+
modelName: o.modelName,
|
|
5437
|
+
startTime: o.startTime,
|
|
5438
|
+
endTime: o.endTime,
|
|
5439
|
+
serviceName: o.serviceName
|
|
5440
|
+
});
|
|
5441
|
+
});
|
|
5442
|
+
const expensiveCmd = commonOpts(new Command7("expensive")).description("Traces with highest total LLM token usage").option("--min-tokens <n>", "Minimum token threshold", intArg).action(async function() {
|
|
5443
|
+
const o = this.optsWithGlobals();
|
|
5444
|
+
await runLlmExpensive({
|
|
5445
|
+
...backendFlagsFromOpts(o),
|
|
5446
|
+
startTime: o.startTime,
|
|
5447
|
+
endTime: o.endTime,
|
|
5448
|
+
serviceName: o.serviceName,
|
|
5449
|
+
genAiRequestModel: o.genAiRequestModel,
|
|
5450
|
+
genAiResponseModel: o.genAiResponseModel,
|
|
5451
|
+
minTokens: o.minTokens,
|
|
5452
|
+
limit: o.limit
|
|
5453
|
+
});
|
|
5454
|
+
});
|
|
5455
|
+
const slowCmd = commonOpts(new Command7("slow")).description("Slowest traces that include LLM spans").option("--min-duration-ms <n>", "Minimum duration", intArg).action(async function() {
|
|
5456
|
+
const o = this.optsWithGlobals();
|
|
5457
|
+
await runLlmSlow({
|
|
5458
|
+
...backendFlagsFromOpts(o),
|
|
5459
|
+
startTime: o.startTime,
|
|
5460
|
+
endTime: o.endTime,
|
|
5461
|
+
serviceName: o.serviceName,
|
|
5462
|
+
genAiRequestModel: o.genAiRequestModel,
|
|
5463
|
+
genAiResponseModel: o.genAiResponseModel,
|
|
5464
|
+
minDurationMs: o.minDurationMs,
|
|
5465
|
+
limit: o.limit
|
|
5466
|
+
});
|
|
5467
|
+
});
|
|
5468
|
+
const toolsCmd = commonOpts(new Command7("tools")).description("Discover tool/function spans grouped by tool name").action(async function() {
|
|
5469
|
+
const o = this.optsWithGlobals();
|
|
5470
|
+
await runLlmTools({
|
|
5471
|
+
...backendFlagsFromOpts(o),
|
|
5472
|
+
startTime: o.startTime,
|
|
5473
|
+
endTime: o.endTime,
|
|
5474
|
+
serviceName: o.serviceName,
|
|
5475
|
+
genAiSystem: o.genAiSystem,
|
|
5476
|
+
limit: o.limit
|
|
5477
|
+
});
|
|
5478
|
+
});
|
|
5479
|
+
addBackendFlags(llmCmd);
|
|
5480
|
+
llmCmd.addCommand(usageCmd);
|
|
5481
|
+
llmCmd.addCommand(modelsCmd);
|
|
5482
|
+
llmCmd.addCommand(modelStatsCmd);
|
|
5483
|
+
llmCmd.addCommand(expensiveCmd);
|
|
5484
|
+
llmCmd.addCommand(slowCmd);
|
|
5485
|
+
llmCmd.addCommand(toolsCmd);
|
|
5486
|
+
program.addCommand(llmCmd);
|
|
5487
|
+
}
|
|
5488
|
+
|
|
5489
|
+
// src/commands/investigate/semconv.ts
|
|
5490
|
+
import {
|
|
5491
|
+
clearSemanticConventionCache,
|
|
5492
|
+
getSemanticConventionNamespace,
|
|
5493
|
+
listSemanticConventionNamespaces
|
|
5494
|
+
} from "autotel-mcp";
|
|
5495
|
+
import { Command as Command8 } from "commander";
|
|
5496
|
+
async function runSemconvList(flags) {
|
|
5497
|
+
await runStatic("semconv list", flags, async () => ({
|
|
5498
|
+
namespaces: await listSemanticConventionNamespaces()
|
|
5499
|
+
}));
|
|
5500
|
+
}
|
|
5501
|
+
async function runSemconvGet(flags) {
|
|
5502
|
+
await runStatic(
|
|
5503
|
+
"semconv get",
|
|
5504
|
+
flags,
|
|
5505
|
+
async () => getSemanticConventionNamespace(flags.namespace)
|
|
5506
|
+
);
|
|
5507
|
+
}
|
|
5508
|
+
async function runSemconvRefresh(flags) {
|
|
5509
|
+
await runStatic("semconv refresh", flags, async () => {
|
|
5510
|
+
clearSemanticConventionCache();
|
|
5511
|
+
return { cleared: true };
|
|
5512
|
+
});
|
|
5513
|
+
}
|
|
5514
|
+
function registerSemconvCommands(program) {
|
|
5515
|
+
const semconvCmd = new Command8("semconv").description(
|
|
5516
|
+
"OpenTelemetry semantic conventions lookup (JSON)"
|
|
5517
|
+
);
|
|
5518
|
+
const listCmd = addStaticFlags(new Command8("list")).description("List semconv namespaces").action(async function() {
|
|
5519
|
+
await runSemconvList(staticFlagsFromOpts(this.optsWithGlobals()));
|
|
5520
|
+
});
|
|
5521
|
+
const getCmd = addStaticFlags(new Command8("get")).description("Get groups for one namespace").argument("<namespace>", "Namespace (e.g. http, rpc, database)").action(async function(namespace) {
|
|
5522
|
+
await runSemconvGet({
|
|
5523
|
+
...staticFlagsFromOpts(this.optsWithGlobals()),
|
|
5524
|
+
namespace
|
|
5525
|
+
});
|
|
5526
|
+
});
|
|
5527
|
+
const refreshCmd = addStaticFlags(new Command8("refresh")).description("Clear semconv cache").action(async function() {
|
|
5528
|
+
await runSemconvRefresh(staticFlagsFromOpts(this.optsWithGlobals()));
|
|
5529
|
+
});
|
|
5530
|
+
semconvCmd.addCommand(listCmd);
|
|
5531
|
+
semconvCmd.addCommand(getCmd);
|
|
5532
|
+
semconvCmd.addCommand(refreshCmd);
|
|
5533
|
+
program.addCommand(semconvCmd);
|
|
5534
|
+
}
|
|
5535
|
+
|
|
5536
|
+
// src/commands/investigate/instrumentation.ts
|
|
5537
|
+
import {
|
|
5538
|
+
scoreSpan,
|
|
5539
|
+
suggestInstrumentationFixes,
|
|
5540
|
+
buildInstrumentationGuide
|
|
5541
|
+
} from "autotel-mcp";
|
|
5542
|
+
import { Command as Command9 } from "commander";
|
|
5543
|
+
import * as fs7 from "fs";
|
|
5544
|
+
function readSpanFromStdinOrFile(spanFile) {
|
|
5545
|
+
let raw;
|
|
5546
|
+
if (spanFile) {
|
|
5547
|
+
raw = fs7.readFileSync(spanFile, "utf8");
|
|
5548
|
+
} else {
|
|
5549
|
+
raw = fs7.readFileSync(0, "utf8");
|
|
5550
|
+
}
|
|
5551
|
+
const parsed = JSON.parse(raw);
|
|
5552
|
+
if (typeof parsed.operationName !== "string" || typeof parsed.serviceName !== "string" || typeof parsed.tags !== "object" || typeof parsed.hasError !== "boolean") {
|
|
5553
|
+
throw new AutotelError({
|
|
5554
|
+
type: "validation",
|
|
5555
|
+
code: "AUTOTEL_E_INVALID_INPUT",
|
|
5556
|
+
message: "score expects JSON with operationName, serviceName, tags, hasError",
|
|
5557
|
+
retryable: false,
|
|
5558
|
+
expected: {
|
|
5559
|
+
shape: {
|
|
5560
|
+
operationName: "string",
|
|
5561
|
+
serviceName: "string",
|
|
5562
|
+
tags: "Record<string, string | number | boolean>",
|
|
5563
|
+
hasError: "boolean"
|
|
5564
|
+
}
|
|
5565
|
+
}
|
|
5566
|
+
});
|
|
5567
|
+
}
|
|
5568
|
+
return parsed;
|
|
5569
|
+
}
|
|
5570
|
+
async function runScoreSpan(flags) {
|
|
5571
|
+
await runStatic("score", flags, async () => {
|
|
5572
|
+
const span = readSpanFromStdinOrFile(flags.spanFile);
|
|
5573
|
+
const result = scoreSpan(span);
|
|
5574
|
+
const suggestions = suggestInstrumentationFixes(span);
|
|
5575
|
+
return { ...result, suggestions };
|
|
5576
|
+
});
|
|
5577
|
+
}
|
|
5578
|
+
async function runScoreExplain(flags) {
|
|
5579
|
+
await runStatic("score explain", flags, async () => ({
|
|
5580
|
+
guide: buildInstrumentationGuide()
|
|
5581
|
+
}));
|
|
5582
|
+
}
|
|
5583
|
+
function registerScoreCommands(program) {
|
|
5584
|
+
const scoreCmd = addStaticFlags(new Command9("score")).description("Score a span for instrumentation quality (JSON)").option("--span-file <path>", "Read span JSON from file (default: stdin)").action(async function() {
|
|
5585
|
+
const o = this.optsWithGlobals();
|
|
5586
|
+
await runScoreSpan({
|
|
5587
|
+
...staticFlagsFromOpts(o),
|
|
5588
|
+
spanFile: o.spanFile
|
|
5589
|
+
});
|
|
5590
|
+
});
|
|
5591
|
+
const explainCmd = addStaticFlags(new Command9("explain")).description("Explain the instrumentation scoring rubric").action(async function() {
|
|
5592
|
+
await runScoreExplain(staticFlagsFromOpts(this.optsWithGlobals()));
|
|
5593
|
+
});
|
|
5594
|
+
scoreCmd.addCommand(explainCmd);
|
|
5595
|
+
program.addCommand(scoreCmd);
|
|
5596
|
+
}
|
|
5597
|
+
|
|
5598
|
+
// src/commands/investigate/collector.ts
|
|
5599
|
+
import {
|
|
5600
|
+
validateOtlpReceiverConfig,
|
|
5601
|
+
suggestCollectorConfig,
|
|
5602
|
+
buildCollectorGuide,
|
|
5603
|
+
listCollectorVersions,
|
|
5604
|
+
listCollectorComponents,
|
|
5605
|
+
getCollectorComponentSchema,
|
|
5606
|
+
getCollectorComponentReadme,
|
|
5607
|
+
validateCollectorComponentConfig,
|
|
5608
|
+
refreshCollectorCatalog,
|
|
5609
|
+
resolveCollectorVersion
|
|
5610
|
+
} from "autotel-mcp";
|
|
5611
|
+
import { Command as Command10 } from "commander";
|
|
5612
|
+
import * as fs8 from "fs";
|
|
5613
|
+
function readJsonFromStdinOrFile(file) {
|
|
5614
|
+
const raw = file ? fs8.readFileSync(file, "utf8") : fs8.readFileSync(0, "utf8");
|
|
5615
|
+
return JSON.parse(raw);
|
|
5616
|
+
}
|
|
5617
|
+
function assertVersion(version) {
|
|
5618
|
+
if (version === void 0) return;
|
|
5619
|
+
if (!/^\d+\.\d+\.\d+$/.test(version)) {
|
|
5620
|
+
throw new AutotelError({
|
|
5621
|
+
type: "validation",
|
|
5622
|
+
code: "AUTOTEL_E_INVALID_INPUT",
|
|
5623
|
+
message: `--version must be semver (got "${version}")`,
|
|
5624
|
+
retryable: false
|
|
5625
|
+
});
|
|
5626
|
+
}
|
|
5627
|
+
}
|
|
5628
|
+
async function runCollectorValidate(flags) {
|
|
5629
|
+
await runStatic("collector validate", flags, async () => {
|
|
5630
|
+
const config = readJsonFromStdinOrFile(flags.configFile);
|
|
5631
|
+
return validateOtlpReceiverConfig(config);
|
|
5632
|
+
});
|
|
5633
|
+
}
|
|
5634
|
+
async function runCollectorSuggest(flags) {
|
|
5635
|
+
await runStatic("collector suggest", flags, async () => ({
|
|
5636
|
+
suggestion: suggestCollectorConfig()
|
|
5637
|
+
}));
|
|
5638
|
+
}
|
|
5639
|
+
async function runCollectorExplain(flags) {
|
|
5640
|
+
await runStatic("collector explain", flags, async () => ({
|
|
5641
|
+
guide: buildCollectorGuide()
|
|
5642
|
+
}));
|
|
5643
|
+
}
|
|
5644
|
+
async function runCollectorVersions(flags) {
|
|
5645
|
+
await runStatic("collector versions", flags, async () => ({
|
|
5646
|
+
versions: await listCollectorVersions()
|
|
5647
|
+
}));
|
|
5648
|
+
}
|
|
5649
|
+
async function runCollectorComponents(flags) {
|
|
5650
|
+
await runStatic("collector components", flags, async () => {
|
|
5651
|
+
assertVersion(flags.version);
|
|
5652
|
+
const resolvedVersion = await resolveCollectorVersion(flags.version);
|
|
5653
|
+
const components = await listCollectorComponents(resolvedVersion);
|
|
5654
|
+
if (flags.kind) {
|
|
5655
|
+
return {
|
|
5656
|
+
version: resolvedVersion,
|
|
5657
|
+
kind: flags.kind,
|
|
5658
|
+
components: components[flags.kind]
|
|
5659
|
+
};
|
|
5660
|
+
}
|
|
5661
|
+
return { version: resolvedVersion, components };
|
|
5662
|
+
});
|
|
5663
|
+
}
|
|
5664
|
+
async function runCollectorSchema(flags) {
|
|
5665
|
+
await runStatic("collector schema", flags, async () => {
|
|
5666
|
+
assertVersion(flags.version);
|
|
5667
|
+
const resolvedVersion = await resolveCollectorVersion(flags.version);
|
|
5668
|
+
const schema = await getCollectorComponentSchema(
|
|
5669
|
+
flags.kind,
|
|
5670
|
+
flags.name,
|
|
5671
|
+
resolvedVersion
|
|
5672
|
+
);
|
|
5673
|
+
return { version: resolvedVersion, kind: flags.kind, name: flags.name, schema };
|
|
5674
|
+
});
|
|
5675
|
+
}
|
|
5676
|
+
async function runCollectorReadme(flags) {
|
|
5677
|
+
await runStatic("collector readme", flags, async () => {
|
|
5678
|
+
assertVersion(flags.version);
|
|
5679
|
+
const resolvedVersion = await resolveCollectorVersion(flags.version);
|
|
5680
|
+
const readme = await getCollectorComponentReadme(
|
|
5681
|
+
flags.kind,
|
|
5682
|
+
flags.name,
|
|
5683
|
+
resolvedVersion
|
|
5684
|
+
);
|
|
5685
|
+
return { version: resolvedVersion, kind: flags.kind, name: flags.name, readme };
|
|
5686
|
+
});
|
|
5687
|
+
}
|
|
5688
|
+
async function runCollectorValidateComponent(flags) {
|
|
5689
|
+
await runStatic("collector validate-component", flags, async () => {
|
|
5690
|
+
assertVersion(flags.version);
|
|
5691
|
+
const resolvedVersion = await resolveCollectorVersion(flags.version);
|
|
5692
|
+
const config = readJsonFromStdinOrFile(flags.configFile);
|
|
5693
|
+
const result = await validateCollectorComponentConfig({
|
|
5694
|
+
kind: flags.kind,
|
|
5695
|
+
name: flags.name,
|
|
5696
|
+
version: resolvedVersion,
|
|
5697
|
+
config
|
|
5698
|
+
});
|
|
5699
|
+
return { version: resolvedVersion, kind: flags.kind, name: flags.name, ...result };
|
|
5700
|
+
});
|
|
5701
|
+
}
|
|
5702
|
+
async function runCollectorRefresh(flags) {
|
|
5703
|
+
await runStatic(
|
|
5704
|
+
"collector refresh",
|
|
5705
|
+
flags,
|
|
5706
|
+
async () => refreshCollectorCatalog()
|
|
5707
|
+
);
|
|
5708
|
+
}
|
|
5709
|
+
function registerCollectorCommands(program) {
|
|
5710
|
+
const collectorCmd = new Command10("collector").description(
|
|
5711
|
+
"OpenTelemetry Collector config + schema commands (JSON)"
|
|
5712
|
+
);
|
|
5713
|
+
const validateCmd = addStaticFlags(new Command10("validate")).description("Validate an OTLP receiver config fragment").option("--config-file <path>", "Read JSON config (default: stdin)").action(async function() {
|
|
5714
|
+
const o = this.optsWithGlobals();
|
|
5715
|
+
await runCollectorValidate({
|
|
5716
|
+
...staticFlagsFromOpts(o),
|
|
5717
|
+
configFile: o.configFile
|
|
5718
|
+
});
|
|
5719
|
+
});
|
|
5720
|
+
const suggestCmd = addStaticFlags(new Command10("suggest")).description("Print a minimal OTLP receiver config").action(async function() {
|
|
5721
|
+
await runCollectorSuggest(staticFlagsFromOpts(this.optsWithGlobals()));
|
|
5722
|
+
});
|
|
5723
|
+
const explainCmd = addStaticFlags(new Command10("explain")).description("Explain OTLP receiver config shape + defaults").action(async function() {
|
|
5724
|
+
await runCollectorExplain(staticFlagsFromOpts(this.optsWithGlobals()));
|
|
5725
|
+
});
|
|
5726
|
+
const versionsCmd = addStaticFlags(new Command10("versions")).description("List supported collector schema versions").action(async function() {
|
|
5727
|
+
await runCollectorVersions(staticFlagsFromOpts(this.optsWithGlobals()));
|
|
5728
|
+
});
|
|
5729
|
+
const componentsCmd = addStaticFlags(new Command10("components")).description("List components for a version (optionally filter by kind)").option("--version <semver>", "Collector version (e.g. 0.110.0)").option(
|
|
5730
|
+
"--kind <kind>",
|
|
5731
|
+
"receiver | processor | exporter | connector | extension"
|
|
5732
|
+
).action(async function() {
|
|
5733
|
+
const o = this.optsWithGlobals();
|
|
5734
|
+
await runCollectorComponents({
|
|
5735
|
+
...staticFlagsFromOpts(o),
|
|
5736
|
+
version: o.version,
|
|
5737
|
+
kind: o.kind
|
|
5738
|
+
});
|
|
5739
|
+
});
|
|
5740
|
+
const schemaCmd = addStaticFlags(new Command10("schema")).description("Get JSON Schema for a collector component").requiredOption("--kind <kind>", "Component kind").requiredOption("--name <name>", "Component name").option("--version <semver>", "Collector version").action(async function() {
|
|
5741
|
+
const o = this.optsWithGlobals();
|
|
5742
|
+
await runCollectorSchema({
|
|
5743
|
+
...staticFlagsFromOpts(o),
|
|
5744
|
+
kind: o.kind,
|
|
5745
|
+
name: o.name,
|
|
5746
|
+
version: o.version
|
|
5747
|
+
});
|
|
5748
|
+
});
|
|
5749
|
+
const readmeCmd = addStaticFlags(new Command10("readme")).description("Get README for a collector component").requiredOption("--kind <kind>", "Component kind").requiredOption("--name <name>", "Component name").option("--version <semver>", "Collector version").action(async function() {
|
|
5750
|
+
const o = this.optsWithGlobals();
|
|
5751
|
+
await runCollectorReadme({
|
|
5752
|
+
...staticFlagsFromOpts(o),
|
|
5753
|
+
kind: o.kind,
|
|
5754
|
+
name: o.name,
|
|
5755
|
+
version: o.version
|
|
5756
|
+
});
|
|
5757
|
+
});
|
|
5758
|
+
const validateComponentCmd = addStaticFlags(
|
|
5759
|
+
new Command10("validate-component")
|
|
5760
|
+
).description("Validate component config against upstream schema").requiredOption("--kind <kind>", "Component kind").requiredOption("--name <name>", "Component name").option("--version <semver>", "Collector version").option("--config-file <path>", "Read JSON config (default: stdin)").action(async function() {
|
|
5761
|
+
const o = this.optsWithGlobals();
|
|
5762
|
+
await runCollectorValidateComponent({
|
|
5763
|
+
...staticFlagsFromOpts(o),
|
|
5764
|
+
kind: o.kind,
|
|
5765
|
+
name: o.name,
|
|
5766
|
+
version: o.version,
|
|
5767
|
+
configFile: o.configFile
|
|
5768
|
+
});
|
|
5769
|
+
});
|
|
5770
|
+
const refreshCmd = addStaticFlags(new Command10("refresh")).description("Refresh in-memory collector metadata cache").action(async function() {
|
|
5771
|
+
await runCollectorRefresh(staticFlagsFromOpts(this.optsWithGlobals()));
|
|
5772
|
+
});
|
|
5773
|
+
collectorCmd.addCommand(validateCmd);
|
|
5774
|
+
collectorCmd.addCommand(suggestCmd);
|
|
5775
|
+
collectorCmd.addCommand(explainCmd);
|
|
5776
|
+
collectorCmd.addCommand(versionsCmd);
|
|
5777
|
+
collectorCmd.addCommand(componentsCmd);
|
|
5778
|
+
collectorCmd.addCommand(schemaCmd);
|
|
5779
|
+
collectorCmd.addCommand(readmeCmd);
|
|
5780
|
+
collectorCmd.addCommand(validateComponentCmd);
|
|
5781
|
+
collectorCmd.addCommand(refreshCmd);
|
|
5782
|
+
program.addCommand(collectorCmd);
|
|
5783
|
+
}
|
|
5784
|
+
|
|
3371
5785
|
// src/cli.ts
|
|
3372
5786
|
function createProgram() {
|
|
3373
|
-
const program = new
|
|
5787
|
+
const program = new Command11();
|
|
3374
5788
|
program.name("autotel").description("CLI for autotel - setup wizard, diagnostics, and incremental features").version("0.1.0");
|
|
3375
5789
|
const addGlobalOptions = (cmd) => {
|
|
3376
5790
|
return cmd.option("--cwd <path>", "Target directory", process.cwd()).option("--verbose", "Show detailed output").option("--quiet", "Only show warnings and errors");
|
|
3377
5791
|
};
|
|
3378
|
-
const initCmd = new
|
|
5792
|
+
const initCmd = new Command11("init").description("Initialize autotel in your project").option("--dry-run", "Skip installation and print what would be done").option("--no-install", "Generate files only, skip package installation").option("--print-install-cmd", "Output the install command without running it").option("-y, --yes", "Accept defaults, non-interactive").option("--preset <name>", "Use a quick preset (e.g., node-datadog-pino)").option("--force", "Overwrite existing config (creates backup first)").option("--workspace-root", "Install at workspace root instead of package root").option("--no-detect", "Skip auto-detection of installed deps").option("--detect-only", "Run detection, print the plan, write nothing").option("--plan <path>", "Apply a pre-built InitPlan JSON file").option("--input <path>", "Read InitPlan JSON from stdin (-) or a file").option("--scan-env", "Consent to reading .env / .env.local for backend detection").option("--json", "Emit machine-readable JSON").option("--output-file <path>", "Persist JSON output to this file").option("--no-secrets-in-output", "Redact secret-shaped values").option("--no-interactive", "Never prompt; fail if input would be required").action(async (opts) => {
|
|
3379
5793
|
const options = {
|
|
3380
5794
|
cwd: opts.cwd ?? process.cwd(),
|
|
3381
5795
|
dryRun: opts.dryRun ?? false,
|
|
3382
|
-
noInstall: opts.
|
|
5796
|
+
noInstall: opts.install === false,
|
|
3383
5797
|
printInstallCmd: opts.printInstallCmd ?? false,
|
|
3384
5798
|
verbose: opts.verbose ?? false,
|
|
3385
5799
|
quiet: opts.quiet ?? false,
|
|
3386
5800
|
workspaceRoot: opts.workspaceRoot ?? false,
|
|
3387
5801
|
yes: opts.yes ?? false,
|
|
3388
5802
|
preset: opts.preset,
|
|
3389
|
-
force: opts.force ?? false
|
|
5803
|
+
force: opts.force ?? false,
|
|
5804
|
+
noDetect: opts.detect === false,
|
|
5805
|
+
detectOnly: opts.detectOnly ?? false,
|
|
5806
|
+
plan: opts.plan,
|
|
5807
|
+
input: opts.input,
|
|
5808
|
+
scanEnv: opts.scanEnv ?? false,
|
|
5809
|
+
json: opts.json ?? false,
|
|
5810
|
+
outputFile: opts.outputFile,
|
|
5811
|
+
noSecrets: opts.secretsInOutput === false,
|
|
5812
|
+
noInteractive: opts.interactive === false
|
|
3390
5813
|
};
|
|
3391
5814
|
if (options.dryRun) {
|
|
3392
5815
|
options.noInstall = true;
|
|
@@ -3396,7 +5819,7 @@ function createProgram() {
|
|
|
3396
5819
|
});
|
|
3397
5820
|
addGlobalOptions(initCmd);
|
|
3398
5821
|
program.addCommand(initCmd);
|
|
3399
|
-
const doctorCmd = new
|
|
5822
|
+
const doctorCmd = new Command11("doctor").description("Run diagnostics on your autotel setup").option("--json", "Output machine-readable JSON").option("--fix", "Auto-fix resolvable issues").option("--list-checks", "List all available checks").option("--env-file <path>", "Specify env file to check").action(async (opts) => {
|
|
3400
5823
|
const options = {
|
|
3401
5824
|
cwd: opts.cwd ?? process.cwd(),
|
|
3402
5825
|
dryRun: false,
|
|
@@ -3414,7 +5837,7 @@ function createProgram() {
|
|
|
3414
5837
|
});
|
|
3415
5838
|
addGlobalOptions(doctorCmd);
|
|
3416
5839
|
program.addCommand(doctorCmd);
|
|
3417
|
-
const addCmd = new
|
|
5840
|
+
const addCmd = new Command11("add").description("Add a backend, subscriber, plugin, or platform").argument("[type]", "Preset type (backend, subscriber, plugin, platform)").argument("[name]", "Preset name (e.g., datadog, posthog, mongoose)").option("--list", "List available presets for the given type").option("--dry-run", "Skip installation and print what would be done").option("--no-install", "Generate files only, skip package installation").option("--print-install-cmd", "Output the install command without running it").option("-y, --yes", "Accept defaults, non-interactive").option("--force", "Overwrite non-CLI-owned config (creates backup first)").option("--json", "Output machine-readable JSON (for --list)").option("--workspace-root", "Install at workspace root instead of package root").action(async (type, name, opts) => {
|
|
3418
5841
|
const options = {
|
|
3419
5842
|
cwd: opts.cwd ?? process.cwd(),
|
|
3420
5843
|
dryRun: opts.dryRun ?? false,
|
|
@@ -3436,8 +5859,8 @@ function createProgram() {
|
|
|
3436
5859
|
});
|
|
3437
5860
|
addGlobalOptions(addCmd);
|
|
3438
5861
|
program.addCommand(addCmd);
|
|
3439
|
-
const codemodCmd = new
|
|
3440
|
-
const traceCmd = new
|
|
5862
|
+
const codemodCmd = new Command11("codemod").description("Codemod commands for adopting autotel");
|
|
5863
|
+
const traceCmd = new Command11("trace").description("Wrap functions in trace() with span name from function/variable/method name").argument("<path>", "File path or glob (e.g. src/index.ts, src/**/*.ts)").option("--dry-run", "Print changes without writing files").option("--name-pattern <pattern>", "Span name template: {name}, {file}, {path}").option("--skip <regex>...", "Skip functions whose name matches (repeatable)").option("--print-files", "Print per-file summary (wrapped count, skipped)").action(async (pathArg, opts) => {
|
|
3441
5864
|
const options = {
|
|
3442
5865
|
cwd: opts.cwd ?? process.cwd(),
|
|
3443
5866
|
dryRun: opts.dryRun ?? false,
|
|
@@ -3457,16 +5880,104 @@ function createProgram() {
|
|
|
3457
5880
|
codemodCmd.addCommand(traceCmd);
|
|
3458
5881
|
addGlobalOptions(codemodCmd);
|
|
3459
5882
|
program.addCommand(codemodCmd);
|
|
5883
|
+
const schemaCmd = new Command11("schema").description("Print the CLI manifest as JSON (agent discovery)").option("--output-file <path>", "Persist JSON to a file").option("--no-secrets-in-output", "Redact secret-shaped values").action((opts) => {
|
|
5884
|
+
runSchema({ outputFile: opts.outputFile, noSecrets: opts.secretsInOutput === false });
|
|
5885
|
+
});
|
|
5886
|
+
const schemaErrorsCmd = new Command11("errors").description("Print error envelope shape + AUTOTEL_E_* codes").option("--output-file <path>", "Persist JSON to a file").action((opts) => {
|
|
5887
|
+
runSchemaErrors({ outputFile: opts.outputFile });
|
|
5888
|
+
});
|
|
5889
|
+
const schemaOutputsCmd = new Command11("outputs").description("Print JSON output shapes per command").option("--output-file <path>", "Persist JSON to a file").action((opts) => {
|
|
5890
|
+
runSchemaOutputs({ outputFile: opts.outputFile });
|
|
5891
|
+
});
|
|
5892
|
+
schemaCmd.addCommand(schemaErrorsCmd);
|
|
5893
|
+
schemaCmd.addCommand(schemaOutputsCmd);
|
|
5894
|
+
program.addCommand(schemaCmd);
|
|
5895
|
+
const commandsCmd = new Command11("commands").description("Print compact tool-style listing of commands").option("--output-file <path>", "Persist JSON to a file").action((opts) => {
|
|
5896
|
+
runCommandsListing({ outputFile: opts.outputFile });
|
|
5897
|
+
});
|
|
5898
|
+
program.addCommand(commandsCmd);
|
|
5899
|
+
const examplesCmd = new Command11("examples").description("Print copy-pasteable examples for a command").argument("[command]", "Command name (omit for all)").option("--output-file <path>", "Persist JSON to a file").action((name, opts) => {
|
|
5900
|
+
runExamples(name, { outputFile: opts.outputFile });
|
|
5901
|
+
});
|
|
5902
|
+
program.addCommand(examplesCmd);
|
|
5903
|
+
const versionCmd = new Command11("version").description("Print version info as JSON").option("--output-file <path>", "Persist JSON to a file").action((opts) => {
|
|
5904
|
+
runVersion({ outputFile: opts.outputFile });
|
|
5905
|
+
});
|
|
5906
|
+
program.addCommand(versionCmd);
|
|
5907
|
+
registerHealthCommands(program);
|
|
5908
|
+
registerDiscoveryCommands(program);
|
|
5909
|
+
registerQueryCommands(program);
|
|
5910
|
+
registerTraceCommands(program);
|
|
5911
|
+
registerTopologyCommands(program);
|
|
5912
|
+
registerDiagnoseCommands(program);
|
|
5913
|
+
registerCorrelateCommands(program);
|
|
5914
|
+
registerLlmCommands(program);
|
|
5915
|
+
registerSemconvCommands(program);
|
|
5916
|
+
registerScoreCommands(program);
|
|
5917
|
+
registerCollectorCommands(program);
|
|
3460
5918
|
return program;
|
|
3461
5919
|
}
|
|
3462
5920
|
async function run() {
|
|
3463
5921
|
const program = createProgram();
|
|
5922
|
+
program.exitOverride();
|
|
5923
|
+
const argvJoined = process.argv.slice(2).join(" ");
|
|
5924
|
+
const isJsonOnly = process.argv.includes("--json") || /^(schema|commands|examples|version|health|capabilities|discover|query|trace|diagnose|topology|correlate|llm|semconv|score|collector)\b/.test(
|
|
5925
|
+
argvJoined
|
|
5926
|
+
);
|
|
5927
|
+
if (isJsonOnly) {
|
|
5928
|
+
program.configureOutput({ writeErr: () => {
|
|
5929
|
+
} });
|
|
5930
|
+
}
|
|
5931
|
+
const stack = [...program.commands];
|
|
5932
|
+
while (stack.length > 0) {
|
|
5933
|
+
const cmd = stack.pop();
|
|
5934
|
+
cmd.exitOverride();
|
|
5935
|
+
if (isJsonOnly) {
|
|
5936
|
+
cmd.configureOutput({ writeErr: () => {
|
|
5937
|
+
} });
|
|
5938
|
+
}
|
|
5939
|
+
stack.push(...cmd.commands);
|
|
5940
|
+
}
|
|
3464
5941
|
await program.parseAsync(process.argv);
|
|
3465
5942
|
}
|
|
3466
5943
|
|
|
5944
|
+
// src/lib/commander-error.ts
|
|
5945
|
+
function commanderErrorToAutotel(error2) {
|
|
5946
|
+
if (error2 === null || typeof error2 !== "object" || !("code" in error2) || typeof error2.code !== "string" || !error2.code.startsWith("commander.")) {
|
|
5947
|
+
return null;
|
|
5948
|
+
}
|
|
5949
|
+
const ce = error2;
|
|
5950
|
+
if (ce.code === "commander.help" || ce.code === "commander.helpDisplayed" || ce.code === "commander.version") {
|
|
5951
|
+
process.exit(ce.exitCode ?? 0);
|
|
5952
|
+
}
|
|
5953
|
+
return new AutotelError({
|
|
5954
|
+
type: "validation",
|
|
5955
|
+
code: "AUTOTEL_E_INVALID_FLAG",
|
|
5956
|
+
message: ce.message,
|
|
5957
|
+
retryable: false,
|
|
5958
|
+
expected: { commanderCode: ce.code }
|
|
5959
|
+
});
|
|
5960
|
+
}
|
|
5961
|
+
|
|
3467
5962
|
// src/index.ts
|
|
5963
|
+
function jsonModeRequested() {
|
|
5964
|
+
return process.argv.includes("--json");
|
|
5965
|
+
}
|
|
3468
5966
|
run().catch((error2) => {
|
|
3469
|
-
|
|
3470
|
-
|
|
5967
|
+
const err = commanderErrorToAutotel(error2) ?? toAutotelError(error2);
|
|
5968
|
+
const isJson = jsonModeRequested() || // schema/commands/examples/version + investigate commands are JSON-only
|
|
5969
|
+
/^(schema|commands|examples|version|health|capabilities|discover|query|trace|diagnose|topology|correlate|llm|semconv|score|collector)\b/.test(
|
|
5970
|
+
process.argv.slice(2).join(" ")
|
|
5971
|
+
);
|
|
5972
|
+
if (isJson) {
|
|
5973
|
+
printJson(err.toEnvelope());
|
|
5974
|
+
} else {
|
|
5975
|
+
process.stderr.write(
|
|
5976
|
+
`Error [${err.code}]: ${err.message}
|
|
5977
|
+
` + (err.fix !== void 0 ? `Fix: ${err.fix}
|
|
5978
|
+
` : "")
|
|
5979
|
+
);
|
|
5980
|
+
}
|
|
5981
|
+
process.exit(exitCodeForError(err));
|
|
3471
5982
|
});
|
|
3472
5983
|
//# sourceMappingURL=index.js.map
|