aisnitch 0.2.5 → 0.2.13

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.3";
11
+ var AISNITCH_VERSION = "0.2.12";
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
@@ -723,7 +723,6 @@ var ANSI_RESET = "\x1B[0m";
723
723
  var ANSI_RED = "\x1B[31m";
724
724
  var ANSI_GREEN = "\x1B[32m";
725
725
  var ANSI_CYAN = "\x1B[36m";
726
- var CLAUDE_FILE_CHANGED_MATCHER = ".*";
727
726
  var CLAUDE_CODE_HOOK_EVENTS = [
728
727
  "SessionStart",
729
728
  "UserPromptSubmit",
@@ -734,15 +733,12 @@ var CLAUDE_CODE_HOOK_EVENTS = [
734
733
  "Notification",
735
734
  "SubagentStart",
736
735
  "SubagentStop",
737
- "TaskCreated",
738
736
  "TaskCompleted",
739
737
  "Stop",
740
738
  "StopFailure",
741
739
  "TeammateIdle",
742
740
  "InstructionsLoaded",
743
741
  "ConfigChange",
744
- "CwdChanged",
745
- "FileChanged",
746
742
  "WorktreeCreate",
747
743
  "WorktreeRemove",
748
744
  "PreCompact",
@@ -843,9 +839,15 @@ var FileToolSetupBase = class {
843
839
  var ClaudeCodeSetup = class extends FileToolSetupBase {
844
840
  settingsPath;
845
841
  hookUrl;
842
+ scriptPath;
846
843
  constructor(httpPort, dependencies = {}) {
847
844
  super("claude-code", dependencies.binaryExists);
848
845
  this.settingsPath = dependencies.claudeSettingsPath ?? join2(dependencies.homeDirectory ?? homedir2(), ".claude", "settings.json");
846
+ this.scriptPath = join2(
847
+ dependencies.homeDirectory ?? homedir2(),
848
+ ".claude",
849
+ "aisnitch-forward.mjs"
850
+ );
849
851
  this.hookUrl = `http://localhost:${httpPort}/hooks/claude-code`;
850
852
  }
851
853
  async detect() {
@@ -854,7 +856,49 @@ var ClaudeCodeSetup = class extends FileToolSetupBase {
854
856
  getConfigPath() {
855
857
  return this.settingsPath;
856
858
  }
859
+ async computeDiff() {
860
+ const currentSettingsContent = await readOptionalFile(this.settingsPath);
861
+ const currentScriptContent = await readOptionalFile(this.scriptPath);
862
+ const nextSettingsContent = this.buildNextSettingsContent(currentSettingsContent);
863
+ const nextScriptContent = buildClaudeForwardScriptSource();
864
+ return [
865
+ renderColoredDiff(
866
+ this.settingsPath,
867
+ currentSettingsContent,
868
+ nextSettingsContent
869
+ ),
870
+ "",
871
+ renderColoredDiff(
872
+ this.scriptPath,
873
+ currentScriptContent,
874
+ nextScriptContent
875
+ )
876
+ ].join("\n");
877
+ }
878
+ async apply() {
879
+ const currentSettingsContent = await readOptionalFile(this.settingsPath);
880
+ const currentScriptContent = await readOptionalFile(this.scriptPath);
881
+ const nextSettingsContent = this.buildNextSettingsContent(currentSettingsContent);
882
+ const nextScriptContent = buildClaudeForwardScriptSource();
883
+ await mkdir2(dirname2(this.settingsPath), { recursive: true });
884
+ await mkdir2(dirname2(this.scriptPath), { recursive: true });
885
+ if (currentSettingsContent !== null) {
886
+ await copyFile(this.settingsPath, this.getFileBackupPath(this.settingsPath));
887
+ }
888
+ if (currentScriptContent !== null) {
889
+ await copyFile(this.scriptPath, this.getFileBackupPath(this.scriptPath));
890
+ }
891
+ await writeFile2(this.settingsPath, nextSettingsContent, "utf8");
892
+ await writeFile2(this.scriptPath, nextScriptContent, "utf8");
893
+ }
894
+ async revert() {
895
+ await restoreBackupOrRemove(this.settingsPath);
896
+ await restoreBackupOrRemove(this.scriptPath);
897
+ }
857
898
  buildNextContent(currentContent) {
899
+ return Promise.resolve(this.buildNextSettingsContent(currentContent));
900
+ }
901
+ buildNextSettingsContent(currentContent) {
858
902
  const parsedSettings = parseClaudeSettings(currentContent);
859
903
  const currentHooks = parsedSettings.hooks ?? {};
860
904
  const nextHooks = {
@@ -864,15 +908,19 @@ var ClaudeCodeSetup = class extends FileToolSetupBase {
864
908
  nextHooks[hookEventName] = ensureClaudeAISnitchHook(
865
909
  currentHooks[hookEventName] ?? [],
866
910
  hookEventName,
867
- this.hookUrl
911
+ this.hookUrl,
912
+ this.scriptPath
868
913
  );
869
914
  }
870
915
  const nextSettings = {
871
916
  ...parsedSettings,
872
917
  hooks: nextHooks
873
918
  };
874
- return Promise.resolve(`${JSON.stringify(nextSettings, null, 2)}
875
- `);
919
+ return `${JSON.stringify(nextSettings, null, 2)}
920
+ `;
921
+ }
922
+ getFileBackupPath(path) {
923
+ return `${path}.bak`;
876
924
  }
877
925
  };
878
926
  var OpenCodeSetup = class extends FileToolSetupBase {
@@ -1676,43 +1724,116 @@ async function defaultConfirm(_diff, prompt) {
1676
1724
  readlineInterface.close();
1677
1725
  }
1678
1726
  }
1679
- function ensureClaudeAISnitchHook(groups, hookEventName, hookUrl) {
1727
+ function ensureClaudeAISnitchHook(groups, hookEventName, hookUrl, scriptPath) {
1680
1728
  const clonedGroups = groups.map((group) => ({
1681
1729
  ...group,
1682
1730
  hooks: group.hooks.map((handler) => ({ ...handler }))
1683
1731
  }));
1684
- const matcher = hookEventName === "FileChanged" ? CLAUDE_FILE_CHANGED_MATCHER : void 0;
1685
- const matchingGroup = clonedGroups.find((group) => group.matcher === matcher);
1732
+ const matchingGroup = clonedGroups.find((group) => group.matcher === void 0);
1686
1733
  if (matchingGroup) {
1687
- const existingHook = matchingGroup.hooks.find((handler) => isClaudeAISnitchHook(handler, hookUrl));
1734
+ matchingGroup.hooks = matchingGroup.hooks.filter(
1735
+ (handler) => !isLegacyClaudeAISnitchHttpHook(handler, hookUrl)
1736
+ );
1737
+ const existingHook = matchingGroup.hooks.find(
1738
+ (handler) => isClaudeAISnitchHook(handler, hookEventName, hookUrl, scriptPath)
1739
+ );
1688
1740
  if (existingHook) {
1689
1741
  if (existingHook.async !== true) {
1690
1742
  existingHook.async = true;
1691
1743
  }
1692
1744
  return clonedGroups;
1693
1745
  }
1694
- matchingGroup.hooks.push(createClaudeAISnitchHook(hookUrl));
1746
+ matchingGroup.hooks.push(
1747
+ createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath)
1748
+ );
1695
1749
  return clonedGroups;
1696
1750
  }
1697
- const nextGroup = matcher === void 0 ? {
1698
- hooks: [createClaudeAISnitchHook(hookUrl)]
1699
- } : {
1700
- matcher,
1701
- hooks: [createClaudeAISnitchHook(hookUrl)]
1702
- };
1703
- clonedGroups.push(nextGroup);
1751
+ clonedGroups.push({
1752
+ hooks: [createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath)]
1753
+ });
1704
1754
  return clonedGroups;
1705
1755
  }
1706
- function createClaudeAISnitchHook(hookUrl) {
1756
+ function createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath) {
1707
1757
  return {
1708
1758
  async: true,
1709
- type: "http",
1710
- url: hookUrl
1759
+ command: buildClaudeForwardCommand(hookEventName, hookUrl, scriptPath),
1760
+ type: "command"
1711
1761
  };
1712
1762
  }
1713
- function isClaudeAISnitchHook(handler, hookUrl) {
1763
+ function isClaudeAISnitchHook(handler, hookEventName, hookUrl, scriptPath) {
1764
+ return handler.type === "command" && typeof handler.command === "string" && handler.command === buildClaudeForwardCommand(hookEventName, hookUrl, scriptPath);
1765
+ }
1766
+ function isLegacyClaudeAISnitchHttpHook(handler, hookUrl) {
1714
1767
  return handler.type === "http" && handler.url === hookUrl;
1715
1768
  }
1769
+ function buildClaudeForwardCommand(hookEventName, hookUrl, scriptPath) {
1770
+ return `node ${shellEscapeSingle(scriptPath)} ${hookEventName} ${shellEscapeSingle(hookUrl)}`;
1771
+ }
1772
+ function buildClaudeForwardScriptSource() {
1773
+ return `#!/usr/bin/env node
1774
+ /**
1775
+ * AISnitch Claude Code hook bridge
1776
+ *
1777
+ * \u{1F4D6} Claude Code currently exposes command hooks fed through stdin JSON.
1778
+ * This bridge keeps the Claude config valid while still forwarding every
1779
+ * selected hook event into the local AISnitch HTTP receiver.
1780
+ */
1781
+
1782
+ function isRecord(value) {
1783
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1784
+ }
1785
+
1786
+ async function readInput() {
1787
+ const chunks = [];
1788
+
1789
+ for await (const chunk of process.stdin) {
1790
+ chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
1791
+ }
1792
+
1793
+ return chunks.join("");
1794
+ }
1795
+
1796
+ async function main() {
1797
+ const hookEventName = process.argv[2] ?? "unknown";
1798
+ const endpoint = process.argv[3] ?? "http://localhost:4821/hooks/claude-code";
1799
+ const rawInput = await readInput();
1800
+ let payload = {};
1801
+
1802
+ if (rawInput.trim().length > 0) {
1803
+ try {
1804
+ const parsedPayload = JSON.parse(rawInput);
1805
+
1806
+ if (isRecord(parsedPayload)) {
1807
+ payload = parsedPayload;
1808
+ }
1809
+ } catch {
1810
+ payload = {
1811
+ raw: rawInput
1812
+ };
1813
+ }
1814
+ }
1815
+
1816
+ try {
1817
+ await fetch(endpoint, {
1818
+ method: "POST",
1819
+ headers: {
1820
+ "content-type": "application/json"
1821
+ },
1822
+ body: JSON.stringify({
1823
+ ...payload,
1824
+ hook_event_name: hookEventName
1825
+ })
1826
+ });
1827
+ } catch {
1828
+ // Claude hooks must stay fire-and-forget for observability only.
1829
+ }
1830
+ }
1831
+
1832
+ void main().catch(() => {
1833
+ // Never bubble hook bridge failures back into Claude Code itself.
1834
+ });
1835
+ `;
1836
+ }
1716
1837
  function ensureGeminiAISnitchHook(groups, hookUrl) {
1717
1838
  const clonedGroups = groups.map((group) => ({
1718
1839
  ...group,
@@ -2227,6 +2348,9 @@ async function readOptionalFile(path) {
2227
2348
  throw error;
2228
2349
  }
2229
2350
  }
2351
+ function shellEscapeSingle(value) {
2352
+ return `'${value.replaceAll("'", `'"'"'`)}'`;
2353
+ }
2230
2354
  async function restoreBackupOrRemove(path) {
2231
2355
  const backupPath = `${path}.bak`;
2232
2356
  if (await fileExists(backupPath)) {
@@ -9513,6 +9637,7 @@ var Pipeline = class {
9513
9637
 
9514
9638
  // src/tui/index.tsx
9515
9639
  import { render } from "ink";
9640
+ import { withFullScreen } from "fullscreen-ink";
9516
9641
  import WebSocket4 from "ws";
9517
9642
 
9518
9643
  // src/tui/App.tsx
@@ -11421,7 +11546,7 @@ function attachEventBusMonitor(eventBus, output) {
11421
11546
  // src/tui/index.tsx
11422
11547
  import { jsx as jsx13 } from "react/jsx-runtime";
11423
11548
  async function renderForegroundTui(options) {
11424
- const app = render(
11549
+ const ink = withFullScreen(
11425
11550
  /* @__PURE__ */ jsx13(
11426
11551
  App,
11427
11552
  {
@@ -11443,7 +11568,8 @@ async function renderForegroundTui(options) {
11443
11568
  }
11444
11569
  )
11445
11570
  );
11446
- await app.waitUntilExit();
11571
+ await ink.start();
11572
+ await ink.waitUntilExit;
11447
11573
  }
11448
11574
  async function renderManagedTui(options) {
11449
11575
  const app = render(