@raindrop-ai/claude-code 0.0.1
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 +30 -0
- package/dist/cli.js +1288 -0
- package/dist/index.cjs +1039 -0
- package/dist/index.d.cts +273 -0
- package/dist/index.d.ts +273 -0
- package/dist/index.js +1006 -0
- package/package.json +62 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1288 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/event-mapper.ts
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "fs";
|
|
5
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
6
|
+
import { tmpdir } from "os";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
|
|
9
|
+
// ../core/dist/chunk-H6VSZSLN.js
|
|
10
|
+
function getCrypto() {
|
|
11
|
+
const c = globalThis.crypto;
|
|
12
|
+
return c;
|
|
13
|
+
}
|
|
14
|
+
function randomBytes(length) {
|
|
15
|
+
const cryptoObj = getCrypto();
|
|
16
|
+
const out = new Uint8Array(length);
|
|
17
|
+
if (cryptoObj && typeof cryptoObj.getRandomValues === "function") {
|
|
18
|
+
cryptoObj.getRandomValues(out);
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
for (let i = 0; i < out.length; i++) out[i] = Math.floor(Math.random() * 256);
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
function base64Encode(bytes) {
|
|
25
|
+
const maybeBuffer = globalThis.Buffer;
|
|
26
|
+
if (maybeBuffer) {
|
|
27
|
+
return maybeBuffer.from(bytes).toString("base64");
|
|
28
|
+
}
|
|
29
|
+
let binary = "";
|
|
30
|
+
for (let i2 = 0; i2 < bytes.length; i2++) {
|
|
31
|
+
binary += String.fromCharCode(bytes[i2]);
|
|
32
|
+
}
|
|
33
|
+
const btoaFn = globalThis.btoa;
|
|
34
|
+
if (typeof btoaFn === "function") return btoaFn(binary);
|
|
35
|
+
const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
36
|
+
let out = "";
|
|
37
|
+
let i = 0;
|
|
38
|
+
while (i < binary.length) {
|
|
39
|
+
const c1 = binary.charCodeAt(i++) & 255;
|
|
40
|
+
const c2 = i < binary.length ? binary.charCodeAt(i++) & 255 : NaN;
|
|
41
|
+
const c3 = i < binary.length ? binary.charCodeAt(i++) & 255 : NaN;
|
|
42
|
+
const e1 = c1 >> 2;
|
|
43
|
+
const e2 = (c1 & 3) << 4 | (Number.isNaN(c2) ? 0 : c2 >> 4);
|
|
44
|
+
const e3 = Number.isNaN(c2) ? 64 : (c2 & 15) << 2 | (Number.isNaN(c3) ? 0 : c3 >> 6);
|
|
45
|
+
const e4 = Number.isNaN(c3) ? 64 : c3 & 63;
|
|
46
|
+
out += alphabet.charAt(e1);
|
|
47
|
+
out += alphabet.charAt(e2);
|
|
48
|
+
out += e3 === 64 ? "=" : alphabet.charAt(e3);
|
|
49
|
+
out += e4 === 64 ? "=" : alphabet.charAt(e4);
|
|
50
|
+
}
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
function wait(ms) {
|
|
54
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
55
|
+
}
|
|
56
|
+
function formatEndpoint(endpoint) {
|
|
57
|
+
if (!endpoint) return void 0;
|
|
58
|
+
return endpoint.endsWith("/") ? endpoint : `${endpoint}/`;
|
|
59
|
+
}
|
|
60
|
+
function parseRetryAfter(headers) {
|
|
61
|
+
var _a;
|
|
62
|
+
const value = (_a = headers.get("Retry-After")) != null ? _a : headers.get("retry-after");
|
|
63
|
+
if (!value) return void 0;
|
|
64
|
+
const asNumber = Number(value);
|
|
65
|
+
if (value.trim() !== "" && !Number.isNaN(asNumber)) return asNumber * 1e3;
|
|
66
|
+
const asDate = new Date(value).getTime();
|
|
67
|
+
if (!Number.isNaN(asDate)) {
|
|
68
|
+
const delta = asDate - Date.now();
|
|
69
|
+
return delta > 0 ? delta : 0;
|
|
70
|
+
}
|
|
71
|
+
return void 0;
|
|
72
|
+
}
|
|
73
|
+
function getRetryDelayMs(attemptNumber, previousError) {
|
|
74
|
+
if (previousError && typeof previousError === "object" && previousError !== null && "retryAfterMs" in previousError) {
|
|
75
|
+
const v = previousError.retryAfterMs;
|
|
76
|
+
if (typeof v === "number") return Math.max(0, v);
|
|
77
|
+
}
|
|
78
|
+
if (attemptNumber <= 1) return 0;
|
|
79
|
+
const base = 500;
|
|
80
|
+
const factor = Math.pow(2, attemptNumber - 2);
|
|
81
|
+
return base * factor;
|
|
82
|
+
}
|
|
83
|
+
async function withRetry(operation, opName, opts) {
|
|
84
|
+
const prefix = opts.sdkName ? `[raindrop-ai/${opts.sdkName}]` : "[raindrop-ai/core]";
|
|
85
|
+
let lastError = void 0;
|
|
86
|
+
for (let attemptNumber = 1; attemptNumber <= opts.maxAttempts; attemptNumber++) {
|
|
87
|
+
if (attemptNumber > 1) {
|
|
88
|
+
const delay = getRetryDelayMs(attemptNumber, lastError);
|
|
89
|
+
if (opts.debug) {
|
|
90
|
+
console.warn(
|
|
91
|
+
`${prefix} ${opName} retry ${attemptNumber}/${opts.maxAttempts} in ${delay}ms`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
if (delay > 0) await wait(delay);
|
|
95
|
+
} else if (opts.debug) {
|
|
96
|
+
console.log(`${prefix} ${opName} attempt ${attemptNumber}/${opts.maxAttempts}`);
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
return await operation();
|
|
100
|
+
} catch (err) {
|
|
101
|
+
lastError = err;
|
|
102
|
+
if (opts.debug) {
|
|
103
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
104
|
+
console.warn(
|
|
105
|
+
`${prefix} ${opName} attempt ${attemptNumber} failed: ${msg}${attemptNumber === opts.maxAttempts ? " (no more retries)" : ""}`
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
if (lastError && typeof lastError === "object" && "retryable" in lastError && !lastError.retryable)
|
|
109
|
+
break;
|
|
110
|
+
if (attemptNumber === opts.maxAttempts) break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
114
|
+
}
|
|
115
|
+
async function postJson(url, body, headers, opts) {
|
|
116
|
+
const opName = `POST ${url}`;
|
|
117
|
+
await withRetry(
|
|
118
|
+
async () => {
|
|
119
|
+
const resp = await fetch(url, {
|
|
120
|
+
method: "POST",
|
|
121
|
+
headers: {
|
|
122
|
+
"Content-Type": "application/json",
|
|
123
|
+
...headers
|
|
124
|
+
},
|
|
125
|
+
body: JSON.stringify(body)
|
|
126
|
+
});
|
|
127
|
+
if (!resp.ok) {
|
|
128
|
+
const text = await resp.text().catch(() => "");
|
|
129
|
+
const err = new Error(
|
|
130
|
+
`HTTP ${resp.status} ${resp.statusText}${text ? `: ${text}` : ""}`
|
|
131
|
+
);
|
|
132
|
+
const retryAfterMs = parseRetryAfter(resp.headers);
|
|
133
|
+
if (typeof retryAfterMs === "number") err.retryAfterMs = retryAfterMs;
|
|
134
|
+
err.retryable = resp.status === 429 || resp.status >= 500;
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
opName,
|
|
139
|
+
opts
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
var SpanStatusCode = {
|
|
143
|
+
UNSET: 0,
|
|
144
|
+
OK: 1,
|
|
145
|
+
ERROR: 2
|
|
146
|
+
};
|
|
147
|
+
function createSpanIds(parent) {
|
|
148
|
+
const traceId = parent ? parent.traceIdB64 : base64Encode(randomBytes(16));
|
|
149
|
+
const spanId = base64Encode(randomBytes(8));
|
|
150
|
+
return {
|
|
151
|
+
traceIdB64: traceId,
|
|
152
|
+
spanIdB64: spanId,
|
|
153
|
+
parentSpanIdB64: parent ? parent.spanIdB64 : void 0
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function nowUnixNanoString() {
|
|
157
|
+
return Date.now().toString() + "000000";
|
|
158
|
+
}
|
|
159
|
+
function attrString(key, value) {
|
|
160
|
+
if (value === void 0) return void 0;
|
|
161
|
+
return { key, value: { stringValue: value } };
|
|
162
|
+
}
|
|
163
|
+
function attrInt(key, value) {
|
|
164
|
+
if (value === void 0) return void 0;
|
|
165
|
+
if (!Number.isFinite(value)) return void 0;
|
|
166
|
+
return { key, value: { intValue: String(Math.trunc(value)) } };
|
|
167
|
+
}
|
|
168
|
+
function buildOtlpSpan(args2) {
|
|
169
|
+
const attrs = args2.attributes.filter((x) => x !== void 0);
|
|
170
|
+
const span = {
|
|
171
|
+
traceId: args2.ids.traceIdB64,
|
|
172
|
+
spanId: args2.ids.spanIdB64,
|
|
173
|
+
name: args2.name,
|
|
174
|
+
startTimeUnixNano: args2.startTimeUnixNano,
|
|
175
|
+
endTimeUnixNano: args2.endTimeUnixNano
|
|
176
|
+
};
|
|
177
|
+
if (args2.ids.parentSpanIdB64) span.parentSpanId = args2.ids.parentSpanIdB64;
|
|
178
|
+
if (attrs.length) span.attributes = attrs;
|
|
179
|
+
if (args2.status) span.status = args2.status;
|
|
180
|
+
return span;
|
|
181
|
+
}
|
|
182
|
+
function buildExportTraceServiceRequest(spans, serviceName = "raindrop.core", serviceVersion = "0.0.0") {
|
|
183
|
+
return {
|
|
184
|
+
resourceSpans: [
|
|
185
|
+
{
|
|
186
|
+
resource: {
|
|
187
|
+
attributes: [{ key: "service.name", value: { stringValue: serviceName } }]
|
|
188
|
+
},
|
|
189
|
+
scopeSpans: [
|
|
190
|
+
{
|
|
191
|
+
scope: { name: serviceName, version: serviceVersion },
|
|
192
|
+
spans
|
|
193
|
+
}
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
]
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
function mergePatches(target, source) {
|
|
200
|
+
var _a, _b, _c, _d;
|
|
201
|
+
const out = { ...target, ...source };
|
|
202
|
+
if (target.properties || source.properties) {
|
|
203
|
+
out.properties = { ...(_a = target.properties) != null ? _a : {}, ...(_b = source.properties) != null ? _b : {} };
|
|
204
|
+
}
|
|
205
|
+
if (target.attachments || source.attachments) {
|
|
206
|
+
out.attachments = [...(_c = target.attachments) != null ? _c : [], ...(_d = source.attachments) != null ? _d : []];
|
|
207
|
+
}
|
|
208
|
+
return out;
|
|
209
|
+
}
|
|
210
|
+
var EventShipper = class {
|
|
211
|
+
constructor(opts) {
|
|
212
|
+
this.buffers = /* @__PURE__ */ new Map();
|
|
213
|
+
this.sticky = /* @__PURE__ */ new Map();
|
|
214
|
+
this.timers = /* @__PURE__ */ new Map();
|
|
215
|
+
this.inFlight = /* @__PURE__ */ new Set();
|
|
216
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
217
|
+
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
218
|
+
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
219
|
+
this.enabled = opts.enabled !== false;
|
|
220
|
+
this.debug = opts.debug;
|
|
221
|
+
this.partialFlushMs = (_c = opts.partialFlushMs) != null ? _c : 1e3;
|
|
222
|
+
this.sdkName = (_d = opts.sdkName) != null ? _d : "core";
|
|
223
|
+
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
224
|
+
this.defaultEventName = (_e = opts.defaultEventName) != null ? _e : "ai_generation";
|
|
225
|
+
const isNode = typeof process !== "undefined" && typeof process.version === "string";
|
|
226
|
+
this.context = {
|
|
227
|
+
library: {
|
|
228
|
+
name: (_f = opts.libraryName) != null ? _f : "@raindrop-ai/core",
|
|
229
|
+
version: (_g = opts.libraryVersion) != null ? _g : "0.0.0"
|
|
230
|
+
},
|
|
231
|
+
metadata: {
|
|
232
|
+
jsRuntime: isNode ? "node" : "web",
|
|
233
|
+
...isNode ? { nodeVersion: process.version } : {}
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
isDebugEnabled() {
|
|
238
|
+
return this.debug;
|
|
239
|
+
}
|
|
240
|
+
authHeaders() {
|
|
241
|
+
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
242
|
+
}
|
|
243
|
+
async patch(eventId, patch) {
|
|
244
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k;
|
|
245
|
+
if (!this.enabled) return;
|
|
246
|
+
if (!eventId || !eventId.trim()) return;
|
|
247
|
+
if (this.debug) {
|
|
248
|
+
console.log(`${this.prefix} queue patch`, {
|
|
249
|
+
eventId,
|
|
250
|
+
userId: patch.userId,
|
|
251
|
+
convoId: patch.convoId,
|
|
252
|
+
eventName: patch.eventName,
|
|
253
|
+
hasInput: typeof patch.input === "string" && patch.input.length > 0,
|
|
254
|
+
hasOutput: typeof patch.output === "string" && patch.output.length > 0,
|
|
255
|
+
attachments: (_b = (_a = patch.attachments) == null ? void 0 : _a.length) != null ? _b : 0,
|
|
256
|
+
isPending: patch.isPending
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
const sticky = (_c = this.sticky.get(eventId)) != null ? _c : {};
|
|
260
|
+
const existing = (_d = this.buffers.get(eventId)) != null ? _d : {};
|
|
261
|
+
const merged = mergePatches(existing, patch);
|
|
262
|
+
merged.isPending = (_g = (_f = (_e = patch.isPending) != null ? _e : existing.isPending) != null ? _f : sticky.isPending) != null ? _g : true;
|
|
263
|
+
this.buffers.set(eventId, merged);
|
|
264
|
+
this.sticky.set(eventId, {
|
|
265
|
+
userId: (_h = merged.userId) != null ? _h : sticky.userId,
|
|
266
|
+
convoId: (_i = merged.convoId) != null ? _i : sticky.convoId,
|
|
267
|
+
eventName: (_j = merged.eventName) != null ? _j : sticky.eventName,
|
|
268
|
+
isPending: (_k = merged.isPending) != null ? _k : sticky.isPending
|
|
269
|
+
});
|
|
270
|
+
const t = this.timers.get(eventId);
|
|
271
|
+
if (t) clearTimeout(t);
|
|
272
|
+
if (merged.isPending === false) {
|
|
273
|
+
await this.flushOne(eventId);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const timeout = setTimeout(() => {
|
|
277
|
+
void this.flushOne(eventId).catch(() => {
|
|
278
|
+
});
|
|
279
|
+
}, this.partialFlushMs);
|
|
280
|
+
this.timers.set(eventId, timeout);
|
|
281
|
+
}
|
|
282
|
+
async finish(eventId, patch) {
|
|
283
|
+
await this.patch(eventId, { ...patch, isPending: false });
|
|
284
|
+
}
|
|
285
|
+
async flush() {
|
|
286
|
+
if (!this.enabled) return;
|
|
287
|
+
const ids = [...this.buffers.keys()];
|
|
288
|
+
await Promise.all(ids.map((id) => this.flushOne(id)));
|
|
289
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
290
|
+
})));
|
|
291
|
+
}
|
|
292
|
+
async shutdown() {
|
|
293
|
+
for (const t of this.timers.values()) clearTimeout(t);
|
|
294
|
+
this.timers.clear();
|
|
295
|
+
await this.flush();
|
|
296
|
+
}
|
|
297
|
+
async trackSignal(signal) {
|
|
298
|
+
var _a, _b;
|
|
299
|
+
if (!this.enabled) return;
|
|
300
|
+
const body = [
|
|
301
|
+
{
|
|
302
|
+
event_id: signal.eventId,
|
|
303
|
+
signal_name: signal.name,
|
|
304
|
+
signal_type: (_a = signal.type) != null ? _a : "default",
|
|
305
|
+
timestamp: signal.timestamp,
|
|
306
|
+
sentiment: signal.sentiment,
|
|
307
|
+
attachment_id: signal.attachmentId,
|
|
308
|
+
properties: {
|
|
309
|
+
...(_b = signal.properties) != null ? _b : {},
|
|
310
|
+
...signal.comment ? { comment: signal.comment } : {},
|
|
311
|
+
...signal.after ? { after: signal.after } : {}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
];
|
|
315
|
+
const url = `${this.baseUrl}signals/track`;
|
|
316
|
+
try {
|
|
317
|
+
await postJson(url, body, this.authHeaders(), {
|
|
318
|
+
maxAttempts: 3,
|
|
319
|
+
debug: this.debug,
|
|
320
|
+
sdkName: this.sdkName
|
|
321
|
+
});
|
|
322
|
+
} catch (err) {
|
|
323
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
324
|
+
console.warn(`${this.prefix} failed to send signal (dropping): ${msg}`);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
async identify(users) {
|
|
328
|
+
if (!this.enabled) return;
|
|
329
|
+
const list = Array.isArray(users) ? users : [users];
|
|
330
|
+
const body = list.filter((user) => {
|
|
331
|
+
if (!(user == null ? void 0 : user.userId) || !user.userId.trim()) {
|
|
332
|
+
if (this.debug) {
|
|
333
|
+
console.warn(`${this.prefix} skipping identify: missing userId`);
|
|
334
|
+
}
|
|
335
|
+
return false;
|
|
336
|
+
}
|
|
337
|
+
return true;
|
|
338
|
+
}).map((user) => {
|
|
339
|
+
var _a;
|
|
340
|
+
return {
|
|
341
|
+
user_id: user.userId,
|
|
342
|
+
traits: (_a = user.traits) != null ? _a : {}
|
|
343
|
+
};
|
|
344
|
+
});
|
|
345
|
+
if (body.length === 0) return;
|
|
346
|
+
const url = `${this.baseUrl}users/identify`;
|
|
347
|
+
try {
|
|
348
|
+
await postJson(url, body, this.authHeaders(), {
|
|
349
|
+
maxAttempts: 3,
|
|
350
|
+
debug: this.debug,
|
|
351
|
+
sdkName: this.sdkName
|
|
352
|
+
});
|
|
353
|
+
} catch (err) {
|
|
354
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
355
|
+
console.warn(`${this.prefix} failed to send identify (dropping): ${msg}`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async flushOne(eventId) {
|
|
359
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m;
|
|
360
|
+
if (!this.enabled) return;
|
|
361
|
+
const timer = this.timers.get(eventId);
|
|
362
|
+
if (timer) {
|
|
363
|
+
clearTimeout(timer);
|
|
364
|
+
this.timers.delete(eventId);
|
|
365
|
+
}
|
|
366
|
+
const accumulated = this.buffers.get(eventId);
|
|
367
|
+
this.buffers.delete(eventId);
|
|
368
|
+
if (!accumulated) return;
|
|
369
|
+
const sticky = (_a = this.sticky.get(eventId)) != null ? _a : {};
|
|
370
|
+
const eventName = (_c = (_b = accumulated.eventName) != null ? _b : sticky.eventName) != null ? _c : this.defaultEventName;
|
|
371
|
+
const userId = (_d = accumulated.userId) != null ? _d : sticky.userId;
|
|
372
|
+
if (!userId) {
|
|
373
|
+
if (this.debug) {
|
|
374
|
+
console.warn(`${this.prefix} skipping track_partial for ${eventId}: missing userId`);
|
|
375
|
+
}
|
|
376
|
+
this.sticky.delete(eventId);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const { wizardSession, ...restProperties } = (_e = accumulated.properties) != null ? _e : {};
|
|
380
|
+
const convoId = (_f = accumulated.convoId) != null ? _f : sticky.convoId;
|
|
381
|
+
const isPending = (_h = (_g = accumulated.isPending) != null ? _g : sticky.isPending) != null ? _h : true;
|
|
382
|
+
const payload = {
|
|
383
|
+
event_id: eventId,
|
|
384
|
+
user_id: userId,
|
|
385
|
+
event: eventName,
|
|
386
|
+
timestamp: (_i = accumulated.timestamp) != null ? _i : (/* @__PURE__ */ new Date()).toISOString(),
|
|
387
|
+
ai_data: {
|
|
388
|
+
input: accumulated.input,
|
|
389
|
+
output: accumulated.output,
|
|
390
|
+
model: accumulated.model,
|
|
391
|
+
convo_id: convoId
|
|
392
|
+
},
|
|
393
|
+
properties: {
|
|
394
|
+
...restProperties,
|
|
395
|
+
...wizardSession ? { "raindrop.wizardSession": wizardSession } : {},
|
|
396
|
+
$context: this.context
|
|
397
|
+
},
|
|
398
|
+
attachments: accumulated.attachments,
|
|
399
|
+
is_pending: isPending
|
|
400
|
+
};
|
|
401
|
+
const url = `${this.baseUrl}events/track_partial`;
|
|
402
|
+
if (this.debug) {
|
|
403
|
+
console.log(`${this.prefix} sending track_partial`, {
|
|
404
|
+
eventId,
|
|
405
|
+
eventName,
|
|
406
|
+
userId,
|
|
407
|
+
convoId,
|
|
408
|
+
isPending,
|
|
409
|
+
inputPreview: typeof accumulated.input === "string" ? accumulated.input.slice(0, 120) : void 0,
|
|
410
|
+
outputPreview: typeof accumulated.output === "string" ? accumulated.output.slice(0, 120) : void 0,
|
|
411
|
+
attachments: (_k = (_j = accumulated.attachments) == null ? void 0 : _j.length) != null ? _k : 0,
|
|
412
|
+
attachmentKinds: (_m = (_l = accumulated.attachments) == null ? void 0 : _l.map((a) => ({
|
|
413
|
+
type: a.type,
|
|
414
|
+
role: a.role,
|
|
415
|
+
name: a.name,
|
|
416
|
+
valuePreview: a.value.slice(0, 60)
|
|
417
|
+
}))) != null ? _m : [],
|
|
418
|
+
endpoint: url
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
const p = postJson(url, payload, this.authHeaders(), {
|
|
422
|
+
maxAttempts: 3,
|
|
423
|
+
debug: this.debug,
|
|
424
|
+
sdkName: this.sdkName
|
|
425
|
+
});
|
|
426
|
+
this.inFlight.add(p);
|
|
427
|
+
try {
|
|
428
|
+
try {
|
|
429
|
+
await p;
|
|
430
|
+
if (this.debug) {
|
|
431
|
+
console.log(`${this.prefix} sent track_partial ${eventId} (${eventName})`);
|
|
432
|
+
}
|
|
433
|
+
} catch (err) {
|
|
434
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
435
|
+
console.warn(`${this.prefix} failed to send track_partial (dropping): ${msg}`);
|
|
436
|
+
}
|
|
437
|
+
} finally {
|
|
438
|
+
this.inFlight.delete(p);
|
|
439
|
+
}
|
|
440
|
+
if (!isPending) {
|
|
441
|
+
this.sticky.delete(eventId);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
var TraceShipper = class {
|
|
446
|
+
constructor(opts) {
|
|
447
|
+
this.queue = [];
|
|
448
|
+
this.inFlight = /* @__PURE__ */ new Set();
|
|
449
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
450
|
+
this.writeKey = (_a = opts.writeKey) == null ? void 0 : _a.trim();
|
|
451
|
+
this.baseUrl = (_b = formatEndpoint(opts.endpoint)) != null ? _b : "https://api.raindrop.ai/v1/";
|
|
452
|
+
this.enabled = opts.enabled !== false;
|
|
453
|
+
this.debug = opts.debug;
|
|
454
|
+
this.debugSpans = opts.debugSpans === true;
|
|
455
|
+
this.flushIntervalMs = (_c = opts.flushIntervalMs) != null ? _c : 1e3;
|
|
456
|
+
this.maxBatchSize = (_d = opts.maxBatchSize) != null ? _d : 50;
|
|
457
|
+
this.maxQueueSize = (_e = opts.maxQueueSize) != null ? _e : 5e3;
|
|
458
|
+
this.sdkName = (_f = opts.sdkName) != null ? _f : "core";
|
|
459
|
+
this.prefix = `[raindrop-ai/${this.sdkName}]`;
|
|
460
|
+
this.serviceName = (_g = opts.serviceName) != null ? _g : "raindrop.core";
|
|
461
|
+
this.serviceVersion = (_h = opts.serviceVersion) != null ? _h : "0.0.0";
|
|
462
|
+
}
|
|
463
|
+
isDebugEnabled() {
|
|
464
|
+
return this.debug;
|
|
465
|
+
}
|
|
466
|
+
authHeaders() {
|
|
467
|
+
return this.writeKey ? { Authorization: `Bearer ${this.writeKey}` } : {};
|
|
468
|
+
}
|
|
469
|
+
startSpan(args2) {
|
|
470
|
+
var _a, _b;
|
|
471
|
+
const ids = createSpanIds(args2.parent);
|
|
472
|
+
const started = (_a = args2.startTimeUnixNano) != null ? _a : nowUnixNanoString();
|
|
473
|
+
const attrs = [
|
|
474
|
+
attrString("ai.telemetry.metadata.raindrop.eventId", args2.eventId),
|
|
475
|
+
attrString("ai.operationId", args2.operationId)
|
|
476
|
+
];
|
|
477
|
+
if ((_b = args2.attributes) == null ? void 0 : _b.length) attrs.push(...args2.attributes);
|
|
478
|
+
return { ids, name: args2.name, startTimeUnixNano: started, attributes: attrs };
|
|
479
|
+
}
|
|
480
|
+
endSpan(span, extra) {
|
|
481
|
+
var _a, _b;
|
|
482
|
+
if (span.endTimeUnixNano) return;
|
|
483
|
+
span.endTimeUnixNano = (_a = extra == null ? void 0 : extra.endTimeUnixNano) != null ? _a : nowUnixNanoString();
|
|
484
|
+
if ((_b = extra == null ? void 0 : extra.attributes) == null ? void 0 : _b.length) {
|
|
485
|
+
span.attributes.push(...extra.attributes);
|
|
486
|
+
}
|
|
487
|
+
let status = extra == null ? void 0 : extra.status;
|
|
488
|
+
if (!status && (extra == null ? void 0 : extra.error) !== void 0) {
|
|
489
|
+
const message = extra.error instanceof Error ? extra.error.message : String(extra.error);
|
|
490
|
+
status = { code: SpanStatusCode.ERROR, message };
|
|
491
|
+
}
|
|
492
|
+
const otlp = buildOtlpSpan({
|
|
493
|
+
ids: span.ids,
|
|
494
|
+
name: span.name,
|
|
495
|
+
startTimeUnixNano: span.startTimeUnixNano,
|
|
496
|
+
endTimeUnixNano: span.endTimeUnixNano,
|
|
497
|
+
attributes: span.attributes,
|
|
498
|
+
status
|
|
499
|
+
});
|
|
500
|
+
this.enqueue(otlp);
|
|
501
|
+
}
|
|
502
|
+
createSpan(args2) {
|
|
503
|
+
var _a;
|
|
504
|
+
const ids = createSpanIds(args2.parent);
|
|
505
|
+
const attrs = [
|
|
506
|
+
attrString("ai.telemetry.metadata.raindrop.eventId", args2.eventId)
|
|
507
|
+
];
|
|
508
|
+
if ((_a = args2.attributes) == null ? void 0 : _a.length) attrs.push(...args2.attributes);
|
|
509
|
+
const otlp = buildOtlpSpan({
|
|
510
|
+
ids,
|
|
511
|
+
name: args2.name,
|
|
512
|
+
startTimeUnixNano: args2.startTimeUnixNano,
|
|
513
|
+
endTimeUnixNano: args2.endTimeUnixNano,
|
|
514
|
+
attributes: attrs,
|
|
515
|
+
status: args2.status
|
|
516
|
+
});
|
|
517
|
+
this.enqueue(otlp);
|
|
518
|
+
}
|
|
519
|
+
enqueue(span) {
|
|
520
|
+
if (!this.enabled) return;
|
|
521
|
+
if (this.debugSpans) {
|
|
522
|
+
const short = (s) => s ? s.slice(-8) : "none";
|
|
523
|
+
console.log(
|
|
524
|
+
`${this.prefix}[span] name=${span.name} trace=${short(span.traceId)} span=${short(span.spanId)} parent=${short(
|
|
525
|
+
span.parentSpanId
|
|
526
|
+
)}`
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
530
|
+
this.queue.shift();
|
|
531
|
+
}
|
|
532
|
+
this.queue.push(span);
|
|
533
|
+
if (this.queue.length >= this.maxBatchSize) {
|
|
534
|
+
void this.flush().catch(() => {
|
|
535
|
+
});
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (!this.timer) {
|
|
539
|
+
this.timer = setTimeout(() => {
|
|
540
|
+
this.timer = void 0;
|
|
541
|
+
void this.flush().catch(() => {
|
|
542
|
+
});
|
|
543
|
+
}, this.flushIntervalMs);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
async flush() {
|
|
547
|
+
if (!this.enabled) return;
|
|
548
|
+
if (this.timer) {
|
|
549
|
+
clearTimeout(this.timer);
|
|
550
|
+
this.timer = void 0;
|
|
551
|
+
}
|
|
552
|
+
while (this.queue.length > 0) {
|
|
553
|
+
const batch = this.queue.splice(0, this.maxBatchSize);
|
|
554
|
+
const body = buildExportTraceServiceRequest(batch, this.serviceName, this.serviceVersion);
|
|
555
|
+
const url = `${this.baseUrl}traces`;
|
|
556
|
+
if (this.debug) {
|
|
557
|
+
console.log(`${this.prefix} sending traces batch`, {
|
|
558
|
+
spans: batch.length,
|
|
559
|
+
endpoint: url
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
const p = postJson(url, body, this.authHeaders(), {
|
|
563
|
+
maxAttempts: 3,
|
|
564
|
+
debug: this.debug,
|
|
565
|
+
sdkName: this.sdkName
|
|
566
|
+
});
|
|
567
|
+
this.inFlight.add(p);
|
|
568
|
+
try {
|
|
569
|
+
try {
|
|
570
|
+
await p;
|
|
571
|
+
if (this.debug) console.log(`${this.prefix} sent ${batch.length} spans`);
|
|
572
|
+
} catch (err) {
|
|
573
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
574
|
+
console.warn(`${this.prefix} failed to send ${batch.length} spans: ${msg}`);
|
|
575
|
+
}
|
|
576
|
+
} finally {
|
|
577
|
+
this.inFlight.delete(p);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
async shutdown() {
|
|
582
|
+
if (this.timer) {
|
|
583
|
+
clearTimeout(this.timer);
|
|
584
|
+
this.timer = void 0;
|
|
585
|
+
}
|
|
586
|
+
await this.flush();
|
|
587
|
+
await Promise.all([...this.inFlight].map((p) => p.catch(() => {
|
|
588
|
+
})));
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
// ../core/dist/index.node.js
|
|
593
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
594
|
+
globalThis.RAINDROP_ASYNC_LOCAL_STORAGE = AsyncLocalStorage;
|
|
595
|
+
|
|
596
|
+
// src/package-info.ts
|
|
597
|
+
var PACKAGE_NAME = "@raindrop-ai/claude-code";
|
|
598
|
+
var PACKAGE_VERSION = "0.0.1";
|
|
599
|
+
|
|
600
|
+
// src/shipper.ts
|
|
601
|
+
var EventShipper2 = class extends EventShipper {
|
|
602
|
+
constructor(opts) {
|
|
603
|
+
var _a, _b, _c, _d;
|
|
604
|
+
super({
|
|
605
|
+
...opts,
|
|
606
|
+
sdkName: (_a = opts.sdkName) != null ? _a : "claude-code",
|
|
607
|
+
libraryName: (_b = opts.libraryName) != null ? _b : PACKAGE_NAME,
|
|
608
|
+
libraryVersion: (_c = opts.libraryVersion) != null ? _c : PACKAGE_VERSION,
|
|
609
|
+
defaultEventName: (_d = opts.defaultEventName) != null ? _d : "claude_code_session"
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
var TraceShipper2 = class extends TraceShipper {
|
|
614
|
+
constructor(opts) {
|
|
615
|
+
var _a, _b, _c;
|
|
616
|
+
super({
|
|
617
|
+
...opts,
|
|
618
|
+
sdkName: (_a = opts.sdkName) != null ? _a : "claude-code",
|
|
619
|
+
serviceName: (_b = opts.serviceName) != null ? _b : "raindrop.claude-code",
|
|
620
|
+
serviceVersion: (_c = opts.serviceVersion) != null ? _c : PACKAGE_VERSION
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
enqueue(span) {
|
|
624
|
+
var _a;
|
|
625
|
+
const attrs = (_a = span.attributes) != null ? _a : [];
|
|
626
|
+
attrs.unshift(
|
|
627
|
+
{ key: "span.id", value: { stringValue: span.spanId } },
|
|
628
|
+
...span.parentSpanId ? [{ key: "span.parent.id", value: { stringValue: span.parentSpanId } }] : []
|
|
629
|
+
);
|
|
630
|
+
span.attributes = attrs;
|
|
631
|
+
super.enqueue(span);
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
// src/event-mapper.ts
|
|
636
|
+
var MAX_ATTR_LENGTH = 32768;
|
|
637
|
+
function truncate(value) {
|
|
638
|
+
if (value === void 0) return void 0;
|
|
639
|
+
if (value.length <= MAX_ATTR_LENGTH) return value;
|
|
640
|
+
const suffix = "\n...[truncated]";
|
|
641
|
+
return value.slice(0, MAX_ATTR_LENGTH - suffix.length) + suffix;
|
|
642
|
+
}
|
|
643
|
+
function safeStringify(value) {
|
|
644
|
+
if (value === void 0 || value === null) return void 0;
|
|
645
|
+
try {
|
|
646
|
+
return truncate(JSON.stringify(value));
|
|
647
|
+
} catch (e) {
|
|
648
|
+
return truncate(String(value));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
var STATE_DIR = join(tmpdir(), "raindrop-claude-code");
|
|
652
|
+
function ensureStateDir() {
|
|
653
|
+
try {
|
|
654
|
+
mkdirSync(STATE_DIR, { recursive: true });
|
|
655
|
+
} catch (e) {
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
function safeKey(key) {
|
|
659
|
+
return key.replace(/[^a-zA-Z0-9_\-]/g, "_");
|
|
660
|
+
}
|
|
661
|
+
function statePath(key) {
|
|
662
|
+
const sanitized = safeKey(key);
|
|
663
|
+
const full = join(STATE_DIR, sanitized);
|
|
664
|
+
if (!full.startsWith(STATE_DIR)) {
|
|
665
|
+
throw new Error("Path traversal detected");
|
|
666
|
+
}
|
|
667
|
+
return full;
|
|
668
|
+
}
|
|
669
|
+
function writeState(key, value) {
|
|
670
|
+
try {
|
|
671
|
+
ensureStateDir();
|
|
672
|
+
writeFileSync(statePath(key), value, "utf-8");
|
|
673
|
+
} catch (e) {
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function readState(key) {
|
|
677
|
+
try {
|
|
678
|
+
const filePath = statePath(key);
|
|
679
|
+
if (!existsSync(filePath)) return void 0;
|
|
680
|
+
return readFileSync(filePath, "utf-8");
|
|
681
|
+
} catch (e) {
|
|
682
|
+
return void 0;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
function deleteState(key) {
|
|
686
|
+
try {
|
|
687
|
+
unlinkSync(statePath(key));
|
|
688
|
+
} catch (e) {
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
function turnEventKey(sessionId) {
|
|
692
|
+
return `event_${sessionId}`;
|
|
693
|
+
}
|
|
694
|
+
function saveCurrentEventId(sessionId, eventId) {
|
|
695
|
+
writeState(turnEventKey(sessionId), eventId);
|
|
696
|
+
}
|
|
697
|
+
function getCurrentEventId(sessionId) {
|
|
698
|
+
const saved = readState(turnEventKey(sessionId));
|
|
699
|
+
if (saved) return saved;
|
|
700
|
+
return `cc_${sessionId}`;
|
|
701
|
+
}
|
|
702
|
+
function saveSpanContext(key, ctx) {
|
|
703
|
+
writeState(key, JSON.stringify(ctx));
|
|
704
|
+
}
|
|
705
|
+
function readSpanContext(key) {
|
|
706
|
+
const raw = readState(key);
|
|
707
|
+
if (!raw) return void 0;
|
|
708
|
+
try {
|
|
709
|
+
return JSON.parse(raw);
|
|
710
|
+
} catch (e) {
|
|
711
|
+
return void 0;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function rootSpanKey(sessionId) {
|
|
715
|
+
return `rootspan_${sessionId}`;
|
|
716
|
+
}
|
|
717
|
+
function subagentSpanKey(agentId) {
|
|
718
|
+
return `subagentspan_${agentId}`;
|
|
719
|
+
}
|
|
720
|
+
function getParentContext(payload) {
|
|
721
|
+
if (payload.agent_id) {
|
|
722
|
+
const sub = readSpanContext(subagentSpanKey(payload.agent_id));
|
|
723
|
+
if (sub) return sub;
|
|
724
|
+
}
|
|
725
|
+
return readSpanContext(rootSpanKey(payload.session_id));
|
|
726
|
+
}
|
|
727
|
+
function saveTimestamp(key) {
|
|
728
|
+
writeState(key, String(Date.now()));
|
|
729
|
+
}
|
|
730
|
+
function readTimestamp(key) {
|
|
731
|
+
const raw = readState(key);
|
|
732
|
+
deleteState(key);
|
|
733
|
+
if (!raw) return void 0;
|
|
734
|
+
const ts = parseInt(raw, 10);
|
|
735
|
+
return Number.isFinite(ts) ? ts : void 0;
|
|
736
|
+
}
|
|
737
|
+
async function mapHookToRaindrop(payload, config, eventShipper, traceShipper) {
|
|
738
|
+
var _a;
|
|
739
|
+
const convoId = payload.session_id;
|
|
740
|
+
const baseProperties = {
|
|
741
|
+
cwd: payload.cwd,
|
|
742
|
+
permission_mode: payload.permission_mode,
|
|
743
|
+
plugin_version: PACKAGE_VERSION,
|
|
744
|
+
...payload.agent_id ? { agent_id: payload.agent_id } : {},
|
|
745
|
+
...payload.agent_type ? { agent_type: payload.agent_type } : {}
|
|
746
|
+
};
|
|
747
|
+
switch (payload.hook_event_name) {
|
|
748
|
+
case "SessionStart":
|
|
749
|
+
writeState(`model_${payload.session_id}`, (_a = payload.model) != null ? _a : "");
|
|
750
|
+
break;
|
|
751
|
+
case "UserPromptSubmit":
|
|
752
|
+
await handleUserPromptSubmit(payload, convoId, config, baseProperties, eventShipper, traceShipper);
|
|
753
|
+
break;
|
|
754
|
+
case "PreToolUse":
|
|
755
|
+
handlePreToolUse(payload);
|
|
756
|
+
break;
|
|
757
|
+
case "PostToolUse":
|
|
758
|
+
handlePostToolUse(payload, getCurrentEventId(payload.session_id), traceShipper);
|
|
759
|
+
break;
|
|
760
|
+
case "PostToolUseFailure":
|
|
761
|
+
handlePostToolUseFailure(payload, getCurrentEventId(payload.session_id), traceShipper);
|
|
762
|
+
break;
|
|
763
|
+
case "Stop":
|
|
764
|
+
await handleStop(payload, getCurrentEventId(payload.session_id), config, baseProperties, eventShipper);
|
|
765
|
+
break;
|
|
766
|
+
case "StopFailure":
|
|
767
|
+
await handleStopFailure(payload, getCurrentEventId(payload.session_id), config, baseProperties, eventShipper);
|
|
768
|
+
break;
|
|
769
|
+
case "SubagentStart":
|
|
770
|
+
handleSubagentStart(payload, getCurrentEventId(payload.session_id), traceShipper);
|
|
771
|
+
break;
|
|
772
|
+
case "SubagentStop":
|
|
773
|
+
handleSubagentStop(payload, getCurrentEventId(payload.session_id), traceShipper);
|
|
774
|
+
break;
|
|
775
|
+
case "PermissionDenied":
|
|
776
|
+
handlePermissionDenied(payload, getCurrentEventId(payload.session_id), traceShipper);
|
|
777
|
+
break;
|
|
778
|
+
case "PostCompact":
|
|
779
|
+
await handlePostCompact(payload, getCurrentEventId(payload.session_id), config, baseProperties, eventShipper);
|
|
780
|
+
break;
|
|
781
|
+
case "SessionEnd":
|
|
782
|
+
await handleSessionEnd(payload, getCurrentEventId(payload.session_id), config, baseProperties, eventShipper);
|
|
783
|
+
deleteState(turnEventKey(payload.session_id));
|
|
784
|
+
deleteState(rootSpanKey(payload.session_id));
|
|
785
|
+
deleteState(`model_${payload.session_id}`);
|
|
786
|
+
break;
|
|
787
|
+
default:
|
|
788
|
+
if (config.debug) {
|
|
789
|
+
console.log(`[raindrop-ai/claude-code] ignoring unhandled event: ${payload.hook_event_name}`);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
async function handleUserPromptSubmit(payload, convoId, config, properties, eventShipper, traceShipper) {
|
|
794
|
+
const eventId = `cc_${randomUUID2()}`;
|
|
795
|
+
saveCurrentEventId(payload.session_id, eventId);
|
|
796
|
+
const model = readState(`model_${payload.session_id}`) || void 0;
|
|
797
|
+
const rootSpan = traceShipper.startSpan({
|
|
798
|
+
name: model != null ? model : "claude-code",
|
|
799
|
+
eventId,
|
|
800
|
+
attributes: [
|
|
801
|
+
attrString("ai.operationId", "generateText"),
|
|
802
|
+
attrString("session.model", model),
|
|
803
|
+
attrString("cwd", payload.cwd)
|
|
804
|
+
]
|
|
805
|
+
});
|
|
806
|
+
traceShipper.endSpan(rootSpan);
|
|
807
|
+
saveSpanContext(rootSpanKey(payload.session_id), {
|
|
808
|
+
traceIdB64: rootSpan.ids.traceIdB64,
|
|
809
|
+
spanIdB64: rootSpan.ids.spanIdB64
|
|
810
|
+
});
|
|
811
|
+
const patch = {
|
|
812
|
+
isPending: true,
|
|
813
|
+
userId: config.userId,
|
|
814
|
+
convoId,
|
|
815
|
+
eventName: "claude_code_session",
|
|
816
|
+
input: payload.prompt,
|
|
817
|
+
model,
|
|
818
|
+
properties
|
|
819
|
+
};
|
|
820
|
+
await eventShipper.patch(eventId, patch);
|
|
821
|
+
}
|
|
822
|
+
function handlePreToolUse(payload) {
|
|
823
|
+
if (payload.tool_use_id) {
|
|
824
|
+
saveTimestamp(`tool_${payload.tool_use_id}`);
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
function createToolSpan(payload, eventId, traceShipper, error) {
|
|
828
|
+
const endMs = Date.now();
|
|
829
|
+
const savedStartMs = payload.tool_use_id ? readTimestamp(`tool_${payload.tool_use_id}`) : void 0;
|
|
830
|
+
const startMs = savedStartMs && savedStartMs <= endMs ? savedStartMs : endMs;
|
|
831
|
+
const durationMs = endMs - startMs;
|
|
832
|
+
const startNano = String(startMs) + "000000";
|
|
833
|
+
const endNano = String(endMs) + "000000";
|
|
834
|
+
const parent = getParentContext(payload);
|
|
835
|
+
traceShipper.createSpan({
|
|
836
|
+
name: "ai.toolCall",
|
|
837
|
+
eventId,
|
|
838
|
+
parent,
|
|
839
|
+
startTimeUnixNano: startNano,
|
|
840
|
+
endTimeUnixNano: endNano,
|
|
841
|
+
attributes: [
|
|
842
|
+
attrString("ai.operationId", "ai.toolCall"),
|
|
843
|
+
attrString("ai.toolCall.name", payload.tool_name),
|
|
844
|
+
attrString("ai.toolCall.id", payload.tool_use_id),
|
|
845
|
+
attrString("ai.toolCall.args", safeStringify(payload.tool_input)),
|
|
846
|
+
...payload.tool_response != null ? [attrString("ai.toolCall.result", safeStringify(payload.tool_response))] : [],
|
|
847
|
+
attrInt("traceloop.entity.duration_ms", durationMs)
|
|
848
|
+
],
|
|
849
|
+
...error ? { status: { code: 2, message: error } } : {}
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
function handlePostToolUse(payload, eventId, traceShipper) {
|
|
853
|
+
createToolSpan(payload, eventId, traceShipper);
|
|
854
|
+
}
|
|
855
|
+
function handlePostToolUseFailure(payload, eventId, traceShipper) {
|
|
856
|
+
var _a;
|
|
857
|
+
createToolSpan(payload, eventId, traceShipper, (_a = payload.error) != null ? _a : "Tool execution failed");
|
|
858
|
+
}
|
|
859
|
+
function handleSubagentStart(payload, eventId, traceShipper) {
|
|
860
|
+
if (payload.agent_id) {
|
|
861
|
+
saveTimestamp(`subagent_${payload.agent_id}`);
|
|
862
|
+
}
|
|
863
|
+
const parent = getParentContext(payload);
|
|
864
|
+
const span = traceShipper.startSpan({
|
|
865
|
+
name: "ai.subagent.start",
|
|
866
|
+
eventId,
|
|
867
|
+
parent,
|
|
868
|
+
attributes: [
|
|
869
|
+
attrString("ai.operationId", "ai.subagent"),
|
|
870
|
+
attrString("ai.subagent.id", payload.agent_id),
|
|
871
|
+
attrString("ai.subagent.type", payload.agent_type)
|
|
872
|
+
]
|
|
873
|
+
});
|
|
874
|
+
traceShipper.endSpan(span);
|
|
875
|
+
if (payload.agent_id) {
|
|
876
|
+
saveSpanContext(subagentSpanKey(payload.agent_id), {
|
|
877
|
+
traceIdB64: span.ids.traceIdB64,
|
|
878
|
+
spanIdB64: span.ids.spanIdB64
|
|
879
|
+
});
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
function handleSubagentStop(payload, eventId, traceShipper) {
|
|
883
|
+
const endMs = Date.now();
|
|
884
|
+
const savedStartMs = payload.agent_id ? readTimestamp(`subagent_${payload.agent_id}`) : void 0;
|
|
885
|
+
const startMs = savedStartMs && savedStartMs <= endMs ? savedStartMs : endMs;
|
|
886
|
+
const durationMs = endMs - startMs;
|
|
887
|
+
const startNano = String(startMs) + "000000";
|
|
888
|
+
const endNano = String(endMs) + "000000";
|
|
889
|
+
const parent = readSpanContext(rootSpanKey(payload.session_id));
|
|
890
|
+
traceShipper.createSpan({
|
|
891
|
+
name: "ai.subagent",
|
|
892
|
+
eventId,
|
|
893
|
+
parent,
|
|
894
|
+
startTimeUnixNano: startNano,
|
|
895
|
+
endTimeUnixNano: endNano,
|
|
896
|
+
attributes: [
|
|
897
|
+
attrString("ai.operationId", "ai.subagent"),
|
|
898
|
+
attrString("ai.subagent.id", payload.agent_id),
|
|
899
|
+
attrString("ai.subagent.type", payload.agent_type),
|
|
900
|
+
attrString("ai.subagent.result", safeStringify(payload.last_assistant_message)),
|
|
901
|
+
attrInt("traceloop.entity.duration_ms", durationMs)
|
|
902
|
+
]
|
|
903
|
+
});
|
|
904
|
+
if (payload.agent_id) {
|
|
905
|
+
deleteState(subagentSpanKey(payload.agent_id));
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
function handlePermissionDenied(payload, eventId, traceShipper) {
|
|
909
|
+
var _a;
|
|
910
|
+
const parent = getParentContext(payload);
|
|
911
|
+
traceShipper.createSpan({
|
|
912
|
+
name: "ai.permissionDenied",
|
|
913
|
+
eventId,
|
|
914
|
+
parent,
|
|
915
|
+
startTimeUnixNano: nowUnixNanoString(),
|
|
916
|
+
endTimeUnixNano: nowUnixNanoString(),
|
|
917
|
+
attributes: [
|
|
918
|
+
attrString("ai.operationId", "ai.permissionDenied"),
|
|
919
|
+
attrString("ai.toolCall.name", payload.tool_name),
|
|
920
|
+
attrString("ai.toolCall.id", payload.tool_use_id),
|
|
921
|
+
attrString("ai.toolCall.args", safeStringify(payload.tool_input)),
|
|
922
|
+
attrString("ai.permissionDenied.reason", payload.reason)
|
|
923
|
+
],
|
|
924
|
+
status: {
|
|
925
|
+
code: 2,
|
|
926
|
+
message: (_a = payload.reason) != null ? _a : "Permission denied"
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
async function handlePostCompact(payload, eventId, config, properties, eventShipper) {
|
|
931
|
+
await eventShipper.patch(eventId, {
|
|
932
|
+
isPending: true,
|
|
933
|
+
userId: config.userId,
|
|
934
|
+
convoId: payload.session_id,
|
|
935
|
+
eventName: "claude_code_session",
|
|
936
|
+
properties: {
|
|
937
|
+
...properties,
|
|
938
|
+
compaction_trigger: payload.trigger,
|
|
939
|
+
compact_summary: payload.compact_summary
|
|
940
|
+
}
|
|
941
|
+
});
|
|
942
|
+
}
|
|
943
|
+
async function handleStop(payload, eventId, config, properties, eventShipper) {
|
|
944
|
+
await eventShipper.finish(eventId, {
|
|
945
|
+
userId: config.userId,
|
|
946
|
+
output: payload.last_assistant_message,
|
|
947
|
+
properties
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
async function handleStopFailure(payload, eventId, config, properties, eventShipper) {
|
|
951
|
+
await eventShipper.finish(eventId, {
|
|
952
|
+
userId: config.userId,
|
|
953
|
+
output: payload.last_assistant_message,
|
|
954
|
+
properties: {
|
|
955
|
+
...properties,
|
|
956
|
+
error: payload.error,
|
|
957
|
+
error_details: payload.error_details
|
|
958
|
+
}
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
async function handleSessionEnd(payload, eventId, config, properties, eventShipper) {
|
|
962
|
+
await eventShipper.finish(eventId, {
|
|
963
|
+
userId: config.userId,
|
|
964
|
+
properties: {
|
|
965
|
+
...properties,
|
|
966
|
+
session_end_reason: payload.reason
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// src/config.ts
|
|
972
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
973
|
+
import { homedir, userInfo } from "os";
|
|
974
|
+
import { join as join2 } from "path";
|
|
975
|
+
var CONFIG_PATH = join2(homedir(), ".config", "raindrop", "config.json");
|
|
976
|
+
function loadConfig() {
|
|
977
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
978
|
+
let file = {};
|
|
979
|
+
try {
|
|
980
|
+
if (existsSync2(CONFIG_PATH)) {
|
|
981
|
+
file = JSON.parse(readFileSync2(CONFIG_PATH, "utf-8"));
|
|
982
|
+
}
|
|
983
|
+
} catch (e) {
|
|
984
|
+
}
|
|
985
|
+
const systemUser = (() => {
|
|
986
|
+
try {
|
|
987
|
+
return userInfo().username;
|
|
988
|
+
} catch (e) {
|
|
989
|
+
return "unknown";
|
|
990
|
+
}
|
|
991
|
+
})();
|
|
992
|
+
return {
|
|
993
|
+
writeKey: (_b = (_a = process.env["RAINDROP_WRITE_KEY"]) != null ? _a : file.write_key) != null ? _b : "",
|
|
994
|
+
endpoint: (_d = (_c = process.env["RAINDROP_API_URL"]) != null ? _c : file.api_url) != null ? _d : "https://api.raindrop.ai/v1",
|
|
995
|
+
userId: (_f = (_e = process.env["RAINDROP_USER_ID"]) != null ? _e : file.user_id) != null ? _f : systemUser,
|
|
996
|
+
debug: process.env["RAINDROP_DEBUG"] === "true" ? true : (_g = file.debug) != null ? _g : false
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
function getConfigPath() {
|
|
1000
|
+
return CONFIG_PATH;
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// src/hook-handler.ts
|
|
1004
|
+
var STDIN_TIMEOUT_MS = 5e3;
|
|
1005
|
+
function readStdin() {
|
|
1006
|
+
return new Promise((resolve2) => {
|
|
1007
|
+
if (process.stdin.isTTY) {
|
|
1008
|
+
resolve2("");
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
const chunks = [];
|
|
1012
|
+
let resolved = false;
|
|
1013
|
+
function done(value) {
|
|
1014
|
+
if (resolved) return;
|
|
1015
|
+
resolved = true;
|
|
1016
|
+
clearTimeout(timeout);
|
|
1017
|
+
process.stdin.removeAllListeners();
|
|
1018
|
+
process.stdin.destroy();
|
|
1019
|
+
resolve2(value);
|
|
1020
|
+
}
|
|
1021
|
+
const timeout = setTimeout(() => {
|
|
1022
|
+
done(chunks.length > 0 ? Buffer.concat(chunks).toString("utf-8") : "");
|
|
1023
|
+
}, STDIN_TIMEOUT_MS);
|
|
1024
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
1025
|
+
process.stdin.on("end", () => done(Buffer.concat(chunks).toString("utf-8")));
|
|
1026
|
+
process.stdin.on("error", () => done(chunks.length > 0 ? Buffer.concat(chunks).toString("utf-8") : ""));
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
async function handleHook() {
|
|
1030
|
+
const raw = await readStdin();
|
|
1031
|
+
if (!raw.trim()) {
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
let payload;
|
|
1035
|
+
try {
|
|
1036
|
+
payload = JSON.parse(raw);
|
|
1037
|
+
} catch (e) {
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
if (!payload.session_id || !payload.hook_event_name) {
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
const config = loadConfig();
|
|
1044
|
+
if (!config.writeKey) {
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
const mapperConfig = {
|
|
1048
|
+
userId: config.userId,
|
|
1049
|
+
debug: config.debug
|
|
1050
|
+
};
|
|
1051
|
+
const eventShipper = new EventShipper2({
|
|
1052
|
+
writeKey: config.writeKey,
|
|
1053
|
+
endpoint: config.endpoint,
|
|
1054
|
+
debug: config.debug
|
|
1055
|
+
});
|
|
1056
|
+
const traceShipper = new TraceShipper2({
|
|
1057
|
+
writeKey: config.writeKey,
|
|
1058
|
+
endpoint: config.endpoint,
|
|
1059
|
+
debug: config.debug
|
|
1060
|
+
});
|
|
1061
|
+
try {
|
|
1062
|
+
await mapHookToRaindrop(payload, mapperConfig, eventShipper, traceShipper);
|
|
1063
|
+
await Promise.all([eventShipper.flush(), traceShipper.flush()]);
|
|
1064
|
+
await Promise.all([eventShipper.shutdown(), traceShipper.shutdown()]);
|
|
1065
|
+
} catch (err) {
|
|
1066
|
+
if (config.debug) {
|
|
1067
|
+
console.error(
|
|
1068
|
+
`[raindrop-ai/claude-code] error processing hook: ${err instanceof Error ? err.message : String(err)}`
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// src/setup.ts
|
|
1075
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
1076
|
+
import { homedir as homedir2, userInfo as userInfo2 } from "os";
|
|
1077
|
+
import { dirname, join as join3, resolve } from "path";
|
|
1078
|
+
import { createInterface } from "readline";
|
|
1079
|
+
function getClaudeSettingsPath(scope, cwd) {
|
|
1080
|
+
if (scope === "project") {
|
|
1081
|
+
return resolve(cwd, ".claude", "settings.json");
|
|
1082
|
+
}
|
|
1083
|
+
return join3(homedir2(), ".claude", "settings.json");
|
|
1084
|
+
}
|
|
1085
|
+
var HOOK_EVENTS = [
|
|
1086
|
+
"SessionStart",
|
|
1087
|
+
"UserPromptSubmit",
|
|
1088
|
+
"PreToolUse",
|
|
1089
|
+
"PostToolUse",
|
|
1090
|
+
"PostToolUseFailure",
|
|
1091
|
+
"SubagentStart",
|
|
1092
|
+
"SubagentStop",
|
|
1093
|
+
"PermissionDenied",
|
|
1094
|
+
"PostCompact",
|
|
1095
|
+
"Stop",
|
|
1096
|
+
"StopFailure",
|
|
1097
|
+
"SessionEnd"
|
|
1098
|
+
];
|
|
1099
|
+
function makeHookConfig() {
|
|
1100
|
+
const hooks = {};
|
|
1101
|
+
for (const event of HOOK_EVENTS) {
|
|
1102
|
+
hooks[event] = [
|
|
1103
|
+
{
|
|
1104
|
+
hooks: [
|
|
1105
|
+
{
|
|
1106
|
+
type: "command",
|
|
1107
|
+
command: "raindrop-claude-code hook",
|
|
1108
|
+
async: true,
|
|
1109
|
+
timeout: 10
|
|
1110
|
+
}
|
|
1111
|
+
]
|
|
1112
|
+
}
|
|
1113
|
+
];
|
|
1114
|
+
}
|
|
1115
|
+
return hooks;
|
|
1116
|
+
}
|
|
1117
|
+
function prompt(question) {
|
|
1118
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
1119
|
+
return new Promise((resolve2) => {
|
|
1120
|
+
rl.question(question, (answer) => {
|
|
1121
|
+
rl.close();
|
|
1122
|
+
resolve2(answer.trim());
|
|
1123
|
+
});
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
async function runSetup(args2) {
|
|
1127
|
+
var _a, _b, _c, _d, _e;
|
|
1128
|
+
const scope = (_a = args2.scope) != null ? _a : "user";
|
|
1129
|
+
const scopeLabel = scope === "project" ? "project" : "global";
|
|
1130
|
+
console.log("\n Raindrop \xD7 Claude Code \u2014 Setup\n");
|
|
1131
|
+
let writeKey = (_c = (_b = args2.writeKey) != null ? _b : process.env["RAINDROP_WRITE_KEY"]) != null ? _c : "";
|
|
1132
|
+
if (!writeKey) {
|
|
1133
|
+
writeKey = await prompt(" Enter your Raindrop write key: ");
|
|
1134
|
+
}
|
|
1135
|
+
if (!writeKey) {
|
|
1136
|
+
console.error("\n Error: write key is required. Get it from https://app.raindrop.ai\n");
|
|
1137
|
+
process.exit(1);
|
|
1138
|
+
}
|
|
1139
|
+
let userId = (_d = args2.userId) != null ? _d : "";
|
|
1140
|
+
if (!userId) {
|
|
1141
|
+
try {
|
|
1142
|
+
userId = userInfo2().username;
|
|
1143
|
+
} catch (e) {
|
|
1144
|
+
userId = "unknown";
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
const configPath = getConfigPath();
|
|
1148
|
+
const configDir = dirname(configPath);
|
|
1149
|
+
mkdirSync2(configDir, { recursive: true });
|
|
1150
|
+
let existingConfig = {};
|
|
1151
|
+
try {
|
|
1152
|
+
if (existsSync3(configPath)) {
|
|
1153
|
+
existingConfig = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
1154
|
+
}
|
|
1155
|
+
} catch (e) {
|
|
1156
|
+
}
|
|
1157
|
+
const raindropConfig = {
|
|
1158
|
+
...existingConfig,
|
|
1159
|
+
write_key: writeKey,
|
|
1160
|
+
user_id: userId
|
|
1161
|
+
};
|
|
1162
|
+
writeFileSync2(configPath, JSON.stringify(raindropConfig, null, 2) + "\n", "utf-8");
|
|
1163
|
+
console.log(` Saved Raindrop config to ${configPath}`);
|
|
1164
|
+
const settingsPath = getClaudeSettingsPath(scope, process.cwd());
|
|
1165
|
+
const settingsDir = dirname(settingsPath);
|
|
1166
|
+
mkdirSync2(settingsDir, { recursive: true });
|
|
1167
|
+
let settings = {};
|
|
1168
|
+
if (existsSync3(settingsPath)) {
|
|
1169
|
+
try {
|
|
1170
|
+
settings = JSON.parse(readFileSync3(settingsPath, "utf-8"));
|
|
1171
|
+
} catch (e) {
|
|
1172
|
+
console.warn(` Warning: could not parse ${settingsPath}, creating fresh`);
|
|
1173
|
+
settings = {};
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
const existingHooks = (_e = settings["hooks"]) != null ? _e : {};
|
|
1177
|
+
const newHooks = makeHookConfig();
|
|
1178
|
+
for (const [event, hookGroups] of Object.entries(newHooks)) {
|
|
1179
|
+
const existing = existingHooks[event];
|
|
1180
|
+
if (!existing || !Array.isArray(existing)) {
|
|
1181
|
+
existingHooks[event] = hookGroups;
|
|
1182
|
+
continue;
|
|
1183
|
+
}
|
|
1184
|
+
const typedExisting = existing;
|
|
1185
|
+
const alreadyHasRaindrop = typedExisting.some(
|
|
1186
|
+
(group) => {
|
|
1187
|
+
var _a2;
|
|
1188
|
+
return (_a2 = group.hooks) == null ? void 0 : _a2.some((h) => typeof h["command"] === "string" && h["command"].includes("raindrop-claude-code"));
|
|
1189
|
+
}
|
|
1190
|
+
);
|
|
1191
|
+
if (!alreadyHasRaindrop) {
|
|
1192
|
+
typedExisting.push(...hookGroups);
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
settings["hooks"] = existingHooks;
|
|
1196
|
+
writeFileSync2(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
1197
|
+
console.log(` Updated Claude Code hooks in ${settingsPath} (${scopeLabel})`);
|
|
1198
|
+
const { execSync } = await import("child_process");
|
|
1199
|
+
const whichCmd = process.platform === "win32" ? "where" : "which";
|
|
1200
|
+
try {
|
|
1201
|
+
execSync(`${whichCmd} raindrop-claude-code`, { stdio: "ignore" });
|
|
1202
|
+
} catch (e) {
|
|
1203
|
+
console.log(`
|
|
1204
|
+
Warning: 'raindrop-claude-code' is not in your PATH.
|
|
1205
|
+
The hooks need the binary globally available. Run:
|
|
1206
|
+
|
|
1207
|
+
npm install -g @raindrop-ai/claude-code
|
|
1208
|
+
`);
|
|
1209
|
+
}
|
|
1210
|
+
console.log(`
|
|
1211
|
+
Done! Claude Code will now send telemetry to Raindrop.
|
|
1212
|
+
Scope: ${scopeLabel}
|
|
1213
|
+
User ID: ${userId}
|
|
1214
|
+
|
|
1215
|
+
To verify, start a Claude Code session and check your Raindrop dashboard.
|
|
1216
|
+
Set RAINDROP_DEBUG=true for verbose logging.
|
|
1217
|
+
`);
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// src/cli.ts
|
|
1221
|
+
var args = process.argv.slice(2);
|
|
1222
|
+
var command = args[0];
|
|
1223
|
+
function parseFlag(name) {
|
|
1224
|
+
const prefix = `--${name}=`;
|
|
1225
|
+
const flag = args.find((a) => a.startsWith(prefix));
|
|
1226
|
+
return flag ? flag.slice(prefix.length) : void 0;
|
|
1227
|
+
}
|
|
1228
|
+
async function main() {
|
|
1229
|
+
switch (command) {
|
|
1230
|
+
case "setup": {
|
|
1231
|
+
const scopeRaw = parseFlag("scope");
|
|
1232
|
+
const scope = scopeRaw === "project" ? "project" : scopeRaw === "user" ? "user" : void 0;
|
|
1233
|
+
await runSetup({
|
|
1234
|
+
writeKey: parseFlag("write-key"),
|
|
1235
|
+
userId: parseFlag("user-id"),
|
|
1236
|
+
scope
|
|
1237
|
+
});
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
case "hook": {
|
|
1241
|
+
await handleHook();
|
|
1242
|
+
break;
|
|
1243
|
+
}
|
|
1244
|
+
case "version":
|
|
1245
|
+
case "--version":
|
|
1246
|
+
case "-v": {
|
|
1247
|
+
console.log(`${PACKAGE_NAME} ${PACKAGE_VERSION}`);
|
|
1248
|
+
break;
|
|
1249
|
+
}
|
|
1250
|
+
case "help":
|
|
1251
|
+
case "--help":
|
|
1252
|
+
case "-h":
|
|
1253
|
+
default: {
|
|
1254
|
+
console.log(`
|
|
1255
|
+
${PACKAGE_NAME} v${PACKAGE_VERSION}
|
|
1256
|
+
|
|
1257
|
+
Raindrop observability for Claude Code CLI.
|
|
1258
|
+
|
|
1259
|
+
Commands:
|
|
1260
|
+
setup Configure Claude Code hooks for Raindrop
|
|
1261
|
+
--write-key=KEY Raindrop write key (or set RAINDROP_WRITE_KEY)
|
|
1262
|
+
--user-id=ID User identifier (defaults to system username)
|
|
1263
|
+
--scope=SCOPE "user" (global, default) or "project" (.claude/ in cwd)
|
|
1264
|
+
|
|
1265
|
+
hook Handle a Claude Code hook event (reads JSON from stdin)
|
|
1266
|
+
version Print version
|
|
1267
|
+
help Show this help message
|
|
1268
|
+
|
|
1269
|
+
Environment:
|
|
1270
|
+
RAINDROP_WRITE_KEY API write key (alternative to --write-key or config file)
|
|
1271
|
+
RAINDROP_USER_ID User ID override
|
|
1272
|
+
RAINDROP_API_URL Custom API endpoint
|
|
1273
|
+
RAINDROP_DEBUG Set to "true" for verbose logging
|
|
1274
|
+
`);
|
|
1275
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
1276
|
+
process.exit(0);
|
|
1277
|
+
} else {
|
|
1278
|
+
console.error(` Unknown command: ${command}
|
|
1279
|
+
`);
|
|
1280
|
+
process.exit(1);
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
main().catch((err) => {
|
|
1286
|
+
console.error(`[raindrop-ai/claude-code] fatal: ${err instanceof Error ? err.message : String(err)}`);
|
|
1287
|
+
process.exit(command === "hook" ? 0 : 1);
|
|
1288
|
+
});
|