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/README.md CHANGED
@@ -51,12 +51,17 @@ node dist/cli/index.js start
51
51
 
52
52
  # Launch with simulated events (no real AI tool needed)
53
53
  node dist/cli/index.js start --mock all
54
+
55
+ # Exhaustive live logger, no TUI
56
+ node dist/cli/index.js logger
54
57
  ```
55
58
 
56
59
  `start` now always opens the TUI dashboard. If the daemon is offline you still land in the UI, see `Daemon not active`, and can start or stop it from inside the TUI with `d`.
57
60
 
58
61
  `start --mock all` boots the dashboard, ensures the daemon is active, then streams realistic fake events from Claude Code, OpenCode, and Gemini CLI so you can see the product immediately.
59
62
 
63
+ If you want the full live payload stream without Ink truncation, use `aisnitch logger`. It attaches to the running daemon and prints one structured field per line, including nested `data.raw.*` paths.
64
+
60
65
  To consume the stream from another terminal:
61
66
 
62
67
  ```bash
@@ -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.1";
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
@@ -11779,6 +11779,179 @@ function toConfigArgv(options) {
11779
11779
  return options.configPath ? ["--config", options.configPath] : [];
11780
11780
  }
11781
11781
 
11782
+ // src/cli/live-logger.ts
11783
+ var import_node_events5 = require("events");
11784
+ var import_ws5 = __toESM(require("ws"), 1);
11785
+ function formatLoggerEventBlock(event) {
11786
+ const headerSegments = [
11787
+ colorize(`#${event["aisnitch.seqnum"]}`, EVENT_COLORS[event.type]),
11788
+ colorize(event["aisnitch.tool"], TOOL_COLORS[event["aisnitch.tool"]]),
11789
+ colorize(event.type, EVENT_COLORS[event.type]),
11790
+ colorize(formatSessionLabelFromEvent(event), TUI_THEME.warning),
11791
+ colorize(event.time, TUI_THEME.muted)
11792
+ ];
11793
+ const flattenedLines = flattenEventRecord(event);
11794
+ return [
11795
+ `${colorize("\u256D\u2500", TUI_THEME.frame)} ${headerSegments.join(colorize(" ", TUI_THEME.muted))}`,
11796
+ ...flattenedLines.map((line) => `${colorize("\u2502", TUI_THEME.frame)} ${line}`),
11797
+ 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)
11798
+ ].join("\n");
11799
+ }
11800
+ function formatLoggerWelcomeLine(message) {
11801
+ const tools = message.tools.length > 0 ? message.tools.join(", ") : "none configured";
11802
+ return `${colorize("AISnitch logger attached", TUI_THEME.success)} ${colorize(`v${message.version}`, TUI_THEME.warning)} ${colorize(`tools=${tools}`, TUI_THEME.muted)}`;
11803
+ }
11804
+ async function attachWebSocketLogger(url, output, filters = {}) {
11805
+ const socket = new import_ws5.default(url);
11806
+ socket.on("message", (data) => {
11807
+ const parsedPayload = parseSocketMessage(data);
11808
+ if (isWelcomeMessage(parsedPayload)) {
11809
+ output.stdout(`${formatLoggerWelcomeLine(parsedPayload)}
11810
+ `);
11811
+ return;
11812
+ }
11813
+ const parsedEvent = AISnitchEventSchema.safeParse(parsedPayload);
11814
+ if (!parsedEvent.success) {
11815
+ output.stderr(
11816
+ `${colorize("logger:", TUI_THEME.danger)} received an unrecognized event payload.
11817
+ `
11818
+ );
11819
+ return;
11820
+ }
11821
+ if (!matchesFilters(parsedEvent.data, filters)) {
11822
+ return;
11823
+ }
11824
+ output.stdout(`${formatLoggerEventBlock(parsedEvent.data)}
11825
+ `);
11826
+ });
11827
+ socket.on("error", (error) => {
11828
+ output.stderr(
11829
+ `${colorize("logger:", TUI_THEME.danger)} ${error instanceof Error ? error.message : "unknown socket error"}
11830
+ `
11831
+ );
11832
+ });
11833
+ await (0, import_node_events5.once)(socket, "open");
11834
+ return async () => {
11835
+ if (socket.readyState === import_ws5.default.CLOSING || socket.readyState === import_ws5.default.CLOSED) {
11836
+ return;
11837
+ }
11838
+ socket.close();
11839
+ await (0, import_node_events5.once)(socket, "close");
11840
+ };
11841
+ }
11842
+ function matchesFilters(event, filters) {
11843
+ if (filters.tool && event["aisnitch.tool"] !== filters.tool) {
11844
+ return false;
11845
+ }
11846
+ if (filters.type && event.type !== filters.type) {
11847
+ return false;
11848
+ }
11849
+ return true;
11850
+ }
11851
+ function flattenEventRecord(event) {
11852
+ const lines = [];
11853
+ flattenValue(lines, "", event);
11854
+ return lines;
11855
+ }
11856
+ function flattenValue(lines, currentPath, value) {
11857
+ if (Array.isArray(value)) {
11858
+ if (value.length === 0) {
11859
+ lines.push(formatLoggerField(currentPath, "[]", "empty"));
11860
+ return;
11861
+ }
11862
+ value.forEach((entry, index) => {
11863
+ flattenValue(lines, `${currentPath}[${index}]`, entry);
11864
+ });
11865
+ return;
11866
+ }
11867
+ if (isRecord10(value)) {
11868
+ const entries = Object.entries(value).sort(
11869
+ ([left], [right]) => left.localeCompare(right)
11870
+ );
11871
+ if (entries.length === 0) {
11872
+ lines.push(formatLoggerField(currentPath, "{}", "empty"));
11873
+ return;
11874
+ }
11875
+ for (const [key, entry] of entries) {
11876
+ const nextPath = currentPath.length === 0 ? key : `${currentPath}.${key}`;
11877
+ flattenValue(lines, nextPath, entry);
11878
+ }
11879
+ return;
11880
+ }
11881
+ lines.push(formatLoggerField(currentPath, value, inferValueKind(value)));
11882
+ }
11883
+ function formatLoggerField(path, value, kind) {
11884
+ const renderedValue = typeof value === "string" ? JSON.stringify(value) : value === null ? "null" : typeof value === "undefined" ? "undefined" : JSON.stringify(value);
11885
+ return `${colorize(path, TUI_THEME.warning)} ${colorize("=", TUI_THEME.muted)} ${colorize(
11886
+ renderedValue,
11887
+ getValueColor(kind)
11888
+ )}`;
11889
+ }
11890
+ function inferValueKind(value) {
11891
+ if (value === null) {
11892
+ return "null";
11893
+ }
11894
+ switch (typeof value) {
11895
+ case "string":
11896
+ return "string";
11897
+ case "number":
11898
+ return "number";
11899
+ case "boolean":
11900
+ return "boolean";
11901
+ default:
11902
+ return "other";
11903
+ }
11904
+ }
11905
+ function getValueColor(kind) {
11906
+ switch (kind) {
11907
+ case "string":
11908
+ return TUI_THEME.panelBody;
11909
+ case "number":
11910
+ return TUI_THEME.success;
11911
+ case "boolean":
11912
+ return TUI_THEME.warning;
11913
+ case "null":
11914
+ return TUI_THEME.danger;
11915
+ case "empty":
11916
+ return TUI_THEME.muted;
11917
+ default:
11918
+ return TUI_THEME.panelBody;
11919
+ }
11920
+ }
11921
+ function colorize(value, color) {
11922
+ const [red, green, blue] = hexToRgb(color);
11923
+ return `\x1B[38;2;${red};${green};${blue}m${value}\x1B[39m`;
11924
+ }
11925
+ function hexToRgb(hexColor) {
11926
+ const sanitized = hexColor.slice(1);
11927
+ return [
11928
+ Number.parseInt(sanitized.slice(0, 2), 16),
11929
+ Number.parseInt(sanitized.slice(2, 4), 16),
11930
+ Number.parseInt(sanitized.slice(4, 6), 16)
11931
+ ];
11932
+ }
11933
+ function parseSocketMessage(data) {
11934
+ if (typeof data === "string") {
11935
+ return JSON.parse(data);
11936
+ }
11937
+ if (Array.isArray(data)) {
11938
+ return JSON.parse(Buffer.concat(data).toString("utf8"));
11939
+ }
11940
+ if (data instanceof ArrayBuffer) {
11941
+ return JSON.parse(Buffer.from(new Uint8Array(data)).toString("utf8"));
11942
+ }
11943
+ return JSON.parse(Buffer.from(data).toString("utf8"));
11944
+ }
11945
+ function isWelcomeMessage(payload) {
11946
+ if (!isRecord10(payload)) {
11947
+ return false;
11948
+ }
11949
+ return payload.type === "welcome" && typeof payload.version === "string" && Array.isArray(payload.tools);
11950
+ }
11951
+ function isRecord10(value) {
11952
+ return typeof value === "object" && value !== null && !Array.isArray(value);
11953
+ }
11954
+
11782
11955
  // src/cli/runtime.ts
11783
11956
  var execFile11 = (0, import_node_util11.promisify)(import_node_child_process12.execFile);
11784
11957
  var DAEMON_READY_TIMEOUT_MS = 4e3;
@@ -11977,7 +12150,22 @@ function createCliRuntime(dependencies = {}) {
11977
12150
  if (!lastLine) {
11978
12151
  return null;
11979
12152
  }
11980
- return lastLine.startsWith("AISnitch CLI failed:") ? lastLine : `AISnitch daemon startup failed: ${lastLine}`;
12153
+ if (lastLine.startsWith("AISnitch CLI failed:")) {
12154
+ return lastLine;
12155
+ }
12156
+ const structuredFailure = parseStructuredDaemonFailure(lastLine);
12157
+ return structuredFailure === null ? null : `AISnitch daemon startup failed: ${structuredFailure}`;
12158
+ } catch {
12159
+ return null;
12160
+ }
12161
+ }
12162
+ function parseStructuredDaemonFailure(logLine) {
12163
+ try {
12164
+ const parsedLog = JSON.parse(logLine);
12165
+ if (typeof parsedLog.level !== "number" || parsedLog.level < 50) {
12166
+ return null;
12167
+ }
12168
+ return typeof parsedLog.msg === "string" && parsedLog.msg.length > 0 ? parsedLog.msg : logLine;
11981
12169
  } catch {
11982
12170
  return null;
11983
12171
  }
@@ -12241,6 +12429,43 @@ function createCliRuntime(dependencies = {}) {
12241
12429
  }
12242
12430
  });
12243
12431
  }
12432
+ async function logger2(options) {
12433
+ const snapshot = await getStatusSnapshot(options);
12434
+ if (!snapshot.running) {
12435
+ throw new Error(
12436
+ "AISnitch logger requires a running daemon. Start one with `aisnitch start --daemon` or use `aisnitch start` first."
12437
+ );
12438
+ }
12439
+ const closeLogger = await attachWebSocketLogger(
12440
+ `ws://127.0.0.1:${snapshot.wsPort}`,
12441
+ output,
12442
+ {
12443
+ tool: options.tool,
12444
+ type: options.type
12445
+ }
12446
+ );
12447
+ await new Promise((resolve2) => {
12448
+ let closed = false;
12449
+ const shutdown = async () => {
12450
+ if (closed) {
12451
+ return;
12452
+ }
12453
+ closed = true;
12454
+ process.off("SIGINT", handleSigint);
12455
+ process.off("SIGTERM", handleSigterm);
12456
+ await Promise.resolve(closeLogger());
12457
+ resolve2();
12458
+ };
12459
+ const handleSigint = () => {
12460
+ void shutdown();
12461
+ };
12462
+ const handleSigterm = () => {
12463
+ void shutdown();
12464
+ };
12465
+ process.once("SIGINT", handleSigint);
12466
+ process.once("SIGTERM", handleSigterm);
12467
+ });
12468
+ }
12244
12469
  async function mock(selection, options) {
12245
12470
  const pathOptions = toPathOptions2(options);
12246
12471
  const daemonState = await readDaemonState(pathOptions);
@@ -12496,6 +12721,7 @@ function createCliRuntime(dependencies = {}) {
12496
12721
  aiderNotify,
12497
12722
  attach,
12498
12723
  install,
12724
+ logger: logger2,
12499
12725
  mock,
12500
12726
  runDaemonProcess,
12501
12727
  selfUpdateRun,
@@ -12708,6 +12934,8 @@ Examples:
12708
12934
  aisnitch status
12709
12935
  aisnitch attach
12710
12936
  aisnitch attach --view full-data
12937
+ aisnitch logger
12938
+ aisnitch logger --tool claude-code
12711
12939
  aisnitch setup claude-code
12712
12940
  aisnitch setup aider
12713
12941
  aisnitch setup gemini-cli
@@ -12726,6 +12954,7 @@ Examples:
12726
12954
  addAdaptersCommand(program, runtime);
12727
12955
  addSetupCommand(program, runtime);
12728
12956
  addAttachCommand(program, runtime);
12957
+ addLoggerCommand(program, runtime);
12729
12958
  addMockCommand(program, runtime);
12730
12959
  addWrapCommand(program, runtime);
12731
12960
  addInstallCommand(program, runtime);
@@ -12866,6 +13095,21 @@ function addWrapCommand(program, runtime) {
12866
13095
  }
12867
13096
  );
12868
13097
  }
13098
+ function addLoggerCommand(program, runtime) {
13099
+ addCommonOptions(
13100
+ program.command("logger").description("Stream exhaustive live event logs without the TUI").option(
13101
+ "--tool <tool>",
13102
+ "Filter the live logger by tool",
13103
+ wrapOptionParser(parseToolFilterOption)
13104
+ ).option(
13105
+ "--type <type>",
13106
+ "Filter the live logger by event type",
13107
+ wrapOptionParser(parseEventTypeFilterOption)
13108
+ )
13109
+ ).action(async (options) => {
13110
+ await runtime.logger(options);
13111
+ });
13112
+ }
12869
13113
  function addInstallCommand(program, runtime) {
12870
13114
  addCommonOptions(
12871
13115
  program.command("install").description("Install a macOS LaunchAgent for AISnitch")