@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/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.4";
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 (!existsSync(filePath)) return void 0;
738
- return readFileSync(filePath, "utf-8");
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 handleStop(payload, getCurrentEventId(payload.session_id), config, baseProperties, eventShipper);
959
+ await handleStopOrFailure(payload, getCurrentEventId(payload.session_id), config, baseProperties, eventShipper, traceShipper);
824
960
  break;
825
961
  case "StopFailure":
826
- await handleStopFailure(payload, getCurrentEventId(payload.session_id), config, baseProperties, eventShipper);
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
- async function handleStop(payload, eventId, config, properties, eventShipper) {
1003
- await eventShipper.patch(eventId, {
1004
- isPending: false,
1005
- userId: config.userId,
1006
- eventName: config.eventName,
1007
- output: payload.last_assistant_message,
1008
- properties
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 handleStopFailure(payload, eventId, config, properties, eventShipper) {
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
- error: payload.error,
1020
- error_details: payload.error_details
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 existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
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 (existsSync2(CONFIG_PATH)) {
1046
- file = JSON.parse(readFileSync2(CONFIG_PATH, "utf-8"));
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 (existsSync2(CONFIG_PATH)) {
1087
- existing = JSON.parse(readFileSync2(CONFIG_PATH, "utf-8"));
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 existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
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 (!existsSync3(CACHE_FILE)) return void 0;
1107
- const data = JSON.parse(readFileSync3(CACHE_FILE, "utf-8"));
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 existsSync4, readFileSync as readFileSync4 } from "fs";
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 (!existsSync4(filePath)) return void 0;
1186
- const ctx = JSON.parse(readFileSync4(filePath, "utf-8"));
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 existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
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 = execSync("claude --version", {
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 (existsSync5(configPath)) {
1428
- existingConfig = JSON.parse(readFileSync5(configPath, "utf-8"));
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 (existsSync5(settingsPath)) {
1827
+ if (existsSync6(settingsPath)) {
1453
1828
  try {
1454
- settings = JSON.parse(readFileSync5(settingsPath, "utf-8"));
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
- execSync(`${whichCmd} raindrop-claude-code`, { stdio: "ignore" });
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
  });