aisnitch 0.2.2 → 0.2.4

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.
@@ -41,7 +41,7 @@ var import_commander = require("commander");
41
41
 
42
42
  // src/package-info.ts
43
43
  var AISNITCH_PACKAGE_NAME = "aisnitch";
44
- var AISNITCH_VERSION = "0.2.2";
44
+ var AISNITCH_VERSION = "0.2.3";
45
45
  var AISNITCH_DESCRIPTION = "Universal bridge for AI coding tool activity \u2014 capture, normalize, stream.";
46
46
 
47
47
  // src/core/events/schema.ts
@@ -3253,8 +3253,16 @@ var EventBus = class {
3253
3253
  },
3254
3254
  "Published event"
3255
3255
  );
3256
- this.emitter.emit("event", parsedEvent.data);
3257
- this.emitter.emit(`event:${parsedEvent.data.type}`, parsedEvent.data);
3256
+ try {
3257
+ this.emitter.emit("event", parsedEvent.data);
3258
+ } catch (error) {
3259
+ logger.warn({ error, eventType: parsedEvent.data.type }, "\u{1F4D6} Error in EventBus global subscriber");
3260
+ }
3261
+ try {
3262
+ this.emitter.emit(`event:${parsedEvent.data.type}`, parsedEvent.data);
3263
+ } catch (error) {
3264
+ logger.warn({ error, eventType: parsedEvent.data.type }, "\u{1F4D6} Error in EventBus typed subscriber");
3265
+ }
3258
3266
  return true;
3259
3267
  }
3260
3268
  /**
@@ -3520,6 +3528,7 @@ var WSServer = class {
3520
3528
  });
3521
3529
  socket.on("error", (error) => {
3522
3530
  logger.warn({ error }, "WebSocket consumer error");
3531
+ this.consumers.delete(socket);
3523
3532
  });
3524
3533
  const welcomeMessage = {
3525
3534
  type: "welcome",
@@ -3674,10 +3683,17 @@ var HTTPReceiver = class {
3674
3683
  }
3675
3684
  async handleRequest(request, response, options) {
3676
3685
  this.requestCount += 1;
3677
- const requestUrl = new URL(
3678
- request.url ?? "/",
3679
- `http://${this.host}:${this.port ?? options.port}`
3680
- );
3686
+ let requestUrl;
3687
+ try {
3688
+ requestUrl = new URL(
3689
+ request.url ?? "/",
3690
+ `http://${this.host}:${this.port ?? options.port}`
3691
+ );
3692
+ } catch {
3693
+ this.invalidRequestCount += 1;
3694
+ this.sendJson(response, 400, { error: "malformed request url" });
3695
+ return;
3696
+ }
3681
3697
  if (request.method === "GET" && requestUrl.pathname === "/health") {
3682
3698
  this.sendJson(response, 200, options.getHealthSnapshot());
3683
3699
  return;
@@ -4018,15 +4034,24 @@ var BaseAdapter = class {
4018
4034
  cwd: data.cwd ?? context.cwd
4019
4035
  }
4020
4036
  });
4021
- const published = await this.publishEventImplementation(event, {
4022
- cwd: context.cwd,
4023
- env: context.env,
4024
- hookPayload: context.hookPayload,
4025
- pid: context.pid,
4026
- sessionId,
4027
- source: context.source,
4028
- transcriptPath: context.transcriptPath
4029
- });
4037
+ let published;
4038
+ try {
4039
+ published = await this.publishEventImplementation(event, {
4040
+ cwd: context.cwd,
4041
+ env: context.env,
4042
+ hookPayload: context.hookPayload,
4043
+ pid: context.pid,
4044
+ sessionId,
4045
+ source: context.source,
4046
+ transcriptPath: context.transcriptPath
4047
+ });
4048
+ } catch (error) {
4049
+ logger.error(
4050
+ { error, eventType: type, adapter: this.name, sessionId },
4051
+ "\u{1F4D6} Failed to publish event \u2014 swallowing to prevent daemon crash"
4052
+ );
4053
+ published = false;
4054
+ }
4030
4055
  if (published) {
4031
4056
  this.eventsEmitted += 1;
4032
4057
  }
@@ -9154,22 +9179,40 @@ var AdapterRegistry = class {
9154
9179
  }
9155
9180
  /**
9156
9181
  * Starts every adapter enabled in the current AISnitch config.
9182
+ * 📖 Each adapter is started independently — one failure does not prevent
9183
+ * the others from starting.
9157
9184
  */
9158
9185
  async startAll(config) {
9159
9186
  for (const adapter of this.list()) {
9160
9187
  if (config.adapters[adapter.name]?.enabled !== true) {
9161
9188
  continue;
9162
9189
  }
9163
- await adapter.start();
9190
+ try {
9191
+ await adapter.start();
9192
+ } catch (error) {
9193
+ logger.error(
9194
+ { error, adapter: adapter.name },
9195
+ `\u{1F4D6} Failed to start adapter "${adapter.name}" \u2014 skipping`
9196
+ );
9197
+ }
9164
9198
  }
9165
9199
  }
9166
9200
  /**
9167
9201
  * Stops every adapter in reverse registration order.
9202
+ * 📖 Each adapter is stopped independently — one failure does not prevent
9203
+ * the others from being stopped.
9168
9204
  */
9169
9205
  async stopAll() {
9170
9206
  const adapters = this.list().reverse();
9171
9207
  for (const adapter of adapters) {
9172
- await adapter.stop();
9208
+ try {
9209
+ await adapter.stop();
9210
+ } catch (error) {
9211
+ logger.warn(
9212
+ { error, adapter: adapter.name },
9213
+ `\u{1F4D6} Error stopping adapter "${adapter.name}" \u2014 continuing`
9214
+ );
9215
+ }
9173
9216
  }
9174
9217
  }
9175
9218
  };
@@ -9274,26 +9317,32 @@ var Pipeline = class {
9274
9317
  await adapter.handleHook(payload);
9275
9318
  });
9276
9319
  }
9320
+ try {
9321
+ this.wsPort = await this.wsServer.start({
9322
+ port: resolvedWsPort,
9323
+ eventBus: this.eventBus,
9324
+ activeTools
9325
+ });
9326
+ this.httpPort = await this.httpReceiver.start({
9327
+ port: resolvedHttpPort,
9328
+ onHook: async (tool, payload) => {
9329
+ await this.handleHook(tool, payload);
9330
+ },
9331
+ getHealthSnapshot: () => this.getHealthSnapshot()
9332
+ });
9333
+ this.socketPath = await this.udsServer.start({
9334
+ socketPath,
9335
+ onEvent: async (event) => {
9336
+ await this.publishEvent(event);
9337
+ }
9338
+ });
9339
+ await this.adapterRegistry.startAll(config);
9340
+ } catch (error) {
9341
+ logger.error({ error }, "\u{1F4D6} Pipeline start failed \u2014 rolling back already-started components");
9342
+ await this.rollbackPartialStart();
9343
+ throw error;
9344
+ }
9277
9345
  this.startedAt = Date.now();
9278
- this.wsPort = await this.wsServer.start({
9279
- port: resolvedWsPort,
9280
- eventBus: this.eventBus,
9281
- activeTools
9282
- });
9283
- this.httpPort = await this.httpReceiver.start({
9284
- port: resolvedHttpPort,
9285
- onHook: async (tool, payload) => {
9286
- await this.handleHook(tool, payload);
9287
- },
9288
- getHealthSnapshot: () => this.getHealthSnapshot()
9289
- });
9290
- this.socketPath = await this.udsServer.start({
9291
- socketPath,
9292
- onEvent: async (event) => {
9293
- await this.publishEvent(event);
9294
- }
9295
- });
9296
- await this.adapterRegistry.startAll(config);
9297
9346
  logger.info(this.getStatus(), "Core pipeline started");
9298
9347
  return this.getStatus();
9299
9348
  }
@@ -9301,10 +9350,25 @@ var Pipeline = class {
9301
9350
  * Stops every pipeline component in reverse dependency order.
9302
9351
  */
9303
9352
  async stop() {
9304
- await this.adapterRegistry?.stopAll();
9305
- await this.httpReceiver.stop();
9306
- await this.udsServer.stop();
9307
- await this.wsServer.stop();
9353
+ const stopSafely = async (label, fn) => {
9354
+ try {
9355
+ await fn();
9356
+ } catch (error) {
9357
+ logger.warn({ error }, `\u{1F4D6} Error while stopping ${label} \u2014 continuing shutdown`);
9358
+ }
9359
+ };
9360
+ await stopSafely("adapter registry", async () => {
9361
+ await this.adapterRegistry?.stopAll();
9362
+ });
9363
+ await stopSafely("HTTP receiver", async () => {
9364
+ await this.httpReceiver.stop();
9365
+ });
9366
+ await stopSafely("UDS server", async () => {
9367
+ await this.udsServer.stop();
9368
+ });
9369
+ await stopSafely("WebSocket server", async () => {
9370
+ await this.wsServer.stop();
9371
+ });
9308
9372
  this.eventBus.unsubscribeAll();
9309
9373
  this.adapterRegistry = null;
9310
9374
  this.enabledTools.clear();
@@ -9321,11 +9385,50 @@ var Pipeline = class {
9321
9385
  registerHookHandler(tool, handler) {
9322
9386
  this.hookHandlers.set(tool, handler);
9323
9387
  }
9388
+ /**
9389
+ * 📖 Rolls back any components that were successfully started before a
9390
+ * failure occurred, preventing orphaned servers or leaking resources.
9391
+ */
9392
+ async rollbackPartialStart() {
9393
+ const stopSafe = async (label, fn) => {
9394
+ try {
9395
+ await fn();
9396
+ } catch (error) {
9397
+ logger.warn({ error }, `\u{1F4D6} Error rolling back ${label}`);
9398
+ }
9399
+ };
9400
+ await stopSafe("adapter registry", async () => {
9401
+ await this.adapterRegistry?.stopAll();
9402
+ });
9403
+ await stopSafe("UDS server", async () => {
9404
+ await this.udsServer.stop();
9405
+ });
9406
+ await stopSafe("HTTP receiver", async () => {
9407
+ await this.httpReceiver.stop();
9408
+ });
9409
+ await stopSafe("WebSocket server", async () => {
9410
+ await this.wsServer.stop();
9411
+ });
9412
+ this.adapterRegistry = null;
9413
+ this.enabledTools.clear();
9414
+ this.hookHandlers.clear();
9415
+ }
9324
9416
  /**
9325
9417
  * Publishes an event after best-effort context enrichment.
9418
+ * 📖 If enrichment fails, the original event is published un-enriched
9419
+ * rather than being dropped entirely.
9326
9420
  */
9327
9421
  async publishEvent(event, context = {}) {
9328
- const enrichedEvent = await this.contextDetector.enrich(event, context);
9422
+ let enrichedEvent;
9423
+ try {
9424
+ enrichedEvent = await this.contextDetector.enrich(event, context);
9425
+ } catch (error) {
9426
+ logger.warn(
9427
+ { error, eventId: event.id },
9428
+ "\u{1F4D6} Context enrichment failed \u2014 publishing un-enriched event"
9429
+ );
9430
+ enrichedEvent = event;
9431
+ }
9329
9432
  return this.eventBus.publish(enrichedEvent);
9330
9433
  }
9331
9434
  /**
@@ -9361,6 +9464,16 @@ var Pipeline = class {
9361
9464
  };
9362
9465
  }
9363
9466
  async handleHook(tool, payload) {
9467
+ try {
9468
+ await this.handleHookInner(tool, payload);
9469
+ } catch (error) {
9470
+ logger.error(
9471
+ { error, tool },
9472
+ "\u{1F4D6} Unhandled error in hook handler \u2014 swallowing to prevent daemon crash"
9473
+ );
9474
+ }
9475
+ }
9476
+ async handleHookInner(tool, payload) {
9364
9477
  if (!this.enabledTools.has(tool)) {
9365
9478
  logger.debug({ tool }, "Ignoring hook for disabled tool");
9366
9479
  return;
@@ -10536,18 +10649,22 @@ function parseSocketPayload(data) {
10536
10649
  return parsedEvent.success ? parsedEvent.data : null;
10537
10650
  }
10538
10651
  function parseUnknownPayload(data) {
10539
- if (typeof data === "string") {
10540
- return JSON.parse(data);
10541
- }
10542
- if (Array.isArray(data)) {
10543
- return JSON.parse(Buffer.concat(data).toString("utf8"));
10544
- }
10545
- if (data instanceof ArrayBuffer) {
10546
- return JSON.parse(
10547
- Buffer.from(new Uint8Array(data)).toString("utf8")
10548
- );
10652
+ try {
10653
+ if (typeof data === "string") {
10654
+ return JSON.parse(data);
10655
+ }
10656
+ if (Array.isArray(data)) {
10657
+ return JSON.parse(Buffer.concat(data).toString("utf8"));
10658
+ }
10659
+ if (data instanceof ArrayBuffer) {
10660
+ return JSON.parse(
10661
+ Buffer.from(new Uint8Array(data)).toString("utf8")
10662
+ );
10663
+ }
10664
+ return JSON.parse(Buffer.from(data).toString("utf8"));
10665
+ } catch {
10666
+ return null;
10549
10667
  }
10550
- return JSON.parse(Buffer.from(data).toString("utf8"));
10551
10668
  }
10552
10669
 
10553
10670
  // src/tui/hooks/useKeyBinds.ts
@@ -11275,16 +11392,20 @@ function ManagedDaemonApp({
11275
11392
  }
11276
11393
  function parseSocketPayload2(data) {
11277
11394
  let parsedPayload;
11278
- if (typeof data === "string") {
11279
- parsedPayload = JSON.parse(data);
11280
- } else if (Array.isArray(data)) {
11281
- parsedPayload = JSON.parse(Buffer.concat(data).toString("utf8"));
11282
- } else if (data instanceof ArrayBuffer) {
11283
- parsedPayload = JSON.parse(
11284
- Buffer.from(new Uint8Array(data)).toString("utf8")
11285
- );
11286
- } else {
11287
- parsedPayload = JSON.parse(Buffer.from(data).toString("utf8"));
11395
+ try {
11396
+ if (typeof data === "string") {
11397
+ parsedPayload = JSON.parse(data);
11398
+ } else if (Array.isArray(data)) {
11399
+ parsedPayload = JSON.parse(Buffer.concat(data).toString("utf8"));
11400
+ } else if (data instanceof ArrayBuffer) {
11401
+ parsedPayload = JSON.parse(
11402
+ Buffer.from(new Uint8Array(data)).toString("utf8")
11403
+ );
11404
+ } else {
11405
+ parsedPayload = JSON.parse(Buffer.from(data).toString("utf8"));
11406
+ }
11407
+ } catch {
11408
+ return null;
11288
11409
  }
11289
11410
  if (typeof parsedPayload === "object" && parsedPayload !== null && "type" in parsedPayload && parsedPayload.type === "welcome") {
11290
11411
  return null;
@@ -11779,6 +11900,183 @@ function toConfigArgv(options) {
11779
11900
  return options.configPath ? ["--config", options.configPath] : [];
11780
11901
  }
11781
11902
 
11903
+ // src/cli/live-logger.ts
11904
+ var import_node_events5 = require("events");
11905
+ var import_ws5 = __toESM(require("ws"), 1);
11906
+ function formatLoggerEventBlock(event) {
11907
+ const headerSegments = [
11908
+ colorize(`#${event["aisnitch.seqnum"]}`, EVENT_COLORS[event.type]),
11909
+ colorize(event["aisnitch.tool"], TOOL_COLORS[event["aisnitch.tool"]]),
11910
+ colorize(event.type, EVENT_COLORS[event.type]),
11911
+ colorize(formatSessionLabelFromEvent(event), TUI_THEME.warning),
11912
+ colorize(event.time, TUI_THEME.muted)
11913
+ ];
11914
+ const flattenedLines = flattenEventRecord(event);
11915
+ return [
11916
+ `${colorize("\u256D\u2500", TUI_THEME.frame)} ${headerSegments.join(colorize(" ", TUI_THEME.muted))}`,
11917
+ ...flattenedLines.map((line) => `${colorize("\u2502", TUI_THEME.frame)} ${line}`),
11918
+ colorize("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500", TUI_THEME.frame)
11919
+ ].join("\n");
11920
+ }
11921
+ function formatLoggerWelcomeLine(message) {
11922
+ const tools = message.tools.length > 0 ? message.tools.join(", ") : "none configured";
11923
+ return `${colorize("AISnitch logger attached", TUI_THEME.success)} ${colorize(`v${message.version}`, TUI_THEME.warning)} ${colorize(`tools=${tools}`, TUI_THEME.muted)}`;
11924
+ }
11925
+ async function attachWebSocketLogger(url, output, filters = {}) {
11926
+ const socket = new import_ws5.default(url);
11927
+ socket.on("message", (data) => {
11928
+ const parsedPayload = parseSocketMessage(data);
11929
+ if (isWelcomeMessage(parsedPayload)) {
11930
+ output.stdout(`${formatLoggerWelcomeLine(parsedPayload)}
11931
+ `);
11932
+ return;
11933
+ }
11934
+ const parsedEvent = AISnitchEventSchema.safeParse(parsedPayload);
11935
+ if (!parsedEvent.success) {
11936
+ output.stderr(
11937
+ `${colorize("logger:", TUI_THEME.danger)} received an unrecognized event payload.
11938
+ `
11939
+ );
11940
+ return;
11941
+ }
11942
+ if (!matchesFilters(parsedEvent.data, filters)) {
11943
+ return;
11944
+ }
11945
+ output.stdout(`${formatLoggerEventBlock(parsedEvent.data)}
11946
+ `);
11947
+ });
11948
+ socket.on("error", (error) => {
11949
+ output.stderr(
11950
+ `${colorize("logger:", TUI_THEME.danger)} ${error instanceof Error ? error.message : "unknown socket error"}
11951
+ `
11952
+ );
11953
+ });
11954
+ await (0, import_node_events5.once)(socket, "open");
11955
+ return async () => {
11956
+ if (socket.readyState === import_ws5.default.CLOSING || socket.readyState === import_ws5.default.CLOSED) {
11957
+ return;
11958
+ }
11959
+ socket.close();
11960
+ await (0, import_node_events5.once)(socket, "close");
11961
+ };
11962
+ }
11963
+ function matchesFilters(event, filters) {
11964
+ if (filters.tool && event["aisnitch.tool"] !== filters.tool) {
11965
+ return false;
11966
+ }
11967
+ if (filters.type && event.type !== filters.type) {
11968
+ return false;
11969
+ }
11970
+ return true;
11971
+ }
11972
+ function flattenEventRecord(event) {
11973
+ const lines = [];
11974
+ flattenValue(lines, "", event);
11975
+ return lines;
11976
+ }
11977
+ function flattenValue(lines, currentPath, value) {
11978
+ if (Array.isArray(value)) {
11979
+ if (value.length === 0) {
11980
+ lines.push(formatLoggerField(currentPath, "[]", "empty"));
11981
+ return;
11982
+ }
11983
+ value.forEach((entry, index) => {
11984
+ flattenValue(lines, `${currentPath}[${index}]`, entry);
11985
+ });
11986
+ return;
11987
+ }
11988
+ if (isRecord10(value)) {
11989
+ const entries = Object.entries(value).sort(
11990
+ ([left], [right]) => left.localeCompare(right)
11991
+ );
11992
+ if (entries.length === 0) {
11993
+ lines.push(formatLoggerField(currentPath, "{}", "empty"));
11994
+ return;
11995
+ }
11996
+ for (const [key, entry] of entries) {
11997
+ const nextPath = currentPath.length === 0 ? key : `${currentPath}.${key}`;
11998
+ flattenValue(lines, nextPath, entry);
11999
+ }
12000
+ return;
12001
+ }
12002
+ lines.push(formatLoggerField(currentPath, value, inferValueKind(value)));
12003
+ }
12004
+ function formatLoggerField(path, value, kind) {
12005
+ const renderedValue = typeof value === "string" ? JSON.stringify(value) : value === null ? "null" : typeof value === "undefined" ? "undefined" : JSON.stringify(value);
12006
+ return `${colorize(path, TUI_THEME.warning)} ${colorize("=", TUI_THEME.muted)} ${colorize(
12007
+ renderedValue,
12008
+ getValueColor(kind)
12009
+ )}`;
12010
+ }
12011
+ function inferValueKind(value) {
12012
+ if (value === null) {
12013
+ return "null";
12014
+ }
12015
+ switch (typeof value) {
12016
+ case "string":
12017
+ return "string";
12018
+ case "number":
12019
+ return "number";
12020
+ case "boolean":
12021
+ return "boolean";
12022
+ default:
12023
+ return "other";
12024
+ }
12025
+ }
12026
+ function getValueColor(kind) {
12027
+ switch (kind) {
12028
+ case "string":
12029
+ return TUI_THEME.panelBody;
12030
+ case "number":
12031
+ return TUI_THEME.success;
12032
+ case "boolean":
12033
+ return TUI_THEME.warning;
12034
+ case "null":
12035
+ return TUI_THEME.danger;
12036
+ case "empty":
12037
+ return TUI_THEME.muted;
12038
+ default:
12039
+ return TUI_THEME.panelBody;
12040
+ }
12041
+ }
12042
+ function colorize(value, color) {
12043
+ const [red, green, blue] = hexToRgb(color);
12044
+ return `\x1B[38;2;${red};${green};${blue}m${value}\x1B[39m`;
12045
+ }
12046
+ function hexToRgb(hexColor) {
12047
+ const sanitized = hexColor.slice(1);
12048
+ return [
12049
+ Number.parseInt(sanitized.slice(0, 2), 16),
12050
+ Number.parseInt(sanitized.slice(2, 4), 16),
12051
+ Number.parseInt(sanitized.slice(4, 6), 16)
12052
+ ];
12053
+ }
12054
+ function parseSocketMessage(data) {
12055
+ try {
12056
+ if (typeof data === "string") {
12057
+ return JSON.parse(data);
12058
+ }
12059
+ if (Array.isArray(data)) {
12060
+ return JSON.parse(Buffer.concat(data).toString("utf8"));
12061
+ }
12062
+ if (data instanceof ArrayBuffer) {
12063
+ return JSON.parse(Buffer.from(new Uint8Array(data)).toString("utf8"));
12064
+ }
12065
+ return JSON.parse(Buffer.from(data).toString("utf8"));
12066
+ } catch {
12067
+ return null;
12068
+ }
12069
+ }
12070
+ function isWelcomeMessage(payload) {
12071
+ if (!isRecord10(payload)) {
12072
+ return false;
12073
+ }
12074
+ return payload.type === "welcome" && typeof payload.version === "string" && Array.isArray(payload.tools);
12075
+ }
12076
+ function isRecord10(value) {
12077
+ return typeof value === "object" && value !== null && !Array.isArray(value);
12078
+ }
12079
+
11782
12080
  // src/cli/runtime.ts
11783
12081
  var execFile11 = (0, import_node_util11.promisify)(import_node_child_process12.execFile);
11784
12082
  var DAEMON_READY_TIMEOUT_MS = 4e3;
@@ -12115,6 +12413,20 @@ function createCliRuntime(dependencies = {}) {
12115
12413
  process.once("SIGINT", () => {
12116
12414
  void shutdown("SIGINT");
12117
12415
  });
12416
+ process.once("uncaughtException", (error) => {
12417
+ output.stderr(
12418
+ `AISnitch crashed: ${error instanceof Error ? error.message : "unknown exception"}
12419
+ `
12420
+ );
12421
+ void shutdown("uncaughtException", 1);
12422
+ });
12423
+ process.once("unhandledRejection", (reason) => {
12424
+ output.stderr(
12425
+ `AISnitch rejected a promise: ${reason instanceof Error ? reason.message : "unknown rejection"}
12426
+ `
12427
+ );
12428
+ void shutdown("unhandledRejection", 1);
12429
+ });
12118
12430
  await renderForegroundTui({
12119
12431
  configuredAdapters: getEnabledAdapters(config),
12120
12432
  eventBus: pipeline.getEventBus(),
@@ -12256,6 +12568,43 @@ function createCliRuntime(dependencies = {}) {
12256
12568
  }
12257
12569
  });
12258
12570
  }
12571
+ async function logger2(options) {
12572
+ const snapshot = await getStatusSnapshot(options);
12573
+ if (!snapshot.running) {
12574
+ throw new Error(
12575
+ "AISnitch logger requires a running daemon. Start one with `aisnitch start --daemon` or use `aisnitch start` first."
12576
+ );
12577
+ }
12578
+ const closeLogger = await attachWebSocketLogger(
12579
+ `ws://127.0.0.1:${snapshot.wsPort}`,
12580
+ output,
12581
+ {
12582
+ tool: options.tool,
12583
+ type: options.type
12584
+ }
12585
+ );
12586
+ await new Promise((resolve2) => {
12587
+ let closed = false;
12588
+ const shutdown = async () => {
12589
+ if (closed) {
12590
+ return;
12591
+ }
12592
+ closed = true;
12593
+ process.off("SIGINT", handleSigint);
12594
+ process.off("SIGTERM", handleSigterm);
12595
+ await Promise.resolve(closeLogger());
12596
+ resolve2();
12597
+ };
12598
+ const handleSigint = () => {
12599
+ void shutdown();
12600
+ };
12601
+ const handleSigterm = () => {
12602
+ void shutdown();
12603
+ };
12604
+ process.once("SIGINT", handleSigint);
12605
+ process.once("SIGTERM", handleSigterm);
12606
+ });
12607
+ }
12259
12608
  async function mock(selection, options) {
12260
12609
  const pathOptions = toPathOptions2(options);
12261
12610
  const daemonState = await readDaemonState(pathOptions);
@@ -12511,6 +12860,7 @@ function createCliRuntime(dependencies = {}) {
12511
12860
  aiderNotify,
12512
12861
  attach,
12513
12862
  install,
12863
+ logger: logger2,
12514
12864
  mock,
12515
12865
  runDaemonProcess,
12516
12866
  selfUpdateRun,
@@ -12723,6 +13073,8 @@ Examples:
12723
13073
  aisnitch status
12724
13074
  aisnitch attach
12725
13075
  aisnitch attach --view full-data
13076
+ aisnitch logger
13077
+ aisnitch logger --tool claude-code
12726
13078
  aisnitch setup claude-code
12727
13079
  aisnitch setup aider
12728
13080
  aisnitch setup gemini-cli
@@ -12741,6 +13093,7 @@ Examples:
12741
13093
  addAdaptersCommand(program, runtime);
12742
13094
  addSetupCommand(program, runtime);
12743
13095
  addAttachCommand(program, runtime);
13096
+ addLoggerCommand(program, runtime);
12744
13097
  addMockCommand(program, runtime);
12745
13098
  addWrapCommand(program, runtime);
12746
13099
  addInstallCommand(program, runtime);
@@ -12881,6 +13234,21 @@ function addWrapCommand(program, runtime) {
12881
13234
  }
12882
13235
  );
12883
13236
  }
13237
+ function addLoggerCommand(program, runtime) {
13238
+ addCommonOptions(
13239
+ program.command("logger").description("Stream exhaustive live event logs without the TUI").option(
13240
+ "--tool <tool>",
13241
+ "Filter the live logger by tool",
13242
+ wrapOptionParser(parseToolFilterOption)
13243
+ ).option(
13244
+ "--type <type>",
13245
+ "Filter the live logger by event type",
13246
+ wrapOptionParser(parseEventTypeFilterOption)
13247
+ )
13248
+ ).action(async (options) => {
13249
+ await runtime.logger(options);
13250
+ });
13251
+ }
12884
13252
  function addInstallCommand(program, runtime) {
12885
13253
  addCommonOptions(
12886
13254
  program.command("install").description("Install a macOS LaunchAgent for AISnitch")