@shawnowen/comet-mcp 2.4.1 → 2.4.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/README.md +12 -1
- package/dist/binding-reaper.d.ts +46 -0
- package/dist/binding-reaper.js +73 -0
- package/dist/http-server.js +121 -0
- package/dist/index.js +310 -6
- package/dist/project-config.d.ts +46 -0
- package/dist/project-config.js +166 -0
- package/dist/tab-groups.d.ts +21 -1
- package/dist/tab-groups.js +184 -0
- package/dist/window-bindings.d.ts +48 -0
- package/dist/window-bindings.js +85 -0
- package/extension/background.js +38 -17
- package/extension/manifest.json +16 -1
- package/extension/perplexity-capability-manifest.json +1181 -0
- package/extension/perplexity-capability-manifest.schema.json +142 -0
- package/extension/session-logic.js +696 -25
- package/extension/session-manager.html +13 -1
- package/extension/sidepanel.css +21 -6
- package/extension/sidepanel.js +598 -68
- package/package.json +1 -1
- package/dist/discovery/capability-entry.d.ts +0 -215
- package/dist/discovery/capability-entry.js +0 -13
- package/dist/discovery/description-template.d.ts +0 -40
- package/dist/discovery/description-template.js +0 -61
- package/dist/discovery/golden-queries.fixture.d.ts +0 -22
- package/dist/discovery/golden-queries.fixture.js +0 -137
- package/dist/discovery/mcp-source.d.ts +0 -38
- package/dist/discovery/mcp-source.js +0 -70
- package/dist/discovery/metadata-completeness.d.ts +0 -48
- package/dist/discovery/metadata-completeness.js +0 -83
- package/dist/discovery/registry.d.ts +0 -35
- package/dist/discovery/registry.js +0 -35
- package/dist/discovery/safety.d.ts +0 -44
- package/dist/discovery/safety.js +0 -59
- package/dist/discovery/schema-validator.d.ts +0 -36
- package/dist/discovery/schema-validator.js +0 -257
- package/dist/discovery/source-error.d.ts +0 -47
- package/dist/discovery/source-error.js +0 -95
- package/dist/discovery/tool-meta.d.ts +0 -41
- package/dist/discovery/tool-meta.js +0 -229
- package/dist/discovery/virtual-tools.d.ts +0 -20
- package/dist/discovery/virtual-tools.js +0 -69
- package/dist/task-thread-aggregator.d.ts +0 -34
- package/dist/task-thread-aggregator.js +0 -480
- package/dist/task-thread-canonical.d.ts +0 -142
- package/dist/task-thread-canonical.js +0 -116
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// Comet Browser MCP Server
|
|
3
3
|
// Claude Code ↔ Perplexity Comet bidirectional interaction
|
|
4
|
-
//
|
|
4
|
+
// 26 tools: 9 browsing + 2 direct interaction + 1 tab groups + 4 lifecycle + 2 orchestration + 6 parity + 2 safe observe (observe + peek)
|
|
5
5
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
7
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -68,6 +68,7 @@ const BROWSING_TOOLS = new Set([
|
|
|
68
68
|
"comet_pdf",
|
|
69
69
|
"comet_scrape",
|
|
70
70
|
"comet_network",
|
|
71
|
+
"comet_monitor",
|
|
71
72
|
"comet_automate",
|
|
72
73
|
"comet_domain",
|
|
73
74
|
]);
|
|
@@ -87,6 +88,7 @@ const BOUND_ROUTING_TOOLS = new Set([
|
|
|
87
88
|
"comet_pdf",
|
|
88
89
|
"comet_scrape",
|
|
89
90
|
"comet_network",
|
|
91
|
+
"comet_monitor",
|
|
90
92
|
"comet_automate",
|
|
91
93
|
"comet_domain",
|
|
92
94
|
]);
|
|
@@ -202,6 +204,8 @@ import { emitLifecycleEvent, createMCPLifecycleEnvelope } from "../vendor/lifecy
|
|
|
202
204
|
import { appendFileSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
203
205
|
import { homedir } from "os";
|
|
204
206
|
import { join, dirname } from "path";
|
|
207
|
+
import { createHash } from "crypto";
|
|
208
|
+
import { execFileSync } from "child_process";
|
|
205
209
|
// Duplicate process detection (Spec 016, FR-010, T005)
|
|
206
210
|
const PID_FILE_PATH = join(homedir(), ".claude", "comet-browser", "comet-mcp.pid");
|
|
207
211
|
function checkDuplicateProcess() {
|
|
@@ -438,7 +442,8 @@ const TOOLS = [
|
|
|
438
442
|
"Actions: list (all groups), list_tabs (all tabs with group info), create (new group from tab IDs), " +
|
|
439
443
|
"update (rename/recolor/collapse), move (reorder), ungroup (remove tabs from group), delete (ungroup all tabs in a group), " +
|
|
440
444
|
"save_group (persist tab URLs to archive), restore_group (reopen tabs from archive), archive_group (save + close tabs), " +
|
|
441
|
-
"list_archived (show all archived tab groups)
|
|
445
|
+
"list_archived (show all archived tab groups), list_metadata (show extension metadata), " +
|
|
446
|
+
"update_metadata (update extension-owned descriptions, labels, archived records, and tab rows).",
|
|
442
447
|
inputSchema: {
|
|
443
448
|
type: "object",
|
|
444
449
|
properties: {
|
|
@@ -456,6 +461,8 @@ const TOOLS = [
|
|
|
456
461
|
"restore_group",
|
|
457
462
|
"archive_group",
|
|
458
463
|
"list_archived",
|
|
464
|
+
"list_metadata",
|
|
465
|
+
"update_metadata",
|
|
459
466
|
],
|
|
460
467
|
description: "The tab group operation to perform",
|
|
461
468
|
},
|
|
@@ -470,7 +477,7 @@ const TOOLS = [
|
|
|
470
477
|
},
|
|
471
478
|
title: {
|
|
472
479
|
type: "string",
|
|
473
|
-
description: "Group title (for create, update)",
|
|
480
|
+
description: "Group or metadata title (for create, update, archive update_metadata)",
|
|
474
481
|
},
|
|
475
482
|
color: {
|
|
476
483
|
type: "string",
|
|
@@ -487,12 +494,52 @@ const TOOLS = [
|
|
|
487
494
|
},
|
|
488
495
|
taskThreadId: {
|
|
489
496
|
type: "string",
|
|
490
|
-
description: "Task thread ID (for save_group, restore_group, archive_group)",
|
|
497
|
+
description: "Task thread ID (for save_group, restore_group, archive_group, archive and archive-tab update_metadata)",
|
|
491
498
|
},
|
|
492
499
|
closeTabs: {
|
|
493
500
|
type: "boolean",
|
|
494
501
|
description: "Close tabs after saving (for save_group; archive_group always closes)",
|
|
495
502
|
},
|
|
503
|
+
entityType: {
|
|
504
|
+
type: "string",
|
|
505
|
+
description: "Extension metadata entity type (for update_metadata)",
|
|
506
|
+
},
|
|
507
|
+
entityId: {
|
|
508
|
+
type: "string",
|
|
509
|
+
description: "Extension metadata entity ID (for update_metadata)",
|
|
510
|
+
},
|
|
511
|
+
sourceFamily: {
|
|
512
|
+
type: "string",
|
|
513
|
+
description: "Extension metadata source family (for update_metadata)",
|
|
514
|
+
},
|
|
515
|
+
windowId: {
|
|
516
|
+
type: "string",
|
|
517
|
+
description: "Window ID for window-label metadata updates",
|
|
518
|
+
},
|
|
519
|
+
activeWindowId: {
|
|
520
|
+
type: "string",
|
|
521
|
+
description: "Active window ID to include in update_metadata audit output",
|
|
522
|
+
},
|
|
523
|
+
policyTier: {
|
|
524
|
+
type: "string",
|
|
525
|
+
description: "Policy tier to include in update_metadata audit output",
|
|
526
|
+
},
|
|
527
|
+
description: {
|
|
528
|
+
type: "string",
|
|
529
|
+
description: "Canonical description to store (for update_metadata)",
|
|
530
|
+
},
|
|
531
|
+
label: {
|
|
532
|
+
type: "string",
|
|
533
|
+
description: "Window label to store (for update_metadata)",
|
|
534
|
+
},
|
|
535
|
+
status: {
|
|
536
|
+
type: "string",
|
|
537
|
+
description: "Archived task-thread status to store (for update_metadata)",
|
|
538
|
+
},
|
|
539
|
+
tabIndex: {
|
|
540
|
+
type: "number",
|
|
541
|
+
description: "Archived tab row index (for archive-tab update_metadata)",
|
|
542
|
+
},
|
|
496
543
|
},
|
|
497
544
|
required: ["action"],
|
|
498
545
|
},
|
|
@@ -953,6 +1000,53 @@ const TOOLS = [
|
|
|
953
1000
|
required: ["action"],
|
|
954
1001
|
},
|
|
955
1002
|
},
|
|
1003
|
+
{
|
|
1004
|
+
name: "comet_monitor",
|
|
1005
|
+
description: "Monitor the current page or a URL for content changes from the caller-owned Comet binding. " +
|
|
1006
|
+
"Stores a SHA-256 baseline by monitor ID, compares later checks, reports line-level diffs, " +
|
|
1007
|
+
"and can capture a screenshot or send a macOS notification on change. Bounded by count/interval.",
|
|
1008
|
+
inputSchema: {
|
|
1009
|
+
type: "object",
|
|
1010
|
+
properties: {
|
|
1011
|
+
url: {
|
|
1012
|
+
type: "string",
|
|
1013
|
+
description: "URL to navigate to before monitoring. If omitted, monitors the current page.",
|
|
1014
|
+
},
|
|
1015
|
+
selector: {
|
|
1016
|
+
type: "string",
|
|
1017
|
+
description: "Optional CSS selector to monitor instead of the full body text.",
|
|
1018
|
+
},
|
|
1019
|
+
id: {
|
|
1020
|
+
type: "string",
|
|
1021
|
+
description: "Stable monitor ID. Defaults to a hash of URL/current page and selector.",
|
|
1022
|
+
},
|
|
1023
|
+
once: {
|
|
1024
|
+
type: "boolean",
|
|
1025
|
+
description: "Check once and compare against the stored baseline. Defaults to true unless count is provided.",
|
|
1026
|
+
},
|
|
1027
|
+
interval: {
|
|
1028
|
+
type: "number",
|
|
1029
|
+
description: "Seconds between checks in continuous mode. Default: 60, max: 300.",
|
|
1030
|
+
},
|
|
1031
|
+
count: {
|
|
1032
|
+
type: "number",
|
|
1033
|
+
description: "Number of checks in continuous mode. Default: 10, max: 20.",
|
|
1034
|
+
},
|
|
1035
|
+
screenshot: {
|
|
1036
|
+
type: "boolean",
|
|
1037
|
+
description: "Capture a PNG screenshot when a change is detected.",
|
|
1038
|
+
},
|
|
1039
|
+
notify: {
|
|
1040
|
+
type: "boolean",
|
|
1041
|
+
description: "Send a macOS notification when a change is detected.",
|
|
1042
|
+
},
|
|
1043
|
+
maxLength: {
|
|
1044
|
+
type: "number",
|
|
1045
|
+
description: "Maximum captured content characters. Default: 20000.",
|
|
1046
|
+
},
|
|
1047
|
+
},
|
|
1048
|
+
},
|
|
1049
|
+
},
|
|
956
1050
|
{
|
|
957
1051
|
name: "comet_automate",
|
|
958
1052
|
description: "Execute a multi-step browser workflow. Each step is a tool+args object. Supports: " +
|
|
@@ -1076,11 +1170,21 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1076
1170
|
isError: true,
|
|
1077
1171
|
};
|
|
1078
1172
|
}
|
|
1173
|
+
// Spec 087 / FR-006, FR-007, FR-009 — load per-project config and
|
|
1174
|
+
// apply tab_group_prefix if set. Existing groups untouched (clarify.md C-2).
|
|
1175
|
+
const { readProjectConfig } = await import("./project-config.js");
|
|
1176
|
+
const { config: projectConfig, warnings: projectConfigWarnings } = readProjectConfig();
|
|
1177
|
+
let taskThreadIdIn = args?.taskThreadId || undefined;
|
|
1178
|
+
if (projectConfig.tab_group_prefix && taskThreadIdIn) {
|
|
1179
|
+
if (!taskThreadIdIn.startsWith(projectConfig.tab_group_prefix)) {
|
|
1180
|
+
taskThreadIdIn = projectConfig.tab_group_prefix + taskThreadIdIn;
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1079
1183
|
// Spec 034: Use SessionRegistry for isolated, safe connections.
|
|
1080
1184
|
// NEVER closes existing tabs. NEVER kills the browser.
|
|
1081
1185
|
const session = await sessionRegistry.register({
|
|
1082
1186
|
agentId: args?.agentId || undefined,
|
|
1083
|
-
taskThreadId:
|
|
1187
|
+
taskThreadId: taskThreadIdIn,
|
|
1084
1188
|
url: args?.url || undefined,
|
|
1085
1189
|
tabGroupColor: args?.tabGroupColor || undefined,
|
|
1086
1190
|
port: 9222,
|
|
@@ -2349,12 +2453,47 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2349
2453
|
],
|
|
2350
2454
|
};
|
|
2351
2455
|
}
|
|
2456
|
+
case "list_metadata": {
|
|
2457
|
+
const metadata = await tabGroupsClient.listExtensionMetadata();
|
|
2458
|
+
return {
|
|
2459
|
+
content: [
|
|
2460
|
+
{
|
|
2461
|
+
type: "text",
|
|
2462
|
+
text: `Extension metadata:\n${JSON.stringify(metadata, null, 2)}`,
|
|
2463
|
+
},
|
|
2464
|
+
],
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
case "update_metadata": {
|
|
2468
|
+
const result = await tabGroupsClient.updateExtensionMetadata({
|
|
2469
|
+
entityType: args?.entityType,
|
|
2470
|
+
entityId: args?.entityId,
|
|
2471
|
+
windowId: args?.windowId,
|
|
2472
|
+
activeWindowId: args?.activeWindowId,
|
|
2473
|
+
taskThreadId: args?.taskThreadId,
|
|
2474
|
+
tabIndex: args?.tabIndex,
|
|
2475
|
+
sourceFamily: args?.sourceFamily,
|
|
2476
|
+
policyTier: args?.policyTier,
|
|
2477
|
+
description: args?.description,
|
|
2478
|
+
title: args?.title,
|
|
2479
|
+
label: args?.label,
|
|
2480
|
+
status: args?.status,
|
|
2481
|
+
});
|
|
2482
|
+
return {
|
|
2483
|
+
content: [
|
|
2484
|
+
{
|
|
2485
|
+
type: "text",
|
|
2486
|
+
text: `Updated extension metadata:\n${JSON.stringify(result, null, 2)}`,
|
|
2487
|
+
},
|
|
2488
|
+
],
|
|
2489
|
+
};
|
|
2490
|
+
}
|
|
2352
2491
|
default:
|
|
2353
2492
|
return {
|
|
2354
2493
|
content: [
|
|
2355
2494
|
{
|
|
2356
2495
|
type: "text",
|
|
2357
|
-
text: `Unknown action: ${action}. Use: list, list_tabs, create, update, move, ungroup, delete, save_group, restore_group, archive_group, list_archived`,
|
|
2496
|
+
text: `Unknown action: ${action}. Use: list, list_tabs, create, update, move, ungroup, delete, save_group, restore_group, archive_group, list_archived, list_metadata, update_metadata`,
|
|
2358
2497
|
},
|
|
2359
2498
|
],
|
|
2360
2499
|
isError: true,
|
|
@@ -3259,6 +3398,171 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
3259
3398
|
],
|
|
3260
3399
|
};
|
|
3261
3400
|
}
|
|
3401
|
+
case "comet_monitor": {
|
|
3402
|
+
const monitorUrl = args?.url;
|
|
3403
|
+
const monitorSelector = args?.selector;
|
|
3404
|
+
const monitorIdArg = args?.id;
|
|
3405
|
+
const monitorOnce = args?.once !== false && args?.count === undefined;
|
|
3406
|
+
const monitorIntervalSec = Math.max(1, Math.min(args?.interval || 60, 300));
|
|
3407
|
+
const monitorCount = monitorOnce
|
|
3408
|
+
? 1
|
|
3409
|
+
: Math.max(1, Math.min(args?.count || 10, 20));
|
|
3410
|
+
const monitorScreenshot = args?.screenshot || false;
|
|
3411
|
+
const monitorNotify = args?.notify || false;
|
|
3412
|
+
const monitorMaxLength = Math.max(1000, Math.min(args?.maxLength || 20000, 100000));
|
|
3413
|
+
if (monitorUrl) {
|
|
3414
|
+
await cometClient.navigate(monitorUrl, true, true);
|
|
3415
|
+
}
|
|
3416
|
+
const outputRoot = join(homedir(), ".Codex", "comet-browser", "output");
|
|
3417
|
+
const scrapedDir = join(outputRoot, "scraped");
|
|
3418
|
+
const screenshotDir = join(outputRoot, "screenshots");
|
|
3419
|
+
mkdirSync(scrapedDir, { recursive: true });
|
|
3420
|
+
mkdirSync(screenshotDir, { recursive: true });
|
|
3421
|
+
async function captureMonitorState() {
|
|
3422
|
+
const result = await cometClient.evaluate(`
|
|
3423
|
+
(() => {
|
|
3424
|
+
const selector = ${monitorSelector ? safeSelector(monitorSelector) : "null"};
|
|
3425
|
+
const node = selector ? document.querySelector(selector) : document.body;
|
|
3426
|
+
const content = node ? ((node.innerText || node.textContent || '').trim()) : '';
|
|
3427
|
+
return {
|
|
3428
|
+
title: document.title,
|
|
3429
|
+
url: window.location.href,
|
|
3430
|
+
selector,
|
|
3431
|
+
content: content.slice(0, ${monitorMaxLength})
|
|
3432
|
+
};
|
|
3433
|
+
})()
|
|
3434
|
+
`);
|
|
3435
|
+
const value = (result.result.value || {});
|
|
3436
|
+
const content = value.content || "";
|
|
3437
|
+
return {
|
|
3438
|
+
title: value.title || "",
|
|
3439
|
+
url: value.url || monitorUrl || "",
|
|
3440
|
+
selector: value.selector || monitorSelector || null,
|
|
3441
|
+
content,
|
|
3442
|
+
hash: createHash("sha256").update(content).digest("hex"),
|
|
3443
|
+
timestamp: new Date().toISOString(),
|
|
3444
|
+
};
|
|
3445
|
+
}
|
|
3446
|
+
const firstState = await captureMonitorState();
|
|
3447
|
+
const monitorId = monitorIdArg ||
|
|
3448
|
+
createHash("sha256")
|
|
3449
|
+
.update(`${firstState.url}:${firstState.selector || ""}`)
|
|
3450
|
+
.digest("hex")
|
|
3451
|
+
.slice(0, 12);
|
|
3452
|
+
const stateFile = join(scrapedDir, `monitor-${monitorId}-state.json`);
|
|
3453
|
+
const previousState = readJsonSafe(stateFile);
|
|
3454
|
+
function diffLines(previousContent, currentContent) {
|
|
3455
|
+
const previousLines = previousContent.split("\n").filter(Boolean);
|
|
3456
|
+
const currentLines = currentContent.split("\n").filter(Boolean);
|
|
3457
|
+
return {
|
|
3458
|
+
added: currentLines.filter((line) => !previousLines.includes(line)).slice(0, 20),
|
|
3459
|
+
removed: previousLines.filter((line) => !currentLines.includes(line)).slice(0, 20),
|
|
3460
|
+
};
|
|
3461
|
+
}
|
|
3462
|
+
async function saveScreenshotOnChange(changeIndex) {
|
|
3463
|
+
if (!monitorScreenshot)
|
|
3464
|
+
return null;
|
|
3465
|
+
const shot = await cometClient.screenshot("png");
|
|
3466
|
+
const shotPath = join(screenshotDir, `monitor-${monitorId}-change-${changeIndex}.png`);
|
|
3467
|
+
writeFileSync(shotPath, Buffer.from(shot.data, "base64"));
|
|
3468
|
+
return shotPath;
|
|
3469
|
+
}
|
|
3470
|
+
function notifyOnChange(message) {
|
|
3471
|
+
if (!monitorNotify)
|
|
3472
|
+
return null;
|
|
3473
|
+
try {
|
|
3474
|
+
execFileSync("osascript", [
|
|
3475
|
+
"-e",
|
|
3476
|
+
`display notification ${JSON.stringify(message)} with title "Comet Monitor"`,
|
|
3477
|
+
]);
|
|
3478
|
+
return "sent";
|
|
3479
|
+
}
|
|
3480
|
+
catch (err) {
|
|
3481
|
+
return `failed: ${err instanceof Error ? err.message : String(err)}`;
|
|
3482
|
+
}
|
|
3483
|
+
}
|
|
3484
|
+
if (monitorOnce) {
|
|
3485
|
+
const changed = Boolean(previousState && previousState.hash !== firstState.hash);
|
|
3486
|
+
const diff = changed && previousState?.content
|
|
3487
|
+
? diffLines(previousState.content, firstState.content)
|
|
3488
|
+
: { added: [], removed: [] };
|
|
3489
|
+
const screenshotPath = changed ? await saveScreenshotOnChange(1) : null;
|
|
3490
|
+
const notification = changed ? notifyOnChange(`Page changed: ${firstState.url}`) : null;
|
|
3491
|
+
writeFileSync(stateFile, JSON.stringify(firstState, null, 2));
|
|
3492
|
+
return {
|
|
3493
|
+
content: [
|
|
3494
|
+
{
|
|
3495
|
+
type: "text",
|
|
3496
|
+
text: JSON.stringify({
|
|
3497
|
+
monitorId,
|
|
3498
|
+
mode: "once",
|
|
3499
|
+
status: previousState ? (changed ? "changed" : "unchanged") : "baseline",
|
|
3500
|
+
url: firstState.url,
|
|
3501
|
+
selector: firstState.selector,
|
|
3502
|
+
timestamp: firstState.timestamp,
|
|
3503
|
+
hash: firstState.hash,
|
|
3504
|
+
previousHash: previousState?.hash || null,
|
|
3505
|
+
contentLength: firstState.content.length,
|
|
3506
|
+
stateFile,
|
|
3507
|
+
screenshotPath,
|
|
3508
|
+
notification,
|
|
3509
|
+
diff,
|
|
3510
|
+
}, null, 2),
|
|
3511
|
+
},
|
|
3512
|
+
],
|
|
3513
|
+
};
|
|
3514
|
+
}
|
|
3515
|
+
const changes = [];
|
|
3516
|
+
let previous = previousState || firstState;
|
|
3517
|
+
if (!previousState) {
|
|
3518
|
+
writeFileSync(stateFile, JSON.stringify(firstState, null, 2));
|
|
3519
|
+
}
|
|
3520
|
+
for (let index = 0; index < monitorCount; index++) {
|
|
3521
|
+
const current = index === 0 ? firstState : await captureMonitorState();
|
|
3522
|
+
const changed = previous.hash !== current.hash;
|
|
3523
|
+
if (changed) {
|
|
3524
|
+
const changeIndex = changes.length + 1;
|
|
3525
|
+
const screenshotPath = await saveScreenshotOnChange(changeIndex);
|
|
3526
|
+
const notification = notifyOnChange(`Page changed: ${current.url}`);
|
|
3527
|
+
changes.push({
|
|
3528
|
+
check: index + 1,
|
|
3529
|
+
timestamp: current.timestamp,
|
|
3530
|
+
hash: current.hash,
|
|
3531
|
+
previousHash: previous.hash,
|
|
3532
|
+
screenshotPath,
|
|
3533
|
+
notification,
|
|
3534
|
+
diff: diffLines(previous.content, current.content),
|
|
3535
|
+
});
|
|
3536
|
+
writeFileSync(stateFile, JSON.stringify(current, null, 2));
|
|
3537
|
+
}
|
|
3538
|
+
previous = current;
|
|
3539
|
+
if (index < monitorCount - 1) {
|
|
3540
|
+
await new Promise((resolve) => setTimeout(resolve, monitorIntervalSec * 1000));
|
|
3541
|
+
}
|
|
3542
|
+
}
|
|
3543
|
+
const logFile = join(scrapedDir, `monitor-${monitorId}-log-${Date.now()}.json`);
|
|
3544
|
+
const summary = {
|
|
3545
|
+
monitorId,
|
|
3546
|
+
mode: "continuous",
|
|
3547
|
+
url: firstState.url,
|
|
3548
|
+
selector: firstState.selector,
|
|
3549
|
+
checks: monitorCount,
|
|
3550
|
+
intervalSeconds: monitorIntervalSec,
|
|
3551
|
+
changesDetected: changes.length,
|
|
3552
|
+
stateFile,
|
|
3553
|
+
logFile,
|
|
3554
|
+
changes,
|
|
3555
|
+
};
|
|
3556
|
+
writeFileSync(logFile, JSON.stringify(summary, null, 2));
|
|
3557
|
+
return {
|
|
3558
|
+
content: [
|
|
3559
|
+
{
|
|
3560
|
+
type: "text",
|
|
3561
|
+
text: JSON.stringify(summary, null, 2),
|
|
3562
|
+
},
|
|
3563
|
+
],
|
|
3564
|
+
};
|
|
3565
|
+
}
|
|
3262
3566
|
case "comet_automate": {
|
|
3263
3567
|
const autoSteps = args?.steps;
|
|
3264
3568
|
const autoVerbose = args?.verbose || false;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export interface ProjectConfig {
|
|
2
|
+
/** Owning organization (HH, SVF, BR, Personal, Equa, ...). Used by skills for confirmation prompts. */
|
|
3
|
+
organization?: string;
|
|
4
|
+
/** Comet profile name. Defaults to "oe" per constitution Article I. */
|
|
5
|
+
default_profile?: string;
|
|
6
|
+
/** Whitelist of entity-specific skill names to surface in this project. */
|
|
7
|
+
entity_skills?: string[];
|
|
8
|
+
/** Enabled domain playbooks for comet_domain. */
|
|
9
|
+
domain_playbooks?: ("qbo" | "mercury" | "salt" | "github" | "google")[];
|
|
10
|
+
/** Prepended to new tab-group titles created by comet_connect. Existing groups untouched. */
|
|
11
|
+
tab_group_prefix?: string;
|
|
12
|
+
/** Drive root for this project's outputs. */
|
|
13
|
+
google_drive_root?: string;
|
|
14
|
+
/** Pin for @shawnowen/comet-mcp version. Empty / absent → latest. */
|
|
15
|
+
mcp_pin?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Walk up from `cwd` to either a directory containing `.comet/config.json`,
|
|
19
|
+
* the user's $HOME (boundary), or filesystem root. Returns the absolute path
|
|
20
|
+
* to the config file, or null.
|
|
21
|
+
*/
|
|
22
|
+
export declare function discoverProjectConfigPath(cwd?: string): string | null;
|
|
23
|
+
/** Clear the discovery cache. Test-only / on cwd change. */
|
|
24
|
+
export declare function clearProjectConfigCache(): void;
|
|
25
|
+
/**
|
|
26
|
+
* Validate a parsed config object. Returns the cleaned config + a list of
|
|
27
|
+
* warning strings for any keys that were ignored. Unknown keys are warned
|
|
28
|
+
* about but do not cause failure.
|
|
29
|
+
*/
|
|
30
|
+
export declare function validateProjectConfig(raw: unknown): {
|
|
31
|
+
config: ProjectConfig;
|
|
32
|
+
warnings: string[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Read and validate the project config. Returns an empty config + warnings if
|
|
36
|
+
* the file is absent, unreadable, or invalid JSON. NEVER throws.
|
|
37
|
+
*
|
|
38
|
+
* Per Spec 087 U-1 / clarify.md C-1 (revised): re-reads on every call.
|
|
39
|
+
* Discovery (walking up from cwd) IS cached by cwd; file contents are NOT.
|
|
40
|
+
*/
|
|
41
|
+
export declare function readProjectConfig(cwd?: string): {
|
|
42
|
+
config: ProjectConfig;
|
|
43
|
+
path: string | null;
|
|
44
|
+
warnings: string[];
|
|
45
|
+
};
|
|
46
|
+
//# sourceMappingURL=project-config.d.ts.map
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// Spec 087 / T040 / FR-006, FR-007, FR-008, FR-009
|
|
2
|
+
//
|
|
3
|
+
// Per-project configuration loader. Reads <project_root>/.comet/config.json
|
|
4
|
+
// when present, validates against a small schema, and re-reads on each access
|
|
5
|
+
// (mirrors bridge-config.ts pattern — no per-session caching of file contents).
|
|
6
|
+
//
|
|
7
|
+
// Discovery: walks upward from cwd to repo root or $HOME, looking for .comet/config.json.
|
|
8
|
+
// Caches only the discovered PATH for the session, not the file contents.
|
|
9
|
+
//
|
|
10
|
+
// All fields optional. Backwards-compatible: absent config = pre-Spec 087 behavior.
|
|
11
|
+
import { readFileSync, existsSync, statSync } from "fs";
|
|
12
|
+
import { homedir } from "os";
|
|
13
|
+
import { join, resolve, sep } from "path";
|
|
14
|
+
const VALID_PLAYBOOKS = new Set(["qbo", "mercury", "salt", "github", "google"]);
|
|
15
|
+
let discoveryCache = null;
|
|
16
|
+
/**
|
|
17
|
+
* Walk up from `cwd` to either a directory containing `.comet/config.json`,
|
|
18
|
+
* the user's $HOME (boundary), or filesystem root. Returns the absolute path
|
|
19
|
+
* to the config file, or null.
|
|
20
|
+
*/
|
|
21
|
+
export function discoverProjectConfigPath(cwd = process.cwd()) {
|
|
22
|
+
// Cache discovery by cwd — file contents are NOT cached.
|
|
23
|
+
if (discoveryCache && discoveryCache.cwd === cwd) {
|
|
24
|
+
return discoveryCache.path;
|
|
25
|
+
}
|
|
26
|
+
const home = homedir();
|
|
27
|
+
let dir = resolve(cwd);
|
|
28
|
+
let result = null;
|
|
29
|
+
while (true) {
|
|
30
|
+
const candidate = join(dir, ".comet", "config.json");
|
|
31
|
+
if (existsSync(candidate)) {
|
|
32
|
+
try {
|
|
33
|
+
if (statSync(candidate).isFile()) {
|
|
34
|
+
result = candidate;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// unreadable: keep walking
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (dir === home || dir === sep || dir === resolve(dir, ".."))
|
|
43
|
+
break;
|
|
44
|
+
dir = resolve(dir, "..");
|
|
45
|
+
}
|
|
46
|
+
discoveryCache = { cwd, path: result };
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
/** Clear the discovery cache. Test-only / on cwd change. */
|
|
50
|
+
export function clearProjectConfigCache() {
|
|
51
|
+
discoveryCache = null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Validate a parsed config object. Returns the cleaned config + a list of
|
|
55
|
+
* warning strings for any keys that were ignored. Unknown keys are warned
|
|
56
|
+
* about but do not cause failure.
|
|
57
|
+
*/
|
|
58
|
+
export function validateProjectConfig(raw) {
|
|
59
|
+
const warnings = [];
|
|
60
|
+
const config = {};
|
|
61
|
+
if (raw == null || typeof raw !== "object") {
|
|
62
|
+
warnings.push("project-config: root must be an object — falling back to defaults");
|
|
63
|
+
return { config, warnings };
|
|
64
|
+
}
|
|
65
|
+
const obj = raw;
|
|
66
|
+
const known = new Set([
|
|
67
|
+
"organization",
|
|
68
|
+
"default_profile",
|
|
69
|
+
"entity_skills",
|
|
70
|
+
"domain_playbooks",
|
|
71
|
+
"tab_group_prefix",
|
|
72
|
+
"google_drive_root",
|
|
73
|
+
"mcp_pin",
|
|
74
|
+
]);
|
|
75
|
+
for (const key of Object.keys(obj)) {
|
|
76
|
+
if (!known.has(key)) {
|
|
77
|
+
warnings.push(`project-config: unknown key '${key}' ignored`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (typeof obj.organization === "string" && obj.organization.length <= 32) {
|
|
81
|
+
config.organization = obj.organization;
|
|
82
|
+
}
|
|
83
|
+
else if (obj.organization !== undefined) {
|
|
84
|
+
warnings.push("project-config: 'organization' must be a string ≤ 32 chars — ignored");
|
|
85
|
+
}
|
|
86
|
+
if (typeof obj.default_profile === "string" && /^[a-z0-9_-]{1,16}$/.test(obj.default_profile)) {
|
|
87
|
+
config.default_profile = obj.default_profile;
|
|
88
|
+
}
|
|
89
|
+
else if (obj.default_profile !== undefined) {
|
|
90
|
+
warnings.push("project-config: 'default_profile' must match /^[a-z0-9_-]{1,16}$/ — falling back to 'oe'");
|
|
91
|
+
}
|
|
92
|
+
if (Array.isArray(obj.entity_skills) && obj.entity_skills.every((x) => typeof x === "string")) {
|
|
93
|
+
config.entity_skills = obj.entity_skills;
|
|
94
|
+
}
|
|
95
|
+
else if (obj.entity_skills !== undefined) {
|
|
96
|
+
warnings.push("project-config: 'entity_skills' must be string[] — ignored");
|
|
97
|
+
}
|
|
98
|
+
if (Array.isArray(obj.domain_playbooks)) {
|
|
99
|
+
const valid = [];
|
|
100
|
+
for (const item of obj.domain_playbooks) {
|
|
101
|
+
if (typeof item === "string" && VALID_PLAYBOOKS.has(item)) {
|
|
102
|
+
valid.push(item);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
warnings.push(`project-config: unknown domain_playbook '${item}' ignored`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (valid.length > 0)
|
|
109
|
+
config.domain_playbooks = valid;
|
|
110
|
+
}
|
|
111
|
+
else if (obj.domain_playbooks !== undefined) {
|
|
112
|
+
warnings.push("project-config: 'domain_playbooks' must be string[] — ignored");
|
|
113
|
+
}
|
|
114
|
+
if (typeof obj.tab_group_prefix === "string" &&
|
|
115
|
+
obj.tab_group_prefix.length <= 16 &&
|
|
116
|
+
!/[,:]/.test(obj.tab_group_prefix)) {
|
|
117
|
+
config.tab_group_prefix = obj.tab_group_prefix;
|
|
118
|
+
}
|
|
119
|
+
else if (obj.tab_group_prefix !== undefined) {
|
|
120
|
+
warnings.push("project-config: 'tab_group_prefix' must be ≤ 16 chars with no comma or colon — ignored");
|
|
121
|
+
}
|
|
122
|
+
if (typeof obj.google_drive_root === "string" &&
|
|
123
|
+
(obj.google_drive_root.startsWith("drive://") ||
|
|
124
|
+
obj.google_drive_root.startsWith("https://drive.google.com/"))) {
|
|
125
|
+
config.google_drive_root = obj.google_drive_root;
|
|
126
|
+
}
|
|
127
|
+
else if (obj.google_drive_root !== undefined) {
|
|
128
|
+
warnings.push("project-config: 'google_drive_root' must start with drive:// or https://drive.google.com/ — ignored");
|
|
129
|
+
}
|
|
130
|
+
if (typeof obj.mcp_pin === "string" && /^\d+\.\d+\.\d+(-[A-Za-z0-9.-]+)?$/.test(obj.mcp_pin)) {
|
|
131
|
+
config.mcp_pin = obj.mcp_pin;
|
|
132
|
+
}
|
|
133
|
+
else if (obj.mcp_pin !== undefined) {
|
|
134
|
+
warnings.push("project-config: 'mcp_pin' must be a semver string — ignored");
|
|
135
|
+
}
|
|
136
|
+
return { config, warnings };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Read and validate the project config. Returns an empty config + warnings if
|
|
140
|
+
* the file is absent, unreadable, or invalid JSON. NEVER throws.
|
|
141
|
+
*
|
|
142
|
+
* Per Spec 087 U-1 / clarify.md C-1 (revised): re-reads on every call.
|
|
143
|
+
* Discovery (walking up from cwd) IS cached by cwd; file contents are NOT.
|
|
144
|
+
*/
|
|
145
|
+
export function readProjectConfig(cwd = process.cwd()) {
|
|
146
|
+
const path = discoverProjectConfigPath(cwd);
|
|
147
|
+
if (!path) {
|
|
148
|
+
return { config: {}, path: null, warnings: [] };
|
|
149
|
+
}
|
|
150
|
+
let raw;
|
|
151
|
+
try {
|
|
152
|
+
raw = JSON.parse(readFileSync(path, "utf8"));
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
return {
|
|
156
|
+
config: {},
|
|
157
|
+
path,
|
|
158
|
+
warnings: [
|
|
159
|
+
`project-config: failed to parse ${path} — ${err.message} — falling back to defaults`,
|
|
160
|
+
],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const { config, warnings } = validateProjectConfig(raw);
|
|
164
|
+
return { config, path, warnings };
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=project-config.js.map
|
package/dist/tab-groups.d.ts
CHANGED
|
@@ -31,6 +31,22 @@ export interface UpdateGroupOptions {
|
|
|
31
31
|
color?: TabGroupColor;
|
|
32
32
|
collapsed?: boolean;
|
|
33
33
|
}
|
|
34
|
+
export interface ExtensionMetadataUpdateOptions {
|
|
35
|
+
entityType?: string;
|
|
36
|
+
entityId?: string | number;
|
|
37
|
+
windowId?: string | number;
|
|
38
|
+
activeWindowId?: string | number;
|
|
39
|
+
id?: string | number;
|
|
40
|
+
taskThreadId?: string;
|
|
41
|
+
tabIndex?: number;
|
|
42
|
+
sourceFamily?: string;
|
|
43
|
+
intent?: string;
|
|
44
|
+
policyTier?: string;
|
|
45
|
+
description?: string;
|
|
46
|
+
title?: string;
|
|
47
|
+
label?: string;
|
|
48
|
+
status?: string;
|
|
49
|
+
}
|
|
34
50
|
export interface TabGroupArchiveEntry {
|
|
35
51
|
taskThreadId: string;
|
|
36
52
|
title: string;
|
|
@@ -42,7 +58,7 @@ export interface TabGroupArchiveEntry {
|
|
|
42
58
|
}[];
|
|
43
59
|
archivedAt: string;
|
|
44
60
|
restoredAt?: string;
|
|
45
|
-
status: "saved" | "archived";
|
|
61
|
+
status: "pending" | "done" | "trashed" | "saved" | "archived";
|
|
46
62
|
}
|
|
47
63
|
export declare class TabGroupsClient {
|
|
48
64
|
private client;
|
|
@@ -83,6 +99,10 @@ export declare class TabGroupsClient {
|
|
|
83
99
|
ungroupTabs(tabIds: number[]): Promise<void>;
|
|
84
100
|
/** List all tabs with their groupId (−1 = ungrouped). */
|
|
85
101
|
listTabs(): Promise<TabInfo[]>;
|
|
102
|
+
/** List extension-owned descriptions, window labels, archives, and selected metadata. */
|
|
103
|
+
listExtensionMetadata(): Promise<Record<string, unknown>>;
|
|
104
|
+
/** Update extension-owned metadata without touching native tab state. */
|
|
105
|
+
updateExtensionMetadata(options: ExtensionMetadataUpdateOptions): Promise<Record<string, unknown>>;
|
|
86
106
|
/** Create a single tab and return its ID. Used by ETT restore_group. */
|
|
87
107
|
createTab(url: string, active?: boolean): Promise<number>;
|
|
88
108
|
/** Close multiple tabs by ID. Used by ETT archive_group. */
|