@aaac/observability 0.1.10 → 0.1.12

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/cli/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  DEFAULT_DB_PATH,
4
+ OtelEmitter,
4
5
  SqliteQueryAdapter,
5
6
  createPipeline,
6
7
  emitHumanInstruction,
@@ -10,7 +11,7 @@ import {
10
11
  generateId,
11
12
  isoToUnixNano,
12
13
  loadEventMappingConfig
13
- } from "../chunk-3H35HIMU.js";
14
+ } from "../chunk-VRQYMG5M.js";
14
15
 
15
16
  // src/cli/index.ts
16
17
  import { readFileSync } from "fs";
@@ -121,6 +122,57 @@ command_sets:
121
122
  fallback:
122
123
  type: boolean
123
124
 
125
+ backfill:
126
+ summary: Re-emit historical spans from SQLite to an OTLP backend
127
+ description: |
128
+ Reads closed/instant spans from the local SQLite database and emits them
129
+ via OtelEmitter to an OTLP-compatible backend.
130
+
131
+ IMPORTANT \u2014 idempotency: The OTel SDK does not support specifying custom
132
+ spanIds, so each backfill invocation creates NEW spans in the backend.
133
+ Repeated invocations will produce DUPLICATE data. Use --from/--to to
134
+ control scope. See #125 \xA77 for full idempotency guidance.
135
+ options:
136
+ - name: db
137
+ schema: { type: string }
138
+ description: Path to observability SQLite database (overrides default)
139
+ - name: endpoint
140
+ schema: { type: string }
141
+ description: "OTLP HTTP endpoint URL (fallback: OTEL_EXPORTER_OTLP_ENDPOINT)"
142
+ - name: service-name
143
+ schema: { type: string }
144
+ description: "OTEL service name reported to backend (default: @aaac/observability)"
145
+ - name: from
146
+ schema: { type: string }
147
+ description: Backfill spans starting at or after this ISO 8601 time
148
+ - name: to
149
+ schema: { type: string }
150
+ description: Backfill spans starting at or before this ISO 8601 time
151
+ - name: event-type
152
+ schema: { type: string }
153
+ description: "Filter by event type (e.g. agent.session)"
154
+ - name: dry-run
155
+ schema: { type: boolean }
156
+ description: Print span count without emitting to OTLP backend
157
+ exits:
158
+ '0':
159
+ description: Backfill completed
160
+ stdout:
161
+ format: json
162
+ schema:
163
+ type: object
164
+ properties:
165
+ spansFound:
166
+ type: integer
167
+ spansEmitted:
168
+ type: integer
169
+ dryRun:
170
+ type: boolean
171
+ '1':
172
+ description: Backfill failed (missing endpoint, DB error, etc.)
173
+ stderr:
174
+ format: text
175
+
124
176
  query:
125
177
  summary: Query the observability store via QueryAdapter
126
178
  options:
@@ -170,7 +222,7 @@ command_sets:
170
222
  stderr:
171
223
  format: text
172
224
  `;
173
- var CONTRACT_JSON_STR = '{\n "cli_contracts": "0.1.0",\n "info": {\n "title": "AaaC Observability CLI",\n "version": "0.1.0",\n "description": "aaac-observ \u2014 external event registration and query for @aaac/observability"\n },\n "command_sets": {\n "aaac-observ": {\n "summary": "aaac-observ \u2014 register external events and query the observability store",\n "executable": "aaac-observ",\n "commands": {\n "record": {\n "summary": "Register an external event into the observability pipeline (thin entrypoint for EventCollector.registerExternalEvent)",\n "options": [\n {\n "name": "event-type",\n "aliases": [\n "t"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Event type (e.g. promotion.commit, process.edit, agent.session)"\n },\n {\n "name": "lifecycle",\n "aliases": [\n "l"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "open",\n "close",\n "event",\n "instant"\n ]\n },\n "description": "Event lifecycle phase: open | close | event | instant"\n },\n {\n "name": "span-id",\n "aliases": [\n "s"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Span ID (auto-generated if omitted)"\n },\n {\n "name": "parent-span-id",\n "schema": {\n "type": "string"\n },\n "description": "Parent span ID for hierarchy"\n },\n {\n "name": "session-id",\n "schema": {\n "type": "string"\n },\n "description": "Session ID to associate with the event"\n },\n {\n "name": "trace-id",\n "schema": {\n "type": "string"\n },\n "description": "Trace ID for distributed tracing"\n },\n {\n "name": "source",\n "schema": {\n "type": "string"\n },\n "description": "Event source identifier (default: external)"\n },\n {\n "name": "attr",\n "schema": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "description": "Attribute as key=value (repeatable, e.g. --attr git.commit=abc123)"\n },\n {\n "name": "link",\n "schema": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "description": "Cross-axis link as targetSpanId:linkType[:targetTraceId] (repeatable)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Event registered successfully",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "eventId": {\n "type": "string"\n },\n "spanId": {\n "type": "string"\n }\n }\n }\n }\n },\n "1": {\n "description": "Registration failed (validation error or write error)",\n "stderr": {\n "format": "text"\n }\n }\n }\n },\n "record-hook": {\n "summary": "Record a Cursor/git hook event \u2014 parses stdin JSON, resolves event_mapping into 3-axis spans/links, injects session_id, and emits human-events (fail-open)",\n "arguments": [\n {\n "name": "hook-name",\n "index": 0,\n "required": true,\n "schema": {\n "type": "string"\n },\n "description": "Cursor/git hook name (e.g. afterFileEdit, subagentStart, beforeShellExecution)"\n }\n ],\n "options": [\n {\n "name": "mapping-config",\n "schema": {\n "type": "string"\n },\n "description": "Path to event-mapping.json (default: .agent-logs/config/event-mapping.json)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Hook recorded (or no-op when stdin/mapping unavailable) \u2014 always fail-open",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "recorded": {\n "type": "integer"\n },\n "fallback": {\n "type": "boolean"\n }\n }\n }\n }\n }\n }\n },\n "query": {\n "summary": "Query the observability store via QueryAdapter",\n "options": [\n {\n "name": "kind",\n "aliases": [\n "k"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "trace",\n "span",\n "search",\n "links"\n ]\n },\n "description": "Query kind: trace | span | search | links"\n },\n {\n "name": "trace-id",\n "schema": {\n "type": "string"\n },\n "description": "Trace ID for trace queries (--kind trace)"\n },\n {\n "name": "span-id",\n "aliases": [\n "s"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Span ID for span/links queries (--kind span or --kind links)"\n },\n {\n "name": "event-type",\n "schema": {\n "type": "string"\n },\n "description": "Event type filter for search queries (--kind search)"\n },\n {\n "name": "task-id",\n "schema": {\n "type": "string"\n },\n "description": "Task ID filter for search queries (--kind search)"\n },\n {\n "name": "from",\n "schema": {\n "type": "string"\n },\n "description": "Start time in ISO 8601 format for search queries (--kind search)"\n },\n {\n "name": "to",\n "schema": {\n "type": "string"\n },\n "description": "End time in ISO 8601 format for search queries (--kind search)"\n },\n {\n "name": "direction",\n "aliases": [\n "d"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "forward",\n "reverse",\n "both"\n ]\n },\n "description": "Link traversal direction for links queries (default: both)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Query succeeded \u2014 results as JSON array",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object"\n }\n }\n },\n "1": {\n "description": "Query failed",\n "stderr": {\n "format": "text"\n }\n }\n }\n }\n }\n }\n }\n}';
225
+ var CONTRACT_JSON_STR = '{\n "cli_contracts": "0.1.0",\n "info": {\n "title": "AaaC Observability CLI",\n "version": "0.1.0",\n "description": "aaac-observ \u2014 external event registration and query for @aaac/observability"\n },\n "command_sets": {\n "aaac-observ": {\n "summary": "aaac-observ \u2014 register external events and query the observability store",\n "executable": "aaac-observ",\n "commands": {\n "record": {\n "summary": "Register an external event into the observability pipeline (thin entrypoint for EventCollector.registerExternalEvent)",\n "options": [\n {\n "name": "event-type",\n "aliases": [\n "t"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Event type (e.g. promotion.commit, process.edit, agent.session)"\n },\n {\n "name": "lifecycle",\n "aliases": [\n "l"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "open",\n "close",\n "event",\n "instant"\n ]\n },\n "description": "Event lifecycle phase: open | close | event | instant"\n },\n {\n "name": "span-id",\n "aliases": [\n "s"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Span ID (auto-generated if omitted)"\n },\n {\n "name": "parent-span-id",\n "schema": {\n "type": "string"\n },\n "description": "Parent span ID for hierarchy"\n },\n {\n "name": "session-id",\n "schema": {\n "type": "string"\n },\n "description": "Session ID to associate with the event"\n },\n {\n "name": "trace-id",\n "schema": {\n "type": "string"\n },\n "description": "Trace ID for distributed tracing"\n },\n {\n "name": "source",\n "schema": {\n "type": "string"\n },\n "description": "Event source identifier (default: external)"\n },\n {\n "name": "attr",\n "schema": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "description": "Attribute as key=value (repeatable, e.g. --attr git.commit=abc123)"\n },\n {\n "name": "link",\n "schema": {\n "type": "array",\n "items": {\n "type": "string"\n }\n },\n "description": "Cross-axis link as targetSpanId:linkType[:targetTraceId] (repeatable)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Event registered successfully",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "eventId": {\n "type": "string"\n },\n "spanId": {\n "type": "string"\n }\n }\n }\n }\n },\n "1": {\n "description": "Registration failed (validation error or write error)",\n "stderr": {\n "format": "text"\n }\n }\n }\n },\n "record-hook": {\n "summary": "Record a Cursor/git hook event \u2014 parses stdin JSON, resolves event_mapping into 3-axis spans/links, injects session_id, and emits human-events (fail-open)",\n "arguments": [\n {\n "name": "hook-name",\n "index": 0,\n "required": true,\n "schema": {\n "type": "string"\n },\n "description": "Cursor/git hook name (e.g. afterFileEdit, subagentStart, beforeShellExecution)"\n }\n ],\n "options": [\n {\n "name": "mapping-config",\n "schema": {\n "type": "string"\n },\n "description": "Path to event-mapping.json (default: .agent-logs/config/event-mapping.json)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Hook recorded (or no-op when stdin/mapping unavailable) \u2014 always fail-open",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "recorded": {\n "type": "integer"\n },\n "fallback": {\n "type": "boolean"\n }\n }\n }\n }\n }\n }\n },\n "backfill": {\n "summary": "Re-emit historical spans from SQLite to an OTLP backend",\n "description": "Reads closed/instant spans from the local SQLite database and emits them\\nvia OtelEmitter to an OTLP-compatible backend.\\n\\nIMPORTANT \u2014 idempotency: The OTel SDK does not support specifying custom\\nspanIds, so each backfill invocation creates NEW spans in the backend.\\nRepeated invocations will produce DUPLICATE data. Use --from/--to to\\ncontrol scope. See #125 \xA77 for full idempotency guidance.\\n",\n "options": [\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n },\n {\n "name": "endpoint",\n "schema": {\n "type": "string"\n },\n "description": "OTLP HTTP endpoint URL (fallback: OTEL_EXPORTER_OTLP_ENDPOINT)"\n },\n {\n "name": "service-name",\n "schema": {\n "type": "string"\n },\n "description": "OTEL service name reported to backend (default: @aaac/observability)"\n },\n {\n "name": "from",\n "schema": {\n "type": "string"\n },\n "description": "Backfill spans starting at or after this ISO 8601 time"\n },\n {\n "name": "to",\n "schema": {\n "type": "string"\n },\n "description": "Backfill spans starting at or before this ISO 8601 time"\n },\n {\n "name": "event-type",\n "schema": {\n "type": "string"\n },\n "description": "Filter by event type (e.g. agent.session)"\n },\n {\n "name": "dry-run",\n "schema": {\n "type": "boolean"\n },\n "description": "Print span count without emitting to OTLP backend"\n }\n ],\n "exits": {\n "0": {\n "description": "Backfill completed",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object",\n "properties": {\n "spansFound": {\n "type": "integer"\n },\n "spansEmitted": {\n "type": "integer"\n },\n "dryRun": {\n "type": "boolean"\n }\n }\n }\n }\n },\n "1": {\n "description": "Backfill failed (missing endpoint, DB error, etc.)",\n "stderr": {\n "format": "text"\n }\n }\n }\n },\n "query": {\n "summary": "Query the observability store via QueryAdapter",\n "options": [\n {\n "name": "kind",\n "aliases": [\n "k"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "trace",\n "span",\n "search",\n "links"\n ]\n },\n "description": "Query kind: trace | span | search | links"\n },\n {\n "name": "trace-id",\n "schema": {\n "type": "string"\n },\n "description": "Trace ID for trace queries (--kind trace)"\n },\n {\n "name": "span-id",\n "aliases": [\n "s"\n ],\n "schema": {\n "type": "string"\n },\n "description": "Span ID for span/links queries (--kind span or --kind links)"\n },\n {\n "name": "event-type",\n "schema": {\n "type": "string"\n },\n "description": "Event type filter for search queries (--kind search)"\n },\n {\n "name": "task-id",\n "schema": {\n "type": "string"\n },\n "description": "Task ID filter for search queries (--kind search)"\n },\n {\n "name": "from",\n "schema": {\n "type": "string"\n },\n "description": "Start time in ISO 8601 format for search queries (--kind search)"\n },\n {\n "name": "to",\n "schema": {\n "type": "string"\n },\n "description": "End time in ISO 8601 format for search queries (--kind search)"\n },\n {\n "name": "direction",\n "aliases": [\n "d"\n ],\n "schema": {\n "type": "string",\n "enum": [\n "forward",\n "reverse",\n "both"\n ]\n },\n "description": "Link traversal direction for links queries (default: both)"\n },\n {\n "name": "db",\n "schema": {\n "type": "string"\n },\n "description": "Path to observability SQLite database (overrides default)"\n }\n ],\n "exits": {\n "0": {\n "description": "Query succeeded \u2014 results as JSON array",\n "stdout": {\n "format": "json",\n "schema": {\n "type": "object"\n }\n }\n },\n "1": {\n "description": "Query failed",\n "stderr": {\n "format": "text"\n }\n }\n }\n }\n }\n }\n }\n}';
174
226
 
175
227
  // src/generated/program.ts
176
228
  function createProgram(handlers2, version) {
@@ -182,6 +234,9 @@ function createProgram(handlers2, version) {
182
234
  program2.command("record-hook").description("Record a Cursor/git hook event \u2014 parses stdin JSON, resolves event_mapping into 3-axis spans/links, injects session_id, and emits human-events (fail-open)").argument("<hook-name>", "Cursor/git hook name (e.g. afterFileEdit, subagentStart, beforeShellExecution)").option("--mapping-config <value>", "Path to event-mapping.json (default: .agent-logs/config/event-mapping.json)").option("--db <value>", "Path to observability SQLite database (overrides default)").action(async (hookName, opts, cmd) => {
183
235
  await handlers2.recordHook(hookName, opts, cmd.optsWithGlobals());
184
236
  });
237
+ program2.command("backfill").description("Re-emit historical spans from SQLite to an OTLP backend").option("--db <value>", "Path to observability SQLite database (overrides default)").option("--endpoint <value>", "OTLP HTTP endpoint URL (fallback: OTEL_EXPORTER_OTLP_ENDPOINT)").option("--service-name <value>", "OTEL service name reported to backend (default: @aaac/observability)").option("--from <value>", "Backfill spans starting at or after this ISO 8601 time").option("--to <value>", "Backfill spans starting at or before this ISO 8601 time").option("--event-type <value>", "Filter by event type (e.g. agent.session)").option("--dry-run", "Print span count without emitting to OTLP backend").option("--flush-timeout-ms <ms>", "Maximum ms to wait for OTLP flush before exit (default: 2000; use \u226530000 for >5k spans)", (v) => parseInt(v, 10)).action(async (opts, cmd) => {
238
+ await handlers2.backfill(opts, cmd.optsWithGlobals());
239
+ });
185
240
  program2.command("query").description("Query the observability store via QueryAdapter").option("-k, --kind <value>", "Query kind: trace | span | search | links").option("--trace-id <value>", "Trace ID for trace queries (--kind trace)").option("-s, --span-id <value>", "Span ID for span/links queries (--kind span or --kind links)").option("--event-type <value>", "Event type filter for search queries (--kind search)").option("--task-id <value>", "Task ID filter for search queries (--kind search)").option("--from <value>", "Start time in ISO 8601 format for search queries (--kind search)").option("--to <value>", "End time in ISO 8601 format for search queries (--kind search)").option("-d, --direction <value>", "Link traversal direction for links queries (default: both)").option("--db <value>", "Path to observability SQLite database (overrides default)").action(async (opts, cmd) => {
186
241
  await handlers2.query(opts, cmd.optsWithGlobals());
187
242
  });
@@ -202,7 +257,7 @@ function createProgram(handlers2, version) {
202
257
  type: "cli-contracts/extract",
203
258
  extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
204
259
  specVersion: doc.cli_contracts ?? "0.1.0",
205
- commands: ["aaac-observ.record", "aaac-observ.record-hook", "aaac-observ.query"]
260
+ commands: ["aaac-observ.record", "aaac-observ.record-hook", "aaac-observ.backfill", "aaac-observ.query"]
206
261
  };
207
262
  }
208
263
  Object.assign(out, doc);
@@ -219,7 +274,7 @@ function createProgram(handlers2, version) {
219
274
  yamlLines.push("extractedAt: " + (/* @__PURE__ */ new Date()).toISOString());
220
275
  yamlLines.push("spec_version: " + (doc.cli_contracts ?? "0.1.0"));
221
276
  yamlLines.push("commands:");
222
- for (const id of ["aaac-observ.record", "aaac-observ.record-hook", "aaac-observ.query"]) {
277
+ for (const id of ["aaac-observ.record", "aaac-observ.record-hook", "aaac-observ.backfill", "aaac-observ.query"]) {
223
278
  yamlLines.push(" - " + id);
224
279
  }
225
280
  }
@@ -640,11 +695,131 @@ async function handleQuery(options, _parentOpts) {
640
695
  adapter.close();
641
696
  }
642
697
 
698
+ // src/cli/backfill.ts
699
+ function spanRecordToMappedSpan(span, links) {
700
+ const start = span.startTime ?? 0n;
701
+ const end = span.endTime ?? start;
702
+ const mappedLinks = links.map((l) => ({
703
+ targetSpanId: l.targetSpanId,
704
+ targetTraceId: l.targetTraceId ?? void 0,
705
+ attributes: {
706
+ link_type: l.linkType,
707
+ ...l.attributes ?? {}
708
+ }
709
+ }));
710
+ return {
711
+ spanId: span.spanId,
712
+ traceId: span.traceId ?? "",
713
+ parentSpanId: span.parentSpanId ?? void 0,
714
+ name: span.eventType,
715
+ startTimeUnixNano: start,
716
+ endTimeUnixNano: end,
717
+ attributes: span.attributes,
718
+ spanEvents: [],
719
+ // span events are not stored in the spans table
720
+ links: mappedLinks
721
+ };
722
+ }
723
+ async function handleBackfill(options, _parentOpts) {
724
+ const dbPath = options.db ?? DEFAULT_DB_PATH;
725
+ const dryRun = options.dryRun ?? false;
726
+ const endpoint = options.endpoint ?? process.env["OTEL_EXPORTER_OTLP_ENDPOINT"];
727
+ if (!dryRun && !endpoint) {
728
+ process.stderr.write(
729
+ "Error: --endpoint is required (or set OTEL_EXPORTER_OTLP_ENDPOINT) unless --dry-run\n"
730
+ );
731
+ process.exit(1);
732
+ }
733
+ let fromNano;
734
+ let toNano;
735
+ if (options.from) {
736
+ if (Number.isNaN(Date.parse(options.from))) {
737
+ process.stderr.write(
738
+ `Error: invalid --from value "${options.from}" \u2014 expected ISO 8601
739
+ `
740
+ );
741
+ process.exit(1);
742
+ }
743
+ fromNano = isoToUnixNano(options.from);
744
+ }
745
+ if (options.to) {
746
+ if (Number.isNaN(Date.parse(options.to))) {
747
+ process.stderr.write(
748
+ `Error: invalid --to value "${options.to}" \u2014 expected ISO 8601
749
+ `
750
+ );
751
+ process.exit(1);
752
+ }
753
+ toNano = isoToUnixNano(options.to);
754
+ }
755
+ const adapter = new SqliteQueryAdapter(dbPath);
756
+ let spans;
757
+ try {
758
+ spans = adapter.querySpans({
759
+ ...options.eventType ? { eventType: options.eventType } : {},
760
+ ...fromNano !== void 0 ? { fromTimeUnixNano: fromNano } : {},
761
+ ...toNano !== void 0 ? { toTimeUnixNano: toNano } : {}
762
+ });
763
+ spans = spans.filter((s) => s.status === "closed" || s.status === "instant");
764
+ } catch (err) {
765
+ process.stderr.write(
766
+ `Error: failed to query SQLite \u2014 ${err instanceof Error ? err.message : String(err)}
767
+ `
768
+ );
769
+ adapter.close();
770
+ process.exit(1);
771
+ return;
772
+ }
773
+ const spansFound = spans.length;
774
+ if (dryRun) {
775
+ adapter.close();
776
+ const result2 = { spansFound, spansEmitted: 0, dryRun: true };
777
+ process.stdout.write(JSON.stringify(result2) + "\n");
778
+ return;
779
+ }
780
+ const mappedSpans = [];
781
+ for (const span of spans) {
782
+ let links = [];
783
+ try {
784
+ links = adapter.getLinks(span.spanId, "forward");
785
+ } catch {
786
+ }
787
+ mappedSpans.push(spanRecordToMappedSpan(span, links));
788
+ }
789
+ adapter.close();
790
+ const effectiveFlushMs = options.flushTimeoutMs ?? 2e3;
791
+ if (spansFound > 1e3 && effectiveFlushMs < 1e4) {
792
+ process.stderr.write(
793
+ `Warning: ${spansFound} spans queued but --flush-timeout-ms is ${effectiveFlushMs} ms (< 10 000 ms). Large batches may not fully flush before the deadline. Recommended: --flush-timeout-ms 30000
794
+ `
795
+ );
796
+ }
797
+ const emitter = new OtelEmitter({
798
+ endpoint,
799
+ serviceName: options.serviceName
800
+ });
801
+ let spansEmitted = 0;
802
+ for (const mapped of mappedSpans) {
803
+ try {
804
+ emitter.emit(mapped);
805
+ spansEmitted++;
806
+ } catch {
807
+ }
808
+ }
809
+ await Promise.race([
810
+ emitter.shutdown(),
811
+ new Promise((resolve2) => setTimeout(resolve2, effectiveFlushMs))
812
+ ]);
813
+ const result = { spansFound, spansEmitted, dryRun: false };
814
+ process.stdout.write(JSON.stringify(result) + "\n");
815
+ }
816
+
643
817
  // src/cli/handlers.ts
644
818
  var handlers = {
645
819
  record: handleRecord,
646
820
  recordHook: handleRecordHook,
647
- query: handleQuery
821
+ query: handleQuery,
822
+ backfill: handleBackfill
648
823
  };
649
824
 
650
825
  // src/cli/index.ts