@mneme-ai/core 2.8.1 → 2.9.1

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=beacon.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beacon.test.d.ts","sourceRoot":"","sources":["../../src/beacon/beacon.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,98 @@
1
+ import { describe, it, expect, afterEach } from "vitest";
2
+ import { spawnBeacon, qrForUrl, formatBeaconPulseLine, pasteCrossWifi } from "./index.js";
3
+ const pending = [];
4
+ afterEach(() => {
5
+ for (const r of pending) {
6
+ try {
7
+ r.server?.close();
8
+ }
9
+ catch { /* BE:silent-by-design */ }
10
+ }
11
+ pending.length = 0;
12
+ });
13
+ describe("v2.9 BEACON", () => {
14
+ it("spawnBeacon returns a token + clipboard + markdown paths even when no LAN", async () => {
15
+ const r = await spawnBeacon({ payload: "soul prompt", port: 0 });
16
+ pending.push(r);
17
+ expect(r.token).toMatch(/^[0-9a-f]{12}$/);
18
+ expect(r.paths.find((p) => p.id === "clipboard")).toBeDefined();
19
+ expect(r.paths.find((p) => p.id === "markdown")).toBeDefined();
20
+ });
21
+ it("when server binds successfully, surfaces LAN URL(s)", async () => {
22
+ const r = await spawnBeacon({ payload: "hello", port: 0 });
23
+ pending.push(r);
24
+ expect(r.server).not.toBeNull();
25
+ expect(r.port).toBeGreaterThan(0);
26
+ if (r.lanIPs.length > 0) {
27
+ expect(r.paths.some((p) => p.id.startsWith("lan-url"))).toBe(true);
28
+ // Each LAN URL should have an accompanying QR data: URI
29
+ expect(r.paths.some((p) => p.id.startsWith("lan-qr") && p.content.startsWith("data:image/svg+xml;base64,"))).toBe(true);
30
+ }
31
+ });
32
+ it("the served page contains the payload and a Copy button", async () => {
33
+ const payload = "MAGIC-PAYLOAD-TOKEN-12345";
34
+ const r = await spawnBeacon({ payload, port: 0, bindHost: "127.0.0.1" });
35
+ pending.push(r);
36
+ if (!r.port)
37
+ return; // Skip on platforms where bind failed.
38
+ const url = `http://127.0.0.1:${r.port}/${r.token}`;
39
+ const resp = await fetch(url);
40
+ expect(resp.ok).toBe(true);
41
+ const html = await resp.text();
42
+ expect(html).toContain(payload);
43
+ expect(html).toContain("Copy soul prompt");
44
+ expect(html).toContain("navigator.clipboard.writeText");
45
+ });
46
+ it("server XSS-escapes user-controlled payload + label", async () => {
47
+ const r = await spawnBeacon({ payload: "<script>alert(1)</script>", label: '<img src=x>', port: 0, bindHost: "127.0.0.1" });
48
+ pending.push(r);
49
+ if (!r.port)
50
+ return;
51
+ const url = `http://127.0.0.1:${r.port}/${r.token}`;
52
+ const html = await (await fetch(url)).text();
53
+ expect(html).not.toContain("<script>alert(1)</script>");
54
+ expect(html).toContain("&lt;script&gt;");
55
+ expect(html).not.toContain("<img src=x>");
56
+ });
57
+ it("server 404s on unknown path", async () => {
58
+ const r = await spawnBeacon({ payload: "x", port: 0, bindHost: "127.0.0.1" });
59
+ pending.push(r);
60
+ if (!r.port)
61
+ return;
62
+ const resp = await fetch(`http://127.0.0.1:${r.port}/wrong-token`);
63
+ expect(resp.status).toBe(404);
64
+ });
65
+ it("qrForUrl returns a data:image/svg+xml URI for short URLs", () => {
66
+ const uri = qrForUrl("http://192.168.1.10:7741/abc");
67
+ expect(uri).not.toBeNull();
68
+ expect(uri.startsWith("data:image/svg+xml;base64,")).toBe(true);
69
+ });
70
+ it("formatBeaconPulseLine emits a compact summary", async () => {
71
+ const r = await spawnBeacon({ payload: "x", port: 0 });
72
+ pending.push(r);
73
+ const line = formatBeaconPulseLine(r);
74
+ expect(line).toContain("BEACON");
75
+ expect(line).toContain("token=");
76
+ expect(line).toContain("paths=");
77
+ });
78
+ it("pasteCrossWifi handles fetch failure gracefully", async () => {
79
+ const r = await pasteCrossWifi("x", { fetchImpl: async () => { throw new Error("network down"); } });
80
+ expect(r).toBeNull();
81
+ });
82
+ it("pasteCrossWifi returns null on non-OK response", async () => {
83
+ const r = await pasteCrossWifi("x", { fetchImpl: async () => new Response("err", { status: 503 }) });
84
+ expect(r).toBeNull();
85
+ });
86
+ it("pasteCrossWifi returns url object on success", async () => {
87
+ const fakeFetch = async () => new Response("https://dpaste.com/ABC123", { status: 200 });
88
+ const r = await pasteCrossWifi("x", { fetchImpl: fakeFetch });
89
+ expect(r?.url).toBe("https://dpaste.com/ABC123");
90
+ expect(r?.provider).toBe("dpaste");
91
+ });
92
+ it("pasteCrossWifi rejects malformed response body", async () => {
93
+ const fakeFetch = async () => new Response("not-a-url", { status: 200 });
94
+ const r = await pasteCrossWifi("x", { fetchImpl: fakeFetch });
95
+ expect(r).toBeNull();
96
+ });
97
+ });
98
+ //# sourceMappingURL=beacon.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"beacon.test.js","sourceRoot":"","sources":["../../src/beacon/beacon.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * v2.9.0 -- BEACON: zero-friction cross-device sync that JUST WORKS.
3
+ *
4
+ * "The user doesn't see folders or files. They chat. Mneme delivers."
5
+ *
6
+ * Pain that drove this: even AURA-DROP (v2.8) assumed an HTML file
7
+ * the user could open. But the real user flow is:
8
+ * - User chats with an AI agent (Claude Code / Cursor / etc).
9
+ * - User doesn't have the source tree. No `ls .mneme/`.
10
+ * - User can't double-click an HTML file.
11
+ *
12
+ * BEACON closes that gap. ONE call from the AI agent returns a payload
13
+ * the AI can RENDER INLINE in the chat:
14
+ *
15
+ * 1. A short URL the AI agent shows as a clickable link (uses the
16
+ * user's CURRENT browser — opens a Mneme-hosted local page that
17
+ * auto-loads the soul prompt into the destination AI).
18
+ * 2. A `data:image/svg+xml;base64,...` QR (image tag inline in chat).
19
+ * 3. The OS clipboard already has the soul prompt for instant paste.
20
+ *
21
+ * Two delivery paths, picked automatically per network condition:
22
+ *
23
+ * SAME-WIFI: bind localhost:port + advertise LAN IP. Phone visits
24
+ * http://192.168.x.y:7741/<token>. Zero internet.
25
+ *
26
+ * CROSS-WIFI: POST to an anonymous paste service (dpaste.com — has
27
+ * a public API with no auth). Returns the public URL.
28
+ * Phone visits the URL. Cross-network works.
29
+ *
30
+ * BEACON is the ONE call AI agents make. It probes both paths in
31
+ * parallel, returns whichever paths succeeded, and the AI agent paints
32
+ * options for the user. Total user actions: scan QR + paste (2 taps).
33
+ *
34
+ * Nobel-tier move: the local server speaks an UPGRADE PROTOCOL — the
35
+ * server-hosted page contains the soul prompt AS DATA, plus the same
36
+ * AURA-DROP "Copy + open destination AI" affordance from v2.8. So even
37
+ * if the LAN URL is the only one that works, the receiving phone gets
38
+ * a real interactive page, not a dump.
39
+ */
40
+ import { type Server } from "node:http";
41
+ export interface BeaconPath {
42
+ /** Stable id: lan-url / paste-url / qr-inline / clipboard. */
43
+ id: string;
44
+ /** Human-readable label for the AI to surface. */
45
+ label: string;
46
+ /** Inline content the AI agent can render directly. */
47
+ content: string;
48
+ /** Render hint for the AI: 'url' / 'image-data-uri' / 'copy-button'. */
49
+ displayHint: "url" | "image-data-uri" | "copy-button" | "markdown";
50
+ /** True iff this path needs no internet (LAN / clipboard / data-URI). */
51
+ offlineCapable: boolean;
52
+ }
53
+ export interface BeaconResult {
54
+ /** Stable token identifying this handoff. */
55
+ token: string;
56
+ /** All paths the AI agent should surface. */
57
+ paths: BeaconPath[];
58
+ /** The local HTTP server (if any) — caller MUST call server.close() when done. */
59
+ server: Server | null;
60
+ /** Port the LAN URL is on, when applicable. */
61
+ port: number | null;
62
+ /** LAN IPs the server is reachable on. */
63
+ lanIPs: string[];
64
+ /** ISO timestamp. */
65
+ generatedAt: string;
66
+ }
67
+ export interface SpawnBeaconInput {
68
+ payload: string;
69
+ /** Vendor target (claude / chatgpt / gemini). Affects the page copy. */
70
+ targetVendor?: string;
71
+ /** Page title / label. */
72
+ label?: string;
73
+ /** Listen port. Default 7741. Falls through to first available > port. */
74
+ port?: number;
75
+ /** Listen address. Default '0.0.0.0' so LAN devices can reach it.
76
+ * Use '127.0.0.1' for localhost-only. */
77
+ bindHost?: string;
78
+ /** Auto-stop the server after this many ms of no requests. Default 600_000 (10 min). */
79
+ idleTimeoutMs?: number;
80
+ }
81
+ /** Spawn a local HTTP server that serves the soul prompt at one URL.
82
+ * Returns the server + the public-ish paths the AI agent can surface. */
83
+ export declare function spawnBeacon(input: SpawnBeaconInput): Promise<BeaconResult>;
84
+ /** Anonymous paste — POSTs to a public service that accepts unauthenticated
85
+ * uploads. Used as cross-WiFi fallback when LAN URL is unreachable from
86
+ * the receiver (different network).
87
+ *
88
+ * Provider: dpaste.com — has a stable POST /api/v2/ form (no auth, no
89
+ * signup). 1-day expiry default. Returns a public URL.
90
+ *
91
+ * Returns null when the network call fails so the caller can fall back
92
+ * to LAN / clipboard / markdown. */
93
+ export declare function pasteCrossWifi(payload: string, opts?: {
94
+ provider?: "dpaste";
95
+ ttlSeconds?: number;
96
+ fetchImpl?: typeof fetch;
97
+ }): Promise<{
98
+ url: string;
99
+ provider: string;
100
+ expires: number;
101
+ } | null>;
102
+ /** Render a QR for an arbitrary URL as a data:image/svg+xml URI. */
103
+ export declare function qrForUrl(url: string): string | null;
104
+ /** One-line pulse summary. */
105
+ export declare function formatBeaconPulseLine(r: BeaconResult): string;
106
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/beacon/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EAAgB,KAAK,MAAM,EAA6C,MAAM,WAAW,CAAC;AAKjG,MAAM,WAAW,UAAU;IACzB,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,WAAW,EAAE,KAAK,GAAG,gBAAgB,GAAG,aAAa,GAAG,UAAU,CAAC;IACnE,yEAAyE;IACzE,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,6CAA6C;IAC7C,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,kFAAkF;IAClF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,+CAA+C;IAC/C,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,qBAAqB;IACrB,WAAW,EAAE,MAAM,CAAC;CACrB;AAqDD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0BAA0B;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;8CAC0C;IAC1C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wFAAwF;IACxF,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;0EAC0E;AAC1E,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,CAAC,CAkGhF;AAED;;;;;;;;qCAQqC;AACrC,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CAAE,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAwBvM;AAED,oEAAoE;AACpE,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAQnD;AAED,8BAA8B;AAC9B,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,YAAY,GAAG,MAAM,CAG7D"}
@@ -0,0 +1,249 @@
1
+ /**
2
+ * v2.9.0 -- BEACON: zero-friction cross-device sync that JUST WORKS.
3
+ *
4
+ * "The user doesn't see folders or files. They chat. Mneme delivers."
5
+ *
6
+ * Pain that drove this: even AURA-DROP (v2.8) assumed an HTML file
7
+ * the user could open. But the real user flow is:
8
+ * - User chats with an AI agent (Claude Code / Cursor / etc).
9
+ * - User doesn't have the source tree. No `ls .mneme/`.
10
+ * - User can't double-click an HTML file.
11
+ *
12
+ * BEACON closes that gap. ONE call from the AI agent returns a payload
13
+ * the AI can RENDER INLINE in the chat:
14
+ *
15
+ * 1. A short URL the AI agent shows as a clickable link (uses the
16
+ * user's CURRENT browser — opens a Mneme-hosted local page that
17
+ * auto-loads the soul prompt into the destination AI).
18
+ * 2. A `data:image/svg+xml;base64,...` QR (image tag inline in chat).
19
+ * 3. The OS clipboard already has the soul prompt for instant paste.
20
+ *
21
+ * Two delivery paths, picked automatically per network condition:
22
+ *
23
+ * SAME-WIFI: bind localhost:port + advertise LAN IP. Phone visits
24
+ * http://192.168.x.y:7741/<token>. Zero internet.
25
+ *
26
+ * CROSS-WIFI: POST to an anonymous paste service (dpaste.com — has
27
+ * a public API with no auth). Returns the public URL.
28
+ * Phone visits the URL. Cross-network works.
29
+ *
30
+ * BEACON is the ONE call AI agents make. It probes both paths in
31
+ * parallel, returns whichever paths succeeded, and the AI agent paints
32
+ * options for the user. Total user actions: scan QR + paste (2 taps).
33
+ *
34
+ * Nobel-tier move: the local server speaks an UPGRADE PROTOCOL — the
35
+ * server-hosted page contains the soul prompt AS DATA, plus the same
36
+ * AURA-DROP "Copy + open destination AI" affordance from v2.8. So even
37
+ * if the LAN URL is the only one that works, the receiving phone gets
38
+ * a real interactive page, not a dump.
39
+ */
40
+ import { createServer } from "node:http";
41
+ import { networkInterfaces } from "node:os";
42
+ import { createHash } from "node:crypto";
43
+ import { encodeQRReal } from "../synapse/qr_real.js";
44
+ const DEFAULT_PORT = 7741;
45
+ function detectLanIPs() {
46
+ const out = [];
47
+ const ifaces = networkInterfaces();
48
+ for (const list of Object.values(ifaces)) {
49
+ if (!list)
50
+ continue;
51
+ for (const i of list) {
52
+ if (i.internal)
53
+ continue;
54
+ if (i.family === "IPv4" && !i.address.startsWith("169.254."))
55
+ out.push(i.address);
56
+ }
57
+ }
58
+ return out;
59
+ }
60
+ function escapeHtml(s) {
61
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
62
+ }
63
+ function renderBeaconPage(payload, vendor, label) {
64
+ const hint = vendor.toLowerCase().includes("claude") ? "claude.ai"
65
+ : vendor.toLowerCase().includes("gpt") || vendor.toLowerCase().includes("chatgpt") ? "chatgpt.com"
66
+ : vendor.toLowerCase().includes("gemini") ? "gemini.google.com"
67
+ : "your AI app";
68
+ return `<!doctype html>
69
+ <html><head>
70
+ <meta charset="utf-8">
71
+ <meta name="viewport" content="width=device-width,initial-scale=1">
72
+ <title>${escapeHtml(label)}</title>
73
+ <style>
74
+ body{font-family:system-ui,sans-serif;max-width:680px;margin:14px auto;padding:0 12px;line-height:1.5;color:#202124}
75
+ h1{font-size:1.1em}
76
+ pre{white-space:pre-wrap;word-break:break-word;background:#f4f4f4;padding:12px;border-radius:8px;font-size:.85em;max-height:50vh;overflow:auto}
77
+ button{font-size:1em;padding:12px 18px;border:0;border-radius:8px;background:#1a73e8;color:#fff;cursor:pointer;margin:6px 0}
78
+ button:active{background:#0d47a1}
79
+ .ok{color:#137333;font-weight:bold}
80
+ small{color:#555}
81
+ a{color:#1a73e8}
82
+ </style></head><body>
83
+ <h1>${escapeHtml(label)}</h1>
84
+ <p><small>Mneme cross-device handoff. Self-contained — no Mneme install needed on this device.</small></p>
85
+ <p><b>Step 1</b> — Tap the button to copy the soul prompt.</p>
86
+ <button id="cp" onclick="navigator.clipboard.writeText(document.getElementById('p').textContent).then(()=>{document.getElementById('s').textContent='Copied! Now paste into ${escapeHtml(hint)}.';document.getElementById('s').className='ok'})">Copy soul prompt</button>
87
+ <p id="s"></p>
88
+ <p><b>Step 2</b> — Open <b><a href="https://${escapeHtml(hint)}" target="_blank">${escapeHtml(hint)}</a></b> and paste.</p>
89
+ <details><summary>Show the soul prompt</summary>
90
+ <pre id="p">${escapeHtml(payload)}</pre>
91
+ </details>
92
+ </body></html>`;
93
+ }
94
+ /** Spawn a local HTTP server that serves the soul prompt at one URL.
95
+ * Returns the server + the public-ish paths the AI agent can surface. */
96
+ export async function spawnBeacon(input) {
97
+ const token = createHash("sha256").update(input.payload + Date.now()).digest("hex").slice(0, 12);
98
+ const label = input.label ?? "Mneme brain transfer";
99
+ const vendor = input.targetVendor ?? "any AI";
100
+ const wantedPort = input.port ?? DEFAULT_PORT;
101
+ const bindHost = input.bindHost ?? "0.0.0.0";
102
+ const idleMs = input.idleTimeoutMs ?? 10 * 60 * 1000;
103
+ // Try to bind; if port is busy, fall through to ephemeral port.
104
+ let server = null;
105
+ let actualPort = null;
106
+ let lastRequestAt = Date.now();
107
+ try {
108
+ server = createServer((req, res) => {
109
+ lastRequestAt = Date.now();
110
+ const url = req.url ?? "/";
111
+ if (url.startsWith(`/${token}`) || url === "/") {
112
+ const html = renderBeaconPage(input.payload, vendor, label);
113
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store" });
114
+ res.end(html);
115
+ }
116
+ else {
117
+ res.writeHead(404, { "content-type": "text/plain" });
118
+ res.end("not found");
119
+ }
120
+ });
121
+ await new Promise((resolve, reject) => {
122
+ server.once("error", reject);
123
+ server.listen(wantedPort, bindHost, () => resolve());
124
+ });
125
+ actualPort = server.address()?.port ?? wantedPort;
126
+ // Idle shutdown
127
+ const idleTimer = setInterval(() => {
128
+ if (Date.now() - lastRequestAt > idleMs) {
129
+ try {
130
+ server?.close();
131
+ }
132
+ catch { /* BE:silent-by-design — cleanup */ }
133
+ clearInterval(idleTimer);
134
+ }
135
+ }, 30_000).unref();
136
+ }
137
+ catch {
138
+ // BE:silent-by-design — fall back to data-URI only paths
139
+ server = null;
140
+ actualPort = null;
141
+ }
142
+ const lanIPs = detectLanIPs();
143
+ const paths = [];
144
+ // PATH 1 — clipboard (same-device 1-click)
145
+ paths.push({
146
+ id: "clipboard",
147
+ label: `Paste into ${vendor} on this device (Ctrl+V / Cmd+V).`,
148
+ content: input.payload,
149
+ displayHint: "copy-button",
150
+ offlineCapable: true,
151
+ });
152
+ // PATH 2 — LAN URL with inline QR
153
+ if (server && actualPort) {
154
+ for (const ip of lanIPs.slice(0, 2)) {
155
+ const url = `http://${ip}:${actualPort}/${token}`;
156
+ paths.push({
157
+ id: `lan-url-${ip}`,
158
+ label: `Phone on same WiFi → ${url}`,
159
+ content: url,
160
+ displayHint: "url",
161
+ offlineCapable: true,
162
+ });
163
+ // QR for the LAN URL — phone scans, browser opens, page auto-loads
164
+ try {
165
+ const qr = encodeQRReal(url, { moduleSize: 6, quietZone: 2 });
166
+ const dataUri = `data:image/svg+xml;base64,${Buffer.from(qr.svg, "utf8").toString("base64")}`;
167
+ paths.push({
168
+ id: `lan-qr-${ip}`,
169
+ label: `Scan with phone camera (same WiFi)`,
170
+ content: dataUri,
171
+ displayHint: "image-data-uri",
172
+ offlineCapable: true,
173
+ });
174
+ }
175
+ catch { /* BE:silent-by-design — QR fails on payloads too large */ }
176
+ }
177
+ }
178
+ // PATH 3 — markdown fallback (universal)
179
+ paths.push({
180
+ id: "markdown",
181
+ label: `Paste this anywhere (universal fallback).`,
182
+ content: input.payload,
183
+ displayHint: "markdown",
184
+ offlineCapable: true,
185
+ });
186
+ return {
187
+ token,
188
+ paths,
189
+ server,
190
+ port: actualPort,
191
+ lanIPs,
192
+ generatedAt: new Date().toISOString(),
193
+ };
194
+ }
195
+ /** Anonymous paste — POSTs to a public service that accepts unauthenticated
196
+ * uploads. Used as cross-WiFi fallback when LAN URL is unreachable from
197
+ * the receiver (different network).
198
+ *
199
+ * Provider: dpaste.com — has a stable POST /api/v2/ form (no auth, no
200
+ * signup). 1-day expiry default. Returns a public URL.
201
+ *
202
+ * Returns null when the network call fails so the caller can fall back
203
+ * to LAN / clipboard / markdown. */
204
+ export async function pasteCrossWifi(payload, opts) {
205
+ const fetchFn = opts?.fetchImpl ?? globalThis.fetch;
206
+ if (typeof fetchFn !== "function")
207
+ return null;
208
+ const ttl = opts?.ttlSeconds ?? 24 * 60 * 60;
209
+ // dpaste.com API: POST form-encoded `content` + `expiry_days`
210
+ try {
211
+ const body = new URLSearchParams({
212
+ content: payload,
213
+ expiry_days: String(Math.max(1, Math.min(365, Math.ceil(ttl / 86400)))),
214
+ syntax: "text",
215
+ });
216
+ const r = await fetchFn("https://dpaste.com/api/v2/", {
217
+ method: "POST",
218
+ headers: { "content-type": "application/x-www-form-urlencoded", "user-agent": "mneme-beacon/2.9" },
219
+ body: body.toString(),
220
+ });
221
+ if (!r.ok)
222
+ return null;
223
+ const url = (await r.text()).trim();
224
+ if (!/^https?:\/\//.test(url))
225
+ return null;
226
+ return { url, provider: "dpaste", expires: Date.now() + ttl * 1000 };
227
+ }
228
+ catch {
229
+ // BE:silent-by-design — network failures fall back to LAN-only paths
230
+ return null;
231
+ }
232
+ }
233
+ /** Render a QR for an arbitrary URL as a data:image/svg+xml URI. */
234
+ export function qrForUrl(url) {
235
+ try {
236
+ const qr = encodeQRReal(url, { moduleSize: 6, quietZone: 2 });
237
+ return `data:image/svg+xml;base64,${Buffer.from(qr.svg, "utf8").toString("base64")}`;
238
+ }
239
+ catch {
240
+ // BE:silent-by-design — QR may fail on very long URLs
241
+ return null;
242
+ }
243
+ }
244
+ /** One-line pulse summary. */
245
+ export function formatBeaconPulseLine(r) {
246
+ const lan = r.lanIPs.length > 0 ? `lan=${r.lanIPs[0]}:${r.port}` : "lan=none";
247
+ return `BEACON · token=${r.token} · ${lan} · paths=${r.paths.length}`;
248
+ }
249
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/beacon/index.ts"],"names":[],"mappings":"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"}