@raindrop-ai/claude-code 0.0.4 → 0.0.6
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 +20 -0
- package/dist/cli.js +713 -39
- package/dist/index.cjs +705 -26
- package/dist/index.d.cts +119 -2
- package/dist/index.d.ts +119 -2
- package/dist/index.js +690 -21
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/event-mapper.ts
|
|
4
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
4
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, readdirSync, unlinkSync, writeFileSync } from "fs";
|
|
5
|
+
import { execSync } from "child_process";
|
|
5
6
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
6
7
|
import { tmpdir } from "os";
|
|
7
8
|
import { join } from "path";
|
|
@@ -653,7 +654,7 @@ globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
|
|
|
653
654
|
|
|
654
655
|
// src/package-info.ts
|
|
655
656
|
var PACKAGE_NAME = "@raindrop-ai/claude-code";
|
|
656
|
-
var PACKAGE_VERSION = "0.0.
|
|
657
|
+
var PACKAGE_VERSION = "0.0.6";
|
|
657
658
|
|
|
658
659
|
// src/shipper.ts
|
|
659
660
|
var EventShipper2 = class extends EventShipper {
|
|
@@ -690,6 +691,138 @@ var TraceShipper2 = class extends TraceShipper {
|
|
|
690
691
|
}
|
|
691
692
|
};
|
|
692
693
|
|
|
694
|
+
// src/transcript.ts
|
|
695
|
+
import { existsSync, readFileSync } from "fs";
|
|
696
|
+
function parseTranscript(transcriptPath) {
|
|
697
|
+
var _a, _b, _c, _d, _e;
|
|
698
|
+
try {
|
|
699
|
+
if (!existsSync(transcriptPath)) return void 0;
|
|
700
|
+
const content = readFileSync(transcriptPath, "utf-8");
|
|
701
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
702
|
+
const summary = {
|
|
703
|
+
totalInputTokens: 0,
|
|
704
|
+
totalOutputTokens: 0,
|
|
705
|
+
totalCacheReadTokens: 0,
|
|
706
|
+
totalCacheCreationTokens: 0,
|
|
707
|
+
turnCount: 0,
|
|
708
|
+
toolsUsed: [],
|
|
709
|
+
hasThinking: false
|
|
710
|
+
};
|
|
711
|
+
const toolNames = /* @__PURE__ */ new Set();
|
|
712
|
+
let lastUsage;
|
|
713
|
+
for (const line of lines) {
|
|
714
|
+
let entry;
|
|
715
|
+
try {
|
|
716
|
+
entry = JSON.parse(line);
|
|
717
|
+
} catch (e) {
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
720
|
+
if (entry.type === "system" && entry.subtype === "turn_duration") {
|
|
721
|
+
const durationMs = entry["durationMs"];
|
|
722
|
+
if (typeof durationMs === "number") {
|
|
723
|
+
summary.totalDurationMs = ((_a = summary.totalDurationMs) != null ? _a : 0) + durationMs;
|
|
724
|
+
}
|
|
725
|
+
const version = entry["version"];
|
|
726
|
+
if (typeof version === "string") {
|
|
727
|
+
summary.codeVersion = version;
|
|
728
|
+
}
|
|
729
|
+
const branch = entry["gitBranch"];
|
|
730
|
+
if (typeof branch === "string") {
|
|
731
|
+
summary.gitBranch = branch;
|
|
732
|
+
}
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
if (entry.type !== "assistant") continue;
|
|
736
|
+
const msg = entry.message;
|
|
737
|
+
if (!msg) continue;
|
|
738
|
+
summary.turnCount++;
|
|
739
|
+
if (msg.model) {
|
|
740
|
+
summary.model = msg.model;
|
|
741
|
+
}
|
|
742
|
+
if (msg.stop_reason) {
|
|
743
|
+
summary.stopReason = msg.stop_reason;
|
|
744
|
+
}
|
|
745
|
+
const entryVersion = entry["version"];
|
|
746
|
+
if (typeof entryVersion === "string") {
|
|
747
|
+
summary.codeVersion = entryVersion;
|
|
748
|
+
}
|
|
749
|
+
const entryBranch = entry["gitBranch"];
|
|
750
|
+
if (typeof entryBranch === "string") {
|
|
751
|
+
summary.gitBranch = entryBranch;
|
|
752
|
+
}
|
|
753
|
+
if (msg.usage) {
|
|
754
|
+
const u = msg.usage;
|
|
755
|
+
summary.totalInputTokens += (_b = u.input_tokens) != null ? _b : 0;
|
|
756
|
+
summary.totalOutputTokens += (_c = u.output_tokens) != null ? _c : 0;
|
|
757
|
+
summary.totalCacheReadTokens += (_d = u.cache_read_input_tokens) != null ? _d : 0;
|
|
758
|
+
summary.totalCacheCreationTokens += (_e = u.cache_creation_input_tokens) != null ? _e : 0;
|
|
759
|
+
if (u.service_tier) {
|
|
760
|
+
summary.serviceTier = u.service_tier;
|
|
761
|
+
}
|
|
762
|
+
lastUsage = u;
|
|
763
|
+
}
|
|
764
|
+
if (Array.isArray(msg.content)) {
|
|
765
|
+
for (const block of msg.content) {
|
|
766
|
+
if (block.type === "tool_use" && block.name) {
|
|
767
|
+
toolNames.add(block.name);
|
|
768
|
+
}
|
|
769
|
+
if (block.type === "thinking") {
|
|
770
|
+
summary.hasThinking = true;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
if (lastUsage) {
|
|
776
|
+
summary.lastTurnInputTokens = lastUsage.input_tokens;
|
|
777
|
+
summary.lastTurnOutputTokens = lastUsage.output_tokens;
|
|
778
|
+
summary.lastTurnCacheReadTokens = lastUsage.cache_read_input_tokens;
|
|
779
|
+
}
|
|
780
|
+
summary.toolsUsed = [...toolNames].sort();
|
|
781
|
+
return summary;
|
|
782
|
+
} catch (e) {
|
|
783
|
+
return void 0;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
function transcriptToProperties(summary) {
|
|
787
|
+
var _a, _b;
|
|
788
|
+
const props = {};
|
|
789
|
+
props["ai.usage.prompt_tokens"] = (_a = summary.lastTurnInputTokens) != null ? _a : summary.totalInputTokens;
|
|
790
|
+
props["ai.usage.completion_tokens"] = (_b = summary.lastTurnOutputTokens) != null ? _b : summary.totalOutputTokens;
|
|
791
|
+
if (summary.lastTurnCacheReadTokens !== void 0) {
|
|
792
|
+
props["ai.usage.cache_read_tokens"] = summary.lastTurnCacheReadTokens;
|
|
793
|
+
}
|
|
794
|
+
props["ai.usage.session_total.prompt_tokens"] = summary.totalInputTokens;
|
|
795
|
+
props["ai.usage.session_total.completion_tokens"] = summary.totalOutputTokens;
|
|
796
|
+
props["ai.usage.session_total.cache_read_tokens"] = summary.totalCacheReadTokens;
|
|
797
|
+
props["ai.usage.session_total.cache_creation_tokens"] = summary.totalCacheCreationTokens;
|
|
798
|
+
if (summary.model) {
|
|
799
|
+
props["ai.model"] = summary.model;
|
|
800
|
+
}
|
|
801
|
+
if (summary.serviceTier) {
|
|
802
|
+
props["ai.service_tier"] = summary.serviceTier;
|
|
803
|
+
}
|
|
804
|
+
if (summary.stopReason) {
|
|
805
|
+
props["ai.stop_reason"] = summary.stopReason;
|
|
806
|
+
}
|
|
807
|
+
if (summary.toolsUsed.length > 0) {
|
|
808
|
+
props["ai.tools_used"] = summary.toolsUsed.join(", ");
|
|
809
|
+
}
|
|
810
|
+
props["ai.turn_count"] = summary.turnCount;
|
|
811
|
+
if (summary.totalDurationMs !== void 0) {
|
|
812
|
+
props["ai.duration_ms"] = summary.totalDurationMs;
|
|
813
|
+
}
|
|
814
|
+
if (summary.codeVersion) {
|
|
815
|
+
props["claude_code.version"] = summary.codeVersion;
|
|
816
|
+
}
|
|
817
|
+
if (summary.gitBranch) {
|
|
818
|
+
props["git.branch"] = summary.gitBranch;
|
|
819
|
+
}
|
|
820
|
+
if (summary.hasThinking) {
|
|
821
|
+
props["ai.has_thinking"] = true;
|
|
822
|
+
}
|
|
823
|
+
return props;
|
|
824
|
+
}
|
|
825
|
+
|
|
693
826
|
// src/event-mapper.ts
|
|
694
827
|
var MAX_ATTR_LENGTH = 32768;
|
|
695
828
|
function truncate(value) {
|
|
@@ -734,8 +867,8 @@ function writeState(key, value) {
|
|
|
734
867
|
function readState(key) {
|
|
735
868
|
try {
|
|
736
869
|
const filePath = statePath(key);
|
|
737
|
-
if (!
|
|
738
|
-
return
|
|
870
|
+
if (!existsSync2(filePath)) return void 0;
|
|
871
|
+
return readFileSync2(filePath, "utf-8");
|
|
739
872
|
} catch (e) {
|
|
740
873
|
return void 0;
|
|
741
874
|
}
|
|
@@ -800,12 +933,15 @@ async function mapHookToRaindrop(payload, config, eventShipper, traceShipper) {
|
|
|
800
933
|
cwd: payload.cwd,
|
|
801
934
|
permission_mode: payload.permission_mode,
|
|
802
935
|
plugin_version: PACKAGE_VERSION,
|
|
936
|
+
sdk: PACKAGE_NAME,
|
|
937
|
+
sdk_version: PACKAGE_VERSION,
|
|
803
938
|
...payload.agent_id ? { agent_id: payload.agent_id } : {},
|
|
804
939
|
...payload.agent_type ? { agent_type: payload.agent_type } : {}
|
|
805
940
|
};
|
|
806
941
|
switch (payload.hook_event_name) {
|
|
807
942
|
case "SessionStart":
|
|
808
943
|
writeState(`model_${payload.session_id}`, (_a = payload.model) != null ? _a : "");
|
|
944
|
+
tryCaptureAppendSystemPrompt(payload.session_id);
|
|
809
945
|
break;
|
|
810
946
|
case "UserPromptSubmit":
|
|
811
947
|
await handleUserPromptSubmit(payload, convoId, config, baseProperties, eventShipper, traceShipper);
|
|
@@ -820,10 +956,13 @@ async function mapHookToRaindrop(payload, config, eventShipper, traceShipper) {
|
|
|
820
956
|
handlePostToolUseFailure(payload, getCurrentEventId(payload.session_id), traceShipper);
|
|
821
957
|
break;
|
|
822
958
|
case "Stop":
|
|
823
|
-
await
|
|
959
|
+
await handleStopOrFailure(payload, getCurrentEventId(payload.session_id), config, baseProperties, eventShipper, traceShipper);
|
|
824
960
|
break;
|
|
825
961
|
case "StopFailure":
|
|
826
|
-
await
|
|
962
|
+
await handleStopOrFailure(payload, getCurrentEventId(payload.session_id), config, baseProperties, eventShipper, traceShipper, {
|
|
963
|
+
error: payload.error,
|
|
964
|
+
error_details: payload.error_details
|
|
965
|
+
});
|
|
827
966
|
break;
|
|
828
967
|
case "SubagentStart":
|
|
829
968
|
handleSubagentStart(payload, getCurrentEventId(payload.session_id), traceShipper);
|
|
@@ -834,6 +973,9 @@ async function mapHookToRaindrop(payload, config, eventShipper, traceShipper) {
|
|
|
834
973
|
case "PermissionDenied":
|
|
835
974
|
handlePermissionDenied(payload, getCurrentEventId(payload.session_id), traceShipper);
|
|
836
975
|
break;
|
|
976
|
+
case "InstructionsLoaded":
|
|
977
|
+
handleInstructionsLoaded(payload);
|
|
978
|
+
break;
|
|
837
979
|
case "PostCompact":
|
|
838
980
|
await handlePostCompact(payload, getCurrentEventId(payload.session_id), config, baseProperties, eventShipper);
|
|
839
981
|
break;
|
|
@@ -842,6 +984,8 @@ async function mapHookToRaindrop(payload, config, eventShipper, traceShipper) {
|
|
|
842
984
|
deleteState(turnEventKey(payload.session_id));
|
|
843
985
|
deleteState(rootSpanKey(payload.session_id));
|
|
844
986
|
deleteState(`model_${payload.session_id}`);
|
|
987
|
+
deleteState(appendSystemPromptKey(payload.session_id));
|
|
988
|
+
cleanupInstructions(payload.session_id);
|
|
845
989
|
break;
|
|
846
990
|
default:
|
|
847
991
|
if (config.debug) {
|
|
@@ -999,25 +1143,243 @@ async function handlePostCompact(payload, eventId, config, properties, eventShip
|
|
|
999
1143
|
}
|
|
1000
1144
|
});
|
|
1001
1145
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1146
|
+
function handleInstructionsLoaded(payload) {
|
|
1147
|
+
var _a;
|
|
1148
|
+
if (!payload.file_path || !payload.session_id) return;
|
|
1149
|
+
try {
|
|
1150
|
+
if (existsSync2(payload.file_path)) {
|
|
1151
|
+
const content = readFileSync2(payload.file_path, "utf-8");
|
|
1152
|
+
const MAX_INSTRUCTIONS_LENGTH = 8192;
|
|
1153
|
+
const truncated = content.length > MAX_INSTRUCTIONS_LENGTH ? content.slice(0, MAX_INSTRUCTIONS_LENGTH) + "\n...[truncated]" : content;
|
|
1154
|
+
const fileKey = safeKey(payload.file_path);
|
|
1155
|
+
const header = `# ${(_a = payload.memory_type) != null ? _a : "instructions"}: ${payload.file_path}
|
|
1156
|
+
`;
|
|
1157
|
+
writeState(
|
|
1158
|
+
`instr_${payload.session_id}_${fileKey}`,
|
|
1159
|
+
header + truncated
|
|
1160
|
+
);
|
|
1161
|
+
}
|
|
1162
|
+
} catch (e) {
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
function gatherInstructions(sessionId) {
|
|
1166
|
+
try {
|
|
1167
|
+
if (!existsSync2(STATE_DIR)) return void 0;
|
|
1168
|
+
const prefix = safeKey(`instr_${sessionId}_`);
|
|
1169
|
+
const files = readdirSync(STATE_DIR).filter((f) => f.startsWith(prefix));
|
|
1170
|
+
if (files.length === 0) return void 0;
|
|
1171
|
+
const parts = [];
|
|
1172
|
+
for (const file of files.sort()) {
|
|
1173
|
+
try {
|
|
1174
|
+
parts.push(readFileSync2(join(STATE_DIR, file), "utf-8"));
|
|
1175
|
+
} catch (e) {
|
|
1176
|
+
continue;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
return parts.length > 0 ? parts.join("\n\n---\n\n") : void 0;
|
|
1180
|
+
} catch (e) {
|
|
1181
|
+
return void 0;
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
function cleanupInstructions(sessionId) {
|
|
1185
|
+
try {
|
|
1186
|
+
if (!existsSync2(STATE_DIR)) return;
|
|
1187
|
+
const prefix = safeKey(`instr_${sessionId}_`);
|
|
1188
|
+
for (const file of readdirSync(STATE_DIR).filter((f) => f.startsWith(prefix))) {
|
|
1189
|
+
try {
|
|
1190
|
+
unlinkSync(join(STATE_DIR, file));
|
|
1191
|
+
} catch (e) {
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
} catch (e) {
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
var APPEND_FLAG = "--append-system-prompt";
|
|
1198
|
+
var APPEND_FILE_FLAG = "--append-system-prompt-file";
|
|
1199
|
+
var MAX_ANCESTOR_DEPTH = 15;
|
|
1200
|
+
var PS_TIMEOUT_MS = 3e3;
|
|
1201
|
+
function appendSystemPromptKey(sessionId) {
|
|
1202
|
+
return `append_sysprompt_${sessionId}`;
|
|
1203
|
+
}
|
|
1204
|
+
function tryCaptureAppendSystemPrompt(sessionId) {
|
|
1205
|
+
try {
|
|
1206
|
+
const content = readAncestorAppendSystemPrompt();
|
|
1207
|
+
if (content) {
|
|
1208
|
+
writeState(appendSystemPromptKey(sessionId), content);
|
|
1209
|
+
}
|
|
1210
|
+
} catch (e) {
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
function readAncestorAppendSystemPrompt() {
|
|
1214
|
+
let pid;
|
|
1215
|
+
try {
|
|
1216
|
+
pid = process.ppid;
|
|
1217
|
+
} catch (e) {
|
|
1218
|
+
return void 0;
|
|
1219
|
+
}
|
|
1220
|
+
if (!pid || pid <= 1) return void 0;
|
|
1221
|
+
for (let depth = 0; depth < MAX_ANCESTOR_DEPTH && pid != null && pid > 1; depth++) {
|
|
1222
|
+
try {
|
|
1223
|
+
const args2 = getProcessArgs(pid);
|
|
1224
|
+
if (args2 && args2.length > 0) {
|
|
1225
|
+
const content = extractAppendSystemPrompt(args2);
|
|
1226
|
+
if (content) return content;
|
|
1227
|
+
}
|
|
1228
|
+
} catch (e) {
|
|
1229
|
+
}
|
|
1230
|
+
try {
|
|
1231
|
+
pid = getParentPid(pid);
|
|
1232
|
+
} catch (e) {
|
|
1233
|
+
break;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return void 0;
|
|
1237
|
+
}
|
|
1238
|
+
function getProcessArgs(pid) {
|
|
1239
|
+
if (process.platform === "linux") {
|
|
1240
|
+
try {
|
|
1241
|
+
const raw = readFileSync2(`/proc/${pid}/cmdline`, "utf-8");
|
|
1242
|
+
const args2 = raw.split("\0").filter(Boolean);
|
|
1243
|
+
return args2.length > 0 ? args2 : void 0;
|
|
1244
|
+
} catch (e) {
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
try {
|
|
1248
|
+
const output = execSync(`ps -ww -o args= -p ${pid}`, {
|
|
1249
|
+
encoding: "utf-8",
|
|
1250
|
+
timeout: PS_TIMEOUT_MS,
|
|
1251
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1252
|
+
});
|
|
1253
|
+
const trimmed = output.trim();
|
|
1254
|
+
if (!trimmed) return void 0;
|
|
1255
|
+
return trimmed.split(/\s+/);
|
|
1256
|
+
} catch (e) {
|
|
1257
|
+
return void 0;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
function getParentPid(pid) {
|
|
1261
|
+
try {
|
|
1262
|
+
const output = execSync(`ps -o ppid= -p ${pid}`, {
|
|
1263
|
+
encoding: "utf-8",
|
|
1264
|
+
timeout: PS_TIMEOUT_MS,
|
|
1265
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
1266
|
+
});
|
|
1267
|
+
const ppid = parseInt(output.trim(), 10);
|
|
1268
|
+
return Number.isFinite(ppid) && ppid > 0 ? ppid : void 0;
|
|
1269
|
+
} catch (e) {
|
|
1270
|
+
return void 0;
|
|
1271
|
+
}
|
|
1272
|
+
}
|
|
1273
|
+
function extractAppendSystemPrompt(args2) {
|
|
1274
|
+
const parts = [];
|
|
1275
|
+
for (let i = 0; i < args2.length; i++) {
|
|
1276
|
+
const arg = args2[i];
|
|
1277
|
+
try {
|
|
1278
|
+
if (arg === APPEND_FILE_FLAG && i + 1 < args2.length) {
|
|
1279
|
+
const filePath = args2[i + 1];
|
|
1280
|
+
i++;
|
|
1281
|
+
try {
|
|
1282
|
+
if (existsSync2(filePath)) {
|
|
1283
|
+
parts.push(readFileSync2(filePath, "utf-8"));
|
|
1284
|
+
}
|
|
1285
|
+
} catch (e) {
|
|
1286
|
+
}
|
|
1287
|
+
continue;
|
|
1288
|
+
}
|
|
1289
|
+
if (arg.startsWith(APPEND_FILE_FLAG + "=")) {
|
|
1290
|
+
const filePath = arg.slice(APPEND_FILE_FLAG.length + 1);
|
|
1291
|
+
try {
|
|
1292
|
+
if (filePath && existsSync2(filePath)) {
|
|
1293
|
+
parts.push(readFileSync2(filePath, "utf-8"));
|
|
1294
|
+
}
|
|
1295
|
+
} catch (e) {
|
|
1296
|
+
}
|
|
1297
|
+
continue;
|
|
1298
|
+
}
|
|
1299
|
+
if (arg === APPEND_FLAG && i + 1 < args2.length) {
|
|
1300
|
+
const valueParts = [];
|
|
1301
|
+
for (let j = i + 1; j < args2.length; j++) {
|
|
1302
|
+
if (args2[j].startsWith("--")) break;
|
|
1303
|
+
valueParts.push(args2[j]);
|
|
1304
|
+
i = j;
|
|
1305
|
+
}
|
|
1306
|
+
if (valueParts.length > 0) {
|
|
1307
|
+
parts.push(valueParts.join(" "));
|
|
1308
|
+
}
|
|
1309
|
+
continue;
|
|
1310
|
+
}
|
|
1311
|
+
if (arg.startsWith(APPEND_FLAG + "=") && !arg.startsWith(APPEND_FILE_FLAG)) {
|
|
1312
|
+
const value = arg.slice(APPEND_FLAG.length + 1);
|
|
1313
|
+
if (value) {
|
|
1314
|
+
parts.push(value);
|
|
1315
|
+
}
|
|
1316
|
+
continue;
|
|
1317
|
+
}
|
|
1318
|
+
} catch (e) {
|
|
1319
|
+
continue;
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
return parts.length > 0 ? parts.join("\n") : void 0;
|
|
1323
|
+
}
|
|
1324
|
+
function readAppendSystemPrompt(sessionId) {
|
|
1325
|
+
try {
|
|
1326
|
+
return readState(appendSystemPromptKey(sessionId)) || void 0;
|
|
1327
|
+
} catch (e) {
|
|
1328
|
+
return void 0;
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
function enrichFromTranscript(payload, eventId, traceShipper) {
|
|
1332
|
+
var _a, _b;
|
|
1333
|
+
if (!payload.transcript_path) {
|
|
1334
|
+
return { summary: void 0, props: {} };
|
|
1335
|
+
}
|
|
1336
|
+
try {
|
|
1337
|
+
const summary = parseTranscript(payload.transcript_path);
|
|
1338
|
+
if (!summary) return { summary: void 0, props: {} };
|
|
1339
|
+
const props = transcriptToProperties(summary);
|
|
1340
|
+
const spanInputTokens = (_a = summary.lastTurnInputTokens) != null ? _a : summary.totalInputTokens;
|
|
1341
|
+
const spanOutputTokens = (_b = summary.lastTurnOutputTokens) != null ? _b : summary.totalOutputTokens;
|
|
1342
|
+
if (summary.model && (spanInputTokens > 0 || spanOutputTokens > 0)) {
|
|
1343
|
+
const parent = getParentContext(payload);
|
|
1344
|
+
const now = nowUnixNanoString();
|
|
1345
|
+
traceShipper.createSpan({
|
|
1346
|
+
name: summary.model,
|
|
1347
|
+
eventId,
|
|
1348
|
+
parent,
|
|
1349
|
+
startTimeUnixNano: now,
|
|
1350
|
+
endTimeUnixNano: now,
|
|
1351
|
+
attributes: [
|
|
1352
|
+
attrString("ai.operationId", "generateText"),
|
|
1353
|
+
attrString("gen_ai.response.model", summary.model),
|
|
1354
|
+
attrString("gen_ai.system", "anthropic"),
|
|
1355
|
+
attrInt("gen_ai.usage.prompt_tokens", spanInputTokens),
|
|
1356
|
+
attrInt("gen_ai.usage.completion_tokens", spanOutputTokens),
|
|
1357
|
+
...summary.lastTurnCacheReadTokens != null && summary.lastTurnCacheReadTokens > 0 ? [attrInt("gen_ai.usage.cache_read_tokens", summary.lastTurnCacheReadTokens)] : []
|
|
1358
|
+
]
|
|
1359
|
+
});
|
|
1360
|
+
}
|
|
1361
|
+
return { summary, props };
|
|
1362
|
+
} catch (e) {
|
|
1363
|
+
return { summary: void 0, props: {} };
|
|
1364
|
+
}
|
|
1010
1365
|
}
|
|
1011
|
-
async function
|
|
1366
|
+
async function handleStopOrFailure(payload, eventId, config, properties, eventShipper, traceShipper, extraProperties) {
|
|
1367
|
+
const { summary, props: transcriptProps } = enrichFromTranscript(payload, eventId, traceShipper);
|
|
1368
|
+
const instructions = gatherInstructions(payload.session_id);
|
|
1369
|
+
const appendSysPrompt = readAppendSystemPrompt(payload.session_id);
|
|
1012
1370
|
await eventShipper.patch(eventId, {
|
|
1013
1371
|
isPending: false,
|
|
1014
1372
|
userId: config.userId,
|
|
1373
|
+
convoId: payload.session_id,
|
|
1015
1374
|
eventName: config.eventName,
|
|
1016
1375
|
output: payload.last_assistant_message,
|
|
1376
|
+
...(summary == null ? void 0 : summary.model) ? { model: summary.model } : {},
|
|
1017
1377
|
properties: {
|
|
1018
1378
|
...properties,
|
|
1019
|
-
|
|
1020
|
-
|
|
1379
|
+
...transcriptProps,
|
|
1380
|
+
...instructions ? { system_instructions: truncate(instructions) } : {},
|
|
1381
|
+
...appendSysPrompt ? { append_system_prompt: truncate(appendSysPrompt) } : {},
|
|
1382
|
+
...extraProperties
|
|
1021
1383
|
}
|
|
1022
1384
|
});
|
|
1023
1385
|
}
|
|
@@ -1034,7 +1396,7 @@ async function handleSessionEnd(payload, eventId, config, properties, eventShipp
|
|
|
1034
1396
|
}
|
|
1035
1397
|
|
|
1036
1398
|
// src/config.ts
|
|
1037
|
-
import { existsSync as
|
|
1399
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1038
1400
|
import { homedir, userInfo } from "os";
|
|
1039
1401
|
import { dirname, join as join2 } from "path";
|
|
1040
1402
|
var CONFIG_PATH = join2(homedir(), ".config", "raindrop", "config.json");
|
|
@@ -1042,8 +1404,8 @@ function loadConfig() {
|
|
|
1042
1404
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
1043
1405
|
let file = {};
|
|
1044
1406
|
try {
|
|
1045
|
-
if (
|
|
1046
|
-
file = JSON.parse(
|
|
1407
|
+
if (existsSync3(CONFIG_PATH)) {
|
|
1408
|
+
file = JSON.parse(readFileSync3(CONFIG_PATH, "utf-8"));
|
|
1047
1409
|
}
|
|
1048
1410
|
} catch (e) {
|
|
1049
1411
|
}
|
|
@@ -1065,6 +1427,17 @@ function loadConfig() {
|
|
|
1065
1427
|
} catch (e) {
|
|
1066
1428
|
}
|
|
1067
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
|
+
}
|
|
1068
1441
|
return {
|
|
1069
1442
|
writeKey: (_c = (_b = process.env["RAINDROP_WRITE_KEY"]) != null ? _b : file.write_key) != null ? _c : "",
|
|
1070
1443
|
endpoint: (_e = (_d = process.env["RAINDROP_API_URL"]) != null ? _d : file.api_url) != null ? _e : "https://api.raindrop.ai/v1",
|
|
@@ -1072,7 +1445,8 @@ function loadConfig() {
|
|
|
1072
1445
|
debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_h = file.debug) != null ? _h : false,
|
|
1073
1446
|
enabled: (_i = file.enabled) != null ? _i : true,
|
|
1074
1447
|
eventName: (_k = (_j = process.env["RAINDROP_EVENT_NAME"]) != null ? _j : file.event_name) != null ? _k : "claude_code_session",
|
|
1075
|
-
customProperties
|
|
1448
|
+
customProperties,
|
|
1449
|
+
selfDiagnostics
|
|
1076
1450
|
};
|
|
1077
1451
|
}
|
|
1078
1452
|
function getConfigPath() {
|
|
@@ -1083,8 +1457,8 @@ function updateConfig(patch) {
|
|
|
1083
1457
|
mkdirSync2(dir, { recursive: true });
|
|
1084
1458
|
let existing = {};
|
|
1085
1459
|
try {
|
|
1086
|
-
if (
|
|
1087
|
-
existing = JSON.parse(
|
|
1460
|
+
if (existsSync3(CONFIG_PATH)) {
|
|
1461
|
+
existing = JSON.parse(readFileSync3(CONFIG_PATH, "utf-8"));
|
|
1088
1462
|
}
|
|
1089
1463
|
} catch (e) {
|
|
1090
1464
|
}
|
|
@@ -1092,7 +1466,7 @@ function updateConfig(patch) {
|
|
|
1092
1466
|
}
|
|
1093
1467
|
|
|
1094
1468
|
// src/local-debugger.ts
|
|
1095
|
-
import { existsSync as
|
|
1469
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1096
1470
|
import { tmpdir as tmpdir2 } from "os";
|
|
1097
1471
|
import { join as join3 } from "path";
|
|
1098
1472
|
var DEFAULT_PORT = 5899;
|
|
@@ -1103,8 +1477,8 @@ var CACHE_FILE = join3(CACHE_DIR, "debugger_cache");
|
|
|
1103
1477
|
var CACHE_TTL_MS = 5e3;
|
|
1104
1478
|
function readCache() {
|
|
1105
1479
|
try {
|
|
1106
|
-
if (!
|
|
1107
|
-
const data = JSON.parse(
|
|
1480
|
+
if (!existsSync4(CACHE_FILE)) return void 0;
|
|
1481
|
+
const data = JSON.parse(readFileSync4(CACHE_FILE, "utf-8"));
|
|
1108
1482
|
if (Date.now() - data.ts > CACHE_TTL_MS) return void 0;
|
|
1109
1483
|
return data;
|
|
1110
1484
|
} catch (e) {
|
|
@@ -1173,7 +1547,7 @@ function mirrorEventToLocalDebugger(baseUrl, payload, debug) {
|
|
|
1173
1547
|
}
|
|
1174
1548
|
|
|
1175
1549
|
// src/hook-handler.ts
|
|
1176
|
-
import { existsSync as
|
|
1550
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
|
|
1177
1551
|
import { join as join4 } from "path";
|
|
1178
1552
|
import { tmpdir as tmpdir3 } from "os";
|
|
1179
1553
|
var STDIN_TIMEOUT_MS = 5e3;
|
|
@@ -1182,8 +1556,8 @@ function getOtlpTraceId(sessionId) {
|
|
|
1182
1556
|
try {
|
|
1183
1557
|
const key = `rootspan_${sessionId}`.replace(/[^a-zA-Z0-9_\-]/g, "_");
|
|
1184
1558
|
const filePath = join4(STATE_DIR2, key);
|
|
1185
|
-
if (!
|
|
1186
|
-
const ctx = JSON.parse(
|
|
1559
|
+
if (!existsSync5(filePath)) return void 0;
|
|
1560
|
+
const ctx = JSON.parse(readFileSync5(filePath, "utf-8"));
|
|
1187
1561
|
if (!ctx.traceIdB64) return void 0;
|
|
1188
1562
|
return Buffer.from(ctx.traceIdB64, "base64").toString("hex");
|
|
1189
1563
|
} catch (e) {
|
|
@@ -1302,11 +1676,11 @@ async function handleHook() {
|
|
|
1302
1676
|
}
|
|
1303
1677
|
|
|
1304
1678
|
// src/setup.ts
|
|
1305
|
-
import { existsSync as
|
|
1679
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
1306
1680
|
import { homedir as homedir2, userInfo as userInfo2 } from "os";
|
|
1307
1681
|
import { dirname as dirname2, join as join5, resolve } from "path";
|
|
1308
1682
|
import { createInterface } from "readline";
|
|
1309
|
-
import { execSync } from "child_process";
|
|
1683
|
+
import { execSync as execSync2 } from "child_process";
|
|
1310
1684
|
function getClaudeSettingsPath(scope, cwd) {
|
|
1311
1685
|
if (scope === "project") {
|
|
1312
1686
|
return resolve(cwd, ".claude", "settings.json");
|
|
@@ -1325,8 +1699,9 @@ var CORE_HOOK_EVENTS = [
|
|
|
1325
1699
|
"SessionEnd"
|
|
1326
1700
|
];
|
|
1327
1701
|
var VERSIONED_HOOK_EVENTS = [
|
|
1328
|
-
{ minVersion: "2.1.78", events: ["StopFailure"] },
|
|
1329
1702
|
{ minVersion: "2.1.76", events: ["PostCompact"] },
|
|
1703
|
+
{ minVersion: "2.1.78", events: ["StopFailure"] },
|
|
1704
|
+
{ minVersion: "2.1.78", events: ["InstructionsLoaded"] },
|
|
1330
1705
|
{ minVersion: "2.1.89", events: ["PermissionDenied"] }
|
|
1331
1706
|
];
|
|
1332
1707
|
function parseVersion(version) {
|
|
@@ -1345,7 +1720,7 @@ function isVersionAtLeast(current, required) {
|
|
|
1345
1720
|
}
|
|
1346
1721
|
function detectClaudeCodeVersion() {
|
|
1347
1722
|
try {
|
|
1348
|
-
const output =
|
|
1723
|
+
const output = execSync2("claude --version", {
|
|
1349
1724
|
encoding: "utf-8",
|
|
1350
1725
|
timeout: 5e3,
|
|
1351
1726
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -1424,8 +1799,8 @@ async function runSetup(args2) {
|
|
|
1424
1799
|
mkdirSync4(configDir, { recursive: true });
|
|
1425
1800
|
let existingConfig = {};
|
|
1426
1801
|
try {
|
|
1427
|
-
if (
|
|
1428
|
-
existingConfig = JSON.parse(
|
|
1802
|
+
if (existsSync6(configPath)) {
|
|
1803
|
+
existingConfig = JSON.parse(readFileSync6(configPath, "utf-8"));
|
|
1429
1804
|
}
|
|
1430
1805
|
} catch (e) {
|
|
1431
1806
|
}
|
|
@@ -1449,9 +1824,9 @@ async function runSetup(args2) {
|
|
|
1449
1824
|
const settingsDir = dirname2(settingsPath);
|
|
1450
1825
|
mkdirSync4(settingsDir, { recursive: true });
|
|
1451
1826
|
let settings = {};
|
|
1452
|
-
if (
|
|
1827
|
+
if (existsSync6(settingsPath)) {
|
|
1453
1828
|
try {
|
|
1454
|
-
settings = JSON.parse(
|
|
1829
|
+
settings = JSON.parse(readFileSync6(settingsPath, "utf-8"));
|
|
1455
1830
|
} catch (e) {
|
|
1456
1831
|
console.warn(` Warning: could not parse ${settingsPath}, creating fresh`);
|
|
1457
1832
|
settings = {};
|
|
@@ -1479,9 +1854,20 @@ async function runSetup(args2) {
|
|
|
1479
1854
|
settings["hooks"] = existingHooks;
|
|
1480
1855
|
writeFileSync4(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
1481
1856
|
console.log(` Updated Claude Code hooks in ${settingsPath} (${scopeLabel})`);
|
|
1857
|
+
try {
|
|
1858
|
+
const mcpList = execSync2("claude mcp list", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] });
|
|
1859
|
+
if (!mcpList.includes("raindrop-diagnostics")) {
|
|
1860
|
+
execSync2(
|
|
1861
|
+
`claude mcp add --transport stdio --scope ${scope === "project" ? "project" : "user"} raindrop-diagnostics -- raindrop-claude-code mcp-serve`,
|
|
1862
|
+
{ stdio: "ignore" }
|
|
1863
|
+
);
|
|
1864
|
+
console.log(` Registered self-diagnostics MCP server`);
|
|
1865
|
+
}
|
|
1866
|
+
} catch (e) {
|
|
1867
|
+
}
|
|
1482
1868
|
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
1483
1869
|
try {
|
|
1484
|
-
|
|
1870
|
+
execSync2(`${whichCmd} raindrop-claude-code`, { stdio: "ignore" });
|
|
1485
1871
|
} catch (e) {
|
|
1486
1872
|
console.log(`
|
|
1487
1873
|
Warning: 'raindrop-claude-code' is not in your PATH.
|
|
@@ -1508,6 +1894,289 @@ async function runSetup(args2) {
|
|
|
1508
1894
|
}
|
|
1509
1895
|
}
|
|
1510
1896
|
|
|
1897
|
+
// src/mcp-serve.ts
|
|
1898
|
+
import { createInterface as createInterface2 } from "readline";
|
|
1899
|
+
import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync } from "fs";
|
|
1900
|
+
import { join as join6 } from "path";
|
|
1901
|
+
import { tmpdir as tmpdir4 } from "os";
|
|
1902
|
+
var DEFAULT_SIGNALS = {
|
|
1903
|
+
missing_context: {
|
|
1904
|
+
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.",
|
|
1905
|
+
sentiment: "NEGATIVE"
|
|
1906
|
+
},
|
|
1907
|
+
repeatedly_broken_tool: {
|
|
1908
|
+
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.",
|
|
1909
|
+
sentiment: "NEGATIVE"
|
|
1910
|
+
},
|
|
1911
|
+
capability_gap: {
|
|
1912
|
+
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.",
|
|
1913
|
+
sentiment: "NEGATIVE"
|
|
1914
|
+
},
|
|
1915
|
+
complete_task_failure: {
|
|
1916
|
+
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.",
|
|
1917
|
+
sentiment: "NEGATIVE"
|
|
1918
|
+
}
|
|
1919
|
+
};
|
|
1920
|
+
var NOTEWORTHY_KEY = "noteworthy";
|
|
1921
|
+
var NOTEWORTHY_DEFAULT_DESCRIPTION = "Only when no specific category applies: flag that this turn is noteworthy for developer review.";
|
|
1922
|
+
function normalizeSignals(custom) {
|
|
1923
|
+
let base;
|
|
1924
|
+
if (!custom || Object.keys(custom).length === 0) {
|
|
1925
|
+
base = { ...DEFAULT_SIGNALS };
|
|
1926
|
+
} else {
|
|
1927
|
+
const validated = {};
|
|
1928
|
+
for (const [key, def] of Object.entries(custom)) {
|
|
1929
|
+
const k = key.trim();
|
|
1930
|
+
if (!k || k === NOTEWORTHY_KEY) continue;
|
|
1931
|
+
if (!def || typeof def !== "object") continue;
|
|
1932
|
+
const desc = typeof def.description === "string" ? def.description.trim() : "";
|
|
1933
|
+
if (!desc) continue;
|
|
1934
|
+
const sentiment = def.sentiment;
|
|
1935
|
+
validated[k] = {
|
|
1936
|
+
description: desc,
|
|
1937
|
+
...sentiment === "POSITIVE" || sentiment === "NEGATIVE" ? { sentiment } : {}
|
|
1938
|
+
};
|
|
1939
|
+
}
|
|
1940
|
+
base = Object.keys(validated).length > 0 ? validated : { ...DEFAULT_SIGNALS };
|
|
1941
|
+
}
|
|
1942
|
+
const customNoteworthy = custom == null ? void 0 : custom[NOTEWORTHY_KEY];
|
|
1943
|
+
base[NOTEWORTHY_KEY] = {
|
|
1944
|
+
description: typeof (customNoteworthy == null ? void 0 : customNoteworthy.description) === "string" && customNoteworthy.description.trim() ? customNoteworthy.description.trim() : NOTEWORTHY_DEFAULT_DESCRIPTION,
|
|
1945
|
+
...(customNoteworthy == null ? void 0 : customNoteworthy.sentiment) === "POSITIVE" || (customNoteworthy == null ? void 0 : customNoteworthy.sentiment) === "NEGATIVE" ? { sentiment: customNoteworthy.sentiment } : {}
|
|
1946
|
+
};
|
|
1947
|
+
return base;
|
|
1948
|
+
}
|
|
1949
|
+
function resolveToolConfig(diagConfig) {
|
|
1950
|
+
var _a;
|
|
1951
|
+
const signals = normalizeSignals(diagConfig == null ? void 0 : diagConfig.signals);
|
|
1952
|
+
const categoryKeys = Object.keys(signals);
|
|
1953
|
+
const toolName = ((_a = diagConfig == null ? void 0 : diagConfig.toolName) == null ? void 0 : _a.trim()) || "__raindrop_report";
|
|
1954
|
+
const toolDescription = buildToolDescription(signals, categoryKeys, diagConfig == null ? void 0 : diagConfig.guidance);
|
|
1955
|
+
return { signals, categoryKeys, toolName, toolDescription };
|
|
1956
|
+
}
|
|
1957
|
+
function buildToolDescription(signals, keys, guidance) {
|
|
1958
|
+
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.";
|
|
1959
|
+
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.";
|
|
1960
|
+
const categoryList = keys.map((key) => {
|
|
1961
|
+
const def = signals[key];
|
|
1962
|
+
const tag = def.sentiment ? ` [${def.sentiment.toLowerCase()}]` : "";
|
|
1963
|
+
return `- ${key}: ${def.description}${tag}`;
|
|
1964
|
+
}).join("\n");
|
|
1965
|
+
const guidanceBlock = (guidance == null ? void 0 : guidance.trim()) ? `
|
|
1966
|
+
Additional guidance: ${guidance.trim()}
|
|
1967
|
+
` : "";
|
|
1968
|
+
return `${preamble}
|
|
1969
|
+
|
|
1970
|
+
${rules}${guidanceBlock}
|
|
1971
|
+
|
|
1972
|
+
Categories:
|
|
1973
|
+
${categoryList}`;
|
|
1974
|
+
}
|
|
1975
|
+
var activeSignals = { ...DEFAULT_SIGNALS, [NOTEWORTHY_KEY]: { description: NOTEWORTHY_DEFAULT_DESCRIPTION } };
|
|
1976
|
+
var activeCategoryKeys = Object.keys(activeSignals);
|
|
1977
|
+
var activeToolName = "__raindrop_report";
|
|
1978
|
+
var DEFAULT_CATEGORY_KEYS = Object.keys(DEFAULT_SIGNALS).concat(NOTEWORTHY_KEY);
|
|
1979
|
+
var STATE_DIR3 = join6(tmpdir4(), "raindrop-claude-code");
|
|
1980
|
+
function resolveCurrentEventId() {
|
|
1981
|
+
try {
|
|
1982
|
+
if (!existsSync7(STATE_DIR3)) return void 0;
|
|
1983
|
+
const files = readdirSync2(STATE_DIR3).filter((f) => f.startsWith("event_"));
|
|
1984
|
+
if (files.length === 0) return void 0;
|
|
1985
|
+
let newest;
|
|
1986
|
+
for (const file of files) {
|
|
1987
|
+
try {
|
|
1988
|
+
const full = join6(STATE_DIR3, file);
|
|
1989
|
+
const st = statSync(full);
|
|
1990
|
+
if (!newest || st.mtimeMs > newest.mtime) {
|
|
1991
|
+
newest = { path: full, mtime: st.mtimeMs };
|
|
1992
|
+
}
|
|
1993
|
+
} catch (e) {
|
|
1994
|
+
continue;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
if (!newest) return void 0;
|
|
1998
|
+
return readFileSync7(newest.path, "utf-8").trim() || void 0;
|
|
1999
|
+
} catch (e) {
|
|
2000
|
+
return void 0;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
async function executeTool(args2) {
|
|
2004
|
+
const category = typeof args2["category"] === "string" ? args2["category"] : "";
|
|
2005
|
+
const detail = typeof args2["detail"] === "string" ? args2["detail"] : "";
|
|
2006
|
+
if (!category || !activeSignals[category]) {
|
|
2007
|
+
return {
|
|
2008
|
+
content: [{ type: "text", text: `Invalid category: ${category}. Valid: ${activeCategoryKeys.join(", ")}` }],
|
|
2009
|
+
isError: true
|
|
2010
|
+
};
|
|
2011
|
+
}
|
|
2012
|
+
if (!detail.trim()) {
|
|
2013
|
+
return {
|
|
2014
|
+
content: [{ type: "text", text: "Detail is required." }],
|
|
2015
|
+
isError: true
|
|
2016
|
+
};
|
|
2017
|
+
}
|
|
2018
|
+
const config = loadConfig();
|
|
2019
|
+
if (!config.enabled) {
|
|
2020
|
+
return { content: [{ type: "text", text: "Signal noted (hooks disabled)." }] };
|
|
2021
|
+
}
|
|
2022
|
+
if (!config.writeKey) {
|
|
2023
|
+
return { content: [{ type: "text", text: "Signal noted (no write key configured)." }] };
|
|
2024
|
+
}
|
|
2025
|
+
const eventId = resolveCurrentEventId();
|
|
2026
|
+
if (!eventId) {
|
|
2027
|
+
return { content: [{ type: "text", text: "Signal noted (no active event found)." }] };
|
|
2028
|
+
}
|
|
2029
|
+
const shipper = new EventShipper2({
|
|
2030
|
+
writeKey: config.writeKey,
|
|
2031
|
+
endpoint: config.endpoint,
|
|
2032
|
+
debug: false,
|
|
2033
|
+
enabled: true
|
|
2034
|
+
});
|
|
2035
|
+
try {
|
|
2036
|
+
const signalDef = activeSignals[category];
|
|
2037
|
+
const isNoteworthy = category === NOTEWORTHY_KEY;
|
|
2038
|
+
await shipper.trackSignal({
|
|
2039
|
+
eventId,
|
|
2040
|
+
name: `self diagnostics - ${category}`,
|
|
2041
|
+
type: isNoteworthy ? "agent_internal" : "agent",
|
|
2042
|
+
...signalDef.sentiment ? { sentiment: signalDef.sentiment } : {},
|
|
2043
|
+
properties: isNoteworthy ? {
|
|
2044
|
+
source: "agent_flag_event_tool",
|
|
2045
|
+
reason: detail,
|
|
2046
|
+
severity: "medium",
|
|
2047
|
+
sdk: PACKAGE_NAME,
|
|
2048
|
+
sdk_version: PACKAGE_VERSION
|
|
2049
|
+
} : {
|
|
2050
|
+
source: "agent_reporting_tool",
|
|
2051
|
+
category,
|
|
2052
|
+
signal_description: signalDef.description,
|
|
2053
|
+
detail,
|
|
2054
|
+
sdk: PACKAGE_NAME,
|
|
2055
|
+
sdk_version: PACKAGE_VERSION
|
|
2056
|
+
}
|
|
2057
|
+
});
|
|
2058
|
+
await shipper.shutdown();
|
|
2059
|
+
} catch (e) {
|
|
2060
|
+
}
|
|
2061
|
+
return { content: [{ type: "text", text: "Signal recorded." }] };
|
|
2062
|
+
}
|
|
2063
|
+
function sendResponse(id, result) {
|
|
2064
|
+
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: id != null ? id : null, result }) + "\n");
|
|
2065
|
+
}
|
|
2066
|
+
function sendError(id, code, message) {
|
|
2067
|
+
process.stdout.write(JSON.stringify({ jsonrpc: "2.0", id: id != null ? id : null, error: { code, message } }) + "\n");
|
|
2068
|
+
}
|
|
2069
|
+
function buildToolSchema(toolName, toolDescription, categoryKeys) {
|
|
2070
|
+
return {
|
|
2071
|
+
name: toolName,
|
|
2072
|
+
description: toolDescription,
|
|
2073
|
+
inputSchema: {
|
|
2074
|
+
type: "object",
|
|
2075
|
+
properties: {
|
|
2076
|
+
category: {
|
|
2077
|
+
type: "string",
|
|
2078
|
+
enum: categoryKeys,
|
|
2079
|
+
description: "The category of issue detected"
|
|
2080
|
+
},
|
|
2081
|
+
detail: {
|
|
2082
|
+
type: "string",
|
|
2083
|
+
description: "A factual one-sentence description of the issue"
|
|
2084
|
+
}
|
|
2085
|
+
},
|
|
2086
|
+
required: ["category", "detail"]
|
|
2087
|
+
}
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
var TOOL_SCHEMA = buildToolSchema(
|
|
2091
|
+
activeToolName,
|
|
2092
|
+
buildToolDescription(activeSignals, activeCategoryKeys),
|
|
2093
|
+
activeCategoryKeys
|
|
2094
|
+
);
|
|
2095
|
+
async function startMcpServer() {
|
|
2096
|
+
globalThis.console = new console.Console(process.stderr, process.stderr);
|
|
2097
|
+
const config = loadConfig();
|
|
2098
|
+
const resolved = resolveToolConfig(config.selfDiagnostics);
|
|
2099
|
+
activeSignals = resolved.signals;
|
|
2100
|
+
activeCategoryKeys = resolved.categoryKeys;
|
|
2101
|
+
activeToolName = resolved.toolName;
|
|
2102
|
+
const toolSchema = buildToolSchema(resolved.toolName, resolved.toolDescription, resolved.categoryKeys);
|
|
2103
|
+
const rl = createInterface2({ input: process.stdin });
|
|
2104
|
+
const inflight = /* @__PURE__ */ new Set();
|
|
2105
|
+
rl.on("line", (line) => {
|
|
2106
|
+
const promise = handleLine(line, toolSchema);
|
|
2107
|
+
inflight.add(promise);
|
|
2108
|
+
promise.finally(() => inflight.delete(promise));
|
|
2109
|
+
});
|
|
2110
|
+
rl.on("close", async () => {
|
|
2111
|
+
await Promise.allSettled(inflight);
|
|
2112
|
+
process.exit(0);
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2115
|
+
async function handleLine(line, toolSchema) {
|
|
2116
|
+
var _a, _b;
|
|
2117
|
+
let req;
|
|
2118
|
+
try {
|
|
2119
|
+
const parsed = JSON.parse(line);
|
|
2120
|
+
if (!parsed || typeof parsed !== "object") {
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
if (typeof parsed.method !== "string") {
|
|
2124
|
+
if (parsed.id !== void 0) {
|
|
2125
|
+
sendError(parsed.id, -32600, "Invalid Request: missing or non-string method");
|
|
2126
|
+
}
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
req = parsed;
|
|
2130
|
+
} catch (e) {
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
try {
|
|
2134
|
+
switch (req.method) {
|
|
2135
|
+
case "initialize":
|
|
2136
|
+
sendResponse(req.id, {
|
|
2137
|
+
protocolVersion: "2024-11-05",
|
|
2138
|
+
capabilities: { tools: {} },
|
|
2139
|
+
serverInfo: { name: PACKAGE_NAME, version: PACKAGE_VERSION }
|
|
2140
|
+
});
|
|
2141
|
+
break;
|
|
2142
|
+
case "notifications/initialized":
|
|
2143
|
+
break;
|
|
2144
|
+
case "tools/list":
|
|
2145
|
+
sendResponse(req.id, { tools: [toolSchema] });
|
|
2146
|
+
break;
|
|
2147
|
+
case "tools/call": {
|
|
2148
|
+
const params = (_a = req.params) != null ? _a : {};
|
|
2149
|
+
const toolName = params["name"];
|
|
2150
|
+
if (toolName !== activeToolName) {
|
|
2151
|
+
sendResponse(req.id, {
|
|
2152
|
+
content: [{ type: "text", text: `Unknown tool: ${toolName}` }],
|
|
2153
|
+
isError: true
|
|
2154
|
+
});
|
|
2155
|
+
break;
|
|
2156
|
+
}
|
|
2157
|
+
const toolArgs = (_b = params["arguments"]) != null ? _b : {};
|
|
2158
|
+
const result = await executeTool(toolArgs);
|
|
2159
|
+
sendResponse(req.id, result);
|
|
2160
|
+
break;
|
|
2161
|
+
}
|
|
2162
|
+
case "ping":
|
|
2163
|
+
sendResponse(req.id, {});
|
|
2164
|
+
break;
|
|
2165
|
+
default:
|
|
2166
|
+
if (req.id !== void 0) {
|
|
2167
|
+
sendError(req.id, -32601, `Method not found: ${req.method}`);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
} catch (err) {
|
|
2171
|
+
try {
|
|
2172
|
+
if (req.id !== void 0) {
|
|
2173
|
+
sendError(req.id, -32603, err instanceof Error ? err.message : String(err));
|
|
2174
|
+
}
|
|
2175
|
+
} catch (e) {
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
|
|
1511
2180
|
// src/cli.ts
|
|
1512
2181
|
var args = process.argv.slice(2);
|
|
1513
2182
|
var command = args[0];
|
|
@@ -1533,6 +2202,10 @@ async function main() {
|
|
|
1533
2202
|
await handleHook();
|
|
1534
2203
|
break;
|
|
1535
2204
|
}
|
|
2205
|
+
case "mcp-serve": {
|
|
2206
|
+
await startMcpServer();
|
|
2207
|
+
break;
|
|
2208
|
+
}
|
|
1536
2209
|
case "status": {
|
|
1537
2210
|
const config = loadConfig();
|
|
1538
2211
|
const result = await detectLocalDebugger(config.debug);
|
|
@@ -1582,6 +2255,7 @@ async function main() {
|
|
|
1582
2255
|
--local-only Install hooks without a write key (local debugger only)
|
|
1583
2256
|
|
|
1584
2257
|
hook Handle a Claude Code hook event (reads JSON from stdin)
|
|
2258
|
+
mcp-serve Start the self-diagnostics MCP server (stdio)
|
|
1585
2259
|
status Check local debugger connectivity
|
|
1586
2260
|
enable Enable Raindrop hooks
|
|
1587
2261
|
disable Disable Raindrop hooks
|
|
@@ -1609,5 +2283,5 @@ async function main() {
|
|
|
1609
2283
|
}
|
|
1610
2284
|
main().catch((err) => {
|
|
1611
2285
|
console.error(`[raindrop-ai/claude-code] fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
1612
|
-
process.exit(command === "hook" ? 0 : 1);
|
|
2286
|
+
process.exit(command === "hook" || command === "mcp-serve" ? 0 : 1);
|
|
1613
2287
|
});
|