aisnitch 0.2.4 → 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 +159 -15
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js +159 -15
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +14 -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 +14 -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)) {
|
|
@@ -5037,6 +5170,9 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
|
|
|
5037
5170
|
});
|
|
5038
5171
|
const context = {
|
|
5039
5172
|
cwd: getString(payload, "cwd"),
|
|
5173
|
+
// 📖 Pass process.env so the context detector can detect the terminal
|
|
5174
|
+
// from TERM_PROGRAM, ITERM_SESSION_ID, etc. — hooks don't carry env vars
|
|
5175
|
+
env: this.env ?? process.env,
|
|
5040
5176
|
hookPayload: payload,
|
|
5041
5177
|
pid: getNumber(payload, "pid"),
|
|
5042
5178
|
sessionId,
|
|
@@ -5270,6 +5406,8 @@ function extractClaudeTranscriptObservations(payload, transcriptPath) {
|
|
|
5270
5406
|
const tokensUsed = extractTokenUsage(payload);
|
|
5271
5407
|
const rawPayload = payload;
|
|
5272
5408
|
const sharedContext = {
|
|
5409
|
+
// 📖 Pass process.env so terminal detection works from transcript path too
|
|
5410
|
+
env: process.env,
|
|
5273
5411
|
hookPayload: rawPayload,
|
|
5274
5412
|
sessionId,
|
|
5275
5413
|
source: "aisnitch://adapters/claude-code/transcript",
|
|
@@ -8926,6 +9064,8 @@ var OpenCodeAdapter = class extends BaseAdapter {
|
|
|
8926
9064
|
});
|
|
8927
9065
|
const context = {
|
|
8928
9066
|
cwd: extractOpenCodeCwd(payload),
|
|
9067
|
+
// 📖 Pass process.env so the context detector can detect the terminal
|
|
9068
|
+
env: this.env ?? process.env,
|
|
8929
9069
|
hookPayload: payload,
|
|
8930
9070
|
pid: getNumber5(payload, "pid"),
|
|
8931
9071
|
sessionId,
|
|
@@ -8936,6 +9076,8 @@ var OpenCodeAdapter = class extends BaseAdapter {
|
|
|
8936
9076
|
cwd: context.cwd,
|
|
8937
9077
|
errorMessage: extractOpenCodeErrorMessage(payload),
|
|
8938
9078
|
errorType: extractOpenCodeErrorType(payload),
|
|
9079
|
+
// 📖 Extract model from payload — OpenCode may send it as "model" or nested in properties
|
|
9080
|
+
model: getString7(payload, "model") ?? getString7(getRecord6(payload.properties), "model"),
|
|
8939
9081
|
project: extractOpenCodeProject(payload),
|
|
8940
9082
|
raw: payload,
|
|
8941
9083
|
toolInput: extractOpenCodeToolInput(payload),
|
|
@@ -9535,6 +9677,7 @@ var Pipeline = class {
|
|
|
9535
9677
|
|
|
9536
9678
|
// src/tui/index.tsx
|
|
9537
9679
|
var import_ink13 = require("ink");
|
|
9680
|
+
var import_fullscreen_ink = require("fullscreen-ink");
|
|
9538
9681
|
var import_ws4 = __toESM(require("ws"), 1);
|
|
9539
9682
|
|
|
9540
9683
|
// src/tui/App.tsx
|
|
@@ -11443,7 +11586,7 @@ function attachEventBusMonitor(eventBus, output) {
|
|
|
11443
11586
|
// src/tui/index.tsx
|
|
11444
11587
|
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
11445
11588
|
async function renderForegroundTui(options) {
|
|
11446
|
-
const
|
|
11589
|
+
const ink = (0, import_fullscreen_ink.withFullScreen)(
|
|
11447
11590
|
/* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
11448
11591
|
App,
|
|
11449
11592
|
{
|
|
@@ -11465,7 +11608,8 @@ async function renderForegroundTui(options) {
|
|
|
11465
11608
|
}
|
|
11466
11609
|
)
|
|
11467
11610
|
);
|
|
11468
|
-
await
|
|
11611
|
+
await ink.start();
|
|
11612
|
+
await ink.waitUntilExit;
|
|
11469
11613
|
}
|
|
11470
11614
|
async function renderManagedTui(options) {
|
|
11471
11615
|
const app = (0, import_ink13.render)(
|