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/README.md +1 -1
- package/dist/cli/index.cjs +152 -26
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +152 -26
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +5 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -220,7 +220,7 @@ flowchart LR
|
|
|
220
220
|
|
|
221
221
|
| Tool | Strategy | Setup |
|
|
222
222
|
|---|---|---|
|
|
223
|
-
| **Claude Code** |
|
|
223
|
+
| **Claude Code** | Command hooks + JSONL transcript watching + process detection | `aisnitch setup claude-code` |
|
|
224
224
|
| **OpenCode** | Local plugin + process detection | `aisnitch setup opencode` |
|
|
225
225
|
| **Gemini CLI** | Command hooks + `logs.json` watching + process detection | `aisnitch setup gemini-cli` |
|
|
226
226
|
| **Codex** | `codex-tui.log` parsing + process detection | `aisnitch setup codex` |
|
package/dist/cli/index.cjs
CHANGED
|
@@ -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.
|
|
44
|
+
var AISNITCH_VERSION = "0.2.12";
|
|
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
|
|
@@ -756,7 +756,6 @@ var ANSI_RESET = "\x1B[0m";
|
|
|
756
756
|
var ANSI_RED = "\x1B[31m";
|
|
757
757
|
var ANSI_GREEN = "\x1B[32m";
|
|
758
758
|
var ANSI_CYAN = "\x1B[36m";
|
|
759
|
-
var CLAUDE_FILE_CHANGED_MATCHER = ".*";
|
|
760
759
|
var CLAUDE_CODE_HOOK_EVENTS = [
|
|
761
760
|
"SessionStart",
|
|
762
761
|
"UserPromptSubmit",
|
|
@@ -767,15 +766,12 @@ var CLAUDE_CODE_HOOK_EVENTS = [
|
|
|
767
766
|
"Notification",
|
|
768
767
|
"SubagentStart",
|
|
769
768
|
"SubagentStop",
|
|
770
|
-
"TaskCreated",
|
|
771
769
|
"TaskCompleted",
|
|
772
770
|
"Stop",
|
|
773
771
|
"StopFailure",
|
|
774
772
|
"TeammateIdle",
|
|
775
773
|
"InstructionsLoaded",
|
|
776
774
|
"ConfigChange",
|
|
777
|
-
"CwdChanged",
|
|
778
|
-
"FileChanged",
|
|
779
775
|
"WorktreeCreate",
|
|
780
776
|
"WorktreeRemove",
|
|
781
777
|
"PreCompact",
|
|
@@ -876,9 +872,15 @@ var FileToolSetupBase = class {
|
|
|
876
872
|
var ClaudeCodeSetup = class extends FileToolSetupBase {
|
|
877
873
|
settingsPath;
|
|
878
874
|
hookUrl;
|
|
875
|
+
scriptPath;
|
|
879
876
|
constructor(httpPort, dependencies = {}) {
|
|
880
877
|
super("claude-code", dependencies.binaryExists);
|
|
881
878
|
this.settingsPath = dependencies.claudeSettingsPath ?? (0, import_node_path2.join)(dependencies.homeDirectory ?? (0, import_node_os2.homedir)(), ".claude", "settings.json");
|
|
879
|
+
this.scriptPath = (0, import_node_path2.join)(
|
|
880
|
+
dependencies.homeDirectory ?? (0, import_node_os2.homedir)(),
|
|
881
|
+
".claude",
|
|
882
|
+
"aisnitch-forward.mjs"
|
|
883
|
+
);
|
|
882
884
|
this.hookUrl = `http://localhost:${httpPort}/hooks/claude-code`;
|
|
883
885
|
}
|
|
884
886
|
async detect() {
|
|
@@ -887,7 +889,49 @@ var ClaudeCodeSetup = class extends FileToolSetupBase {
|
|
|
887
889
|
getConfigPath() {
|
|
888
890
|
return this.settingsPath;
|
|
889
891
|
}
|
|
892
|
+
async computeDiff() {
|
|
893
|
+
const currentSettingsContent = await readOptionalFile(this.settingsPath);
|
|
894
|
+
const currentScriptContent = await readOptionalFile(this.scriptPath);
|
|
895
|
+
const nextSettingsContent = this.buildNextSettingsContent(currentSettingsContent);
|
|
896
|
+
const nextScriptContent = buildClaudeForwardScriptSource();
|
|
897
|
+
return [
|
|
898
|
+
renderColoredDiff(
|
|
899
|
+
this.settingsPath,
|
|
900
|
+
currentSettingsContent,
|
|
901
|
+
nextSettingsContent
|
|
902
|
+
),
|
|
903
|
+
"",
|
|
904
|
+
renderColoredDiff(
|
|
905
|
+
this.scriptPath,
|
|
906
|
+
currentScriptContent,
|
|
907
|
+
nextScriptContent
|
|
908
|
+
)
|
|
909
|
+
].join("\n");
|
|
910
|
+
}
|
|
911
|
+
async apply() {
|
|
912
|
+
const currentSettingsContent = await readOptionalFile(this.settingsPath);
|
|
913
|
+
const currentScriptContent = await readOptionalFile(this.scriptPath);
|
|
914
|
+
const nextSettingsContent = this.buildNextSettingsContent(currentSettingsContent);
|
|
915
|
+
const nextScriptContent = buildClaudeForwardScriptSource();
|
|
916
|
+
await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(this.settingsPath), { recursive: true });
|
|
917
|
+
await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(this.scriptPath), { recursive: true });
|
|
918
|
+
if (currentSettingsContent !== null) {
|
|
919
|
+
await (0, import_promises2.copyFile)(this.settingsPath, this.getFileBackupPath(this.settingsPath));
|
|
920
|
+
}
|
|
921
|
+
if (currentScriptContent !== null) {
|
|
922
|
+
await (0, import_promises2.copyFile)(this.scriptPath, this.getFileBackupPath(this.scriptPath));
|
|
923
|
+
}
|
|
924
|
+
await (0, import_promises2.writeFile)(this.settingsPath, nextSettingsContent, "utf8");
|
|
925
|
+
await (0, import_promises2.writeFile)(this.scriptPath, nextScriptContent, "utf8");
|
|
926
|
+
}
|
|
927
|
+
async revert() {
|
|
928
|
+
await restoreBackupOrRemove(this.settingsPath);
|
|
929
|
+
await restoreBackupOrRemove(this.scriptPath);
|
|
930
|
+
}
|
|
890
931
|
buildNextContent(currentContent) {
|
|
932
|
+
return Promise.resolve(this.buildNextSettingsContent(currentContent));
|
|
933
|
+
}
|
|
934
|
+
buildNextSettingsContent(currentContent) {
|
|
891
935
|
const parsedSettings = parseClaudeSettings(currentContent);
|
|
892
936
|
const currentHooks = parsedSettings.hooks ?? {};
|
|
893
937
|
const nextHooks = {
|
|
@@ -897,15 +941,19 @@ var ClaudeCodeSetup = class extends FileToolSetupBase {
|
|
|
897
941
|
nextHooks[hookEventName] = ensureClaudeAISnitchHook(
|
|
898
942
|
currentHooks[hookEventName] ?? [],
|
|
899
943
|
hookEventName,
|
|
900
|
-
this.hookUrl
|
|
944
|
+
this.hookUrl,
|
|
945
|
+
this.scriptPath
|
|
901
946
|
);
|
|
902
947
|
}
|
|
903
948
|
const nextSettings = {
|
|
904
949
|
...parsedSettings,
|
|
905
950
|
hooks: nextHooks
|
|
906
951
|
};
|
|
907
|
-
return
|
|
908
|
-
|
|
952
|
+
return `${JSON.stringify(nextSettings, null, 2)}
|
|
953
|
+
`;
|
|
954
|
+
}
|
|
955
|
+
getFileBackupPath(path) {
|
|
956
|
+
return `${path}.bak`;
|
|
909
957
|
}
|
|
910
958
|
};
|
|
911
959
|
var OpenCodeSetup = class extends FileToolSetupBase {
|
|
@@ -1709,43 +1757,116 @@ async function defaultConfirm(_diff, prompt) {
|
|
|
1709
1757
|
readlineInterface.close();
|
|
1710
1758
|
}
|
|
1711
1759
|
}
|
|
1712
|
-
function ensureClaudeAISnitchHook(groups, hookEventName, hookUrl) {
|
|
1760
|
+
function ensureClaudeAISnitchHook(groups, hookEventName, hookUrl, scriptPath) {
|
|
1713
1761
|
const clonedGroups = groups.map((group) => ({
|
|
1714
1762
|
...group,
|
|
1715
1763
|
hooks: group.hooks.map((handler) => ({ ...handler }))
|
|
1716
1764
|
}));
|
|
1717
|
-
const
|
|
1718
|
-
const matchingGroup = clonedGroups.find((group) => group.matcher === matcher);
|
|
1765
|
+
const matchingGroup = clonedGroups.find((group) => group.matcher === void 0);
|
|
1719
1766
|
if (matchingGroup) {
|
|
1720
|
-
|
|
1767
|
+
matchingGroup.hooks = matchingGroup.hooks.filter(
|
|
1768
|
+
(handler) => !isLegacyClaudeAISnitchHttpHook(handler, hookUrl)
|
|
1769
|
+
);
|
|
1770
|
+
const existingHook = matchingGroup.hooks.find(
|
|
1771
|
+
(handler) => isClaudeAISnitchHook(handler, hookEventName, hookUrl, scriptPath)
|
|
1772
|
+
);
|
|
1721
1773
|
if (existingHook) {
|
|
1722
1774
|
if (existingHook.async !== true) {
|
|
1723
1775
|
existingHook.async = true;
|
|
1724
1776
|
}
|
|
1725
1777
|
return clonedGroups;
|
|
1726
1778
|
}
|
|
1727
|
-
matchingGroup.hooks.push(
|
|
1779
|
+
matchingGroup.hooks.push(
|
|
1780
|
+
createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath)
|
|
1781
|
+
);
|
|
1728
1782
|
return clonedGroups;
|
|
1729
1783
|
}
|
|
1730
|
-
|
|
1731
|
-
hooks: [createClaudeAISnitchHook(hookUrl)]
|
|
1732
|
-
}
|
|
1733
|
-
matcher,
|
|
1734
|
-
hooks: [createClaudeAISnitchHook(hookUrl)]
|
|
1735
|
-
};
|
|
1736
|
-
clonedGroups.push(nextGroup);
|
|
1784
|
+
clonedGroups.push({
|
|
1785
|
+
hooks: [createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath)]
|
|
1786
|
+
});
|
|
1737
1787
|
return clonedGroups;
|
|
1738
1788
|
}
|
|
1739
|
-
function createClaudeAISnitchHook(hookUrl) {
|
|
1789
|
+
function createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath) {
|
|
1740
1790
|
return {
|
|
1741
1791
|
async: true,
|
|
1742
|
-
|
|
1743
|
-
|
|
1792
|
+
command: buildClaudeForwardCommand(hookEventName, hookUrl, scriptPath),
|
|
1793
|
+
type: "command"
|
|
1744
1794
|
};
|
|
1745
1795
|
}
|
|
1746
|
-
function isClaudeAISnitchHook(handler, hookUrl) {
|
|
1796
|
+
function isClaudeAISnitchHook(handler, hookEventName, hookUrl, scriptPath) {
|
|
1797
|
+
return handler.type === "command" && typeof handler.command === "string" && handler.command === buildClaudeForwardCommand(hookEventName, hookUrl, scriptPath);
|
|
1798
|
+
}
|
|
1799
|
+
function isLegacyClaudeAISnitchHttpHook(handler, hookUrl) {
|
|
1747
1800
|
return handler.type === "http" && handler.url === hookUrl;
|
|
1748
1801
|
}
|
|
1802
|
+
function buildClaudeForwardCommand(hookEventName, hookUrl, scriptPath) {
|
|
1803
|
+
return `node ${shellEscapeSingle(scriptPath)} ${hookEventName} ${shellEscapeSingle(hookUrl)}`;
|
|
1804
|
+
}
|
|
1805
|
+
function buildClaudeForwardScriptSource() {
|
|
1806
|
+
return `#!/usr/bin/env node
|
|
1807
|
+
/**
|
|
1808
|
+
* AISnitch Claude Code hook bridge
|
|
1809
|
+
*
|
|
1810
|
+
* \u{1F4D6} Claude Code currently exposes command hooks fed through stdin JSON.
|
|
1811
|
+
* This bridge keeps the Claude config valid while still forwarding every
|
|
1812
|
+
* selected hook event into the local AISnitch HTTP receiver.
|
|
1813
|
+
*/
|
|
1814
|
+
|
|
1815
|
+
function isRecord(value) {
|
|
1816
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
async function readInput() {
|
|
1820
|
+
const chunks = [];
|
|
1821
|
+
|
|
1822
|
+
for await (const chunk of process.stdin) {
|
|
1823
|
+
chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
return chunks.join("");
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
async function main() {
|
|
1830
|
+
const hookEventName = process.argv[2] ?? "unknown";
|
|
1831
|
+
const endpoint = process.argv[3] ?? "http://localhost:4821/hooks/claude-code";
|
|
1832
|
+
const rawInput = await readInput();
|
|
1833
|
+
let payload = {};
|
|
1834
|
+
|
|
1835
|
+
if (rawInput.trim().length > 0) {
|
|
1836
|
+
try {
|
|
1837
|
+
const parsedPayload = JSON.parse(rawInput);
|
|
1838
|
+
|
|
1839
|
+
if (isRecord(parsedPayload)) {
|
|
1840
|
+
payload = parsedPayload;
|
|
1841
|
+
}
|
|
1842
|
+
} catch {
|
|
1843
|
+
payload = {
|
|
1844
|
+
raw: rawInput
|
|
1845
|
+
};
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
try {
|
|
1850
|
+
await fetch(endpoint, {
|
|
1851
|
+
method: "POST",
|
|
1852
|
+
headers: {
|
|
1853
|
+
"content-type": "application/json"
|
|
1854
|
+
},
|
|
1855
|
+
body: JSON.stringify({
|
|
1856
|
+
...payload,
|
|
1857
|
+
hook_event_name: hookEventName
|
|
1858
|
+
})
|
|
1859
|
+
});
|
|
1860
|
+
} catch {
|
|
1861
|
+
// Claude hooks must stay fire-and-forget for observability only.
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
void main().catch(() => {
|
|
1866
|
+
// Never bubble hook bridge failures back into Claude Code itself.
|
|
1867
|
+
});
|
|
1868
|
+
`;
|
|
1869
|
+
}
|
|
1749
1870
|
function ensureGeminiAISnitchHook(groups, hookUrl) {
|
|
1750
1871
|
const clonedGroups = groups.map((group) => ({
|
|
1751
1872
|
...group,
|
|
@@ -2260,6 +2381,9 @@ async function readOptionalFile(path) {
|
|
|
2260
2381
|
throw error;
|
|
2261
2382
|
}
|
|
2262
2383
|
}
|
|
2384
|
+
function shellEscapeSingle(value) {
|
|
2385
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
2386
|
+
}
|
|
2263
2387
|
async function restoreBackupOrRemove(path) {
|
|
2264
2388
|
const backupPath = `${path}.bak`;
|
|
2265
2389
|
if (await fileExists(backupPath)) {
|
|
@@ -9544,6 +9668,7 @@ var Pipeline = class {
|
|
|
9544
9668
|
|
|
9545
9669
|
// src/tui/index.tsx
|
|
9546
9670
|
var import_ink13 = require("ink");
|
|
9671
|
+
var import_fullscreen_ink = require("fullscreen-ink");
|
|
9547
9672
|
var import_ws4 = __toESM(require("ws"), 1);
|
|
9548
9673
|
|
|
9549
9674
|
// src/tui/App.tsx
|
|
@@ -11452,7 +11577,7 @@ function attachEventBusMonitor(eventBus, output) {
|
|
|
11452
11577
|
// src/tui/index.tsx
|
|
11453
11578
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
11454
11579
|
async function renderForegroundTui(options) {
|
|
11455
|
-
const
|
|
11580
|
+
const ink = (0, import_fullscreen_ink.withFullScreen)(
|
|
11456
11581
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
11457
11582
|
App,
|
|
11458
11583
|
{
|
|
@@ -11474,7 +11599,8 @@ async function renderForegroundTui(options) {
|
|
|
11474
11599
|
}
|
|
11475
11600
|
)
|
|
11476
11601
|
);
|
|
11477
|
-
await
|
|
11602
|
+
await ink.start();
|
|
11603
|
+
await ink.waitUntilExit;
|
|
11478
11604
|
}
|
|
11479
11605
|
async function renderManagedTui(options) {
|
|
11480
11606
|
const app = (0, import_ink13.render)(
|