mcp-scraper 0.3.26 → 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 +2366 -303
- 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 +23 -8
- 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-2GHI2D6Q.js → chunk-6KHGYJIT.js} +1366 -10
- 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-HIGHKTEV.js → server-KUNOK47B.js} +695 -19
- 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/package.json +1 -1
- package/dist/chunk-2GHI2D6Q.js.map +0 -1
- package/dist/chunk-G7PQ64LM.js.map +0 -1
- package/dist/chunk-MS2UZ6HR.js +0 -7
- package/dist/chunk-MS2UZ6HR.js.map +0 -1
- package/dist/chunk-WYCER2HW.js.map +0 -1
- package/dist/server-HIGHKTEV.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
|
|
@@ -14527,8 +14528,10 @@ mcpApp.all("/", async (c) => {
|
|
|
14527
14528
|
sessionIdGenerator: void 0,
|
|
14528
14529
|
enableJsonResponse: true
|
|
14529
14530
|
});
|
|
14531
|
+
const consoleBaseUrl = process.env.BROWSER_AGENT_CONSOLE_URL?.trim() || baseUrl;
|
|
14530
14532
|
const server = buildPaaExtractorMcpServer(executor, { savesReportsLocally: false });
|
|
14531
14533
|
registerSerpIntelligenceCaptureTools(server, executor);
|
|
14534
|
+
registerBrowserAgentMcpTools(server, { baseUrl, apiKey: callerKey, consoleBaseUrl });
|
|
14532
14535
|
await server.connect(transport);
|
|
14533
14536
|
return transport.handleRequest(c.req.raw);
|
|
14534
14537
|
} catch {
|
|
@@ -16746,7 +16749,7 @@ function decryptMemoryKey(stored) {
|
|
|
16746
16749
|
}
|
|
16747
16750
|
async function memoryCall(toolName, args, userMemoryKey) {
|
|
16748
16751
|
try {
|
|
16749
|
-
const res = await fetch(`${MEMORY_BASE_URL()}/api/mcp/
|
|
16752
|
+
const res = await fetch(`${MEMORY_BASE_URL()}/api/mcp/memoryServer/tools/${toolName}/execute`, {
|
|
16750
16753
|
method: "POST",
|
|
16751
16754
|
headers: { "content-type": "application/json" },
|
|
16752
16755
|
body: JSON.stringify({ data: { ...args, apiKey: userMemoryKey } })
|
|
@@ -16910,7 +16913,7 @@ import { getCookie, setCookie } from "hono/cookie";
|
|
|
16910
16913
|
import { createHash as createHash3, randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
|
|
16911
16914
|
import { importPKCS8, exportJWK, SignJWT } from "jose";
|
|
16912
16915
|
var ISSUER = "https://mcpscraper.dev";
|
|
16913
|
-
var RESOURCE = () => process.env.MCP_MEMORY_RESOURCE_URL ?? "https://
|
|
16916
|
+
var RESOURCE = () => process.env.MCP_MEMORY_RESOURCE_URL ?? "https://memory.mcpscraper.dev/mcp";
|
|
16914
16917
|
var SCRAPER_RESOURCE = () => (process.env.MCP_SCRAPER_RESOURCE_URL ?? "https://mcpscraper.dev/mcp").replace(/\/$/, "");
|
|
16915
16918
|
var MEMORY_SCOPES = ["memory.read", "memory.write", "memory.admin"];
|
|
16916
16919
|
var SCRAPER_SCOPES = ["scraper.use"];
|
|
@@ -17315,6 +17318,677 @@ oauthApp.post("/oauth/token", async (c) => {
|
|
|
17315
17318
|
return tokenErrorResponse(c, "unsupported_grant_type", "grant_type must be authorization_code or refresh_token", 400);
|
|
17316
17319
|
});
|
|
17317
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
|
+
|
|
17318
17992
|
// src/api/site-audit-worker.ts
|
|
17319
17993
|
var MAX_CONCURRENT_SITE_AUDIT = 1;
|
|
17320
17994
|
async function drainSiteAuditQueue(budget) {
|
|
@@ -17729,7 +18403,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
|
|
|
17729
18403
|
c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
|
|
17730
18404
|
return next();
|
|
17731
18405
|
});
|
|
17732
|
-
var app = new
|
|
18406
|
+
var app = new Hono16();
|
|
17733
18407
|
var STRIPE_API_VERSION = "2026-02-25.clover";
|
|
17734
18408
|
function requireStripeSecret() {
|
|
17735
18409
|
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
@@ -17772,6 +18446,7 @@ function opForCostPath(p) {
|
|
|
17772
18446
|
return "fb_ad";
|
|
17773
18447
|
}
|
|
17774
18448
|
if (p.startsWith("/instagram/")) return "instagram";
|
|
18449
|
+
if (p === "/reddit/thread") return "reddit_thread";
|
|
17775
18450
|
if (p.startsWith("/api/internal/site-architecture-auditor")) return "audit_site";
|
|
17776
18451
|
if (p.startsWith("/agent/")) return "browser_session";
|
|
17777
18452
|
return null;
|
|
@@ -17996,17 +18671,17 @@ app.get("/memory/vaults", auth2, async (c) => {
|
|
|
17996
18671
|
app.get("/memory/connect", auth2, async (c) => {
|
|
17997
18672
|
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
17998
18673
|
if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
|
|
17999
|
-
const
|
|
18674
|
+
const url = "https://memory.mcpscraper.dev/mcp";
|
|
18000
18675
|
const config = {
|
|
18001
18676
|
mcpServers: {
|
|
18002
|
-
|
|
18003
|
-
|
|
18004
|
-
|
|
18005
|
-
env: { MCP_MEMORY_KEY: key }
|
|
18677
|
+
Memory: {
|
|
18678
|
+
url,
|
|
18679
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
18006
18680
|
}
|
|
18007
18681
|
}
|
|
18008
18682
|
};
|
|
18009
|
-
|
|
18683
|
+
const codeCommand = `claude mcp add --transport http memory ${url} --header "Authorization: Bearer ${key}"`;
|
|
18684
|
+
return c.json({ ok: true, url, key, config, codeCommand });
|
|
18010
18685
|
});
|
|
18011
18686
|
app.post("/memory/vault", auth2, async (c) => {
|
|
18012
18687
|
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
@@ -18191,7 +18866,7 @@ function countPaaQuestions(result) {
|
|
|
18191
18866
|
return Array.isArray(value.flat) ? value.flat.length : 0;
|
|
18192
18867
|
}
|
|
18193
18868
|
function paaCostForQuestionCount(questionCount) {
|
|
18194
|
-
return Math.max(1, questionCount) * MC_COSTS.paa;
|
|
18869
|
+
return MC_COSTS.paa_base + Math.max(1, questionCount) * MC_COSTS.paa;
|
|
18195
18870
|
}
|
|
18196
18871
|
async function checkHarvestLimits(user, reuseLockId) {
|
|
18197
18872
|
if (reuseLockId && await reuseExistingConcurrencyGate(user.id, reuseLockId, 30 * 60)) return null;
|
|
@@ -18237,7 +18912,7 @@ app.post("/harvest", auth2, async (c) => {
|
|
|
18237
18912
|
serpOnly: body.serpOnly ?? false,
|
|
18238
18913
|
pages: Math.min(2, Math.max(1, body.pages ?? 1))
|
|
18239
18914
|
};
|
|
18240
|
-
const harvestCost = options.serpOnly ? MC_COSTS.serp : options.maxQuestions
|
|
18915
|
+
const harvestCost = options.serpOnly ? MC_COSTS.serp : paaCostForQuestionCount(options.maxQuestions);
|
|
18241
18916
|
const { ok: harvestOk, balance_mc: harvestBal } = await debitMc(user.id, harvestCost, options.serpOnly ? LedgerOperation.SERP : LedgerOperation.PAA, options.query);
|
|
18242
18917
|
if (!harvestOk) return c.json(insufficientBalanceResponse(harvestBal, harvestCost), 402);
|
|
18243
18918
|
const jobId = await createJob(user.id, options.query, { ...options, billingHoldMc: harvestCost }, body.callback_url);
|
|
@@ -18273,7 +18948,7 @@ app.post("/harvest/sync", auth2, async (c) => {
|
|
|
18273
18948
|
serpOnly: body.serpOnly ?? false,
|
|
18274
18949
|
pages: Math.min(2, Math.max(1, body.pages ?? 1))
|
|
18275
18950
|
};
|
|
18276
|
-
const syncCost = options.serpOnly ? MC_COSTS.serp : options.maxQuestions
|
|
18951
|
+
const syncCost = options.serpOnly ? MC_COSTS.serp : paaCostForQuestionCount(options.maxQuestions);
|
|
18277
18952
|
const { ok: syncOk, balance_mc: syncBal } = await debitMc(user.id, syncCost, options.serpOnly ? LedgerOperation.SERP : LedgerOperation.PAA, options.query);
|
|
18278
18953
|
if (!syncOk) return c.json(insufficientBalanceResponse(syncBal, syncCost), 402);
|
|
18279
18954
|
const jobId = await createRunningJob(user.id, options.query, options);
|
|
@@ -18869,7 +19544,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
18869
19544
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
18870
19545
|
return c.json({ error: "Unauthorized" }, 401);
|
|
18871
19546
|
}
|
|
18872
|
-
const { drainQueue } = await import("./worker-
|
|
19547
|
+
const { drainQueue } = await import("./worker-NV4GTIRG.js");
|
|
18873
19548
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
18874
19549
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
18875
19550
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
|
|
@@ -18894,6 +19569,7 @@ app.route("/workflows", workflowApp);
|
|
|
18894
19569
|
app.route("/serp-intelligence", serpIntelligenceApp);
|
|
18895
19570
|
app.route("/mcp", mcpApp);
|
|
18896
19571
|
app.route("/agent", buildBrowserAgentRoutes());
|
|
19572
|
+
app.route("/chat", chatApp);
|
|
18897
19573
|
app.get("/console", (c) => c.html(renderConsoleHtml()));
|
|
18898
19574
|
app.get("/console/auth/:id", async (c) => {
|
|
18899
19575
|
const id = c.req.param("id");
|
|
@@ -19017,4 +19693,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
19017
19693
|
export {
|
|
19018
19694
|
app
|
|
19019
19695
|
};
|
|
19020
|
-
//# sourceMappingURL=server-
|
|
19696
|
+
//# sourceMappingURL=server-KUNOK47B.js.map
|