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/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.
|
|
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
|
|
@@ -843,9 +843,15 @@ var FileToolSetupBase = class {
|
|
|
843
843
|
var ClaudeCodeSetup = class extends FileToolSetupBase {
|
|
844
844
|
settingsPath;
|
|
845
845
|
hookUrl;
|
|
846
|
+
scriptPath;
|
|
846
847
|
constructor(httpPort, dependencies = {}) {
|
|
847
848
|
super("claude-code", dependencies.binaryExists);
|
|
848
849
|
this.settingsPath = dependencies.claudeSettingsPath ?? join2(dependencies.homeDirectory ?? homedir2(), ".claude", "settings.json");
|
|
850
|
+
this.scriptPath = join2(
|
|
851
|
+
dependencies.homeDirectory ?? homedir2(),
|
|
852
|
+
".claude",
|
|
853
|
+
"aisnitch-forward.mjs"
|
|
854
|
+
);
|
|
849
855
|
this.hookUrl = `http://localhost:${httpPort}/hooks/claude-code`;
|
|
850
856
|
}
|
|
851
857
|
async detect() {
|
|
@@ -854,7 +860,49 @@ var ClaudeCodeSetup = class extends FileToolSetupBase {
|
|
|
854
860
|
getConfigPath() {
|
|
855
861
|
return this.settingsPath;
|
|
856
862
|
}
|
|
863
|
+
async computeDiff() {
|
|
864
|
+
const currentSettingsContent = await readOptionalFile(this.settingsPath);
|
|
865
|
+
const currentScriptContent = await readOptionalFile(this.scriptPath);
|
|
866
|
+
const nextSettingsContent = this.buildNextSettingsContent(currentSettingsContent);
|
|
867
|
+
const nextScriptContent = buildClaudeForwardScriptSource();
|
|
868
|
+
return [
|
|
869
|
+
renderColoredDiff(
|
|
870
|
+
this.settingsPath,
|
|
871
|
+
currentSettingsContent,
|
|
872
|
+
nextSettingsContent
|
|
873
|
+
),
|
|
874
|
+
"",
|
|
875
|
+
renderColoredDiff(
|
|
876
|
+
this.scriptPath,
|
|
877
|
+
currentScriptContent,
|
|
878
|
+
nextScriptContent
|
|
879
|
+
)
|
|
880
|
+
].join("\n");
|
|
881
|
+
}
|
|
882
|
+
async apply() {
|
|
883
|
+
const currentSettingsContent = await readOptionalFile(this.settingsPath);
|
|
884
|
+
const currentScriptContent = await readOptionalFile(this.scriptPath);
|
|
885
|
+
const nextSettingsContent = this.buildNextSettingsContent(currentSettingsContent);
|
|
886
|
+
const nextScriptContent = buildClaudeForwardScriptSource();
|
|
887
|
+
await mkdir2(dirname2(this.settingsPath), { recursive: true });
|
|
888
|
+
await mkdir2(dirname2(this.scriptPath), { recursive: true });
|
|
889
|
+
if (currentSettingsContent !== null) {
|
|
890
|
+
await copyFile(this.settingsPath, this.getFileBackupPath(this.settingsPath));
|
|
891
|
+
}
|
|
892
|
+
if (currentScriptContent !== null) {
|
|
893
|
+
await copyFile(this.scriptPath, this.getFileBackupPath(this.scriptPath));
|
|
894
|
+
}
|
|
895
|
+
await writeFile2(this.settingsPath, nextSettingsContent, "utf8");
|
|
896
|
+
await writeFile2(this.scriptPath, nextScriptContent, "utf8");
|
|
897
|
+
}
|
|
898
|
+
async revert() {
|
|
899
|
+
await restoreBackupOrRemove(this.settingsPath);
|
|
900
|
+
await restoreBackupOrRemove(this.scriptPath);
|
|
901
|
+
}
|
|
857
902
|
buildNextContent(currentContent) {
|
|
903
|
+
return Promise.resolve(this.buildNextSettingsContent(currentContent));
|
|
904
|
+
}
|
|
905
|
+
buildNextSettingsContent(currentContent) {
|
|
858
906
|
const parsedSettings = parseClaudeSettings(currentContent);
|
|
859
907
|
const currentHooks = parsedSettings.hooks ?? {};
|
|
860
908
|
const nextHooks = {
|
|
@@ -864,15 +912,19 @@ var ClaudeCodeSetup = class extends FileToolSetupBase {
|
|
|
864
912
|
nextHooks[hookEventName] = ensureClaudeAISnitchHook(
|
|
865
913
|
currentHooks[hookEventName] ?? [],
|
|
866
914
|
hookEventName,
|
|
867
|
-
this.hookUrl
|
|
915
|
+
this.hookUrl,
|
|
916
|
+
this.scriptPath
|
|
868
917
|
);
|
|
869
918
|
}
|
|
870
919
|
const nextSettings = {
|
|
871
920
|
...parsedSettings,
|
|
872
921
|
hooks: nextHooks
|
|
873
922
|
};
|
|
874
|
-
return
|
|
875
|
-
|
|
923
|
+
return `${JSON.stringify(nextSettings, null, 2)}
|
|
924
|
+
`;
|
|
925
|
+
}
|
|
926
|
+
getFileBackupPath(path) {
|
|
927
|
+
return `${path}.bak`;
|
|
876
928
|
}
|
|
877
929
|
};
|
|
878
930
|
var OpenCodeSetup = class extends FileToolSetupBase {
|
|
@@ -1676,7 +1728,7 @@ async function defaultConfirm(_diff, prompt) {
|
|
|
1676
1728
|
readlineInterface.close();
|
|
1677
1729
|
}
|
|
1678
1730
|
}
|
|
1679
|
-
function ensureClaudeAISnitchHook(groups, hookEventName, hookUrl) {
|
|
1731
|
+
function ensureClaudeAISnitchHook(groups, hookEventName, hookUrl, scriptPath) {
|
|
1680
1732
|
const clonedGroups = groups.map((group) => ({
|
|
1681
1733
|
...group,
|
|
1682
1734
|
hooks: group.hooks.map((handler) => ({ ...handler }))
|
|
@@ -1684,35 +1736,113 @@ function ensureClaudeAISnitchHook(groups, hookEventName, hookUrl) {
|
|
|
1684
1736
|
const matcher = hookEventName === "FileChanged" ? CLAUDE_FILE_CHANGED_MATCHER : void 0;
|
|
1685
1737
|
const matchingGroup = clonedGroups.find((group) => group.matcher === matcher);
|
|
1686
1738
|
if (matchingGroup) {
|
|
1687
|
-
|
|
1739
|
+
matchingGroup.hooks = matchingGroup.hooks.filter(
|
|
1740
|
+
(handler) => !isLegacyClaudeAISnitchHttpHook(handler, hookUrl)
|
|
1741
|
+
);
|
|
1742
|
+
const existingHook = matchingGroup.hooks.find(
|
|
1743
|
+
(handler) => isClaudeAISnitchHook(handler, hookEventName, hookUrl, scriptPath)
|
|
1744
|
+
);
|
|
1688
1745
|
if (existingHook) {
|
|
1689
1746
|
if (existingHook.async !== true) {
|
|
1690
1747
|
existingHook.async = true;
|
|
1691
1748
|
}
|
|
1692
1749
|
return clonedGroups;
|
|
1693
1750
|
}
|
|
1694
|
-
matchingGroup.hooks.push(
|
|
1751
|
+
matchingGroup.hooks.push(
|
|
1752
|
+
createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath)
|
|
1753
|
+
);
|
|
1695
1754
|
return clonedGroups;
|
|
1696
1755
|
}
|
|
1697
1756
|
const nextGroup = matcher === void 0 ? {
|
|
1698
|
-
hooks: [createClaudeAISnitchHook(hookUrl)]
|
|
1757
|
+
hooks: [createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath)]
|
|
1699
1758
|
} : {
|
|
1700
1759
|
matcher,
|
|
1701
|
-
hooks: [createClaudeAISnitchHook(hookUrl)]
|
|
1760
|
+
hooks: [createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath)]
|
|
1702
1761
|
};
|
|
1703
1762
|
clonedGroups.push(nextGroup);
|
|
1704
1763
|
return clonedGroups;
|
|
1705
1764
|
}
|
|
1706
|
-
function createClaudeAISnitchHook(hookUrl) {
|
|
1765
|
+
function createClaudeAISnitchHook(hookEventName, hookUrl, scriptPath) {
|
|
1707
1766
|
return {
|
|
1708
1767
|
async: true,
|
|
1709
|
-
|
|
1710
|
-
|
|
1768
|
+
command: buildClaudeForwardCommand(hookEventName, hookUrl, scriptPath),
|
|
1769
|
+
type: "command"
|
|
1711
1770
|
};
|
|
1712
1771
|
}
|
|
1713
|
-
function isClaudeAISnitchHook(handler, hookUrl) {
|
|
1772
|
+
function isClaudeAISnitchHook(handler, hookEventName, hookUrl, scriptPath) {
|
|
1773
|
+
return handler.type === "command" && typeof handler.command === "string" && handler.command === buildClaudeForwardCommand(hookEventName, hookUrl, scriptPath);
|
|
1774
|
+
}
|
|
1775
|
+
function isLegacyClaudeAISnitchHttpHook(handler, hookUrl) {
|
|
1714
1776
|
return handler.type === "http" && handler.url === hookUrl;
|
|
1715
1777
|
}
|
|
1778
|
+
function buildClaudeForwardCommand(hookEventName, hookUrl, scriptPath) {
|
|
1779
|
+
return `node ${shellEscapeSingle(scriptPath)} ${hookEventName} ${shellEscapeSingle(hookUrl)}`;
|
|
1780
|
+
}
|
|
1781
|
+
function buildClaudeForwardScriptSource() {
|
|
1782
|
+
return `#!/usr/bin/env node
|
|
1783
|
+
/**
|
|
1784
|
+
* AISnitch Claude Code hook bridge
|
|
1785
|
+
*
|
|
1786
|
+
* \u{1F4D6} Claude Code currently exposes command hooks fed through stdin JSON.
|
|
1787
|
+
* This bridge keeps the Claude config valid while still forwarding every
|
|
1788
|
+
* selected hook event into the local AISnitch HTTP receiver.
|
|
1789
|
+
*/
|
|
1790
|
+
|
|
1791
|
+
function isRecord(value) {
|
|
1792
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
async function readInput() {
|
|
1796
|
+
const chunks = [];
|
|
1797
|
+
|
|
1798
|
+
for await (const chunk of process.stdin) {
|
|
1799
|
+
chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8"));
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
return chunks.join("");
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
async function main() {
|
|
1806
|
+
const hookEventName = process.argv[2] ?? "unknown";
|
|
1807
|
+
const endpoint = process.argv[3] ?? "http://localhost:4821/hooks/claude-code";
|
|
1808
|
+
const rawInput = await readInput();
|
|
1809
|
+
let payload = {};
|
|
1810
|
+
|
|
1811
|
+
if (rawInput.trim().length > 0) {
|
|
1812
|
+
try {
|
|
1813
|
+
const parsedPayload = JSON.parse(rawInput);
|
|
1814
|
+
|
|
1815
|
+
if (isRecord(parsedPayload)) {
|
|
1816
|
+
payload = parsedPayload;
|
|
1817
|
+
}
|
|
1818
|
+
} catch {
|
|
1819
|
+
payload = {
|
|
1820
|
+
raw: rawInput
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
}
|
|
1824
|
+
|
|
1825
|
+
try {
|
|
1826
|
+
await fetch(endpoint, {
|
|
1827
|
+
method: "POST",
|
|
1828
|
+
headers: {
|
|
1829
|
+
"content-type": "application/json"
|
|
1830
|
+
},
|
|
1831
|
+
body: JSON.stringify({
|
|
1832
|
+
...payload,
|
|
1833
|
+
hook_event_name: hookEventName
|
|
1834
|
+
})
|
|
1835
|
+
});
|
|
1836
|
+
} catch {
|
|
1837
|
+
// Claude hooks must stay fire-and-forget for observability only.
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
void main().catch(() => {
|
|
1842
|
+
// Never bubble hook bridge failures back into Claude Code itself.
|
|
1843
|
+
});
|
|
1844
|
+
`;
|
|
1845
|
+
}
|
|
1716
1846
|
function ensureGeminiAISnitchHook(groups, hookUrl) {
|
|
1717
1847
|
const clonedGroups = groups.map((group) => ({
|
|
1718
1848
|
...group,
|
|
@@ -2227,6 +2357,9 @@ async function readOptionalFile(path) {
|
|
|
2227
2357
|
throw error;
|
|
2228
2358
|
}
|
|
2229
2359
|
}
|
|
2360
|
+
function shellEscapeSingle(value) {
|
|
2361
|
+
return `'${value.replaceAll("'", `'"'"'`)}'`;
|
|
2362
|
+
}
|
|
2230
2363
|
async function restoreBackupOrRemove(path) {
|
|
2231
2364
|
const backupPath = `${path}.bak`;
|
|
2232
2365
|
if (await fileExists(backupPath)) {
|
|
@@ -5006,6 +5139,9 @@ var ClaudeCodeAdapter = class extends BaseAdapter {
|
|
|
5006
5139
|
});
|
|
5007
5140
|
const context = {
|
|
5008
5141
|
cwd: getString(payload, "cwd"),
|
|
5142
|
+
// 📖 Pass process.env so the context detector can detect the terminal
|
|
5143
|
+
// from TERM_PROGRAM, ITERM_SESSION_ID, etc. — hooks don't carry env vars
|
|
5144
|
+
env: this.env ?? process.env,
|
|
5009
5145
|
hookPayload: payload,
|
|
5010
5146
|
pid: getNumber(payload, "pid"),
|
|
5011
5147
|
sessionId,
|
|
@@ -5239,6 +5375,8 @@ function extractClaudeTranscriptObservations(payload, transcriptPath) {
|
|
|
5239
5375
|
const tokensUsed = extractTokenUsage(payload);
|
|
5240
5376
|
const rawPayload = payload;
|
|
5241
5377
|
const sharedContext = {
|
|
5378
|
+
// 📖 Pass process.env so terminal detection works from transcript path too
|
|
5379
|
+
env: process.env,
|
|
5242
5380
|
hookPayload: rawPayload,
|
|
5243
5381
|
sessionId,
|
|
5244
5382
|
source: "aisnitch://adapters/claude-code/transcript",
|
|
@@ -8895,6 +9033,8 @@ var OpenCodeAdapter = class extends BaseAdapter {
|
|
|
8895
9033
|
});
|
|
8896
9034
|
const context = {
|
|
8897
9035
|
cwd: extractOpenCodeCwd(payload),
|
|
9036
|
+
// 📖 Pass process.env so the context detector can detect the terminal
|
|
9037
|
+
env: this.env ?? process.env,
|
|
8898
9038
|
hookPayload: payload,
|
|
8899
9039
|
pid: getNumber5(payload, "pid"),
|
|
8900
9040
|
sessionId,
|
|
@@ -8905,6 +9045,8 @@ var OpenCodeAdapter = class extends BaseAdapter {
|
|
|
8905
9045
|
cwd: context.cwd,
|
|
8906
9046
|
errorMessage: extractOpenCodeErrorMessage(payload),
|
|
8907
9047
|
errorType: extractOpenCodeErrorType(payload),
|
|
9048
|
+
// 📖 Extract model from payload — OpenCode may send it as "model" or nested in properties
|
|
9049
|
+
model: getString7(payload, "model") ?? getString7(getRecord6(payload.properties), "model"),
|
|
8908
9050
|
project: extractOpenCodeProject(payload),
|
|
8909
9051
|
raw: payload,
|
|
8910
9052
|
toolInput: extractOpenCodeToolInput(payload),
|
|
@@ -9504,6 +9646,7 @@ var Pipeline = class {
|
|
|
9504
9646
|
|
|
9505
9647
|
// src/tui/index.tsx
|
|
9506
9648
|
import { render } from "ink";
|
|
9649
|
+
import { withFullScreen } from "fullscreen-ink";
|
|
9507
9650
|
import WebSocket4 from "ws";
|
|
9508
9651
|
|
|
9509
9652
|
// src/tui/App.tsx
|
|
@@ -11412,7 +11555,7 @@ function attachEventBusMonitor(eventBus, output) {
|
|
|
11412
11555
|
// src/tui/index.tsx
|
|
11413
11556
|
import { jsx as jsx13 } from "react/jsx-runtime";
|
|
11414
11557
|
async function renderForegroundTui(options) {
|
|
11415
|
-
const
|
|
11558
|
+
const ink = withFullScreen(
|
|
11416
11559
|
/* @__PURE__ */ jsx13(
|
|
11417
11560
|
App,
|
|
11418
11561
|
{
|
|
@@ -11434,7 +11577,8 @@ async function renderForegroundTui(options) {
|
|
|
11434
11577
|
}
|
|
11435
11578
|
)
|
|
11436
11579
|
);
|
|
11437
|
-
await
|
|
11580
|
+
await ink.start();
|
|
11581
|
+
await ink.waitUntilExit;
|
|
11438
11582
|
}
|
|
11439
11583
|
async function renderManagedTui(options) {
|
|
11440
11584
|
const app = render(
|