mcp-scraper 0.3.26 → 0.3.28
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 +2367 -305
- 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 +132 -9
- package/dist/bin/mcp-stdio-server.cjs.map +1 -1
- package/dist/bin/mcp-stdio-server.js +13 -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-7MY2NSQX.js +7 -0
- package/dist/chunk-7MY2NSQX.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-2GHI2D6Q.js → chunk-NGD4ITAO.js} +1383 -17
- package/dist/chunk-NGD4ITAO.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-IDDX7KGT.js} +696 -42
- package/dist/server-IDDX7KGT.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
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
CaptureSerpPageSnapshotsInputSchema,
|
|
3
|
-
CaptureSerpPageSnapshotsOutputSchema,
|
|
4
|
-
CaptureSerpSnapshotInputSchema,
|
|
5
|
-
CaptureSerpSnapshotOutputSchema,
|
|
6
2
|
HttpMcpToolExecutor,
|
|
7
3
|
auditImages,
|
|
8
4
|
buildLinkGraph,
|
|
@@ -11,17 +7,16 @@ import {
|
|
|
11
7
|
computeIssues,
|
|
12
8
|
configureReportSaving,
|
|
13
9
|
exportFanout,
|
|
14
|
-
formatCaptureSerpPageSnapshots,
|
|
15
|
-
formatCaptureSerpSnapshot,
|
|
16
10
|
harvestTimeoutBudget,
|
|
17
|
-
liveWebToolAnnotations,
|
|
18
11
|
outputBaseDir,
|
|
12
|
+
registerBrowserAgentMcpTools,
|
|
13
|
+
registerSerpIntelligenceCaptureTools,
|
|
19
14
|
renderImageSection,
|
|
20
15
|
renderIssueReport,
|
|
21
16
|
renderLinkReport,
|
|
22
17
|
sanitizeAttempts,
|
|
23
18
|
sanitizeHarvestResult
|
|
24
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-NGD4ITAO.js";
|
|
25
20
|
import {
|
|
26
21
|
csvRecords,
|
|
27
22
|
listWorkflowDefinitions,
|
|
@@ -34,7 +29,7 @@ import {
|
|
|
34
29
|
workflowStepCount,
|
|
35
30
|
workflowSupportsSteps
|
|
36
31
|
} from "./chunk-NNEIXK5L.js";
|
|
37
|
-
import "./chunk-
|
|
32
|
+
import "./chunk-7MY2NSQX.js";
|
|
38
33
|
import {
|
|
39
34
|
BROWSER_OPEN_MIN_BALANCE_MC,
|
|
40
35
|
CONCURRENCY_PRICE_ID,
|
|
@@ -54,7 +49,7 @@ import {
|
|
|
54
49
|
harvestProblemResponse,
|
|
55
50
|
insufficientBalanceResponse,
|
|
56
51
|
serializeHarvestProblem
|
|
57
|
-
} from "./chunk-
|
|
52
|
+
} from "./chunk-H74L743B.js";
|
|
58
53
|
import {
|
|
59
54
|
BrowserDriver,
|
|
60
55
|
MapsSelectors,
|
|
@@ -67,7 +62,7 @@ import {
|
|
|
67
62
|
recordVendorUsage,
|
|
68
63
|
resolveKernelProxyId,
|
|
69
64
|
runWithCostContext
|
|
70
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-QSSH4RCX.js";
|
|
71
66
|
import {
|
|
72
67
|
CaptchaError,
|
|
73
68
|
RECAPTCHA_INSTRUCTIONS,
|
|
@@ -5261,7 +5256,7 @@ async function extractSite(opts) {
|
|
|
5261
5256
|
}
|
|
5262
5257
|
|
|
5263
5258
|
// src/api/server.ts
|
|
5264
|
-
import { Hono as
|
|
5259
|
+
import { Hono as Hono16 } from "hono";
|
|
5265
5260
|
import { serve as serveInngest } from "inngest/hono";
|
|
5266
5261
|
|
|
5267
5262
|
// src/inngest/client.ts
|
|
@@ -14500,22 +14495,6 @@ async function requireMcpCallerKey(c) {
|
|
|
14500
14495
|
return callerKey;
|
|
14501
14496
|
}
|
|
14502
14497
|
var mcpApp = new Hono11();
|
|
14503
|
-
function registerSerpIntelligenceCaptureTools(server, executor) {
|
|
14504
|
-
server.registerTool("capture_serp_snapshot", {
|
|
14505
|
-
title: "SERP Intelligence Snapshot",
|
|
14506
|
-
description: "Capture a structured SERP Intelligence Google snapshot through POST /serp-intelligence/capture, the same product capture path used by Phoenix. Split query from location and infer gl/hl. Routing is automatic \u2014 leave proxyMode unset. Set debug true when investigating location evidence, proxy behavior, CAPTCHA, or capture reliability.",
|
|
14507
|
-
inputSchema: CaptureSerpSnapshotInputSchema,
|
|
14508
|
-
outputSchema: CaptureSerpSnapshotOutputSchema,
|
|
14509
|
-
annotations: liveWebToolAnnotations("SERP Intelligence Snapshot")
|
|
14510
|
-
}, async (input) => formatCaptureSerpSnapshot(await executor.captureSerpSnapshot(input), input));
|
|
14511
|
-
server.registerTool("capture_serp_page_snapshots", {
|
|
14512
|
-
title: "SERP Intelligence Page Snapshots",
|
|
14513
|
-
description: "Capture public ranking-page evidence through POST /serp-intelligence/page-snapshots, the same product page snapshot path used by Phoenix. Provide urls for simple captures or targets when preserving organic, AI citation, local-pack, configured target, or site-subject source metadata. Private IPs, localhost, file URLs, and internal URLs are rejected by the service. Use timeoutMs for slow pages and debug true for sanitized proxy/browser diagnostics.",
|
|
14514
|
-
inputSchema: CaptureSerpPageSnapshotsInputSchema,
|
|
14515
|
-
outputSchema: CaptureSerpPageSnapshotsOutputSchema,
|
|
14516
|
-
annotations: liveWebToolAnnotations("SERP Intelligence Page Snapshots")
|
|
14517
|
-
}, async (input) => formatCaptureSerpPageSnapshots(await executor.captureSerpPageSnapshots(input), input));
|
|
14518
|
-
}
|
|
14519
14498
|
mcpApp.all("/", async (c) => {
|
|
14520
14499
|
try {
|
|
14521
14500
|
const keyOrError = await requireMcpCallerKey(c);
|
|
@@ -14527,8 +14506,10 @@ mcpApp.all("/", async (c) => {
|
|
|
14527
14506
|
sessionIdGenerator: void 0,
|
|
14528
14507
|
enableJsonResponse: true
|
|
14529
14508
|
});
|
|
14509
|
+
const consoleBaseUrl = process.env.BROWSER_AGENT_CONSOLE_URL?.trim() || baseUrl;
|
|
14530
14510
|
const server = buildPaaExtractorMcpServer(executor, { savesReportsLocally: false });
|
|
14531
14511
|
registerSerpIntelligenceCaptureTools(server, executor);
|
|
14512
|
+
registerBrowserAgentMcpTools(server, { baseUrl, apiKey: callerKey, consoleBaseUrl });
|
|
14532
14513
|
await server.connect(transport);
|
|
14533
14514
|
return transport.handleRequest(c.req.raw);
|
|
14534
14515
|
} catch {
|
|
@@ -16746,7 +16727,7 @@ function decryptMemoryKey(stored) {
|
|
|
16746
16727
|
}
|
|
16747
16728
|
async function memoryCall(toolName, args, userMemoryKey) {
|
|
16748
16729
|
try {
|
|
16749
|
-
const res = await fetch(`${MEMORY_BASE_URL()}/api/mcp/
|
|
16730
|
+
const res = await fetch(`${MEMORY_BASE_URL()}/api/mcp/memoryServer/tools/${toolName}/execute`, {
|
|
16750
16731
|
method: "POST",
|
|
16751
16732
|
headers: { "content-type": "application/json" },
|
|
16752
16733
|
body: JSON.stringify({ data: { ...args, apiKey: userMemoryKey } })
|
|
@@ -16910,7 +16891,7 @@ import { getCookie, setCookie } from "hono/cookie";
|
|
|
16910
16891
|
import { createHash as createHash3, randomBytes as randomBytes2, randomUUID as randomUUID3 } from "crypto";
|
|
16911
16892
|
import { importPKCS8, exportJWK, SignJWT } from "jose";
|
|
16912
16893
|
var ISSUER = "https://mcpscraper.dev";
|
|
16913
|
-
var RESOURCE = () => process.env.MCP_MEMORY_RESOURCE_URL ?? "https://
|
|
16894
|
+
var RESOURCE = () => process.env.MCP_MEMORY_RESOURCE_URL ?? "https://memory.mcpscraper.dev/mcp";
|
|
16914
16895
|
var SCRAPER_RESOURCE = () => (process.env.MCP_SCRAPER_RESOURCE_URL ?? "https://mcpscraper.dev/mcp").replace(/\/$/, "");
|
|
16915
16896
|
var MEMORY_SCOPES = ["memory.read", "memory.write", "memory.admin"];
|
|
16916
16897
|
var SCRAPER_SCOPES = ["scraper.use"];
|
|
@@ -17315,6 +17296,677 @@ oauthApp.post("/oauth/token", async (c) => {
|
|
|
17315
17296
|
return tokenErrorResponse(c, "unsupported_grant_type", "grant_type must be authorization_code or refresh_token", 400);
|
|
17316
17297
|
});
|
|
17317
17298
|
|
|
17299
|
+
// src/api/chat-routes.ts
|
|
17300
|
+
import { Hono as Hono15 } from "hono";
|
|
17301
|
+
var chatApp = new Hono15();
|
|
17302
|
+
function tokenOk(token) {
|
|
17303
|
+
return !!token && token.startsWith("mk_") && token.length > 10;
|
|
17304
|
+
}
|
|
17305
|
+
function jsonError(message, status = 400) {
|
|
17306
|
+
return new Response(JSON.stringify({ ok: false, error: message }), { status, headers: { "content-type": "application/json" } });
|
|
17307
|
+
}
|
|
17308
|
+
chatApp.get("/api/channels", async (c) => {
|
|
17309
|
+
const token = c.req.query("token");
|
|
17310
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17311
|
+
const r = await memoryCall("listVaultsTool", {}, token);
|
|
17312
|
+
if (!r.ok) return c.json(r);
|
|
17313
|
+
const vaults = (r.vaults ?? []).filter((v) => v.kind === "channel");
|
|
17314
|
+
return c.json({ ok: true, channels: vaults });
|
|
17315
|
+
});
|
|
17316
|
+
chatApp.get("/api/messages", async (c) => {
|
|
17317
|
+
const token = c.req.query("token");
|
|
17318
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17319
|
+
const vault = c.req.query("vault");
|
|
17320
|
+
if (!vault) return jsonError("vault is required");
|
|
17321
|
+
const parentMessageId = c.req.query("parentMessageId") || void 0;
|
|
17322
|
+
const r = await memoryCall("listChannelMessagesTool", { vault, parentMessageId }, token);
|
|
17323
|
+
return c.json(r);
|
|
17324
|
+
});
|
|
17325
|
+
chatApp.get("/api/poll", async (c) => {
|
|
17326
|
+
const token = c.req.query("token");
|
|
17327
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17328
|
+
const vault = c.req.query("vault");
|
|
17329
|
+
if (!vault) return jsonError("vault is required");
|
|
17330
|
+
const r = await memoryCall("pollChannelTool", { vault }, token);
|
|
17331
|
+
return c.json(r);
|
|
17332
|
+
});
|
|
17333
|
+
function parseAttachNote(v) {
|
|
17334
|
+
if (!v || typeof v !== "object") return void 0;
|
|
17335
|
+
const a = v;
|
|
17336
|
+
return a.vault && a.path ? { vault: a.vault, path: a.path } : void 0;
|
|
17337
|
+
}
|
|
17338
|
+
chatApp.post("/api/post", async (c) => {
|
|
17339
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17340
|
+
if (!tokenOk(body.token)) return jsonError("missing or malformed token", 401);
|
|
17341
|
+
if (!body.vault || !body.content) return jsonError("vault and content are required");
|
|
17342
|
+
const attachNote = parseAttachNote(body.attachNote);
|
|
17343
|
+
const r = await memoryCall("postMessageTool", { vault: body.vault, content: body.content, ...attachNote ? { attachNote } : {} }, body.token);
|
|
17344
|
+
return c.json(r);
|
|
17345
|
+
});
|
|
17346
|
+
chatApp.post("/api/reply", async (c) => {
|
|
17347
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17348
|
+
if (!tokenOk(body.token)) return jsonError("missing or malformed token", 401);
|
|
17349
|
+
if (!body.vault || !body.parentMessageId || !body.content) return jsonError("vault, parentMessageId and content are required");
|
|
17350
|
+
const attachNote = parseAttachNote(body.attachNote);
|
|
17351
|
+
const r = await memoryCall("replyMessageTool", { vault: body.vault, parentMessageId: body.parentMessageId, content: body.content, ...attachNote ? { attachNote } : {} }, body.token);
|
|
17352
|
+
return c.json(r);
|
|
17353
|
+
});
|
|
17354
|
+
chatApp.get("/api/notes-own", async (c) => {
|
|
17355
|
+
const token = c.req.query("token");
|
|
17356
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17357
|
+
const vaultsRes = await memoryCall("listVaultsTool", {}, token);
|
|
17358
|
+
if (!vaultsRes.ok) return c.json(vaultsRes);
|
|
17359
|
+
const ownVaults = (vaultsRes.vaults ?? []).filter((v) => v.role === "owner" && v.kind === "notes");
|
|
17360
|
+
const lists = await Promise.all(ownVaults.map((v) => memoryCall("listTool", { vault: v.handle }, token)));
|
|
17361
|
+
const notes = ownVaults.flatMap((v, i) => {
|
|
17362
|
+
const r = lists[i];
|
|
17363
|
+
return r.ok ? (r.notes ?? []).map((n) => ({ vault: v.handle, path: n.path, title: n.title })) : [];
|
|
17364
|
+
});
|
|
17365
|
+
return c.json({ ok: true, notes });
|
|
17366
|
+
});
|
|
17367
|
+
chatApp.get("/api/message-note", async (c) => {
|
|
17368
|
+
const token = c.req.query("token");
|
|
17369
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17370
|
+
const vault = c.req.query("vault");
|
|
17371
|
+
const messageId = c.req.query("messageId");
|
|
17372
|
+
if (!vault || !messageId) return jsonError("vault and messageId are required");
|
|
17373
|
+
const r = await memoryCall("getMessageNoteTool", { vault, messageId }, token);
|
|
17374
|
+
return c.json(r);
|
|
17375
|
+
});
|
|
17376
|
+
chatApp.post("/api/react", async (c) => {
|
|
17377
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17378
|
+
if (!tokenOk(body.token)) return jsonError("missing or malformed token", 401);
|
|
17379
|
+
if (!body.vault || !body.messageId || !body.emoji) return jsonError("vault, messageId and emoji are required");
|
|
17380
|
+
const r = await memoryCall("reactMessageTool", { vault: body.vault, messageId: body.messageId, emoji: body.emoji, remove: !!body.remove }, body.token);
|
|
17381
|
+
return c.json(r);
|
|
17382
|
+
});
|
|
17383
|
+
chatApp.get("/api/mentions", async (c) => {
|
|
17384
|
+
const token = c.req.query("token");
|
|
17385
|
+
if (!tokenOk(token)) return jsonError("missing or malformed token", 401);
|
|
17386
|
+
const r = await memoryCall("myMentionsTool", {}, token);
|
|
17387
|
+
return c.json(r);
|
|
17388
|
+
});
|
|
17389
|
+
chatApp.get("/api/members", 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
|
+
if (!vault) return jsonError("vault is required");
|
|
17394
|
+
const r = await memoryCall("listChannelMembersTool", { vault }, token);
|
|
17395
|
+
return c.json(r);
|
|
17396
|
+
});
|
|
17397
|
+
chatApp.post("/api/channel", async (c) => {
|
|
17398
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17399
|
+
if (!tokenOk(body.token)) return jsonError("missing or malformed token", 401);
|
|
17400
|
+
if (!body.name) return jsonError("name is required");
|
|
17401
|
+
const inviteEmails = (body.inviteEmails ?? []).filter(Boolean).slice(0, 99);
|
|
17402
|
+
const r = await memoryCall(
|
|
17403
|
+
"createChannelTool",
|
|
17404
|
+
{ name: body.name, inviteMembers: inviteEmails.map((identity) => ({ identity })) },
|
|
17405
|
+
body.token
|
|
17406
|
+
);
|
|
17407
|
+
return c.json(r);
|
|
17408
|
+
});
|
|
17409
|
+
chatApp.post("/api/invite", async (c) => {
|
|
17410
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17411
|
+
if (!tokenOk(body.token)) return jsonError("missing or malformed token", 401);
|
|
17412
|
+
if (!body.vault || !body.email) return jsonError("vault and email are required");
|
|
17413
|
+
const scopeMap = {
|
|
17414
|
+
read: { read: true },
|
|
17415
|
+
write: { read: true, write: true },
|
|
17416
|
+
admin: { read: true, write: true, admin: true }
|
|
17417
|
+
};
|
|
17418
|
+
const scope = scopeMap[body.scope ?? "write"] ?? scopeMap.write;
|
|
17419
|
+
const r = await memoryCall("shareVaultTool", { vault: body.vault, granteeIdentity: body.email, scope }, body.token);
|
|
17420
|
+
return c.json(r);
|
|
17421
|
+
});
|
|
17422
|
+
chatApp.post("/api/member-remove", async (c) => {
|
|
17423
|
+
const body = await c.req.json().catch(() => ({}));
|
|
17424
|
+
if (!tokenOk(body.token)) return jsonError("missing or malformed token", 401);
|
|
17425
|
+
if (!body.vault || !body.identity) return jsonError("vault and identity are required");
|
|
17426
|
+
const r = await memoryCall("removeChannelMemberTool", { vault: body.vault, identity: body.identity }, body.token);
|
|
17427
|
+
return c.json(r);
|
|
17428
|
+
});
|
|
17429
|
+
chatApp.get("/:token", (c) => {
|
|
17430
|
+
const token = c.req.param("token");
|
|
17431
|
+
if (!tokenOk(token)) {
|
|
17432
|
+
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);
|
|
17433
|
+
}
|
|
17434
|
+
return c.html(renderChatPage(token));
|
|
17435
|
+
});
|
|
17436
|
+
function renderChatPage(token) {
|
|
17437
|
+
return `<!doctype html>
|
|
17438
|
+
<html>
|
|
17439
|
+
<head>
|
|
17440
|
+
<meta charset="utf-8">
|
|
17441
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
17442
|
+
<title>Chat Everywhere</title>
|
|
17443
|
+
<style>
|
|
17444
|
+
:root { color-scheme: light }
|
|
17445
|
+
* { box-sizing: border-box }
|
|
17446
|
+
body { margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif; color: #1B1523; background: #EDEAF2; }
|
|
17447
|
+
[id="app"] { display: flex; height: 100vh; }
|
|
17448
|
+
[id="sidebar"] { width: 264px; flex-shrink: 0; display: flex; flex-direction: column; background: #2B0E36; color: #fff; }
|
|
17449
|
+
[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; }
|
|
17450
|
+
[id="sidebar"] .section-label { font:700 11px system-ui; color:#B79FC4; letter-spacing:.05em; text-transform:uppercase; padding:14px 14px 4px; }
|
|
17451
|
+
[id="channel-list"], [id="dm-list"] { padding: 0 8px; }
|
|
17452
|
+
.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; }
|
|
17453
|
+
.chan-row:hover, .chan-row.active { background: rgba(255,255,255,.08); color:#fff; }
|
|
17454
|
+
.badge { background:#FF6F91; color:#fff; font:700 10.5px system-ui; padding:1px 6px; border-radius:999px; margin-left:auto; }
|
|
17455
|
+
[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; }
|
|
17456
|
+
[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; }
|
|
17457
|
+
[id="main"] { flex: 1; display:flex; flex-direction:column; background:#fff; min-width:0; }
|
|
17458
|
+
[id="chan-header"] { display:flex; align-items:center; justify-content:space-between; padding:14px 20px; border-bottom:1px solid #E6DEEA; }
|
|
17459
|
+
[id="chan-title"] { font:700 16px system-ui; }
|
|
17460
|
+
[id="manage-btn"] { background:transparent; border:1px solid #E6DEEA; border-radius:999px; padding:5px 12px; font:600 12px system-ui; color:#6B6275; cursor:pointer; }
|
|
17461
|
+
[id="messages"] { flex:1; overflow:auto; padding:10px 8px; }
|
|
17462
|
+
.msg { display:flex; gap:10px; padding:10px 16px; border-bottom:1px solid #F0ECF3; }
|
|
17463
|
+
.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; }
|
|
17464
|
+
.msg-body { flex:1; min-width:0; position:relative; }
|
|
17465
|
+
.msg { position:relative; }
|
|
17466
|
+
.msg-head { display:flex; align-items:center; gap:6px; flex-wrap:wrap; }
|
|
17467
|
+
.msg-author { font:600 14px system-ui; }
|
|
17468
|
+
.agent-pill { font:700 9px system-ui; letter-spacing:.05em; color:#fff; background:#0E8074; padding:2px 6px; border-radius:4px; }
|
|
17469
|
+
.msg-ts { font:400 12px system-ui; color:#A89BB5; }
|
|
17470
|
+
.msg-content { font-size:15px; line-height:1.5; color:#2B2433; white-space:pre-wrap; margin-top:2px; }
|
|
17471
|
+
.reactions { display:flex; gap:6px; margin-top:7px; flex-wrap:wrap; align-items:center; }
|
|
17472
|
+
.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; }
|
|
17473
|
+
.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; }
|
|
17474
|
+
.seen { display:flex; align-items:center; gap:5px; margin-top:6px; font-size:11px; color:#A89BB5; }
|
|
17475
|
+
.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; }
|
|
17476
|
+
.msg:hover .msg-actions, .msg-actions.pinned-open { opacity:1; pointer-events:auto; }
|
|
17477
|
+
.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; }
|
|
17478
|
+
.icon-btn:hover { background:#F2EEF5; color:#6B3FA0; }
|
|
17479
|
+
.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; }
|
|
17480
|
+
.emoji-popover.open { display:flex; }
|
|
17481
|
+
.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; }
|
|
17482
|
+
.emoji-opt:hover { background:#F2EEF5; }
|
|
17483
|
+
[id="composer-row"] { padding:12px 20px 16px; border-top:1px solid #E6DEEA; }
|
|
17484
|
+
[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; }
|
|
17485
|
+
[id="composer"]:focus { border-color:#6B3FA0; }
|
|
17486
|
+
[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; }
|
|
17487
|
+
[id="thread-panel"] { width:340px; flex-shrink:0; border-left:1px solid #E6DEEA; display:none; flex-direction:column; background:#FCFBFD; }
|
|
17488
|
+
[id="thread-panel"].open { display:flex; }
|
|
17489
|
+
[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; }
|
|
17490
|
+
[id="thread-close"] { background:transparent; border:none; cursor:pointer; font:600 16px system-ui; color:#948C9C; }
|
|
17491
|
+
[id="thread-messages"] { flex:1; overflow:auto; }
|
|
17492
|
+
[id="thread-composer-row"] { padding:12px 16px 16px; border-top:1px solid #E6DEEA; }
|
|
17493
|
+
[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; }
|
|
17494
|
+
.overlay { position:fixed; inset:0; background:rgba(27,21,35,.42); display:none; align-items:center; justify-content:center; z-index:50; }
|
|
17495
|
+
.overlay.open { display:flex; }
|
|
17496
|
+
.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; }
|
|
17497
|
+
.modal-head { display:flex; align-items:center; justify-content:space-between; padding:16px 18px; border-bottom:1px solid #E6DEEA; font:700 15px system-ui; }
|
|
17498
|
+
.modal-body { padding:16px 18px; overflow:auto; flex:1; }
|
|
17499
|
+
.modal-foot { padding:14px 18px; border-top:1px solid #E6DEEA; display:flex; justify-content:flex-end; gap:8px; }
|
|
17500
|
+
.modal label { font:600 12px system-ui; color:#6B6275; display:block; margin-bottom:6px; margin-top: 12px; }
|
|
17501
|
+
.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; }
|
|
17502
|
+
.btn { background:transparent; border:1px solid #E6DEEA; border-radius:7px; padding:8px 16px; font:600 13px system-ui; cursor:pointer; color:#6B6275; }
|
|
17503
|
+
.btn-primary { background:#6B3FA0; color:#fff; border:none; border-radius:7px; padding:8px 18px; font:600 13px system-ui; cursor:pointer; }
|
|
17504
|
+
.member-row { display:flex; align-items:center; gap:9px; padding:6px 4px; font:500 13.5px system-ui; }
|
|
17505
|
+
.member-row .x { margin-left:auto; background:transparent; border:none; cursor:pointer; color:#C2BACB; font:600 14px system-ui; }
|
|
17506
|
+
.empty { padding: 40px; text-align:center; color:#948C9C; font:14px system-ui; }
|
|
17507
|
+
.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; }
|
|
17508
|
+
.note-chip:hover { background:#F2EEF5; }
|
|
17509
|
+
[id="composer-row"] { position:relative; }
|
|
17510
|
+
.attach-chip-row { display:none; align-items:center; gap:6px; margin-bottom:6px; font:500 12.5px system-ui; color:#6B3FA0; }
|
|
17511
|
+
.attach-chip-row.show { display:flex; }
|
|
17512
|
+
.attach-chip-row button { background:transparent; border:none; cursor:pointer; color:#B7ADC2; font:600 13px system-ui; }
|
|
17513
|
+
[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; }
|
|
17514
|
+
[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; }
|
|
17515
|
+
[id="note-picker-search"] { margin-bottom:10px; }
|
|
17516
|
+
.note-pick-row { padding:8px 6px; border-radius:7px; cursor:pointer; font:500 13.5px system-ui; color:#3D3548; }
|
|
17517
|
+
.note-pick-row:hover { background:#F2EEF5; }
|
|
17518
|
+
.note-pick-row .vault-tag { color:#A89BB5; font-size:11.5px; margin-left:6px; }
|
|
17519
|
+
[id="note-card-body"] { font-size:14.5px; line-height:1.55; color:#2B2433; white-space:pre-wrap; }
|
|
17520
|
+
</style>
|
|
17521
|
+
</head>
|
|
17522
|
+
<body>
|
|
17523
|
+
<div id="app">
|
|
17524
|
+
<div id="sidebar">
|
|
17525
|
+
<div class="brand">Chat Everywhere</div>
|
|
17526
|
+
<button id="mentions-btn">@ Mentions <span id="mentions-badge" class="badge" style="display:none"></span></button>
|
|
17527
|
+
<div class="section-label">Channels</div>
|
|
17528
|
+
<div id="channel-list"></div>
|
|
17529
|
+
<button id="create-btn">+ New channel</button>
|
|
17530
|
+
</div>
|
|
17531
|
+
<div id="main">
|
|
17532
|
+
<div id="chan-header" style="display:none">
|
|
17533
|
+
<div id="chan-title"></div>
|
|
17534
|
+
<button id="manage-btn">Members</button>
|
|
17535
|
+
</div>
|
|
17536
|
+
<div id="messages"><div class="empty">Pick a channel on the left.</div></div>
|
|
17537
|
+
<div id="composer-row" style="display:none">
|
|
17538
|
+
<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>
|
|
17539
|
+
<textarea id="composer" placeholder="Message... @ to mention someone"></textarea>
|
|
17540
|
+
<button id="send-btn">Send</button>
|
|
17541
|
+
<button id="attach-btn" title="Attach a note">\u{1F4CE} Attach note</button>
|
|
17542
|
+
<div style="clear:both"></div>
|
|
17543
|
+
</div>
|
|
17544
|
+
</div>
|
|
17545
|
+
<div id="thread-panel">
|
|
17546
|
+
<div id="thread-header"><span>Thread</span><button id="thread-close">x</button></div>
|
|
17547
|
+
<div id="thread-messages"></div>
|
|
17548
|
+
<div id="thread-composer-row">
|
|
17549
|
+
<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>
|
|
17550
|
+
<textarea id="thread-composer" placeholder="Reply in thread"></textarea>
|
|
17551
|
+
<button id="thread-send-btn" class="btn-primary" style="margin-top:7px;float:right">Reply</button>
|
|
17552
|
+
<button id="thread-attach-btn" title="Attach a note">\u{1F4CE} Attach</button>
|
|
17553
|
+
<div style="clear:both"></div>
|
|
17554
|
+
</div>
|
|
17555
|
+
</div>
|
|
17556
|
+
</div>
|
|
17557
|
+
|
|
17558
|
+
<div id="note-picker-overlay" class="overlay">
|
|
17559
|
+
<div class="modal">
|
|
17560
|
+
<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>
|
|
17561
|
+
<div class="modal-body">
|
|
17562
|
+
<input id="note-picker-search" placeholder="Search your notes by title or path..." />
|
|
17563
|
+
<div id="note-picker-list"></div>
|
|
17564
|
+
</div>
|
|
17565
|
+
</div>
|
|
17566
|
+
</div>
|
|
17567
|
+
|
|
17568
|
+
<div id="note-card-overlay" class="overlay">
|
|
17569
|
+
<div class="modal">
|
|
17570
|
+
<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>
|
|
17571
|
+
<div class="modal-body"><div id="note-card-body"></div></div>
|
|
17572
|
+
</div>
|
|
17573
|
+
</div>
|
|
17574
|
+
|
|
17575
|
+
<div id="mentions-overlay" class="overlay">
|
|
17576
|
+
<div class="modal">
|
|
17577
|
+
<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>
|
|
17578
|
+
<div id="mentions-list" class="modal-body"></div>
|
|
17579
|
+
</div>
|
|
17580
|
+
</div>
|
|
17581
|
+
|
|
17582
|
+
<div id="create-overlay" class="overlay">
|
|
17583
|
+
<div class="modal">
|
|
17584
|
+
<div class="modal-head">New channel</div>
|
|
17585
|
+
<div class="modal-body">
|
|
17586
|
+
<label>Channel name</label>
|
|
17587
|
+
<input id="new-channel-name" placeholder="e.g. fundraise-q3" />
|
|
17588
|
+
<label>Invite by email (comma-separated, optional)</label>
|
|
17589
|
+
<input id="new-channel-invites" placeholder="andrew@x.com, seo@y.com" />
|
|
17590
|
+
<div id="create-error" style="color:#C2410C;font:13px system-ui;margin-top:8px;display:none"></div>
|
|
17591
|
+
</div>
|
|
17592
|
+
<div class="modal-foot">
|
|
17593
|
+
<button class="btn" id="create-cancel">Cancel</button>
|
|
17594
|
+
<button class="btn-primary" id="create-submit">Create</button>
|
|
17595
|
+
</div>
|
|
17596
|
+
</div>
|
|
17597
|
+
</div>
|
|
17598
|
+
|
|
17599
|
+
<div id="manage-overlay" class="overlay">
|
|
17600
|
+
<div class="modal">
|
|
17601
|
+
<div class="modal-head">Members</div>
|
|
17602
|
+
<div class="modal-body">
|
|
17603
|
+
<div id="member-list"></div>
|
|
17604
|
+
<label>Invite someone</label>
|
|
17605
|
+
<input id="invite-email" placeholder="email" />
|
|
17606
|
+
<select id="invite-scope" style="margin-top:8px">
|
|
17607
|
+
<option value="write">Write - can post</option>
|
|
17608
|
+
<option value="read">Read - view only</option>
|
|
17609
|
+
<option value="admin">Admin - manage members</option>
|
|
17610
|
+
</select>
|
|
17611
|
+
<div id="invite-error" style="color:#C2410C;font:13px system-ui;margin-top:8px;display:none"></div>
|
|
17612
|
+
</div>
|
|
17613
|
+
<div class="modal-foot">
|
|
17614
|
+
<button class="btn-primary" id="invite-submit">Invite</button>
|
|
17615
|
+
<button class="btn" id="manage-close">Done</button>
|
|
17616
|
+
</div>
|
|
17617
|
+
</div>
|
|
17618
|
+
</div>
|
|
17619
|
+
|
|
17620
|
+
<script>
|
|
17621
|
+
const TOKEN = ${JSON.stringify(token)};
|
|
17622
|
+
const state = { channels: [], activeVault: null, activeName: null, members: [], threadParent: null, pollTimer: null, pendingAttach: null, threadPendingAttach: null, ownNotes: null, pickerTarget: 'main' };
|
|
17623
|
+
|
|
17624
|
+
function api(path, opts) {
|
|
17625
|
+
const url = '/chat/api/' + path;
|
|
17626
|
+
return fetch(url, opts).then(r => r.json());
|
|
17627
|
+
}
|
|
17628
|
+
function get(path, params) {
|
|
17629
|
+
const qs = new URLSearchParams({ token: TOKEN, ...params }).toString();
|
|
17630
|
+
return api(path + '?' + qs);
|
|
17631
|
+
}
|
|
17632
|
+
function post(path, body) {
|
|
17633
|
+
return api(path, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ token: TOKEN, ...body }) });
|
|
17634
|
+
}
|
|
17635
|
+
|
|
17636
|
+
function colorFor(email) {
|
|
17637
|
+
let h = 0;
|
|
17638
|
+
for (let i = 0; i < email.length; i++) h = (h * 31 + email.charCodeAt(i)) % 360;
|
|
17639
|
+
return 'hsl(' + h + ', 55%, 45%)';
|
|
17640
|
+
}
|
|
17641
|
+
function initialsFor(email) {
|
|
17642
|
+
const local = (email || '?').split('@')[0];
|
|
17643
|
+
return (local.slice(0, 2) || '??').toUpperCase();
|
|
17644
|
+
}
|
|
17645
|
+
function esc(s) {
|
|
17646
|
+
return (s || '').replace(/[&<>]/g, c => ({ '&': '&', '<': '<', '>': '>' }[c]));
|
|
17647
|
+
}
|
|
17648
|
+
function fmtTime(iso) {
|
|
17649
|
+
try { return new Date(iso).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' }); } catch (e) { return ''; }
|
|
17650
|
+
}
|
|
17651
|
+
|
|
17652
|
+
async function loadChannels() {
|
|
17653
|
+
const r = await get('channels', {});
|
|
17654
|
+
if (!r.ok) { document.getElementById('channel-list').innerHTML = '<div class="empty">' + esc(r.error || 'failed to load') + '</div>'; return; }
|
|
17655
|
+
state.channels = r.channels || [];
|
|
17656
|
+
const list = document.getElementById('channel-list');
|
|
17657
|
+
list.innerHTML = '';
|
|
17658
|
+
state.channels.forEach(ch => {
|
|
17659
|
+
const btn = document.createElement('button');
|
|
17660
|
+
btn.className = 'chan-row' + (ch.handle === state.activeVault ? ' active' : '');
|
|
17661
|
+
btn.textContent = '# ' + ch.vault;
|
|
17662
|
+
btn.onclick = () => openChannel(ch.handle, ch.vault);
|
|
17663
|
+
list.appendChild(btn);
|
|
17664
|
+
});
|
|
17665
|
+
if (!state.channels.length) list.innerHTML = '<div class="empty" style="color:#B79FC4">No channels yet</div>';
|
|
17666
|
+
}
|
|
17667
|
+
|
|
17668
|
+
async function openChannel(handle, name) {
|
|
17669
|
+
state.activeVault = handle;
|
|
17670
|
+
state.activeName = name;
|
|
17671
|
+
closeThread();
|
|
17672
|
+
document.querySelectorAll('.chan-row').forEach(b => b.classList.toggle('active', b.textContent === '# ' + name));
|
|
17673
|
+
document.getElementById('chan-header').style.display = 'flex';
|
|
17674
|
+
document.getElementById('chan-title').textContent = '#' + name;
|
|
17675
|
+
document.getElementById('composer-row').style.display = 'block';
|
|
17676
|
+
await loadMembers();
|
|
17677
|
+
await loadMessages();
|
|
17678
|
+
restartPolling();
|
|
17679
|
+
}
|
|
17680
|
+
|
|
17681
|
+
const QUICK_EMOJI = ['\u{1F44D}', '\u2764\uFE0F', '\u{1F602}', '\u{1F389}', '\u{1F440}', '\u2705'];
|
|
17682
|
+
|
|
17683
|
+
function renderMessage(m, container, compact) {
|
|
17684
|
+
const div = document.createElement('div');
|
|
17685
|
+
div.className = 'msg';
|
|
17686
|
+
const author = m.authorIdentity || 'unknown';
|
|
17687
|
+
div.innerHTML =
|
|
17688
|
+
'<div class="avatar" style="background:' + colorFor(author) + (compact ? ';width:28px;height:28px' : '') + '">' + initialsFor(author) + '</div>' +
|
|
17689
|
+
'<div class="msg-body">' +
|
|
17690
|
+
'<div class="msg-head"><span class="msg-author">' + esc(author) + '</span>' +
|
|
17691
|
+
(m.authorIsAgent ? '<span class="agent-pill">AGENT</span>' : '') +
|
|
17692
|
+
'<span class="msg-ts">' + fmtTime(m.postedAt) + '</span></div>' +
|
|
17693
|
+
(!compact ? '<span class="msg-actions">' +
|
|
17694
|
+
'<button class="icon-btn reply-icon" title="Reply">\u{1F4AC}</button>' +
|
|
17695
|
+
'<button class="icon-btn react-icon" title="Add reaction">\u{1F642}</button>' +
|
|
17696
|
+
'</span>' : '') +
|
|
17697
|
+
'<div class="msg-content"></div>' +
|
|
17698
|
+
(m.attachedNote ? '<button class="note-chip">\u{1F4CE} ' + esc(m.attachedNote.title) + '</button>' : '') +
|
|
17699
|
+
(m.reactions && m.reactions.length ? '<div class="reactions"></div>' : '') +
|
|
17700
|
+
(!compact ? '<button class="reply-summary" style="' + (m.replyCount ? '' : 'display:none') + '">' + (m.replyCount || 0) + ' replies' + (m.lastReplyAt ? ' - last ' + fmtTime(m.lastReplyAt) : '') + '</button>' : '') +
|
|
17701
|
+
(!compact ? '<div class="emoji-popover"></div>' : '') +
|
|
17702
|
+
(m.readBy && m.readBy.length ? '<div class="seen">seen by ' + m.readBy.length + '</div>' : '') +
|
|
17703
|
+
'</div>';
|
|
17704
|
+
div.querySelector('.msg-content').textContent = m.content;
|
|
17705
|
+
const noteChip = div.querySelector('.note-chip');
|
|
17706
|
+
if (noteChip) noteChip.onclick = () => openNoteCard(state.activeVault, m.messageId);
|
|
17707
|
+
if (m.reactions && m.reactions.length) {
|
|
17708
|
+
const counts = {};
|
|
17709
|
+
m.reactions.forEach(r => { counts[r.emoji] = (counts[r.emoji] || 0) + 1; });
|
|
17710
|
+
const rdiv = div.querySelector('.reactions');
|
|
17711
|
+
Object.entries(counts).forEach(([emoji, count]) => {
|
|
17712
|
+
const b = document.createElement('button');
|
|
17713
|
+
b.className = 'reaction';
|
|
17714
|
+
b.textContent = emoji + ' ' + count;
|
|
17715
|
+
b.onclick = () => react(m.messageId, emoji);
|
|
17716
|
+
rdiv.appendChild(b);
|
|
17717
|
+
});
|
|
17718
|
+
}
|
|
17719
|
+
if (!compact) {
|
|
17720
|
+
const replyBtn = div.querySelector('.reply-summary');
|
|
17721
|
+
if (replyBtn) replyBtn.onclick = () => openThread(m.messageId);
|
|
17722
|
+
const replyIcon = div.querySelector('.reply-icon');
|
|
17723
|
+
if (replyIcon) replyIcon.onclick = () => openThread(m.messageId);
|
|
17724
|
+
const reactIcon = div.querySelector('.react-icon');
|
|
17725
|
+
const popover = div.querySelector('.emoji-popover');
|
|
17726
|
+
if (reactIcon && popover) {
|
|
17727
|
+
QUICK_EMOJI.forEach(emoji => {
|
|
17728
|
+
const b = document.createElement('button');
|
|
17729
|
+
b.className = 'emoji-opt';
|
|
17730
|
+
b.textContent = emoji;
|
|
17731
|
+
b.onclick = () => { react(m.messageId, emoji); popover.classList.remove('open'); };
|
|
17732
|
+
popover.appendChild(b);
|
|
17733
|
+
});
|
|
17734
|
+
const actions = div.querySelector('.msg-actions');
|
|
17735
|
+
reactIcon.onclick = (e) => {
|
|
17736
|
+
e.stopPropagation();
|
|
17737
|
+
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'); } });
|
|
17738
|
+
popover.classList.toggle('open');
|
|
17739
|
+
if (actions) actions.classList.toggle('pinned-open', popover.classList.contains('open'));
|
|
17740
|
+
};
|
|
17741
|
+
document.addEventListener('click', () => { popover.classList.remove('open'); if (actions) actions.classList.remove('pinned-open'); });
|
|
17742
|
+
}
|
|
17743
|
+
}
|
|
17744
|
+
container.appendChild(div);
|
|
17745
|
+
}
|
|
17746
|
+
|
|
17747
|
+
async function loadMessages() {
|
|
17748
|
+
const r = await get('messages', { vault: state.activeVault });
|
|
17749
|
+
const box = document.getElementById('messages');
|
|
17750
|
+
box.innerHTML = '';
|
|
17751
|
+
if (!r.ok) { box.innerHTML = '<div class="empty">' + esc(r.error) + '</div>'; return; }
|
|
17752
|
+
(r.messages || []).forEach(m => renderMessage(m, box, false));
|
|
17753
|
+
box.scrollTop = box.scrollHeight;
|
|
17754
|
+
}
|
|
17755
|
+
|
|
17756
|
+
async function react(messageId, emoji) {
|
|
17757
|
+
await post('react', { vault: state.activeVault, messageId, emoji });
|
|
17758
|
+
if (state.threadParent) await loadThread(state.threadParent); else await loadMessages();
|
|
17759
|
+
}
|
|
17760
|
+
|
|
17761
|
+
document.getElementById('send-btn').onclick = async () => {
|
|
17762
|
+
const ta = document.getElementById('composer');
|
|
17763
|
+
const content = ta.value.trim();
|
|
17764
|
+
if (!content && !state.pendingAttach) return;
|
|
17765
|
+
ta.value = '';
|
|
17766
|
+
const attachNote = state.pendingAttach ? { vault: state.pendingAttach.vault, path: state.pendingAttach.path } : undefined;
|
|
17767
|
+
clearAttach('main');
|
|
17768
|
+
await post('post', { vault: state.activeVault, content: content || (attachNote ? 'shared a note' : ''), attachNote });
|
|
17769
|
+
await loadMessages();
|
|
17770
|
+
};
|
|
17771
|
+
document.getElementById('composer').addEventListener('keydown', e => {
|
|
17772
|
+
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); document.getElementById('send-btn').click(); }
|
|
17773
|
+
});
|
|
17774
|
+
|
|
17775
|
+
function clearAttach(target) {
|
|
17776
|
+
if (target === 'main') {
|
|
17777
|
+
state.pendingAttach = null;
|
|
17778
|
+
document.getElementById('attach-chip-row').classList.remove('show');
|
|
17779
|
+
} else {
|
|
17780
|
+
state.threadPendingAttach = null;
|
|
17781
|
+
document.getElementById('thread-attach-chip-row').classList.remove('show');
|
|
17782
|
+
}
|
|
17783
|
+
}
|
|
17784
|
+
document.getElementById('attach-chip-clear').onclick = () => clearAttach('main');
|
|
17785
|
+
document.getElementById('thread-attach-chip-clear').onclick = () => clearAttach('thread');
|
|
17786
|
+
|
|
17787
|
+
async function openNotePicker(target) {
|
|
17788
|
+
state.pickerTarget = target;
|
|
17789
|
+
document.getElementById('note-picker-search').value = '';
|
|
17790
|
+
document.getElementById('note-picker-overlay').classList.add('open');
|
|
17791
|
+
if (!state.ownNotes) {
|
|
17792
|
+
const r = await get('notes-own', {});
|
|
17793
|
+
state.ownNotes = r.ok ? (r.notes || []) : [];
|
|
17794
|
+
}
|
|
17795
|
+
renderNotePickerList(state.ownNotes);
|
|
17796
|
+
document.getElementById('note-picker-search').focus();
|
|
17797
|
+
}
|
|
17798
|
+
function renderNotePickerList(notes) {
|
|
17799
|
+
const box = document.getElementById('note-picker-list');
|
|
17800
|
+
box.innerHTML = '';
|
|
17801
|
+
if (!notes.length) { box.innerHTML = '<div class="empty">No notes found.</div>'; return; }
|
|
17802
|
+
notes.forEach(n => {
|
|
17803
|
+
const row = document.createElement('div');
|
|
17804
|
+
row.className = 'note-pick-row';
|
|
17805
|
+
row.innerHTML = esc(n.title) + '<span class="vault-tag">' + esc(n.vault) + '/' + esc(n.path) + '</span>';
|
|
17806
|
+
row.onclick = () => {
|
|
17807
|
+
const chip = { vault: n.vault, path: n.path, title: n.title };
|
|
17808
|
+
if (state.pickerTarget === 'main') {
|
|
17809
|
+
state.pendingAttach = chip;
|
|
17810
|
+
document.getElementById('attach-chip-label').textContent = n.title;
|
|
17811
|
+
document.getElementById('attach-chip-row').classList.add('show');
|
|
17812
|
+
} else {
|
|
17813
|
+
state.threadPendingAttach = chip;
|
|
17814
|
+
document.getElementById('thread-attach-chip-label').textContent = n.title;
|
|
17815
|
+
document.getElementById('thread-attach-chip-row').classList.add('show');
|
|
17816
|
+
}
|
|
17817
|
+
document.getElementById('note-picker-overlay').classList.remove('open');
|
|
17818
|
+
};
|
|
17819
|
+
box.appendChild(row);
|
|
17820
|
+
});
|
|
17821
|
+
}
|
|
17822
|
+
document.getElementById('attach-btn').onclick = () => openNotePicker('main');
|
|
17823
|
+
document.getElementById('thread-attach-btn').onclick = () => openNotePicker('thread');
|
|
17824
|
+
document.getElementById('note-picker-close').onclick = () => document.getElementById('note-picker-overlay').classList.remove('open');
|
|
17825
|
+
document.getElementById('note-picker-search').addEventListener('input', e => {
|
|
17826
|
+
const q = e.target.value.trim().toLowerCase();
|
|
17827
|
+
const filtered = !q ? state.ownNotes : (state.ownNotes || []).filter(n => n.title.toLowerCase().includes(q) || n.path.toLowerCase().includes(q));
|
|
17828
|
+
renderNotePickerList(filtered || []);
|
|
17829
|
+
});
|
|
17830
|
+
|
|
17831
|
+
async function openNoteCard(vault, messageId) {
|
|
17832
|
+
document.getElementById('note-card-title').textContent = 'Loading...';
|
|
17833
|
+
document.getElementById('note-card-body').textContent = '';
|
|
17834
|
+
document.getElementById('note-card-overlay').classList.add('open');
|
|
17835
|
+
const r = await get('message-note', { vault, messageId });
|
|
17836
|
+
if (!r.ok) {
|
|
17837
|
+
document.getElementById('note-card-title').textContent = 'Unavailable';
|
|
17838
|
+
document.getElementById('note-card-body').textContent = r.error || 'Could not load this note.';
|
|
17839
|
+
return;
|
|
17840
|
+
}
|
|
17841
|
+
document.getElementById('note-card-title').textContent = r.note.title;
|
|
17842
|
+
document.getElementById('note-card-body').textContent = r.note.content;
|
|
17843
|
+
}
|
|
17844
|
+
document.getElementById('note-card-close').onclick = () => document.getElementById('note-card-overlay').classList.remove('open');
|
|
17845
|
+
|
|
17846
|
+
async function openThread(parentMessageId) {
|
|
17847
|
+
state.threadParent = parentMessageId;
|
|
17848
|
+
document.getElementById('thread-panel').classList.add('open');
|
|
17849
|
+
await loadThread(parentMessageId);
|
|
17850
|
+
}
|
|
17851
|
+
function closeThread() {
|
|
17852
|
+
state.threadParent = null;
|
|
17853
|
+
document.getElementById('thread-panel').classList.remove('open');
|
|
17854
|
+
}
|
|
17855
|
+
document.getElementById('thread-close').onclick = closeThread;
|
|
17856
|
+
|
|
17857
|
+
async function loadThread(parentMessageId) {
|
|
17858
|
+
const rootBox = document.getElementById('thread-messages');
|
|
17859
|
+
rootBox.innerHTML = '';
|
|
17860
|
+
const all = await get('messages', { vault: state.activeVault });
|
|
17861
|
+
const root = (all.messages || []).find(m => m.messageId === parentMessageId);
|
|
17862
|
+
if (root) renderMessage(root, rootBox, true);
|
|
17863
|
+
const r = await get('messages', { vault: state.activeVault, parentMessageId });
|
|
17864
|
+
const replyHeader = document.createElement('div');
|
|
17865
|
+
replyHeader.style.cssText = 'padding:6px 16px;font:600 11.5px system-ui;color:#948C9C;border-bottom:1px solid #F2EEF5;margin-bottom:4px';
|
|
17866
|
+
replyHeader.textContent = (r.messages || []).length + ' replies';
|
|
17867
|
+
rootBox.appendChild(replyHeader);
|
|
17868
|
+
(r.messages || []).forEach(m => renderMessage(m, rootBox, true));
|
|
17869
|
+
}
|
|
17870
|
+
document.getElementById('thread-send-btn').onclick = async () => {
|
|
17871
|
+
const ta = document.getElementById('thread-composer');
|
|
17872
|
+
const content = ta.value.trim();
|
|
17873
|
+
if ((!content && !state.threadPendingAttach) || !state.threadParent) return;
|
|
17874
|
+
ta.value = '';
|
|
17875
|
+
const attachNote = state.threadPendingAttach ? { vault: state.threadPendingAttach.vault, path: state.threadPendingAttach.path } : undefined;
|
|
17876
|
+
clearAttach('thread');
|
|
17877
|
+
await post('reply', { vault: state.activeVault, parentMessageId: state.threadParent, content: content || (attachNote ? 'shared a note' : ''), attachNote });
|
|
17878
|
+
await loadThread(state.threadParent);
|
|
17879
|
+
await loadChannels();
|
|
17880
|
+
};
|
|
17881
|
+
|
|
17882
|
+
async function loadMembers() {
|
|
17883
|
+
const r = await get('members', { vault: state.activeVault });
|
|
17884
|
+
state.members = r.ok ? (r.members || []) : [];
|
|
17885
|
+
}
|
|
17886
|
+
|
|
17887
|
+
document.getElementById('manage-btn').onclick = async () => {
|
|
17888
|
+
await loadMembers();
|
|
17889
|
+
const list = document.getElementById('member-list');
|
|
17890
|
+
list.innerHTML = '';
|
|
17891
|
+
state.members.forEach(m => {
|
|
17892
|
+
const row = document.createElement('div');
|
|
17893
|
+
row.className = 'member-row';
|
|
17894
|
+
row.innerHTML = '<span class="avatar" style="width:24px;height:24px;background:' + colorFor(m.identity) + '">' + initialsFor(m.identity) + '</span>' +
|
|
17895
|
+
'<span>' + esc(m.identity) + (m.isAgent ? ' <span class="agent-pill">AGENT</span>' : '') + '</span>' +
|
|
17896
|
+
'<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>';
|
|
17897
|
+
list.appendChild(row);
|
|
17898
|
+
});
|
|
17899
|
+
document.getElementById('manage-overlay').classList.add('open');
|
|
17900
|
+
};
|
|
17901
|
+
document.getElementById('manage-close').onclick = () => document.getElementById('manage-overlay').classList.remove('open');
|
|
17902
|
+
document.getElementById('invite-submit').onclick = async () => {
|
|
17903
|
+
const email = document.getElementById('invite-email').value.trim();
|
|
17904
|
+
const scope = document.getElementById('invite-scope').value;
|
|
17905
|
+
const errEl = document.getElementById('invite-error');
|
|
17906
|
+
errEl.style.display = 'none';
|
|
17907
|
+
if (!email) return;
|
|
17908
|
+
const r = await post('invite', { vault: state.activeVault, email, scope });
|
|
17909
|
+
if (!r.ok) { errEl.textContent = r.error || 'invite failed'; errEl.style.display = 'block'; return; }
|
|
17910
|
+
document.getElementById('invite-email').value = '';
|
|
17911
|
+
document.getElementById('manage-btn').click();
|
|
17912
|
+
};
|
|
17913
|
+
|
|
17914
|
+
document.getElementById('create-btn').onclick = () => document.getElementById('create-overlay').classList.add('open');
|
|
17915
|
+
document.getElementById('create-cancel').onclick = () => document.getElementById('create-overlay').classList.remove('open');
|
|
17916
|
+
document.getElementById('create-submit').onclick = async () => {
|
|
17917
|
+
const name = document.getElementById('new-channel-name').value.trim();
|
|
17918
|
+
const invitesRaw = document.getElementById('new-channel-invites').value.trim();
|
|
17919
|
+
const errEl = document.getElementById('create-error');
|
|
17920
|
+
errEl.style.display = 'none';
|
|
17921
|
+
if (!name) return;
|
|
17922
|
+
const inviteEmails = invitesRaw ? invitesRaw.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
17923
|
+
const r = await post('channel', { name, inviteEmails });
|
|
17924
|
+
if (!r.ok) { errEl.textContent = r.error || 'create failed'; errEl.style.display = 'block'; return; }
|
|
17925
|
+
document.getElementById('create-overlay').classList.remove('open');
|
|
17926
|
+
document.getElementById('new-channel-name').value = '';
|
|
17927
|
+
document.getElementById('new-channel-invites').value = '';
|
|
17928
|
+
await loadChannels();
|
|
17929
|
+
if (r.members && r.members.some(m => !m.ok)) {
|
|
17930
|
+
alert('Channel created, but some invites failed: ' + r.members.filter(m => !m.ok).map(m => m.identity + ' (' + m.error + ')').join(', '));
|
|
17931
|
+
}
|
|
17932
|
+
};
|
|
17933
|
+
|
|
17934
|
+
document.getElementById('mentions-btn').onclick = async () => {
|
|
17935
|
+
const r = await get('mentions', {});
|
|
17936
|
+
const list = document.getElementById('mentions-list');
|
|
17937
|
+
list.innerHTML = '';
|
|
17938
|
+
(r.mentions || []).forEach(m => {
|
|
17939
|
+
const row = document.createElement('button');
|
|
17940
|
+
row.style.cssText = 'display:block;width:100%;text-align:left;background:transparent;border:none;cursor:pointer;padding:10px 0;border-bottom:1px solid #F2EEF5';
|
|
17941
|
+
row.innerHTML = '<div style="font:600 11.5px system-ui;color:#6B3FA0">' + esc(m.channel) + '</div>' +
|
|
17942
|
+
'<div style="font:600 13.5px system-ui">' + esc(m.authorIdentity) + ' <span style="font:400 12px;color:#948C9C">- ' + fmtTime(m.mentionedAt) + '</span></div>' +
|
|
17943
|
+
'<div style="font:400 13.5px;color:#3D3548;margin-top:2px">' + esc(m.content) + '</div>';
|
|
17944
|
+
row.onclick = () => { document.getElementById('mentions-overlay').classList.remove('open'); openChannel(m.channel, (state.channels.find(c => c.handle === m.channel) || {}).vault || m.channel); };
|
|
17945
|
+
list.appendChild(row);
|
|
17946
|
+
});
|
|
17947
|
+
if (!(r.mentions || []).length) list.innerHTML = '<div class="empty">No mentions yet</div>';
|
|
17948
|
+
document.getElementById('mentions-overlay').classList.add('open');
|
|
17949
|
+
};
|
|
17950
|
+
document.getElementById('mentions-close').onclick = () => document.getElementById('mentions-overlay').classList.remove('open');
|
|
17951
|
+
|
|
17952
|
+
function restartPolling() {
|
|
17953
|
+
if (state.pollTimer) clearInterval(state.pollTimer);
|
|
17954
|
+
state.pollTimer = setInterval(async () => {
|
|
17955
|
+
if (document.visibilityState !== 'visible' || !state.activeVault) return;
|
|
17956
|
+
const r = await get('poll', { vault: state.activeVault });
|
|
17957
|
+
if (r.ok && ((r.newMessages || []).length || (r.newReplies || []).length || (r.newReactions || []).length)) {
|
|
17958
|
+
await loadMessages();
|
|
17959
|
+
if (state.threadParent) await loadThread(state.threadParent);
|
|
17960
|
+
}
|
|
17961
|
+
}, 4000);
|
|
17962
|
+
}
|
|
17963
|
+
|
|
17964
|
+
loadChannels();
|
|
17965
|
+
</script>
|
|
17966
|
+
</body>
|
|
17967
|
+
</html>`;
|
|
17968
|
+
}
|
|
17969
|
+
|
|
17318
17970
|
// src/api/site-audit-worker.ts
|
|
17319
17971
|
var MAX_CONCURRENT_SITE_AUDIT = 1;
|
|
17320
17972
|
async function drainSiteAuditQueue(budget) {
|
|
@@ -17729,7 +18381,7 @@ var sessionAuth = createMiddleware3(async (c, next) => {
|
|
|
17729
18381
|
c.set("sessionUser", { ...refreshed, balance_mc: balanceMc });
|
|
17730
18382
|
return next();
|
|
17731
18383
|
});
|
|
17732
|
-
var app = new
|
|
18384
|
+
var app = new Hono16();
|
|
17733
18385
|
var STRIPE_API_VERSION = "2026-02-25.clover";
|
|
17734
18386
|
function requireStripeSecret() {
|
|
17735
18387
|
const secret2 = process.env.STRIPE_SECRET_KEY?.trim();
|
|
@@ -17772,6 +18424,7 @@ function opForCostPath(p) {
|
|
|
17772
18424
|
return "fb_ad";
|
|
17773
18425
|
}
|
|
17774
18426
|
if (p.startsWith("/instagram/")) return "instagram";
|
|
18427
|
+
if (p === "/reddit/thread") return "reddit_thread";
|
|
17775
18428
|
if (p.startsWith("/api/internal/site-architecture-auditor")) return "audit_site";
|
|
17776
18429
|
if (p.startsWith("/agent/")) return "browser_session";
|
|
17777
18430
|
return null;
|
|
@@ -17996,17 +18649,17 @@ app.get("/memory/vaults", auth2, async (c) => {
|
|
|
17996
18649
|
app.get("/memory/connect", auth2, async (c) => {
|
|
17997
18650
|
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
17998
18651
|
if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
|
|
17999
|
-
const
|
|
18652
|
+
const url = "https://memory.mcpscraper.dev/mcp";
|
|
18000
18653
|
const config = {
|
|
18001
18654
|
mcpServers: {
|
|
18002
|
-
|
|
18003
|
-
|
|
18004
|
-
|
|
18005
|
-
env: { MCP_MEMORY_KEY: key }
|
|
18655
|
+
Memory: {
|
|
18656
|
+
url,
|
|
18657
|
+
headers: { Authorization: `Bearer ${key}` }
|
|
18006
18658
|
}
|
|
18007
18659
|
}
|
|
18008
18660
|
};
|
|
18009
|
-
|
|
18661
|
+
const codeCommand = `claude mcp add --transport http memory ${url} --header "Authorization: Bearer ${key}"`;
|
|
18662
|
+
return c.json({ ok: true, url, key, config, codeCommand });
|
|
18010
18663
|
});
|
|
18011
18664
|
app.post("/memory/vault", auth2, async (c) => {
|
|
18012
18665
|
const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
|
|
@@ -18191,7 +18844,7 @@ function countPaaQuestions(result) {
|
|
|
18191
18844
|
return Array.isArray(value.flat) ? value.flat.length : 0;
|
|
18192
18845
|
}
|
|
18193
18846
|
function paaCostForQuestionCount(questionCount) {
|
|
18194
|
-
return Math.max(1, questionCount) * MC_COSTS.paa;
|
|
18847
|
+
return MC_COSTS.paa_base + Math.max(1, questionCount) * MC_COSTS.paa;
|
|
18195
18848
|
}
|
|
18196
18849
|
async function checkHarvestLimits(user, reuseLockId) {
|
|
18197
18850
|
if (reuseLockId && await reuseExistingConcurrencyGate(user.id, reuseLockId, 30 * 60)) return null;
|
|
@@ -18237,7 +18890,7 @@ app.post("/harvest", auth2, async (c) => {
|
|
|
18237
18890
|
serpOnly: body.serpOnly ?? false,
|
|
18238
18891
|
pages: Math.min(2, Math.max(1, body.pages ?? 1))
|
|
18239
18892
|
};
|
|
18240
|
-
const harvestCost = options.serpOnly ? MC_COSTS.serp : options.maxQuestions
|
|
18893
|
+
const harvestCost = options.serpOnly ? MC_COSTS.serp : paaCostForQuestionCount(options.maxQuestions);
|
|
18241
18894
|
const { ok: harvestOk, balance_mc: harvestBal } = await debitMc(user.id, harvestCost, options.serpOnly ? LedgerOperation.SERP : LedgerOperation.PAA, options.query);
|
|
18242
18895
|
if (!harvestOk) return c.json(insufficientBalanceResponse(harvestBal, harvestCost), 402);
|
|
18243
18896
|
const jobId = await createJob(user.id, options.query, { ...options, billingHoldMc: harvestCost }, body.callback_url);
|
|
@@ -18273,7 +18926,7 @@ app.post("/harvest/sync", auth2, async (c) => {
|
|
|
18273
18926
|
serpOnly: body.serpOnly ?? false,
|
|
18274
18927
|
pages: Math.min(2, Math.max(1, body.pages ?? 1))
|
|
18275
18928
|
};
|
|
18276
|
-
const syncCost = options.serpOnly ? MC_COSTS.serp : options.maxQuestions
|
|
18929
|
+
const syncCost = options.serpOnly ? MC_COSTS.serp : paaCostForQuestionCount(options.maxQuestions);
|
|
18277
18930
|
const { ok: syncOk, balance_mc: syncBal } = await debitMc(user.id, syncCost, options.serpOnly ? LedgerOperation.SERP : LedgerOperation.PAA, options.query);
|
|
18278
18931
|
if (!syncOk) return c.json(insufficientBalanceResponse(syncBal, syncCost), 402);
|
|
18279
18932
|
const jobId = await createRunningJob(user.id, options.query, options);
|
|
@@ -18869,7 +19522,7 @@ app.get("/cron/tick", async (c) => {
|
|
|
18869
19522
|
if (!process.env.CRON_SECRET || secret2 !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
18870
19523
|
return c.json({ error: "Unauthorized" }, 401);
|
|
18871
19524
|
}
|
|
18872
|
-
const { drainQueue } = await import("./worker-
|
|
19525
|
+
const { drainQueue } = await import("./worker-NV4GTIRG.js");
|
|
18873
19526
|
const budget = { maxJobs: 10, deadlineMs: Date.now() + 28e4 };
|
|
18874
19527
|
const workflowDispatchResult = await dispatchDueWorkflowSchedules(`${new URL(c.req.url).protocol}//${new URL(c.req.url).host}`);
|
|
18875
19528
|
const [results, sweepResult, reapResult, expiredResult, blobCleanup] = await Promise.all([
|
|
@@ -18894,6 +19547,7 @@ app.route("/workflows", workflowApp);
|
|
|
18894
19547
|
app.route("/serp-intelligence", serpIntelligenceApp);
|
|
18895
19548
|
app.route("/mcp", mcpApp);
|
|
18896
19549
|
app.route("/agent", buildBrowserAgentRoutes());
|
|
19550
|
+
app.route("/chat", chatApp);
|
|
18897
19551
|
app.get("/console", (c) => c.html(renderConsoleHtml()));
|
|
18898
19552
|
app.get("/console/auth/:id", async (c) => {
|
|
18899
19553
|
const id = c.req.param("id");
|
|
@@ -19017,4 +19671,4 @@ app.get("/blog/:slug/", (c) => {
|
|
|
19017
19671
|
export {
|
|
19018
19672
|
app
|
|
19019
19673
|
};
|
|
19020
|
-
//# sourceMappingURL=server-
|
|
19674
|
+
//# sourceMappingURL=server-IDDX7KGT.js.map
|