autotel-cli 0.8.7 → 0.8.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -4,7 +4,8 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/commands/init.ts
7
- import * as path6 from "path";
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 relative5 = [];
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
- relative5.push(imp);
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
- relative5.sort(sortBySource);
382
- return [...sideEffect, ...external, ...relative5];
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/dependency-planner.ts
642
- function createDependencyPlan() {
643
- return {
644
- required: [],
645
- optional: [],
646
- devOnly: []
647
- };
648
- }
649
- function mergePackages(target, source) {
650
- for (const pkg of source) {
651
- if (!target.includes(pkg)) {
652
- target.push(pkg);
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 addPresetToPlan(plan, preset) {
657
- mergePackages(plan.required, preset.packages.required);
658
- mergePackages(plan.optional, preset.packages.optional);
659
- mergePackages(plan.devOnly, preset.packages.devOnly);
660
- }
661
- function addPresetsToPlan(plan, presets) {
662
- for (const preset of presets) {
663
- addPresetToPlan(plan, preset);
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 getProdPackages(plan) {
667
- return [...plan.required, ...plan.optional];
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 getDevPackages(plan) {
670
- return [...plan.devOnly];
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 addCorePackages(plan) {
673
- mergePackages(plan.required, ["autotel"]);
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 addAutoInstrumentationPackages(plan, selection) {
676
- if (selection === "none") {
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
- if (selection === "all") {
680
- mergePackages(plan.required, ["@opentelemetry/auto-instrumentations-node"]);
681
- return;
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
- for (const name of selection) {
684
- mergePackages(plan.required, [`@opentelemetry/instrumentation-${name}`]);
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 buildDependencyPlan(options) {
688
- const plan = createDependencyPlan();
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/ui/prompts.ts
1697
- import { select, checkbox, confirm, input } from "@inquirer/prompts";
1698
- async function promptRuntime() {
1699
- return await select({
1700
- message: "What runtime are you using?",
1701
- choices: [
1702
- { value: "node", name: "Node.js" },
1703
- { value: "lambda", name: "AWS Lambda" },
1704
- { value: "cloudflare", name: "Cloudflare Workers" },
1705
- { value: "edge", name: "Edge Runtime (Vercel Edge, etc.)" }
1706
- ],
1707
- default: "node"
1708
- });
1709
- }
1710
- async function promptBackend(backends2) {
1711
- const choices = [
1712
- { value: "local", name: "Local/Console (development only)" },
1713
- ...[...backends2.entries()].map(([slug, preset]) => ({
1714
- value: slug,
1715
- name: `${preset.name} - ${preset.description}`
1716
- }))
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
+ }
1717
2091
  ];
1718
- return await select({
1719
- message: "Where do you want to send telemetry?",
1720
- choices,
1721
- default: "local"
1722
- });
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 };
1723
2117
  }
1724
- async function promptLogging() {
1725
- return await select({
1726
- message: "Which logging framework do you use?",
1727
- choices: [
1728
- { value: null, name: "None / Not sure" },
1729
- { value: "pino", name: "Pino" },
1730
- { value: "winston", name: "Winston" }
1731
- ],
1732
- default: null
1733
- });
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;
1734
2124
  }
1735
- async function promptDatabases(plugins2) {
1736
- const choices = [...plugins2.entries()].filter(([, preset]) => preset.type === "plugin").map(([slug, preset]) => ({
1737
- value: slug,
1738
- name: `${preset.name} - ${preset.description}`
1739
- }));
1740
- if (choices.length === 0) {
1741
- return [];
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
+ };
1742
2179
  }
1743
- return await checkbox({
1744
- message: "Which databases/ORMs do you use? (space to select, enter to continue)",
1745
- choices
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
1746
2194
  });
1747
2195
  }
1748
- async function promptSubscribers(subscribers2) {
1749
- const choices = [...subscribers2.entries()].map(([slug, preset]) => ({
1750
- value: slug,
1751
- name: `${preset.name} - ${preset.description}`
1752
- }));
1753
- if (choices.length === 0) {
1754
- return [];
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
+ });
1755
2206
  }
1756
- return await checkbox({
1757
- message: "Which event destinations? (space to select, enter to continue)",
1758
- choices
1759
- });
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;
1760
2233
  }
1761
- async function promptAutoInstrumentation() {
1762
- return await select({
1763
- message: "Auto-instrument common libraries?",
1764
- choices: [
1765
- { value: "all", name: "All (recommended) - http, express, pg, redis, etc." },
1766
- { value: "specific", name: "Let me choose specific ones" },
1767
- { value: "none", name: "None - I'll handle it manually" }
1768
- ],
1769
- default: "all"
1770
- });
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));
1771
2266
  }
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" }
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
+ ""
1783
2278
  ];
1784
- return await select({
1785
- message: "How do you start your app?",
1786
- choices,
1787
- default: "node-esm"
1788
- });
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");
1789
2334
  }
1790
- async function promptExistingConfigAction() {
1791
- return await select({
1792
- message: "Existing instrumentation detected. What would you like to do?",
2335
+ async function confirmOrEditPlan(plan) {
2336
+ console.log(renderPlanPreview(plan));
2337
+ console.log("");
2338
+ const choice = await select({
2339
+ message: "Proceed?",
1793
2340
  choices: [
1794
- { value: "update", name: "Update existing file (recommended)" },
1795
- { value: "new", name: "Create new file (src/autotel-config.mts)" },
1796
- { value: "abort", name: "Abort" }
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" }
1797
2344
  ],
1798
- default: "update"
2345
+ default: "apply"
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 };
2358
+ }
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;
2391
+ }
2392
+ if (typeof value === "string" && SECRET_VALUE_PATTERN.test(value)) {
2393
+ return REDACTED;
2394
+ }
2395
+ return value;
2396
+ }
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;
2404
+ }
2405
+ }
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
+ }
2414
+ }
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 chalk from "chalk";
2426
+ import chalk2 from "chalk";
1804
2427
  var STATUS = {
1805
- ok: chalk.green("[OK]"),
1806
- warn: chalk.yellow("[WARN]"),
1807
- error: chalk.red("[ERROR]"),
1808
- info: chalk.blue("[INFO]"),
1809
- skip: chalk.gray("[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(` ${chalk.dim(detail)}`);
2440
+ lines.push(` ${chalk2.dim(detail)}`);
1818
2441
  }
1819
2442
  }
1820
2443
  if (check.fix) {
1821
- lines.push(` ${chalk.cyan("Fix:")} ${check.fix.cmd}`);
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(chalk.green(`${summary.ok} passed`));
2451
+ parts.push(chalk2.green(`${summary.ok} passed`));
1829
2452
  }
1830
2453
  if (summary.warnings > 0) {
1831
- parts.push(chalk.yellow(`${summary.warnings} warning${summary.warnings > 1 ? "s" : ""}`));
2454
+ parts.push(chalk2.yellow(`${summary.warnings} warning${summary.warnings > 1 ? "s" : ""}`));
1832
2455
  }
1833
2456
  if (summary.errors > 0) {
1834
- parts.push(chalk.red(`${summary.errors} error${summary.errors > 1 ? "s" : ""}`));
2457
+ parts.push(chalk2.red(`${summary.errors} error${summary.errors > 1 ? "s" : ""}`));
1835
2458
  }
1836
2459
  if (summary.skipped > 0) {
1837
- parts.push(chalk.gray(`${summary.skipped} skipped`));
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(chalk.dim(`Detected: ${options.detected}`));
2467
+ lines.push(chalk2.dim(`Detected: ${options.detected}`));
1845
2468
  }
1846
2469
  if (options.wrote && options.wrote.length > 0) {
1847
- lines.push(chalk.dim(`Wrote: ${options.wrote.join(", ")}`));
2470
+ lines.push(chalk2.dim(`Wrote: ${options.wrote.join(", ")}`));
1848
2471
  }
1849
2472
  if (options.next) {
1850
- lines.push(chalk.cyan(`Next: ${options.next}`));
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(chalk.bold(text));
2484
+ console.log(chalk2.bold(text));
1856
2485
  }
1857
2486
  function info(text) {
1858
- console.log(chalk.blue(text));
2487
+ console.log(chalk2.blue(text));
1859
2488
  }
1860
2489
  function success(text) {
1861
- console.log(chalk.green(text));
2490
+ console.log(chalk2.green(text));
1862
2491
  }
1863
2492
  function warn(text) {
1864
- console.log(chalk.yellow(text));
2493
+ console.log(chalk2.yellow(text));
1865
2494
  }
1866
2495
  function error(text) {
1867
- console.log(chalk.red(text));
2496
+ console.log(chalk2.red(text));
1868
2497
  }
1869
2498
  function dim(text) {
1870
- console.log(chalk.dim(text));
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
- const spinner = createSpinner();
1910
- if (options.verbose) {
1911
- process.env["AUTOTEL_VERBOSE"] = "true";
1912
- }
1913
- if (options.quiet) {
1914
- process.env["AUTOTEL_QUIET"] = "true";
2530
+ if (options.json) {
2531
+ configureJsonOutput({
2532
+ outputFile: options.outputFile,
2533
+ noSecrets: options.noSecrets
2534
+ });
1915
2535
  }
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 (!project) {
1919
- spinner.fail("No package.json found");
1920
- error("Run this command in a directory with a package.json, or use --cwd");
1921
- process.exit(1);
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
- spinner.succeed(`Found ${project.packageJson.name ?? "project"}`);
1924
- verbose(`Package root: ${project.packageRoot}`);
1925
- verbose(`Package manager: ${project.packageManager}`);
1926
- const existingConfig = detectConfig(project.packageRoot);
1927
- if (existingConfig.found && !options.force) {
1928
- info(`Existing instrumentation detected at ${existingConfig.path}`);
1929
- if (options.yes || isCI()) {
1930
- warn("Use --force to overwrite existing config");
1931
- process.exit(0);
1932
- }
1933
- const action = await promptExistingConfigAction();
1934
- if (action === "abort") {
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
- process.exit(0);
1937
- }
1938
- }
1939
- const selectedPresets = [];
1940
- let autoInstrumentations = "all";
1941
- let startupStyle = "node-esm";
1942
- if (options.preset) {
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
- error(`Unknown preset: ${options.preset}`);
1965
- info("Available presets: node-datadog-pino, node-datadog-agent, node-honeycomb, node-otlp");
1966
- process.exit(1);
2586
+ info("Detection-only mode \u2014 no files written");
2587
+ console.log(JSON.stringify(plan, null, 2));
1967
2588
  }
1968
- } else if (options.yes || isCI()) {
1969
- info("Using defaults (local backend, all auto-instrumentations)");
1970
- const localPreset = getPreset("backend", "local");
1971
- if (localPreset) {
1972
- selectedPresets.push(localPreset);
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
- } else {
1975
- const runtime = await promptRuntime();
1976
- verbose(`Runtime: ${runtime}`);
1977
- const backendSlug = await promptBackend(backends);
1978
- const backendPreset = getPreset("backend", backendSlug);
1979
- if (backendPreset) {
1980
- selectedPresets.push(backendPreset);
1981
- }
1982
- await promptLogging();
1983
- const pluginSlugs = await promptDatabases(plugins);
1984
- for (const slug of pluginSlugs) {
1985
- const preset = getPreset("plugin", slug);
1986
- if (preset) selectedPresets.push(preset);
1987
- }
1988
- const subscriberSlugs = await promptSubscribers(subscribers);
1989
- for (const slug of subscriberSlugs) {
1990
- const preset = getPreset("subscriber", slug);
1991
- if (preset) selectedPresets.push(preset);
1992
- }
1993
- const autoChoice = await promptAutoInstrumentation();
1994
- if (autoChoice === "none") {
1995
- autoInstrumentations = "none";
1996
- } else if (autoChoice === "specific") {
1997
- autoInstrumentations = "all";
1998
- }
1999
- if (runtime === "node") {
2000
- startupStyle = await promptStartupStyle(project.hasTypeScript);
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
+ });
2001
2644
  }
2002
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
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);
2713
+ }
2003
2714
  const codeFile = createCodeFile();
2004
2715
  addImport(codeFile, { source: "autotel/register", sideEffect: true });
2005
2716
  addImport(codeFile, { source: "autotel", specifiers: ["init"] });
2006
- for (const preset of selectedPresets) {
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 (!codeFile.backendConfig) {
2736
+ if (codeFile.backendConfig === null) {
2020
2737
  setBackendConfig(codeFile, "// Local/console mode - no backend configured");
2021
2738
  }
2022
- const instrumentationContent = renderCodeFile(codeFile);
2023
- const depPlan = buildDependencyPlan({
2024
- presets: selectedPresets,
2025
- autoInstrumentations
2026
- });
2027
- const envVars = [];
2028
- for (const preset of selectedPresets) {
2029
- envVars.push(...preset.env.required, ...preset.env.optional);
2030
- }
2031
- const envExampleContent = generateEnvExample(envVars);
2032
- const instrumentationPath = getInstrumentationPath(project.packageRoot, project.hasTypeScript);
2033
- const envExamplePath = path6.join(project.packageRoot, ".env.example");
2034
- if (options.dryRun) {
2035
- heading("\nDry run - no files will be written\n");
2036
- info(`Would write: ${path6.relative(project.cwd, instrumentationPath)}`);
2037
- console.log("---");
2038
- console.log(instrumentationContent);
2039
- console.log("---\n");
2040
- if (envExampleContent) {
2041
- info(`Would write: ${path6.relative(project.cwd, envExamplePath)}`);
2042
- console.log("---");
2043
- console.log(envExampleContent);
2044
- console.log("---\n");
2045
- }
2046
- const prodPkgs2 = getProdPackages(depPlan);
2047
- const devPkgs2 = getDevPackages(depPlan);
2048
- if (prodPkgs2.length > 0) {
2049
- const cmd = getInstallCommand(project.packageManager, prodPkgs2);
2050
- info(`Would run: ${cmd}`);
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
+ });
2051
2753
  }
2052
- if (devPkgs2.length > 0) {
2053
- const cmd = getInstallCommand(project.packageManager, devPkgs2, { dev: true });
2054
- info(`Would run: ${cmd}`);
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));
2055
2779
  }
2056
- process.exit(0);
2780
+ } else {
2781
+ atomicWrite(instrumentationPath, newContent, {
2782
+ root: project.packageRoot
2783
+ });
2784
+ result.wroteFiles.push(path9.relative(project.cwd, instrumentationPath));
2057
2785
  }
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...");
2786
+ const envVars = [];
2787
+ for (const p of presets) {
2788
+ envVars.push(...p.env.required, ...p.env.optional);
2789
+ }
2790
+ const envExampleContent = generateEnvExample(envVars);
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
- spinner.succeed("Dependencies installed");
2816
+ result.ranInstalls.push(cmd);
2084
2817
  } catch {
2085
- spinner.fail("Failed to install dependencies");
2086
- error(`Run manually: ${cmd}`);
2818
+ result.installErrors.push(cmd);
2087
2819
  }
2088
2820
  }
2089
- }
2090
- if (devPkgs.length > 0) {
2091
- const cmd = getInstallCommand(project.packageManager, devPkgs, { dev: true });
2092
- if (options.printInstallCmd) {
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
- spinner.succeed("Dev dependencies installed");
2827
+ result.ranInstalls.push(cmd);
2099
2828
  } catch {
2100
- spinner.fail("Failed to install dev dependencies");
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
- const relInstrPath = path6.relative(project.packageRoot, instrumentationPath);
2117
- let nextStepCmd;
2118
- switch (startupStyle) {
2119
- case "tsx":
2120
- nextStepCmd = `tsx --import ./${relInstrPath} src/index.ts`;
2121
- break;
2122
- case "node-esm":
2123
- default:
2124
- nextStepCmd = `node --import ./${relInstrPath} dist/index.js`;
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
- const pmInfo = project.workspace.isMonorepo ? `${project.packageManager} workspace, package root ${project.packageRoot}` : `${project.packageManager}`;
2127
- const writtenFiles = [path6.relative(project.cwd, instrumentationPath)];
2128
- if (envExampleContent && !fileExists(envExamplePath)) {
2129
- writtenFiles.push(".env.example");
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
- console.log(formatFooter({
2132
- detected: pmInfo,
2133
- wrote: writtenFiles,
2134
- next: nextStepCmd
2135
- }));
2136
- const allNextSteps = selectedPresets.flatMap((p) => p.nextSteps);
2137
- if (allNextSteps.length > 0) {
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 allNextSteps) {
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 path7 from "path";
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 ${path7.basename(entrypoint)}:${result.lineNumber}`,
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 ${path7.basename(entrypoint)}`
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 path8 from "path";
2267
- import * as fs2 from "fs";
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 = path8.join(packageRoot, "src");
3027
+ const srcDir = path11.join(packageRoot, "src");
2298
3028
  const dirsToCheck = [packageRoot, srcDir].filter(
2299
- (dir) => fs2.existsSync(dir) && fs2.statSync(dir).isDirectory()
3029
+ (dir) => fs5.existsSync(dir) && fs5.statSync(dir).isDirectory()
2300
3030
  );
2301
3031
  for (const dir of dirsToCheck) {
2302
- const files = fs2.readdirSync(dir, { recursive: true });
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 = path8.join(dir, file);
3035
+ const filePath = path11.join(dir, file);
2306
3036
  try {
2307
- if (fs2.statSync(filePath).isFile() && /\.(ts|js|mts|mjs|tsx|jsx)$/.test(file)) {
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 path10 from "path";
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 path9 from "path";
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 = path9.basename(filePath, path9.extname(filePath));
3059
- const relPath = path9.relative(cwd, filePath).replaceAll("\\", "/");
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 = path10.isAbsolute(pathArg) ? pathArg : path10.resolve(cwd, pathArg);
4025
+ const absolute = path13.isAbsolute(pathArg) ? pathArg : path13.resolve(cwd, pathArg);
3296
4026
  if (fileExists(absolute)) {
3297
- const ext = path10.extname(absolute);
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 = path10.isAbsolute(pathArg) ? pathArg : path10.join(cwd, pathArg);
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 = path10.extname(f);
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: verbose2, quiet } = options;
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 (verbose2) dim(`Skip ${filePath} (unreadable)`);
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 (verbose2 && error2 instanceof Error && error2.stack) dim(error2.stack);
4072
+ if (verbose && error2 instanceof Error && error2.stack) dim(error2.stack);
3343
4073
  process.exitCode = 1;
3344
4074
  continue;
3345
4075
  }
3346
- const relativePath = path10.relative(cwd, filePath);
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 fs3 = await import("fs");
3352
- fs3.writeFileSync(filePath, result.modified, "utf8");
4081
+ const fs7 = await import("fs");
4082
+ fs7.writeFileSync(filePath, result.modified, "utf8");
3353
4083
  }
3354
4084
  }
3355
4085
  const showSummary = printFiles || dryRun || result.changed;
@@ -3368,6 +4098,369 @@ 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
+ function getCommand(name) {
4311
+ return COMMANDS.find((c) => c.name === name);
4312
+ }
4313
+ var ERROR_CATALOGUE = [
4314
+ { code: AutotelErrorCodes.E_NO_PACKAGE_JSON, type: "environment", description: "No package.json found in cwd or ancestors" },
4315
+ { code: AutotelErrorCodes.E_UNKNOWN_PRESET, type: "validation", description: "Preset slug not in registry" },
4316
+ { code: AutotelErrorCodes.E_INVALID_PLAN, type: "validation", description: "Plan file failed schema validation" },
4317
+ { code: AutotelErrorCodes.E_INVALID_INPUT, type: "validation", description: "Stdin or --input payload invalid" },
4318
+ { code: AutotelErrorCodes.E_INVALID_FLAG, type: "validation", description: "Conflicting or invalid flag combination" },
4319
+ { code: AutotelErrorCodes.E_NO_WORKSPACE_PACKAGES, type: "environment", description: "Workspace root has no detectable packages" },
4320
+ { code: AutotelErrorCodes.E_ENV_CONSENT_REQUIRED, type: "environment", description: ".env file present; consent required (pass --scan-env or run interactively)" },
4321
+ { code: AutotelErrorCodes.E_EXISTING_CONFIG, type: "conflict", description: "Existing instrumentation config; use --force or run with merge" },
4322
+ { code: AutotelErrorCodes.E_AMBIGUOUS_LOGGER, type: "conflict", description: "Multiple loggers detected and selection could not be inferred" },
4323
+ { code: AutotelErrorCodes.E_INSTALL_FAILED, type: "install", description: "Package manager install command failed" },
4324
+ { code: AutotelErrorCodes.E_WRITE_FAILED, type: "io", description: "Failed to write a file" },
4325
+ { code: AutotelErrorCodes.E_READ_FAILED, type: "io", description: "Failed to read a file" },
4326
+ { code: AutotelErrorCodes.E_UNKNOWN, type: "runtime", description: "Unexpected error" }
4327
+ ];
4328
+
4329
+ // src/commands/schema.ts
4330
+ function configure(opts) {
4331
+ configureJsonOutput({
4332
+ outputFile: opts.outputFile,
4333
+ noSecrets: opts.noSecrets
4334
+ });
4335
+ }
4336
+ function readSelfVersion() {
4337
+ try {
4338
+ const here = path14.dirname(fileURLToPath(import.meta.url));
4339
+ let dir = here;
4340
+ for (let i = 0; i < 5; i++) {
4341
+ const candidate = path14.join(dir, "package.json");
4342
+ if (fs6.existsSync(candidate)) {
4343
+ const pkg = JSON.parse(fs6.readFileSync(candidate, "utf8"));
4344
+ if (pkg.name === "autotel-cli") return String(pkg.version ?? "0.0.0");
4345
+ }
4346
+ dir = path14.dirname(dir);
4347
+ }
4348
+ } catch {
4349
+ }
4350
+ return "0.0.0";
4351
+ }
4352
+ function runSchema(opts) {
4353
+ configure(opts);
4354
+ printJson({
4355
+ ok: true,
4356
+ command: "autotel schema",
4357
+ version: readSelfVersion(),
4358
+ commands: COMMANDS
4359
+ });
4360
+ }
4361
+ function runSchemaErrors(opts) {
4362
+ configure(opts);
4363
+ printJson({
4364
+ ok: true,
4365
+ command: "autotel schema errors",
4366
+ envelope: {
4367
+ ok: false,
4368
+ command: "<command>",
4369
+ error: {
4370
+ type: "<one of: validation, environment, auth, conflict, install, io, runtime>",
4371
+ code: "<AUTOTEL_E_*>",
4372
+ message: "human-readable message",
4373
+ retryable: false,
4374
+ fix: "optional remediation hint",
4375
+ expected: { "<key>": "<value>" },
4376
+ suggestions: ["optional follow-up commands"]
4377
+ }
4378
+ },
4379
+ codes: ERROR_CATALOGUE,
4380
+ exitCodes: {
4381
+ "0": "success",
4382
+ "1": "runtime / unexpected failure",
4383
+ "2": "validation / conflict / refusal"
4384
+ }
4385
+ });
4386
+ }
4387
+ function runSchemaOutputs(opts) {
4388
+ configure(opts);
4389
+ printJson({
4390
+ ok: true,
4391
+ command: "autotel schema outputs",
4392
+ outputs: {
4393
+ "autotel init --json": {
4394
+ ok: "boolean",
4395
+ command: "string",
4396
+ detected: {
4397
+ packages: 'array of { name, version, resolution: "target" | "workspace-root" }',
4398
+ logger: '"pino" | "winston" | "bunyan" | null',
4399
+ backend: '{ source: "env" | "wrangler" | "deps" | "prompt" | "default", value: string }',
4400
+ platform: '"cloudflare" | "aws-lambda" | "edge" | null'
4401
+ },
4402
+ plan: {
4403
+ presets: "string[] (slugs)",
4404
+ packagesToInstall: { prod: "string[]", dev: "string[]" },
4405
+ filesToWrite: 'array of { path, action: "create" | "merge" | "skip" }',
4406
+ envVars: "array of { name, sensitive, action }"
4407
+ },
4408
+ nextSteps: "string[]"
4409
+ }
4410
+ }
4411
+ });
4412
+ }
4413
+ function runCommandsListing(opts) {
4414
+ configure(opts);
4415
+ const compact = COMMANDS.map((c) => ({
4416
+ name: c.name,
4417
+ description: c.description,
4418
+ mutating: c.mutating,
4419
+ network: c.network,
4420
+ writesFiles: c.writesFiles,
4421
+ supportsDryRun: c.supportsDryRun,
4422
+ supportsJson: c.supportsJson
4423
+ }));
4424
+ printJson({ ok: true, command: "autotel commands", commands: compact });
4425
+ }
4426
+ function runExamples(name, opts) {
4427
+ configure(opts);
4428
+ if (name === void 0) {
4429
+ const all = COMMANDS.filter((c) => c.examples && c.examples.length > 0).map(
4430
+ (c) => ({ command: c.name, examples: c.examples })
4431
+ );
4432
+ printJson({ ok: true, command: "autotel examples", examples: all });
4433
+ return;
4434
+ }
4435
+ const cmd = getCommand(name);
4436
+ if (!cmd) {
4437
+ throw new AutotelError({
4438
+ type: "validation",
4439
+ code: AutotelErrorCodes.E_INVALID_INPUT,
4440
+ message: `Unknown command: ${name}`,
4441
+ expected: { command: COMMANDS.map((c) => c.name) },
4442
+ fix: "Run `autotel commands --json` to see available commands"
4443
+ });
4444
+ }
4445
+ printJson({
4446
+ ok: true,
4447
+ command: "autotel examples",
4448
+ target: cmd.name,
4449
+ examples: cmd.examples ?? []
4450
+ });
4451
+ }
4452
+ function runVersion(opts) {
4453
+ configure(opts);
4454
+ printJson({
4455
+ ok: true,
4456
+ command: "autotel version",
4457
+ version: readSelfVersion(),
4458
+ node: process.version,
4459
+ platform: process.platform,
4460
+ arch: process.arch
4461
+ });
4462
+ }
4463
+
3371
4464
  // src/cli.ts
3372
4465
  function createProgram() {
3373
4466
  const program = new Command();
@@ -3375,18 +4468,27 @@ function createProgram() {
3375
4468
  const addGlobalOptions = (cmd) => {
3376
4469
  return cmd.option("--cwd <path>", "Target directory", process.cwd()).option("--verbose", "Show detailed output").option("--quiet", "Only show warnings and errors");
3377
4470
  };
3378
- const initCmd = new Command("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").action(async (opts) => {
4471
+ const initCmd = new Command("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
4472
  const options = {
3380
4473
  cwd: opts.cwd ?? process.cwd(),
3381
4474
  dryRun: opts.dryRun ?? false,
3382
- noInstall: opts.noInstall ?? false,
4475
+ noInstall: opts.install === false,
3383
4476
  printInstallCmd: opts.printInstallCmd ?? false,
3384
4477
  verbose: opts.verbose ?? false,
3385
4478
  quiet: opts.quiet ?? false,
3386
4479
  workspaceRoot: opts.workspaceRoot ?? false,
3387
4480
  yes: opts.yes ?? false,
3388
4481
  preset: opts.preset,
3389
- force: opts.force ?? false
4482
+ force: opts.force ?? false,
4483
+ noDetect: opts.detect === false,
4484
+ detectOnly: opts.detectOnly ?? false,
4485
+ plan: opts.plan,
4486
+ input: opts.input,
4487
+ scanEnv: opts.scanEnv ?? false,
4488
+ json: opts.json ?? false,
4489
+ outputFile: opts.outputFile,
4490
+ noSecrets: opts.secretsInOutput === false,
4491
+ noInteractive: opts.interactive === false
3390
4492
  };
3391
4493
  if (options.dryRun) {
3392
4494
  options.noInstall = true;
@@ -3457,6 +4559,30 @@ function createProgram() {
3457
4559
  codemodCmd.addCommand(traceCmd);
3458
4560
  addGlobalOptions(codemodCmd);
3459
4561
  program.addCommand(codemodCmd);
4562
+ const schemaCmd = new Command("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) => {
4563
+ runSchema({ outputFile: opts.outputFile, noSecrets: opts.secretsInOutput === false });
4564
+ });
4565
+ const schemaErrorsCmd = new Command("errors").description("Print error envelope shape + AUTOTEL_E_* codes").option("--output-file <path>", "Persist JSON to a file").action((opts) => {
4566
+ runSchemaErrors({ outputFile: opts.outputFile });
4567
+ });
4568
+ const schemaOutputsCmd = new Command("outputs").description("Print JSON output shapes per command").option("--output-file <path>", "Persist JSON to a file").action((opts) => {
4569
+ runSchemaOutputs({ outputFile: opts.outputFile });
4570
+ });
4571
+ schemaCmd.addCommand(schemaErrorsCmd);
4572
+ schemaCmd.addCommand(schemaOutputsCmd);
4573
+ program.addCommand(schemaCmd);
4574
+ const commandsCmd = new Command("commands").description("Print compact tool-style listing of commands").option("--output-file <path>", "Persist JSON to a file").action((opts) => {
4575
+ runCommandsListing({ outputFile: opts.outputFile });
4576
+ });
4577
+ program.addCommand(commandsCmd);
4578
+ const examplesCmd = new Command("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) => {
4579
+ runExamples(name, { outputFile: opts.outputFile });
4580
+ });
4581
+ program.addCommand(examplesCmd);
4582
+ const versionCmd = new Command("version").description("Print version info as JSON").option("--output-file <path>", "Persist JSON to a file").action((opts) => {
4583
+ runVersion({ outputFile: opts.outputFile });
4584
+ });
4585
+ program.addCommand(versionCmd);
3460
4586
  return program;
3461
4587
  }
3462
4588
  async function run() {
@@ -3465,8 +4591,24 @@ async function run() {
3465
4591
  }
3466
4592
 
3467
4593
  // src/index.ts
4594
+ function jsonModeRequested() {
4595
+ return process.argv.includes("--json");
4596
+ }
3468
4597
  run().catch((error2) => {
3469
- console.error("Error:", error2 instanceof Error ? error2.message : error2);
3470
- process.exit(1);
4598
+ const err = toAutotelError(error2);
4599
+ const isJson = jsonModeRequested() || // schema/commands/examples/version are JSON-only
4600
+ /^(schema|commands|examples|version)\b/.test(
4601
+ process.argv.slice(2).join(" ")
4602
+ );
4603
+ if (isJson) {
4604
+ printJson(err.toEnvelope());
4605
+ } else {
4606
+ process.stderr.write(
4607
+ `Error [${err.code}]: ${err.message}
4608
+ ` + (err.fix !== void 0 ? `Fix: ${err.fix}
4609
+ ` : "")
4610
+ );
4611
+ }
4612
+ process.exit(exitCodeForError(err));
3471
4613
  });
3472
4614
  //# sourceMappingURL=index.js.map