agentation-vue-mcp 0.0.2

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 ADDED
@@ -0,0 +1,2952 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
+
34
+ // src/server/mcp.ts
35
+ function setHttpBaseUrl(url) {
36
+ httpBaseUrl = url;
37
+ }
38
+ function setApiKey(key) {
39
+ apiKey = key;
40
+ }
41
+ async function httpGet(path2) {
42
+ const headers = {};
43
+ if (apiKey) {
44
+ headers["x-api-key"] = apiKey;
45
+ }
46
+ const res = await fetch(`${httpBaseUrl}${path2}`, { headers });
47
+ if (!res.ok) {
48
+ const body = await res.text();
49
+ throw new Error(`HTTP ${res.status}: ${body}`);
50
+ }
51
+ return res.json();
52
+ }
53
+ async function httpPatch(path2, body) {
54
+ const headers = { "Content-Type": "application/json" };
55
+ if (apiKey) {
56
+ headers["x-api-key"] = apiKey;
57
+ }
58
+ const res = await fetch(`${httpBaseUrl}${path2}`, {
59
+ method: "PATCH",
60
+ headers,
61
+ body: JSON.stringify(body)
62
+ });
63
+ if (!res.ok) {
64
+ const text = await res.text();
65
+ throw new Error(`HTTP ${res.status}: ${text}`);
66
+ }
67
+ return res.json();
68
+ }
69
+ async function httpPost(path2, body) {
70
+ const headers = { "Content-Type": "application/json" };
71
+ if (apiKey) {
72
+ headers["x-api-key"] = apiKey;
73
+ }
74
+ const res = await fetch(`${httpBaseUrl}${path2}`, {
75
+ method: "POST",
76
+ headers,
77
+ body: JSON.stringify(body)
78
+ });
79
+ if (!res.ok) {
80
+ const text = await res.text();
81
+ throw new Error(`HTTP ${res.status}: ${text}`);
82
+ }
83
+ return res.json();
84
+ }
85
+ function success(data) {
86
+ return {
87
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
88
+ };
89
+ }
90
+ function error(message) {
91
+ return {
92
+ content: [{ type: "text", text: message }],
93
+ isError: true
94
+ };
95
+ }
96
+ function watchForAnnotations(sessionId, batchWindowMs, timeoutMs) {
97
+ return new Promise((resolve) => {
98
+ let aborted = false;
99
+ const controller = new AbortController();
100
+ let batchTimeout = null;
101
+ const detectedSessions = /* @__PURE__ */ new Set();
102
+ const collectedAnnotations = [];
103
+ const cleanup = () => {
104
+ aborted = true;
105
+ controller.abort();
106
+ if (batchTimeout) clearTimeout(batchTimeout);
107
+ };
108
+ const timeoutId = setTimeout(() => {
109
+ cleanup();
110
+ resolve({ type: "timeout" });
111
+ }, timeoutMs);
112
+ const sseUrl = sessionId ? `${httpBaseUrl}/sessions/${sessionId}/events?agent=true` : `${httpBaseUrl}/events?agent=true`;
113
+ const sseHeaders = { Accept: "text/event-stream" };
114
+ if (apiKey) {
115
+ sseHeaders["x-api-key"] = apiKey;
116
+ }
117
+ fetch(sseUrl, {
118
+ signal: controller.signal,
119
+ headers: sseHeaders
120
+ }).then(async (res) => {
121
+ if (!res.ok) {
122
+ clearTimeout(timeoutId);
123
+ cleanup();
124
+ resolve({ type: "error", message: `HTTP server returned ${res.status}: ${res.statusText}` });
125
+ return;
126
+ }
127
+ if (!res.body) {
128
+ clearTimeout(timeoutId);
129
+ cleanup();
130
+ resolve({ type: "error", message: "No response body from SSE endpoint" });
131
+ return;
132
+ }
133
+ const reader = res.body.getReader();
134
+ const decoder = new TextDecoder();
135
+ let buffer = "";
136
+ while (!aborted) {
137
+ const { done, value } = await reader.read();
138
+ if (done) {
139
+ if (!aborted) {
140
+ clearTimeout(timeoutId);
141
+ cleanup();
142
+ if (collectedAnnotations.length > 0) {
143
+ resolve({
144
+ type: "annotations",
145
+ annotations: collectedAnnotations,
146
+ sessions: Array.from(detectedSessions)
147
+ });
148
+ } else {
149
+ resolve({ type: "error", message: "SSE connection closed unexpectedly. The agentation server may have restarted." });
150
+ }
151
+ }
152
+ return;
153
+ }
154
+ buffer += decoder.decode(value, { stream: true });
155
+ const lines = buffer.split("\n");
156
+ buffer = lines.pop() || "";
157
+ for (const line of lines) {
158
+ if (line.startsWith("data: ")) {
159
+ try {
160
+ const event = JSON.parse(line.slice(6));
161
+ if (event.type === "annotation.created") {
162
+ if (event.sequence === 0) continue;
163
+ if (sessionId && event.sessionId !== sessionId) continue;
164
+ detectedSessions.add(event.sessionId);
165
+ collectedAnnotations.push(event.payload);
166
+ if (!batchTimeout) {
167
+ batchTimeout = setTimeout(() => {
168
+ clearTimeout(timeoutId);
169
+ cleanup();
170
+ resolve({
171
+ type: "annotations",
172
+ annotations: collectedAnnotations,
173
+ sessions: Array.from(detectedSessions)
174
+ });
175
+ }, batchWindowMs);
176
+ }
177
+ }
178
+ } catch {
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }).catch((err) => {
184
+ if (!aborted) {
185
+ clearTimeout(timeoutId);
186
+ const message = err instanceof Error ? err.message : "Unknown connection error";
187
+ if (message.includes("ECONNREFUSED") || message.includes("fetch failed")) {
188
+ resolve({ type: "error", message: `Cannot connect to HTTP server at ${httpBaseUrl}. Is the agentation server running?` });
189
+ } else if (message.includes("abort")) {
190
+ resolve({ type: "timeout" });
191
+ } else {
192
+ resolve({ type: "error", message: `Connection error: ${message}` });
193
+ }
194
+ }
195
+ });
196
+ });
197
+ }
198
+ async function handleTool(name, args) {
199
+ switch (name) {
200
+ case "agentation_list_sessions": {
201
+ const sessions = await httpGet("/sessions");
202
+ return success({
203
+ sessions: sessions.map((s) => ({
204
+ id: s.id,
205
+ url: s.url,
206
+ status: s.status,
207
+ createdAt: s.createdAt
208
+ }))
209
+ });
210
+ }
211
+ case "agentation_get_session": {
212
+ const { sessionId } = GetSessionSchema.parse(args);
213
+ try {
214
+ const session = await httpGet(`/sessions/${sessionId}`);
215
+ return success(session);
216
+ } catch (err) {
217
+ if (err.message.includes("404")) {
218
+ return error(`Session not found: ${sessionId}`);
219
+ }
220
+ throw err;
221
+ }
222
+ }
223
+ case "agentation_get_pending": {
224
+ const { sessionId } = GetPendingSchema.parse(args);
225
+ const response = await httpGet(`/sessions/${sessionId}/pending`);
226
+ return success({
227
+ count: response.count,
228
+ annotations: response.annotations.map((a) => ({
229
+ id: a.id,
230
+ comment: a.comment,
231
+ element: a.element,
232
+ elementPath: a.elementPath,
233
+ url: a.url,
234
+ intent: a.intent,
235
+ severity: a.severity,
236
+ timestamp: a.timestamp,
237
+ nearbyText: a.nearbyText,
238
+ reactComponents: a.reactComponents
239
+ }))
240
+ });
241
+ }
242
+ case "agentation_get_all_pending": {
243
+ const response = await httpGet("/pending");
244
+ return success({
245
+ count: response.count,
246
+ annotations: response.annotations.map((a) => ({
247
+ id: a.id,
248
+ comment: a.comment,
249
+ element: a.element,
250
+ elementPath: a.elementPath,
251
+ url: a.url,
252
+ intent: a.intent,
253
+ severity: a.severity,
254
+ timestamp: a.timestamp,
255
+ nearbyText: a.nearbyText,
256
+ reactComponents: a.reactComponents
257
+ }))
258
+ });
259
+ }
260
+ case "agentation_acknowledge": {
261
+ const { annotationId } = AcknowledgeSchema.parse(args);
262
+ try {
263
+ await httpPatch(`/annotations/${annotationId}`, { status: "acknowledged" });
264
+ return success({ acknowledged: true, annotationId });
265
+ } catch (err) {
266
+ if (err.message.includes("404")) {
267
+ return error(`Annotation not found: ${annotationId}`);
268
+ }
269
+ throw err;
270
+ }
271
+ }
272
+ case "agentation_resolve": {
273
+ const { annotationId, summary } = ResolveSchema.parse(args);
274
+ try {
275
+ await httpPatch(`/annotations/${annotationId}`, {
276
+ status: "resolved",
277
+ resolvedBy: "agent"
278
+ });
279
+ if (summary) {
280
+ await httpPost(`/annotations/${annotationId}/thread`, {
281
+ role: "agent",
282
+ content: `Resolved: ${summary}`
283
+ });
284
+ }
285
+ return success({ resolved: true, annotationId, summary });
286
+ } catch (err) {
287
+ if (err.message.includes("404")) {
288
+ return error(`Annotation not found: ${annotationId}`);
289
+ }
290
+ throw err;
291
+ }
292
+ }
293
+ case "agentation_dismiss": {
294
+ const { annotationId, reason } = DismissSchema.parse(args);
295
+ try {
296
+ await httpPatch(`/annotations/${annotationId}`, {
297
+ status: "dismissed",
298
+ resolvedBy: "agent"
299
+ });
300
+ await httpPost(`/annotations/${annotationId}/thread`, {
301
+ role: "agent",
302
+ content: `Dismissed: ${reason}`
303
+ });
304
+ return success({ dismissed: true, annotationId, reason });
305
+ } catch (err) {
306
+ if (err.message.includes("404")) {
307
+ return error(`Annotation not found: ${annotationId}`);
308
+ }
309
+ throw err;
310
+ }
311
+ }
312
+ case "agentation_reply": {
313
+ const { annotationId, message } = ReplySchema.parse(args);
314
+ try {
315
+ await httpPost(`/annotations/${annotationId}/thread`, {
316
+ role: "agent",
317
+ content: message
318
+ });
319
+ return success({ replied: true, annotationId, message });
320
+ } catch (err) {
321
+ if (err.message.includes("404")) {
322
+ return error(`Annotation not found: ${annotationId}`);
323
+ }
324
+ throw err;
325
+ }
326
+ }
327
+ case "agentation_watch_annotations": {
328
+ const parsed = WatchAnnotationsSchema.parse(args);
329
+ const sessionId = parsed.sessionId;
330
+ const batchWindowSeconds = Math.min(60, Math.max(1, parsed.batchWindowSeconds ?? 10));
331
+ const timeoutSeconds = Math.min(300, Math.max(1, parsed.timeoutSeconds ?? 120));
332
+ try {
333
+ const pendingPath = sessionId ? `/sessions/${sessionId}/pending` : "/pending";
334
+ const pending = await httpGet(pendingPath);
335
+ if (pending.count > 0) {
336
+ const sessions = [...new Set(pending.annotations.map((a) => a.sessionId))];
337
+ return success({
338
+ timeout: false,
339
+ count: pending.count,
340
+ sessions,
341
+ annotations: pending.annotations.map((a) => ({
342
+ id: a.id,
343
+ comment: a.comment,
344
+ element: a.element,
345
+ elementPath: a.elementPath,
346
+ url: a.url,
347
+ intent: a.intent,
348
+ severity: a.severity,
349
+ timestamp: a.timestamp,
350
+ nearbyText: a.nearbyText,
351
+ reactComponents: a.reactComponents
352
+ }))
353
+ });
354
+ }
355
+ } catch (err) {
356
+ console.error("[MCP] Pending drain failed, falling through to SSE watch:", err);
357
+ }
358
+ const result = await watchForAnnotations(
359
+ sessionId,
360
+ batchWindowSeconds * 1e3,
361
+ timeoutSeconds * 1e3
362
+ );
363
+ switch (result.type) {
364
+ case "annotations":
365
+ return success({
366
+ timeout: false,
367
+ count: result.annotations.length,
368
+ sessions: result.sessions,
369
+ annotations: result.annotations.map((a) => ({
370
+ id: a.id,
371
+ comment: a.comment,
372
+ element: a.element,
373
+ elementPath: a.elementPath,
374
+ url: a.url,
375
+ intent: a.intent,
376
+ severity: a.severity,
377
+ timestamp: a.timestamp,
378
+ nearbyText: a.nearbyText,
379
+ reactComponents: a.reactComponents
380
+ }))
381
+ });
382
+ case "timeout":
383
+ return success({
384
+ timeout: true,
385
+ message: `No new annotations within ${timeoutSeconds} seconds`
386
+ });
387
+ case "error":
388
+ return error(result.message);
389
+ }
390
+ }
391
+ default:
392
+ return error(`Unknown tool: ${name}`);
393
+ }
394
+ }
395
+ async function startMcpServer(baseUrl) {
396
+ if (baseUrl) {
397
+ setHttpBaseUrl(baseUrl);
398
+ }
399
+ const server = new import_server.Server(
400
+ {
401
+ name: "agentation",
402
+ version: "0.0.1"
403
+ },
404
+ {
405
+ capabilities: {
406
+ tools: {}
407
+ }
408
+ }
409
+ );
410
+ server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
411
+ return { tools: TOOLS };
412
+ });
413
+ server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
414
+ const { name, arguments: args } = request.params;
415
+ try {
416
+ return await handleTool(name, args);
417
+ } catch (err) {
418
+ const message = err instanceof Error ? err.message : "Unknown error";
419
+ return error(message);
420
+ }
421
+ });
422
+ const transport = new import_stdio.StdioServerTransport();
423
+ await server.connect(transport);
424
+ const isRemote = httpBaseUrl.startsWith("https://") || !httpBaseUrl.includes("localhost") && !httpBaseUrl.includes("127.0.0.1");
425
+ if (isRemote && apiKey) {
426
+ console.error(`[MCP] Agentation MCP server started on stdio (Remote: ${httpBaseUrl}, API key: configured)`);
427
+ } else if (isRemote) {
428
+ console.error(`[MCP] Agentation MCP server started on stdio (Remote: ${httpBaseUrl}, API key: not configured)`);
429
+ } else {
430
+ console.error(`[MCP] Agentation MCP server started on stdio (HTTP: ${httpBaseUrl})`);
431
+ }
432
+ }
433
+ var import_server, import_stdio, import_types, import_zod, httpBaseUrl, apiKey, GetPendingSchema, AcknowledgeSchema, ResolveSchema, DismissSchema, ReplySchema, GetSessionSchema, WatchAnnotationsSchema, TOOLS;
434
+ var init_mcp = __esm({
435
+ "src/server/mcp.ts"() {
436
+ "use strict";
437
+ import_server = require("@modelcontextprotocol/sdk/server/index.js");
438
+ import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
439
+ import_types = require("@modelcontextprotocol/sdk/types.js");
440
+ import_zod = require("zod");
441
+ httpBaseUrl = "http://localhost:4747";
442
+ GetPendingSchema = import_zod.z.object({
443
+ sessionId: import_zod.z.string().describe("The session ID to get pending annotations for")
444
+ });
445
+ AcknowledgeSchema = import_zod.z.object({
446
+ annotationId: import_zod.z.string().describe("The annotation ID to acknowledge")
447
+ });
448
+ ResolveSchema = import_zod.z.object({
449
+ annotationId: import_zod.z.string().describe("The annotation ID to resolve"),
450
+ summary: import_zod.z.string().optional().describe("Optional summary of how it was resolved")
451
+ });
452
+ DismissSchema = import_zod.z.object({
453
+ annotationId: import_zod.z.string().describe("The annotation ID to dismiss"),
454
+ reason: import_zod.z.string().describe("Reason for dismissing this annotation")
455
+ });
456
+ ReplySchema = import_zod.z.object({
457
+ annotationId: import_zod.z.string().describe("The annotation ID to reply to"),
458
+ message: import_zod.z.string().describe("The reply message")
459
+ });
460
+ GetSessionSchema = import_zod.z.object({
461
+ sessionId: import_zod.z.string().describe("The session ID to get")
462
+ });
463
+ WatchAnnotationsSchema = import_zod.z.object({
464
+ sessionId: import_zod.z.string().optional().describe("Optional session ID to filter. If not provided, watches ALL sessions."),
465
+ batchWindowSeconds: import_zod.z.number().optional().default(10).describe("Seconds to wait after first annotation before returning batch (default: 10, max: 60)"),
466
+ timeoutSeconds: import_zod.z.number().optional().default(120).describe("Max seconds to wait for first annotation (default: 120, max: 300)")
467
+ });
468
+ TOOLS = [
469
+ {
470
+ name: "agentation_list_sessions",
471
+ description: "List all active annotation sessions",
472
+ inputSchema: {
473
+ type: "object",
474
+ properties: {},
475
+ required: []
476
+ }
477
+ },
478
+ {
479
+ name: "agentation_get_session",
480
+ description: "Get a session with all its annotations",
481
+ inputSchema: {
482
+ type: "object",
483
+ properties: {
484
+ sessionId: {
485
+ type: "string",
486
+ description: "The session ID to get"
487
+ }
488
+ },
489
+ required: ["sessionId"]
490
+ }
491
+ },
492
+ {
493
+ name: "agentation_get_pending",
494
+ description: "Get all pending (unacknowledged) annotations for a session. Use this to see what feedback the human has given that needs attention.",
495
+ inputSchema: {
496
+ type: "object",
497
+ properties: {
498
+ sessionId: {
499
+ type: "string",
500
+ description: "The session ID to get pending annotations for"
501
+ }
502
+ },
503
+ required: ["sessionId"]
504
+ }
505
+ },
506
+ {
507
+ name: "agentation_get_all_pending",
508
+ description: "Get all pending annotations across ALL sessions. Use this to see all unaddressed feedback from the human across all pages they've visited.",
509
+ inputSchema: {
510
+ type: "object",
511
+ properties: {},
512
+ required: []
513
+ }
514
+ },
515
+ {
516
+ name: "agentation_acknowledge",
517
+ description: "Mark an annotation as acknowledged. Use this to let the human know you've seen their feedback and will address it.",
518
+ inputSchema: {
519
+ type: "object",
520
+ properties: {
521
+ annotationId: {
522
+ type: "string",
523
+ description: "The annotation ID to acknowledge"
524
+ }
525
+ },
526
+ required: ["annotationId"]
527
+ }
528
+ },
529
+ {
530
+ name: "agentation_resolve",
531
+ description: "Mark an annotation as resolved. Use this after you've addressed the feedback. Optionally include a summary of what you did.",
532
+ inputSchema: {
533
+ type: "object",
534
+ properties: {
535
+ annotationId: {
536
+ type: "string",
537
+ description: "The annotation ID to resolve"
538
+ },
539
+ summary: {
540
+ type: "string",
541
+ description: "Optional summary of how it was resolved"
542
+ }
543
+ },
544
+ required: ["annotationId"]
545
+ }
546
+ },
547
+ {
548
+ name: "agentation_dismiss",
549
+ description: "Dismiss an annotation. Use this when you've decided not to address the feedback, with a reason why.",
550
+ inputSchema: {
551
+ type: "object",
552
+ properties: {
553
+ annotationId: {
554
+ type: "string",
555
+ description: "The annotation ID to dismiss"
556
+ },
557
+ reason: {
558
+ type: "string",
559
+ description: "Reason for dismissing this annotation"
560
+ }
561
+ },
562
+ required: ["annotationId", "reason"]
563
+ }
564
+ },
565
+ {
566
+ name: "agentation_reply",
567
+ description: "Add a reply to an annotation's thread. Use this to ask clarifying questions or provide updates to the human.",
568
+ inputSchema: {
569
+ type: "object",
570
+ properties: {
571
+ annotationId: {
572
+ type: "string",
573
+ description: "The annotation ID to reply to"
574
+ },
575
+ message: {
576
+ type: "string",
577
+ description: "The reply message"
578
+ }
579
+ },
580
+ required: ["annotationId", "message"]
581
+ }
582
+ },
583
+ {
584
+ name: "agentation_watch_annotations",
585
+ description: "Block until new annotations appear, then collect a batch and return them. Triggers automatically when annotations are created \u2014 the user just annotates in the browser and the agent picks them up. After detecting the first new annotation, waits for a batch window to collect more before returning. Use in a loop for hands-free processing. After addressing each annotation, call agentation_resolve with the annotation ID and a summary of what you did. Only resolve annotations the user accepted \u2014 if the user rejects your change, leave the annotation open.",
586
+ inputSchema: {
587
+ type: "object",
588
+ properties: {
589
+ sessionId: {
590
+ type: "string",
591
+ description: "Optional session ID to filter. If not provided, watches ALL sessions."
592
+ },
593
+ batchWindowSeconds: {
594
+ type: "number",
595
+ description: "Seconds to wait after first annotation before returning batch (default: 10, max: 60)"
596
+ },
597
+ timeoutSeconds: {
598
+ type: "number",
599
+ description: "Max seconds to wait for first annotation (default: 120, max: 300)"
600
+ }
601
+ },
602
+ required: []
603
+ }
604
+ }
605
+ ];
606
+ }
607
+ });
608
+
609
+ // src/server/events.ts
610
+ var globalSequence, EventBus, eventBus, UserEventBus, userEventBus;
611
+ var init_events = __esm({
612
+ "src/server/events.ts"() {
613
+ "use strict";
614
+ globalSequence = 0;
615
+ EventBus = class {
616
+ handlers = /* @__PURE__ */ new Set();
617
+ sessionHandlers = /* @__PURE__ */ new Map();
618
+ /**
619
+ * Subscribe to all events.
620
+ */
621
+ subscribe(handler) {
622
+ this.handlers.add(handler);
623
+ return () => this.handlers.delete(handler);
624
+ }
625
+ /**
626
+ * Subscribe to events for a specific session.
627
+ */
628
+ subscribeToSession(sessionId, handler) {
629
+ if (!this.sessionHandlers.has(sessionId)) {
630
+ this.sessionHandlers.set(sessionId, /* @__PURE__ */ new Set());
631
+ }
632
+ this.sessionHandlers.get(sessionId).add(handler);
633
+ return () => {
634
+ const handlers = this.sessionHandlers.get(sessionId);
635
+ if (handlers) {
636
+ handlers.delete(handler);
637
+ if (handlers.size === 0) {
638
+ this.sessionHandlers.delete(sessionId);
639
+ }
640
+ }
641
+ };
642
+ }
643
+ /**
644
+ * Emit an event to all subscribers.
645
+ */
646
+ emit(type, sessionId, payload) {
647
+ const event = {
648
+ type,
649
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
650
+ sessionId,
651
+ sequence: ++globalSequence,
652
+ payload
653
+ };
654
+ for (const handler of this.handlers) {
655
+ try {
656
+ handler(event);
657
+ } catch (err) {
658
+ console.error("[EventBus] Handler error:", err);
659
+ }
660
+ }
661
+ const sessionHandlers = this.sessionHandlers.get(sessionId);
662
+ if (sessionHandlers) {
663
+ for (const handler of sessionHandlers) {
664
+ try {
665
+ handler(event);
666
+ } catch (err) {
667
+ console.error("[EventBus] Session handler error:", err);
668
+ }
669
+ }
670
+ }
671
+ return event;
672
+ }
673
+ /**
674
+ * Get current sequence number (for reconnect logic).
675
+ */
676
+ getSequence() {
677
+ return globalSequence;
678
+ }
679
+ /**
680
+ * Set sequence from persisted state (for server restart).
681
+ */
682
+ setSequence(seq) {
683
+ globalSequence = seq;
684
+ }
685
+ };
686
+ eventBus = new EventBus();
687
+ UserEventBus = class {
688
+ userHandlers = /* @__PURE__ */ new Map();
689
+ userSessionHandlers = /* @__PURE__ */ new Map();
690
+ /**
691
+ * Subscribe to all events for a specific user.
692
+ */
693
+ subscribeForUser(userId, handler) {
694
+ if (!this.userHandlers.has(userId)) {
695
+ this.userHandlers.set(userId, /* @__PURE__ */ new Set());
696
+ }
697
+ this.userHandlers.get(userId).add(handler);
698
+ return () => {
699
+ const handlers = this.userHandlers.get(userId);
700
+ if (handlers) {
701
+ handlers.delete(handler);
702
+ if (handlers.size === 0) {
703
+ this.userHandlers.delete(userId);
704
+ }
705
+ }
706
+ };
707
+ }
708
+ /**
709
+ * Subscribe to events for a specific session of a specific user.
710
+ */
711
+ subscribeToSessionForUser(userId, sessionId, handler) {
712
+ if (!this.userSessionHandlers.has(userId)) {
713
+ this.userSessionHandlers.set(userId, /* @__PURE__ */ new Map());
714
+ }
715
+ const userSessions = this.userSessionHandlers.get(userId);
716
+ if (!userSessions.has(sessionId)) {
717
+ userSessions.set(sessionId, /* @__PURE__ */ new Set());
718
+ }
719
+ userSessions.get(sessionId).add(handler);
720
+ return () => {
721
+ const userSessions2 = this.userSessionHandlers.get(userId);
722
+ if (userSessions2) {
723
+ const handlers = userSessions2.get(sessionId);
724
+ if (handlers) {
725
+ handlers.delete(handler);
726
+ if (handlers.size === 0) {
727
+ userSessions2.delete(sessionId);
728
+ }
729
+ }
730
+ if (userSessions2.size === 0) {
731
+ this.userSessionHandlers.delete(userId);
732
+ }
733
+ }
734
+ };
735
+ }
736
+ /**
737
+ * Emit an event scoped to a specific user.
738
+ * Only handlers for that user will receive the event.
739
+ */
740
+ emitForUser(userId, type, sessionId, payload) {
741
+ const event = {
742
+ type,
743
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
744
+ sessionId,
745
+ sequence: ++globalSequence,
746
+ payload
747
+ };
748
+ const userHandlers = this.userHandlers.get(userId);
749
+ if (userHandlers) {
750
+ for (const handler of userHandlers) {
751
+ try {
752
+ handler(event);
753
+ } catch (err) {
754
+ console.error("[UserEventBus] Handler error:", err);
755
+ }
756
+ }
757
+ }
758
+ const userSessions = this.userSessionHandlers.get(userId);
759
+ if (userSessions) {
760
+ const sessionHandlers = userSessions.get(sessionId);
761
+ if (sessionHandlers) {
762
+ for (const handler of sessionHandlers) {
763
+ try {
764
+ handler(event);
765
+ } catch (err) {
766
+ console.error("[UserEventBus] Session handler error:", err);
767
+ }
768
+ }
769
+ }
770
+ }
771
+ return event;
772
+ }
773
+ /**
774
+ * Check if a user has any active listeners.
775
+ */
776
+ hasListenersForUser(userId) {
777
+ const hasGlobal = this.userHandlers.has(userId) && this.userHandlers.get(userId).size > 0;
778
+ const hasSessions = this.userSessionHandlers.has(userId) && this.userSessionHandlers.get(userId).size > 0;
779
+ return hasGlobal || hasSessions;
780
+ }
781
+ /**
782
+ * Get count of listeners for a user.
783
+ */
784
+ getListenerCountForUser(userId) {
785
+ let count = 0;
786
+ const handlers = this.userHandlers.get(userId);
787
+ if (handlers) count += handlers.size;
788
+ const sessions = this.userSessionHandlers.get(userId);
789
+ if (sessions) {
790
+ for (const sessionHandlers of sessions.values()) {
791
+ count += sessionHandlers.size;
792
+ }
793
+ }
794
+ return count;
795
+ }
796
+ };
797
+ userEventBus = new UserEventBus();
798
+ }
799
+ });
800
+
801
+ // src/server/sqlite.ts
802
+ var sqlite_exports = {};
803
+ __export(sqlite_exports, {
804
+ createSQLiteStore: () => createSQLiteStore,
805
+ createTenantStore: () => createTenantStore
806
+ });
807
+ function getDbPath() {
808
+ const dataDir = (0, import_path.join)((0, import_os.homedir)(), ".agentation");
809
+ if (!(0, import_fs.existsSync)(dataDir)) {
810
+ (0, import_fs.mkdirSync)(dataDir, { recursive: true });
811
+ }
812
+ return (0, import_path.join)(dataDir, "store.db");
813
+ }
814
+ function initDatabase(db) {
815
+ db.exec(`
816
+ -- Multi-tenant tables
817
+ CREATE TABLE IF NOT EXISTS organizations (
818
+ id TEXT PRIMARY KEY,
819
+ name TEXT NOT NULL,
820
+ created_at TEXT NOT NULL,
821
+ updated_at TEXT
822
+ );
823
+
824
+ CREATE TABLE IF NOT EXISTS users (
825
+ id TEXT PRIMARY KEY,
826
+ email TEXT NOT NULL UNIQUE,
827
+ org_id TEXT NOT NULL,
828
+ role TEXT NOT NULL DEFAULT 'member',
829
+ created_at TEXT NOT NULL,
830
+ updated_at TEXT,
831
+ FOREIGN KEY (org_id) REFERENCES organizations(id)
832
+ );
833
+
834
+ CREATE TABLE IF NOT EXISTS api_keys (
835
+ id TEXT PRIMARY KEY,
836
+ key_prefix TEXT NOT NULL,
837
+ key_hash TEXT NOT NULL UNIQUE,
838
+ user_id TEXT NOT NULL,
839
+ name TEXT NOT NULL,
840
+ created_at TEXT NOT NULL,
841
+ expires_at TEXT,
842
+ last_used_at TEXT,
843
+ FOREIGN KEY (user_id) REFERENCES users(id)
844
+ );
845
+
846
+ CREATE TABLE IF NOT EXISTS sessions (
847
+ id TEXT PRIMARY KEY,
848
+ url TEXT NOT NULL,
849
+ status TEXT NOT NULL DEFAULT 'active',
850
+ created_at TEXT NOT NULL,
851
+ updated_at TEXT,
852
+ project_id TEXT,
853
+ metadata TEXT,
854
+ user_id TEXT,
855
+ FOREIGN KEY (user_id) REFERENCES users(id)
856
+ );
857
+
858
+ CREATE TABLE IF NOT EXISTS annotations (
859
+ id TEXT PRIMARY KEY,
860
+ session_id TEXT NOT NULL,
861
+ x REAL NOT NULL,
862
+ y REAL NOT NULL,
863
+ comment TEXT NOT NULL,
864
+ element TEXT NOT NULL,
865
+ element_path TEXT NOT NULL,
866
+ timestamp INTEGER NOT NULL,
867
+ selected_text TEXT,
868
+ bounding_box TEXT,
869
+ nearby_text TEXT,
870
+ css_classes TEXT,
871
+ nearby_elements TEXT,
872
+ computed_styles TEXT,
873
+ full_path TEXT,
874
+ accessibility TEXT,
875
+ is_multi_select INTEGER DEFAULT 0,
876
+ is_fixed INTEGER DEFAULT 0,
877
+ react_components TEXT,
878
+ url TEXT,
879
+ intent TEXT,
880
+ severity TEXT,
881
+ status TEXT DEFAULT 'pending',
882
+ thread TEXT,
883
+ created_at TEXT NOT NULL,
884
+ updated_at TEXT,
885
+ resolved_at TEXT,
886
+ resolved_by TEXT,
887
+ author_id TEXT,
888
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
889
+ );
890
+
891
+ CREATE TABLE IF NOT EXISTS annotations_v2 (
892
+ id TEXT PRIMARY KEY,
893
+ session_id TEXT NOT NULL,
894
+ schema_version INTEGER NOT NULL DEFAULT 1,
895
+ timestamp TEXT NOT NULL,
896
+ url TEXT NOT NULL,
897
+ element_selector TEXT NOT NULL,
898
+ element_text TEXT,
899
+ comment TEXT NOT NULL,
900
+ source TEXT NOT NULL,
901
+ metadata TEXT,
902
+ created_at TEXT NOT NULL,
903
+ updated_at TEXT,
904
+ FOREIGN KEY (session_id) REFERENCES sessions(id)
905
+ );
906
+
907
+ CREATE TABLE IF NOT EXISTS events (
908
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
909
+ type TEXT NOT NULL,
910
+ timestamp TEXT NOT NULL,
911
+ session_id TEXT NOT NULL,
912
+ sequence INTEGER NOT NULL UNIQUE,
913
+ payload TEXT NOT NULL,
914
+ user_id TEXT,
915
+ FOREIGN KEY (user_id) REFERENCES users(id)
916
+ );
917
+
918
+ -- Indexes
919
+ CREATE INDEX IF NOT EXISTS idx_users_org ON users(org_id);
920
+ CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
921
+ CREATE INDEX IF NOT EXISTS idx_api_keys_user ON api_keys(user_id);
922
+ CREATE INDEX IF NOT EXISTS idx_api_keys_hash ON api_keys(key_hash);
923
+ CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id);
924
+ CREATE INDEX IF NOT EXISTS idx_annotations_session ON annotations(session_id);
925
+ CREATE INDEX IF NOT EXISTS idx_annotations_v2_session ON annotations_v2(session_id);
926
+ CREATE INDEX IF NOT EXISTS idx_events_session_seq ON events(session_id, sequence);
927
+ CREATE INDEX IF NOT EXISTS idx_events_user ON events(user_id);
928
+ `);
929
+ }
930
+ function generateId() {
931
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
932
+ }
933
+ function rowToSession(row) {
934
+ return {
935
+ id: row.id,
936
+ url: row.url,
937
+ status: row.status,
938
+ createdAt: row.created_at,
939
+ updatedAt: row.updated_at,
940
+ projectId: row.project_id,
941
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
942
+ userId: row.user_id
943
+ };
944
+ }
945
+ function rowToOrganization(row) {
946
+ return {
947
+ id: row.id,
948
+ name: row.name,
949
+ createdAt: row.created_at,
950
+ updatedAt: row.updated_at
951
+ };
952
+ }
953
+ function rowToUser(row) {
954
+ return {
955
+ id: row.id,
956
+ email: row.email,
957
+ orgId: row.org_id,
958
+ role: row.role,
959
+ createdAt: row.created_at,
960
+ updatedAt: row.updated_at
961
+ };
962
+ }
963
+ function rowToApiKey(row) {
964
+ return {
965
+ id: row.id,
966
+ keyPrefix: row.key_prefix,
967
+ keyHash: row.key_hash,
968
+ userId: row.user_id,
969
+ name: row.name,
970
+ createdAt: row.created_at,
971
+ expiresAt: row.expires_at,
972
+ lastUsedAt: row.last_used_at
973
+ };
974
+ }
975
+ function rowToAnnotation(row) {
976
+ return {
977
+ id: row.id,
978
+ sessionId: row.session_id,
979
+ x: row.x,
980
+ y: row.y,
981
+ comment: row.comment,
982
+ element: row.element,
983
+ elementPath: row.element_path,
984
+ timestamp: row.timestamp,
985
+ selectedText: row.selected_text,
986
+ boundingBox: row.bounding_box ? JSON.parse(row.bounding_box) : void 0,
987
+ nearbyText: row.nearby_text,
988
+ cssClasses: row.css_classes,
989
+ nearbyElements: row.nearby_elements,
990
+ computedStyles: row.computed_styles,
991
+ fullPath: row.full_path,
992
+ accessibility: row.accessibility,
993
+ isMultiSelect: Boolean(row.is_multi_select),
994
+ isFixed: Boolean(row.is_fixed),
995
+ reactComponents: row.react_components,
996
+ url: row.url,
997
+ intent: row.intent,
998
+ severity: row.severity,
999
+ status: row.status,
1000
+ thread: row.thread ? JSON.parse(row.thread) : void 0,
1001
+ createdAt: row.created_at,
1002
+ updatedAt: row.updated_at,
1003
+ resolvedAt: row.resolved_at,
1004
+ resolvedBy: row.resolved_by,
1005
+ authorId: row.author_id
1006
+ };
1007
+ }
1008
+ function rowToAnnotationV2(row) {
1009
+ return {
1010
+ id: row.id,
1011
+ schemaVersion: row.schema_version,
1012
+ timestamp: row.timestamp,
1013
+ url: row.url,
1014
+ elementSelector: row.element_selector,
1015
+ elementText: row.element_text,
1016
+ comment: row.comment,
1017
+ source: JSON.parse(row.source),
1018
+ metadata: row.metadata ? JSON.parse(row.metadata) : void 0,
1019
+ sessionId: row.session_id,
1020
+ createdAt: row.created_at,
1021
+ updatedAt: row.updated_at
1022
+ };
1023
+ }
1024
+ function createSQLiteStore(dbPath) {
1025
+ const db = new import_better_sqlite3.default(dbPath ?? getDbPath());
1026
+ db.pragma("journal_mode = WAL");
1027
+ initDatabase(db);
1028
+ const lastEvent = db.prepare("SELECT MAX(sequence) as seq FROM events").get();
1029
+ if (lastEvent?.seq) {
1030
+ eventBus.setSequence(lastEvent.seq);
1031
+ }
1032
+ const stmts = {
1033
+ // Sessions
1034
+ insertSession: db.prepare(`
1035
+ INSERT INTO sessions (id, url, status, created_at, project_id, metadata)
1036
+ VALUES (@id, @url, @status, @createdAt, @projectId, @metadata)
1037
+ `),
1038
+ getSession: db.prepare("SELECT * FROM sessions WHERE id = ?"),
1039
+ updateSessionStatus: db.prepare(`
1040
+ UPDATE sessions SET status = @status, updated_at = @updatedAt WHERE id = @id
1041
+ `),
1042
+ listSessions: db.prepare("SELECT * FROM sessions ORDER BY created_at DESC"),
1043
+ // Annotations
1044
+ insertAnnotation: db.prepare(`
1045
+ INSERT INTO annotations (
1046
+ id, session_id, x, y, comment, element, element_path, timestamp,
1047
+ selected_text, bounding_box, nearby_text, css_classes, nearby_elements,
1048
+ computed_styles, full_path, accessibility, is_multi_select, is_fixed,
1049
+ react_components, url, intent, severity, status, thread, created_at,
1050
+ updated_at, resolved_at, resolved_by, author_id
1051
+ ) VALUES (
1052
+ @id, @sessionId, @x, @y, @comment, @element, @elementPath, @timestamp,
1053
+ @selectedText, @boundingBox, @nearbyText, @cssClasses, @nearbyElements,
1054
+ @computedStyles, @fullPath, @accessibility, @isMultiSelect, @isFixed,
1055
+ @reactComponents, @url, @intent, @severity, @status, @thread, @createdAt,
1056
+ @updatedAt, @resolvedAt, @resolvedBy, @authorId
1057
+ )
1058
+ `),
1059
+ getAnnotation: db.prepare("SELECT * FROM annotations WHERE id = ?"),
1060
+ getAnnotationsBySession: db.prepare("SELECT * FROM annotations WHERE session_id = ? ORDER BY timestamp"),
1061
+ getPendingAnnotations: db.prepare("SELECT * FROM annotations WHERE session_id = ? AND status = 'pending' ORDER BY timestamp"),
1062
+ deleteAnnotation: db.prepare("DELETE FROM annotations WHERE id = ?"),
1063
+ updateAnnotation: db.prepare(`
1064
+ UPDATE annotations SET
1065
+ comment = COALESCE(@comment, comment),
1066
+ status = COALESCE(@status, status),
1067
+ updated_at = @updatedAt,
1068
+ resolved_at = COALESCE(@resolvedAt, resolved_at),
1069
+ resolved_by = COALESCE(@resolvedBy, resolved_by),
1070
+ thread = COALESCE(@thread, thread),
1071
+ intent = COALESCE(@intent, intent),
1072
+ severity = COALESCE(@severity, severity)
1073
+ WHERE id = @id
1074
+ `),
1075
+ // Events
1076
+ insertEvent: db.prepare(`
1077
+ INSERT INTO events (type, timestamp, session_id, sequence, payload)
1078
+ VALUES (@type, @timestamp, @sessionId, @sequence, @payload)
1079
+ `),
1080
+ getEventsSince: db.prepare(`
1081
+ SELECT * FROM events WHERE session_id = ? AND sequence > ? ORDER BY sequence
1082
+ `),
1083
+ pruneOldEvents: db.prepare(`
1084
+ DELETE FROM events WHERE timestamp < ?
1085
+ `),
1086
+ // Annotations V2
1087
+ insertAnnotationV2: db.prepare(`
1088
+ INSERT INTO annotations_v2 (
1089
+ id, session_id, schema_version, timestamp, url, element_selector,
1090
+ element_text, comment, source, metadata, created_at, updated_at
1091
+ ) VALUES (
1092
+ @id, @sessionId, @schemaVersion, @timestamp, @url, @elementSelector,
1093
+ @elementText, @comment, @source, @metadata, @createdAt, @updatedAt
1094
+ )
1095
+ `),
1096
+ getAnnotationV2: db.prepare("SELECT * FROM annotations_v2 WHERE id = ?"),
1097
+ getAnnotationsV2BySession: db.prepare(
1098
+ "SELECT * FROM annotations_v2 WHERE session_id = ? ORDER BY created_at"
1099
+ ),
1100
+ updateAnnotationV2: db.prepare(`
1101
+ UPDATE annotations_v2 SET
1102
+ element_text = COALESCE(@elementText, element_text),
1103
+ comment = COALESCE(@comment, comment),
1104
+ source = COALESCE(@source, source),
1105
+ metadata = COALESCE(@metadata, metadata),
1106
+ updated_at = @updatedAt
1107
+ WHERE id = @id
1108
+ `),
1109
+ deleteAnnotationV2: db.prepare("DELETE FROM annotations_v2 WHERE id = ?")
1110
+ };
1111
+ const retentionDays = parseInt(process.env.AGENTATION_EVENT_RETENTION_DAYS || "7", 10);
1112
+ const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1e3).toISOString();
1113
+ stmts.pruneOldEvents.run(cutoff);
1114
+ function persistEvent(event) {
1115
+ stmts.insertEvent.run({
1116
+ type: event.type,
1117
+ timestamp: event.timestamp,
1118
+ sessionId: event.sessionId,
1119
+ sequence: event.sequence,
1120
+ payload: JSON.stringify(event.payload)
1121
+ });
1122
+ }
1123
+ return {
1124
+ // Sessions
1125
+ createSession(url, projectId) {
1126
+ const session = {
1127
+ id: generateId(),
1128
+ url,
1129
+ status: "active",
1130
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1131
+ projectId
1132
+ };
1133
+ stmts.insertSession.run({
1134
+ id: session.id,
1135
+ url: session.url,
1136
+ status: session.status,
1137
+ createdAt: session.createdAt,
1138
+ projectId: session.projectId ?? null,
1139
+ metadata: null
1140
+ });
1141
+ const event = eventBus.emit("session.created", session.id, session);
1142
+ persistEvent(event);
1143
+ return session;
1144
+ },
1145
+ getSession(id) {
1146
+ const row = stmts.getSession.get(id);
1147
+ return row ? rowToSession(row) : void 0;
1148
+ },
1149
+ getSessionWithAnnotations(id) {
1150
+ const sessionRow = stmts.getSession.get(id);
1151
+ if (!sessionRow) return void 0;
1152
+ const annotationRows = stmts.getAnnotationsBySession.all(id);
1153
+ return {
1154
+ ...rowToSession(sessionRow),
1155
+ annotations: annotationRows.map(rowToAnnotation)
1156
+ };
1157
+ },
1158
+ updateSessionStatus(id, status) {
1159
+ const updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1160
+ const result = stmts.updateSessionStatus.run({ id, status, updatedAt });
1161
+ if (result.changes === 0) return void 0;
1162
+ const session = this.getSession(id);
1163
+ if (session) {
1164
+ const eventType = status === "closed" ? "session.closed" : "session.updated";
1165
+ const event = eventBus.emit(eventType, id, session);
1166
+ persistEvent(event);
1167
+ }
1168
+ return session;
1169
+ },
1170
+ listSessions() {
1171
+ const rows = stmts.listSessions.all();
1172
+ return rows.map(rowToSession);
1173
+ },
1174
+ // Annotations
1175
+ addAnnotation(sessionId, data) {
1176
+ const session = this.getSession(sessionId);
1177
+ if (!session) return void 0;
1178
+ const annotation = {
1179
+ ...data,
1180
+ id: generateId(),
1181
+ sessionId,
1182
+ status: "pending",
1183
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1184
+ };
1185
+ stmts.insertAnnotation.run({
1186
+ id: annotation.id,
1187
+ sessionId: annotation.sessionId,
1188
+ x: annotation.x,
1189
+ y: annotation.y,
1190
+ comment: annotation.comment,
1191
+ element: annotation.element,
1192
+ elementPath: annotation.elementPath,
1193
+ timestamp: annotation.timestamp,
1194
+ selectedText: annotation.selectedText ?? null,
1195
+ boundingBox: annotation.boundingBox ? JSON.stringify(annotation.boundingBox) : null,
1196
+ nearbyText: annotation.nearbyText ?? null,
1197
+ cssClasses: annotation.cssClasses ?? null,
1198
+ nearbyElements: annotation.nearbyElements ?? null,
1199
+ computedStyles: annotation.computedStyles ?? null,
1200
+ fullPath: annotation.fullPath ?? null,
1201
+ accessibility: annotation.accessibility ?? null,
1202
+ isMultiSelect: annotation.isMultiSelect ? 1 : 0,
1203
+ isFixed: annotation.isFixed ? 1 : 0,
1204
+ reactComponents: annotation.reactComponents ?? null,
1205
+ url: annotation.url ?? null,
1206
+ intent: annotation.intent ?? null,
1207
+ severity: annotation.severity ?? null,
1208
+ status: annotation.status ?? "pending",
1209
+ thread: annotation.thread ? JSON.stringify(annotation.thread) : null,
1210
+ createdAt: annotation.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
1211
+ updatedAt: null,
1212
+ resolvedAt: null,
1213
+ resolvedBy: null,
1214
+ authorId: annotation.authorId ?? null
1215
+ });
1216
+ const event = eventBus.emit("annotation.created", sessionId, annotation);
1217
+ persistEvent(event);
1218
+ return annotation;
1219
+ },
1220
+ getAnnotation(id) {
1221
+ const row = stmts.getAnnotation.get(id);
1222
+ return row ? rowToAnnotation(row) : void 0;
1223
+ },
1224
+ updateAnnotation(id, data) {
1225
+ const existing = this.getAnnotation(id);
1226
+ if (!existing) return void 0;
1227
+ stmts.updateAnnotation.run({
1228
+ id,
1229
+ comment: data.comment ?? null,
1230
+ status: data.status ?? null,
1231
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
1232
+ resolvedAt: data.resolvedAt ?? null,
1233
+ resolvedBy: data.resolvedBy ?? null,
1234
+ thread: data.thread ? JSON.stringify(data.thread) : null,
1235
+ intent: data.intent ?? null,
1236
+ severity: data.severity ?? null
1237
+ });
1238
+ const updated = this.getAnnotation(id);
1239
+ if (updated && existing.sessionId) {
1240
+ const event = eventBus.emit("annotation.updated", existing.sessionId, updated);
1241
+ persistEvent(event);
1242
+ }
1243
+ return updated;
1244
+ },
1245
+ updateAnnotationStatus(id, status, resolvedBy) {
1246
+ const isResolved = status === "resolved" || status === "dismissed";
1247
+ return this.updateAnnotation(id, {
1248
+ status,
1249
+ resolvedAt: isResolved ? (/* @__PURE__ */ new Date()).toISOString() : void 0,
1250
+ resolvedBy: isResolved ? resolvedBy || "agent" : void 0
1251
+ });
1252
+ },
1253
+ addThreadMessage(annotationId, role, content) {
1254
+ const existing = this.getAnnotation(annotationId);
1255
+ if (!existing) return void 0;
1256
+ const message = {
1257
+ id: generateId(),
1258
+ role,
1259
+ content,
1260
+ timestamp: Date.now()
1261
+ };
1262
+ const thread = [...existing.thread || [], message];
1263
+ const updated = this.updateAnnotation(annotationId, { thread });
1264
+ if (updated && existing.sessionId) {
1265
+ const event = eventBus.emit("thread.message", existing.sessionId, message);
1266
+ persistEvent(event);
1267
+ }
1268
+ return updated;
1269
+ },
1270
+ getPendingAnnotations(sessionId) {
1271
+ const rows = stmts.getPendingAnnotations.all(sessionId);
1272
+ return rows.map(rowToAnnotation);
1273
+ },
1274
+ getSessionAnnotations(sessionId) {
1275
+ const rows = stmts.getAnnotationsBySession.all(sessionId);
1276
+ return rows.map(rowToAnnotation);
1277
+ },
1278
+ deleteAnnotation(id) {
1279
+ const existing = this.getAnnotation(id);
1280
+ if (!existing) return void 0;
1281
+ stmts.deleteAnnotation.run(id);
1282
+ if (existing.sessionId) {
1283
+ const event = eventBus.emit("annotation.deleted", existing.sessionId, existing);
1284
+ persistEvent(event);
1285
+ }
1286
+ return existing;
1287
+ },
1288
+ // -- Annotations V2 (Vue schema) ------------------------------------------
1289
+ getSessionWithAnnotationsV2(id) {
1290
+ const sessionRow = stmts.getSession.get(id);
1291
+ if (!sessionRow) return void 0;
1292
+ const annotationRows = stmts.getAnnotationsV2BySession.all(id);
1293
+ return {
1294
+ ...rowToSession(sessionRow),
1295
+ annotations: annotationRows.map(rowToAnnotationV2)
1296
+ };
1297
+ },
1298
+ addAnnotationV2(sessionId, data) {
1299
+ const session = this.getSession(sessionId);
1300
+ if (!session) return void 0;
1301
+ const existing = this.getAnnotationV2(data.id);
1302
+ if (existing) return existing;
1303
+ const annotation = {
1304
+ ...data,
1305
+ sessionId,
1306
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1307
+ };
1308
+ stmts.insertAnnotationV2.run({
1309
+ id: annotation.id,
1310
+ sessionId: annotation.sessionId,
1311
+ schemaVersion: annotation.schemaVersion,
1312
+ timestamp: annotation.timestamp,
1313
+ url: annotation.url,
1314
+ elementSelector: annotation.elementSelector,
1315
+ elementText: annotation.elementText ?? null,
1316
+ comment: annotation.comment,
1317
+ source: JSON.stringify(annotation.source),
1318
+ metadata: annotation.metadata ? JSON.stringify(annotation.metadata) : null,
1319
+ createdAt: annotation.createdAt,
1320
+ updatedAt: null
1321
+ });
1322
+ return annotation;
1323
+ },
1324
+ getAnnotationV2(id) {
1325
+ const row = stmts.getAnnotationV2.get(id);
1326
+ return row ? rowToAnnotationV2(row) : void 0;
1327
+ },
1328
+ updateAnnotationV2(id, data) {
1329
+ const existing = this.getAnnotationV2(id);
1330
+ if (!existing) return void 0;
1331
+ stmts.updateAnnotationV2.run({
1332
+ id,
1333
+ elementText: data.elementText ?? null,
1334
+ comment: data.comment ?? null,
1335
+ source: data.source ? JSON.stringify(data.source) : null,
1336
+ metadata: data.metadata ? JSON.stringify(data.metadata) : null,
1337
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
1338
+ });
1339
+ return this.getAnnotationV2(id);
1340
+ },
1341
+ getSessionAnnotationsV2(sessionId) {
1342
+ const rows = stmts.getAnnotationsV2BySession.all(sessionId);
1343
+ return rows.map(rowToAnnotationV2);
1344
+ },
1345
+ deleteAnnotationV2(id) {
1346
+ const existing = this.getAnnotationV2(id);
1347
+ if (!existing) return void 0;
1348
+ stmts.deleteAnnotationV2.run(id);
1349
+ return existing;
1350
+ },
1351
+ // Events
1352
+ getEventsSince(sessionId, sequence) {
1353
+ const rows = stmts.getEventsSince.all(sessionId, sequence);
1354
+ return rows.map((row) => ({
1355
+ type: row.type,
1356
+ timestamp: row.timestamp,
1357
+ sessionId: row.session_id,
1358
+ sequence: row.sequence,
1359
+ payload: JSON.parse(row.payload)
1360
+ }));
1361
+ },
1362
+ // Lifecycle
1363
+ close() {
1364
+ db.close();
1365
+ }
1366
+ };
1367
+ }
1368
+ function createTenantStore(dbPath) {
1369
+ const db = new import_better_sqlite3.default(dbPath ?? getDbPath());
1370
+ db.pragma("journal_mode = WAL");
1371
+ initDatabase(db);
1372
+ const lastEvent = db.prepare("SELECT MAX(sequence) as seq FROM events").get();
1373
+ if (lastEvent?.seq) {
1374
+ eventBus.setSequence(lastEvent.seq);
1375
+ }
1376
+ const tenantStmts = {
1377
+ // Organizations
1378
+ insertOrg: db.prepare(`
1379
+ INSERT INTO organizations (id, name, created_at)
1380
+ VALUES (@id, @name, @createdAt)
1381
+ `),
1382
+ getOrg: db.prepare("SELECT * FROM organizations WHERE id = ?"),
1383
+ // Users
1384
+ insertUser: db.prepare(`
1385
+ INSERT INTO users (id, email, org_id, role, created_at)
1386
+ VALUES (@id, @email, @orgId, @role, @createdAt)
1387
+ `),
1388
+ getUser: db.prepare("SELECT * FROM users WHERE id = ?"),
1389
+ getUserByEmail: db.prepare("SELECT * FROM users WHERE email = ?"),
1390
+ getUsersByOrg: db.prepare("SELECT * FROM users WHERE org_id = ?"),
1391
+ // API Keys
1392
+ insertApiKey: db.prepare(`
1393
+ INSERT INTO api_keys (id, key_prefix, key_hash, user_id, name, created_at, expires_at)
1394
+ VALUES (@id, @keyPrefix, @keyHash, @userId, @name, @createdAt, @expiresAt)
1395
+ `),
1396
+ getApiKeyByHash: db.prepare("SELECT * FROM api_keys WHERE key_hash = ?"),
1397
+ listApiKeys: db.prepare("SELECT * FROM api_keys WHERE user_id = ? ORDER BY created_at DESC"),
1398
+ deleteApiKey: db.prepare("DELETE FROM api_keys WHERE id = ?"),
1399
+ updateApiKeyLastUsed: db.prepare("UPDATE api_keys SET last_used_at = ? WHERE id = ?"),
1400
+ // User-scoped sessions
1401
+ insertSessionForUser: db.prepare(`
1402
+ INSERT INTO sessions (id, url, status, created_at, project_id, metadata, user_id)
1403
+ VALUES (@id, @url, @status, @createdAt, @projectId, @metadata, @userId)
1404
+ `),
1405
+ listSessionsForUser: db.prepare("SELECT * FROM sessions WHERE user_id = ? ORDER BY created_at DESC"),
1406
+ getSessionForUser: db.prepare("SELECT * FROM sessions WHERE id = ? AND user_id = ?"),
1407
+ getAnnotationsBySession: db.prepare("SELECT * FROM annotations WHERE session_id = ? ORDER BY timestamp"),
1408
+ getPendingAnnotationsForSession: db.prepare("SELECT * FROM annotations WHERE session_id = ? AND status = 'pending' ORDER BY timestamp"),
1409
+ // Get all pending for a user (across all their sessions)
1410
+ getAllPendingForUser: db.prepare(`
1411
+ SELECT a.* FROM annotations a
1412
+ JOIN sessions s ON a.session_id = s.id
1413
+ WHERE s.user_id = ? AND a.status = 'pending'
1414
+ ORDER BY a.timestamp
1415
+ `),
1416
+ // Events
1417
+ insertEvent: db.prepare(`
1418
+ INSERT INTO events (type, timestamp, session_id, sequence, payload, user_id)
1419
+ VALUES (@type, @timestamp, @sessionId, @sequence, @payload, @userId)
1420
+ `),
1421
+ // Prune old events
1422
+ pruneOldEvents: db.prepare(`
1423
+ DELETE FROM events WHERE timestamp < ?
1424
+ `)
1425
+ };
1426
+ const retentionDays = parseInt(process.env.AGENTATION_EVENT_RETENTION_DAYS || "7", 10);
1427
+ const cutoff = new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1e3).toISOString();
1428
+ tenantStmts.pruneOldEvents.run(cutoff);
1429
+ function persistEventForUser(event, userId) {
1430
+ tenantStmts.insertEvent.run({
1431
+ type: event.type,
1432
+ timestamp: event.timestamp,
1433
+ sessionId: event.sessionId,
1434
+ sequence: event.sequence,
1435
+ payload: JSON.stringify(event.payload),
1436
+ userId
1437
+ });
1438
+ }
1439
+ return {
1440
+ // Organizations
1441
+ createOrganization(name) {
1442
+ const org = {
1443
+ id: `org_${generateId()}`,
1444
+ name,
1445
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1446
+ };
1447
+ tenantStmts.insertOrg.run({
1448
+ id: org.id,
1449
+ name: org.name,
1450
+ createdAt: org.createdAt
1451
+ });
1452
+ return org;
1453
+ },
1454
+ getOrganization(id) {
1455
+ const row = tenantStmts.getOrg.get(id);
1456
+ return row ? rowToOrganization(row) : void 0;
1457
+ },
1458
+ // Users
1459
+ createUser(email, orgId, role = "member") {
1460
+ const user = {
1461
+ id: `user_${generateId()}`,
1462
+ email,
1463
+ orgId,
1464
+ role,
1465
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1466
+ };
1467
+ tenantStmts.insertUser.run({
1468
+ id: user.id,
1469
+ email: user.email,
1470
+ orgId: user.orgId,
1471
+ role: user.role,
1472
+ createdAt: user.createdAt
1473
+ });
1474
+ return user;
1475
+ },
1476
+ getUser(id) {
1477
+ const row = tenantStmts.getUser.get(id);
1478
+ return row ? rowToUser(row) : void 0;
1479
+ },
1480
+ getUserByEmail(email) {
1481
+ const row = tenantStmts.getUserByEmail.get(email);
1482
+ return row ? rowToUser(row) : void 0;
1483
+ },
1484
+ getUsersByOrg(orgId) {
1485
+ const rows = tenantStmts.getUsersByOrg.all(orgId);
1486
+ return rows.map(rowToUser);
1487
+ },
1488
+ // API Keys
1489
+ createApiKey(userId, name, expiresAt) {
1490
+ const id = `key_${generateId()}`;
1491
+ const rawKey = `sk_live_${(0, import_crypto.randomBytes)(32).toString("base64url")}`;
1492
+ const keyPrefix = rawKey.substring(0, 12);
1493
+ const keyHash = (0, import_crypto.createHash)("sha256").update(rawKey).digest("hex");
1494
+ const apiKey2 = {
1495
+ id,
1496
+ keyPrefix,
1497
+ keyHash,
1498
+ userId,
1499
+ name,
1500
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1501
+ expiresAt
1502
+ };
1503
+ tenantStmts.insertApiKey.run({
1504
+ id: apiKey2.id,
1505
+ keyPrefix: apiKey2.keyPrefix,
1506
+ keyHash: apiKey2.keyHash,
1507
+ userId: apiKey2.userId,
1508
+ name: apiKey2.name,
1509
+ createdAt: apiKey2.createdAt,
1510
+ expiresAt: apiKey2.expiresAt ?? null
1511
+ });
1512
+ return { apiKey: apiKey2, rawKey };
1513
+ },
1514
+ getApiKeyByHash(hash) {
1515
+ const row = tenantStmts.getApiKeyByHash.get(hash);
1516
+ return row ? rowToApiKey(row) : void 0;
1517
+ },
1518
+ listApiKeys(userId) {
1519
+ const rows = tenantStmts.listApiKeys.all(userId);
1520
+ return rows.map(rowToApiKey);
1521
+ },
1522
+ deleteApiKey(id) {
1523
+ const result = tenantStmts.deleteApiKey.run(id);
1524
+ return result.changes > 0;
1525
+ },
1526
+ updateApiKeyLastUsed(id) {
1527
+ tenantStmts.updateApiKeyLastUsed.run((/* @__PURE__ */ new Date()).toISOString(), id);
1528
+ },
1529
+ // User-scoped sessions
1530
+ createSessionForUser(userId, url, projectId) {
1531
+ const session = {
1532
+ id: generateId(),
1533
+ url,
1534
+ status: "active",
1535
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1536
+ projectId
1537
+ };
1538
+ tenantStmts.insertSessionForUser.run({
1539
+ id: session.id,
1540
+ url: session.url,
1541
+ status: session.status,
1542
+ createdAt: session.createdAt,
1543
+ projectId: session.projectId ?? null,
1544
+ metadata: null,
1545
+ userId
1546
+ });
1547
+ const event = eventBus.emit("session.created", session.id, session);
1548
+ persistEventForUser(event, userId);
1549
+ return session;
1550
+ },
1551
+ listSessionsForUser(userId) {
1552
+ const rows = tenantStmts.listSessionsForUser.all(userId);
1553
+ return rows.map(rowToSession);
1554
+ },
1555
+ getSessionForUser(userId, sessionId) {
1556
+ const row = tenantStmts.getSessionForUser.get(sessionId, userId);
1557
+ return row ? rowToSession(row) : void 0;
1558
+ },
1559
+ getSessionWithAnnotationsForUser(userId, sessionId) {
1560
+ const sessionRow = tenantStmts.getSessionForUser.get(sessionId, userId);
1561
+ if (!sessionRow) return void 0;
1562
+ const annotationRows = tenantStmts.getAnnotationsBySession.all(sessionId);
1563
+ return {
1564
+ ...rowToSession(sessionRow),
1565
+ annotations: annotationRows.map(rowToAnnotation)
1566
+ };
1567
+ },
1568
+ // User-scoped annotations
1569
+ getPendingAnnotationsForUser(userId, sessionId) {
1570
+ const session = this.getSessionForUser(userId, sessionId);
1571
+ if (!session) return [];
1572
+ const rows = tenantStmts.getPendingAnnotationsForSession.all(sessionId);
1573
+ return rows.map(rowToAnnotation);
1574
+ },
1575
+ getAllPendingForUser(userId) {
1576
+ const rows = tenantStmts.getAllPendingForUser.all(userId);
1577
+ return rows.map(rowToAnnotation);
1578
+ },
1579
+ // Lifecycle
1580
+ close() {
1581
+ db.close();
1582
+ }
1583
+ };
1584
+ }
1585
+ var import_better_sqlite3, import_crypto, import_fs, import_path, import_os;
1586
+ var init_sqlite = __esm({
1587
+ "src/server/sqlite.ts"() {
1588
+ "use strict";
1589
+ import_better_sqlite3 = __toESM(require("better-sqlite3"));
1590
+ import_crypto = require("crypto");
1591
+ import_fs = require("fs");
1592
+ import_path = require("path");
1593
+ import_os = require("os");
1594
+ init_events();
1595
+ }
1596
+ });
1597
+
1598
+ // src/server/store.ts
1599
+ function getStore() {
1600
+ if (!_store) {
1601
+ _store = initializeStore();
1602
+ }
1603
+ return _store;
1604
+ }
1605
+ function initializeStore() {
1606
+ if (process.env.AGENTATION_STORE === "memory") {
1607
+ process.stderr.write("[Store] Using in-memory store (AGENTATION_STORE=memory)\n");
1608
+ return createMemoryStore();
1609
+ }
1610
+ try {
1611
+ const { createSQLiteStore: createSQLiteStore2 } = (init_sqlite(), __toCommonJS(sqlite_exports));
1612
+ const store2 = createSQLiteStore2();
1613
+ process.stderr.write("[Store] Using SQLite store (~/.agentation/store.db)\n");
1614
+ return store2;
1615
+ } catch (err) {
1616
+ console.warn("[Store] SQLite unavailable, falling back to in-memory:", err.message);
1617
+ return createMemoryStore();
1618
+ }
1619
+ }
1620
+ function createMemoryStore() {
1621
+ const sessions = /* @__PURE__ */ new Map();
1622
+ const annotations = /* @__PURE__ */ new Map();
1623
+ const annotationsV2 = /* @__PURE__ */ new Map();
1624
+ const events = [];
1625
+ function generateId2() {
1626
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
1627
+ }
1628
+ return {
1629
+ createSession(url, projectId) {
1630
+ const session = {
1631
+ id: generateId2(),
1632
+ url,
1633
+ status: "active",
1634
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1635
+ projectId
1636
+ };
1637
+ sessions.set(session.id, session);
1638
+ const event = eventBus.emit("session.created", session.id, session);
1639
+ events.push(event);
1640
+ return session;
1641
+ },
1642
+ getSession(id) {
1643
+ return sessions.get(id);
1644
+ },
1645
+ getSessionWithAnnotations(id) {
1646
+ const session = sessions.get(id);
1647
+ if (!session) return void 0;
1648
+ const sessionAnnotations = Array.from(annotations.values()).filter(
1649
+ (a) => a.sessionId === id
1650
+ );
1651
+ return {
1652
+ ...session,
1653
+ annotations: sessionAnnotations
1654
+ };
1655
+ },
1656
+ getSessionWithAnnotationsV2(id) {
1657
+ const session = sessions.get(id);
1658
+ if (!session) return void 0;
1659
+ return {
1660
+ ...session,
1661
+ annotations: Array.from(annotationsV2.values()).filter(
1662
+ (a) => a.sessionId === id
1663
+ )
1664
+ };
1665
+ },
1666
+ updateSessionStatus(id, status) {
1667
+ const session = sessions.get(id);
1668
+ if (!session) return void 0;
1669
+ session.status = status;
1670
+ session.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1671
+ const eventType = status === "closed" ? "session.closed" : "session.updated";
1672
+ const event = eventBus.emit(eventType, id, session);
1673
+ events.push(event);
1674
+ return session;
1675
+ },
1676
+ listSessions() {
1677
+ return Array.from(sessions.values());
1678
+ },
1679
+ addAnnotation(sessionId, data) {
1680
+ const session = sessions.get(sessionId);
1681
+ if (!session) return void 0;
1682
+ const annotation = {
1683
+ ...data,
1684
+ id: generateId2(),
1685
+ sessionId,
1686
+ status: "pending",
1687
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1688
+ };
1689
+ annotations.set(annotation.id, annotation);
1690
+ const event = eventBus.emit("annotation.created", sessionId, annotation);
1691
+ events.push(event);
1692
+ return annotation;
1693
+ },
1694
+ getAnnotation(id) {
1695
+ return annotations.get(id);
1696
+ },
1697
+ updateAnnotation(id, data) {
1698
+ const annotation = annotations.get(id);
1699
+ if (!annotation) return void 0;
1700
+ Object.assign(annotation, data, { updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
1701
+ if (annotation.sessionId) {
1702
+ const event = eventBus.emit("annotation.updated", annotation.sessionId, annotation);
1703
+ events.push(event);
1704
+ }
1705
+ return annotation;
1706
+ },
1707
+ updateAnnotationStatus(id, status, resolvedBy) {
1708
+ const annotation = annotations.get(id);
1709
+ if (!annotation) return void 0;
1710
+ annotation.status = status;
1711
+ annotation.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1712
+ if (status === "resolved" || status === "dismissed") {
1713
+ annotation.resolvedAt = (/* @__PURE__ */ new Date()).toISOString();
1714
+ annotation.resolvedBy = resolvedBy || "agent";
1715
+ }
1716
+ if (annotation.sessionId) {
1717
+ const event = eventBus.emit("annotation.updated", annotation.sessionId, annotation);
1718
+ events.push(event);
1719
+ }
1720
+ return annotation;
1721
+ },
1722
+ addThreadMessage(annotationId, role, content) {
1723
+ const annotation = annotations.get(annotationId);
1724
+ if (!annotation) return void 0;
1725
+ const message = {
1726
+ id: generateId2(),
1727
+ role,
1728
+ content,
1729
+ timestamp: Date.now()
1730
+ };
1731
+ if (!annotation.thread) {
1732
+ annotation.thread = [];
1733
+ }
1734
+ annotation.thread.push(message);
1735
+ annotation.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1736
+ if (annotation.sessionId) {
1737
+ const event = eventBus.emit("thread.message", annotation.sessionId, message);
1738
+ events.push(event);
1739
+ }
1740
+ return annotation;
1741
+ },
1742
+ getPendingAnnotations(sessionId) {
1743
+ return Array.from(annotations.values()).filter(
1744
+ (a) => a.sessionId === sessionId && a.status === "pending"
1745
+ );
1746
+ },
1747
+ getSessionAnnotations(sessionId) {
1748
+ return Array.from(annotations.values()).filter(
1749
+ (a) => a.sessionId === sessionId
1750
+ );
1751
+ },
1752
+ deleteAnnotation(id) {
1753
+ const annotation = annotations.get(id);
1754
+ if (!annotation) return void 0;
1755
+ annotations.delete(id);
1756
+ if (annotation.sessionId) {
1757
+ const event = eventBus.emit("annotation.deleted", annotation.sessionId, annotation);
1758
+ events.push(event);
1759
+ }
1760
+ return annotation;
1761
+ },
1762
+ // -- Annotations V2 (Vue schema) ------------------------------------------
1763
+ addAnnotationV2(sessionId, data) {
1764
+ const session = sessions.get(sessionId);
1765
+ if (!session) return void 0;
1766
+ const existing = annotationsV2.get(data.id);
1767
+ if (existing) return existing;
1768
+ const annotation = {
1769
+ ...data,
1770
+ sessionId,
1771
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
1772
+ };
1773
+ annotationsV2.set(annotation.id, annotation);
1774
+ return annotation;
1775
+ },
1776
+ getAnnotationV2(id) {
1777
+ return annotationsV2.get(id);
1778
+ },
1779
+ updateAnnotationV2(id, data) {
1780
+ const annotation = annotationsV2.get(id);
1781
+ if (!annotation) return void 0;
1782
+ Object.assign(annotation, data, { updatedAt: (/* @__PURE__ */ new Date()).toISOString() });
1783
+ return annotation;
1784
+ },
1785
+ getSessionAnnotationsV2(sessionId) {
1786
+ return Array.from(annotationsV2.values()).filter(
1787
+ (a) => a.sessionId === sessionId
1788
+ );
1789
+ },
1790
+ deleteAnnotationV2(id) {
1791
+ const annotation = annotationsV2.get(id);
1792
+ if (!annotation) return void 0;
1793
+ annotationsV2.delete(id);
1794
+ return annotation;
1795
+ },
1796
+ getEventsSince(sessionId, sequence) {
1797
+ return events.filter(
1798
+ (e) => e.sessionId === sessionId && e.sequence > sequence
1799
+ );
1800
+ },
1801
+ close() {
1802
+ sessions.clear();
1803
+ annotations.clear();
1804
+ annotationsV2.clear();
1805
+ events.length = 0;
1806
+ }
1807
+ };
1808
+ }
1809
+ function createSession(url, projectId) {
1810
+ return getStore().createSession(url, projectId);
1811
+ }
1812
+ function getSession(id) {
1813
+ return getStore().getSession(id);
1814
+ }
1815
+ function getSessionWithAnnotations(id) {
1816
+ return getStore().getSessionWithAnnotations(id);
1817
+ }
1818
+ function updateSessionStatus(id, status) {
1819
+ return getStore().updateSessionStatus(id, status);
1820
+ }
1821
+ function listSessions() {
1822
+ return getStore().listSessions();
1823
+ }
1824
+ function addAnnotation(sessionId, data) {
1825
+ return getStore().addAnnotation(sessionId, data);
1826
+ }
1827
+ function getAnnotation(id) {
1828
+ return getStore().getAnnotation(id);
1829
+ }
1830
+ function updateAnnotation(id, data) {
1831
+ return getStore().updateAnnotation(id, data);
1832
+ }
1833
+ function updateAnnotationStatus(id, status, resolvedBy) {
1834
+ return getStore().updateAnnotationStatus(id, status, resolvedBy);
1835
+ }
1836
+ function addThreadMessage(annotationId, role, content) {
1837
+ return getStore().addThreadMessage(annotationId, role, content);
1838
+ }
1839
+ function getPendingAnnotations(sessionId) {
1840
+ return getStore().getPendingAnnotations(sessionId);
1841
+ }
1842
+ function getSessionAnnotations(sessionId) {
1843
+ return getStore().getSessionAnnotations(sessionId);
1844
+ }
1845
+ function deleteAnnotation(id) {
1846
+ return getStore().deleteAnnotation(id);
1847
+ }
1848
+ function getSessionWithAnnotationsV2(id) {
1849
+ return getStore().getSessionWithAnnotationsV2(id);
1850
+ }
1851
+ function addAnnotationV2(sessionId, data) {
1852
+ return getStore().addAnnotationV2(sessionId, data);
1853
+ }
1854
+ function getAnnotationV2(id) {
1855
+ return getStore().getAnnotationV2(id);
1856
+ }
1857
+ function updateAnnotationV2(id, data) {
1858
+ return getStore().updateAnnotationV2(id, data);
1859
+ }
1860
+ function getSessionAnnotationsV2(sessionId) {
1861
+ return getStore().getSessionAnnotationsV2(sessionId);
1862
+ }
1863
+ function deleteAnnotationV2(id) {
1864
+ return getStore().deleteAnnotationV2(id);
1865
+ }
1866
+ function getEventsSince(sessionId, sequence) {
1867
+ return getStore().getEventsSince(sessionId, sequence);
1868
+ }
1869
+ function clearAll() {
1870
+ getStore().close();
1871
+ _store = null;
1872
+ }
1873
+ var _store, store;
1874
+ var init_store = __esm({
1875
+ "src/server/store.ts"() {
1876
+ "use strict";
1877
+ init_events();
1878
+ _store = null;
1879
+ store = {
1880
+ get instance() {
1881
+ return getStore();
1882
+ }
1883
+ };
1884
+ }
1885
+ });
1886
+
1887
+ // src/server/http.ts
1888
+ function log(message) {
1889
+ process.stderr.write(message + "\n");
1890
+ }
1891
+ function setCloudApiKey(key) {
1892
+ cloudApiKey = key;
1893
+ }
1894
+ function isCloudMode() {
1895
+ return !!cloudApiKey;
1896
+ }
1897
+ function createMcpSession() {
1898
+ const transport = new import_streamableHttp.StreamableHTTPServerTransport({
1899
+ sessionIdGenerator: () => crypto.randomUUID()
1900
+ });
1901
+ const server = new import_server2.Server(
1902
+ { name: "agentation", version: "0.0.1" },
1903
+ { capabilities: { tools: {} } }
1904
+ );
1905
+ server.setRequestHandler(import_types2.ListToolsRequestSchema, async () => ({ tools: TOOLS }));
1906
+ server.setRequestHandler(import_types2.CallToolRequestSchema, async (req) => {
1907
+ try {
1908
+ return await handleTool(req.params.name, req.params.arguments);
1909
+ } catch (err) {
1910
+ const message = err instanceof Error ? err.message : "Unknown error";
1911
+ return error(message);
1912
+ }
1913
+ });
1914
+ server.connect(transport);
1915
+ return { server, transport };
1916
+ }
1917
+ function getWebhookUrls() {
1918
+ const urls = [];
1919
+ const singleUrl = process.env.AGENTATION_WEBHOOK_URL;
1920
+ if (singleUrl) {
1921
+ urls.push(singleUrl.trim());
1922
+ }
1923
+ const multipleUrls = process.env.AGENTATION_WEBHOOKS;
1924
+ if (multipleUrls) {
1925
+ const parsed = multipleUrls.split(",").map((url) => url.trim()).filter((url) => url.length > 0);
1926
+ urls.push(...parsed);
1927
+ }
1928
+ return urls;
1929
+ }
1930
+ function sendWebhooks(actionRequest) {
1931
+ const webhookUrls = getWebhookUrls();
1932
+ if (webhookUrls.length === 0) {
1933
+ return;
1934
+ }
1935
+ const payload = JSON.stringify(actionRequest);
1936
+ for (const url of webhookUrls) {
1937
+ fetch(url, {
1938
+ method: "POST",
1939
+ headers: {
1940
+ "Content-Type": "application/json",
1941
+ "User-Agent": "Agentation-Webhook/1.0"
1942
+ },
1943
+ body: payload
1944
+ }).then((res) => {
1945
+ log(
1946
+ `[Webhook] POST ${url} -> ${res.status} ${res.statusText}`
1947
+ );
1948
+ }).catch((err) => {
1949
+ console.error(`[Webhook] POST ${url} failed:`, err.message);
1950
+ });
1951
+ }
1952
+ log(
1953
+ `[Webhook] Fired ${webhookUrls.length} webhook(s) for session ${actionRequest.sessionId}`
1954
+ );
1955
+ }
1956
+ async function parseBody(req) {
1957
+ return new Promise((resolve, reject) => {
1958
+ let body = "";
1959
+ req.on("data", (chunk) => body += chunk);
1960
+ req.on("end", () => {
1961
+ try {
1962
+ resolve(body ? JSON.parse(body) : {});
1963
+ } catch {
1964
+ reject(new Error("Invalid JSON"));
1965
+ }
1966
+ });
1967
+ req.on("error", reject);
1968
+ });
1969
+ }
1970
+ function sendJson(res, status, data) {
1971
+ res.writeHead(status, {
1972
+ "Content-Type": "application/json",
1973
+ "Access-Control-Allow-Origin": "*",
1974
+ "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
1975
+ "Access-Control-Allow-Headers": "Content-Type"
1976
+ });
1977
+ res.end(JSON.stringify(data));
1978
+ }
1979
+ function sendError(res, status, message) {
1980
+ sendJson(res, status, { error: message });
1981
+ }
1982
+ function handleCors(res) {
1983
+ res.writeHead(204, {
1984
+ "Access-Control-Allow-Origin": "*",
1985
+ "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
1986
+ "Access-Control-Allow-Headers": "Content-Type, Accept, Mcp-Session-Id",
1987
+ "Access-Control-Expose-Headers": "Mcp-Session-Id",
1988
+ "Access-Control-Max-Age": "86400"
1989
+ });
1990
+ res.end();
1991
+ }
1992
+ async function proxyToCloud(req, res, pathname) {
1993
+ const method = req.method || "GET";
1994
+ const cloudUrl = `${CLOUD_API_URL}${pathname}`;
1995
+ const headers = {
1996
+ "x-api-key": cloudApiKey
1997
+ };
1998
+ if (req.headers["content-type"]) {
1999
+ headers["Content-Type"] = req.headers["content-type"];
2000
+ }
2001
+ let body;
2002
+ if (method !== "GET" && method !== "HEAD") {
2003
+ body = await new Promise((resolve, reject) => {
2004
+ let data = "";
2005
+ req.on("data", (chunk) => data += chunk);
2006
+ req.on("end", () => resolve(data));
2007
+ req.on("error", reject);
2008
+ });
2009
+ }
2010
+ try {
2011
+ const cloudRes = await fetch(cloudUrl, {
2012
+ method,
2013
+ headers,
2014
+ body
2015
+ });
2016
+ if (cloudRes.headers.get("content-type")?.includes("text/event-stream")) {
2017
+ res.writeHead(cloudRes.status, {
2018
+ "Content-Type": "text/event-stream",
2019
+ "Cache-Control": "no-cache",
2020
+ Connection: "keep-alive",
2021
+ "Access-Control-Allow-Origin": "*"
2022
+ });
2023
+ const reader = cloudRes.body?.getReader();
2024
+ if (reader) {
2025
+ const pump = async () => {
2026
+ while (true) {
2027
+ const { done, value } = await reader.read();
2028
+ if (done) break;
2029
+ res.write(value);
2030
+ }
2031
+ res.end();
2032
+ };
2033
+ pump().catch(() => res.end());
2034
+ req.on("close", () => {
2035
+ reader.cancel();
2036
+ });
2037
+ }
2038
+ return;
2039
+ }
2040
+ const data = await cloudRes.text();
2041
+ res.writeHead(cloudRes.status, {
2042
+ "Content-Type": cloudRes.headers.get("content-type") || "application/json",
2043
+ "Access-Control-Allow-Origin": "*",
2044
+ "Access-Control-Allow-Methods": "GET, POST, PATCH, DELETE, OPTIONS",
2045
+ "Access-Control-Allow-Headers": "Content-Type"
2046
+ });
2047
+ res.end(data);
2048
+ } catch (err) {
2049
+ console.error("[Cloud Proxy] Error:", err);
2050
+ sendError(res, 502, `Cloud proxy error: ${err.message}`);
2051
+ }
2052
+ }
2053
+ function sendSSEEvent(res, event) {
2054
+ res.write(`event: ${event.type}
2055
+ `);
2056
+ res.write(`id: ${event.sequence}
2057
+ `);
2058
+ res.write(`data: ${JSON.stringify(event)}
2059
+
2060
+ `);
2061
+ }
2062
+ async function handleMcp(req, res) {
2063
+ const method = req.method || "GET";
2064
+ const sessionId = req.headers["mcp-session-id"];
2065
+ res.setHeader("Access-Control-Allow-Origin", "*");
2066
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
2067
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, Mcp-Session-Id");
2068
+ res.setHeader("Access-Control-Expose-Headers", "Mcp-Session-Id");
2069
+ if (method === "POST") {
2070
+ let transport;
2071
+ if (sessionId) {
2072
+ if (!mcpTransports.has(sessionId)) {
2073
+ res.writeHead(404, { "Content-Type": "application/json" });
2074
+ res.end(JSON.stringify({
2075
+ jsonrpc: "2.0",
2076
+ error: { code: -32e3, message: "Session not found. Please re-initialize." },
2077
+ id: null
2078
+ }));
2079
+ return;
2080
+ }
2081
+ transport = mcpTransports.get(sessionId);
2082
+ } else {
2083
+ const { transport: newTransport } = createMcpSession();
2084
+ transport = newTransport;
2085
+ }
2086
+ try {
2087
+ const body = await new Promise((resolve, reject) => {
2088
+ let data = "";
2089
+ req.on("data", (chunk) => data += chunk);
2090
+ req.on("end", () => resolve(data));
2091
+ req.on("error", reject);
2092
+ });
2093
+ const parsedBody = body ? JSON.parse(body) : void 0;
2094
+ await transport.handleRequest(req, res, parsedBody);
2095
+ const newSessionId = transport.sessionId;
2096
+ if (newSessionId && !mcpTransports.has(newSessionId)) {
2097
+ mcpTransports.set(newSessionId, transport);
2098
+ log(`[MCP HTTP] New session created: ${newSessionId}`);
2099
+ }
2100
+ } catch (err) {
2101
+ console.error("[MCP HTTP] Error handling request:", err);
2102
+ if (!res.headersSent) {
2103
+ res.writeHead(500, { "Content-Type": "application/json" });
2104
+ res.end(JSON.stringify({ error: "Internal server error" }));
2105
+ }
2106
+ }
2107
+ return;
2108
+ }
2109
+ if (method === "GET") {
2110
+ if (!sessionId || !mcpTransports.has(sessionId)) {
2111
+ res.writeHead(400, { "Content-Type": "application/json" });
2112
+ res.end(JSON.stringify({ error: "Missing or invalid Mcp-Session-Id" }));
2113
+ return;
2114
+ }
2115
+ const transport = mcpTransports.get(sessionId);
2116
+ try {
2117
+ await transport.handleRequest(req, res);
2118
+ } catch (err) {
2119
+ console.error("[MCP HTTP] Error handling SSE:", err);
2120
+ if (!res.headersSent) {
2121
+ res.writeHead(500, { "Content-Type": "application/json" });
2122
+ res.end(JSON.stringify({ error: "Internal server error" }));
2123
+ }
2124
+ }
2125
+ return;
2126
+ }
2127
+ if (method === "DELETE") {
2128
+ if (sessionId && mcpTransports.has(sessionId)) {
2129
+ const transport = mcpTransports.get(sessionId);
2130
+ await transport.close();
2131
+ mcpTransports.delete(sessionId);
2132
+ res.writeHead(204);
2133
+ res.end();
2134
+ } else {
2135
+ res.writeHead(404, { "Content-Type": "application/json" });
2136
+ res.end(JSON.stringify({ error: "Session not found" }));
2137
+ }
2138
+ return;
2139
+ }
2140
+ res.writeHead(405, { "Content-Type": "application/json" });
2141
+ res.end(JSON.stringify({ error: "Method not allowed" }));
2142
+ }
2143
+ function matchRoute(method, pathname) {
2144
+ for (const route of routes) {
2145
+ if (route.method !== method) continue;
2146
+ const match = pathname.match(route.pattern);
2147
+ if (match) {
2148
+ const params = {};
2149
+ route.paramNames.forEach((name, i) => {
2150
+ params[name] = match[i + 1];
2151
+ });
2152
+ return { handler: route.handler, params };
2153
+ }
2154
+ }
2155
+ return null;
2156
+ }
2157
+ function startHttpServer(port, apiKey2) {
2158
+ if (apiKey2) {
2159
+ setCloudApiKey(apiKey2);
2160
+ }
2161
+ const server = (0, import_http.createServer)(async (req, res) => {
2162
+ const url = new URL(req.url || "/", `http://localhost:${port}`);
2163
+ const pathname = url.pathname;
2164
+ const method = req.method || "GET";
2165
+ if (method !== "OPTIONS" && pathname !== "/health") {
2166
+ log(`[HTTP] ${method} ${pathname}`);
2167
+ }
2168
+ if (method === "OPTIONS") {
2169
+ return handleCors(res);
2170
+ }
2171
+ if (pathname === "/health" && method === "GET") {
2172
+ return sendJson(res, 200, { status: "ok", mode: isCloudMode() ? "cloud" : "local" });
2173
+ }
2174
+ if (pathname === "/status" && method === "GET") {
2175
+ const webhookUrls = getWebhookUrls();
2176
+ return sendJson(res, 200, {
2177
+ mode: isCloudMode() ? "cloud" : "local",
2178
+ webhooksConfigured: webhookUrls.length > 0,
2179
+ webhookCount: webhookUrls.length,
2180
+ activeListeners: sseConnections.size,
2181
+ agentListeners: agentConnections.size
2182
+ });
2183
+ }
2184
+ if (pathname === "/mcp") {
2185
+ return handleMcp(req, res);
2186
+ }
2187
+ if (isCloudMode()) {
2188
+ return proxyToCloud(req, res, pathname + url.search);
2189
+ }
2190
+ const match = matchRoute(method, pathname);
2191
+ if (!match) {
2192
+ return sendError(res, 404, "Not found");
2193
+ }
2194
+ try {
2195
+ await match.handler(req, res, match.params);
2196
+ } catch (err) {
2197
+ console.error("Request error:", err);
2198
+ sendError(res, 500, "Internal server error");
2199
+ }
2200
+ });
2201
+ server.on("error", (err) => {
2202
+ if (err.code === "EADDRINUSE") {
2203
+ log(`[HTTP] Port ${port} already in use \u2014 skipping HTTP server (MCP stdio still active)`);
2204
+ } else {
2205
+ log(`[HTTP] Server error: ${err.message}`);
2206
+ }
2207
+ });
2208
+ server.listen(port, () => {
2209
+ if (isCloudMode()) {
2210
+ log(`[HTTP] Agentation server listening on http://localhost:${port} (cloud mode)`);
2211
+ } else {
2212
+ log(`[HTTP] Agentation server listening on http://localhost:${port}`);
2213
+ }
2214
+ });
2215
+ }
2216
+ var import_http, import_streamableHttp, import_server2, import_types2, cloudApiKey, CLOUD_API_URL, sseConnections, agentConnections, mcpTransports, createSessionHandler, listSessionsHandler, getSessionHandler, addAnnotationHandler, updateAnnotationHandler, getAnnotationHandler, deleteAnnotationHandler, getSessionV2Handler, addAnnotationV2Handler, updateAnnotationV2Handler, getAnnotationV2Handler, deleteAnnotationV2Handler, getPendingHandler, getAllPendingHandler, requestActionHandler, addThreadHandler, sseHandler, globalSseHandler, routes;
2217
+ var init_http = __esm({
2218
+ "src/server/http.ts"() {
2219
+ "use strict";
2220
+ import_http = require("http");
2221
+ import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
2222
+ import_server2 = require("@modelcontextprotocol/sdk/server/index.js");
2223
+ import_types2 = require("@modelcontextprotocol/sdk/types.js");
2224
+ init_mcp();
2225
+ init_store();
2226
+ init_events();
2227
+ CLOUD_API_URL = "https://agentation-mcp-cloud.vercel.app/api";
2228
+ sseConnections = /* @__PURE__ */ new Set();
2229
+ agentConnections = /* @__PURE__ */ new Set();
2230
+ mcpTransports = /* @__PURE__ */ new Map();
2231
+ createSessionHandler = async (req, res) => {
2232
+ try {
2233
+ const body = await parseBody(req);
2234
+ if (!body.url) {
2235
+ return sendError(res, 400, "url is required");
2236
+ }
2237
+ const session = createSession(body.url, body.projectId);
2238
+ sendJson(res, 201, session);
2239
+ } catch (err) {
2240
+ sendError(res, 400, err.message);
2241
+ }
2242
+ };
2243
+ listSessionsHandler = async (_req, res) => {
2244
+ const sessions = listSessions();
2245
+ sendJson(res, 200, sessions);
2246
+ };
2247
+ getSessionHandler = async (_req, res, params) => {
2248
+ const session = getSessionWithAnnotations(params.id);
2249
+ if (!session) {
2250
+ return sendError(res, 404, "Session not found");
2251
+ }
2252
+ sendJson(res, 200, session);
2253
+ };
2254
+ addAnnotationHandler = async (req, res, params) => {
2255
+ try {
2256
+ const body = await parseBody(req);
2257
+ if (!body.comment || !body.element || !body.elementPath) {
2258
+ return sendError(res, 400, "comment, element, and elementPath are required");
2259
+ }
2260
+ const annotation = addAnnotation(params.id, body);
2261
+ if (!annotation) {
2262
+ return sendError(res, 404, "Session not found");
2263
+ }
2264
+ sendJson(res, 201, annotation);
2265
+ } catch (err) {
2266
+ sendError(res, 400, err.message);
2267
+ }
2268
+ };
2269
+ updateAnnotationHandler = async (req, res, params) => {
2270
+ try {
2271
+ const body = await parseBody(req);
2272
+ const existing = getAnnotation(params.id);
2273
+ if (!existing) {
2274
+ return sendError(res, 404, "Annotation not found");
2275
+ }
2276
+ const annotation = updateAnnotation(params.id, body);
2277
+ sendJson(res, 200, annotation);
2278
+ } catch (err) {
2279
+ sendError(res, 400, err.message);
2280
+ }
2281
+ };
2282
+ getAnnotationHandler = async (_req, res, params) => {
2283
+ const annotation = getAnnotation(params.id);
2284
+ if (!annotation) {
2285
+ return sendError(res, 404, "Annotation not found");
2286
+ }
2287
+ sendJson(res, 200, annotation);
2288
+ };
2289
+ deleteAnnotationHandler = async (_req, res, params) => {
2290
+ const annotation = deleteAnnotation(params.id);
2291
+ if (!annotation) {
2292
+ return sendError(res, 404, "Annotation not found");
2293
+ }
2294
+ sendJson(res, 200, { deleted: true, annotationId: params.id });
2295
+ };
2296
+ getSessionV2Handler = async (_req, res, params) => {
2297
+ const session = getSessionWithAnnotationsV2(params.id);
2298
+ if (!session) {
2299
+ return sendError(res, 404, "Session not found");
2300
+ }
2301
+ sendJson(res, 200, session);
2302
+ };
2303
+ addAnnotationV2Handler = async (req, res, params) => {
2304
+ try {
2305
+ const body = await parseBody(req);
2306
+ if (!body.id || !body.comment || !body.elementSelector || !body.source) {
2307
+ return sendError(res, 400, "id, comment, elementSelector, and source are required");
2308
+ }
2309
+ const existing = getAnnotationV2(body.id);
2310
+ if (existing) {
2311
+ if (existing.sessionId !== params.id) {
2312
+ return sendError(res, 409, "Annotation ID already exists in a different session");
2313
+ }
2314
+ return sendJson(res, 200, existing);
2315
+ }
2316
+ const annotation = addAnnotationV2(params.id, body);
2317
+ if (!annotation) {
2318
+ return sendError(res, 404, "Session not found");
2319
+ }
2320
+ sendJson(res, 201, annotation);
2321
+ } catch (err) {
2322
+ sendError(res, 400, err.message);
2323
+ }
2324
+ };
2325
+ updateAnnotationV2Handler = async (req, res, params) => {
2326
+ try {
2327
+ const body = await parseBody(req);
2328
+ const existing = getAnnotationV2(params.id);
2329
+ if (!existing) {
2330
+ return sendError(res, 404, "Annotation not found");
2331
+ }
2332
+ const annotation = updateAnnotationV2(params.id, body);
2333
+ sendJson(res, 200, annotation);
2334
+ } catch (err) {
2335
+ sendError(res, 400, err.message);
2336
+ }
2337
+ };
2338
+ getAnnotationV2Handler = async (_req, res, params) => {
2339
+ const annotation = getAnnotationV2(params.id);
2340
+ if (!annotation) {
2341
+ return sendError(res, 404, "Annotation not found");
2342
+ }
2343
+ sendJson(res, 200, annotation);
2344
+ };
2345
+ deleteAnnotationV2Handler = async (_req, res, params) => {
2346
+ const annotation = deleteAnnotationV2(params.id);
2347
+ if (!annotation) {
2348
+ return sendError(res, 404, "Annotation not found");
2349
+ }
2350
+ sendJson(res, 200, { deleted: true, annotationId: params.id });
2351
+ };
2352
+ getPendingHandler = async (_req, res, params) => {
2353
+ const pending = getPendingAnnotations(params.id);
2354
+ sendJson(res, 200, { count: pending.length, annotations: pending });
2355
+ };
2356
+ getAllPendingHandler = async (_req, res) => {
2357
+ const sessions = listSessions();
2358
+ const allPending = sessions.flatMap((session) => getPendingAnnotations(session.id));
2359
+ sendJson(res, 200, { count: allPending.length, annotations: allPending });
2360
+ };
2361
+ requestActionHandler = async (req, res, params) => {
2362
+ try {
2363
+ const sessionId = params.id;
2364
+ const body = await parseBody(req);
2365
+ const session = getSessionWithAnnotations(sessionId);
2366
+ if (!session) {
2367
+ return sendError(res, 404, "Session not found");
2368
+ }
2369
+ if (!body.output) {
2370
+ return sendError(res, 400, "output is required");
2371
+ }
2372
+ const actionRequest = {
2373
+ sessionId,
2374
+ annotations: session.annotations,
2375
+ output: body.output,
2376
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2377
+ };
2378
+ eventBus.emit("action.requested", sessionId, actionRequest);
2379
+ const webhookUrls = getWebhookUrls();
2380
+ sendWebhooks(actionRequest);
2381
+ const agentListeners = agentConnections.size;
2382
+ const webhooks = webhookUrls.length;
2383
+ sendJson(res, 200, {
2384
+ success: true,
2385
+ annotationCount: session.annotations.length,
2386
+ delivered: {
2387
+ sseListeners: agentListeners,
2388
+ webhooks,
2389
+ total: agentListeners + webhooks
2390
+ }
2391
+ });
2392
+ } catch (err) {
2393
+ sendError(res, 400, err.message);
2394
+ }
2395
+ };
2396
+ addThreadHandler = async (req, res, params) => {
2397
+ try {
2398
+ const body = await parseBody(req);
2399
+ if (!body.role || !body.content) {
2400
+ return sendError(res, 400, "role and content are required");
2401
+ }
2402
+ const annotation = addThreadMessage(params.id, body.role, body.content);
2403
+ if (!annotation) {
2404
+ return sendError(res, 404, "Annotation not found");
2405
+ }
2406
+ sendJson(res, 201, annotation);
2407
+ } catch (err) {
2408
+ sendError(res, 400, err.message);
2409
+ }
2410
+ };
2411
+ sseHandler = async (req, res, params) => {
2412
+ const sessionId = params.id;
2413
+ const url = new URL(req.url || "/", "http://localhost");
2414
+ const isAgent = url.searchParams.get("agent") === "true";
2415
+ const session = getSessionWithAnnotations(sessionId);
2416
+ if (!session) {
2417
+ return sendError(res, 404, "Session not found");
2418
+ }
2419
+ res.writeHead(200, {
2420
+ "Content-Type": "text/event-stream",
2421
+ "Cache-Control": "no-cache",
2422
+ Connection: "keep-alive",
2423
+ "Access-Control-Allow-Origin": "*"
2424
+ });
2425
+ sseConnections.add(res);
2426
+ if (isAgent) {
2427
+ agentConnections.add(res);
2428
+ }
2429
+ res.write(": connected\n\n");
2430
+ const lastEventId = req.headers["last-event-id"];
2431
+ if (lastEventId) {
2432
+ const lastSequence = parseInt(lastEventId, 10);
2433
+ if (!isNaN(lastSequence)) {
2434
+ const missedEvents = getEventsSince(sessionId, lastSequence);
2435
+ for (const event of missedEvents) {
2436
+ sendSSEEvent(res, event);
2437
+ }
2438
+ }
2439
+ }
2440
+ const unsubscribe = eventBus.subscribeToSession(sessionId, (event) => {
2441
+ sendSSEEvent(res, event);
2442
+ });
2443
+ const keepAlive = setInterval(() => {
2444
+ res.write(": ping\n\n");
2445
+ }, 3e4);
2446
+ req.on("close", () => {
2447
+ clearInterval(keepAlive);
2448
+ unsubscribe();
2449
+ sseConnections.delete(res);
2450
+ agentConnections.delete(res);
2451
+ });
2452
+ };
2453
+ globalSseHandler = async (req, res) => {
2454
+ const url = new URL(req.url || "/", "http://localhost");
2455
+ const domain = url.searchParams.get("domain");
2456
+ const isAgent = url.searchParams.get("agent") === "true";
2457
+ res.writeHead(200, {
2458
+ "Content-Type": "text/event-stream",
2459
+ "Cache-Control": "no-cache",
2460
+ Connection: "keep-alive",
2461
+ "Access-Control-Allow-Origin": "*"
2462
+ });
2463
+ sseConnections.add(res);
2464
+ if (isAgent) {
2465
+ agentConnections.add(res);
2466
+ }
2467
+ res.write(`: connected${domain ? ` to domain ${domain}` : ""}
2468
+
2469
+ `);
2470
+ if (isAgent) {
2471
+ let syncCount = 0;
2472
+ const sessions = listSessions();
2473
+ for (const session of sessions) {
2474
+ try {
2475
+ if (domain) {
2476
+ const sessionHost = new URL(session.url).host;
2477
+ if (sessionHost !== domain) continue;
2478
+ }
2479
+ const pending = getPendingAnnotations(session.id);
2480
+ for (const annotation of pending) {
2481
+ sendSSEEvent(res, {
2482
+ type: "annotation.created",
2483
+ sessionId: session.id,
2484
+ timestamp: annotation.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
2485
+ sequence: 0,
2486
+ payload: annotation
2487
+ });
2488
+ syncCount++;
2489
+ }
2490
+ } catch {
2491
+ }
2492
+ }
2493
+ res.write(`event: sync.complete
2494
+ data: ${JSON.stringify({ domain: domain ?? "all", count: syncCount, timestamp: (/* @__PURE__ */ new Date()).toISOString() })}
2495
+
2496
+ `);
2497
+ }
2498
+ const unsubscribe = eventBus.subscribe((event) => {
2499
+ if (!domain) {
2500
+ sendSSEEvent(res, event);
2501
+ return;
2502
+ }
2503
+ const session = getSession(event.sessionId);
2504
+ if (session) {
2505
+ try {
2506
+ const sessionHost = new URL(session.url).host;
2507
+ if (sessionHost === domain) {
2508
+ sendSSEEvent(res, event);
2509
+ }
2510
+ } catch {
2511
+ }
2512
+ }
2513
+ });
2514
+ const keepAlive = setInterval(() => {
2515
+ res.write(": ping\n\n");
2516
+ }, 3e4);
2517
+ req.on("close", () => {
2518
+ clearInterval(keepAlive);
2519
+ unsubscribe();
2520
+ sseConnections.delete(res);
2521
+ agentConnections.delete(res);
2522
+ });
2523
+ };
2524
+ routes = [
2525
+ // V2 routes (Vue schema) — must be before legacy routes for clean matching
2526
+ {
2527
+ method: "GET",
2528
+ pattern: /^\/v2\/sessions$/,
2529
+ handler: listSessionsHandler,
2530
+ paramNames: []
2531
+ },
2532
+ {
2533
+ method: "POST",
2534
+ pattern: /^\/v2\/sessions$/,
2535
+ handler: createSessionHandler,
2536
+ paramNames: []
2537
+ },
2538
+ {
2539
+ method: "GET",
2540
+ pattern: /^\/v2\/sessions\/([^/]+)$/,
2541
+ handler: getSessionV2Handler,
2542
+ paramNames: ["id"]
2543
+ },
2544
+ {
2545
+ method: "POST",
2546
+ pattern: /^\/v2\/sessions\/([^/]+)\/annotations$/,
2547
+ handler: addAnnotationV2Handler,
2548
+ paramNames: ["id"]
2549
+ },
2550
+ {
2551
+ method: "PATCH",
2552
+ pattern: /^\/v2\/annotations\/([^/]+)$/,
2553
+ handler: updateAnnotationV2Handler,
2554
+ paramNames: ["id"]
2555
+ },
2556
+ {
2557
+ method: "GET",
2558
+ pattern: /^\/v2\/annotations\/([^/]+)$/,
2559
+ handler: getAnnotationV2Handler,
2560
+ paramNames: ["id"]
2561
+ },
2562
+ {
2563
+ method: "DELETE",
2564
+ pattern: /^\/v2\/annotations\/([^/]+)$/,
2565
+ handler: deleteAnnotationV2Handler,
2566
+ paramNames: ["id"]
2567
+ },
2568
+ // Legacy routes (React schema)
2569
+ {
2570
+ method: "GET",
2571
+ pattern: /^\/events$/,
2572
+ handler: globalSseHandler,
2573
+ paramNames: []
2574
+ },
2575
+ {
2576
+ method: "GET",
2577
+ pattern: /^\/pending$/,
2578
+ handler: getAllPendingHandler,
2579
+ paramNames: []
2580
+ },
2581
+ {
2582
+ method: "GET",
2583
+ pattern: /^\/sessions$/,
2584
+ handler: listSessionsHandler,
2585
+ paramNames: []
2586
+ },
2587
+ {
2588
+ method: "POST",
2589
+ pattern: /^\/sessions$/,
2590
+ handler: createSessionHandler,
2591
+ paramNames: []
2592
+ },
2593
+ {
2594
+ method: "GET",
2595
+ pattern: /^\/sessions\/([^/]+)$/,
2596
+ handler: getSessionHandler,
2597
+ paramNames: ["id"]
2598
+ },
2599
+ {
2600
+ method: "GET",
2601
+ pattern: /^\/sessions\/([^/]+)\/events$/,
2602
+ handler: sseHandler,
2603
+ paramNames: ["id"]
2604
+ },
2605
+ {
2606
+ method: "GET",
2607
+ pattern: /^\/sessions\/([^/]+)\/pending$/,
2608
+ handler: getPendingHandler,
2609
+ paramNames: ["id"]
2610
+ },
2611
+ {
2612
+ method: "POST",
2613
+ pattern: /^\/sessions\/([^/]+)\/action$/,
2614
+ handler: requestActionHandler,
2615
+ paramNames: ["id"]
2616
+ },
2617
+ {
2618
+ method: "POST",
2619
+ pattern: /^\/sessions\/([^/]+)\/annotations$/,
2620
+ handler: addAnnotationHandler,
2621
+ paramNames: ["id"]
2622
+ },
2623
+ {
2624
+ method: "PATCH",
2625
+ pattern: /^\/annotations\/([^/]+)$/,
2626
+ handler: updateAnnotationHandler,
2627
+ paramNames: ["id"]
2628
+ },
2629
+ {
2630
+ method: "GET",
2631
+ pattern: /^\/annotations\/([^/]+)$/,
2632
+ handler: getAnnotationHandler,
2633
+ paramNames: ["id"]
2634
+ },
2635
+ {
2636
+ method: "DELETE",
2637
+ pattern: /^\/annotations\/([^/]+)$/,
2638
+ handler: deleteAnnotationHandler,
2639
+ paramNames: ["id"]
2640
+ },
2641
+ {
2642
+ method: "POST",
2643
+ pattern: /^\/annotations\/([^/]+)\/thread$/,
2644
+ handler: addThreadHandler,
2645
+ paramNames: ["id"]
2646
+ }
2647
+ ];
2648
+ }
2649
+ });
2650
+
2651
+ // src/server/index.ts
2652
+ var server_exports = {};
2653
+ __export(server_exports, {
2654
+ addAnnotation: () => addAnnotation,
2655
+ addAnnotationV2: () => addAnnotationV2,
2656
+ addThreadMessage: () => addThreadMessage,
2657
+ clearAll: () => clearAll,
2658
+ createSession: () => createSession,
2659
+ deleteAnnotation: () => deleteAnnotation,
2660
+ deleteAnnotationV2: () => deleteAnnotationV2,
2661
+ getAnnotation: () => getAnnotation,
2662
+ getAnnotationV2: () => getAnnotationV2,
2663
+ getEventsSince: () => getEventsSince,
2664
+ getPendingAnnotations: () => getPendingAnnotations,
2665
+ getSession: () => getSession,
2666
+ getSessionAnnotations: () => getSessionAnnotations,
2667
+ getSessionAnnotationsV2: () => getSessionAnnotationsV2,
2668
+ getSessionWithAnnotations: () => getSessionWithAnnotations,
2669
+ getSessionWithAnnotationsV2: () => getSessionWithAnnotationsV2,
2670
+ getStore: () => getStore,
2671
+ listSessions: () => listSessions,
2672
+ setApiKey: () => setApiKey,
2673
+ setCloudApiKey: () => setCloudApiKey,
2674
+ startHttpServer: () => startHttpServer,
2675
+ startMcpServer: () => startMcpServer,
2676
+ store: () => store,
2677
+ updateAnnotation: () => updateAnnotation,
2678
+ updateAnnotationStatus: () => updateAnnotationStatus,
2679
+ updateAnnotationV2: () => updateAnnotationV2,
2680
+ updateSessionStatus: () => updateSessionStatus
2681
+ });
2682
+ var init_server = __esm({
2683
+ "src/server/index.ts"() {
2684
+ "use strict";
2685
+ init_http();
2686
+ init_mcp();
2687
+ init_http();
2688
+ init_mcp();
2689
+ init_store();
2690
+ }
2691
+ });
2692
+
2693
+ // src/cli.ts
2694
+ var readline = __toESM(require("readline"));
2695
+ var fs = __toESM(require("fs"));
2696
+ var path = __toESM(require("path"));
2697
+ var import_child_process = require("child_process");
2698
+ var command = process.argv[2];
2699
+ async function runInit() {
2700
+ const rl = readline.createInterface({
2701
+ input: process.stdin,
2702
+ output: process.stdout
2703
+ });
2704
+ const question = (q) => new Promise((resolve) => rl.question(q, resolve));
2705
+ console.log(`
2706
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
2707
+ \u2551 Agentation MCP Setup Wizard \u2551
2708
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
2709
+ `);
2710
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
2711
+ const claudeConfigPath = path.join(homeDir, ".claude.json");
2712
+ const hasClaudeConfig = fs.existsSync(claudeConfigPath);
2713
+ if (hasClaudeConfig) {
2714
+ console.log(`\u2713 Found Claude Code config at ${claudeConfigPath}`);
2715
+ } else {
2716
+ console.log(`\u25CB No Claude Code config found at ${claudeConfigPath}`);
2717
+ }
2718
+ console.log();
2719
+ console.log(`The Agentation MCP server allows Claude Code to receive`);
2720
+ console.log(`real-time annotations and respond to feedback.`);
2721
+ console.log();
2722
+ const setupMcp = await question(`Set up MCP server integration? [Y/n] `);
2723
+ const wantsMcp = setupMcp.toLowerCase() !== "n";
2724
+ if (wantsMcp) {
2725
+ let port = 4747;
2726
+ const portAnswer = await question(`HTTP server port [4747]: `);
2727
+ if (portAnswer && !isNaN(parseInt(portAnswer, 10))) {
2728
+ port = parseInt(portAnswer, 10);
2729
+ }
2730
+ const mcpArgs = port === 4747 ? ["mcp", "add", "agentation", "--", "npx", "agentation-vue-mcp", "server"] : ["mcp", "add", "agentation", "--", "npx", "agentation-vue-mcp", "server", "--port", String(port)];
2731
+ console.log();
2732
+ console.log(`Running: claude ${mcpArgs.join(" ")}`);
2733
+ try {
2734
+ const result = (0, import_child_process.spawn)("claude", mcpArgs, { stdio: "inherit" });
2735
+ await new Promise((resolve, reject) => {
2736
+ result.on("close", (code) => {
2737
+ if (code === 0) resolve();
2738
+ else reject(new Error(`claude mcp add exited with code ${code}`));
2739
+ });
2740
+ result.on("error", reject);
2741
+ });
2742
+ console.log(`\u2713 Registered agentation MCP server with Claude Code`);
2743
+ } catch (err) {
2744
+ console.log(`\u2717 Could not register MCP server automatically: ${err}`);
2745
+ console.log(` You can register manually by running:`);
2746
+ console.log(` claude mcp add agentation -- npx agentation-vue-mcp server`);
2747
+ }
2748
+ console.log();
2749
+ const testNow = await question(`Start server and test connection? [Y/n] `);
2750
+ if (testNow.toLowerCase() !== "n") {
2751
+ console.log();
2752
+ console.log(`Starting server on port ${port}...`);
2753
+ const server = (0, import_child_process.spawn)("agentation-vue-mcp", ["server", "--port", String(port)], {
2754
+ stdio: "inherit",
2755
+ detached: false
2756
+ });
2757
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
2758
+ try {
2759
+ const response = await fetch(`http://localhost:${port}/health`);
2760
+ if (response.ok) {
2761
+ console.log();
2762
+ console.log(`\u2713 Server is running on http://localhost:${port}`);
2763
+ console.log(`\u2713 MCP tools available to Claude Code`);
2764
+ console.log();
2765
+ console.log(`Press Ctrl+C to stop the server.`);
2766
+ await new Promise(() => {
2767
+ });
2768
+ } else {
2769
+ console.log(`\u2717 Server health check failed: ${response.status}`);
2770
+ server.kill();
2771
+ }
2772
+ } catch (err) {
2773
+ console.log(`\u2717 Could not connect to server: ${err}`);
2774
+ server.kill();
2775
+ }
2776
+ }
2777
+ }
2778
+ console.log();
2779
+ console.log(`Setup complete! Run 'agentation-vue-mcp doctor' to verify your setup.`);
2780
+ rl.close();
2781
+ }
2782
+ async function runDoctor() {
2783
+ console.log(`
2784
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
2785
+ \u2551 Agentation MCP Doctor \u2551
2786
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
2787
+ `);
2788
+ let allPassed = true;
2789
+ const results = [];
2790
+ const nodeVersion = process.version;
2791
+ const majorVersion = parseInt(nodeVersion.slice(1).split(".")[0], 10);
2792
+ if (majorVersion >= 18) {
2793
+ results.push({ name: "Node.js", status: "pass", message: `${nodeVersion} (18+ required)` });
2794
+ } else {
2795
+ results.push({ name: "Node.js", status: "fail", message: `${nodeVersion} (18+ required)` });
2796
+ allPassed = false;
2797
+ }
2798
+ const homeDir = process.env.HOME || process.env.USERPROFILE || "";
2799
+ const claudeConfigPath = path.join(homeDir, ".claude.json");
2800
+ if (fs.existsSync(claudeConfigPath)) {
2801
+ try {
2802
+ const config = JSON.parse(fs.readFileSync(claudeConfigPath, "utf-8"));
2803
+ let found = false;
2804
+ if (config.mcpServers?.agentation) {
2805
+ found = true;
2806
+ }
2807
+ if (!found && config.projects) {
2808
+ for (const proj of Object.values(config.projects)) {
2809
+ if (proj.mcpServers?.agentation) {
2810
+ found = true;
2811
+ break;
2812
+ }
2813
+ }
2814
+ }
2815
+ if (found) {
2816
+ results.push({ name: "Claude Code config", status: "pass", message: "MCP server configured" });
2817
+ } else {
2818
+ results.push({ name: "Claude Code config", status: "warn", message: "Config exists but no agentation MCP entry. Run: claude mcp add agentation -- npx agentation-vue-mcp server" });
2819
+ }
2820
+ } catch {
2821
+ results.push({ name: "Claude Code config", status: "fail", message: "Could not parse config file" });
2822
+ allPassed = false;
2823
+ }
2824
+ } else {
2825
+ results.push({ name: "Claude Code config", status: "warn", message: "No config found at ~/.claude.json. Run: claude mcp add agentation -- npx agentation-vue-mcp server" });
2826
+ }
2827
+ const oldConfigPath = path.join(homeDir, ".claude", "claude_code_config.json");
2828
+ if (fs.existsSync(oldConfigPath)) {
2829
+ results.push({ name: "Stale config", status: "warn", message: `${oldConfigPath} exists but Claude Code doesn't read this file. Safe to delete.` });
2830
+ }
2831
+ try {
2832
+ const response = await fetch("http://localhost:4747/health", { signal: AbortSignal.timeout(2e3) });
2833
+ if (response.ok) {
2834
+ results.push({ name: "Server (port 4747)", status: "pass", message: "Running and healthy" });
2835
+ } else {
2836
+ results.push({ name: "Server (port 4747)", status: "warn", message: `Responded with ${response.status}` });
2837
+ }
2838
+ } catch {
2839
+ results.push({ name: "Server (port 4747)", status: "warn", message: "Not running (start with: agentation-vue-mcp server)" });
2840
+ }
2841
+ for (const r of results) {
2842
+ const icon = r.status === "pass" ? "\u2713" : r.status === "fail" ? "\u2717" : "\u25CB";
2843
+ const color = r.status === "pass" ? "\x1B[32m" : r.status === "fail" ? "\x1B[31m" : "\x1B[33m";
2844
+ console.log(`${color}${icon}\x1B[0m ${r.name}: ${r.message}`);
2845
+ }
2846
+ console.log();
2847
+ if (allPassed) {
2848
+ console.log(`All checks passed!`);
2849
+ } else {
2850
+ console.log(`Some checks failed. Run 'agentation-vue-mcp init' to fix.`);
2851
+ process.exit(1);
2852
+ }
2853
+ }
2854
+ if (command === "init") {
2855
+ runInit().catch((err) => {
2856
+ console.error("Init failed:", err);
2857
+ process.exit(1);
2858
+ });
2859
+ } else if (command === "doctor") {
2860
+ runDoctor().catch((err) => {
2861
+ console.error("Doctor failed:", err);
2862
+ process.exit(1);
2863
+ });
2864
+ } else if (command === "server") {
2865
+ Promise.resolve().then(() => (init_server(), server_exports)).then(({ startHttpServer: startHttpServer2, startMcpServer: startMcpServer2, setApiKey: setApiKey3 }) => {
2866
+ const args = process.argv.slice(3);
2867
+ let port = 4747;
2868
+ let mcpOnly = false;
2869
+ let httpUrl = "http://localhost:4747";
2870
+ let apiKeyArg;
2871
+ for (let i = 0; i < args.length; i++) {
2872
+ if (args[i] === "--port" && args[i + 1]) {
2873
+ const parsed = parseInt(args[i + 1], 10);
2874
+ if (!isNaN(parsed) && parsed > 0 && parsed < 65536) {
2875
+ port = parsed;
2876
+ if (!args.includes("--http-url")) {
2877
+ httpUrl = `http://localhost:${port}`;
2878
+ }
2879
+ }
2880
+ i++;
2881
+ }
2882
+ if (args[i] === "--mcp-only") {
2883
+ mcpOnly = true;
2884
+ }
2885
+ if (args[i] === "--http-url" && args[i + 1]) {
2886
+ httpUrl = args[i + 1];
2887
+ i++;
2888
+ }
2889
+ if (args[i] === "--api-key" && args[i + 1]) {
2890
+ apiKeyArg = args[i + 1];
2891
+ i++;
2892
+ }
2893
+ }
2894
+ const apiKey2 = apiKeyArg || process.env.AGENTATION_API_KEY;
2895
+ if (apiKey2) {
2896
+ setApiKey3(apiKey2);
2897
+ }
2898
+ if (!mcpOnly) {
2899
+ startHttpServer2(port, apiKey2);
2900
+ }
2901
+ startMcpServer2(httpUrl).catch((err) => {
2902
+ console.error("MCP server error:", err);
2903
+ process.exit(1);
2904
+ });
2905
+ });
2906
+ } else if (command === "help" || command === "--help" || command === "-h" || !command) {
2907
+ console.log(`
2908
+ agentation-vue-mcp - MCP server for Agentation visual feedback
2909
+
2910
+ Usage:
2911
+ agentation-vue-mcp init Interactive setup wizard
2912
+ agentation-vue-mcp server [options] Start the annotation server
2913
+ agentation-vue-mcp doctor Check your setup and diagnose issues
2914
+ agentation-vue-mcp help Show this help message
2915
+
2916
+ Server Options:
2917
+ --port <port> HTTP server port (default: 4747)
2918
+ --mcp-only Skip HTTP server, only run MCP on stdio
2919
+ --http-url <url> HTTP server URL for MCP to fetch from
2920
+ --api-key <key> API key for cloud storage (or set AGENTATION_API_KEY env var)
2921
+
2922
+ Commands:
2923
+ init Guided setup that configures Claude Code to use the MCP server.
2924
+ Registers the server via 'claude mcp add'.
2925
+
2926
+ server Starts both an HTTP server and MCP server for collecting annotations.
2927
+ The HTTP server receives annotations from the React component.
2928
+ The MCP server exposes tools for Claude Code to read/act on annotations.
2929
+
2930
+ doctor Runs diagnostic checks on your setup:
2931
+ - Node.js version
2932
+ - Claude Code configuration
2933
+ - Server connectivity
2934
+
2935
+ Examples:
2936
+ agentation-vue-mcp init Set up Agentation MCP
2937
+ agentation-vue-mcp server Start server on default port 4747
2938
+ agentation-vue-mcp server --port 8080 Start server on port 8080
2939
+ agentation-vue-mcp doctor Check if everything is configured correctly
2940
+
2941
+ # Use cloud storage with API key (local server proxies to cloud)
2942
+ agentation-vue-mcp server --api-key ag_xxx
2943
+
2944
+ # Or using environment variable
2945
+ AGENTATION_API_KEY=ag_xxx agentation-vue-mcp server
2946
+ `);
2947
+ } else {
2948
+ console.error(`Unknown command: ${command}`);
2949
+ console.error("Run 'agentation-vue-mcp help' for usage information.");
2950
+ process.exit(1);
2951
+ }
2952
+ //# sourceMappingURL=cli.js.map