skema-core 0.1.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -3
- package/dist/cli.js +896 -63
- package/dist/index.d.mts +37 -7
- package/dist/index.d.ts +37 -7
- package/dist/index.js +745 -135
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +746 -136
- package/dist/index.mjs.map +1 -1
- package/dist/mcp.js +413 -0
- package/dist/server.d.mts +107 -31
- package/dist/server.d.ts +107 -31
- package/dist/server.js +451 -42
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +430 -32
- package/dist/server.mjs.map +1 -1
- package/package.json +11 -3
package/dist/cli.js
CHANGED
|
@@ -3,11 +3,16 @@
|
|
|
3
3
|
|
|
4
4
|
var fs = require('fs');
|
|
5
5
|
var path = require('path');
|
|
6
|
+
var index_js = require('@modelcontextprotocol/sdk/server/index.js');
|
|
7
|
+
var stdio_js = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
8
|
+
var types_js = require('@modelcontextprotocol/sdk/types.js');
|
|
9
|
+
var WebSocket2 = require('ws');
|
|
6
10
|
require('dotenv/config');
|
|
7
|
-
var ws = require('ws');
|
|
8
11
|
var child_process = require('child_process');
|
|
9
12
|
var generativeAi = require('@google/generative-ai');
|
|
10
13
|
|
|
14
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
|
+
|
|
11
16
|
function _interopNamespace(e) {
|
|
12
17
|
if (e && e.__esModule) return e;
|
|
13
18
|
var n = Object.create(null);
|
|
@@ -28,6 +33,7 @@ function _interopNamespace(e) {
|
|
|
28
33
|
|
|
29
34
|
var fs__namespace = /*#__PURE__*/_interopNamespace(fs);
|
|
30
35
|
var path__namespace = /*#__PURE__*/_interopNamespace(path);
|
|
36
|
+
var WebSocket2__default = /*#__PURE__*/_interopDefault(WebSocket2);
|
|
31
37
|
|
|
32
38
|
var __defProp = Object.defineProperty;
|
|
33
39
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -221,6 +227,411 @@ var init_init = __esm({
|
|
|
221
227
|
}
|
|
222
228
|
}
|
|
223
229
|
});
|
|
230
|
+
|
|
231
|
+
// src/mcp/server.ts
|
|
232
|
+
var server_exports = {};
|
|
233
|
+
__export(server_exports, {
|
|
234
|
+
startMcpServer: () => startMcpServer
|
|
235
|
+
});
|
|
236
|
+
function scheduleReconnect() {
|
|
237
|
+
if (reconnectTimer) return;
|
|
238
|
+
reconnectTimer = setTimeout(async () => {
|
|
239
|
+
reconnectTimer = null;
|
|
240
|
+
console.error("[Skema MCP] Attempting to reconnect to daemon...");
|
|
241
|
+
try {
|
|
242
|
+
await connectToDaemon();
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}, RECONNECT_INTERVAL);
|
|
246
|
+
}
|
|
247
|
+
function connectToDaemon() {
|
|
248
|
+
return new Promise((resolve, reject) => {
|
|
249
|
+
if (daemonWs && daemonConnected) {
|
|
250
|
+
resolve();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (daemonWs) {
|
|
254
|
+
daemonWs.removeAllListeners();
|
|
255
|
+
daemonWs = null;
|
|
256
|
+
}
|
|
257
|
+
daemonWs = new WebSocket2__default.default(DAEMON_URL);
|
|
258
|
+
daemonWs.on("open", () => {
|
|
259
|
+
daemonConnected = true;
|
|
260
|
+
console.error("[Skema MCP] Connected to daemon at", DAEMON_URL);
|
|
261
|
+
daemonWs.send(JSON.stringify({
|
|
262
|
+
id: `mcp-identify-${Date.now()}`,
|
|
263
|
+
type: "identify",
|
|
264
|
+
client: "mcp-server"
|
|
265
|
+
}));
|
|
266
|
+
resolve();
|
|
267
|
+
});
|
|
268
|
+
daemonWs.on("message", (data) => {
|
|
269
|
+
try {
|
|
270
|
+
const msg = JSON.parse(data.toString());
|
|
271
|
+
if (msg.id && pendingRequests.has(msg.id)) {
|
|
272
|
+
const pending = pendingRequests.get(msg.id);
|
|
273
|
+
pendingRequests.delete(msg.id);
|
|
274
|
+
clearTimeout(pending.timeout);
|
|
275
|
+
if (msg.type === "error") {
|
|
276
|
+
pending.reject(new Error(msg.error));
|
|
277
|
+
} else {
|
|
278
|
+
pending.resolve(msg);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} catch (e) {
|
|
282
|
+
console.error("[Skema MCP] Failed to parse daemon message:", e);
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
daemonWs.on("close", () => {
|
|
286
|
+
daemonConnected = false;
|
|
287
|
+
daemonWs = null;
|
|
288
|
+
console.error("[Skema MCP] Disconnected from daemon");
|
|
289
|
+
scheduleReconnect();
|
|
290
|
+
});
|
|
291
|
+
daemonWs.on("error", (err) => {
|
|
292
|
+
daemonConnected = false;
|
|
293
|
+
console.error("[Skema MCP] Daemon connection error:", err.message);
|
|
294
|
+
reject(err);
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
function sendToDaemon(type, payload = {}) {
|
|
299
|
+
return new Promise(async (resolve, reject) => {
|
|
300
|
+
try {
|
|
301
|
+
if (!daemonConnected) {
|
|
302
|
+
await connectToDaemon();
|
|
303
|
+
}
|
|
304
|
+
if (!daemonWs || !daemonConnected) {
|
|
305
|
+
reject(new Error("Not connected to Skema daemon. Is it running?"));
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const id = `mcp-${++messageIdCounter}`;
|
|
309
|
+
const timeout = setTimeout(() => {
|
|
310
|
+
pendingRequests.delete(id);
|
|
311
|
+
reject(new Error(`Daemon request timed out: ${type}`));
|
|
312
|
+
}, 3e4);
|
|
313
|
+
pendingRequests.set(id, { resolve, reject, timeout });
|
|
314
|
+
daemonWs.send(JSON.stringify({ id, type, ...payload }));
|
|
315
|
+
} catch (err) {
|
|
316
|
+
reject(err);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
function success(data) {
|
|
321
|
+
return {
|
|
322
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function error(message) {
|
|
326
|
+
return {
|
|
327
|
+
content: [{ type: "text", text: message }],
|
|
328
|
+
isError: true
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
async function handleTool(name, args2) {
|
|
332
|
+
switch (name) {
|
|
333
|
+
case "skema_get_pending": {
|
|
334
|
+
try {
|
|
335
|
+
const response = await sendToDaemon("get-pending-annotations");
|
|
336
|
+
return success({
|
|
337
|
+
count: response.count,
|
|
338
|
+
annotations: response.annotations
|
|
339
|
+
});
|
|
340
|
+
} catch (err) {
|
|
341
|
+
return error(`Failed to get pending annotations: ${err.message}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
case "skema_get_all_annotations": {
|
|
345
|
+
try {
|
|
346
|
+
const response = await sendToDaemon("get-all-annotations");
|
|
347
|
+
return success({
|
|
348
|
+
count: response.count,
|
|
349
|
+
annotations: response.annotations
|
|
350
|
+
});
|
|
351
|
+
} catch (err) {
|
|
352
|
+
return error(`Failed to get annotations: ${err.message}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
case "skema_get_annotation": {
|
|
356
|
+
try {
|
|
357
|
+
const response = await sendToDaemon("get-annotation", {
|
|
358
|
+
annotationId: args2.annotationId
|
|
359
|
+
});
|
|
360
|
+
return success(response.annotation);
|
|
361
|
+
} catch (err) {
|
|
362
|
+
return error(`Failed to get annotation: ${err.message}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
case "skema_acknowledge": {
|
|
366
|
+
try {
|
|
367
|
+
await sendToDaemon("acknowledge-annotation", {
|
|
368
|
+
annotationId: args2.annotationId
|
|
369
|
+
});
|
|
370
|
+
return success({ acknowledged: true, annotationId: args2.annotationId });
|
|
371
|
+
} catch (err) {
|
|
372
|
+
return error(`Failed to acknowledge: ${err.message}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
case "skema_resolve": {
|
|
376
|
+
try {
|
|
377
|
+
await sendToDaemon("resolve-annotation", {
|
|
378
|
+
annotationId: args2.annotationId,
|
|
379
|
+
summary: args2.summary
|
|
380
|
+
});
|
|
381
|
+
return success({
|
|
382
|
+
resolved: true,
|
|
383
|
+
annotationId: args2.annotationId,
|
|
384
|
+
summary: args2.summary
|
|
385
|
+
});
|
|
386
|
+
} catch (err) {
|
|
387
|
+
return error(`Failed to resolve: ${err.message}`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
case "skema_dismiss": {
|
|
391
|
+
try {
|
|
392
|
+
await sendToDaemon("dismiss-annotation", {
|
|
393
|
+
annotationId: args2.annotationId,
|
|
394
|
+
reason: args2.reason
|
|
395
|
+
});
|
|
396
|
+
return success({
|
|
397
|
+
dismissed: true,
|
|
398
|
+
annotationId: args2.annotationId,
|
|
399
|
+
reason: args2.reason
|
|
400
|
+
});
|
|
401
|
+
} catch (err) {
|
|
402
|
+
return error(`Failed to dismiss: ${err.message}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
case "skema_watch": {
|
|
406
|
+
const timeoutSeconds = Math.min(300, Math.max(1, args2?.timeoutSeconds ?? 120));
|
|
407
|
+
const pollInterval = 2e3;
|
|
408
|
+
const maxPolls = Math.ceil(timeoutSeconds * 1e3 / pollInterval);
|
|
409
|
+
for (let i = 0; i < maxPolls; i++) {
|
|
410
|
+
try {
|
|
411
|
+
const response = await sendToDaemon("get-pending-annotations");
|
|
412
|
+
if (response.count > 0) {
|
|
413
|
+
return success({
|
|
414
|
+
timeout: false,
|
|
415
|
+
count: response.count,
|
|
416
|
+
annotations: response.annotations
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
} catch (err) {
|
|
420
|
+
return error(`Lost connection to daemon: ${err.message}`);
|
|
421
|
+
}
|
|
422
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
423
|
+
}
|
|
424
|
+
return success({
|
|
425
|
+
timeout: true,
|
|
426
|
+
message: `No new annotations within ${timeoutSeconds} seconds`
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
default:
|
|
430
|
+
return error(`Unknown tool: ${name}`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
async function startMcpServer() {
|
|
434
|
+
try {
|
|
435
|
+
await connectToDaemon();
|
|
436
|
+
} catch {
|
|
437
|
+
console.error("[Skema MCP] Warning: Could not connect to daemon. Will auto-retry...");
|
|
438
|
+
scheduleReconnect();
|
|
439
|
+
}
|
|
440
|
+
const server = new index_js.Server(
|
|
441
|
+
{
|
|
442
|
+
name: "skema",
|
|
443
|
+
version: "0.3.0"
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
capabilities: {
|
|
447
|
+
tools: {},
|
|
448
|
+
resources: {}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
);
|
|
452
|
+
server.setRequestHandler(types_js.ListToolsRequestSchema, async () => {
|
|
453
|
+
return { tools: TOOLS };
|
|
454
|
+
});
|
|
455
|
+
server.setRequestHandler(types_js.CallToolRequestSchema, async (request) => {
|
|
456
|
+
const { name, arguments: args2 } = request.params;
|
|
457
|
+
try {
|
|
458
|
+
return await handleTool(name, args2);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
461
|
+
return error(message);
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
server.setRequestHandler(types_js.ListResourcesRequestSchema, async () => {
|
|
465
|
+
return {
|
|
466
|
+
resources: [
|
|
467
|
+
{
|
|
468
|
+
uri: "skema://status",
|
|
469
|
+
name: "Skema Status",
|
|
470
|
+
description: "Current Skema daemon connection status and pending annotation count",
|
|
471
|
+
mimeType: "application/json"
|
|
472
|
+
}
|
|
473
|
+
]
|
|
474
|
+
};
|
|
475
|
+
});
|
|
476
|
+
server.setRequestHandler(types_js.ReadResourceRequestSchema, async (request) => {
|
|
477
|
+
const { uri } = request.params;
|
|
478
|
+
switch (uri) {
|
|
479
|
+
case "skema://status": {
|
|
480
|
+
let pendingCount = 0;
|
|
481
|
+
let connected = false;
|
|
482
|
+
try {
|
|
483
|
+
const response = await sendToDaemon("get-pending-annotations");
|
|
484
|
+
pendingCount = response.count;
|
|
485
|
+
connected = true;
|
|
486
|
+
} catch {
|
|
487
|
+
connected = false;
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
contents: [{
|
|
491
|
+
uri,
|
|
492
|
+
mimeType: "application/json",
|
|
493
|
+
text: JSON.stringify({
|
|
494
|
+
daemonConnected: connected,
|
|
495
|
+
daemonUrl: DAEMON_URL,
|
|
496
|
+
pendingAnnotations: pendingCount
|
|
497
|
+
}, null, 2)
|
|
498
|
+
}]
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
default:
|
|
502
|
+
throw new Error(`Unknown resource: ${uri}`);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
505
|
+
server.oninitialized = () => {
|
|
506
|
+
const clientInfo = server.getClientVersion();
|
|
507
|
+
const clientName = clientInfo?.name || "unknown";
|
|
508
|
+
console.error("[Skema MCP] Connected client:", clientName, clientInfo?.version || "");
|
|
509
|
+
if (daemonWs && daemonConnected) {
|
|
510
|
+
daemonWs.send(JSON.stringify({
|
|
511
|
+
id: `mcp-client-info-${Date.now()}`,
|
|
512
|
+
type: "mcp-client-info",
|
|
513
|
+
clientName,
|
|
514
|
+
clientVersion: clientInfo?.version
|
|
515
|
+
}));
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
const transport = new stdio_js.StdioServerTransport();
|
|
519
|
+
await server.connect(transport);
|
|
520
|
+
console.error("[Skema MCP] Server started");
|
|
521
|
+
console.error("[Skema MCP] Daemon URL:", DAEMON_URL);
|
|
522
|
+
console.error("[Skema MCP] Tools: skema_get_pending, skema_acknowledge, skema_resolve, skema_dismiss, skema_watch");
|
|
523
|
+
}
|
|
524
|
+
var DAEMON_PORT, DAEMON_URL, daemonWs, daemonConnected, reconnectTimer, RECONNECT_INTERVAL, messageIdCounter, pendingRequests, TOOLS;
|
|
525
|
+
var init_server = __esm({
|
|
526
|
+
"src/mcp/server.ts"() {
|
|
527
|
+
DAEMON_PORT = parseInt(process.env.SKEMA_PORT || "9999", 10);
|
|
528
|
+
DAEMON_URL = `ws://localhost:${DAEMON_PORT}`;
|
|
529
|
+
daemonWs = null;
|
|
530
|
+
daemonConnected = false;
|
|
531
|
+
reconnectTimer = null;
|
|
532
|
+
RECONNECT_INTERVAL = 3e3;
|
|
533
|
+
messageIdCounter = 0;
|
|
534
|
+
pendingRequests = /* @__PURE__ */ new Map();
|
|
535
|
+
TOOLS = [
|
|
536
|
+
{
|
|
537
|
+
name: "skema_get_pending",
|
|
538
|
+
description: "Get all pending annotations from the Skema browser overlay. These are visual annotations (DOM selections, drawings, multi-selects) that the user has made on their web page and wants you to implement as code changes. Each annotation includes the user's comment describing what they want, plus element selectors, CSS paths, bounding boxes, and other context to help you find the right code to modify.",
|
|
539
|
+
inputSchema: {
|
|
540
|
+
type: "object",
|
|
541
|
+
properties: {},
|
|
542
|
+
required: []
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: "skema_get_all_annotations",
|
|
547
|
+
description: "Get all annotations (pending, acknowledged, resolved, dismissed). Useful for reviewing the full history of annotation requests and their current status.",
|
|
548
|
+
inputSchema: {
|
|
549
|
+
type: "object",
|
|
550
|
+
properties: {},
|
|
551
|
+
required: []
|
|
552
|
+
}
|
|
553
|
+
},
|
|
554
|
+
{
|
|
555
|
+
name: "skema_get_annotation",
|
|
556
|
+
description: "Get details of a specific annotation by ID.",
|
|
557
|
+
inputSchema: {
|
|
558
|
+
type: "object",
|
|
559
|
+
properties: {
|
|
560
|
+
annotationId: {
|
|
561
|
+
type: "string",
|
|
562
|
+
description: "The annotation ID to look up"
|
|
563
|
+
}
|
|
564
|
+
},
|
|
565
|
+
required: ["annotationId"]
|
|
566
|
+
}
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
name: "skema_acknowledge",
|
|
570
|
+
description: "Mark an annotation as acknowledged. Use this to tell the user you've seen their annotation and are working on it. The browser overlay will update to show the status change.",
|
|
571
|
+
inputSchema: {
|
|
572
|
+
type: "object",
|
|
573
|
+
properties: {
|
|
574
|
+
annotationId: {
|
|
575
|
+
type: "string",
|
|
576
|
+
description: "The annotation ID to acknowledge"
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
required: ["annotationId"]
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
name: "skema_resolve",
|
|
584
|
+
description: "Mark an annotation as resolved after you've implemented the requested change. Include a summary of what you did so the user can see it in the browser overlay.",
|
|
585
|
+
inputSchema: {
|
|
586
|
+
type: "object",
|
|
587
|
+
properties: {
|
|
588
|
+
annotationId: {
|
|
589
|
+
type: "string",
|
|
590
|
+
description: "The annotation ID to resolve"
|
|
591
|
+
},
|
|
592
|
+
summary: {
|
|
593
|
+
type: "string",
|
|
594
|
+
description: "Summary of what was done to resolve this annotation"
|
|
595
|
+
}
|
|
596
|
+
},
|
|
597
|
+
required: ["annotationId"]
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
name: "skema_dismiss",
|
|
602
|
+
description: "Dismiss an annotation if you decide not to implement it. Include a reason so the user understands why.",
|
|
603
|
+
inputSchema: {
|
|
604
|
+
type: "object",
|
|
605
|
+
properties: {
|
|
606
|
+
annotationId: {
|
|
607
|
+
type: "string",
|
|
608
|
+
description: "The annotation ID to dismiss"
|
|
609
|
+
},
|
|
610
|
+
reason: {
|
|
611
|
+
type: "string",
|
|
612
|
+
description: "Reason for dismissing this annotation"
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
required: ["annotationId", "reason"]
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
{
|
|
619
|
+
name: "skema_watch",
|
|
620
|
+
description: "Wait for new annotations to appear. This blocks until the user creates new annotations in the browser overlay, then returns them as a batch. Use this in a loop for hands-free processing: call skema_watch, process the returned annotations, resolve them, then call skema_watch again.",
|
|
621
|
+
inputSchema: {
|
|
622
|
+
type: "object",
|
|
623
|
+
properties: {
|
|
624
|
+
timeoutSeconds: {
|
|
625
|
+
type: "number",
|
|
626
|
+
description: "Max seconds to wait for annotations (default: 120, max: 300)"
|
|
627
|
+
}
|
|
628
|
+
},
|
|
629
|
+
required: []
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
];
|
|
633
|
+
}
|
|
634
|
+
});
|
|
224
635
|
var PROVIDERS = {
|
|
225
636
|
gemini: {
|
|
226
637
|
command: "gemini",
|
|
@@ -430,6 +841,50 @@ function isProviderAvailable(provider) {
|
|
|
430
841
|
function getAvailableProviders() {
|
|
431
842
|
return ["gemini", "claude"].filter(isProviderAvailable);
|
|
432
843
|
}
|
|
844
|
+
function checkProviderAuthorized(provider) {
|
|
845
|
+
const { execSync: execSync3 } = __require("child_process");
|
|
846
|
+
try {
|
|
847
|
+
if (provider === "gemini") {
|
|
848
|
+
execSync3("gemini --version", { stdio: "ignore", timeout: 5e3 });
|
|
849
|
+
return true;
|
|
850
|
+
} else if (provider === "claude") {
|
|
851
|
+
execSync3("claude --version", { stdio: "ignore", timeout: 5e3 });
|
|
852
|
+
return true;
|
|
853
|
+
}
|
|
854
|
+
return false;
|
|
855
|
+
} catch {
|
|
856
|
+
return false;
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
function getProviderStatus(provider) {
|
|
860
|
+
const installed = isProviderAvailable(provider);
|
|
861
|
+
if (!installed) {
|
|
862
|
+
return {
|
|
863
|
+
installed: false,
|
|
864
|
+
authorized: false,
|
|
865
|
+
message: `${provider} CLI not installed`
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
const authorized = checkProviderAuthorized(provider);
|
|
869
|
+
if (!authorized) {
|
|
870
|
+
return {
|
|
871
|
+
installed: true,
|
|
872
|
+
authorized: false,
|
|
873
|
+
message: `${provider} CLI installed but not authorized`
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
return {
|
|
877
|
+
installed: true,
|
|
878
|
+
authorized: true,
|
|
879
|
+
message: "Ready"
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
function getAllProviderStatuses() {
|
|
883
|
+
return {
|
|
884
|
+
gemini: getProviderStatus("gemini"),
|
|
885
|
+
claude: getProviderStatus("claude")
|
|
886
|
+
};
|
|
887
|
+
}
|
|
433
888
|
|
|
434
889
|
// src/lib/utils.ts
|
|
435
890
|
function getGridCellReference(x, y, gridSize = 100) {
|
|
@@ -442,7 +897,7 @@ function getGridCellReference(x, y, gridSize = 100) {
|
|
|
442
897
|
// src/server/prompts.ts
|
|
443
898
|
var CRITICAL_RULES = `CRITICAL RULES:
|
|
444
899
|
- Do NOT create new files. Only edit existing files.
|
|
445
|
-
- Do NOT modify the import
|
|
900
|
+
- Do NOT modify the Skema overlay component import or the SkemaOverlay component itself.
|
|
446
901
|
- Ensure all JSX tags are properly closed - every <tag> needs a matching </tag>.
|
|
447
902
|
- Do NOT run any shell commands. No npm, no git, no lint, no build commands. Just edit files.
|
|
448
903
|
- STOP immediately after making the file changes. Do not verify, do not run tests, do not check status.`;
|
|
@@ -581,7 +1036,7 @@ var DRAWING_IMPLEMENTATION_GUIDELINES = `- **CRITICAL: DO NOT CREATE ANY NEW FIL
|
|
|
581
1036
|
var DRAWING_ERROR_PREVENTION_RULES = `1. **NEVER CREATE NEW FILES** - Do NOT create new component files, utility files, or any other files. Write everything inline in the existing file
|
|
582
1037
|
2. You do not need to update package.json or anything, just add / edit the react component.
|
|
583
1038
|
3. Do NOT add import statements in the middle of the file or inside JSX - imports go ONLY at the top
|
|
584
|
-
4. Do NOT modify the
|
|
1039
|
+
4. Do NOT modify the Skema overlay component import or the SkemaOverlay component itself
|
|
585
1040
|
5. If you need something that requires an import and it's not already imported, either use an alternative that doesn't need an import, or add the import at the very TOP of the file with the other imports
|
|
586
1041
|
6. DONT MAKE ANY CHANGES THAT WOULD RESULT IN A Build Error
|
|
587
1042
|
7. **JSX SYNTAX VALIDATION** - ALWAYS ensure every JSX tag is properly closed. Every opening tag like <div>, <a>, <span>, <button> MUST have a matching closing tag </div>, </a>, </span>, </button>. Self-closing tags like <img />, <input />, <br /> must end with />. Before finishing, mentally verify all tag pairs are balanced.`;
|
|
@@ -702,8 +1157,8 @@ async function analyzeWithGemini(base64Image, apiKey, model = "gemini-2.5-flash"
|
|
|
702
1157
|
description: text,
|
|
703
1158
|
provider: "gemini"
|
|
704
1159
|
};
|
|
705
|
-
} catch (
|
|
706
|
-
const message =
|
|
1160
|
+
} catch (error2) {
|
|
1161
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
707
1162
|
console.error("[Vision] Gemini analysis failed:", message);
|
|
708
1163
|
return {
|
|
709
1164
|
success: false,
|
|
@@ -748,8 +1203,8 @@ async function analyzeWithClaude(base64Image, apiKey, model = "claude-sonnet-4-2
|
|
|
748
1203
|
description,
|
|
749
1204
|
provider: "claude"
|
|
750
1205
|
};
|
|
751
|
-
} catch (
|
|
752
|
-
const message =
|
|
1206
|
+
} catch (error2) {
|
|
1207
|
+
const message = error2 instanceof Error ? error2.message : String(error2);
|
|
753
1208
|
console.error("[Vision] Claude analysis failed:", message);
|
|
754
1209
|
return {
|
|
755
1210
|
success: false,
|
|
@@ -786,10 +1241,97 @@ function isVisionAvailable(provider) {
|
|
|
786
1241
|
}
|
|
787
1242
|
}
|
|
788
1243
|
|
|
1244
|
+
// src/server/annotation-store.ts
|
|
1245
|
+
var storedAnnotations = /* @__PURE__ */ new Map();
|
|
1246
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
1247
|
+
function notify(event, annotation) {
|
|
1248
|
+
for (const listener of listeners) {
|
|
1249
|
+
try {
|
|
1250
|
+
listener(event, annotation);
|
|
1251
|
+
} catch (e) {
|
|
1252
|
+
console.error("[AnnotationStore] Listener error:", e);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
function queueAnnotation(annotation, comment) {
|
|
1257
|
+
const id = annotation.id || `ann-${Date.now()}`;
|
|
1258
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1259
|
+
const stored = {
|
|
1260
|
+
annotation: { ...annotation, id },
|
|
1261
|
+
comment,
|
|
1262
|
+
status: "pending",
|
|
1263
|
+
createdAt: now,
|
|
1264
|
+
updatedAt: now
|
|
1265
|
+
};
|
|
1266
|
+
storedAnnotations.set(id, stored);
|
|
1267
|
+
console.log(`[AnnotationStore] Queued annotation ${id}: "${comment.slice(0, 50)}..."`);
|
|
1268
|
+
notify("annotation.created", stored);
|
|
1269
|
+
return stored;
|
|
1270
|
+
}
|
|
1271
|
+
function getPendingAnnotations() {
|
|
1272
|
+
return Array.from(storedAnnotations.values()).filter((a) => a.status === "pending");
|
|
1273
|
+
}
|
|
1274
|
+
function getAllAnnotations() {
|
|
1275
|
+
return Array.from(storedAnnotations.values());
|
|
1276
|
+
}
|
|
1277
|
+
function getAnnotation(id) {
|
|
1278
|
+
return storedAnnotations.get(id);
|
|
1279
|
+
}
|
|
1280
|
+
function acknowledgeAnnotation(id) {
|
|
1281
|
+
const stored = storedAnnotations.get(id);
|
|
1282
|
+
if (!stored) return void 0;
|
|
1283
|
+
stored.status = "acknowledged";
|
|
1284
|
+
stored.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1285
|
+
notify("annotation.updated", stored);
|
|
1286
|
+
return stored;
|
|
1287
|
+
}
|
|
1288
|
+
function resolveAnnotation(id, summary) {
|
|
1289
|
+
const stored = storedAnnotations.get(id);
|
|
1290
|
+
if (!stored) return void 0;
|
|
1291
|
+
stored.status = "resolved";
|
|
1292
|
+
stored.resolvedBy = "agent";
|
|
1293
|
+
stored.resolutionSummary = summary;
|
|
1294
|
+
stored.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1295
|
+
notify("annotation.updated", stored);
|
|
1296
|
+
return stored;
|
|
1297
|
+
}
|
|
1298
|
+
function dismissAnnotation(id, reason) {
|
|
1299
|
+
const stored = storedAnnotations.get(id);
|
|
1300
|
+
if (!stored) return void 0;
|
|
1301
|
+
stored.status = "dismissed";
|
|
1302
|
+
stored.resolvedBy = "agent";
|
|
1303
|
+
stored.dismissalReason = reason;
|
|
1304
|
+
stored.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1305
|
+
notify("annotation.updated", stored);
|
|
1306
|
+
return stored;
|
|
1307
|
+
}
|
|
1308
|
+
function clearAnnotations() {
|
|
1309
|
+
storedAnnotations.clear();
|
|
1310
|
+
}
|
|
1311
|
+
function getPendingCount() {
|
|
1312
|
+
let count = 0;
|
|
1313
|
+
for (const a of storedAnnotations.values()) {
|
|
1314
|
+
if (a.status === "pending") count++;
|
|
1315
|
+
}
|
|
1316
|
+
return count;
|
|
1317
|
+
}
|
|
1318
|
+
|
|
789
1319
|
// src/server/daemon.ts
|
|
790
1320
|
var currentProvider = "gemini";
|
|
791
1321
|
var workingDirectory = process.cwd();
|
|
1322
|
+
var currentMode = "direct-cli";
|
|
792
1323
|
var annotationSnapshots2 = /* @__PURE__ */ new Map();
|
|
1324
|
+
var mcpServerClient = null;
|
|
1325
|
+
var mcpClientName = null;
|
|
1326
|
+
function getAnnotationCounts() {
|
|
1327
|
+
const all = getAllAnnotations();
|
|
1328
|
+
return {
|
|
1329
|
+
pending: all.filter((a) => a.status === "pending").length,
|
|
1330
|
+
acknowledged: all.filter((a) => a.status === "acknowledged").length,
|
|
1331
|
+
resolved: all.filter((a) => a.status === "resolved").length,
|
|
1332
|
+
dismissed: all.filter((a) => a.status === "dismissed").length
|
|
1333
|
+
};
|
|
1334
|
+
}
|
|
793
1335
|
function createSnapshot2(annotationId) {
|
|
794
1336
|
try {
|
|
795
1337
|
const stashRef = child_process.execSync("git stash create", { cwd: workingDirectory, encoding: "utf-8" }).trim();
|
|
@@ -801,8 +1343,8 @@ function createSnapshot2(annotationId) {
|
|
|
801
1343
|
const headRef = child_process.execSync("git rev-parse HEAD", { cwd: workingDirectory, encoding: "utf-8" }).trim();
|
|
802
1344
|
annotationSnapshots2.set(annotationId, headRef);
|
|
803
1345
|
return headRef;
|
|
804
|
-
} catch (
|
|
805
|
-
console.error("[Daemon] Failed to create snapshot:",
|
|
1346
|
+
} catch (error2) {
|
|
1347
|
+
console.error("[Daemon] Failed to create snapshot:", error2);
|
|
806
1348
|
return null;
|
|
807
1349
|
}
|
|
808
1350
|
}
|
|
@@ -828,8 +1370,8 @@ function revertSnapshot(annotationId) {
|
|
|
828
1370
|
}
|
|
829
1371
|
annotationSnapshots2.delete(annotationId);
|
|
830
1372
|
return { success: true, message: `Reverted ${changedFiles.length} file(s)` };
|
|
831
|
-
} catch (
|
|
832
|
-
return { success: false, message: String(
|
|
1373
|
+
} catch (error2) {
|
|
1374
|
+
return { success: false, message: String(error2) };
|
|
833
1375
|
}
|
|
834
1376
|
}
|
|
835
1377
|
var handlers = {
|
|
@@ -849,17 +1391,45 @@ var handlers = {
|
|
|
849
1391
|
if (!["gemini", "claude"].includes(newProvider)) {
|
|
850
1392
|
return { id: msg.id, type: "error", error: `Invalid provider: ${newProvider}` };
|
|
851
1393
|
}
|
|
852
|
-
if (!isProviderAvailable(newProvider)) {
|
|
1394
|
+
if (currentMode === "direct-cli" && !isProviderAvailable(newProvider)) {
|
|
853
1395
|
return {
|
|
854
1396
|
id: msg.id,
|
|
855
1397
|
type: "error",
|
|
856
|
-
error: `Provider "${newProvider}" is not installed
|
|
1398
|
+
error: `Provider "${newProvider}" CLI is not installed.`
|
|
857
1399
|
};
|
|
858
1400
|
}
|
|
859
1401
|
currentProvider = newProvider;
|
|
860
1402
|
console.log(`[Daemon] Switched to provider: ${currentProvider}`);
|
|
861
1403
|
return { id: msg.id, type: "provider-changed", provider: currentProvider };
|
|
862
1404
|
},
|
|
1405
|
+
"check-providers": async (msg) => {
|
|
1406
|
+
const statuses = getAllProviderStatuses();
|
|
1407
|
+
return {
|
|
1408
|
+
id: msg.id,
|
|
1409
|
+
type: "provider-statuses",
|
|
1410
|
+
providerStatus: statuses
|
|
1411
|
+
};
|
|
1412
|
+
},
|
|
1413
|
+
// -------------------------------------------------------------------------
|
|
1414
|
+
// Mode Management
|
|
1415
|
+
// -------------------------------------------------------------------------
|
|
1416
|
+
"get-mode": async (msg) => {
|
|
1417
|
+
return {
|
|
1418
|
+
id: msg.id,
|
|
1419
|
+
type: "mode",
|
|
1420
|
+
mode: currentMode,
|
|
1421
|
+
availableModes: ["direct-cli", "mcp"]
|
|
1422
|
+
};
|
|
1423
|
+
},
|
|
1424
|
+
"set-mode": async (msg) => {
|
|
1425
|
+
const newMode = msg.mode;
|
|
1426
|
+
if (!["direct-cli", "mcp"].includes(newMode)) {
|
|
1427
|
+
return { id: msg.id, type: "error", error: `Invalid mode: ${newMode}` };
|
|
1428
|
+
}
|
|
1429
|
+
currentMode = newMode;
|
|
1430
|
+
console.log(`[Daemon] Switched to mode: ${currentMode}`);
|
|
1431
|
+
return { id: msg.id, type: "mode-changed", mode: currentMode };
|
|
1432
|
+
},
|
|
863
1433
|
// -------------------------------------------------------------------------
|
|
864
1434
|
// AI Generation (streaming)
|
|
865
1435
|
// -------------------------------------------------------------------------
|
|
@@ -867,21 +1437,41 @@ var handlers = {
|
|
|
867
1437
|
const annotation = msg.annotation;
|
|
868
1438
|
const projectContext = msg.projectContext;
|
|
869
1439
|
const annotationId = annotation.id || `temp-${Date.now()}`;
|
|
1440
|
+
const requestMode = msg.mode || currentMode;
|
|
1441
|
+
const requestProvider = msg.provider || currentProvider;
|
|
1442
|
+
if (requestMode === "mcp") {
|
|
1443
|
+
const comment = annotation.comment || "";
|
|
1444
|
+
const stored = queueAnnotation(annotation, comment);
|
|
1445
|
+
const counts = getAnnotationCounts();
|
|
1446
|
+
sendMessage(ws, {
|
|
1447
|
+
id: msg.id,
|
|
1448
|
+
type: "annotation-queued",
|
|
1449
|
+
success: true,
|
|
1450
|
+
annotationId: stored.annotation.id,
|
|
1451
|
+
pendingCount: getPendingCount(),
|
|
1452
|
+
annotationCounts: counts
|
|
1453
|
+
});
|
|
1454
|
+
broadcastToClients({
|
|
1455
|
+
type: "mcp-annotation-counts",
|
|
1456
|
+
counts
|
|
1457
|
+
});
|
|
1458
|
+
return;
|
|
1459
|
+
}
|
|
870
1460
|
createSnapshot2(annotationId);
|
|
871
1461
|
let visionDescription = "";
|
|
872
1462
|
const drawingAnnotation = annotation;
|
|
873
1463
|
if (annotation.type === "drawing" && drawingAnnotation.drawingImage) {
|
|
1464
|
+
sendMessage(ws, {
|
|
1465
|
+
id: msg.id,
|
|
1466
|
+
type: "ai-event",
|
|
1467
|
+
event: {
|
|
1468
|
+
type: "text",
|
|
1469
|
+
content: `[Analyzing drawing with vision...]`,
|
|
1470
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1471
|
+
provider: requestProvider
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
874
1474
|
if (isVisionAvailable()) {
|
|
875
|
-
sendMessage(ws, {
|
|
876
|
-
id: msg.id,
|
|
877
|
-
type: "ai-event",
|
|
878
|
-
event: {
|
|
879
|
-
type: "text",
|
|
880
|
-
content: `[Analyzing drawing with Gemini vision...]`,
|
|
881
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
882
|
-
provider: currentProvider
|
|
883
|
-
}
|
|
884
|
-
});
|
|
885
1475
|
const visionResult = await analyzeImage(drawingAnnotation.drawingImage, {
|
|
886
1476
|
provider: "gemini"
|
|
887
1477
|
});
|
|
@@ -895,7 +1485,7 @@ var handlers = {
|
|
|
895
1485
|
content: `[Vision analysis complete]
|
|
896
1486
|
${visionDescription}`,
|
|
897
1487
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
898
|
-
provider:
|
|
1488
|
+
provider: requestProvider
|
|
899
1489
|
}
|
|
900
1490
|
});
|
|
901
1491
|
} else {
|
|
@@ -906,7 +1496,7 @@ ${visionDescription}`,
|
|
|
906
1496
|
type: "error",
|
|
907
1497
|
content: `Vision analysis failed: ${visionResult.error}`,
|
|
908
1498
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
909
|
-
provider:
|
|
1499
|
+
provider: requestProvider
|
|
910
1500
|
}
|
|
911
1501
|
});
|
|
912
1502
|
}
|
|
@@ -918,14 +1508,13 @@ ${visionDescription}`,
|
|
|
918
1508
|
type: "text",
|
|
919
1509
|
content: `[Vision not available - set GEMINI_API_KEY for image analysis]`,
|
|
920
1510
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
921
|
-
provider:
|
|
1511
|
+
provider: requestProvider
|
|
922
1512
|
}
|
|
923
1513
|
});
|
|
924
1514
|
}
|
|
925
1515
|
}
|
|
926
1516
|
const prompt = buildPromptFromAnnotation(annotation, projectContext, {
|
|
927
1517
|
fastMode: msg.fastMode === true,
|
|
928
|
-
// Default to detailed mode (false) unless explicitly set to true
|
|
929
1518
|
visionDescription
|
|
930
1519
|
});
|
|
931
1520
|
sendMessage(ws, {
|
|
@@ -935,11 +1524,25 @@ ${visionDescription}`,
|
|
|
935
1524
|
type: "debug",
|
|
936
1525
|
content: prompt,
|
|
937
1526
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
938
|
-
provider:
|
|
1527
|
+
provider: requestProvider
|
|
939
1528
|
}
|
|
940
1529
|
});
|
|
1530
|
+
const cliProvider = requestProvider;
|
|
1531
|
+
if (!isProviderAvailable(cliProvider)) {
|
|
1532
|
+
sendMessage(ws, {
|
|
1533
|
+
id: msg.id,
|
|
1534
|
+
type: "ai-event",
|
|
1535
|
+
event: {
|
|
1536
|
+
type: "error",
|
|
1537
|
+
content: `${requestProvider} CLI is not installed. Run: npm install -g @google/gemini-cli`,
|
|
1538
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1539
|
+
provider: requestProvider
|
|
1540
|
+
}
|
|
1541
|
+
});
|
|
1542
|
+
return;
|
|
1543
|
+
}
|
|
941
1544
|
const config = {
|
|
942
|
-
provider:
|
|
1545
|
+
provider: cliProvider,
|
|
943
1546
|
cwd: workingDirectory,
|
|
944
1547
|
model: msg.model
|
|
945
1548
|
};
|
|
@@ -957,7 +1560,8 @@ ${visionDescription}`,
|
|
|
957
1560
|
type: "generate-complete",
|
|
958
1561
|
success: true,
|
|
959
1562
|
annotationId,
|
|
960
|
-
provider:
|
|
1563
|
+
provider: requestProvider,
|
|
1564
|
+
mode: requestMode
|
|
961
1565
|
});
|
|
962
1566
|
break;
|
|
963
1567
|
}
|
|
@@ -975,6 +1579,109 @@ ${visionDescription}`,
|
|
|
975
1579
|
return { id: msg.id, type: "revert-result", ...result };
|
|
976
1580
|
},
|
|
977
1581
|
// -------------------------------------------------------------------------
|
|
1582
|
+
// MCP Annotation Queue Management
|
|
1583
|
+
// -------------------------------------------------------------------------
|
|
1584
|
+
"get-pending-annotations": async (msg) => {
|
|
1585
|
+
const pending = getPendingAnnotations();
|
|
1586
|
+
return {
|
|
1587
|
+
id: msg.id,
|
|
1588
|
+
type: "pending-annotations",
|
|
1589
|
+
count: pending.length,
|
|
1590
|
+
annotations: pending.map(serializeStoredAnnotation)
|
|
1591
|
+
};
|
|
1592
|
+
},
|
|
1593
|
+
"get-all-annotations": async (msg) => {
|
|
1594
|
+
const all = getAllAnnotations();
|
|
1595
|
+
return {
|
|
1596
|
+
id: msg.id,
|
|
1597
|
+
type: "all-annotations",
|
|
1598
|
+
count: all.length,
|
|
1599
|
+
annotations: all.map(serializeStoredAnnotation)
|
|
1600
|
+
};
|
|
1601
|
+
},
|
|
1602
|
+
"get-annotation": async (msg) => {
|
|
1603
|
+
const id = msg.annotationId;
|
|
1604
|
+
const stored = getAnnotation(id);
|
|
1605
|
+
if (!stored) {
|
|
1606
|
+
return { id: msg.id, type: "error", error: `Annotation not found: ${id}` };
|
|
1607
|
+
}
|
|
1608
|
+
return {
|
|
1609
|
+
id: msg.id,
|
|
1610
|
+
type: "annotation",
|
|
1611
|
+
annotation: serializeStoredAnnotation(stored)
|
|
1612
|
+
};
|
|
1613
|
+
},
|
|
1614
|
+
"acknowledge-annotation": async (msg) => {
|
|
1615
|
+
const id = msg.annotationId;
|
|
1616
|
+
const stored = acknowledgeAnnotation(id);
|
|
1617
|
+
if (!stored) {
|
|
1618
|
+
return { id: msg.id, type: "error", error: `Annotation not found: ${id}` };
|
|
1619
|
+
}
|
|
1620
|
+
const counts = getAnnotationCounts();
|
|
1621
|
+
broadcastToClients({
|
|
1622
|
+
type: "annotation-status-changed",
|
|
1623
|
+
annotationId: id,
|
|
1624
|
+
status: "acknowledged"
|
|
1625
|
+
});
|
|
1626
|
+
broadcastToClients({ type: "mcp-annotation-counts", counts });
|
|
1627
|
+
return {
|
|
1628
|
+
id: msg.id,
|
|
1629
|
+
type: "annotation-acknowledged",
|
|
1630
|
+
annotationId: id
|
|
1631
|
+
};
|
|
1632
|
+
},
|
|
1633
|
+
"resolve-annotation": async (msg) => {
|
|
1634
|
+
const id = msg.annotationId;
|
|
1635
|
+
const summary = msg.summary;
|
|
1636
|
+
const stored = resolveAnnotation(id, summary);
|
|
1637
|
+
if (!stored) {
|
|
1638
|
+
return { id: msg.id, type: "error", error: `Annotation not found: ${id}` };
|
|
1639
|
+
}
|
|
1640
|
+
const counts = getAnnotationCounts();
|
|
1641
|
+
broadcastToClients({
|
|
1642
|
+
type: "annotation-status-changed",
|
|
1643
|
+
annotationId: id,
|
|
1644
|
+
status: "resolved",
|
|
1645
|
+
summary
|
|
1646
|
+
});
|
|
1647
|
+
broadcastToClients({ type: "mcp-annotation-counts", counts });
|
|
1648
|
+
return {
|
|
1649
|
+
id: msg.id,
|
|
1650
|
+
type: "annotation-resolved",
|
|
1651
|
+
annotationId: id,
|
|
1652
|
+
summary
|
|
1653
|
+
};
|
|
1654
|
+
},
|
|
1655
|
+
"dismiss-annotation": async (msg) => {
|
|
1656
|
+
const id = msg.annotationId;
|
|
1657
|
+
const reason = msg.reason;
|
|
1658
|
+
const stored = dismissAnnotation(id, reason);
|
|
1659
|
+
if (!stored) {
|
|
1660
|
+
return { id: msg.id, type: "error", error: `Annotation not found: ${id}` };
|
|
1661
|
+
}
|
|
1662
|
+
const counts = getAnnotationCounts();
|
|
1663
|
+
broadcastToClients({
|
|
1664
|
+
type: "annotation-status-changed",
|
|
1665
|
+
annotationId: id,
|
|
1666
|
+
status: "dismissed",
|
|
1667
|
+
reason
|
|
1668
|
+
});
|
|
1669
|
+
broadcastToClients({ type: "mcp-annotation-counts", counts });
|
|
1670
|
+
return {
|
|
1671
|
+
id: msg.id,
|
|
1672
|
+
type: "annotation-dismissed",
|
|
1673
|
+
annotationId: id,
|
|
1674
|
+
reason
|
|
1675
|
+
};
|
|
1676
|
+
},
|
|
1677
|
+
"clear-queued-annotations": async (msg) => {
|
|
1678
|
+
clearAnnotations();
|
|
1679
|
+
return {
|
|
1680
|
+
id: msg.id,
|
|
1681
|
+
type: "annotations-cleared"
|
|
1682
|
+
};
|
|
1683
|
+
},
|
|
1684
|
+
// -------------------------------------------------------------------------
|
|
978
1685
|
// File Operations
|
|
979
1686
|
// -------------------------------------------------------------------------
|
|
980
1687
|
"read-file": async (msg) => {
|
|
@@ -983,8 +1690,8 @@ ${visionDescription}`,
|
|
|
983
1690
|
try {
|
|
984
1691
|
const content = fs__namespace.readFileSync(absolutePath, "utf-8");
|
|
985
1692
|
return { id: msg.id, type: "file-content", path: filePath, content };
|
|
986
|
-
} catch (
|
|
987
|
-
return { id: msg.id, type: "error", error: `Failed to read file: ${
|
|
1693
|
+
} catch (error2) {
|
|
1694
|
+
return { id: msg.id, type: "error", error: `Failed to read file: ${error2}` };
|
|
988
1695
|
}
|
|
989
1696
|
},
|
|
990
1697
|
"write-file": async (msg) => {
|
|
@@ -996,8 +1703,8 @@ ${visionDescription}`,
|
|
|
996
1703
|
fs__namespace.writeFileSync(absolutePath, content, "utf-8");
|
|
997
1704
|
console.log(`[Daemon] Wrote file: ${filePath}`);
|
|
998
1705
|
return { id: msg.id, type: "write-success", path: filePath };
|
|
999
|
-
} catch (
|
|
1000
|
-
return { id: msg.id, type: "error", error: `Failed to write file: ${
|
|
1706
|
+
} catch (error2) {
|
|
1707
|
+
return { id: msg.id, type: "error", error: `Failed to write file: ${error2}` };
|
|
1001
1708
|
}
|
|
1002
1709
|
},
|
|
1003
1710
|
"list-files": async (msg) => {
|
|
@@ -1010,8 +1717,8 @@ ${visionDescription}`,
|
|
|
1010
1717
|
isDirectory: entry.isDirectory()
|
|
1011
1718
|
}));
|
|
1012
1719
|
return { id: msg.id, type: "file-list", path: dirPath, files };
|
|
1013
|
-
} catch (
|
|
1014
|
-
return { id: msg.id, type: "error", error: `Failed to list files: ${
|
|
1720
|
+
} catch (error2) {
|
|
1721
|
+
return { id: msg.id, type: "error", error: `Failed to list files: ${error2}` };
|
|
1015
1722
|
}
|
|
1016
1723
|
},
|
|
1017
1724
|
// -------------------------------------------------------------------------
|
|
@@ -1024,18 +1731,45 @@ ${visionDescription}`,
|
|
|
1024
1731
|
}
|
|
1025
1732
|
console.log(`[Daemon] Running command: ${command2}`);
|
|
1026
1733
|
return new Promise((resolve) => {
|
|
1027
|
-
child_process.exec(command2, { cwd: workingDirectory }, (
|
|
1734
|
+
child_process.exec(command2, { cwd: workingDirectory }, (error2, stdout, stderr) => {
|
|
1028
1735
|
resolve({
|
|
1029
1736
|
id: msg.id,
|
|
1030
1737
|
type: "command-result",
|
|
1031
1738
|
stdout,
|
|
1032
1739
|
stderr,
|
|
1033
|
-
exitCode:
|
|
1740
|
+
exitCode: error2 ? error2.code : 0
|
|
1034
1741
|
});
|
|
1035
1742
|
});
|
|
1036
1743
|
});
|
|
1037
1744
|
},
|
|
1038
1745
|
// -------------------------------------------------------------------------
|
|
1746
|
+
// MCP Server Identification
|
|
1747
|
+
// -------------------------------------------------------------------------
|
|
1748
|
+
identify: async (msg, ws) => {
|
|
1749
|
+
if (msg.client === "mcp-server") {
|
|
1750
|
+
mcpServerClient = ws;
|
|
1751
|
+
mcpClientName = null;
|
|
1752
|
+
console.log("[Daemon] MCP server identified and connected");
|
|
1753
|
+
broadcastToClients({
|
|
1754
|
+
type: "mcp-server-status",
|
|
1755
|
+
connected: true,
|
|
1756
|
+
clientName: null
|
|
1757
|
+
});
|
|
1758
|
+
return { id: msg.id, type: "identified", client: "mcp-server" };
|
|
1759
|
+
}
|
|
1760
|
+
return { id: msg.id, type: "identified", client: "unknown" };
|
|
1761
|
+
},
|
|
1762
|
+
"mcp-client-info": async (msg) => {
|
|
1763
|
+
mcpClientName = msg.clientName || null;
|
|
1764
|
+
console.log("[Daemon] MCP client identified:", mcpClientName, msg.clientVersion || "");
|
|
1765
|
+
broadcastToClients({
|
|
1766
|
+
type: "mcp-server-status",
|
|
1767
|
+
connected: true,
|
|
1768
|
+
clientName: mcpClientName
|
|
1769
|
+
});
|
|
1770
|
+
return { id: msg.id, type: "ok" };
|
|
1771
|
+
},
|
|
1772
|
+
// -------------------------------------------------------------------------
|
|
1039
1773
|
// Status
|
|
1040
1774
|
// -------------------------------------------------------------------------
|
|
1041
1775
|
ping: async (msg) => {
|
|
@@ -1043,23 +1777,70 @@ ${visionDescription}`,
|
|
|
1043
1777
|
id: msg.id,
|
|
1044
1778
|
type: "pong",
|
|
1045
1779
|
provider: currentProvider,
|
|
1780
|
+
mode: currentMode,
|
|
1046
1781
|
cwd: workingDirectory,
|
|
1047
|
-
availableProviders: getAvailableProviders()
|
|
1782
|
+
availableProviders: getAvailableProviders(),
|
|
1783
|
+
availableModes: ["direct-cli", "mcp"],
|
|
1784
|
+
mcpServerConnected: mcpServerClient?.readyState === WebSocket2.WebSocket.OPEN,
|
|
1785
|
+
mcpClientName
|
|
1048
1786
|
};
|
|
1049
1787
|
}
|
|
1050
1788
|
};
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1789
|
+
var connectedClients = /* @__PURE__ */ new Set();
|
|
1790
|
+
function broadcastToClients(message) {
|
|
1791
|
+
for (const client of connectedClients) {
|
|
1792
|
+
sendMessage(client, message);
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
function serializeStoredAnnotation(stored) {
|
|
1796
|
+
return {
|
|
1797
|
+
id: stored.annotation.id,
|
|
1798
|
+
type: stored.annotation.type,
|
|
1799
|
+
comment: stored.comment,
|
|
1800
|
+
status: stored.status,
|
|
1801
|
+
createdAt: stored.createdAt,
|
|
1802
|
+
updatedAt: stored.updatedAt,
|
|
1803
|
+
resolvedBy: stored.resolvedBy,
|
|
1804
|
+
resolutionSummary: stored.resolutionSummary,
|
|
1805
|
+
dismissalReason: stored.dismissalReason,
|
|
1806
|
+
// Include key annotation data the agent needs
|
|
1807
|
+
annotation: {
|
|
1808
|
+
type: stored.annotation.type,
|
|
1809
|
+
selector: stored.annotation.selector,
|
|
1810
|
+
tagName: stored.annotation.tagName,
|
|
1811
|
+
text: stored.annotation.text,
|
|
1812
|
+
elementPath: stored.annotation.elementPath,
|
|
1813
|
+
boundingBox: stored.annotation.boundingBox,
|
|
1814
|
+
drawingSvg: stored.annotation.drawingSvg,
|
|
1815
|
+
drawingImage: stored.annotation.drawingImage,
|
|
1816
|
+
extractedText: stored.annotation.extractedText,
|
|
1817
|
+
nearbyElements: stored.annotation.nearbyElements,
|
|
1818
|
+
viewport: stored.annotation.viewport,
|
|
1819
|
+
projectStyles: stored.annotation.projectStyles,
|
|
1820
|
+
isMultiSelect: stored.annotation.isMultiSelect,
|
|
1821
|
+
elements: stored.annotation.elements
|
|
1822
|
+
}
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
function sendMessage(ws, message) {
|
|
1826
|
+
if (ws.readyState === WebSocket2.WebSocket.OPEN) {
|
|
1827
|
+
ws.send(JSON.stringify(message));
|
|
1054
1828
|
}
|
|
1055
1829
|
}
|
|
1056
1830
|
function handleConnection(ws) {
|
|
1057
1831
|
console.log("[Daemon] Client connected");
|
|
1832
|
+
connectedClients.add(ws);
|
|
1058
1833
|
sendMessage(ws, {
|
|
1059
1834
|
type: "connected",
|
|
1060
1835
|
provider: currentProvider,
|
|
1836
|
+
mode: currentMode,
|
|
1061
1837
|
cwd: workingDirectory,
|
|
1062
|
-
availableProviders: getAvailableProviders()
|
|
1838
|
+
availableProviders: getAvailableProviders(),
|
|
1839
|
+
availableModes: ["direct-cli", "mcp"],
|
|
1840
|
+
pendingAnnotations: currentMode === "mcp" ? getPendingCount() : 0,
|
|
1841
|
+
providerStatus: getAllProviderStatuses(),
|
|
1842
|
+
mcpServerConnected: mcpServerClient?.readyState === WebSocket2.WebSocket.OPEN,
|
|
1843
|
+
mcpClientName
|
|
1063
1844
|
});
|
|
1064
1845
|
ws.on("message", async (data) => {
|
|
1065
1846
|
let msg;
|
|
@@ -1079,55 +1860,72 @@ function handleConnection(ws) {
|
|
|
1079
1860
|
if (response) {
|
|
1080
1861
|
sendMessage(ws, response);
|
|
1081
1862
|
}
|
|
1082
|
-
} catch (
|
|
1863
|
+
} catch (error2) {
|
|
1083
1864
|
sendMessage(ws, {
|
|
1084
1865
|
id: msg.id,
|
|
1085
1866
|
type: "error",
|
|
1086
|
-
error: `Handler error: ${
|
|
1867
|
+
error: `Handler error: ${error2}`
|
|
1087
1868
|
});
|
|
1088
1869
|
}
|
|
1089
1870
|
});
|
|
1090
1871
|
ws.on("close", () => {
|
|
1091
1872
|
console.log("[Daemon] Client disconnected");
|
|
1873
|
+
connectedClients.delete(ws);
|
|
1874
|
+
if (ws === mcpServerClient) {
|
|
1875
|
+
mcpServerClient = null;
|
|
1876
|
+
mcpClientName = null;
|
|
1877
|
+
console.log("[Daemon] MCP server disconnected");
|
|
1878
|
+
broadcastToClients({
|
|
1879
|
+
type: "mcp-server-status",
|
|
1880
|
+
connected: false,
|
|
1881
|
+
clientName: null
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1092
1884
|
});
|
|
1093
|
-
ws.on("error", (
|
|
1094
|
-
console.error("[Daemon] WebSocket error:",
|
|
1885
|
+
ws.on("error", (error2) => {
|
|
1886
|
+
console.error("[Daemon] WebSocket error:", error2);
|
|
1095
1887
|
});
|
|
1096
1888
|
}
|
|
1097
1889
|
function startDaemon(config = {}) {
|
|
1098
1890
|
const port = config.port ?? 9999;
|
|
1099
1891
|
workingDirectory = config.cwd ?? process.cwd();
|
|
1100
1892
|
currentProvider = config.defaultProvider ?? "gemini";
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
if (
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1893
|
+
currentMode = config.defaultMode ?? "direct-cli";
|
|
1894
|
+
if (currentMode === "direct-cli") {
|
|
1895
|
+
if (!isProviderAvailable(currentProvider)) {
|
|
1896
|
+
const available = getAvailableProviders();
|
|
1897
|
+
if (available.length > 0) {
|
|
1898
|
+
console.log(`[Daemon] ${currentProvider} CLI not found, falling back to ${available[0]}`);
|
|
1899
|
+
currentProvider = available[0];
|
|
1900
|
+
} else {
|
|
1901
|
+
console.warn("[Daemon] Warning: No CLI providers found. Install gemini or claude CLI.");
|
|
1902
|
+
}
|
|
1108
1903
|
}
|
|
1109
1904
|
}
|
|
1110
|
-
const wss = new
|
|
1905
|
+
const wss = new WebSocket2.WebSocketServer({ port });
|
|
1111
1906
|
wss.on("connection", handleConnection);
|
|
1112
1907
|
wss.on("listening", () => {
|
|
1908
|
+
const cliProviders = getAvailableProviders();
|
|
1113
1909
|
console.log("");
|
|
1114
1910
|
console.log(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
|
|
1115
1911
|
console.log(" \u2502 \u2502");
|
|
1116
|
-
console.log(` \u2502
|
|
1912
|
+
console.log(` \u2502 Skema Daemon running on ws://localhost:${port} \u2502`);
|
|
1117
1913
|
console.log(" \u2502 \u2502");
|
|
1914
|
+
console.log(` \u2502 Mode: ${currentMode.padEnd(39)}\u2502`);
|
|
1118
1915
|
console.log(` \u2502 Provider: ${currentProvider.padEnd(35)}\u2502`);
|
|
1119
1916
|
console.log(` \u2502 Directory: ${workingDirectory.slice(-33).padEnd(34)}\u2502`);
|
|
1917
|
+
console.log(` \u2502 CLI Providers: ${(cliProviders.join(", ") || "none").padEnd(29)}\u2502`);
|
|
1120
1918
|
console.log(" \u2502 \u2502");
|
|
1121
1919
|
console.log(" \u2502 Waiting for browser connections... \u2502");
|
|
1122
1920
|
console.log(" \u2502 \u2502");
|
|
1123
1921
|
console.log(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518");
|
|
1124
1922
|
console.log("");
|
|
1125
1923
|
});
|
|
1126
|
-
wss.on("error", (
|
|
1127
|
-
if (
|
|
1924
|
+
wss.on("error", (error2) => {
|
|
1925
|
+
if (error2.code === "EADDRINUSE") {
|
|
1128
1926
|
console.error(`[Daemon] Port ${port} is already in use. Is another Skema daemon running?`);
|
|
1129
1927
|
} else {
|
|
1130
|
-
console.error("[Daemon] Server error:",
|
|
1928
|
+
console.error("[Daemon] Server error:", error2);
|
|
1131
1929
|
}
|
|
1132
1930
|
});
|
|
1133
1931
|
return {
|
|
@@ -1148,17 +1946,26 @@ function printHelp() {
|
|
|
1148
1946
|
console.log("");
|
|
1149
1947
|
console.log(" Usage:");
|
|
1150
1948
|
console.log(" npx skema-core Start the daemon (default)");
|
|
1949
|
+
console.log(" npx skema-core --mcp Start as MCP server (for Cursor/Claude Desktop)");
|
|
1151
1950
|
console.log(" npx skema-core init Configure your project");
|
|
1152
1951
|
console.log(" npx skema-core help Show this help");
|
|
1153
1952
|
console.log("");
|
|
1154
1953
|
console.log(" Options (for daemon):");
|
|
1155
|
-
console.log(" -p, --port <port>
|
|
1156
|
-
console.log(" -d, --dir <path>
|
|
1157
|
-
console.log(" --provider <name>
|
|
1954
|
+
console.log(" -p, --port <port> Port number (default: 9999)");
|
|
1955
|
+
console.log(" -d, --dir <path> Working directory");
|
|
1956
|
+
console.log(" --provider <name> Default AI provider (gemini|claude)");
|
|
1957
|
+
console.log(" --mode <mode> Execution mode (direct-cli|mcp)");
|
|
1958
|
+
console.log(" --mcp Start as MCP server (stdio transport)");
|
|
1959
|
+
console.log("");
|
|
1960
|
+
console.log(" Execution Modes:");
|
|
1961
|
+
console.log(" direct-cli Use Gemini/Claude CLI agents (default, no API key needed)");
|
|
1962
|
+
console.log(" mcp Route through AI agent (Cursor, Claude Desktop)");
|
|
1158
1963
|
console.log("");
|
|
1159
1964
|
console.log(" Examples:");
|
|
1160
1965
|
console.log(" npx skema-core");
|
|
1161
1966
|
console.log(" npx skema-core --port 8080");
|
|
1967
|
+
console.log(" npx skema-core --provider claude");
|
|
1968
|
+
console.log(" npx skema-core --mcp");
|
|
1162
1969
|
console.log(" npx skema-core init");
|
|
1163
1970
|
console.log("");
|
|
1164
1971
|
console.log(' Note: After installing skema-core, you can also use "skema" directly.');
|
|
@@ -1178,6 +1985,11 @@ function parseArgs(args2) {
|
|
|
1178
1985
|
} else if (arg === "--provider") {
|
|
1179
1986
|
config.defaultProvider = next;
|
|
1180
1987
|
i++;
|
|
1988
|
+
} else if (arg === "--mode") {
|
|
1989
|
+
config.defaultMode = next;
|
|
1990
|
+
i++;
|
|
1991
|
+
} else if (arg === "--mcp") {
|
|
1992
|
+
config.mcp = true;
|
|
1181
1993
|
}
|
|
1182
1994
|
}
|
|
1183
1995
|
return config;
|
|
@@ -1185,9 +1997,25 @@ function parseArgs(args2) {
|
|
|
1185
1997
|
async function runInit() {
|
|
1186
1998
|
await Promise.resolve().then(() => (init_init(), init_exports));
|
|
1187
1999
|
}
|
|
2000
|
+
async function runMcpServer() {
|
|
2001
|
+
const { startMcpServer: startMcpServer2 } = await Promise.resolve().then(() => (init_server(), server_exports));
|
|
2002
|
+
await startMcpServer2();
|
|
2003
|
+
}
|
|
1188
2004
|
function runDaemon(args2) {
|
|
1189
2005
|
const config = parseArgs(args2);
|
|
1190
|
-
|
|
2006
|
+
if (config.mcp) {
|
|
2007
|
+
runMcpServer().catch((error2) => {
|
|
2008
|
+
console.error("[Skema] MCP server error:", error2);
|
|
2009
|
+
process.exit(1);
|
|
2010
|
+
});
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
startDaemon({
|
|
2014
|
+
port: config.port,
|
|
2015
|
+
cwd: config.cwd,
|
|
2016
|
+
defaultProvider: config.defaultProvider,
|
|
2017
|
+
defaultMode: config.defaultMode
|
|
2018
|
+
});
|
|
1191
2019
|
}
|
|
1192
2020
|
if (command === "help" || command === "-h" || command === "--help") {
|
|
1193
2021
|
printHelp();
|
|
@@ -1195,6 +2023,11 @@ if (command === "help" || command === "-h" || command === "--help") {
|
|
|
1195
2023
|
runInit();
|
|
1196
2024
|
} else if (command === "serve") {
|
|
1197
2025
|
runDaemon(args.slice(1));
|
|
2026
|
+
} else if (command === "mcp" || command === "--mcp") {
|
|
2027
|
+
runMcpServer().catch((error2) => {
|
|
2028
|
+
console.error("[Skema] MCP server error:", error2);
|
|
2029
|
+
process.exit(1);
|
|
2030
|
+
});
|
|
1198
2031
|
} else {
|
|
1199
2032
|
runDaemon(args);
|
|
1200
2033
|
}
|