aisnitch 0.2.1 → 0.2.3

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
@@ -8,7 +8,7 @@ import { Command, InvalidArgumentError } from "commander";
8
8
 
9
9
  // src/package-info.ts
10
10
  var AISNITCH_PACKAGE_NAME = "aisnitch";
11
- var AISNITCH_VERSION = "0.2.1";
11
+ var AISNITCH_VERSION = "0.2.3";
12
12
  var AISNITCH_DESCRIPTION = "Universal bridge for AI coding tool activity \u2014 capture, normalize, stream.";
13
13
 
14
14
  // src/core/events/schema.ts
@@ -11748,6 +11748,179 @@ function toConfigArgv(options) {
11748
11748
  return options.configPath ? ["--config", options.configPath] : [];
11749
11749
  }
11750
11750
 
11751
+ // src/cli/live-logger.ts
11752
+ import { once as once5 } from "events";
11753
+ import WebSocket5 from "ws";
11754
+ function formatLoggerEventBlock(event) {
11755
+ const headerSegments = [
11756
+ colorize(`#${event["aisnitch.seqnum"]}`, EVENT_COLORS[event.type]),
11757
+ colorize(event["aisnitch.tool"], TOOL_COLORS[event["aisnitch.tool"]]),
11758
+ colorize(event.type, EVENT_COLORS[event.type]),
11759
+ colorize(formatSessionLabelFromEvent(event), TUI_THEME.warning),
11760
+ colorize(event.time, TUI_THEME.muted)
11761
+ ];
11762
+ const flattenedLines = flattenEventRecord(event);
11763
+ return [
11764
+ `${colorize("\u256D\u2500", TUI_THEME.frame)} ${headerSegments.join(colorize(" ", TUI_THEME.muted))}`,
11765
+ ...flattenedLines.map((line) => `${colorize("\u2502", TUI_THEME.frame)} ${line}`),
11766
+ 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)
11767
+ ].join("\n");
11768
+ }
11769
+ function formatLoggerWelcomeLine(message) {
11770
+ const tools = message.tools.length > 0 ? message.tools.join(", ") : "none configured";
11771
+ return `${colorize("AISnitch logger attached", TUI_THEME.success)} ${colorize(`v${message.version}`, TUI_THEME.warning)} ${colorize(`tools=${tools}`, TUI_THEME.muted)}`;
11772
+ }
11773
+ async function attachWebSocketLogger(url, output, filters = {}) {
11774
+ const socket = new WebSocket5(url);
11775
+ socket.on("message", (data) => {
11776
+ const parsedPayload = parseSocketMessage(data);
11777
+ if (isWelcomeMessage(parsedPayload)) {
11778
+ output.stdout(`${formatLoggerWelcomeLine(parsedPayload)}
11779
+ `);
11780
+ return;
11781
+ }
11782
+ const parsedEvent = AISnitchEventSchema.safeParse(parsedPayload);
11783
+ if (!parsedEvent.success) {
11784
+ output.stderr(
11785
+ `${colorize("logger:", TUI_THEME.danger)} received an unrecognized event payload.
11786
+ `
11787
+ );
11788
+ return;
11789
+ }
11790
+ if (!matchesFilters(parsedEvent.data, filters)) {
11791
+ return;
11792
+ }
11793
+ output.stdout(`${formatLoggerEventBlock(parsedEvent.data)}
11794
+ `);
11795
+ });
11796
+ socket.on("error", (error) => {
11797
+ output.stderr(
11798
+ `${colorize("logger:", TUI_THEME.danger)} ${error instanceof Error ? error.message : "unknown socket error"}
11799
+ `
11800
+ );
11801
+ });
11802
+ await once5(socket, "open");
11803
+ return async () => {
11804
+ if (socket.readyState === WebSocket5.CLOSING || socket.readyState === WebSocket5.CLOSED) {
11805
+ return;
11806
+ }
11807
+ socket.close();
11808
+ await once5(socket, "close");
11809
+ };
11810
+ }
11811
+ function matchesFilters(event, filters) {
11812
+ if (filters.tool && event["aisnitch.tool"] !== filters.tool) {
11813
+ return false;
11814
+ }
11815
+ if (filters.type && event.type !== filters.type) {
11816
+ return false;
11817
+ }
11818
+ return true;
11819
+ }
11820
+ function flattenEventRecord(event) {
11821
+ const lines = [];
11822
+ flattenValue(lines, "", event);
11823
+ return lines;
11824
+ }
11825
+ function flattenValue(lines, currentPath, value) {
11826
+ if (Array.isArray(value)) {
11827
+ if (value.length === 0) {
11828
+ lines.push(formatLoggerField(currentPath, "[]", "empty"));
11829
+ return;
11830
+ }
11831
+ value.forEach((entry, index) => {
11832
+ flattenValue(lines, `${currentPath}[${index}]`, entry);
11833
+ });
11834
+ return;
11835
+ }
11836
+ if (isRecord10(value)) {
11837
+ const entries = Object.entries(value).sort(
11838
+ ([left], [right]) => left.localeCompare(right)
11839
+ );
11840
+ if (entries.length === 0) {
11841
+ lines.push(formatLoggerField(currentPath, "{}", "empty"));
11842
+ return;
11843
+ }
11844
+ for (const [key, entry] of entries) {
11845
+ const nextPath = currentPath.length === 0 ? key : `${currentPath}.${key}`;
11846
+ flattenValue(lines, nextPath, entry);
11847
+ }
11848
+ return;
11849
+ }
11850
+ lines.push(formatLoggerField(currentPath, value, inferValueKind(value)));
11851
+ }
11852
+ function formatLoggerField(path, value, kind) {
11853
+ const renderedValue = typeof value === "string" ? JSON.stringify(value) : value === null ? "null" : typeof value === "undefined" ? "undefined" : JSON.stringify(value);
11854
+ return `${colorize(path, TUI_THEME.warning)} ${colorize("=", TUI_THEME.muted)} ${colorize(
11855
+ renderedValue,
11856
+ getValueColor(kind)
11857
+ )}`;
11858
+ }
11859
+ function inferValueKind(value) {
11860
+ if (value === null) {
11861
+ return "null";
11862
+ }
11863
+ switch (typeof value) {
11864
+ case "string":
11865
+ return "string";
11866
+ case "number":
11867
+ return "number";
11868
+ case "boolean":
11869
+ return "boolean";
11870
+ default:
11871
+ return "other";
11872
+ }
11873
+ }
11874
+ function getValueColor(kind) {
11875
+ switch (kind) {
11876
+ case "string":
11877
+ return TUI_THEME.panelBody;
11878
+ case "number":
11879
+ return TUI_THEME.success;
11880
+ case "boolean":
11881
+ return TUI_THEME.warning;
11882
+ case "null":
11883
+ return TUI_THEME.danger;
11884
+ case "empty":
11885
+ return TUI_THEME.muted;
11886
+ default:
11887
+ return TUI_THEME.panelBody;
11888
+ }
11889
+ }
11890
+ function colorize(value, color) {
11891
+ const [red, green, blue] = hexToRgb(color);
11892
+ return `\x1B[38;2;${red};${green};${blue}m${value}\x1B[39m`;
11893
+ }
11894
+ function hexToRgb(hexColor) {
11895
+ const sanitized = hexColor.slice(1);
11896
+ return [
11897
+ Number.parseInt(sanitized.slice(0, 2), 16),
11898
+ Number.parseInt(sanitized.slice(2, 4), 16),
11899
+ Number.parseInt(sanitized.slice(4, 6), 16)
11900
+ ];
11901
+ }
11902
+ function parseSocketMessage(data) {
11903
+ if (typeof data === "string") {
11904
+ return JSON.parse(data);
11905
+ }
11906
+ if (Array.isArray(data)) {
11907
+ return JSON.parse(Buffer.concat(data).toString("utf8"));
11908
+ }
11909
+ if (data instanceof ArrayBuffer) {
11910
+ return JSON.parse(Buffer.from(new Uint8Array(data)).toString("utf8"));
11911
+ }
11912
+ return JSON.parse(Buffer.from(data).toString("utf8"));
11913
+ }
11914
+ function isWelcomeMessage(payload) {
11915
+ if (!isRecord10(payload)) {
11916
+ return false;
11917
+ }
11918
+ return payload.type === "welcome" && typeof payload.version === "string" && Array.isArray(payload.tools);
11919
+ }
11920
+ function isRecord10(value) {
11921
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11922
+ }
11923
+
11751
11924
  // src/cli/runtime.ts
11752
11925
  var execFile11 = promisify11(execFileCallback11);
11753
11926
  var DAEMON_READY_TIMEOUT_MS = 4e3;
@@ -11946,7 +12119,22 @@ function createCliRuntime(dependencies = {}) {
11946
12119
  if (!lastLine) {
11947
12120
  return null;
11948
12121
  }
11949
- return lastLine.startsWith("AISnitch CLI failed:") ? lastLine : `AISnitch daemon startup failed: ${lastLine}`;
12122
+ if (lastLine.startsWith("AISnitch CLI failed:")) {
12123
+ return lastLine;
12124
+ }
12125
+ const structuredFailure = parseStructuredDaemonFailure(lastLine);
12126
+ return structuredFailure === null ? null : `AISnitch daemon startup failed: ${structuredFailure}`;
12127
+ } catch {
12128
+ return null;
12129
+ }
12130
+ }
12131
+ function parseStructuredDaemonFailure(logLine) {
12132
+ try {
12133
+ const parsedLog = JSON.parse(logLine);
12134
+ if (typeof parsedLog.level !== "number" || parsedLog.level < 50) {
12135
+ return null;
12136
+ }
12137
+ return typeof parsedLog.msg === "string" && parsedLog.msg.length > 0 ? parsedLog.msg : logLine;
11950
12138
  } catch {
11951
12139
  return null;
11952
12140
  }
@@ -12210,6 +12398,43 @@ function createCliRuntime(dependencies = {}) {
12210
12398
  }
12211
12399
  });
12212
12400
  }
12401
+ async function logger2(options) {
12402
+ const snapshot = await getStatusSnapshot(options);
12403
+ if (!snapshot.running) {
12404
+ throw new Error(
12405
+ "AISnitch logger requires a running daemon. Start one with `aisnitch start --daemon` or use `aisnitch start` first."
12406
+ );
12407
+ }
12408
+ const closeLogger = await attachWebSocketLogger(
12409
+ `ws://127.0.0.1:${snapshot.wsPort}`,
12410
+ output,
12411
+ {
12412
+ tool: options.tool,
12413
+ type: options.type
12414
+ }
12415
+ );
12416
+ await new Promise((resolve2) => {
12417
+ let closed = false;
12418
+ const shutdown = async () => {
12419
+ if (closed) {
12420
+ return;
12421
+ }
12422
+ closed = true;
12423
+ process.off("SIGINT", handleSigint);
12424
+ process.off("SIGTERM", handleSigterm);
12425
+ await Promise.resolve(closeLogger());
12426
+ resolve2();
12427
+ };
12428
+ const handleSigint = () => {
12429
+ void shutdown();
12430
+ };
12431
+ const handleSigterm = () => {
12432
+ void shutdown();
12433
+ };
12434
+ process.once("SIGINT", handleSigint);
12435
+ process.once("SIGTERM", handleSigterm);
12436
+ });
12437
+ }
12213
12438
  async function mock(selection, options) {
12214
12439
  const pathOptions = toPathOptions2(options);
12215
12440
  const daemonState = await readDaemonState(pathOptions);
@@ -12465,6 +12690,7 @@ function createCliRuntime(dependencies = {}) {
12465
12690
  aiderNotify,
12466
12691
  attach,
12467
12692
  install,
12693
+ logger: logger2,
12468
12694
  mock,
12469
12695
  runDaemonProcess,
12470
12696
  selfUpdateRun,
@@ -12677,6 +12903,8 @@ Examples:
12677
12903
  aisnitch status
12678
12904
  aisnitch attach
12679
12905
  aisnitch attach --view full-data
12906
+ aisnitch logger
12907
+ aisnitch logger --tool claude-code
12680
12908
  aisnitch setup claude-code
12681
12909
  aisnitch setup aider
12682
12910
  aisnitch setup gemini-cli
@@ -12695,6 +12923,7 @@ Examples:
12695
12923
  addAdaptersCommand(program, runtime);
12696
12924
  addSetupCommand(program, runtime);
12697
12925
  addAttachCommand(program, runtime);
12926
+ addLoggerCommand(program, runtime);
12698
12927
  addMockCommand(program, runtime);
12699
12928
  addWrapCommand(program, runtime);
12700
12929
  addInstallCommand(program, runtime);
@@ -12835,6 +13064,21 @@ function addWrapCommand(program, runtime) {
12835
13064
  }
12836
13065
  );
12837
13066
  }
13067
+ function addLoggerCommand(program, runtime) {
13068
+ addCommonOptions(
13069
+ program.command("logger").description("Stream exhaustive live event logs without the TUI").option(
13070
+ "--tool <tool>",
13071
+ "Filter the live logger by tool",
13072
+ wrapOptionParser(parseToolFilterOption)
13073
+ ).option(
13074
+ "--type <type>",
13075
+ "Filter the live logger by event type",
13076
+ wrapOptionParser(parseEventTypeFilterOption)
13077
+ )
13078
+ ).action(async (options) => {
13079
+ await runtime.logger(options);
13080
+ });
13081
+ }
12838
13082
  function addInstallCommand(program, runtime) {
12839
13083
  addCommonOptions(
12840
13084
  program.command("install").description("Install a macOS LaunchAgent for AISnitch")