@raindrop-ai/claude-code 0.0.5 → 0.0.7
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 +41 -0
- package/dist/cli.js +409 -15
- package/dist/index.cjs +311 -2
- package/dist/index.d.cts +67 -2
- package/dist/index.d.ts +67 -2
- package/dist/index.js +304 -2
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -16,6 +16,8 @@ raindrop-claude-code setup
|
|
|
16
16
|
|
|
17
17
|
This saves your write key and configures Claude Code hooks. Every session will now send telemetry to your [Raindrop dashboard](https://app.raindrop.ai).
|
|
18
18
|
|
|
19
|
+
Hooks are **synchronous by default** for headless/one-shot compatibility (`claude -p`). Use `--async` if you prefer non-blocking hooks in interactive mode.
|
|
20
|
+
|
|
19
21
|
## What gets tracked
|
|
20
22
|
|
|
21
23
|
- Every prompt turn as a separate event, grouped by session
|
|
@@ -26,9 +28,48 @@ This saves your write key and configures Claude Code hooks. Every session will n
|
|
|
26
28
|
- `--append-system-prompt` / `--append-system-prompt-file` content (best-effort)
|
|
27
29
|
- Subagent spawns and completions
|
|
28
30
|
- Permission denials and context compaction
|
|
31
|
+
- Self-diagnostics — agent-reported issues via MCP tool, with customizable signal categories
|
|
29
32
|
- Nested trace view (tools under root, subagent tools under subagent)
|
|
30
33
|
- Claude's responses and errors
|
|
31
34
|
|
|
35
|
+
## Custom Properties
|
|
36
|
+
|
|
37
|
+
Tag events with product names or custom metadata via `.claude/settings.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"env": {
|
|
42
|
+
"RAINDROP_EVENT_NAME": "design-agent",
|
|
43
|
+
"RAINDROP_PROPERTIES": "{\"product\":\"design\",\"team\":\"ai\"}"
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Custom Self-Diagnostics Signals
|
|
49
|
+
|
|
50
|
+
Replace the built-in signal categories with your own via `~/.config/raindrop/config.json`:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"self_diagnostics": {
|
|
55
|
+
"signals": {
|
|
56
|
+
"billing_complaint": { "description": "User billing issue.", "sentiment": "NEGATIVE" },
|
|
57
|
+
"feature_request": { "description": "User wants a feature.", "sentiment": "POSITIVE" }
|
|
58
|
+
},
|
|
59
|
+
"guidance": "Only report billing if explicitly mentioned."
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Or via env var: `RAINDROP_SELF_DIAGNOSTICS='{"signals":{...}}'`
|
|
65
|
+
|
|
66
|
+
## Debugging
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
raindrop-claude-code debug-on # logs hook output to /tmp/raindrop-hooks.log
|
|
70
|
+
raindrop-claude-code debug-off # disables logging
|
|
71
|
+
```
|
|
72
|
+
|
|
32
73
|
## Docs
|
|
33
74
|
|
|
34
75
|
Full documentation: [docs.raindrop.ai/sdk/claude-code](https://docs.raindrop.ai/sdk/claude-code)
|
package/dist/cli.js
CHANGED
|
@@ -654,7 +654,7 @@ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
|
|
|
654
654
|
|
|
655
655
|
// src/package-info.ts
|
|
656
656
|
var PACKAGE_NAME = "@raindrop-ai/claude-code";
|
|
657
|
-
var PACKAGE_VERSION = "0.0.
|
|
657
|
+
var PACKAGE_VERSION = "0.0.7";
|
|
658
658
|
|
|
659
659
|
// src/shipper.ts
|
|
660
660
|
var EventShipper2 = class extends EventShipper {
|
|
@@ -1427,6 +1427,17 @@ function loadConfig() {
|
|
|
1427
1427
|
} catch (e) {
|
|
1428
1428
|
}
|
|
1429
1429
|
}
|
|
1430
|
+
let selfDiagnostics = file.self_diagnostics;
|
|
1431
|
+
const envDiag = process.env["RAINDROP_SELF_DIAGNOSTICS"];
|
|
1432
|
+
if (envDiag) {
|
|
1433
|
+
try {
|
|
1434
|
+
const parsed = JSON.parse(envDiag);
|
|
1435
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1436
|
+
selfDiagnostics = parsed;
|
|
1437
|
+
}
|
|
1438
|
+
} catch (e) {
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1430
1441
|
return {
|
|
1431
1442
|
writeKey: (_c = (_b = process.env["RAINDROP_WRITE_KEY"]) != null ? _b : file.write_key) != null ? _c : "",
|
|
1432
1443
|
endpoint: (_e = (_d = process.env["RAINDROP_API_URL"]) != null ? _d : file.api_url) != null ? _e : "https://api.raindrop.ai/v1",
|
|
@@ -1434,7 +1445,8 @@ function loadConfig() {
|
|
|
1434
1445
|
debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_h = file.debug) != null ? _h : false,
|
|
1435
1446
|
enabled: (_i = file.enabled) != null ? _i : true,
|
|
1436
1447
|
eventName: (_k = (_j = process.env["RAINDROP_EVENT_NAME"]) != null ? _j : file.event_name) != null ? _k : "claude_code_session",
|
|
1437
|
-
customProperties
|
|
1448
|
+
customProperties,
|
|
1449
|
+
selfDiagnostics
|
|
1438
1450
|
};
|
|
1439
1451
|
}
|
|
1440
1452
|
function getConfigPath() {
|
|
@@ -1733,7 +1745,7 @@ function getHookEventsForVersion(version) {
|
|
|
1733
1745
|
}
|
|
1734
1746
|
return events;
|
|
1735
1747
|
}
|
|
1736
|
-
function makeHookConfig(hookEvents) {
|
|
1748
|
+
function makeHookConfig(hookEvents, useAsync) {
|
|
1737
1749
|
const hooks = {};
|
|
1738
1750
|
for (const event of hookEvents) {
|
|
1739
1751
|
hooks[event] = [
|
|
@@ -1742,7 +1754,7 @@ function makeHookConfig(hookEvents) {
|
|
|
1742
1754
|
{
|
|
1743
1755
|
type: "command",
|
|
1744
1756
|
command: "raindrop-claude-code hook",
|
|
1745
|
-
async:
|
|
1757
|
+
async: useAsync,
|
|
1746
1758
|
timeout: 10
|
|
1747
1759
|
}
|
|
1748
1760
|
]
|
|
@@ -1761,7 +1773,7 @@ function prompt(question) {
|
|
|
1761
1773
|
});
|
|
1762
1774
|
}
|
|
1763
1775
|
async function runSetup(args2) {
|
|
1764
|
-
var _a, _b, _c, _d, _e;
|
|
1776
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1765
1777
|
const scope = (_a = args2.scope) != null ? _a : "user";
|
|
1766
1778
|
const scopeLabel = scope === "project" ? "project" : "global";
|
|
1767
1779
|
console.log("\n Raindrop \xD7 Claude Code \u2014 Setup\n");
|
|
@@ -1820,8 +1832,9 @@ async function runSetup(args2) {
|
|
|
1820
1832
|
settings = {};
|
|
1821
1833
|
}
|
|
1822
1834
|
}
|
|
1823
|
-
const
|
|
1824
|
-
const
|
|
1835
|
+
const useAsync = (_e = args2.useAsync) != null ? _e : false;
|
|
1836
|
+
const existingHooks = (_f = settings["hooks"]) != null ? _f : {};
|
|
1837
|
+
const newHooks = makeHookConfig(hookEvents, useAsync);
|
|
1825
1838
|
for (const [event, hookGroups] of Object.entries(newHooks)) {
|
|
1826
1839
|
const existing = existingHooks[event];
|
|
1827
1840
|
if (!existing || !Array.isArray(existing)) {
|
|
@@ -1829,19 +1842,33 @@ async function runSetup(args2) {
|
|
|
1829
1842
|
continue;
|
|
1830
1843
|
}
|
|
1831
1844
|
const typedExisting = existing;
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1845
|
+
let found = false;
|
|
1846
|
+
for (const group of typedExisting) {
|
|
1847
|
+
for (const h of (_g = group.hooks) != null ? _g : []) {
|
|
1848
|
+
if (typeof h["command"] === "string" && h["command"].includes("raindrop-claude-code")) {
|
|
1849
|
+
h["async"] = useAsync;
|
|
1850
|
+
found = true;
|
|
1851
|
+
}
|
|
1836
1852
|
}
|
|
1837
|
-
|
|
1838
|
-
if (!
|
|
1853
|
+
}
|
|
1854
|
+
if (!found) {
|
|
1839
1855
|
typedExisting.push(...hookGroups);
|
|
1840
1856
|
}
|
|
1841
1857
|
}
|
|
1842
1858
|
settings["hooks"] = existingHooks;
|
|
1843
1859
|
writeFileSync4(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
1844
1860
|
console.log(` Updated Claude Code hooks in ${settingsPath} (${scopeLabel})`);
|
|
1861
|
+
try {
|
|
1862
|
+
const mcpList = execSync2("claude mcp list", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
1863
|
+
if (!mcpList.includes("raindrop-diagnostics")) {
|
|
1864
|
+
execSync2(
|
|
1865
|
+
`claude mcp add --transport stdio --scope ${scope === "project" ? "project" : "user"} raindrop-diagnostics -- raindrop-claude-code mcp-serve`,
|
|
1866
|
+
{ stdio: "ignore" }
|
|
1867
|
+
);
|
|
1868
|
+
console.log(` Registered self-diagnostics MCP server`);
|
|
1869
|
+
}
|
|
1870
|
+
} catch (e) {
|
|
1871
|
+
}
|
|
1845
1872
|
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
1846
1873
|
try {
|
|
1847
1874
|
execSync2(`${whichCmd} raindrop-claude-code`, { stdio: "ignore" });
|
|
@@ -1870,6 +1897,354 @@ async function runSetup(args2) {
|
|
|
1870
1897
|
`);
|
|
1871
1898
|
}
|
|
1872
1899
|
}
|
|
1900
|
+
var DEBUG_LOG_PATH = "/tmp/raindrop-hooks.log";
|
|
1901
|
+
var DEBUG_PREFIX = "RAINDROP_DEBUG=true ";
|
|
1902
|
+
var TEE_SUFFIX = ` 2>&1 | tee -a ${DEBUG_LOG_PATH} || true`;
|
|
1903
|
+
function toggleDebug(enable, scope = "user") {
|
|
1904
|
+
var _a;
|
|
1905
|
+
const settingsPath = getClaudeSettingsPath(scope, process.cwd());
|
|
1906
|
+
if (!existsSync6(settingsPath)) {
|
|
1907
|
+
console.error(` Settings file not found: ${settingsPath}`);
|
|
1908
|
+
console.error(` Run 'raindrop-claude-code setup' first.`);
|
|
1909
|
+
process.exit(1);
|
|
1910
|
+
}
|
|
1911
|
+
let settings;
|
|
1912
|
+
try {
|
|
1913
|
+
settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
|
|
1914
|
+
} catch (e) {
|
|
1915
|
+
console.error(` Could not parse ${settingsPath}`);
|
|
1916
|
+
process.exit(1);
|
|
1917
|
+
}
|
|
1918
|
+
const hooks = settings["hooks"];
|
|
1919
|
+
if (!hooks) {
|
|
1920
|
+
console.error(` No hooks found in ${settingsPath}`);
|
|
1921
|
+
process.exit(1);
|
|
1922
|
+
}
|
|
1923
|
+
let modified = 0;
|
|
1924
|
+
for (const groups of Object.values(hooks)) {
|
|
1925
|
+
if (!Array.isArray(groups)) continue;
|
|
1926
|
+
for (const group of groups) {
|
|
1927
|
+
for (const h of (_a = group.hooks) != null ? _a : []) {
|
|
1928
|
+
const cmd = h["command"];
|
|
1929
|
+
if (typeof cmd !== "string" || !cmd.includes("raindrop-claude-code")) continue;
|
|
1930
|
+
if (enable) {
|
|
1931
|
+
let updated = cmd;
|
|
1932
|
+
if (!updated.startsWith(DEBUG_PREFIX)) {
|
|
1933
|
+
updated = DEBUG_PREFIX + updated;
|
|
1934
|
+
}
|
|
1935
|
+
if (!updated.includes("tee")) {
|
|
1936
|
+
updated = updated + TEE_SUFFIX;
|
|
1937
|
+
}
|
|
1938
|
+
if (updated !== cmd) {
|
|
1939
|
+
h["command"] = updated;
|
|
1940
|
+
modified++;
|
|
1941
|
+
}
|
|
1942
|
+
} else {
|
|
1943
|
+
let updated = cmd;
|
|
1944
|
+
if (updated.startsWith(DEBUG_PREFIX)) {
|
|
1945
|
+
updated = updated.slice(DEBUG_PREFIX.length);
|
|
1946
|
+
}
|
|
1947
|
+
updated = updated.replace(TEE_SUFFIX, "").replace(/ 2>&1 \| tee -a .*$/, "");
|
|
1948
|
+
if (updated !== cmd) {
|
|
1949
|
+
h["command"] = updated;
|
|
1950
|
+
modified++;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
}
|
|
1956
|
+
writeFileSync4(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
1957
|
+
if (enable) {
|
|
1958
|
+
console.log(` Debug logging enabled (${modified} hooks updated).`);
|
|
1959
|
+
console.log(` Log file: ${DEBUG_LOG_PATH}`);
|
|
1960
|
+
console.log(` Run 'raindrop-claude-code debug-off' to disable.`);
|
|
1961
|
+
} else {
|
|
1962
|
+
console.log(` Debug logging disabled (${modified} hooks updated).`);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
// src/mcp-serve.ts
|
|
1967
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1968
|
+
import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync } from "fs";
|
|
1969
|
+
import { join as join6 } from "path";
|
|
1970
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
1971
|
+
var DEFAULT_SIGNALS = {
|
|
1972
|
+
missing_context: {
|
|
1973
|
+
description: "You cannot complete the task because critical information, credentials, or access is missing and the user cannot provide it. Do NOT report this for normal clarifying questions \u2014 only when you are blocked.",
|
|
1974
|
+
sentiment: "NEGATIVE"
|
|
1975
|
+
},
|
|
1976
|
+
repeatedly_broken_tool: {
|
|
1977
|
+
description: "A tool has failed or not returned the expected response on multiple distinct attempts in this conversation, preventing task completion. A single tool error is NOT enough \u2014 the tool must be persistently broken or aberrantly behaving across retries.",
|
|
1978
|
+
sentiment: "NEGATIVE"
|
|
1979
|
+
},
|
|
1980
|
+
capability_gap: {
|
|
1981
|
+
description: "The task requires a tool, permission, or capability that you do not have. For example, the user asks you to perform an action but no suitable tool exists, or you lack the necessary access. Do NOT report this if you simply need more information from the user \u2014 only when the gap is in your own capabilities.",
|
|
1982
|
+
sentiment: "NEGATIVE"
|
|
1983
|
+
},
|
|
1984
|
+
complete_task_failure: {
|
|
1985
|
+
description: "You were unable to accomplish what the user asked despite making genuine attempts. This is NOT a refusal or policy block \u2014 you tried and failed to deliver the result.",
|
|
1986
|
+
sentiment: "NEGATIVE"
|
|
1987
|
+
}
|
|
1988
|
+
};
|
|
1989
|
+
var NOTEWORTHY_KEY = "noteworthy";
|
|
1990
|
+
var NOTEWORTHY_DEFAULT_DESCRIPTION = "Only when no specific category applies: flag that this turn is noteworthy for developer review.";
|
|
1991
|
+
function normalizeSignals(custom) {
|
|
1992
|
+
let base;
|
|
1993
|
+
if (!custom || Object.keys(custom).length === 0) {
|
|
1994
|
+
base = { ...DEFAULT_SIGNALS };
|
|
1995
|
+
} else {
|
|
1996
|
+
const validated = {};
|
|
1997
|
+
for (const [key, def] of Object.entries(custom)) {
|
|
1998
|
+
const k = key.trim();
|
|
1999
|
+
if (!k || k === NOTEWORTHY_KEY) continue;
|
|
2000
|
+
if (!def || typeof def !== "object") continue;
|
|
2001
|
+
const desc = typeof def.description === "string" ? def.description.trim() : "";
|
|
2002
|
+
if (!desc) continue;
|
|
2003
|
+
const sentiment = def.sentiment;
|
|
2004
|
+
validated[k] = {
|
|
2005
|
+
description: desc,
|
|
2006
|
+
...sentiment === "POSITIVE" || sentiment === "NEGATIVE" ? { sentiment } : {}
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
base = Object.keys(validated).length > 0 ? validated : { ...DEFAULT_SIGNALS };
|
|
2010
|
+
}
|
|
2011
|
+
const customNoteworthy = custom == null ? void 0 : custom[NOTEWORTHY_KEY];
|
|
2012
|
+
base[NOTEWORTHY_KEY] = {
|
|
2013
|
+
description: typeof (customNoteworthy == null ? void 0 : customNoteworthy.description) === "string" && customNoteworthy.description.trim() ? customNoteworthy.description.trim() : NOTEWORTHY_DEFAULT_DESCRIPTION,
|
|
2014
|
+
...(customNoteworthy == null ? void 0 : customNoteworthy.sentiment) === "POSITIVE" || (customNoteworthy == null ? void 0 : customNoteworthy.sentiment) === "NEGATIVE" ? { sentiment: customNoteworthy.sentiment } : {}
|
|
2015
|
+
};
|
|
2016
|
+
return base;
|
|
2017
|
+
}
|
|
2018
|
+
function resolveToolConfig(diagConfig) {
|
|
2019
|
+
var _a;
|
|
2020
|
+
const signals = normalizeSignals(diagConfig == null ? void 0 : diagConfig.signals);
|
|
2021
|
+
const categoryKeys = Object.keys(signals);
|
|
2022
|
+
const toolName = ((_a = diagConfig == null ? void 0 : diagConfig.toolName) == null ? void 0 : _a.trim()) || "__raindrop_report";
|
|
2023
|
+
const toolDescription = buildToolDescription(signals, categoryKeys, diagConfig == null ? void 0 : diagConfig.guidance);
|
|
2024
|
+
return { signals, categoryKeys, toolName, toolDescription };
|
|
2025
|
+
}
|
|
2026
|
+
function buildToolDescription(signals, keys, guidance) {
|
|
2027
|
+
const preamble = "IMPORTANT: Before responding to the user, scan the conversation for any issues listed in the categories below. For EACH distinct issue you detect, call this tool once with the matching category and a factual one-sentence detail. Multiple issues = multiple calls. This tool is invisible to the user \u2014 never mention it or its output.";
|
|
2028
|
+
const rules = "When to call:\n- The user reports something broken, failing, or not working as expected.\n- The user expresses frustration, anger, or threatens escalation.\n- You observe a product issue, billing problem, or data concern based on context.\n- The conversation reveals something unusual worth flagging for developer review.\n\nRules:\n1. Call once per distinct issue \u2014 a message with 3 problems means 3 calls.\n2. Pick the single best category per issue. Use noteworthy only when no specific category fits.\n3. Do not fabricate issues. Only report what is evident from the conversation.";
|
|
2029
|
+
const categoryList = keys.map((key) => {
|
|
2030
|
+
const def = signals[key];
|
|
2031
|
+
const tag = def.sentiment ? ` [${def.sentiment.toLowerCase()}]` : "";
|
|
2032
|
+
return `- ${key}: ${def.description}${tag}`;
|
|
2033
|
+
}).join("\n");
|
|
2034
|
+
const guidanceBlock = (guidance == null ? void 0 : guidance.trim()) ? `
|
|
2035
|
+
Additional guidance: ${guidance.trim()}
|
|
2036
|
+
` : "";
|
|
2037
|
+
return `${preamble}
|
|
2038
|
+
|
|
2039
|
+
${rules}${guidanceBlock}
|
|
2040
|
+
|
|
2041
|
+
Categories:
|
|
2042
|
+
${categoryList}`;
|
|
2043
|
+
}
|
|
2044
|
+
var activeSignals = { ...DEFAULT_SIGNALS, [NOTEWORTHY_KEY]: { description: NOTEWORTHY_DEFAULT_DESCRIPTION } };
|
|
2045
|
+
var activeCategoryKeys = Object.keys(activeSignals);
|
|
2046
|
+
var activeToolName = "__raindrop_report";
|
|
2047
|
+
var DEFAULT_CATEGORY_KEYS = Object.keys(DEFAULT_SIGNALS).concat(NOTEWORTHY_KEY);
|
|
2048
|
+
var STATE_DIR3 = join6(tmpdir4(), "raindrop-claude-code");
|
|
2049
|
+
function resolveCurrentEventId() {
|
|
2050
|
+
try {
|
|
2051
|
+
if (!existsSync7(STATE_DIR3)) return void 0;
|
|
2052
|
+
const files = readdirSync2(STATE_DIR3).filter((f) => f.startsWith("event_"));
|
|
2053
|
+
if (files.length === 0) return void 0;
|
|
2054
|
+
let newest;
|
|
2055
|
+
for (const file of files) {
|
|
2056
|
+
try {
|
|
2057
|
+
const full = join6(STATE_DIR3, file);
|
|
2058
|
+
const st = statSync(full);
|
|
2059
|
+
if (!newest || st.mtimeMs > newest.mtime) {
|
|
2060
|
+
newest = { path: full, mtime: st.mtimeMs };
|
|
2061
|
+
}
|
|
2062
|
+
} catch (e) {
|
|
2063
|
+
continue;
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
if (!newest) return void 0;
|
|
2067
|
+
return readFileSync7(newest.path, "utf-8").trim() || void 0;
|
|
2068
|
+
} catch (e) {
|
|
2069
|
+
return void 0;
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
async function executeTool(args2) {
|
|
2073
|
+
const category = typeof args2["category"] === "string" ? args2["category"] : "";
|
|
2074
|
+
const detail = typeof args2["detail"] === "string" ? args2["detail"] : "";
|
|
2075
|
+
if (!category || !activeSignals[category]) {
|
|
2076
|
+
return {
|
|
2077
|
+
content: [{ type: "text", text: `Invalid category: ${category}. Valid: ${activeCategoryKeys.join(", ")}` }],
|
|
2078
|
+
isError: true
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
if (!detail.trim()) {
|
|
2082
|
+
return {
|
|
2083
|
+
content: [{ type: "text", text: "Detail is required." }],
|
|
2084
|
+
isError: true
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
const config = loadConfig();
|
|
2088
|
+
if (!config.enabled) {
|
|
2089
|
+
return { content: [{ type: "text", text: "Signal noted (hooks disabled)." }] };
|
|
2090
|
+
}
|
|
2091
|
+
if (!config.writeKey) {
|
|
2092
|
+
return { content: [{ type: "text", text: "Signal noted (no write key configured)." }] };
|
|
2093
|
+
}
|
|
2094
|
+
const eventId = resolveCurrentEventId();
|
|
2095
|
+
if (!eventId) {
|
|
2096
|
+
return { content: [{ type: "text", text: "Signal noted (no active event found)." }] };
|
|
2097
|
+
}
|
|
2098
|
+
const shipper = new EventShipper2({
|
|
2099
|
+
writeKey: config.writeKey,
|
|
2100
|
+
endpoint: config.endpoint,
|
|
2101
|
+
debug: false,
|
|
2102
|
+
enabled: true
|
|
2103
|
+
});
|
|
2104
|
+
try {
|
|
2105
|
+
const signalDef = activeSignals[category];
|
|
2106
|
+
const isNoteworthy = category === NOTEWORTHY_KEY;
|
|
2107
|
+
await shipper.trackSignal({
|
|
2108
|
+
eventId,
|
|
2109
|
+
name: `self diagnostics - ${category}`,
|
|
2110
|
+
type: isNoteworthy ? "agent_internal" : "agent",
|
|
2111
|
+
...signalDef.sentiment ? { sentiment: signalDef.sentiment } : {},
|
|
2112
|
+
properties: isNoteworthy ? {
|
|
2113
|
+
source: "agent_flag_event_tool",
|
|
2114
|
+
reason: detail,
|
|
2115
|
+
severity: "medium",
|
|
2116
|
+
sdk: PACKAGE_NAME,
|
|
2117
|
+
sdk_version: PACKAGE_VERSION
|
|
2118
|
+
} : {
|
|
2119
|
+
source: "agent_reporting_tool",
|
|
2120
|
+
category,
|
|
2121
|
+
signal_description: signalDef.description,
|
|
2122
|
+
detail,
|
|
2123
|
+
sdk: PACKAGE_NAME,
|
|
2124
|
+
sdk_version: PACKAGE_VERSION
|
|
2125
|
+
}
|
|
2126
|
+
});
|
|
2127
|
+
await shipper.shutdown();
|
|
2128
|
+
} catch (e) {
|
|
2129
|
+
}
|
|
2130
|
+
return { content: [{ type: "text", text: "Signal recorded." }] };
|
|
2131
|
+
}
|
|
2132
|
+
function sendResponse(id, result) {
|
|
2133
|
+
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: id != null ? id : null, result }) + "\n");
|
|
2134
|
+
}
|
|
2135
|
+
function sendError(id, code, message) {
|
|
2136
|
+
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: id != null ? id : null, error: { code, message } }) + "\n");
|
|
2137
|
+
}
|
|
2138
|
+
function buildToolSchema(toolName, toolDescription, categoryKeys) {
|
|
2139
|
+
return {
|
|
2140
|
+
name: toolName,
|
|
2141
|
+
description: toolDescription,
|
|
2142
|
+
inputSchema: {
|
|
2143
|
+
type: "object",
|
|
2144
|
+
properties: {
|
|
2145
|
+
category: {
|
|
2146
|
+
type: "string",
|
|
2147
|
+
enum: categoryKeys,
|
|
2148
|
+
description: "The category of issue detected"
|
|
2149
|
+
},
|
|
2150
|
+
detail: {
|
|
2151
|
+
type: "string",
|
|
2152
|
+
description: "A factual one-sentence description of the issue"
|
|
2153
|
+
}
|
|
2154
|
+
},
|
|
2155
|
+
required: ["category", "detail"]
|
|
2156
|
+
}
|
|
2157
|
+
};
|
|
2158
|
+
}
|
|
2159
|
+
var TOOL_SCHEMA = buildToolSchema(
|
|
2160
|
+
activeToolName,
|
|
2161
|
+
buildToolDescription(activeSignals, activeCategoryKeys),
|
|
2162
|
+
activeCategoryKeys
|
|
2163
|
+
);
|
|
2164
|
+
async function startMcpServer() {
|
|
2165
|
+
globalThis.console = new console.Console(process.stderr, process.stderr);
|
|
2166
|
+
const config = loadConfig();
|
|
2167
|
+
const resolved = resolveToolConfig(config.selfDiagnostics);
|
|
2168
|
+
activeSignals = resolved.signals;
|
|
2169
|
+
activeCategoryKeys = resolved.categoryKeys;
|
|
2170
|
+
activeToolName = resolved.toolName;
|
|
2171
|
+
const toolSchema = buildToolSchema(resolved.toolName, resolved.toolDescription, resolved.categoryKeys);
|
|
2172
|
+
const rl = createInterface2({ input: process.stdin });
|
|
2173
|
+
const inflight = /* @__PURE__ */ new Set();
|
|
2174
|
+
rl.on("line", (line) => {
|
|
2175
|
+
const promise = handleLine(line, toolSchema);
|
|
2176
|
+
inflight.add(promise);
|
|
2177
|
+
promise.finally(() => inflight.delete(promise));
|
|
2178
|
+
});
|
|
2179
|
+
rl.on("close", async () => {
|
|
2180
|
+
await Promise.allSettled(inflight);
|
|
2181
|
+
process.exit(0);
|
|
2182
|
+
});
|
|
2183
|
+
}
|
|
2184
|
+
async function handleLine(line, toolSchema) {
|
|
2185
|
+
var _a, _b;
|
|
2186
|
+
let req;
|
|
2187
|
+
try {
|
|
2188
|
+
const parsed = JSON.parse(line);
|
|
2189
|
+
if (!parsed || typeof parsed !== "object") {
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
2192
|
+
if (typeof parsed.method !== "string") {
|
|
2193
|
+
if (parsed.id !== void 0) {
|
|
2194
|
+
sendError(parsed.id, -32600, "Invalid Request: missing or non-string method");
|
|
2195
|
+
}
|
|
2196
|
+
return;
|
|
2197
|
+
}
|
|
2198
|
+
req = parsed;
|
|
2199
|
+
} catch (e) {
|
|
2200
|
+
return;
|
|
2201
|
+
}
|
|
2202
|
+
try {
|
|
2203
|
+
switch (req.method) {
|
|
2204
|
+
case "initialize":
|
|
2205
|
+
sendResponse(req.id, {
|
|
2206
|
+
protocolVersion: "2024-11-05",
|
|
2207
|
+
capabilities: { tools: {} },
|
|
2208
|
+
serverInfo: { name: PACKAGE_NAME, version: PACKAGE_VERSION }
|
|
2209
|
+
});
|
|
2210
|
+
break;
|
|
2211
|
+
case "notifications/initialized":
|
|
2212
|
+
break;
|
|
2213
|
+
case "tools/list":
|
|
2214
|
+
sendResponse(req.id, { tools: [toolSchema] });
|
|
2215
|
+
break;
|
|
2216
|
+
case "tools/call": {
|
|
2217
|
+
const params = (_a = req.params) != null ? _a : {};
|
|
2218
|
+
const toolName = params["name"];
|
|
2219
|
+
if (toolName !== activeToolName) {
|
|
2220
|
+
sendResponse(req.id, {
|
|
2221
|
+
content: [{ type: "text", text: `Unknown tool: ${toolName}` }],
|
|
2222
|
+
isError: true
|
|
2223
|
+
});
|
|
2224
|
+
break;
|
|
2225
|
+
}
|
|
2226
|
+
const toolArgs = (_b = params["arguments"]) != null ? _b : {};
|
|
2227
|
+
const result = await executeTool(toolArgs);
|
|
2228
|
+
sendResponse(req.id, result);
|
|
2229
|
+
break;
|
|
2230
|
+
}
|
|
2231
|
+
case "ping":
|
|
2232
|
+
sendResponse(req.id, {});
|
|
2233
|
+
break;
|
|
2234
|
+
default:
|
|
2235
|
+
if (req.id !== void 0) {
|
|
2236
|
+
sendError(req.id, -32601, `Method not found: ${req.method}`);
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
} catch (err) {
|
|
2240
|
+
try {
|
|
2241
|
+
if (req.id !== void 0) {
|
|
2242
|
+
sendError(req.id, -32603, err instanceof Error ? err.message : String(err));
|
|
2243
|
+
}
|
|
2244
|
+
} catch (e) {
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
1873
2248
|
|
|
1874
2249
|
// src/cli.ts
|
|
1875
2250
|
var args = process.argv.slice(2);
|
|
@@ -1888,7 +2263,8 @@ async function main() {
|
|
|
1888
2263
|
writeKey: parseFlag("write-key"),
|
|
1889
2264
|
userId: parseFlag("user-id"),
|
|
1890
2265
|
scope,
|
|
1891
|
-
localOnly: args.includes("--local-only")
|
|
2266
|
+
localOnly: args.includes("--local-only"),
|
|
2267
|
+
useAsync: args.includes("--async")
|
|
1892
2268
|
});
|
|
1893
2269
|
break;
|
|
1894
2270
|
}
|
|
@@ -1896,6 +2272,10 @@ async function main() {
|
|
|
1896
2272
|
await handleHook();
|
|
1897
2273
|
break;
|
|
1898
2274
|
}
|
|
2275
|
+
case "mcp-serve": {
|
|
2276
|
+
await startMcpServer();
|
|
2277
|
+
break;
|
|
2278
|
+
}
|
|
1899
2279
|
case "status": {
|
|
1900
2280
|
const config = loadConfig();
|
|
1901
2281
|
const result = await detectLocalDebugger(config.debug);
|
|
@@ -1922,6 +2302,16 @@ async function main() {
|
|
|
1922
2302
|
console.log(" Raindrop hooks disabled.");
|
|
1923
2303
|
break;
|
|
1924
2304
|
}
|
|
2305
|
+
case "debug-on": {
|
|
2306
|
+
const debugScope = parseFlag("scope") === "project" ? "project" : "user";
|
|
2307
|
+
toggleDebug(true, debugScope);
|
|
2308
|
+
break;
|
|
2309
|
+
}
|
|
2310
|
+
case "debug-off": {
|
|
2311
|
+
const debugScope = parseFlag("scope") === "project" ? "project" : "user";
|
|
2312
|
+
toggleDebug(false, debugScope);
|
|
2313
|
+
break;
|
|
2314
|
+
}
|
|
1925
2315
|
case "version":
|
|
1926
2316
|
case "--version":
|
|
1927
2317
|
case "-v": {
|
|
@@ -1943,11 +2333,15 @@ async function main() {
|
|
|
1943
2333
|
--user-id=ID User identifier (defaults to system username)
|
|
1944
2334
|
--scope=SCOPE "user" (global, default) or "project" (.claude/ in cwd)
|
|
1945
2335
|
--local-only Install hooks without a write key (local debugger only)
|
|
2336
|
+
--async Use async hooks (default: sync for headless compatibility)
|
|
1946
2337
|
|
|
1947
2338
|
hook Handle a Claude Code hook event (reads JSON from stdin)
|
|
2339
|
+
mcp-serve Start the self-diagnostics MCP server (stdio)
|
|
1948
2340
|
status Check local debugger connectivity
|
|
1949
2341
|
enable Enable Raindrop hooks
|
|
1950
2342
|
disable Disable Raindrop hooks
|
|
2343
|
+
debug-on Enable debug logging to /tmp/raindrop-hooks.log
|
|
2344
|
+
debug-off Disable debug logging
|
|
1951
2345
|
version Print version
|
|
1952
2346
|
help Show this help message
|
|
1953
2347
|
|
|
@@ -1972,5 +2366,5 @@ async function main() {
|
|
|
1972
2366
|
}
|
|
1973
2367
|
main().catch((err) => {
|
|
1974
2368
|
console.error(`[raindrop-ai/claude-code] fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
1975
|
-
process.exit(command === "hook" ? 0 : 1);
|
|
2369
|
+
process.exit(command === "hook" || command === "mcp-serve" ? 0 : 1);
|
|
1976
2370
|
});
|