aisnitch 0.2.2 → 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.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
@@ -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;
@@ -12256,6 +12429,43 @@ function createCliRuntime(dependencies = {}) {
12256
12429
  }
12257
12430
  });
12258
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
+ }
12259
12469
  async function mock(selection, options) {
12260
12470
  const pathOptions = toPathOptions2(options);
12261
12471
  const daemonState = await readDaemonState(pathOptions);
@@ -12511,6 +12721,7 @@ function createCliRuntime(dependencies = {}) {
12511
12721
  aiderNotify,
12512
12722
  attach,
12513
12723
  install,
12724
+ logger: logger2,
12514
12725
  mock,
12515
12726
  runDaemonProcess,
12516
12727
  selfUpdateRun,
@@ -12723,6 +12934,8 @@ Examples:
12723
12934
  aisnitch status
12724
12935
  aisnitch attach
12725
12936
  aisnitch attach --view full-data
12937
+ aisnitch logger
12938
+ aisnitch logger --tool claude-code
12726
12939
  aisnitch setup claude-code
12727
12940
  aisnitch setup aider
12728
12941
  aisnitch setup gemini-cli
@@ -12741,6 +12954,7 @@ Examples:
12741
12954
  addAdaptersCommand(program, runtime);
12742
12955
  addSetupCommand(program, runtime);
12743
12956
  addAttachCommand(program, runtime);
12957
+ addLoggerCommand(program, runtime);
12744
12958
  addMockCommand(program, runtime);
12745
12959
  addWrapCommand(program, runtime);
12746
12960
  addInstallCommand(program, runtime);
@@ -12881,6 +13095,21 @@ function addWrapCommand(program, runtime) {
12881
13095
  }
12882
13096
  );
12883
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
+ }
12884
13113
  function addInstallCommand(program, runtime) {
12885
13114
  addCommonOptions(
12886
13115
  program.command("install").description("Install a macOS LaunchAgent for AISnitch")