autotel-cli 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +203 -7
- package/package.json +6 -5
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -13,6 +13,7 @@ import ora from "ora";
|
|
|
13
13
|
import * as semver from "semver";
|
|
14
14
|
import { Project, SyntaxKind } from "ts-morph";
|
|
15
15
|
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { JsonTraceSerializer, ProtobufTraceSerializer } from "@opentelemetry/otlp-transformer";
|
|
16
17
|
import { buildCollectorGuide, buildInstrumentationGuide, clearSemanticConventionCache, collectUsage, detectAnomalies, discoverLogFields, discoverServices, discoverTraceFields, findRootCause, getCollectorComponentReadme, getCollectorComponentSchema, getModelStats, getSemanticConventionNamespace, listCollectorComponents, listCollectorVersions, listModels, listSemanticConventionNamespaces, listToolUsage, pickErrorMessage, rankExpensiveTraces, rankSlowTraces, refreshCollectorCatalog, resolveCollectorVersion, scoreSpan, suggestCollectorConfig, suggestInstrumentationFixes, toLogSearchQuery, toMetricSearchQuery, toSpanSearchQuery, toTraceSearchQuery, validateCollectorComponentConfig, validateOtlpReceiverConfig } from "autotel-mcp";
|
|
17
18
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
18
19
|
import { appendFile, mkdir, readFile, unlink, writeFile } from "node:fs/promises";
|
|
@@ -2250,6 +2251,7 @@ const AutotelErrorCodes = {
|
|
|
2250
2251
|
E_INSTALL_FAILED: "AUTOTEL_E_INSTALL_FAILED",
|
|
2251
2252
|
E_WRITE_FAILED: "AUTOTEL_E_WRITE_FAILED",
|
|
2252
2253
|
E_READ_FAILED: "AUTOTEL_E_READ_FAILED",
|
|
2254
|
+
E_RATE_LIMITED: "AUTOTEL_E_RATE_LIMITED",
|
|
2253
2255
|
E_UNKNOWN: "AUTOTEL_E_UNKNOWN"
|
|
2254
2256
|
};
|
|
2255
2257
|
/**
|
|
@@ -7211,6 +7213,11 @@ const ERROR_CATALOGUE = [
|
|
|
7211
7213
|
type: "io",
|
|
7212
7214
|
description: "Failed to read a file"
|
|
7213
7215
|
},
|
|
7216
|
+
{
|
|
7217
|
+
code: AutotelErrorCodes.E_RATE_LIMITED,
|
|
7218
|
+
type: "runtime",
|
|
7219
|
+
description: "Backend rate limit outlasted the retry budget — wait and retry"
|
|
7220
|
+
},
|
|
7214
7221
|
{
|
|
7215
7222
|
code: AutotelErrorCodes.E_UNKNOWN,
|
|
7216
7223
|
type: "runtime",
|
|
@@ -7377,6 +7384,9 @@ function applyFlagsToEnv(flags) {
|
|
|
7377
7384
|
if (flags.lokiBaseUrl !== void 0) process.env.LOKI_BASE_URL = flags.lokiBaseUrl;
|
|
7378
7385
|
if (flags.collectorPort !== void 0) process.env.AUTOTEL_COLLECTOR_PORT = String(flags.collectorPort);
|
|
7379
7386
|
if (flags.fixturePath !== void 0) process.env.AUTOTEL_FIXTURE_PATH = flags.fixturePath;
|
|
7387
|
+
if (flags.logfireBaseUrl !== void 0) process.env.LOGFIRE_BASE_URL = flags.logfireBaseUrl;
|
|
7388
|
+
if (flags.datadogSite !== void 0) process.env.DD_SITE = flags.datadogSite;
|
|
7389
|
+
if (flags.signozBaseUrl !== void 0) process.env.SIGNOZ_BASE_URL = flags.signozBaseUrl;
|
|
7380
7390
|
}
|
|
7381
7391
|
/**
|
|
7382
7392
|
* Build a backend from CLI flags/env. Caller is responsible for calling
|
|
@@ -7451,10 +7461,17 @@ function toInvestigateError(command, error) {
|
|
|
7451
7461
|
expected: { issues }
|
|
7452
7462
|
});
|
|
7453
7463
|
}
|
|
7464
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
7465
|
+
if (/\bHTTP 429\b/.test(message)) return new AutotelError({
|
|
7466
|
+
type: "runtime",
|
|
7467
|
+
code: AutotelErrorCodes.E_RATE_LIMITED,
|
|
7468
|
+
message: `autotel ${command} failed: ${message} — the backend rate limit outlasted the retry budget. Wait for the limit window to reset and run the command again.`,
|
|
7469
|
+
retryable: true
|
|
7470
|
+
});
|
|
7454
7471
|
return new AutotelError({
|
|
7455
7472
|
type: "runtime",
|
|
7456
7473
|
code: "AUTOTEL_E_UNKNOWN",
|
|
7457
|
-
message: `autotel ${command} failed: ${
|
|
7474
|
+
message: `autotel ${command} failed: ${message}`,
|
|
7458
7475
|
retryable: false
|
|
7459
7476
|
});
|
|
7460
7477
|
}
|
|
@@ -7472,7 +7489,7 @@ const floatArg = (v) => Number.parseFloat(v);
|
|
|
7472
7489
|
* investigate command. Applied to a Command in place (mutates + returns).
|
|
7473
7490
|
*/
|
|
7474
7491
|
function addBackendFlags(cmd) {
|
|
7475
|
-
return cmd.option("--backend <kind>", "Backend: collector|jaeger|tempo|prometheus|loki|stack|auto|fixture (env: AUTOTEL_BACKEND)").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("--prometheus-base-url <url>", "Prometheus base URL (env: PROMETHEUS_BASE_URL)").option("--loki-base-url <url>", "Loki base URL (env: LOKI_BASE_URL)").option("--collector-port <n>", "OTLP receiver port for the collector backend", intArg).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");
|
|
7492
|
+
return cmd.option("--backend <kind>", "Backend: collector|jaeger|tempo|prometheus|loki|stack|auto|fixture|logfire|datadog|signoz (env: AUTOTEL_BACKEND)").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("--prometheus-base-url <url>", "Prometheus base URL (env: PROMETHEUS_BASE_URL)").option("--loki-base-url <url>", "Loki base URL (env: LOKI_BASE_URL)").option("--collector-port <n>", "OTLP receiver port for the collector backend", intArg).option("--fixture-path <path>", "Fixture JSON for the fixture backend").option("--logfire-base-url <url>", "Logfire region URL (env: LOGFIRE_BASE_URL; token via LOGFIRE_READ_TOKEN)").option("--datadog-site <site>", "Datadog site, e.g. datadoghq.eu, or a full API URL (env: DD_SITE; keys via DD_API_KEY + DD_APP_KEY)").option("--signoz-base-url <url>", "SigNoz base URL (env: SIGNOZ_BASE_URL; key via SIGNOZ_API_KEY)").option("--output-file <path>", "Persist JSON output to this file").option("--no-secrets-in-output", "Redact secret-shaped values");
|
|
7476
7493
|
}
|
|
7477
7494
|
/**
|
|
7478
7495
|
* Smaller flag set for commands that don't need a backend (semconv,
|
|
@@ -7490,6 +7507,9 @@ function backendFlagsFromOpts(opts) {
|
|
|
7490
7507
|
lokiBaseUrl: opts.lokiBaseUrl,
|
|
7491
7508
|
collectorPort: opts.collectorPort,
|
|
7492
7509
|
fixturePath: opts.fixturePath,
|
|
7510
|
+
logfireBaseUrl: opts.logfireBaseUrl,
|
|
7511
|
+
datadogSite: opts.datadogSite,
|
|
7512
|
+
signozBaseUrl: opts.signozBaseUrl,
|
|
7493
7513
|
outputFile: opts.outputFile,
|
|
7494
7514
|
noSecrets: opts.secretsInOutput === false
|
|
7495
7515
|
};
|
|
@@ -7517,22 +7537,200 @@ function windowFlagsFromOpts(opts) {
|
|
|
7517
7537
|
};
|
|
7518
7538
|
}
|
|
7519
7539
|
//#endregion
|
|
7540
|
+
//#region src/commands/investigate/freshness.ts
|
|
7541
|
+
const PROBE_SPAN_NAME = "autotel.freshness.probe";
|
|
7542
|
+
const PROBE_SCOPE = "autotel-cli/freshness";
|
|
7543
|
+
/** Nominal probe span duration; a zero-length span reads oddly in a UI. */
|
|
7544
|
+
const PROBE_DURATION_MS = 1;
|
|
7545
|
+
function randomHex(bytes) {
|
|
7546
|
+
const out = [];
|
|
7547
|
+
for (let index = 0; index < bytes; index++) out.push(Math.floor(Math.random() * 256).toString(16).padStart(2, "0"));
|
|
7548
|
+
return out.join("");
|
|
7549
|
+
}
|
|
7550
|
+
/** Epoch ms → OTel's `[seconds, nanoseconds]` pair. */
|
|
7551
|
+
function toHrTime(ms) {
|
|
7552
|
+
const seconds = Math.floor(ms / 1e3);
|
|
7553
|
+
return [seconds, Math.round((ms - seconds * 1e3) * 1e6)];
|
|
7554
|
+
}
|
|
7555
|
+
/** One finished span, ready for either serializer. */
|
|
7556
|
+
function buildProbeSpan(service, startMs) {
|
|
7557
|
+
const traceId = randomHex(16);
|
|
7558
|
+
const spanId = randomHex(8);
|
|
7559
|
+
return {
|
|
7560
|
+
name: PROBE_SPAN_NAME,
|
|
7561
|
+
kind: 0,
|
|
7562
|
+
spanContext: () => ({
|
|
7563
|
+
traceId,
|
|
7564
|
+
spanId,
|
|
7565
|
+
traceFlags: 1
|
|
7566
|
+
}),
|
|
7567
|
+
parentSpanContext: void 0,
|
|
7568
|
+
startTime: toHrTime(startMs),
|
|
7569
|
+
endTime: toHrTime(startMs + PROBE_DURATION_MS),
|
|
7570
|
+
duration: toHrTime(PROBE_DURATION_MS),
|
|
7571
|
+
status: { code: 0 },
|
|
7572
|
+
attributes: {},
|
|
7573
|
+
links: [],
|
|
7574
|
+
events: [],
|
|
7575
|
+
ended: true,
|
|
7576
|
+
resource: { attributes: { "service.name": service } },
|
|
7577
|
+
instrumentationScope: { name: PROBE_SCOPE },
|
|
7578
|
+
droppedAttributesCount: 0,
|
|
7579
|
+
droppedEventsCount: 0,
|
|
7580
|
+
droppedLinksCount: 0
|
|
7581
|
+
};
|
|
7582
|
+
}
|
|
7583
|
+
/** Serialize a probe span into an OTLP request body plus its content type. */
|
|
7584
|
+
function encodeProbe(span, encoding) {
|
|
7585
|
+
const spans = [span];
|
|
7586
|
+
return encoding === "json" ? {
|
|
7587
|
+
body: JsonTraceSerializer.serializeRequest(spans) ?? /* @__PURE__ */ new Uint8Array(),
|
|
7588
|
+
contentType: "application/json"
|
|
7589
|
+
} : {
|
|
7590
|
+
body: ProtobufTraceSerializer.serializeRequest(spans) ?? /* @__PURE__ */ new Uint8Array(),
|
|
7591
|
+
contentType: "application/x-protobuf"
|
|
7592
|
+
};
|
|
7593
|
+
}
|
|
7594
|
+
/**
|
|
7595
|
+
* Parse the standard `OTEL_EXPORTER_OTLP_HEADERS` format (`k=v,k2=v2`).
|
|
7596
|
+
*
|
|
7597
|
+
* Hosted OTLP endpoints all require an auth header, and this is the variable
|
|
7598
|
+
* users already set for their exporters — so the probe reads the same one
|
|
7599
|
+
* rather than inventing a flag that would put a token in argv.
|
|
7600
|
+
*/
|
|
7601
|
+
function otlpHeadersFromEnv(raw) {
|
|
7602
|
+
const headers = {};
|
|
7603
|
+
for (const pair of (raw ?? "").split(",")) {
|
|
7604
|
+
const separator = pair.indexOf("=");
|
|
7605
|
+
if (separator <= 0) continue;
|
|
7606
|
+
const key = pair.slice(0, separator).trim();
|
|
7607
|
+
const value = pair.slice(separator + 1).trim();
|
|
7608
|
+
if (key === "") continue;
|
|
7609
|
+
headers[key] = value;
|
|
7610
|
+
}
|
|
7611
|
+
return headers;
|
|
7612
|
+
}
|
|
7613
|
+
/** POST an OTLP/HTTP trace. Kept separate so tests never touch the network. */
|
|
7614
|
+
async function sendOtlpTrace(endpoint, span, headers = {}, encoding = "protobuf") {
|
|
7615
|
+
const url = resolveOtlpTraceUrl(endpoint);
|
|
7616
|
+
const { body, contentType } = encodeProbe(span, encoding);
|
|
7617
|
+
const response = await fetch(url, {
|
|
7618
|
+
method: "POST",
|
|
7619
|
+
headers: {
|
|
7620
|
+
"Content-Type": contentType,
|
|
7621
|
+
...headers
|
|
7622
|
+
},
|
|
7623
|
+
body
|
|
7624
|
+
});
|
|
7625
|
+
if (!response.ok) {
|
|
7626
|
+
const otherEncoding = encoding === "json" ? "protobuf" : "json";
|
|
7627
|
+
throw new Error(`OTLP endpoint ${url} rejected the probe span: ${response.status} ${response.statusText}. Sent ${encoding}; if the receiver wants the other encoding, pass --otlp-encoding ${otherEncoding}.`);
|
|
7628
|
+
}
|
|
7629
|
+
}
|
|
7630
|
+
/** Resolve an OTLP base endpoint without discarding vendor-specific paths. */
|
|
7631
|
+
function resolveOtlpTraceUrl(endpoint) {
|
|
7632
|
+
const url = new URL(endpoint);
|
|
7633
|
+
const basePath = url.pathname.replace(/\/+$/, "");
|
|
7634
|
+
if (!basePath.endsWith("/v1/traces")) url.pathname = `${basePath}/v1/traces`;
|
|
7635
|
+
return url.toString();
|
|
7636
|
+
}
|
|
7637
|
+
const DEFAULT_TIMEOUT_MS = 12e4;
|
|
7638
|
+
const DEFAULT_POLL_INTERVAL_MS = 2e3;
|
|
7639
|
+
function defaultSleep(ms) {
|
|
7640
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7641
|
+
}
|
|
7642
|
+
const DEADLINE_REACHED = Symbol("freshness-deadline-reached");
|
|
7643
|
+
async function beforeDeadline(promise, remainingMs) {
|
|
7644
|
+
if (remainingMs <= 0) return DEADLINE_REACHED;
|
|
7645
|
+
let timer;
|
|
7646
|
+
try {
|
|
7647
|
+
return await Promise.race([promise, new Promise((resolve) => {
|
|
7648
|
+
timer = setTimeout(() => resolve(DEADLINE_REACHED), remainingMs);
|
|
7649
|
+
})]);
|
|
7650
|
+
} finally {
|
|
7651
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
7652
|
+
}
|
|
7653
|
+
}
|
|
7654
|
+
/**
|
|
7655
|
+
* Write one probe span and time how long the backend takes to serve it back.
|
|
7656
|
+
*
|
|
7657
|
+
* An empty successful read means "not yet". Read failures are surfaced because
|
|
7658
|
+
* authentication and configuration errors cannot become fresh by polling.
|
|
7659
|
+
*/
|
|
7660
|
+
async function measureFreshness(options) {
|
|
7661
|
+
const { backend, otlpEndpoint, encoding = "protobuf", timeoutMs = DEFAULT_TIMEOUT_MS, pollIntervalMs = DEFAULT_POLL_INTERVAL_MS, headers = otlpHeadersFromEnv(process.env.OTEL_EXPORTER_OTLP_HEADERS), now = Date.now, sleep = defaultSleep, send = sendOtlpTrace } = options;
|
|
7662
|
+
const probeService = `autotel-freshness-probe-${randomHex(6)}`;
|
|
7663
|
+
const startedAt = now();
|
|
7664
|
+
const timedOutResult = (attempts) => ({
|
|
7665
|
+
probeService,
|
|
7666
|
+
otlpEndpoint,
|
|
7667
|
+
encoding,
|
|
7668
|
+
timeToQueryableSeconds: null,
|
|
7669
|
+
timedOut: true,
|
|
7670
|
+
attempts,
|
|
7671
|
+
timeoutMs
|
|
7672
|
+
});
|
|
7673
|
+
if (await beforeDeadline(send(otlpEndpoint, buildProbeSpan(probeService, startedAt), headers, encoding), timeoutMs) === DEADLINE_REACHED) return timedOutResult(0);
|
|
7674
|
+
let attempts = 0;
|
|
7675
|
+
for (;;) {
|
|
7676
|
+
attempts++;
|
|
7677
|
+
const remainingMs = timeoutMs - (now() - startedAt);
|
|
7678
|
+
const result = await beforeDeadline(backend.searchTraces({
|
|
7679
|
+
service: probeService,
|
|
7680
|
+
limit: 1
|
|
7681
|
+
}), remainingMs);
|
|
7682
|
+
if (result === DEADLINE_REACHED) return timedOutResult(attempts);
|
|
7683
|
+
if ((result.items?.length ?? 0) > 0) return {
|
|
7684
|
+
probeService,
|
|
7685
|
+
otlpEndpoint,
|
|
7686
|
+
encoding,
|
|
7687
|
+
timeToQueryableSeconds: (now() - startedAt) / 1e3,
|
|
7688
|
+
timedOut: false,
|
|
7689
|
+
attempts,
|
|
7690
|
+
timeoutMs
|
|
7691
|
+
};
|
|
7692
|
+
if (now() - startedAt >= timeoutMs) return timedOutResult(attempts);
|
|
7693
|
+
await sleep(Math.min(pollIntervalMs, timeoutMs - (now() - startedAt)));
|
|
7694
|
+
}
|
|
7695
|
+
}
|
|
7696
|
+
//#endregion
|
|
7520
7697
|
//#region src/commands/investigate/health.ts
|
|
7698
|
+
function resolvedFreshnessEncoding(requested, backend) {
|
|
7699
|
+
return requested ?? (backend.kind === "collector" ? "json" : "protobuf");
|
|
7700
|
+
}
|
|
7521
7701
|
async function runHealth(flags) {
|
|
7522
7702
|
await runInvestigate("health", flags, async (backend) => {
|
|
7523
7703
|
const [health, capabilities] = await Promise.all([backend.healthCheck(), Promise.resolve(backend.capabilities())]);
|
|
7524
|
-
|
|
7704
|
+
const base = {
|
|
7525
7705
|
...health,
|
|
7526
7706
|
signals: capabilities
|
|
7527
7707
|
};
|
|
7708
|
+
if (!flags.otlpEndpoint) return base;
|
|
7709
|
+
const encoding = resolvedFreshnessEncoding(flags.otlpEncoding, backend);
|
|
7710
|
+
const freshness = await measureFreshness({
|
|
7711
|
+
backend,
|
|
7712
|
+
otlpEndpoint: flags.otlpEndpoint,
|
|
7713
|
+
timeoutMs: flags.freshnessTimeoutMs,
|
|
7714
|
+
encoding
|
|
7715
|
+
});
|
|
7716
|
+
return {
|
|
7717
|
+
...base,
|
|
7718
|
+
freshness
|
|
7719
|
+
};
|
|
7528
7720
|
});
|
|
7529
7721
|
}
|
|
7530
7722
|
async function runCapabilities(flags) {
|
|
7531
7723
|
await runInvestigate("capabilities", flags, async (backend) => backend.capabilities());
|
|
7532
7724
|
}
|
|
7533
7725
|
function registerHealthCommands(program) {
|
|
7534
|
-
const healthCmd = new Command("health").description("Backend health check + signal coverage (JSON)").action(async function() {
|
|
7535
|
-
|
|
7726
|
+
const healthCmd = new Command("health").description("Backend health check + signal coverage (JSON)").option("--otlp-endpoint <url>", "Also measure ingest-to-queryable lag: write a probe span to this OTLP/HTTP endpoint and poll until it reads back").option("--freshness-timeout-ms <ms>", "Give up waiting for the probe span after this long (default 120000)", (value) => Number.parseInt(value, 10)).option("--otlp-encoding <encoding>", "Probe payload encoding: protobuf (default, and the only one some vendors accept) or json (the built-in collector)").action(async function() {
|
|
7727
|
+
const opts = this.opts();
|
|
7728
|
+
await runHealth({
|
|
7729
|
+
...backendFlagsFromOpts(opts),
|
|
7730
|
+
otlpEndpoint: opts.otlpEndpoint,
|
|
7731
|
+
freshnessTimeoutMs: opts.freshnessTimeoutMs,
|
|
7732
|
+
otlpEncoding: opts.otlpEncoding
|
|
7733
|
+
});
|
|
7536
7734
|
});
|
|
7537
7735
|
addBackendFlags(healthCmd);
|
|
7538
7736
|
program.addCommand(healthCmd);
|
|
@@ -9246,5 +9444,3 @@ run().catch((error) => {
|
|
|
9246
9444
|
});
|
|
9247
9445
|
//#endregion
|
|
9248
9446
|
export {};
|
|
9249
|
-
|
|
9250
|
-
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "autotel-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "CLI for autotel - setup wizard, diagnostics, and incremental features",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@inquirer/prompts": "^8.5.2",
|
|
20
|
+
"@opentelemetry/otlp-transformer": "^0.221.0",
|
|
20
21
|
"chalk": "^5.6.2",
|
|
21
22
|
"commander": "^15.0.0",
|
|
22
23
|
"glob": "^13.0.6",
|
|
@@ -24,22 +25,22 @@
|
|
|
24
25
|
"semver": "^7.8.5",
|
|
25
26
|
"ts-morph": "^28.0.0",
|
|
26
27
|
"yaml": "^2.9.0",
|
|
27
|
-
"autotel-mcp": "0.
|
|
28
|
+
"autotel-mcp": "0.2.0"
|
|
28
29
|
},
|
|
29
30
|
"devDependencies": {
|
|
30
31
|
"@eslint/js": "^10.0.1",
|
|
31
32
|
"@types/eslint-config-prettier": "^6.11.3",
|
|
32
|
-
"@types/node": "^26.1.
|
|
33
|
+
"@types/node": "^26.1.2",
|
|
33
34
|
"@types/semver": "^7.7.1",
|
|
34
35
|
"eslint": "^10.7.0",
|
|
35
36
|
"eslint-config-prettier": "^10.1.8",
|
|
36
37
|
"eslint-plugin-unicorn": "^64.0.0",
|
|
37
|
-
"tsdown": "^0.22.
|
|
38
|
+
"tsdown": "^0.22.14",
|
|
38
39
|
"tsx": "^4.23.1",
|
|
39
40
|
"typescript": "^6.0.3",
|
|
40
41
|
"typescript-eslint": "^8.65.0",
|
|
41
42
|
"vitest": "^4.1.10",
|
|
42
|
-
"autotel-telemetry": "0.1.
|
|
43
|
+
"autotel-telemetry": "0.1.2"
|
|
43
44
|
},
|
|
44
45
|
"engines": {
|
|
45
46
|
"node": ">=22.0.0"
|