mcp-scraper 0.3.25 → 0.3.27
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/bin/api-server.cjs +2439 -346
- package/dist/bin/api-server.cjs.map +1 -1
- package/dist/bin/api-server.js +2 -2
- package/dist/bin/mcp-scraper-cli.cjs +1 -1
- package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
- package/dist/bin/mcp-scraper-cli.js +1 -1
- package/dist/bin/mcp-scraper-install.cjs +2 -2
- package/dist/bin/mcp-scraper-install.cjs.map +1 -1
- package/dist/bin/mcp-scraper-install.js +2 -2
- package/dist/bin/mcp-stdio-server.cjs +88 -33
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +10 -1357
- package/dist/bin/mcp-stdio-server.js.map +1 -1
- package/dist/bin/paa-harvest.cjs +1 -1
- package/dist/bin/paa-harvest.cjs.map +1 -1
- package/dist/bin/paa-harvest.js +1 -1
- package/dist/{chunk-FE2WC4JR.js → chunk-6KHGYJIT.js} +1431 -35
- package/dist/chunk-6KHGYJIT.js.map +1 -0
- package/dist/chunk-DYV72F6A.js +7 -0
- package/dist/chunk-DYV72F6A.js.map +1 -0
- package/dist/{chunk-WYCER2HW.js → chunk-H74L743B.js} +12 -11
- package/dist/chunk-H74L743B.js.map +1 -0
- package/dist/{chunk-G7PQ64LM.js → chunk-QSSH4RCX.js} +2 -2
- package/dist/chunk-QSSH4RCX.js.map +1 -0
- package/dist/{chunk-DOBQN3EY.js → chunk-T3VZD2I4.js} +2 -2
- package/dist/{chunk-DOBQN3EY.js.map → chunk-T3VZD2I4.js.map} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/{server-RRCKVBHG.js → server-KUNOK47B.js} +695 -29
- package/dist/server-KUNOK47B.js.map +1 -0
- package/dist/{worker-UC6D2756.js → worker-NV4GTIRG.js} +3 -3
- package/docs/spec-kernel-computer-controls.md +166 -0
- package/docs/spec-server-instructions.md +94 -0
- package/package.json +1 -1
- package/dist/chunk-FE2WC4JR.js.map +0 -1
- package/dist/chunk-G7PQ64LM.js.map +0 -1
- package/dist/chunk-WPHAMNT7.js +0 -7
- package/dist/chunk-WPHAMNT7.js.map +0 -1
- package/dist/chunk-WYCER2HW.js.map +0 -1
- package/dist/server-RRCKVBHG.js.map +0 -1
- /package/dist/{worker-UC6D2756.js.map → worker-NV4GTIRG.js.map} +0 -0
|
@@ -16,12 +16,13 @@ import {
|
|
|
16
16
|
harvestTimeoutBudget,
|
|
17
17
|
liveWebToolAnnotations,
|
|
18
18
|
outputBaseDir,
|
|
19
|
+
registerBrowserAgentMcpTools,
|
|
19
20
|
renderImageSection,
|
|
20
21
|
renderIssueReport,
|
|
21
22
|
renderLinkReport,
|
|
22
23
|
sanitizeAttempts,
|
|
23
24
|
sanitizeHarvestResult
|
|
24
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-6KHGYJIT.js";
|
|
25
26
|
import {
|
|
26
27
|
csvRecords,
|
|
27
28
|
listWorkflowDefinitions,
|
|
@@ -34,7 +35,7 @@ import {
|
|
|
34
35
|
workflowStepCount,
|
|
35
36
|
workflowSupportsSteps
|
|
36
37
|
} from "./chunk-NNEIXK5L.js";
|
|
37
|
-
import "./chunk-
|
|
38
|
+
import "./chunk-DYV72F6A.js";
|
|
38
39
|
import {
|
|
39
40
|
BROWSER_OPEN_MIN_BALANCE_MC,
|
|
40
41
|
CONCURRENCY_PRICE_ID,
|
|
@@ -54,7 +55,7 @@ import {
|
|
|
54
55
|
harvestProblemResponse,
|
|
55
56
|
insufficientBalanceResponse,
|
|
56
57
|
serializeHarvestProblem
|
|
57
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-H74L743B.js";
|
|
58
59
|
import {
|
|
59
60
|
BrowserDriver,
|
|
60
61
|
MapsSelectors,
|
|
@@ -67,7 +68,7 @@ import {
|
|
|
67
68
|
recordVendorUsage,
|
|
68
69
|
resolveKernelProxyId,
|
|
69
70
|
runWithCostContext
|
|
70
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-QSSH4RCX.js";
|
|
71
72
|
import {
|
|
72
73
|
CaptchaError,
|
|
73
74
|
RECAPTCHA_INSTRUCTIONS,
|
|
@@ -5261,7 +5262,7 @@ async function extractSite(opts) {
|
|
|
5261
5262
|
}
|
|
5262
5263
|
|
|
5263
5264
|
// src/api/server.ts
|
|
5264
|
-
import { Hono as
|
|
5265
|
+
import { Hono as Hono16 } from "hono";
|
|
5265
5266
|
import { serve as serveInngest } from "inngest/hono";
|
|
5266
5267
|
|
|
5267
5268
|
// src/inngest/client.ts
|
|
@@ -8048,16 +8049,6 @@ var CATALOG = {
|
|
|
8048
8049
|
isSpecial: true,
|
|
8049
8050
|
tagline: "Generate a rank-tracker blueprint",
|
|
8050
8051
|
params: [{ key: "domain", label: "Domain", type: "string", required: true }]
|
|
8051
|
-
},
|
|
8052
|
-
{
|
|
8053
|
-
key: "deep_research_workflow",
|
|
8054
|
-
label: "Deep Research",
|
|
8055
|
-
kind: "simple",
|
|
8056
|
-
endpoint: "/workflows/run",
|
|
8057
|
-
mcpName: "workflow_run",
|
|
8058
|
-
isSpecial: true,
|
|
8059
|
-
tagline: "Multi-source, fact-checked research report",
|
|
8060
|
-
params: [{ key: "question", label: "Question", type: "string", required: true }]
|
|
8061
8052
|
}
|
|
8062
8053
|
]
|
|
8063
8054
|
}
|
|
@@ -14537,8 +14528,10 @@ mcpApp.all("/", async (c) => {
|
|
|
14537
14528
|
sessionIdGenerator: void 0,
|
|
14538
14529
|
enableJsonResponse: true
|
|
14539
14530
|
});
|
|
14531
|
+
const consoleBaseUrl = process.env.BROWSER_AGENT_CONSOLE_URL?.trim() || baseUrl;
|
|
14540
14532
|
const server = buildPaaExtractorMcpServer(executor, { savesReportsLocally: false });
|
|
14541
14533
|
registerSerpIntelligenceCaptureTools(server, executor);
|
|
14534
|
+
registerBrowserAgentMcpTools(server, { baseUrl, apiKey: callerKey, consoleBaseUrl });
|
|
14542
14535
|
await server.connect(transport);
|
|
14543
14536
|
return transport.handleRequest(c.req.raw);
|
|
14544
14537
|
} catch {
|
|
@@ -16756,7 +16749,7 @@ function decryptMemoryKey(stored) {
|
|
|
16756
16749
|
}
|
|
16757
16750
|
async function memoryCall(toolName, args, userMemoryKey) {
|
|
16758
16751
|
try {
|
|
16759
|
-
const res = await fetch(`${MEMORY_BASE_URL()}/api/mcp/
|
|
16752
|
+
const res = await fetch(`${MEMORY_BASE_URL()}/api/mcp/memoryServer/tools/${toolName}/execute`, {
|
|
16760
16753
|
method: "POST",
|
|
16761
16754
|
headers: { "content-type": "application/json" },
|
|
16762
16755
|
body: JSON.stringify({ data: { ...args, apiKey: userMemoryKey } })
|
|
@@ -16920,7 +16913,7 @@ import { getCookie, setCookie } from "hono/cookie";
|
|
|
16920
16913
|
import { createHash as createHash3, randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
|
|
16921
16914
|
import { importPKCS8, exportJWK, SignJWT } from "jose";
|
|
16922
16915
|
var ISSUER = "https://mcpscraper.dev";
|
|
16923
|
-
var RESOURCE = () => process.env.MCP_MEMORY_RESOURCE_URL ?? "https://
|
|
16916
|
+
var RESOURCE = () => process.env.MCP_MEMORY_RESOURCE_URL ?? "https://memory.mcpscraper.dev/mcp";
|
|
16924
16917
|
var SCRAPER_RESOURCE = () => (process.env.MCP_SCRAPER_RESOURCE_URL ?? "https://mcpscraper.dev/mcp").replace(/\/$/, "");
|
|
16925
16918
|
var MEMORY_SCOPES = ["memory.read", "memory.write", "memory.admin"];
|
|
16926
16919
|
var SCRAPER_SCOPES = ["scraper.use"];
|
|
@@ -17325,6 +17318,677 @@ oauthApp.post("/oauth/token", async (c) => {
|
|
|
17325
17318
|
return tokenErrorResponse(c, "unsupported_grant_type", "grant_type must be authorization_code or refresh_token", 400);
|
|
17326
17319
|
});
|
|
17327
17320
|
|
|
17321
|
+
// src/api/chat-routes.ts
|
|
17322
|
+
import { Hono as Hono15 } from "hono";
|
|
17323
|
+
var chatApp = new Hono15();
|
|
17324
|
+
function tokenOk(token) {
|
|
17325
|
+
return !!token && token.startsWith("mk_") && token.length > 10;
|
|
17326
|
+
}
|
|
17327
|
+
function jsonError(message, status = 400) {
|
|
17328
|
+
return new Response(JSON.stringify({ ok: false, error: message }), { status, headers: { "content-type": "application/json" } });
|
|
17329
|
+
}
|
|
17330
|
+
chatApp.get("/api/channels", async (c) => {
|
|
17331
|
+
const token = c.req.query("token");
|
|
17332
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17333
|
+
const r = await memoryCall("listVaultsTool", {}, token);
|
|
17334
|
+
if (!r.ok) return c.json(r);
|
|
17335
|
+
const vaults = (r.vaults ?? []).filter((v) => v.kind === "channel");
|
|
17336
|
+
return c.json({ ok: true, channels: vaults });
|
|
17337
|
+
});
|
|
17338
|
+
chatApp.get("/api/messages", async (c) => {
|
|
17339
|
+
const token = c.req.query("token");
|
|
17340
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17341
|
+
const vault = c.req.query("vault");
|
|
17342
|
+
if (!vault) return jsonError("vault is required");
|
|
17343
|
+
const parentMessageId = c.req.query("parentMessageId") || void 0;
|
|
17344
|
+
const r = await memoryCall("listChannelMessagesTool", { vault, parentMessageId }, token);
|
|
17345
|
+
return c.json(r);
|
|
17346
|
+
});
|
|
17347
|
+
chatApp.get("/api/poll", async (c) => {
|
|
17348
|
+
const token = c.req.query("token");
|
|
17349
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17350
|
+
const vault = c.req.query("vault");
|
|
17351
|
+
if (!vault) return jsonError("vault is required");
|
|
17352
|
+
const r = await memoryCall("pollChannelTool", { vault }, token);
|
|
17353
|
+
return c.json(r);
|
|
17354
|
+
});
|
|
17355
|
+
function parseAttachNote(v) {
|
|
17356
|
+
if (!v || typeof v !== "object") return void 0;
|
|
17357
|
+
const a = v;
|
|
17358
|
+
return a.vault && a.path ? { vault: a.vault, path: a.path } : void 0;
|
|
17359
|
+
}
|
|
17360
|
+
chatApp.post("/api/post", async (c) => {
|
|
17361
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17362
|
+
if (!tokenOk(body.token)) return jsonError("missing or malformed token", 401);
|
|
17363
|
+
if (!body.vault || !body.content) return jsonError("vault and content are required");
|
|
17364
|
+
const attachNote = parseAttachNote(body.attachNote);
|
|
17365
|
+
const r = await memoryCall("postMessageTool", { vault: body.vault, content: body.content, ...attachNote ? { attachNote } : {} }, body.token);
|
|
17366
|
+
return c.json(r);
|
|
17367
|
+
});
|
|
17368
|
+
chatApp.post("/api/reply", async (c) => {
|
|
17369
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17370
|
+
if (!tokenOk(body.token)) return jsonError("missing or malformed token", 401);
|
|
17371
|
+
if (!body.vault || !body.parentMessageId || !body.content) return jsonError("vault, parentMessageId and content are required");
|
|
17372
|
+
const attachNote = parseAttachNote(body.attachNote);
|
|
17373
|
+
const r = await memoryCall("replyMessageTool", { vault: body.vault, parentMessageId: body.parentMessageId, content: body.content, ...attachNote ? { attachNote } : {} }, body.token);
|
|
17374
|
+
return c.json(r);
|
|
17375
|
+
});
|
|
17376
|
+
chatApp.get("/api/notes-own", async (c) => {
|
|
17377
|
+
const token = c.req.query("token");
|
|
17378
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17379
|
+
const vaultsRes = await memoryCall("listVaultsTool", {}, token);
|
|
17380
|
+
if (!vaultsRes.ok) return c.json(vaultsRes);
|
|
17381
|
+
const ownVaults = (vaultsRes.vaults ?? []).filter((v) => v.role === "owner" && v.kind === "notes");
|
|
17382
|
+
const lists = await Promise.all(ownVaults.map((v) => memoryCall("listTool", { vault: v.handle }, token)));
|
|
17383
|
+
const notes = ownVaults.flatMap((v, i) => {
|
|
17384
|
+
const r = lists[i];
|
|
17385
|
+
return r.ok ? (r.notes ?? []).map((n) => ({ vault: v.handle, path: n.path, title: n.title })) : [];
|
|
17386
|
+
});
|
|
17387
|
+
return c.json({ ok: true, notes });
|
|
17388
|
+
});
|
|
17389
|
+
chatApp.get("/api/message-note", async (c) => {
|
|
17390
|
+
const token = c.req.query("token");
|
|
17391
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17392
|
+
const vault = c.req.query("vault");
|
|
17393
|
+
const messageId = c.req.query("messageId");
|
|
17394
|
+
if (!vault || !messageId) return jsonError("vault and messageId are required");
|
|
17395
|
+
const r = await memoryCall("getMessageNoteTool", { vault, messageId }, token);
|
|
17396
|
+
return c.json(r);
|
|
17397
|
+
});
|
|
17398
|
+
chatApp.post("/api/react", async (c) => {
|
|
17399
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17400
|
+
if (!tokenOk(body.token)) return jsonError("missing or malformed token", 401);
|
|
17401
|
+
if (!body.vault || !body.messageId || !body.emoji) return jsonError("vault, messageId and emoji are required");
|
|
17402
|
+
const r = await memoryCall("reactMessageTool", { vault: body.vault, messageId: body.messageId, emoji: body.emoji, remove: !!body.remove }, body.token);
|
|
17403
|
+
return c.json(r);
|
|
17404
|
+
});
|
|
17405
|
+
chatApp.get("/api/mentions", async (c) => {
|
|
17406
|
+
const token = c.req.query("token");
|
|
17407
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17408
|
+
const r = await memoryCall("myMentionsTool", {}, token);
|
|
17409
|
+
return c.json(r);
|
|
17410
|
+
});
|
|
17411
|
+
chatApp.get("/api/members", async (c) => {
|
|
17412
|
+
const token = c.req.query("token");
|
|
17413
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17414
|
+
const vault = c.req.query("vault");
|
|
17415
|
+
if (!vault) return jsonError("vault is required");
|
|
17416
|
+
const r = await memoryCall("listChannelMembersTool", { vault }, token);
|
|
17417
|
+
return c.json(r);
|
|
17418
|
+
});
|
|
17419
|
+
chatApp.post("/api/channel", async (c) => {
|
|
17420
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17421
|
+
if (!tokenOk(body.token)) return jsonError("missing or malformed token", 401);
|
|
17422
|
+
if (!body.name) return jsonError("name is required");
|
|
17423
|
+
const inviteEmails = (body.inviteEmails ?? []).filter(Boolean).slice(0, 99);
|
|
17424
|
+
const r = await memoryCall(
|
|
17425
|
+
"createChannelTool",
|
|
17426
|
+
{ name: body.name, inviteMembers: inviteEmails.map((identity) => ({ identity })) },
|
|
17427
|
+
body.token
|
|
17428
|
+
);
|
|
17429
|
+
return c.json(r);
|
|
17430
|
+
});
|
|
17431
|
+
chatApp.post("/api/invite", async (c) => {
|
|
17432
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17433
|
+
if (!tokenOk(body.token)) return jsonError("missing or malformed token", 401);
|
|
17434
|
+
if (!body.vault || !body.email) return jsonError("vault and email are required");
|
|
17435
|
+
const scopeMap = {
|
|
17436
|
+
read: { read: true },
|
|
17437
|
+
write: { read: true, write: true },
|
|
17438
|
+
admin: { read: true, write: true, admin: true }
|
|
17439
|
+
};
|
|
17440
|
+
const scope = scopeMap[body.scope ?? "write"] ?? scopeMap.write;
|
|
17441
|
+
const r = await memoryCall("shareVaultTool", { vault: body.vault, granteeIdentity: body.email, scope }, body.token);
|
|
17442
|
+
return c.json(r);
|
|
17443
|
+
});
|
|
17444
|
+
chatApp.post("/api/member-remove", async (c) => {
|
|
17445
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17446
|
+
if (!tokenOk(body.token)) return jsonError("missing or malformed token", 401);
|
|
17447
|
+
if (!body.vault || !body.identity) return jsonError("vault and identity are required");
|
|
17448
|
+
const r = await memoryCall("removeChannelMemberTool", { vault: body.vault, identity: body.identity }, body.token);
|
|
17449
|
+
return c.json(r);
|
|
17450
|
+
});
|
|
17451
|
+
chatApp.get("/:token", (c) => {
|
|
17452
|
+
const token = c.req.param("token");
|
|
17453
|
+
if (!tokenOk(token)) {
|
|
17454
|
+
return c.html(`<!doctype html><html><body style="font-family:system-ui;padding:40px;color:#3D3548"><h2>This chat link isn't valid.</h2><p>Ask the AI to run get-chat-link again, or revoke-chat-link then get-chat-link if you think it was compromised.</p></body></html>`, 401);
|
|
17455
|
+
}
|
|
17456
|
+
return c.html(renderChatPage(token));
|
|
17457
|
+
});
|
|
17458
|
+
function renderChatPage(token) {
|
|
17459
|
+
return `<!doctype html>
|
|
17460
|
+
<html>
|
|
17461
|
+
<head>
|
|
17462
|
+
<meta charset="utf-8">
|
|
17463
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
17464
|
+
<title>Chat Everywhere</title>
|
|
17465
|
+
<style>
|
|
17466
|
+
:root { color-scheme: light }
|
|
17467
|
+
* { box-sizing: border-box }
|
|
17468
|
+
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; color: #1B1523; background: #EDEAF2; }
|
|
17469
|
+
[id="app"] { display: flex; height: 100vh; }
|
|
17470
|
+
[id="sidebar"] { width: 264px; flex-shrink: 0; display: flex; flex-direction: column; background: #2B0E36; color: #fff; }
|
|
17471
|
+
[id="sidebar"] .brand { display:flex; align-items:center; gap:8px; padding:18px 16px 14px; border-bottom:1px solid rgba(255,255,255,.08); font-weight:700; }
|
|
17472
|
+
[id="sidebar"] .section-label { font:700 11px system-ui; color:#B79FC4; letter-spacing:.05em; text-transform:uppercase; padding:14px 14px 4px; }
|
|
17473
|
+
[id="channel-list"], [id="dm-list"] { padding: 0 8px; }
|
|
17474
|
+
.chan-row { width:100%; display:flex; align-items:center; gap:7px; padding:7px 8px; border-radius:6px; border:none; background:transparent; color:#D9C7E0; cursor:pointer; text-align:left; font:500 13.5px system-ui; }
|
|
17475
|
+
.chan-row:hover, .chan-row.active { background: rgba(255,255,255,.08); color:#fff; }
|
|
17476
|
+
.badge { background:#FF6F91; color:#fff; font:700 10.5px system-ui; padding:1px 6px; border-radius:999px; margin-left:auto; }
|
|
17477
|
+
[id="mentions-btn"] { margin: 8px 14px; display:flex; align-items:center; gap:8px; background:rgba(255,255,255,.07); border:none; border-radius:7px; padding:8px 10px; color:#EFE7F4; cursor:pointer; font:600 13px system-ui; }
|
|
17478
|
+
[id="create-btn"] { margin: 4px 14px; background: transparent; border: 1px dashed rgba(255,255,255,.25); border-radius:7px; padding:6px 10px; color:#D9C7E0; cursor:pointer; font:600 12px system-ui; }
|
|
17479
|
+
[id="main"] { flex: 1; display:flex; flex-direction:column; background:#fff; min-width:0; }
|
|
17480
|
+
[id="chan-header"] { display:flex; align-items:center; justify-content:space-between; padding:14px 20px; border-bottom:1px solid #E6DEEA; }
|
|
17481
|
+
[id="chan-title"] { font:700 16px system-ui; }
|
|
17482
|
+
[id="manage-btn"] { background:transparent; border:1px solid #E6DEEA; border-radius:999px; padding:5px 12px; font:600 12px system-ui; color:#6B6275; cursor:pointer; }
|
|
17483
|
+
[id="messages"] { flex:1; overflow:auto; padding:10px 8px; }
|
|
17484
|
+
.msg { display:flex; gap:10px; padding:10px 16px; border-bottom:1px solid #F0ECF3; }
|
|
17485
|
+
.avatar { width:36px; height:36px; border-radius:8px; display:flex; align-items:center; justify-content:center; color:#fff; font:600 12px system-ui; flex-shrink:0; }
|
|
17486
|
+
.msg-body { flex:1; min-width:0; position:relative; }
|
|
17487
|
+
.msg { position:relative; }
|
|
17488
|
+
.msg-head { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
|
|
17489
|
+
.msg-author { font:600 14px system-ui; }
|
|
17490
|
+
.agent-pill { font:700 9px system-ui; letter-spacing:.05em; color:#fff; background:#0E8074; padding:2px 6px; border-radius:4px; }
|
|
17491
|
+
.msg-ts { font:400 12px system-ui; color:#A89BB5; }
|
|
17492
|
+
.msg-content { font-size:15px; line-height:1.5; color:#2B2433; white-space:pre-wrap; margin-top:2px; }
|
|
17493
|
+
.reactions { display:flex; gap:6px; margin-top:7px; flex-wrap:wrap; align-items:center; }
|
|
17494
|
+
.reaction { display:flex; align-items:center; gap:4px; padding:3px 8px; border-radius:999px; border:1px solid #E6DEEA; background:#F7F4FA; cursor:pointer; font:500 12px system-ui; color:#5A5165; }
|
|
17495
|
+
.reply-summary, .thread-link { margin-top:7px; display:inline-flex; align-items:center; gap:5px; border:none; background:transparent; cursor:pointer; font:600 12.5px system-ui; color:#6B3FA0; padding:0; }
|
|
17496
|
+
.seen { display:flex; align-items:center; gap:5px; margin-top:6px; font-size:11px; color:#A89BB5; }
|
|
17497
|
+
.msg-actions { position:absolute; top:-6px; right:8px; display:flex; gap:2px; background:#fff; border:1px solid #E6DEEA; border-radius:8px; padding:2px; box-shadow:0 2px 6px rgba(0,0,0,.05); opacity:0; pointer-events:none; transition:opacity .1s; }
|
|
17498
|
+
.msg:hover .msg-actions, .msg-actions.pinned-open { opacity:1; pointer-events:auto; }
|
|
17499
|
+
.icon-btn { display:flex; align-items:center; justify-content:center; width:26px; height:26px; border-radius:6px; border:none; background:transparent; color:#948C9C; cursor:pointer; font-size:14px; flex-shrink:0; }
|
|
17500
|
+
.icon-btn:hover { background:#F2EEF5; color:#6B3FA0; }
|
|
17501
|
+
.emoji-popover { position:absolute; top:32px; right:10px; background:#fff; border:1px solid #E6DEEA; border-radius:10px; box-shadow:0 8px 24px rgba(0,0,0,.12); padding:6px; display:none; gap:2px; z-index:40; }
|
|
17502
|
+
.emoji-popover.open { display:flex; }
|
|
17503
|
+
.emoji-opt { width:30px; height:30px; display:flex; align-items:center; justify-content:center; border:none; background:transparent; cursor:pointer; font-size:17px; border-radius:6px; }
|
|
17504
|
+
.emoji-opt:hover { background:#F2EEF5; }
|
|
17505
|
+
[id="composer-row"] { padding:12px 20px 16px; border-top:1px solid #E6DEEA; }
|
|
17506
|
+
[id="composer"] { width:100%; box-sizing:border-box; resize:none; height:46px; border:1px solid #E6DEEA; border-radius:9px; padding:11px 14px; font:14px system-ui; outline:none; }
|
|
17507
|
+
[id="composer"]:focus { border-color:#6B3FA0; }
|
|
17508
|
+
[id="send-btn"] { background:#6B3FA0; color:#fff; border:none; border-radius:7px; padding:8px 18px; font:600 13.5px system-ui; cursor:pointer; margin-top:8px; float:right; }
|
|
17509
|
+
[id="thread-panel"] { width:340px; flex-shrink:0; border-left:1px solid #E6DEEA; display:none; flex-direction:column; background:#FCFBFD; }
|
|
17510
|
+
[id="thread-panel"].open { display:flex; }
|
|
17511
|
+
[id="thread-header"] { display:flex; align-items:center; justify-content:space-between; padding:14px 16px; border-bottom:1px solid #E6DEEA; font:700 14.5px system-ui; }
|
|
17512
|
+
[id="thread-close"] { background:transparent; border:none; cursor:pointer; font:600 16px system-ui; color:#948C9C; }
|
|
17513
|
+
[id="thread-messages"] { flex:1; overflow:auto; }
|
|
17514
|
+
[id="thread-composer-row"] { padding:12px 16px 16px; border-top:1px solid #E6DEEA; }
|
|
17515
|
+
[id="thread-composer"] { width:100%; box-sizing:border-box; resize:none; height:40px; border:1px solid #E6DEEA; border-radius:9px; padding:9px 12px; font:13.5px system-ui; outline:none; }
|
|
17516
|
+
.overlay { position:fixed; inset:0; background:rgba(27,21,35,.42); display:none; align-items:center; justify-content:center; z-index:50; }
|
|
17517
|
+
.overlay.open { display:flex; }
|
|
17518
|
+
.modal { width:420px; max-height:640px; background:#fff; border-radius:12px; box-shadow:0 30px 70px rgba(0,0,0,.25); display:flex; flex-direction:column; overflow:hidden; }
|
|
17519
|
+
.modal-head { display:flex; align-items:center; justify-content:space-between; padding:16px 18px; border-bottom:1px solid #E6DEEA; font:700 15px system-ui; }
|
|
17520
|
+
.modal-body { padding:16px 18px; overflow:auto; flex:1; }
|
|
17521
|
+
.modal-foot { padding:14px 18px; border-top:1px solid #E6DEEA; display:flex; justify-content:flex-end; gap:8px; }
|
|
17522
|
+
.modal label { font:600 12px system-ui; color:#6B6275; display:block; margin-bottom:6px; margin-top: 12px; }
|
|
17523
|
+
.modal input, .modal select { width:100%; box-sizing:border-box; border:1px solid #E6DEEA; border-radius:8px; padding:9px 12px; font:14px system-ui; outline:none; }
|
|
17524
|
+
.btn { background:transparent; border:1px solid #E6DEEA; border-radius:7px; padding:8px 16px; font:600 13px system-ui; cursor:pointer; color:#6B6275; }
|
|
17525
|
+
.btn-primary { background:#6B3FA0; color:#fff; border:none; border-radius:7px; padding:8px 18px; font:600 13px system-ui; cursor:pointer; }
|
|
17526
|
+
.member-row { display:flex; align-items:center; gap:9px; padding:6px 4px; font:500 13.5px system-ui; }
|
|
17527
|
+
.member-row .x { margin-left:auto; background:transparent; border:none; cursor:pointer; color:#C2BACB; font:600 14px system-ui; }
|
|
17528
|
+
.empty { padding: 40px; text-align:center; color:#948C9C; font:14px system-ui; }
|
|
17529
|
+
.note-chip { margin-top:8px; display:inline-flex; align-items:center; gap:6px; padding:5px 10px; border-radius:8px; border:1px solid #E6DEEA; background:#FAF7FC; cursor:pointer; font:600 12.5px system-ui; color:#6B3FA0; text-decoration:underline; text-decoration-color:#D9CCEB; }
|
|
17530
|
+
.note-chip:hover { background:#F2EEF5; }
|
|
17531
|
+
[id="composer-row"] { position:relative; }
|
|
17532
|
+
.attach-chip-row { display:none; align-items:center; gap:6px; margin-bottom:6px; font:500 12.5px system-ui; color:#6B3FA0; }
|
|
17533
|
+
.attach-chip-row.show { display:flex; }
|
|
17534
|
+
.attach-chip-row button { background:transparent; border:none; cursor:pointer; color:#B7ADC2; font:600 13px system-ui; }
|
|
17535
|
+
[id="attach-btn"] { background:transparent; border:1px solid #E6DEEA; border-radius:7px; padding:8px 12px; font:600 13px system-ui; cursor:pointer; color:#6B6275; margin-top:8px; float:right; margin-right:8px; }
|
|
17536
|
+
[id="thread-attach-btn"] { background:transparent; border:1px solid #E6DEEA; border-radius:7px; padding:7px 10px; font:600 12.5px system-ui; cursor:pointer; color:#6B6275; margin-top:7px; float:right; margin-right:8px; }
|
|
17537
|
+
[id="note-picker-search"] { margin-bottom:10px; }
|
|
17538
|
+
.note-pick-row { padding:8px 6px; border-radius:7px; cursor:pointer; font:500 13.5px system-ui; color:#3D3548; }
|
|
17539
|
+
.note-pick-row:hover { background:#F2EEF5; }
|
|
17540
|
+
.note-pick-row .vault-tag { color:#A89BB5; font-size:11.5px; margin-left:6px; }
|
|
17541
|
+
[id="note-card-body"] { font-size:14.5px; line-height:1.55; color:#2B2433; white-space:pre-wrap; }
|
|
17542
|
+
</style>
|
|
17543
|
+
</head>
|
|
17544
|
+
<body>
|
|
17545
|
+
<div id="app">
|
|
17546
|
+
<div id="sidebar">
|
|
17547
|
+
<div class="brand">Chat Everywhere</div>
|
|
17548
|
+
<button id="mentions-btn">@ Mentions <span id="mentions-badge" class="badge" style="display:none"></span></button>
|
|
17549
|
+
<div class="section-label">Channels</div>
|
|
17550
|
+
<div id="channel-list"></div>
|
|
17551
|
+
<button id="create-btn">+ New channel</button>
|
|
17552
|
+
</div>
|
|
17553
|
+
<div id="main">
|
|
17554
|
+
<div id="chan-header" style="display:none">
|
|
17555
|
+
<div id="chan-title"></div>
|
|
17556
|
+
<button id="manage-btn">Members</button>
|
|
17557
|
+
</div>
|
|
17558
|
+
<div id="messages"><div class="empty">Pick a channel on the left.</div></div>
|
|
17559
|
+
<div id="composer-row" style="display:none">
|
|
17560
|
+
<div id="attach-chip-row" class="attach-chip-row"><span>\u{1F4CE} <span id="attach-chip-label"></span></span><button id="attach-chip-clear" title="Remove attachment">x</button></div>
|
|
17561
|
+
<textarea id="composer" placeholder="Message... @ to mention someone"></textarea>
|
|
17562
|
+
<button id="send-btn">Send</button>
|
|
17563
|
+
<button id="attach-btn" title="Attach a note">\u{1F4CE} Attach note</button>
|
|
17564
|
+
<div style="clear:both"></div>
|
|
17565
|
+
</div>
|
|
17566
|
+
</div>
|
|
17567
|
+
<div id="thread-panel">
|
|
17568
|
+
<div id="thread-header"><span>Thread</span><button id="thread-close">x</button></div>
|
|
17569
|
+
<div id="thread-messages"></div>
|
|
17570
|
+
<div id="thread-composer-row">
|
|
17571
|
+
<div id="thread-attach-chip-row" class="attach-chip-row"><span>\u{1F4CE} <span id="thread-attach-chip-label"></span></span><button id="thread-attach-chip-clear" title="Remove attachment">x</button></div>
|
|
17572
|
+
<textarea id="thread-composer" placeholder="Reply in thread"></textarea>
|
|
17573
|
+
<button id="thread-send-btn" class="btn-primary" style="margin-top:7px;float:right">Reply</button>
|
|
17574
|
+
<button id="thread-attach-btn" title="Attach a note">\u{1F4CE} Attach</button>
|
|
17575
|
+
<div style="clear:both"></div>
|
|
17576
|
+
</div>
|
|
17577
|
+
</div>
|
|
17578
|
+
</div>
|
|
17579
|
+
|
|
17580
|
+
<div id="note-picker-overlay" class="overlay">
|
|
17581
|
+
<div class="modal">
|
|
17582
|
+
<div class="modal-head">Attach a note <button id="note-picker-close" style="background:none;border:none;font-size:18px;cursor:pointer;color:#948C9C">x</button></div>
|
|
17583
|
+
<div class="modal-body">
|
|
17584
|
+
<input id="note-picker-search" placeholder="Search your notes by title or path..." />
|
|
17585
|
+
<div id="note-picker-list"></div>
|
|
17586
|
+
</div>
|
|
17587
|
+
</div>
|
|
17588
|
+
</div>
|
|
17589
|
+
|
|
17590
|
+
<div id="note-card-overlay" class="overlay">
|
|
17591
|
+
<div class="modal">
|
|
17592
|
+
<div class="modal-head"><span id="note-card-title"></span><button id="note-card-close" style="background:none;border:none;font-size:18px;cursor:pointer;color:#948C9C">x</button></div>
|
|
17593
|
+
<div class="modal-body"><div id="note-card-body"></div></div>
|
|
17594
|
+
</div>
|
|
17595
|
+
</div>
|
|
17596
|
+
|
|
17597
|
+
<div id="mentions-overlay" class="overlay">
|
|
17598
|
+
<div class="modal">
|
|
17599
|
+
<div class="modal-head">Where am I tagged <button id="mentions-close" style="background:none;border:none;font-size:18px;cursor:pointer;color:#948C9C">x</button></div>
|
|
17600
|
+
<div id="mentions-list" class="modal-body"></div>
|
|
17601
|
+
</div>
|
|
17602
|
+
</div>
|
|
17603
|
+
|
|
17604
|
+
<div id="create-overlay" class="overlay">
|
|
17605
|
+
<div class="modal">
|
|
17606
|
+
<div class="modal-head">New channel</div>
|
|
17607
|
+
<div class="modal-body">
|
|
17608
|
+
<label>Channel name</label>
|
|
17609
|
+
<input id="new-channel-name" placeholder="e.g. fundraise-q3" />
|
|
17610
|
+
<label>Invite by email (comma-separated, optional)</label>
|
|
17611
|
+
<input id="new-channel-invites" placeholder="andrew@x.com, seo@y.com" />
|
|
17612
|
+
<div id="create-error" style="color:#C2410C;font:13px system-ui;margin-top:8px;display:none"></div>
|
|
17613
|
+
</div>
|
|
17614
|
+
<div class="modal-foot">
|
|
17615
|
+
<button class="btn" id="create-cancel">Cancel</button>
|
|
17616
|
+
<button class="btn-primary" id="create-submit">Create</button>
|
|
17617
|
+
</div>
|
|
17618
|
+
</div>
|
|
17619
|
+
</div>
|
|
17620
|
+
|
|
17621
|
+
<div id="manage-overlay" class="overlay">
|
|
17622
|
+
<div class="modal">
|
|
17623
|
+
<div class="modal-head">Members</div>
|
|
17624
|
+
<div class="modal-body">
|
|
17625
|
+
<div id="member-list"></div>
|
|
17626
|
+
<label>Invite someone</label>
|
|
17627
|
+
<input id="invite-email" placeholder="email" />
|
|
17628
|
+
<select id="invite-scope" style="margin-top:8px">
|
|
17629
|
+
<option value="write">Write - can post</option>
|
|
17630
|
+
<option value="read">Read - view only</option>
|
|
17631
|
+
<option value="admin">Admin - manage members</option>
|
|
17632
|
+
</select>
|
|
17633
|
+
<div id="invite-error" style="color:#C2410C;font:13px system-ui;margin-top:8px;display:none"></div>
|
|
17634
|
+
</div>
|
|
17635
|
+
<div class="modal-foot">
|
|
17636
|
+
<button class="btn-primary" id="invite-submit">Invite</button>
|
|
17637
|
+
<button class="btn" id="manage-close">Done</button>
|
|
17638
|
+
</div>
|
|
17639
|
+
</div>
|
|
17640
|
+
</div>
|
|
17641
|
+
|
|
17642
|
+
<script>
|
|
17643
|
+
const TOKEN = ${JSON.stringify(token)};
|
|
17644
|
+
const state = { channels: [], activeVault: null, activeName: null, members: [], threadParent: null, pollTimer: null, pendingAttach: null, threadPendingAttach: null, ownNotes: null, pickerTarget: 'main' };
|
|
17645
|
+
|
|
17646
|
+
function api(path, opts) {
|
|
17647
|
+
const url = '/chat/api/' + path;
|
|
17648
|
+
return fetch(url, opts).then(r => r.json());
|
|
17649
|
+
}
|
|
17650
|
+
function get(path, params) {
|
|
17651
|
+
const qs = new URLSearchParams({ token: TOKEN, ...params }).toString();
|
|
17652
|
+
return api(path + '?' + qs);
|
|
17653
|
+
}
|
|
17654
|
+
function post(path, body) {
|
|
17655
|
+
return api(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ token: TOKEN, ...body }) });
|
|
17656
|
+
}
|
|
17657
|
+
|
|
17658
|
+
function colorFor(email) {
|
|
17659
|
+
let h = 0;
|
|
17660
|
+
for (let i = 0; i < email.length; i++) h = (h * 31 + email.charCodeAt(i)) % 360;
|
|
17661
|
+
return 'hsl(' + h + ', 55%, 45%)';
|
|
17662
|
+
}
|
|
17663
|
+
function initialsFor(email) {
|
|
17664
|
+
const local = (email || '?').split('@')[0];
|
|
17665
|
+
return (local.slice(0, 2) || '??').toUpperCase();
|
|
17666
|
+
}
|
|
17667
|
+
function esc(s) {
|
|
17668
|
+
return (s || '').replace(/[&<>]/g, c => ({ '&': '&', '<': '<', '>': '>' }[c]));
|
|
17669
|
+
}
|
|
17670
|
+
function fmtTime(iso) {
|
|
17671
|
+
try { return new Date(iso).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); } catch (e) { return ''; }
|
|
17672
|
+
}
|
|
17673
|
+
|
|
17674
|
+
async function loadChannels() {
|
|
17675
|
+
const r = await get('channels', {});
|
|
17676
|
+
if (!r.ok) { document.getElementById('channel-list').innerHTML = '<div class="empty">' + esc(r.error || 'failed to load') + '</div>'; return; }
|
|
17677
|
+
state.channels = r.channels || [];
|
|
17678
|
+
const list = document.getElementById('channel-list');
|
|
17679
|
+
list.innerHTML = '';
|
|
17680
|
+
state.channels.forEach(ch => {
|
|
17681
|
+
const btn = document.createElement('button');
|
|
17682
|
+
btn.className = 'chan-row' + (ch.handle === state.activeVault ? ' active' : '');
|
|
17683
|
+
btn.textContent = '# ' + ch.vault;
|
|
17684
|
+
btn.onclick = () => openChannel(ch.handle, ch.vault);
|
|
17685
|
+
list.appendChild(btn);
|
|
17686
|
+
});
|
|
17687
|
+
if (!state.channels.length) list.innerHTML = '<div class="empty" style="color:#B79FC4">No channels yet</div>';
|
|
17688
|
+
}
|
|
17689
|
+
|
|
17690
|
+
async function openChannel(handle, name) {
|
|
17691
|
+
state.activeVault = handle;
|
|
17692
|
+
state.activeName = name;
|
|
17693
|
+
closeThread();
|
|
17694
|
+
document.querySelectorAll('.chan-row').forEach(b => b.classList.toggle('active', b.textContent === '# ' + name));
|
|
17695
|
+
document.getElementById('chan-header').style.display = 'flex';
|
|
17696
|
+
document.getElementById('chan-title').textContent = '#' + name;
|
|
17697
|
+
document.getElementById('composer-row').style.display = 'block';
|
|
17698
|
+
await loadMembers();
|
|
17699
|
+
await loadMessages();
|
|
17700
|
+
restartPolling();
|
|
17701
|
+
}
|
|
17702
|
+
|
|
17703
|
+
const QUICK_EMOJI = ['\u{1F44D}', '\u2764\uFE0F', '\u{1F602}', '\u{1F389}', '\u{1F440}', '\u2705'];
|
|
17704
|
+
|
|
17705
|
+
function renderMessage(m, container, compact) {
|
|
17706
|
+
const div = document.createElement('div');
|
|
17707
|
+
div.className = 'msg';
|
|
17708
|
+
const author = m.authorIdentity || 'unknown';
|
|
17709
|
+
div.innerHTML =
|
|
17710
|
+
'<div class="avatar" style="background:' + colorFor(author) + (compact ? ';width:28px;height:28px' : '') + '">' + initialsFor(author) + '</div>' +
|
|
17711
|
+
'<div class="msg-body">' +
|
|
17712
|
+
'<div class="msg-head"><span class="msg-author">' + esc(author) + '</span>' +
|
|
17713
|
+
(m.authorIsAgent ? '<span class="agent-pill">AGENT</span>' : '') +
|
|
17714
|
+
'<span class="msg-ts">' + fmtTime(m.postedAt) + '</span></div>' +
|
|
17715
|
+
(!compact ? '<span class="msg-actions">' +
|
|
17716
|
+
'<button class="icon-btn reply-icon" title="Reply">\u{1F4AC}</button>' +
|
|
17717
|
+
'<button class="icon-btn react-icon" title="Add reaction">\u{1F642}</button>' +
|
|
17718
|
+
'</span>' : '') +
|
|
17719
|
+
'<div class="msg-content"></div>' +
|
|
17720
|
+
(m.attachedNote ? '<button class="note-chip">\u{1F4CE} ' + esc(m.attachedNote.title) + '</button>' : '') +
|
|
17721
|
+
(m.reactions && m.reactions.length ? '<div class="reactions"></div>' : '') +
|
|
17722
|
+
(!compact ? '<button class="reply-summary" style="' + (m.replyCount ? '' : 'display:none') + '">' + (m.replyCount || 0) + ' replies' + (m.lastReplyAt ? ' - last ' + fmtTime(m.lastReplyAt) : '') + '</button>' : '') +
|
|
17723
|
+
(!compact ? '<div class="emoji-popover"></div>' : '') +
|
|
17724
|
+
(m.readBy && m.readBy.length ? '<div class="seen">seen by ' + m.readBy.length + '</div>' : '') +
|
|
17725
|
+
'</div>';
|
|
17726
|
+
div.querySelector('.msg-content').textContent = m.content;
|
|
17727
|
+
const noteChip = div.querySelector('.note-chip');
|
|
17728
|
+
if (noteChip) noteChip.onclick = () => openNoteCard(state.activeVault, m.messageId);
|
|
17729
|
+
if (m.reactions && m.reactions.length) {
|
|
17730
|
+
const counts = {};
|
|
17731
|
+
m.reactions.forEach(r => { counts[r.emoji] = (counts[r.emoji] || 0) + 1; });
|
|
17732
|
+
const rdiv = div.querySelector('.reactions');
|
|
17733
|
+
Object.entries(counts).forEach(([emoji, count]) => {
|
|
17734
|
+
const b = document.createElement('button');
|
|
17735
|
+
b.className = 'reaction';
|
|
17736
|
+
b.textContent = emoji + ' ' + count;
|
|
17737
|
+
b.onclick = () => react(m.messageId, emoji);
|
|
17738
|
+
rdiv.appendChild(b);
|
|
17739
|
+
});
|
|
17740
|
+
}
|
|
17741
|
+
if (!compact) {
|
|
17742
|
+
const replyBtn = div.querySelector('.reply-summary');
|
|
17743
|
+
if (replyBtn) replyBtn.onclick = () => openThread(m.messageId);
|
|
17744
|
+
const replyIcon = div.querySelector('.reply-icon');
|
|
17745
|
+
if (replyIcon) replyIcon.onclick = () => openThread(m.messageId);
|
|
17746
|
+
const reactIcon = div.querySelector('.react-icon');
|
|
17747
|
+
const popover = div.querySelector('.emoji-popover');
|
|
17748
|
+
if (reactIcon && popover) {
|
|
17749
|
+
QUICK_EMOJI.forEach(emoji => {
|
|
17750
|
+
const b = document.createElement('button');
|
|
17751
|
+
b.className = 'emoji-opt';
|
|
17752
|
+
b.textContent = emoji;
|
|
17753
|
+
b.onclick = () => { react(m.messageId, emoji); popover.classList.remove('open'); };
|
|
17754
|
+
popover.appendChild(b);
|
|
17755
|
+
});
|
|
17756
|
+
const actions = div.querySelector('.msg-actions');
|
|
17757
|
+
reactIcon.onclick = (e) => {
|
|
17758
|
+
e.stopPropagation();
|
|
17759
|
+
document.querySelectorAll('.emoji-popover.open').forEach(p => { if (p !== popover) { p.classList.remove('open'); p.closest('.msg-body').querySelector('.msg-actions').classList.remove('pinned-open'); } });
|
|
17760
|
+
popover.classList.toggle('open');
|
|
17761
|
+
if (actions) actions.classList.toggle('pinned-open', popover.classList.contains('open'));
|
|
17762
|
+
};
|
|
17763
|
+
document.addEventListener('click', () => { popover.classList.remove('open'); if (actions) actions.classList.remove('pinned-open'); });
|
|
17764
|
+
}
|
|
17765
|
+
}
|
|
17766
|
+
container.appendChild(div);
|
|
17767
|
+
}
|
|
17768
|
+
|
|
17769
|
+
async function loadMessages() {
|
|
17770
|
+
const r = await get('messages', { vault: state.activeVault });
|
|
17771
|
+
const box = document.getElementById('messages');
|
|
17772
|
+
box.innerHTML = '';
|
|
17773
|
+
if (!r.ok) { box.innerHTML = '<div class="empty">' + esc(r.error) + '</div>'; return; }
|
|
17774
|
+
(r.messages || []).forEach(m => renderMessage(m, box, false));
|
|
17775
|
+
box.scrollTop = box.scrollHeight;
|
|
17776
|
+
}
|
|
17777
|
+
|
|
17778
|
+
async function react(messageId, emoji) {
|
|
17779
|
+
await post('react', { vault: state.activeVault, messageId, emoji });
|
|
17780
|
+
if (state.threadParent) await loadThread(state.threadParent); else await loadMessages();
|
|
17781
|
+
}
|
|
17782
|
+
|
|
17783
|
+
document.getElementById('send-btn').onclick = async () => {
|
|
17784
|
+
const ta = document.getElementById('composer');
|
|
17785
|
+
const content = ta.value.trim();
|
|
17786
|
+
if (!content && !state.pendingAttach) return;
|
|
17787
|
+
ta.value = '';
|
|
17788
|
+
const attachNote = state.pendingAttach ? { vault: state.pendingAttach.vault, path: state.pendingAttach.path } : undefined;
|
|
17789
|
+
clearAttach('main');
|
|
17790
|
+
await post('post', { vault: state.activeVault, content: content || (attachNote ? 'shared a note' : ''), attachNote });
|
|
17791
|
+
await loadMessages();
|
|
17792
|
+
};
|
|
17793
|
+
document.getElementById('composer').addEventListener('keydown', e => {
|
|
17794
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); document.getElementById('send-btn').click(); }
|
|
17795
|
+
});
|
|
17796
|
+
|
|
17797
|
+
function clearAttach(target) {
|
|
17798
|
+
if (target === 'main') {
|
|
17799
|
+
state.pendingAttach = null;
|
|
17800
|
+
document.getElementById('attach-chip-row').classList.remove('show');
|
|
17801
|
+
} else {
|
|
17802
|
+
state.threadPendingAttach = null;
|
|
17803
|
+
document.getElementById('thread-attach-chip-row').classList.remove('show');
|
|
17804
|
+
}
|
|
17805
|
+
}
|
|
17806
|
+
document.getElementById('attach-chip-clear').onclick = () => clearAttach('main');
|
|
17807
|
+
document.getElementById('thread-attach-chip-clear').onclick = () => clearAttach('thread');
|
|
17808
|
+
|
|
17809
|
+
async function openNotePicker(target) {
|
|
17810
|
+
state.pickerTarget = target;
|
|
17811
|
+
document.getElementById('note-picker-search').value = '';
|
|
17812
|
+
document.getElementById('note-picker-overlay').classList.add('open');
|
|
17813
|
+
if (!state.ownNotes) {
|
|
17814
|
+
const r = await get('notes-own', {});
|
|
17815
|
+
state.ownNotes = r.ok ? (r.notes || []) : [];
|
|
17816
|
+
}
|
|
17817
|
+
renderNotePickerList(state.ownNotes);
|
|
17818
|
+
document.getElementById('note-picker-search').focus();
|
|
17819
|
+
}
|
|
17820
|
+
function renderNotePickerList(notes) {
|
|
17821
|
+
const box = document.getElementById('note-picker-list');
|
|
17822
|
+
box.innerHTML = '';
|
|
17823
|
+
if (!notes.length) { box.innerHTML = '<div class="empty">No notes found.</div>'; return; }
|
|
17824
|
+
notes.forEach(n => {
|
|
17825
|
+
const row = document.createElement('div');
|
|
17826
|
+
row.className = 'note-pick-row';
|
|
17827
|
+
row.innerHTML = esc(n.title) + '<span class="vault-tag">' + esc(n.vault) + '/' + esc(n.path) + '</span>';
|
|
17828
|
+
row.onclick = () => {
|
|
17829
|
+
const chip = { vault: n.vault, path: n.path, title: n.title };
|
|
17830
|
+
if (state.pickerTarget === 'main') {
|
|
17831
|
+
state.pendingAttach = chip;
|
|
17832
|
+
document.getElementById('attach-chip-label').textContent = n.title;
|
|
17833
|
+
document.getElementById('attach-chip-row').classList.add('show');
|
|
17834
|
+
} else {
|
|
17835
|
+
state.threadPendingAttach = chip;
|
|
17836
|
+
document.getElementById('thread-attach-chip-label').textContent = n.title;
|
|
17837
|
+
document.getElementById('thread-attach-chip-row').classList.add('show');
|
|
17838
|
+
}
|
|
17839
|
+
document.getElementById('note-picker-overlay').classList.remove('open');
|
|
17840
|
+
};
|
|
17841
|
+
box.appendChild(row);
|
|
17842
|
+
});
|
|
17843
|
+
}
|
|
17844
|
+
document.getElementById('attach-btn').onclick = () => openNotePicker('main');
|
|
17845
|
+
document.getElementById('thread-attach-btn').onclick = () => openNotePicker('thread');
|
|
17846
|
+
document.getElementById('note-picker-close').onclick = () => document.getElementById('note-picker-overlay').classList.remove('open');
|
|
17847
|
+
document.getElementById('note-picker-search').addEventListener('input', e => {
|
|
17848
|
+
const q = e.target.value.trim().toLowerCase();
|
|
17849
|
+
const filtered = !q ? state.ownNotes : (state.ownNotes || []).filter(n => n.title.toLowerCase().includes(q) || n.path.toLowerCase().includes(q));
|
|
17850
|
+
renderNotePickerList(filtered || []);
|
|
17851
|
+
});
|
|
17852
|
+
|
|
17853
|
+
async function openNoteCard(vault, messageId) {
|
|
17854
|
+
document.getElementById('note-card-title').textContent = 'Loading...';
|
|
17855
|
+
document.getElementById('note-card-body').textContent = '';
|
|
17856
|
+
document.getElementById('note-card-overlay').classList.add('open');
|
|
17857
|
+
const r = await get('message-note', { vault, messageId });
|
|
17858
|
+
if (!r.ok) {
|
|
17859
|
+
document.getElementById('note-card-title').textContent = 'Unavailable';
|
|
17860
|
+
document.getElementById('note-card-body').textContent = r.error || 'Could not load this note.';
|
|
17861
|
+
return;
|
|
17862
|
+
}
|
|
17863
|
+
document.getElementById('note-card-title').textContent = r.note.title;
|
|
17864
|
+
document.getElementById('note-card-body').textContent = r.note.content;
|
|
17865
|
+
}
|
|
17866
|
+
document.getElementById('note-card-close').onclick = () => document.getElementById('note-card-overlay').classList.remove('open');
|
|
17867
|
+
|
|
17868
|
+
async function openThread(parentMessageId) {
|
|
17869
|
+
state.threadParent = parentMessageId;
|
|
17870
|
+
document.getElementById('thread-panel').classList.add('open');
|
|
17871
|
+
await loadThread(parentMessageId);
|
|
17872
|
+
}
|
|
17873
|
+
function closeThread() {
|
|
17874
|
+
state.threadParent = null;
|
|
17875
|
+
document.getElementById('thread-panel').classList.remove('open');
|
|
17876
|
+
}
|
|
17877
|
+
document.getElementById('thread-close').onclick = closeThread;
|
|
17878
|
+
|
|
17879
|
+
async function loadThread(parentMessageId) {
|
|
17880
|
+
const rootBox = document.getElementById('thread-messages');
|
|
17881
|
+
rootBox.innerHTML = '';
|
|
17882
|
+
const all = await get('messages', { vault: state.activeVault });
|
|
17883
|
+
const root = (all.messages || []).find(m => m.messageId === parentMessageId);
|
|
17884
|
+
if (root) renderMessage(root, rootBox, true);
|
|
17885
|
+
const r = await get('messages', { vault: state.activeVault, parentMessageId });
|
|
17886
|
+
const replyHeader = document.createElement('div');
|
|
17887
|
+
replyHeader.style.cssText = 'padding:6px 16px;font:600 11.5px system-ui;color:#948C9C;border-bottom:1px solid #F2EEF5;margin-bottom:4px';
|
|
17888
|
+
replyHeader.textContent = (r.messages || []).length + ' replies';
|
|
17889
|
+
rootBox.appendChild(replyHeader);
|
|
17890
|
+
(r.messages || []).forEach(m => renderMessage(m, rootBox, true));
|
|
17891
|
+
}
|
|
17892
|
+
document.getElementById('thread-send-btn').onclick = async () => {
|
|
17893
|
+
const ta = document.getElementById('thread-composer');
|
|
17894
|
+
const content = ta.value.trim();
|
|
17895
|
+
if ((!content && !state.threadPendingAttach) || !state.threadParent) return;
|
|
17896
|
+
ta.value = '';
|
|
17897
|
+
const attachNote = state.threadPendingAttach ? { vault: state.threadPendingAttach.vault, path: state.threadPendingAttach.path } : undefined;
|
|
17898
|
+
clearAttach('thread');
|
|
17899
|
+
await post('reply', { vault: state.activeVault, parentMessageId: state.threadParent, content: content || (attachNote ? 'shared a note' : ''), attachNote });
|
|
17900
|
+
await loadThread(state.threadParent);
|
|
17901
|
+
await loadChannels();
|
|
17902
|
+
};
|
|
17903
|
+
|
|
17904
|
+
async function loadMembers() {
|
|
17905
|
+
const r = await get('members', { vault: state.activeVault });
|
|
17906
|
+
state.members = r.ok ? (r.members || []) : [];
|
|
17907
|
+
}
|
|
17908
|
+
|
|
17909
|
+
document.getElementById('manage-btn').onclick = async () => {
|
|
17910
|
+
await loadMembers();
|
|
17911
|
+
const list = document.getElementById('member-list');
|
|
17912
|
+
list.innerHTML = '';
|
|
17913
|
+
state.members.forEach(m => {
|
|
17914
|
+
const row = document.createElement('div');
|
|
17915
|
+
row.className = 'member-row';
|
|
17916
|
+
row.innerHTML = '<span class="avatar" style="width:24px;height:24px;background:' + colorFor(m.identity) + '">' + initialsFor(m.identity) + '</span>' +
|
|
17917
|
+
'<span>' + esc(m.identity) + (m.isAgent ? ' <span class="agent-pill">AGENT</span>' : '') + '</span>' +
|
|
17918
|
+
'<span style="font:600 10.5px system-ui;color:#6B6275;background:#F2EEF5;padding:2px 7px;border-radius:999px;margin-left:auto">' + (m.scope.admin ? 'admin' : m.scope.write ? 'write' : 'read') + '</span>';
|
|
17919
|
+
list.appendChild(row);
|
|
17920
|
+
});
|
|
17921
|
+
document.getElementById('manage-overlay').classList.add('open');
|
|
17922
|
+
};
|
|
17923
|
+
document.getElementById('manage-close').onclick = () => document.getElementById('manage-overlay').classList.remove('open');
|
|
17924
|
+
document.getElementById('invite-submit').onclick = async () => {
|
|
17925
|
+
const email = document.getElementById('invite-email').value.trim();
|
|
17926
|
+
const scope = document.getElementById('invite-scope').value;
|
|
17927
|
+
const errEl = document.getElementById('invite-error');
|
|
17928
|
+
errEl.style.display = 'none';
|
|
17929
|
+
if (!email) return;
|
|
17930
|
+
const r = await post('invite', { vault: state.activeVault, email, scope });
|
|
17931
|
+
if (!r.ok) { errEl.textContent = r.error || 'invite failed'; errEl.style.display = 'block'; return; }
|
|
17932
|
+
document.getElementById('invite-email').value = '';
|
|
17933
|
+
document.getElementById('manage-btn').click();
|
|
17934
|
+
};
|
|
17935
|
+
|
|
17936
|
+
document.getElementById('create-btn').onclick = () => document.getElementById('create-overlay').classList.add('open');
|
|
17937
|
+
document.getElementById('create-cancel').onclick = () => document.getElementById('create-overlay').classList.remove('open');
|
|
17938
|
+
document.getElementById('create-submit').onclick = async () => {
|
|
17939
|
+
const name = document.getElementById('new-channel-name').value.trim();
|
|
17940
|
+
const invitesRaw = document.getElementById('new-channel-invites').value.trim();
|
|
17941
|
+
const errEl = document.getElementById('create-error');
|
|
17942
|
+
errEl.style.display = 'none';
|
|
17943
|
+
if (!name) return;
|
|
17944
|
+
const inviteEmails = invitesRaw ? invitesRaw.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
17945
|
+
const r = await post('channel', { name, inviteEmails });
|
|
17946
|
+
if (!r.ok) { errEl.textContent = r.error || 'create failed'; errEl.style.display = 'block'; return; }
|
|
17947
|
+
document.getElementById('create-overlay').classList.remove('open');
|
|
17948
|
+
document.getElementById('new-channel-name').value = '';
|
|
17949
|
+
document.getElementById('new-channel-invites').value = '';
|
|
17950
|
+
await loadChannels();
|
|
17951
|
+
if (r.members && r.members.some(m => !m.ok)) {
|
|
17952
|
+
alert('Channel created, but some invites failed: ' + r.members.filter(m => !m.ok).map(m => m.identity + ' (' + m.error + ')').join(', '));
|
|
17953
|
+
}
|
|
17954
|
+
};
|
|
17955
|
+
|
|
17956
|
+
document.getElementById('mentions-btn').onclick = async () => {
|
|
17957
|
+
const r = await get('mentions', {});
|
|
17958
|
+
const list = document.getElementById('mentions-list');
|
|
17959
|
+
list.innerHTML = '';
|
|
17960
|
+
(r.mentions || []).forEach(m => {
|
|
17961
|
+
const row = document.createElement('button');
|
|
17962
|
+
row.style.cssText = 'display:block;width:100%;text-align:left;background:transparent;border:none;cursor:pointer;padding:10px 0;border-bottom:1px solid #F2EEF5';
|
|
17963
|
+
row.innerHTML = '<div style="font:600 11.5px system-ui;color:#6B3FA0">' + esc(m.channel) + '</div>' +
|
|
17964
|
+
'<div style="font:600 13.5px system-ui">' + esc(m.authorIdentity) + ' <span style="font:400 12px;color:#948C9C">- ' + fmtTime(m.mentionedAt) + '</span></div>' +
|
|
17965
|
+
'<div style="font:400 13.5px;color:#3D3548;margin-top:2px">' + esc(m.content) + '</div>';
|
|
17966
|
+
row.onclick = () => { document.getElementById('mentions-overlay').classList.remove('open'); openChannel(m.channel, (state.channels.find(c => c.handle === m.channel) || {}).vault || m.channel); };
|
|
17967
|
+
list.appendChild(row);
|
|
17968
|
+
});
|
|
17969
|
+
if (!(r.mentions || []).length) list.innerHTML = '<div class="empty">No mentions yet</div>';
|
|
17970
|
+
document.getElementById('mentions-overlay').classList.add('open');
|
|
17971
|
+
};
|
|
17972
|
+
document.getElementById('mentions-close').onclick = () => document.getElementById('mentions-overlay').classList.remove('open');
|
|
17973
|
+
|
|
17974
|
+
function restartPolling() {
|
|
17975
|
+
if (state.pollTimer) clearInterval(state.pollTimer);
|
|
17976
|
+
state.pollTimer = setInterval(async () => {
|
|
17977
|
+
if (document.visibilityState !== 'visible' || !state.activeVault) return;
|
|
17978
|
+
const r = await get('poll', { vault: state.activeVault });
|
|
17979
|
+
if (r.ok && ((r.newMessages || []).length || (r.newReplies || []).length || (r.newReactions || []).length)) {
|
|
17980
|
+
await loadMessages();
|
|
17981
|
+
if (state.threadParent) await loadThread(state.threadParent);
|
|
17982
|
+
}
|
|
17983
|
+
}, 4000);
|
|
17984
|
+
}
|
|
17985
|
+
|
|
17986
|
+
loadChannels();
|
|
17987
|
+
</script>
|
|
17988
|
+
</body>
|
|
17989
|
+
</html>`;
|
|
17990
|
+
}
|
|
17991
|
+
|
|
17328
17992
|
// src/api/site-audit-worker.ts
|
|
17329
17993
|
var MAX_CONCURRENT_SITE_AUDIT = 1;
|
|
17330
17994
|
async function drainSiteAuditQueue(budget) {
|
|
@@ -17739,7 +18403,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
|
|
|
17739
18403
|
c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
|
|
17740
18404
|
return next();
|
|
17741
18405
|
});
|
|
17742
|
-
var app = new
|
|
18406
|
+
var app = new Hono16();
|
|
17743
18407
|
var STRIPE_API_VERSION = "2026-02-25.clover";
|
|
17744
18408
|
function requireStripeSecret() {
|
|
17745
18409
|
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
@@ -17782,6 +18446,7 @@ function opForCostPath(p) {
|
|
|
17782
18446
|
return "fb_ad";
|
|
17783
18447
|
}
|
|
17784
18448
|
if (p.startsWith("/instagram/")) return "instagram";
|
|
18449
|
+
if (p === "/reddit/thread") return "reddit_thread";
|
|
17785
18450
|
if (p.startsWith("/api/internal/site-architecture-auditor")) return "audit_site";
|
|
17786
18451
|
if (p.startsWith("/agent/")) return "browser_session";
|
|
17787
18452
|
return null;
|
|
@@ -18006,17 +18671,17 @@ app.get("/memory/vaults", auth2, async (c) => {
|
|
|
18006
18671
|
app.get("/memory/connect", auth2, async (c) => {
|
|
18007
18672
|
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
18008
18673
|
if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
|
|
18009
|
-
const
|
|
18674
|
+
const url = "https://memory.mcpscraper.dev/mcp";
|
|
18010
18675
|
const config = {
|
|
18011
18676
|
mcpServers: {
|
|
18012
|
-
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
env: { MCP_MEMORY_KEY: key }
|
|
18677
|
+
Memory: {
|
|
18678
|
+
url,
|
|
18679
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
18016
18680
|
}
|
|
18017
18681
|
}
|
|
18018
18682
|
};
|
|
18019
|
-
|
|
18683
|
+
const codeCommand = `claude mcp add --transport http memory ${url} --header "Authorization: Bearer ${key}"`;
|
|
18684
|
+
return c.json({ ok: true, url, key, config, codeCommand });
|
|
18020
18685
|
});
|
|
18021
18686
|
app.post("/memory/vault", auth2, async (c) => {
|
|
18022
18687
|
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
@@ -18201,7 +18866,7 @@ function countPaaQuestions(result) {
|
|
|
18201
18866
|
return Array.isArray(value.flat) ? value.flat.length : 0;
|
|
18202
18867
|
}
|
|
18203
18868
|
function paaCostForQuestionCount(questionCount) {
|
|
18204
|
-
return Math.max(1, questionCount) * MC_COSTS.paa;
|
|
18869
|
+
return MC_COSTS.paa_base + Math.max(1, questionCount) * MC_COSTS.paa;
|
|
18205
18870
|
}
|
|
18206
18871
|
async function checkHarvestLimits(user, reuseLockId) {
|
|
18207
18872
|
if (reuseLockId && await reuseExistingConcurrencyGate(user.id, reuseLockId, 30 * 60)) return null;
|
|
@@ -18247,7 +18912,7 @@ app.post("/harvest", auth2, async (c) => {
|
|
|
18247
18912
|
serpOnly: body.serpOnly ?? false,
|
|
18248
18913
|
pages: Math.min(2, Math.max(1, body.pages ?? 1))
|
|
18249
18914
|
};
|
|
18250
|
-
const harvestCost = options.serpOnly ? MC_COSTS.serp : options.maxQuestions
|
|
18915
|
+
const harvestCost = options.serpOnly ? MC_COSTS.serp : paaCostForQuestionCount(options.maxQuestions);
|
|
18251
18916
|
const { ok: harvestOk, balance_mc: harvestBal } = await debitMc(user.id, harvestCost, options.serpOnly ? LedgerOperation.SERP : LedgerOperation.PAA, options.query);
|
|
18252
18917
|
if (!harvestOk) return c.json(insufficientBalanceResponse(harvestBal, harvestCost), 402);
|
|
18253
18918
|
const jobId = await createJob(user.id, options.query, { ...options, billingHoldMc: harvestCost }, body.callback_url);
|
|
@@ -18283,7 +18948,7 @@ app.post("/harvest/sync", auth2, async (c) => {
|
|
|
18283
18948
|
serpOnly: body.serpOnly ?? false,
|
|
18284
18949
|
pages: Math.min(2, Math.max(1, body.pages ?? 1))
|
|
18285
18950
|
};
|
|
18286
|
-
const syncCost = options.serpOnly ? MC_COSTS.serp : options.maxQuestions
|
|
18951
|
+
const syncCost = options.serpOnly ? MC_COSTS.serp : paaCostForQuestionCount(options.maxQuestions);
|
|
18287
18952
|
const { ok: syncOk, balance_mc: syncBal } = await debitMc(user.id, syncCost, options.serpOnly ? LedgerOperation.SERP : LedgerOperation.PAA, options.query);
|
|
18288
18953
|
if (!syncOk) return c.json(insufficientBalanceResponse(syncBal, syncCost), 402);
|
|
18289
18954
|
const jobId = await createRunningJob(user.id, options.query, options);
|
|
@@ -18879,7 +19544,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
18879
19544
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
18880
19545
|
return c.json({ error: "Unauthorized" }, 401);
|
|
18881
19546
|
}
|
|
18882
|
-
const { drainQueue } = await import("./worker-
|
|
19547
|
+
const { drainQueue } = await import("./worker-NV4GTIRG.js");
|
|
18883
19548
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
18884
19549
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
18885
19550
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
|
|
@@ -18904,6 +19569,7 @@ app.route("/workflows", workflowApp);
|
|
|
18904
19569
|
app.route("/serp-intelligence", serpIntelligenceApp);
|
|
18905
19570
|
app.route("/mcp", mcpApp);
|
|
18906
19571
|
app.route("/agent", buildBrowserAgentRoutes());
|
|
19572
|
+
app.route("/chat", chatApp);
|
|
18907
19573
|
app.get("/console", (c) => c.html(renderConsoleHtml()));
|
|
18908
19574
|
app.get("/console/auth/:id", async (c) => {
|
|
18909
19575
|
const id = c.req.param("id");
|
|
@@ -19027,4 +19693,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
19027
19693
|
export {
|
|
19028
19694
|
app
|
|
19029
19695
|
};
|
|
19030
|
-
//# sourceMappingURL=server-
|
|
19696
|
+
//# sourceMappingURL=server-KUNOK47B.js.map
|