aisnitch 0.2.5 → 0.2.12
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 +150 -15
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +150 -15
- 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
|
|
@@ -876,9 +876,15 @@ var FileToolSetupBase = class {
|
|
|
876
876
|
var ClaudeCodeSetup = class extends FileToolSetupBase {
|
|
877
877
|
settingsPath;
|
|
878
878
|
hookUrl;
|
|
879
|
+
scriptPath;
|
|
879
880
|
constructor(httpPort, dependencies = {}) {
|
|
880
881
|
super("claude-code", dependencies.binaryExists);
|
|
881
882
|
this.settingsPath = dependencies.claudeSettingsPath ?? (0, import_node_path2.join)(dependencies.homeDirectory ?? (0, import_node_os2.homedir)(), ".claude", "settings.json");
|
|
883
|
+
this.scriptPath = (0, import_node_path2.join)(
|
|
884
|
+
dependencies.homeDirectory ?? (0, import_node_os2.homedir)(),
|
|
885
|
+
".claude",
|
|
886
|
+
"aisnitch-forward.mjs"
|
|
887
|
+
);
|
|
882
888
|
this.hookUrl = `http://localhost:${httpPort}/hooks/claude-code`;
|
|
883
889
|
}
|
|
884
890
|
async detect() {
|
|
@@ -887,7 +893,49 @@ var ClaudeCodeSetup = class extends FileToolSetupBase {
|
|
|
887
893
|
getConfigPath() {
|
|
888
894
|
return this.settingsPath;
|
|
889
895
|
}
|
|
896
|
+
async computeDiff() {
|
|
897
|
+
const currentSettingsContent = await readOptionalFile(this.settingsPath);
|
|
898
|
+
const currentScriptContent = await readOptionalFile(this.scriptPath);
|
|
899
|
+
const nextSettingsContent = this.buildNextSettingsContent(currentSettingsContent);
|
|
900
|
+
const nextScriptContent = buildClaudeForwardScriptSource();
|
|
901
|
+
return [
|
|
902
|
+
renderColoredDiff(
|
|
903
|
+
this.settingsPath,
|
|
904
|
+
currentSettingsContent,
|
|
905
|
+
nextSettingsContent
|
|
906
|
+
),
|
|
907
|
+
"",
|
|
908
|
+
renderColoredDiff(
|
|
909
|
+
this.scriptPath,
|
|
910
|
+
currentScriptContent,
|
|
911
|
+
nextScriptContent
|
|
912
|
+
)
|
|
913
|
+
].join("\n");
|
|
914
|
+
}
|
|
915
|
+
async apply() {
|
|
916
|
+
const currentSettingsContent = await readOptionalFile(this.settingsPath);
|
|
917
|
+
const currentScriptContent = await readOptionalFile(this.scriptPath);
|
|
918
|
+
const nextSettingsContent = this.buildNextSettingsContent(currentSettingsContent);
|
|
919
|
+
const nextScriptContent = buildClaudeForwardScriptSource();
|
|
920
|
+
await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(this.settingsPath), { recursive: true });
|
|
921
|
+
await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(this.scriptPath), { recursive: true });
|
|
922
|
+
if (currentSettingsContent !== null) {
|
|
923
|
+
await (0, import_promises2.copyFile)(this.settingsPath, this.getFileBackupPath(this.settingsPath));
|
|
924
|
+
}
|
|
925
|
+
if (currentScriptContent !== null) {
|
|
926
|
+
await (0, import_promises2.copyFile)(this.scriptPath, this.getFileBackupPath(this.scriptPath));
|
|
927
|
+
}
|
|
928
|
+
await (0, import_promises2.writeFile)(this.settingsPath, nextSettingsContent, "utf8");
|
|
929
|
+
await (0, import_promises2.writeFile)(this.scriptPath, nextScriptContent, "utf8");
|
|
930
|
+
}
|
|
931
|
+
async revert() {
|
|
932
|
+
await restoreBackupOrRemove(this.settingsPath);
|
|
933
|
+
await restoreBackupOrRemove(this.scriptPath);
|
|
934
|
+
}
|
|
890
935
|
buildNextContent(currentContent) {
|
|
936
|
+
return Promise.resolve(this.buildNextSettingsContent(currentContent));
|
|
937
|
+
}
|
|
938
|
+
buildNextSettingsContent(currentContent) {
|
|
891
939
|
const parsedSettings = parseClaudeSettings(currentContent);
|
|
892
940
|
const currentHooks = parsedSettings.hooks ?? {};
|
|
893
941
|
const nextHooks = {
|
|
@@ -897,15 +945,19 @@ var ClaudeCodeSetup = class extends FileToolSetupBase {
|
|
|
897
945
|
nextHooks[hookEventName] = ensureClaudeAISnitchHook(
|
|
898
946
|
currentHooks[hookEventName] ?? [],
|
|
899
947
|
hookEventName,
|
|
900
|
-
this.hookUrl
|
|
948
|
+
this.hookUrl,
|
|
949
|
+
this.scriptPath
|
|
901
950
|
);
|
|
902
951
|
}
|
|
903
952
|
const nextSettings = {
|
|
904
953
|
...parsedSettings,
|
|
905
954
|
hooks: nextHooks
|
|
906
955
|
};
|
|
907
|
-
return
|
|
908
|
-
|
|
956
|
+
return `${JSON.stringify(nextSettings, null, 2)}
|
|
957
|
+
`;
|
|
958
|
+
}
|
|
959
|
+
getFileBackupPath(path) {
|
|
960
|
+
return `${path}.bak`;
|
|
909
961
|
}
|
|
910
962
|
};
|
|
911
963
|
var OpenCodeSetup = class extends FileToolSetupBase {
|
|
@@ -1709,7 +1761,7 @@ async function defaultConfirm(_diff, prompt) {
|
|
|
1709
1761
|
readlineInterface.close();
|
|
1710
1762
|
}
|
|
1711
1763
|
}
|
|
1712
|
-
function ensureClaudeAISnitchHook(groups, hookEventName, hookUrl) {
|
|
1764
|
+
function ensureClaudeAISnitchHook(groups, hookEventName, hookUrl, scriptPath) {
|
|
1713
1765
|
const clonedGroups = groups.map((group) => ({
|
|
1714
1766
|
...group,
|
|
1715
1767
|
hooks: group.hooks.map((handler) => ({ ...handler }))
|
|
@@ -1717,35 +1769,113 @@ function ensureClaudeAISnitchHook(groups, hookEventName, hookUrl) {
|
|
|
1717
1769
|
const matcher = hookEventName === "FileChanged" ? CLAUDE_FILE_CHANGED_MATCHER : void 0;
|
|
1718
1770
|
const matchingGroup = clonedGroups.find((group) => group.matcher === matcher);
|
|
1719
1771
|
if (matchingGroup) {
|
|
1720
|
-
|
|
1772
|
+
matchingGroup.hooks = matchingGroup.hooks.filter(
|
|
1773
|
+
(handler) => !isLegacyClaudeAISnitchHttpHook(handler, hookUrl)
|
|
1774
|
+
);
|
|
1775
|
+
const existingHook = matchingGroup.hooks.find(
|
|
1776
|
+
(handler) => isClaudeAISnitchHook(handler, hookEventName, hookUrl, scriptPath)
|
|
1777
|
+
);
|
|
1721
1778
|
if (existingHook) {
|
|
1722
1779
|
if (existingHook.async !== true) {
|
|
1723
1780
|
existingHook.async = true;
|
|
1724
1781
|
}
|
|
1725
1782
|
return clonedGroups;
|
|
1726
1783
|
}
|
|
1727
|
-
matchingGroup.hooks.push(
|
|
1784
|
+
matchingGroup.hooks.push(
|
|
1785
|
+
createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath)
|
|
1786
|
+
);
|
|
1728
1787
|
return clonedGroups;
|
|
1729
1788
|
}
|
|
1730
1789
|
const nextGroup = matcher === void 0 ? {
|
|
1731
|
-
hooks: [createClaudeAISnitchHook(hookUrl)]
|
|
1790
|
+
hooks: [createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath)]
|
|
1732
1791
|
} : {
|
|
1733
1792
|
matcher,
|
|
1734
|
-
hooks: [createClaudeAISnitchHook(hookUrl)]
|
|
1793
|
+
hooks: [createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath)]
|
|
1735
1794
|
};
|
|
1736
1795
|
clonedGroups.push(nextGroup);
|
|
1737
1796
|
return clonedGroups;
|
|
1738
1797
|
}
|
|
1739
|
-
function createClaudeAISnitchHook(hookUrl) {
|
|
1798
|
+
function createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath) {
|
|
1740
1799
|
return {
|
|
1741
1800
|
async: true,
|
|
1742
|
-
|
|
1743
|
-
|
|
1801
|
+
command: buildClaudeForwardCommand(hookEventName, hookUrl, scriptPath),
|
|
1802
|
+
type: "command"
|
|
1744
1803
|
};
|
|
1745
1804
|
}
|
|
1746
|
-
function isClaudeAISnitchHook(handler, hookUrl) {
|
|
1805
|
+
function isClaudeAISnitchHook(handler, hookEventName, hookUrl, scriptPath) {
|
|
1806
|
+
return handler.type === "command" && typeof handler.command === "string" && handler.command === buildClaudeForwardCommand(hookEventName, hookUrl, scriptPath);
|
|
1807
|
+
}
|
|
1808
|
+
function isLegacyClaudeAISnitchHttpHook(handler, hookUrl) {
|
|
1747
1809
|
return handler.type === "http" && handler.url === hookUrl;
|
|
1748
1810
|
}
|
|
1811
|
+
function buildClaudeForwardCommand(hookEventName, hookUrl, scriptPath) {
|
|
1812
|
+
return `node ${shellEscapeSingle(scriptPath)} ${hookEventName} ${shellEscapeSingle(hookUrl)}`;
|
|
1813
|
+
}
|
|
1814
|
+
function buildClaudeForwardScriptSource() {
|
|
1815
|
+
return `#!/usr/bin/env node
|
|
1816
|
+
/**
|
|
1817
|
+
* AISnitch Claude Code hook bridge
|
|
1818
|
+
*
|
|
1819
|
+
* \u{1F4D6} Claude Code currently exposes command hooks fed through stdin JSON.
|
|
1820
|
+
* This bridge keeps the Claude config valid while still forwarding every
|
|
1821
|
+
* selected hook event into the local AISnitch HTTP receiver.
|
|
1822
|
+
*/
|
|
1823
|
+
|
|
1824
|
+
function isRecord(value) {
|
|
1825
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
async function readInput() {
|
|
1829
|
+
const chunks = [];
|
|
1830
|
+
|
|
1831
|
+
for await (const chunk of process.stdin) {
|
|
1832
|
+
chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
return chunks.join("");
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
async function main() {
|
|
1839
|
+
const hookEventName = process.argv[2] ?? "unknown";
|
|
1840
|
+
const endpoint = process.argv[3] ?? "http://localhost:4821/hooks/claude-code";
|
|
1841
|
+
const rawInput = await readInput();
|
|
1842
|
+
let payload = {};
|
|
1843
|
+
|
|
1844
|
+
if (rawInput.trim().length > 0) {
|
|
1845
|
+
try {
|
|
1846
|
+
const parsedPayload = JSON.parse(rawInput);
|
|
1847
|
+
|
|
1848
|
+
if (isRecord(parsedPayload)) {
|
|
1849
|
+
payload = parsedPayload;
|
|
1850
|
+
}
|
|
1851
|
+
} catch {
|
|
1852
|
+
payload = {
|
|
1853
|
+
raw: rawInput
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
try {
|
|
1859
|
+
await fetch(endpoint, {
|
|
1860
|
+
method: "POST",
|
|
1861
|
+
headers: {
|
|
1862
|
+
"content-type": "application/json"
|
|
1863
|
+
},
|
|
1864
|
+
body: JSON.stringify({
|
|
1865
|
+
...payload,
|
|
1866
|
+
hook_event_name: hookEventName
|
|
1867
|
+
})
|
|
1868
|
+
});
|
|
1869
|
+
} catch {
|
|
1870
|
+
// Claude hooks must stay fire-and-forget for observability only.
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
void main().catch(() => {
|
|
1875
|
+
// Never bubble hook bridge failures back into Claude Code itself.
|
|
1876
|
+
});
|
|
1877
|
+
`;
|
|
1878
|
+
}
|
|
1749
1879
|
function ensureGeminiAISnitchHook(groups, hookUrl) {
|
|
1750
1880
|
const clonedGroups = groups.map((group) => ({
|
|
1751
1881
|
...group,
|
|
@@ -2260,6 +2390,9 @@ async function readOptionalFile(path) {
|
|
|
2260
2390
|
throw error;
|
|
2261
2391
|
}
|
|
2262
2392
|
}
|
|
2393
|
+
function shellEscapeSingle(value) {
|
|
2394
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
2395
|
+
}
|
|
2263
2396
|
async function restoreBackupOrRemove(path) {
|
|
2264
2397
|
const backupPath = `${path}.bak`;
|
|
2265
2398
|
if (await fileExists(backupPath)) {
|
|
@@ -9544,6 +9677,7 @@ var Pipeline = class {
|
|
|
9544
9677
|
|
|
9545
9678
|
// src/tui/index.tsx
|
|
9546
9679
|
var import_ink13 = require("ink");
|
|
9680
|
+
var import_fullscreen_ink = require("fullscreen-ink");
|
|
9547
9681
|
var import_ws4 = __toESM(require("ws"), 1);
|
|
9548
9682
|
|
|
9549
9683
|
// src/tui/App.tsx
|
|
@@ -11452,7 +11586,7 @@ function attachEventBusMonitor(eventBus, output) {
|
|
|
11452
11586
|
// src/tui/index.tsx
|
|
11453
11587
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
11454
11588
|
async function renderForegroundTui(options) {
|
|
11455
|
-
const
|
|
11589
|
+
const ink = (0, import_fullscreen_ink.withFullScreen)(
|
|
11456
11590
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
11457
11591
|
App,
|
|
11458
11592
|
{
|
|
@@ -11474,7 +11608,8 @@ async function renderForegroundTui(options) {
|
|
|
11474
11608
|
}
|
|
11475
11609
|
)
|
|
11476
11610
|
);
|
|
11477
|
-
await
|
|
11611
|
+
await ink.start();
|
|
11612
|
+
await ink.waitUntilExit;
|
|
11478
11613
|
}
|
|
11479
11614
|
async function renderManagedTui(options) {
|
|
11480
11615
|
const app = (0, import_ink13.render)(
|