@mirrowel/opencode-openshare 0.1.1 → 0.2.0-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -37
- package/ci/README.md +9 -3
- package/ci/openshare.js +916 -199
- package/dist/binding-queue.d.ts +1 -0
- package/dist/binding-queue.js +22 -0
- package/dist/commands.js +41 -285
- package/dist/contract.d.ts +156 -0
- package/dist/contract.js +263 -0
- package/dist/core.js +888 -96
- package/dist/dialogs.js +39 -1
- package/dist/settings.js +141 -16
- package/dist/share-flow.d.ts +7 -2
- package/dist/share-flow.js +32 -17
- package/dist/sidebar.d.ts +3 -1
- package/dist/sidebar.js +3 -38
- package/dist/tui.js +286 -421
- package/dist/v2-server.d.ts +0 -27
- package/dist/v2-server.js +61 -121
- package/package.json +3 -1
- package/src/binding-queue.ts +26 -0
- package/src/commands.ts +41 -280
- package/src/contract.ts +320 -0
- package/src/core.ts +864 -99
- package/src/dialogs.tsx +45 -2
- package/src/settings.ts +133 -15
- package/src/share-flow.ts +33 -19
- package/src/sidebar.tsx +4 -45
- package/src/v2-server.ts +57 -110
package/dist/tui.js
CHANGED
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
};
|
|
2
|
+
// src/tui.tsx
|
|
3
|
+
import { effect as _$effect3 } from "@opentui/solid";
|
|
4
|
+
import { createTextNode as _$createTextNode2 } from "@opentui/solid";
|
|
5
|
+
import { insertNode as _$insertNode3 } from "@opentui/solid";
|
|
6
|
+
import { insert as _$insert3 } from "@opentui/solid";
|
|
7
|
+
import { setProp as _$setProp3 } from "@opentui/solid";
|
|
8
|
+
import { createElement as _$createElement3 } from "@opentui/solid";
|
|
9
|
+
|
|
10
|
+
// src/share-flow.ts
|
|
11
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
13
12
|
|
|
14
13
|
// src/config.ts
|
|
15
14
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
16
15
|
import { basename, dirname, join, relative } from "path";
|
|
17
16
|
import { fileURLToPath } from "url";
|
|
17
|
+
var METADATA_KEY = "openshare";
|
|
18
|
+
var URL_FILE_NONE = "none";
|
|
19
|
+
var DEFAULTS = {
|
|
20
|
+
workerUrl: "",
|
|
21
|
+
createToken: "",
|
|
22
|
+
debounceMs: 1e4,
|
|
23
|
+
completedDelayMs: 5000,
|
|
24
|
+
modelLabel: "$Provider/$Model",
|
|
25
|
+
ttlDays: 90,
|
|
26
|
+
openTasksInNewTab: true,
|
|
27
|
+
authLogin: "",
|
|
28
|
+
authPassword: "",
|
|
29
|
+
spoolDir: "",
|
|
30
|
+
urlFile: "",
|
|
31
|
+
announceStderr: true
|
|
32
|
+
};
|
|
33
|
+
var CONFIG_NAME = "openshare.config.json";
|
|
34
|
+
var LOCAL_NAME = "openshare.local.json";
|
|
18
35
|
function moduleDir() {
|
|
19
36
|
return dirname(fileURLToPath(import.meta.pathname ?? import.meta.url));
|
|
20
37
|
}
|
|
@@ -290,23 +307,6 @@ function globalConfigDir(env) {
|
|
|
290
307
|
const home = process.env.USERPROFILE ?? process.env.HOME ?? "";
|
|
291
308
|
return join(home, ".config", "opencode");
|
|
292
309
|
}
|
|
293
|
-
var METADATA_KEY = "openshare", URL_FILE_NONE = "none", DEFAULTS, CONFIG_NAME = "openshare.config.json", LOCAL_NAME = "openshare.local.json";
|
|
294
|
-
var init_config = __esm(() => {
|
|
295
|
-
DEFAULTS = {
|
|
296
|
-
workerUrl: "",
|
|
297
|
-
createToken: "",
|
|
298
|
-
debounceMs: 1e4,
|
|
299
|
-
completedDelayMs: 5000,
|
|
300
|
-
modelLabel: "$Provider/$Model",
|
|
301
|
-
ttlDays: 90,
|
|
302
|
-
openTasksInNewTab: true,
|
|
303
|
-
authLogin: "",
|
|
304
|
-
authPassword: "",
|
|
305
|
-
spoolDir: "",
|
|
306
|
-
urlFile: "",
|
|
307
|
-
announceStderr: true
|
|
308
|
-
};
|
|
309
|
-
});
|
|
310
310
|
|
|
311
311
|
// src/sidebar.tsx
|
|
312
312
|
import { effect as _$effect } from "@opentui/solid";
|
|
@@ -314,9 +314,6 @@ import { insertNode as _$insertNode } from "@opentui/solid";
|
|
|
314
314
|
import { insert as _$insert } from "@opentui/solid";
|
|
315
315
|
import { setProp as _$setProp } from "@opentui/solid";
|
|
316
316
|
import { createElement as _$createElement } from "@opentui/solid";
|
|
317
|
-
import { appendFileSync } from "fs";
|
|
318
|
-
import { tmpdir } from "os";
|
|
319
|
-
import { join as join2 } from "path";
|
|
320
317
|
function dbg(v, depth = 0) {
|
|
321
318
|
const t = typeof v;
|
|
322
319
|
if (v === null)
|
|
@@ -340,31 +337,9 @@ function dbg(v, depth = 0) {
|
|
|
340
337
|
return "obj:?}";
|
|
341
338
|
}
|
|
342
339
|
}
|
|
343
|
-
function
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
if (Array.isArray(el))
|
|
347
|
-
return `[${el.map((x) => dbgEl(x, depth)).join(",")}]`;
|
|
348
|
-
if (typeof el === "function")
|
|
349
|
-
return "fn-node";
|
|
350
|
-
if (depth >= 4)
|
|
351
|
-
return "\u2026";
|
|
352
|
-
try {
|
|
353
|
-
const rec = el;
|
|
354
|
-
const t = dbg(rec.t ?? rec.type, 2);
|
|
355
|
-
const kids = rec.children;
|
|
356
|
-
const props = Object.keys(rec).filter((k) => !["t", "type", "children"].includes(k));
|
|
357
|
-
return `<${t} ${props.join(" ")}>${kids === undefined ? "" : dbgEl(kids, depth + 1)}</${t}>`;
|
|
358
|
-
} catch (e) {
|
|
359
|
-
return `el?-${dbg(e)}`;
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
function dbgLog(line) {
|
|
363
|
-
try {
|
|
364
|
-
appendFileSync(DBG_FILE, `${new Date().toISOString()} ${line}
|
|
365
|
-
`);
|
|
366
|
-
} catch {}
|
|
367
|
-
}
|
|
340
|
+
function dbgLog(_line) {}
|
|
341
|
+
var GONE_TTL_MS = 60000;
|
|
342
|
+
var goneCache = new Map;
|
|
368
343
|
function goneVerdict(sid, server, shareId) {
|
|
369
344
|
const key = `${sid}|${server}`;
|
|
370
345
|
const cached = goneCache.get(key);
|
|
@@ -379,13 +354,10 @@ function goneVerdict(sid, server, shareId) {
|
|
|
379
354
|
return cached;
|
|
380
355
|
}
|
|
381
356
|
function createSidebarSlot(api, opts) {
|
|
382
|
-
dbgLog(`createSidebarSlot called; api.state=${!!api.state} api.theme=${!!api.theme} api.version=${dbg(api.app?.version)}`);
|
|
383
357
|
return {
|
|
384
358
|
slots: {
|
|
385
359
|
sidebar_title: (ctx, props) => {
|
|
386
|
-
const seq = ++renderSeq;
|
|
387
360
|
try {
|
|
388
|
-
dbgLog(`render#${seq} ctx=${dbg(ctx)} props=${dbg(props)}`);
|
|
389
361
|
const sid = typeof props?.session_id === "string" ? props.session_id : "";
|
|
390
362
|
const title = String(props?.title ?? "") || "Session";
|
|
391
363
|
const session = sid ? api.state.session.get(sid) : undefined;
|
|
@@ -398,7 +370,6 @@ function createSidebarSlot(api, opts) {
|
|
|
398
370
|
const anyGone = verdicts.some((v) => v?.status === "gone");
|
|
399
371
|
const multi = liveCount > 1 ? `\xB7${liveCount} ` : "";
|
|
400
372
|
const theme = ctx?.theme?.current ?? api.theme.current;
|
|
401
|
-
dbgLog(`render#${seq} sid=${dbg(sid)} title=${dbg(title)} session=${session ? "found" : "MISSING"} binding=${binding ? `id=${dbg(binding.id)} enabled=${dbg(binding.enabled)}` : "none"} status=${dbg(status)} theme=${dbg(theme)}`);
|
|
402
373
|
const copyLink = () => {
|
|
403
374
|
if (anyGone) {
|
|
404
375
|
opts?.onShareCommand?.();
|
|
@@ -445,25 +416,25 @@ function createSidebarSlot(api, opts) {
|
|
|
445
416
|
_$effect((_$p) => _$setProp(_el$2, "fg", theme.text, _$p));
|
|
446
417
|
return _el$;
|
|
447
418
|
})();
|
|
448
|
-
dbgLog(`render#${seq} RETURNS ${dbgEl(el)}`);
|
|
449
419
|
return el;
|
|
450
420
|
} catch (error) {
|
|
451
|
-
dbgLog(`render#${seq} THREW ${error?.stack ?? dbg(error)} \u2014 rethrowing`);
|
|
452
421
|
throw error;
|
|
453
422
|
}
|
|
454
423
|
}
|
|
455
424
|
}
|
|
456
425
|
};
|
|
457
426
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
427
|
+
|
|
428
|
+
// src/binding-queue.ts
|
|
429
|
+
var queues = new Map;
|
|
430
|
+
function queueBindingWrite(sid, fn) {
|
|
431
|
+
const prev = queues.get(sid) ?? Promise.resolve();
|
|
432
|
+
const run = prev.then(fn, fn);
|
|
433
|
+
queues.set(sid, run.catch(() => {}));
|
|
434
|
+
return run;
|
|
435
|
+
}
|
|
464
436
|
|
|
465
437
|
// src/share-flow.ts
|
|
466
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
467
438
|
function currentConfig(baseDir = moduleDir()) {
|
|
468
439
|
return loadConfig(baseDir);
|
|
469
440
|
}
|
|
@@ -577,17 +548,19 @@ function hostOf(url) {
|
|
|
577
548
|
}
|
|
578
549
|
}
|
|
579
550
|
async function patchSessionBinding(client, sid, mutator) {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
551
|
+
return queueBindingWrite(sid, async () => {
|
|
552
|
+
const info = await getSession(client, sid);
|
|
553
|
+
if (!info)
|
|
554
|
+
return false;
|
|
555
|
+
const metadata = { ...info.metadata ?? {} };
|
|
556
|
+
const raw = info.metadata?.[METADATA_KEY];
|
|
557
|
+
const binding = raw && typeof raw === "object" ? { ...raw } : { v: 2, servers: {} };
|
|
558
|
+
const servers = binding.servers && typeof binding.servers === "object" ? { ...binding.servers } : {};
|
|
559
|
+
mutator(servers, binding);
|
|
560
|
+
binding.servers = servers;
|
|
561
|
+
metadata[METADATA_KEY] = binding;
|
|
562
|
+
return patchMetadata(client, sid, metadata);
|
|
563
|
+
});
|
|
591
564
|
}
|
|
592
565
|
function bindingOf(info) {
|
|
593
566
|
const all = bindingsOf(info);
|
|
@@ -818,6 +791,7 @@ function adminTokenFor(url, cfg = currentConfig()) {
|
|
|
818
791
|
const entry = serverEntries(cfg).find((s) => s.url === base);
|
|
819
792
|
return entry?.entry.adminToken ?? (cfg.workerUrl && normServerUrl(cfg.workerUrl) === base ? cfg.createToken : "") ?? "";
|
|
820
793
|
}
|
|
794
|
+
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
821
795
|
async function waitForBinding(client, sid, timeoutMs = 25000) {
|
|
822
796
|
const start = Date.now();
|
|
823
797
|
while (Date.now() - start < timeoutMs) {
|
|
@@ -828,7 +802,15 @@ async function waitForBinding(client, sid, timeoutMs = 25000) {
|
|
|
828
802
|
}
|
|
829
803
|
return;
|
|
830
804
|
}
|
|
831
|
-
async function
|
|
805
|
+
async function requestShareConsent(host) {
|
|
806
|
+
return host.ui.showConfirm({
|
|
807
|
+
title: "Share Session",
|
|
808
|
+
message: `Publish this session (and its subagent sessions) to your openshare worker?
|
|
809
|
+
The link lets anyone with it read the conversation.`,
|
|
810
|
+
confirmLabel: "Share"
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
async function shareWithConsent(host, sessionID) {
|
|
832
814
|
if (!sessionID)
|
|
833
815
|
return { ok: false, message: "No active session to share." };
|
|
834
816
|
const cfg = currentConfig();
|
|
@@ -841,9 +823,8 @@ async function runShareFlow(host, sessionID) {
|
|
|
841
823
|
await host.clipboard.write(existing.url);
|
|
842
824
|
return { ok: true, message: `Share URL copied to clipboard: ${existing.url}`, url: existing.url };
|
|
843
825
|
}
|
|
844
|
-
if (!host
|
|
845
|
-
return { ok: false, message: "
|
|
846
|
-
}
|
|
826
|
+
if (!await requestShareConsent(host))
|
|
827
|
+
return { ok: false, message: "Share cancelled." };
|
|
847
828
|
return confirmShare(host, root);
|
|
848
829
|
}
|
|
849
830
|
async function confirmShare(host, rootSessionID) {
|
|
@@ -886,26 +867,6 @@ async function runUnshareFlow(host, sessionID) {
|
|
|
886
867
|
message: deleted === all.length ? `Sharing stopped; ${deleted} share page${deleted === 1 ? "" : "s"} deleted.` : `Sharing stopped (${deleted}/${all.length} pages deleted; the rest will follow via the sweeper).`
|
|
887
868
|
};
|
|
888
869
|
}
|
|
889
|
-
var CONSENT_KV_KEY = "openshare_consent", sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
890
|
-
var init_share_flow = __esm(() => {
|
|
891
|
-
init_config();
|
|
892
|
-
init_sidebar();
|
|
893
|
-
});
|
|
894
|
-
|
|
895
|
-
// src/tui.tsx
|
|
896
|
-
import { effect as _$effect3 } from "@opentui/solid";
|
|
897
|
-
import { createTextNode as _$createTextNode2 } from "@opentui/solid";
|
|
898
|
-
import { insertNode as _$insertNode3 } from "@opentui/solid";
|
|
899
|
-
import { insert as _$insert3 } from "@opentui/solid";
|
|
900
|
-
import { setProp as _$setProp3 } from "@opentui/solid";
|
|
901
|
-
import { createElement as _$createElement3 } from "@opentui/solid";
|
|
902
|
-
|
|
903
|
-
// src/commands.ts
|
|
904
|
-
init_share_flow();
|
|
905
|
-
|
|
906
|
-
// src/settings.ts
|
|
907
|
-
init_config();
|
|
908
|
-
init_share_flow();
|
|
909
870
|
|
|
910
871
|
// src/ui-prefs.ts
|
|
911
872
|
var UI_WIDTH_KV = "openshare.ui-width";
|
|
@@ -1806,33 +1767,93 @@ ${sourcesMessage(sources)}`
|
|
|
1806
1767
|
const sessionRows = async () => {
|
|
1807
1768
|
if (!startAtSession)
|
|
1808
1769
|
return [];
|
|
1809
|
-
const
|
|
1810
|
-
const
|
|
1811
|
-
|
|
1812
|
-
|
|
1770
|
+
const root = await resolveRoot(host.client, startAtSession);
|
|
1771
|
+
const bindings = await readBindings(host.client, root);
|
|
1772
|
+
const live = bindings.filter((b) => b.url && b.enabled !== false);
|
|
1773
|
+
const rows = [];
|
|
1774
|
+
if (live.length === 0) {
|
|
1775
|
+
rows.push({
|
|
1776
|
+
title: "Share this session",
|
|
1813
1777
|
value: "session:share",
|
|
1814
|
-
description:
|
|
1815
|
-
help:
|
|
1778
|
+
description: "set the share flag; the server plugin creates the page",
|
|
1779
|
+
help: "Publishes this session (and its subagent sessions) to the active server. The full history lands in the page \u2014 sharing mid-session is first-class, nothing is missed. You will be asked to confirm before anything is published."
|
|
1780
|
+
});
|
|
1781
|
+
} else if (live.length === 1) {
|
|
1782
|
+
const b = live[0];
|
|
1783
|
+
rows.push({
|
|
1784
|
+
title: "Copy share link",
|
|
1785
|
+
value: "session:copy",
|
|
1786
|
+
description: "copy the share URL to the clipboard",
|
|
1787
|
+
help: `Copies the share URL to the clipboard.
|
|
1788
|
+
|
|
1789
|
+
URL: ${b.url}
|
|
1790
|
+
Share id: ${b.id ?? "?"}`
|
|
1791
|
+
});
|
|
1792
|
+
} else {
|
|
1793
|
+
for (const b of live) {
|
|
1794
|
+
rows.push({
|
|
1795
|
+
title: `Copy link \u2014 ${hostOf(b.url || b.server)}`,
|
|
1796
|
+
value: `session:copy:${b.server}`,
|
|
1797
|
+
description: b.url,
|
|
1798
|
+
help: `Copies this server's share URL to the clipboard.
|
|
1816
1799
|
|
|
1817
|
-
URL: ${
|
|
1818
|
-
Share id: ${
|
|
1800
|
+
URL: ${b.url}
|
|
1801
|
+
Share id: ${b.id}
|
|
1802
|
+
|
|
1803
|
+
Each server carries an independent page of the same session.`
|
|
1804
|
+
});
|
|
1819
1805
|
}
|
|
1820
|
-
|
|
1821
|
-
|
|
1806
|
+
rows.push({
|
|
1807
|
+
title: "Copy all links",
|
|
1808
|
+
value: "session:copyall",
|
|
1809
|
+
description: `${live.length} servers`,
|
|
1810
|
+
help: `Copies every bound server's share URL.
|
|
1811
|
+
|
|
1812
|
+
${live.map((b) => `\u2022 ${b.url}`).join(`
|
|
1813
|
+
`)}`
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
if (live.length) {
|
|
1822
1817
|
rows.push({
|
|
1823
1818
|
title: "Stop sharing",
|
|
1824
1819
|
value: "session:unshare",
|
|
1825
|
-
description:
|
|
1820
|
+
description: live.length > 1 ? `delete every page (${live.length} servers), or leave snapshots` : "delete the page, or leave a snapshot",
|
|
1826
1821
|
danger: true,
|
|
1827
1822
|
help: `Choose per run: DELETE every page server-side (links stop working \u2014 final "gone" state), or LEAVE AS-IS (stop updating; pages stay readable until their TTL \u2014 a frozen snapshot).
|
|
1828
1823
|
|
|
1829
1824
|
URLs:
|
|
1830
|
-
${
|
|
1825
|
+
${live.map((b) => b.url).join(`
|
|
1831
1826
|
`)}
|
|
1832
1827
|
|
|
1833
1828
|
Sharing again later always creates fresh page(s) with new URLs.`
|
|
1834
1829
|
});
|
|
1830
|
+
if (live.length > 1) {
|
|
1831
|
+
rows.push({
|
|
1832
|
+
title: "Stop on one server\u2026",
|
|
1833
|
+
value: "session:unshare:one",
|
|
1834
|
+
description: "delete or snapshot a single server's page",
|
|
1835
|
+
help: `Per-server stop: pick the server, then delete its page or leave it as a read-only snapshot. Other servers are unaffected.
|
|
1836
|
+
|
|
1837
|
+
Bound servers:
|
|
1838
|
+
${live.map((b) => `\u2022 ${b.server} \u2014 ${b.url}`).join(`
|
|
1839
|
+
`)}`
|
|
1840
|
+
});
|
|
1841
|
+
const otherServers = serverEntries().filter((s) => !live.some((b) => b.server === s.url));
|
|
1842
|
+
if (otherServers.length) {
|
|
1843
|
+
rows.push({
|
|
1844
|
+
title: "Share to another server\u2026",
|
|
1845
|
+
value: "session:more",
|
|
1846
|
+
description: otherServers.map((s) => hostOf(s.url)).join(", "),
|
|
1847
|
+
help: `Adds one of your other configured servers as a destination for THIS session: it becomes the active server and the full history is mirrored there within seconds.
|
|
1848
|
+
|
|
1849
|
+
Available:
|
|
1850
|
+
${otherServers.map((s) => `\u2022 ${s.url}`).join(`
|
|
1851
|
+
`)}`
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1854
|
+
}
|
|
1835
1855
|
}
|
|
1856
|
+
const binding = bindings[0];
|
|
1836
1857
|
rows.push({
|
|
1837
1858
|
title: "Session TTL override (days)",
|
|
1838
1859
|
value: "session:ttl",
|
|
@@ -1943,16 +1964,98 @@ Note: no token held \u2014 the plugin enrolls on first contact (public) or needs
|
|
|
1943
1964
|
}
|
|
1944
1965
|
if (choice.startsWith("session:") && startAtSession) {
|
|
1945
1966
|
const root = await resolveRoot(host.client, startAtSession);
|
|
1946
|
-
const
|
|
1967
|
+
const all = await readBindings(host.client, root);
|
|
1968
|
+
const live = all.filter((b) => b.url && b.enabled !== false);
|
|
1969
|
+
const current = all[0];
|
|
1947
1970
|
if (choice === "session:share") {
|
|
1948
1971
|
if (!isConfigured(cfg)) {
|
|
1949
1972
|
await showInfo(host.ui, { title: "Not configured", message: "Connect a server (Servers group) first, then share." });
|
|
1950
1973
|
return;
|
|
1951
1974
|
}
|
|
1952
|
-
const result = await
|
|
1975
|
+
const result = await shareWithConsent(host, startAtSession);
|
|
1953
1976
|
host.ui.toast({ variant: result.ok ? "success" : "error", title: "OpenShare", message: result.message, duration: result.ok ? 8000 : 15000 });
|
|
1954
1977
|
return;
|
|
1955
1978
|
}
|
|
1979
|
+
if (choice === "session:copy" && live[0]) {
|
|
1980
|
+
const okw = await host.clipboard.write(live[0].url);
|
|
1981
|
+
host.ui.toast({ variant: okw ? "success" : "error", title: "OpenShare", message: okw ? `Share URL copied: ${live[0].url}` : `Clipboard failed \u2014 ${live[0].url}` });
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
if (choice.startsWith("session:copy:")) {
|
|
1985
|
+
const b = live.find((x) => `session:copy:${x.server}` === choice);
|
|
1986
|
+
if (b) {
|
|
1987
|
+
const okw = await host.clipboard.write(b.url);
|
|
1988
|
+
host.ui.toast({ variant: okw ? "success" : "error", title: "OpenShare", message: okw ? `Share URL copied: ${b.url}` : `Clipboard failed \u2014 ${b.url}` });
|
|
1989
|
+
}
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
if (choice === "session:copyall") {
|
|
1993
|
+
const joined = live.map((b) => b.url).filter(Boolean).join(`
|
|
1994
|
+
`);
|
|
1995
|
+
const okw = await host.clipboard.write(joined);
|
|
1996
|
+
host.ui.toast({ variant: okw ? "success" : "error", title: "OpenShare", message: okw ? `${live.length} share URL(s) copied` : "Clipboard failed" });
|
|
1997
|
+
return;
|
|
1998
|
+
}
|
|
1999
|
+
if (choice === "session:unshare:one") {
|
|
2000
|
+
const pick = await showSelect(host.ui, {
|
|
2001
|
+
title: "Stop on which server",
|
|
2002
|
+
options: [...live.map((b) => ({ title: hostOf(b.url || b.server), value: b.server, description: b.url })), { title: "Cancel", value: "__cancel" }]
|
|
2003
|
+
});
|
|
2004
|
+
if (!pick || pick === "__cancel")
|
|
2005
|
+
return;
|
|
2006
|
+
const target = live.find((b) => b.server === pick);
|
|
2007
|
+
if (!target)
|
|
2008
|
+
return;
|
|
2009
|
+
const how = await showSelect(host.ui, {
|
|
2010
|
+
title: `Stop sharing \u2014 ${hostOf(target.url || target.server)}`,
|
|
2011
|
+
options: [
|
|
2012
|
+
{ title: "Delete the page", value: "delete", description: "this link stops working for everyone", danger: true },
|
|
2013
|
+
{ title: "Leave as-is (snapshot)", value: "leave", description: "stop updating; the page stays until its TTL" },
|
|
2014
|
+
{ title: "Cancel", value: "__cancel" }
|
|
2015
|
+
]
|
|
2016
|
+
});
|
|
2017
|
+
if (!how || how === "__cancel")
|
|
2018
|
+
return;
|
|
2019
|
+
if (how === "leave") {
|
|
2020
|
+
await patchSessionBinding(host.client, root, (servers) => {
|
|
2021
|
+
delete servers[target.server];
|
|
2022
|
+
servers[target.server] = { optedOut: true };
|
|
2023
|
+
});
|
|
2024
|
+
host.ui.toast({ variant: "success", title: "OpenShare", message: `${hostOf(target.url || target.server)} left as-is \u2014 a snapshot until its TTL.` });
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
const targetEntry = all.find((b) => b.server === target.server);
|
|
2028
|
+
if (!targetEntry?.secret) {
|
|
2029
|
+
host.ui.toast({ variant: "error", title: "OpenShare", message: "No secret for this page \u2014 cannot delete it server-side; binding cleared." });
|
|
2030
|
+
await patchSessionBinding(host.client, root, (servers) => {
|
|
2031
|
+
delete servers[target.server];
|
|
2032
|
+
});
|
|
2033
|
+
return;
|
|
2034
|
+
}
|
|
2035
|
+
const gone = await workerDeleteShare(target.server, target.id, targetEntry.secret);
|
|
2036
|
+
await patchSessionBinding(host.client, root, (servers) => {
|
|
2037
|
+
delete servers[target.server];
|
|
2038
|
+
});
|
|
2039
|
+
host.ui.toast({ variant: gone ? "success" : "warning", title: "OpenShare", message: gone ? `Page deleted on ${hostOf(target.url || target.server)}.` : `Could not delete the page (it may already be gone) \u2014 binding cleared.` });
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
if (choice === "session:more") {
|
|
2043
|
+
const otherServers = serverEntries().filter((s) => !live.some((b) => b.server === s.url));
|
|
2044
|
+
if (!otherServers.length)
|
|
2045
|
+
return;
|
|
2046
|
+
const pick = await showSelect(host.ui, {
|
|
2047
|
+
title: "Share to another server",
|
|
2048
|
+
options: [...otherServers.map((s) => ({ title: hostOf(s.url), value: s.url, description: s.entry.label })), { title: "Cancel", value: "__cancel" }]
|
|
2049
|
+
});
|
|
2050
|
+
if (!pick || pick === "__cancel")
|
|
2051
|
+
return;
|
|
2052
|
+
for (const s of serverEntries())
|
|
2053
|
+
if (s.url !== pick)
|
|
2054
|
+
saveServerEntry(s.url, { active: undefined });
|
|
2055
|
+
saveServerEntry(pick, { active: true });
|
|
2056
|
+
host.ui.toast({ variant: "success", title: "OpenShare", message: `${hostOf(pick)} activated \u2014 this session mirrors there within seconds.` });
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
1956
2059
|
if (choice === "session:unshare") {
|
|
1957
2060
|
const how = await showSelect(host.ui, {
|
|
1958
2061
|
title: "Stop sharing",
|
|
@@ -2141,7 +2244,6 @@ Connect now (URL + access code if the hoster gave you one), or open the full set
|
|
|
2141
2244
|
}
|
|
2142
2245
|
|
|
2143
2246
|
// src/commands.ts
|
|
2144
|
-
init_sidebar();
|
|
2145
2247
|
var PALETTE_CATEGORY = "OpenShare";
|
|
2146
2248
|
function finish(host, result) {
|
|
2147
2249
|
try {
|
|
@@ -2154,20 +2256,20 @@ function finish(host, result) {
|
|
|
2154
2256
|
duration: result.ok ? 8000 : 15000
|
|
2155
2257
|
});
|
|
2156
2258
|
}
|
|
2157
|
-
async function
|
|
2158
|
-
const sid = sessionID();
|
|
2159
|
-
if (!sid)
|
|
2160
|
-
return;
|
|
2161
|
-
const root = await resolveRoot(host.client, sid);
|
|
2259
|
+
async function sweepBindings(host, sid, root) {
|
|
2162
2260
|
let all = await readBindings(host.client, root);
|
|
2261
|
+
let goneShown = false;
|
|
2163
2262
|
const collided = all.find((b) => b.collided);
|
|
2164
|
-
if (collided)
|
|
2165
|
-
|
|
2263
|
+
if (collided) {
|
|
2264
|
+
await showCollisionDialog(host, root, collided.server, collided.id, collided.collided ?? "");
|
|
2265
|
+
return null;
|
|
2266
|
+
}
|
|
2166
2267
|
const live = all.filter((b) => b.url && b.enabled !== false && b.id);
|
|
2167
2268
|
if (live.length) {
|
|
2168
2269
|
const verdicts = await Promise.all(live.map((b) => verifyShare(b.server, b.id)));
|
|
2169
2270
|
const gone = live.filter((b, i) => verdicts[i] === "gone");
|
|
2170
2271
|
if (gone.length) {
|
|
2272
|
+
goneShown = true;
|
|
2171
2273
|
await clearGoneBindingEntries(host, root, gone.map((b) => b.server));
|
|
2172
2274
|
all = all.filter((b) => !gone.some((g) => g.server === b.server));
|
|
2173
2275
|
const list = gone.map((b) => `\u2022 ${hostOf(b.url || b.server)} \u2014 ${b.url}`).join(`
|
|
@@ -2187,258 +2289,14 @@ ${list}` }
|
|
|
2187
2289
|
]
|
|
2188
2290
|
});
|
|
2189
2291
|
if (choice === "again") {
|
|
2190
|
-
finish(host, await
|
|
2191
|
-
return;
|
|
2292
|
+
finish(host, await shareWithConsent(host, sid));
|
|
2293
|
+
return null;
|
|
2192
2294
|
}
|
|
2193
2295
|
if (all.length === 0)
|
|
2194
|
-
return;
|
|
2195
|
-
}
|
|
2196
|
-
}
|
|
2197
|
-
if (all.length === 0)
|
|
2198
|
-
return;
|
|
2199
|
-
const otherServers = serverEntries().filter((s) => !all.some((b) => b.server === s.url));
|
|
2200
|
-
if (all.length === 1 && otherServers.length === 0) {
|
|
2201
|
-
const b = all[0];
|
|
2202
|
-
host.ui.showMenu({
|
|
2203
|
-
title: "Session is shared",
|
|
2204
|
-
hint: b.url,
|
|
2205
|
-
options: [
|
|
2206
|
-
{
|
|
2207
|
-
title: "Copy share link",
|
|
2208
|
-
value: "copy",
|
|
2209
|
-
description: "copy the share URL to the clipboard",
|
|
2210
|
-
help: `Copies the share URL to the clipboard (navigator + OSC52 fallback).
|
|
2211
|
-
|
|
2212
|
-
URL: ${b.url}
|
|
2213
|
-
Server: ${b.server}
|
|
2214
|
-
Share id: ${b.id}
|
|
2215
|
-
|
|
2216
|
-
Anyone with the link can read the page (plus the login, if read auth is set).`
|
|
2217
|
-
},
|
|
2218
|
-
{
|
|
2219
|
-
title: "Stop sharing",
|
|
2220
|
-
value: "stop",
|
|
2221
|
-
description: "Delete the share page \u2014 the link stops working for everyone",
|
|
2222
|
-
help: `Deletes the page on ${hostOf(b.url || b.server)} server-side and clears this session's share flag. Viewers with the link see a final "gone" state. This cannot be undone \u2014 sharing again creates a NEW page with a new URL.`
|
|
2223
|
-
},
|
|
2224
|
-
{ title: "Cancel", value: "cancel" }
|
|
2225
|
-
]
|
|
2226
|
-
}).then(async (choice) => {
|
|
2227
|
-
if (choice === "copy") {
|
|
2228
|
-
const ok = await host.clipboard.write(b.url);
|
|
2229
|
-
finish(host, ok ? { ok: true, message: `Share URL copied to clipboard: ${b.url}`, url: b.url } : { ok: false, message: `Could not write the clipboard \u2014 link: ${b.url}`, url: b.url });
|
|
2230
|
-
return;
|
|
2231
|
-
}
|
|
2232
|
-
if (choice === "stop") {
|
|
2233
|
-
const how = await host.ui.showMenu({
|
|
2234
|
-
title: "Stop sharing",
|
|
2235
|
-
hint: b.url,
|
|
2236
|
-
options: [
|
|
2237
|
-
{
|
|
2238
|
-
title: "Delete the page",
|
|
2239
|
-
value: "delete",
|
|
2240
|
-
description: "the link stops working for everyone",
|
|
2241
|
-
danger: true,
|
|
2242
|
-
help: `Deletes the page on ${hostOf(b.url || b.server)} and clears this session's share flag. Viewers with the link see a final "gone" state. Sharing again later creates a fresh page with a new URL.`
|
|
2243
|
-
},
|
|
2244
|
-
{
|
|
2245
|
-
title: "Leave as-is (snapshot)",
|
|
2246
|
-
value: "leave",
|
|
2247
|
-
description: "stop updating; the page stays until its TTL",
|
|
2248
|
-
help: `Stops mirroring WITHOUT deleting anything \u2014 viewers keep reading the snapshot until the share's TTL expires. Useful for handing someone a frozen copy while you keep working.`
|
|
2249
|
-
},
|
|
2250
|
-
{ title: "Cancel", value: "cancel" }
|
|
2251
|
-
]
|
|
2252
|
-
});
|
|
2253
|
-
if (!how || how === "cancel")
|
|
2254
|
-
return;
|
|
2255
|
-
if (how === "leave") {
|
|
2256
|
-
await patchSessionBinding(host.client, root, (servers) => {
|
|
2257
|
-
delete servers[b.server];
|
|
2258
|
-
servers[b.server] = { optedOut: true };
|
|
2259
|
-
});
|
|
2260
|
-
finish(host, { ok: true, message: `${hostOf(b.url || b.server)} left as-is \u2014 a snapshot until its TTL.` });
|
|
2261
|
-
return;
|
|
2262
|
-
}
|
|
2263
|
-
const yes = await host.ui.showConfirm({
|
|
2264
|
-
title: "Delete the share page",
|
|
2265
|
-
message: `Delete the page on ${hostOf(b.url || b.server)}?
|
|
2266
|
-
The link stops working for everyone.`,
|
|
2267
|
-
confirmLabel: "Delete",
|
|
2268
|
-
danger: true
|
|
2269
|
-
});
|
|
2270
|
-
if (!yes) {
|
|
2271
|
-
host.ui.dialog.clear();
|
|
2272
|
-
return;
|
|
2273
|
-
}
|
|
2274
|
-
finish(host, await runUnshareFlow(host, sessionID()));
|
|
2275
|
-
}
|
|
2276
|
-
});
|
|
2277
|
-
return;
|
|
2278
|
-
}
|
|
2279
|
-
const options = all.map((b) => ({
|
|
2280
|
-
title: `Copy link \u2014 ${hostOf(b.url || b.server)}`,
|
|
2281
|
-
value: `copy:${b.server}`,
|
|
2282
|
-
description: b.url,
|
|
2283
|
-
help: `Copies this server's share URL to the clipboard.
|
|
2284
|
-
|
|
2285
|
-
URL: ${b.url}
|
|
2286
|
-
Server: ${b.server}
|
|
2287
|
-
Share id: ${b.id}
|
|
2288
|
-
|
|
2289
|
-
Each server carries an independent page of the same session.`
|
|
2290
|
-
}));
|
|
2291
|
-
options.push({
|
|
2292
|
-
title: "Manage per server\u2026",
|
|
2293
|
-
value: "manage",
|
|
2294
|
-
description: `${all.length} servers \u2014 stop one, leave a snapshot`,
|
|
2295
|
-
help: `Per-server actions: copy the link, stop sharing there (page deleted), or leave it as a read-only snapshot (updates stop, the page lives out its TTL). Other servers are unaffected.
|
|
2296
|
-
|
|
2297
|
-
Bound servers:
|
|
2298
|
-
${all.map((b) => `\u2022 ${b.server} \u2014 ${b.url}`).join(`
|
|
2299
|
-
`)}`
|
|
2300
|
-
});
|
|
2301
|
-
if (otherServers.length) {
|
|
2302
|
-
options.push({
|
|
2303
|
-
title: "Share to another server\u2026",
|
|
2304
|
-
value: "more",
|
|
2305
|
-
description: otherServers.map((s) => hostOf(s.url)).join(", "),
|
|
2306
|
-
help: `Adds one of your other configured servers as a destination for THIS session: it becomes the active server and the full history is mirrored there within seconds.
|
|
2307
|
-
|
|
2308
|
-
Available:
|
|
2309
|
-
${otherServers.map((s) => `\u2022 ${s.url}`).join(`
|
|
2310
|
-
`)}`
|
|
2311
|
-
});
|
|
2312
|
-
}
|
|
2313
|
-
options.push({
|
|
2314
|
-
title: "Stop sharing (all)",
|
|
2315
|
-
value: "stop-all",
|
|
2316
|
-
description: "delete every server's page, or leave snapshots",
|
|
2317
|
-
danger: true,
|
|
2318
|
-
help: `Deletes the page on all ${all.length} servers (or leaves them as read-only snapshots \u2014 your choice on the next screen) and clears the share flag. Sharing again creates fresh pages.`
|
|
2319
|
-
});
|
|
2320
|
-
options.push({ title: "Cancel", value: "cancel" });
|
|
2321
|
-
const choice = await host.ui.showMenu({ title: `Session is shared \xB7 ${all.length} servers`, options });
|
|
2322
|
-
if (!choice || choice === "cancel")
|
|
2323
|
-
return;
|
|
2324
|
-
if (choice.startsWith("copy:")) {
|
|
2325
|
-
const b = all.find((x) => `copy:${x.server}` === choice);
|
|
2326
|
-
if (b) {
|
|
2327
|
-
const ok = await host.clipboard.write(b.url);
|
|
2328
|
-
finish(host, ok ? { ok: true, message: `Share URL copied: ${b.url}`, url: b.url } : { ok: false, message: `Clipboard failed \u2014 ${b.url}`, url: b.url });
|
|
2329
|
-
}
|
|
2330
|
-
return;
|
|
2331
|
-
}
|
|
2332
|
-
if (choice === "stop-all") {
|
|
2333
|
-
const how = await host.ui.showMenu({
|
|
2334
|
-
title: `Stop sharing \xB7 ${all.length} servers`,
|
|
2335
|
-
options: [
|
|
2336
|
-
{ title: "Delete every page", value: "delete", description: "all links stop working", danger: true, help: `Deletes the page on every bound server (${all.map((b) => hostOf(b.url || b.server)).join(", ")}) and clears the share flag.` },
|
|
2337
|
-
{ title: "Leave all as-is (snapshots)", value: "leave", description: "stop updating; pages stay until their TTLs", help: `Stops mirroring everywhere without deleting anything \u2014 every page stays readable as a frozen snapshot until its TTL expires.` },
|
|
2338
|
-
{ title: "Cancel", value: "cancel" }
|
|
2339
|
-
]
|
|
2340
|
-
});
|
|
2341
|
-
if (!how || how === "cancel")
|
|
2342
|
-
return;
|
|
2343
|
-
if (how === "leave") {
|
|
2344
|
-
await patchSessionBinding(host.client, root, (servers) => {
|
|
2345
|
-
for (const b of all) {
|
|
2346
|
-
delete servers[b.server];
|
|
2347
|
-
servers[b.server] = { optedOut: true };
|
|
2348
|
-
}
|
|
2349
|
-
});
|
|
2350
|
-
finish(host, { ok: true, message: `${all.length} server(s) left as-is \u2014 snapshots until their TTLs.` });
|
|
2351
|
-
return;
|
|
2352
|
-
}
|
|
2353
|
-
const yes = await host.ui.showConfirm({
|
|
2354
|
-
title: "Stop sharing",
|
|
2355
|
-
message: `Delete the share pages on all ${all.length} servers? The links stop working for everyone.`,
|
|
2356
|
-
confirmLabel: "Stop all",
|
|
2357
|
-
danger: true
|
|
2358
|
-
});
|
|
2359
|
-
if (!yes)
|
|
2360
|
-
return;
|
|
2361
|
-
finish(host, await runUnshareFlow(host, sessionID()));
|
|
2362
|
-
return;
|
|
2363
|
-
}
|
|
2364
|
-
if (choice === "more") {
|
|
2365
|
-
const pick = await host.ui.showMenu({
|
|
2366
|
-
title: "Share to another server",
|
|
2367
|
-
options: [...otherServers.map((s) => ({ title: hostOf(s.url), value: s.url, description: s.entry.label })), { title: "Cancel", value: "__cancel" }]
|
|
2368
|
-
});
|
|
2369
|
-
if (!pick || pick === "__cancel")
|
|
2370
|
-
return;
|
|
2371
|
-
await Promise.resolve().then(() => init_share_flow());
|
|
2372
|
-
for (const s of serverEntries())
|
|
2373
|
-
if (s.url !== pick)
|
|
2374
|
-
saveServerEntry(s.url, { active: undefined });
|
|
2375
|
-
saveServerEntry(pick, { active: true });
|
|
2376
|
-
finish(host, { ok: true, message: `${hostOf(pick)} activated \u2014 this session mirrors there within seconds (new sessions share to it too).` });
|
|
2377
|
-
return;
|
|
2378
|
-
}
|
|
2379
|
-
if (choice === "manage") {
|
|
2380
|
-
const pick = await host.ui.showMenu({
|
|
2381
|
-
title: "Manage server",
|
|
2382
|
-
options: [...all.map((b) => ({ title: hostOf(b.url || b.server), value: b.server, description: b.url })), { title: "Cancel", value: "__cancel" }]
|
|
2383
|
-
});
|
|
2384
|
-
if (!pick || pick === "__cancel")
|
|
2385
|
-
return;
|
|
2386
|
-
const b = all.find((x) => x.server === pick);
|
|
2387
|
-
if (!b)
|
|
2388
|
-
return;
|
|
2389
|
-
const action = await host.ui.showMenu({
|
|
2390
|
-
title: `${hostOf(b.url || b.server)}`,
|
|
2391
|
-
hint: b.url,
|
|
2392
|
-
options: [
|
|
2393
|
-
{
|
|
2394
|
-
title: "Copy link",
|
|
2395
|
-
value: "copy",
|
|
2396
|
-
description: "copy the share URL",
|
|
2397
|
-
help: `Copies this server's share URL to the clipboard.
|
|
2398
|
-
|
|
2399
|
-
URL: ${b.url}
|
|
2400
|
-
Share id: ${b.id}`
|
|
2401
|
-
},
|
|
2402
|
-
{
|
|
2403
|
-
title: "Stop sharing here",
|
|
2404
|
-
value: "stop",
|
|
2405
|
-
description: "delete THIS server's page; other servers unaffected",
|
|
2406
|
-
help: `Deletes the page on ${hostOf(b.url || b.server)} and retires this server for the session (the engine stops mirroring here). Other servers keep streaming. Sharing here again later creates a new page.`
|
|
2407
|
-
},
|
|
2408
|
-
{
|
|
2409
|
-
title: "Leave as-is (snapshot)",
|
|
2410
|
-
value: "leave",
|
|
2411
|
-
description: "stop updating it; the page stays until its TTL",
|
|
2412
|
-
help: `Stops mirroring to ${hostOf(b.url || b.server)} WITHOUT deleting anything \u2014 viewers keep reading the snapshot until the share's TTL expires. Useful for handing someone a frozen copy while you keep working.`
|
|
2413
|
-
},
|
|
2414
|
-
{ title: "Cancel", value: "cancel" }
|
|
2415
|
-
]
|
|
2416
|
-
});
|
|
2417
|
-
if (!action || action === "cancel")
|
|
2418
|
-
return;
|
|
2419
|
-
if (action === "copy") {
|
|
2420
|
-
const ok = await host.clipboard.write(b.url);
|
|
2421
|
-
finish(host, ok ? { ok: true, message: `Share URL copied: ${b.url}`, url: b.url } : { ok: false, message: `Clipboard failed \u2014 ${b.url}`, url: b.url });
|
|
2422
|
-
return;
|
|
2423
|
-
}
|
|
2424
|
-
if (action === "stop") {
|
|
2425
|
-
const deleted = await workerDeleteShare(b.server || b.url.slice(0, b.url.indexOf("/s/")), b.id, b.secret);
|
|
2426
|
-
await patchSessionBinding(host.client, root, (servers) => {
|
|
2427
|
-
delete servers[b.server];
|
|
2428
|
-
servers[b.server] = { optedOut: true };
|
|
2429
|
-
});
|
|
2430
|
-
finish(host, { ok: true, message: deleted ? `Stopped sharing on ${hostOf(b.url || b.server)} \u2014 page deleted.` : `Stopped sharing on ${hostOf(b.url || b.server)} (page deletion follows via the sweeper).` });
|
|
2431
|
-
return;
|
|
2432
|
-
}
|
|
2433
|
-
if (action === "leave") {
|
|
2434
|
-
await patchSessionBinding(host.client, root, (servers) => {
|
|
2435
|
-
delete servers[b.server];
|
|
2436
|
-
servers[b.server] = { optedOut: true };
|
|
2437
|
-
});
|
|
2438
|
-
finish(host, { ok: true, message: `${hostOf(b.url || b.server)} left as-is \u2014 a snapshot until its TTL.` });
|
|
2439
|
-
return;
|
|
2296
|
+
return { all, goneShown };
|
|
2440
2297
|
}
|
|
2441
2298
|
}
|
|
2299
|
+
return { all, goneShown };
|
|
2442
2300
|
}
|
|
2443
2301
|
async function showCollisionDialog(host, root, server, shareId, error) {
|
|
2444
2302
|
const debugInfo = JSON.stringify({ server, shareId, error, sessionID: root, plugin: "openshare", time: new Date().toISOString() }, null, 2);
|
|
@@ -2515,39 +2373,17 @@ function buildShareCommand(host, sessionID) {
|
|
|
2515
2373
|
return;
|
|
2516
2374
|
}
|
|
2517
2375
|
const root = await resolveRoot(host.client, sid);
|
|
2518
|
-
const
|
|
2519
|
-
|
|
2520
|
-
dbgLog(`/share gate: sid=${sid} root=${root} bindings=${bindings.length}`);
|
|
2521
|
-
if (bindings.some((b) => b.url && b.enabled !== false)) {
|
|
2522
|
-
await showShareManager(host, sessionID);
|
|
2523
|
-
host.ui.dialog.clear();
|
|
2376
|
+
const swept = await sweepBindings(host, sid, root);
|
|
2377
|
+
if (swept === null)
|
|
2524
2378
|
return;
|
|
2525
|
-
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
const yes = await host.ui.showConfirm({
|
|
2529
|
-
title: "Share Session",
|
|
2530
|
-
message: `Publish this session (and its subagent sessions) to your openshare worker?
|
|
2531
|
-
The link lets anyone with it read the conversation.`,
|
|
2532
|
-
confirmLabel: "Share"
|
|
2533
|
-
});
|
|
2534
|
-
if (!yes)
|
|
2535
|
-
return;
|
|
2536
|
-
try {
|
|
2537
|
-
host.kv.set(CONSENT_KV_KEY, true);
|
|
2538
|
-
dbgLog("consent kv.set ok");
|
|
2539
|
-
} catch (e) {
|
|
2540
|
-
dbgLog(`consent kv.set THREW: ${e?.message ?? e}`);
|
|
2541
|
-
}
|
|
2542
|
-
try {
|
|
2543
|
-
finish(host, await confirmShare(host, root));
|
|
2544
|
-
} catch (e) {
|
|
2545
|
-
dbgLog(`confirmShare rejected: ${e?.stack ?? e}`);
|
|
2546
|
-
finish(host, { ok: false, message: "Share failed (see debug log)." });
|
|
2547
|
-
}
|
|
2379
|
+
const { all, goneShown } = swept;
|
|
2380
|
+
if (all.some((b) => b.url && b.enabled !== false)) {
|
|
2381
|
+
await settingsDialog(host, sid);
|
|
2548
2382
|
return;
|
|
2549
2383
|
}
|
|
2550
|
-
|
|
2384
|
+
if (goneShown && all.length === 0)
|
|
2385
|
+
return;
|
|
2386
|
+
finish(host, await shareWithConsent(host, sid));
|
|
2551
2387
|
}
|
|
2552
2388
|
};
|
|
2553
2389
|
}
|
|
@@ -2572,11 +2408,7 @@ function registerCommands(host, sessionID) {
|
|
|
2572
2408
|
});
|
|
2573
2409
|
}
|
|
2574
2410
|
|
|
2575
|
-
// src/tui.tsx
|
|
2576
|
-
init_sidebar();
|
|
2577
|
-
|
|
2578
2411
|
// src/dialogs.tsx
|
|
2579
|
-
init_sidebar();
|
|
2580
2412
|
import { use as _$use } from "@opentui/solid";
|
|
2581
2413
|
import { effect as _$effect2 } from "@opentui/solid";
|
|
2582
2414
|
import { memo as _$memo } from "@opentui/solid";
|
|
@@ -3337,13 +3169,36 @@ function MenuDialog(props) {
|
|
|
3337
3169
|
}
|
|
3338
3170
|
});
|
|
3339
3171
|
}
|
|
3172
|
+
var pendingDialog;
|
|
3173
|
+
function claimDialog(api) {
|
|
3174
|
+
if (pendingDialog) {
|
|
3175
|
+
try {
|
|
3176
|
+
pendingDialog();
|
|
3177
|
+
} catch {}
|
|
3178
|
+
try {
|
|
3179
|
+
api.ui.dialog.clear();
|
|
3180
|
+
} catch {}
|
|
3181
|
+
}
|
|
3182
|
+
let released = false;
|
|
3183
|
+
const release = () => {
|
|
3184
|
+
if (released)
|
|
3185
|
+
return;
|
|
3186
|
+
released = true;
|
|
3187
|
+
if (pendingDialog === release)
|
|
3188
|
+
pendingDialog = undefined;
|
|
3189
|
+
};
|
|
3190
|
+
pendingDialog = release;
|
|
3191
|
+
return release;
|
|
3192
|
+
}
|
|
3340
3193
|
function showMenuOnce(api, props) {
|
|
3341
3194
|
return new Promise((resolve) => {
|
|
3342
3195
|
let settled = false;
|
|
3196
|
+
const release = claimDialog(api);
|
|
3343
3197
|
const done = (value, _clear = false) => {
|
|
3344
3198
|
if (settled)
|
|
3345
3199
|
return;
|
|
3346
3200
|
settled = true;
|
|
3201
|
+
release();
|
|
3347
3202
|
dbgLog(`menu(${props.title}) -> ${value === undefined ? "cancel" : String(value)}`);
|
|
3348
3203
|
resolve(value);
|
|
3349
3204
|
};
|
|
@@ -3369,10 +3224,12 @@ function showMenuOnce(api, props) {
|
|
|
3369
3224
|
function showInfoDialog(api, props) {
|
|
3370
3225
|
return new Promise((resolve) => {
|
|
3371
3226
|
let settled = false;
|
|
3227
|
+
const release = claimDialog(api);
|
|
3372
3228
|
const done = (value) => {
|
|
3373
3229
|
if (settled)
|
|
3374
3230
|
return;
|
|
3375
3231
|
settled = true;
|
|
3232
|
+
release();
|
|
3376
3233
|
resolve(value);
|
|
3377
3234
|
};
|
|
3378
3235
|
api.ui.dialog.replace(() => _$createComponent(InfoDialog, {
|
|
@@ -3602,13 +3459,17 @@ function InfoDialog(props) {
|
|
|
3602
3459
|
_$insertNode2(_el$44, _el$45);
|
|
3603
3460
|
_$insert2(_el$45, () => action.title);
|
|
3604
3461
|
_$effect2((_p$) => {
|
|
3605
|
-
var _v$12 = action.danger ? theme().error : theme().primary, _v$13 = theme().background;
|
|
3462
|
+
var _v$12 = action.key === "enter" ? action.danger ? theme().error : theme().primary : undefined, _v$13 = action.key === "enter" ? undefined : true, _v$14 = action.danger ? theme().error : theme().textMuted, _v$15 = action.key === "enter" ? theme().background : action.danger ? theme().error : theme().textMuted;
|
|
3606
3463
|
_v$12 !== _p$.e && (_p$.e = _$setProp2(_el$43, "backgroundColor", _v$12, _p$.e));
|
|
3607
|
-
_v$13 !== _p$.t && (_p$.t = _$setProp2(_el$
|
|
3464
|
+
_v$13 !== _p$.t && (_p$.t = _$setProp2(_el$43, "border", _v$13, _p$.t));
|
|
3465
|
+
_v$14 !== _p$.a && (_p$.a = _$setProp2(_el$43, "borderColor", _v$14, _p$.a));
|
|
3466
|
+
_v$15 !== _p$.o && (_p$.o = _$setProp2(_el$44, "fg", _v$15, _p$.o));
|
|
3608
3467
|
return _p$;
|
|
3609
3468
|
}, {
|
|
3610
3469
|
e: undefined,
|
|
3611
|
-
t: undefined
|
|
3470
|
+
t: undefined,
|
|
3471
|
+
a: undefined,
|
|
3472
|
+
o: undefined
|
|
3612
3473
|
});
|
|
3613
3474
|
return _el$43;
|
|
3614
3475
|
})()
|
|
@@ -3743,10 +3604,10 @@ function PromptDialog(props) {
|
|
|
3743
3604
|
_$insert2(_el$47, () => text() || props.placeholder || "");
|
|
3744
3605
|
_$insertNode2(_el$48, _$createTextNode(`\u258C`));
|
|
3745
3606
|
_$effect2((_p$) => {
|
|
3746
|
-
var _v$
|
|
3747
|
-
_v$
|
|
3748
|
-
_v$
|
|
3749
|
-
_v$
|
|
3607
|
+
var _v$16 = theme().backgroundPanel, _v$17 = text() ? theme().text : theme().textMuted, _v$18 = theme().accent;
|
|
3608
|
+
_v$16 !== _p$.e && (_p$.e = _$setProp2(_el$46, "backgroundColor", _v$16, _p$.e));
|
|
3609
|
+
_v$17 !== _p$.t && (_p$.t = _$setProp2(_el$47, "fg", _v$17, _p$.t));
|
|
3610
|
+
_v$18 !== _p$.a && (_p$.a = _$setProp2(_el$48, "fg", _v$18, _p$.a));
|
|
3750
3611
|
return _p$;
|
|
3751
3612
|
}, {
|
|
3752
3613
|
e: undefined,
|
|
@@ -3767,10 +3628,12 @@ function PromptDialog(props) {
|
|
|
3767
3628
|
function showPromptOnce(api, props) {
|
|
3768
3629
|
return new Promise((resolve) => {
|
|
3769
3630
|
let settled = false;
|
|
3631
|
+
const release = claimDialog(api);
|
|
3770
3632
|
const done = (value, _clear = false) => {
|
|
3771
3633
|
if (settled)
|
|
3772
3634
|
return;
|
|
3773
3635
|
settled = true;
|
|
3636
|
+
release();
|
|
3774
3637
|
dbgLog(`prompt(${props.title}) -> ${value === undefined ? "cancel" : `len=${value.length}`}`);
|
|
3775
3638
|
resolve(value);
|
|
3776
3639
|
};
|
|
@@ -3805,10 +3668,12 @@ function toMenuOptions(options) {
|
|
|
3805
3668
|
function showSizePickerOnce(api, current) {
|
|
3806
3669
|
return new Promise((resolve) => {
|
|
3807
3670
|
let settled = false;
|
|
3671
|
+
const release = claimDialog(api);
|
|
3808
3672
|
const done = (value) => {
|
|
3809
3673
|
if (settled)
|
|
3810
3674
|
return;
|
|
3811
3675
|
settled = true;
|
|
3676
|
+
release();
|
|
3812
3677
|
resolve(value);
|
|
3813
3678
|
};
|
|
3814
3679
|
api.ui.dialog.replace(() => _$createComponent(SizePickerDialog, {
|
|
@@ -4023,9 +3888,9 @@ function SizePickerDialog(props) {
|
|
|
4023
3888
|
_$insertNode2(_el$56, _el$57);
|
|
4024
3889
|
_$insert2(_el$56, height, _el$57);
|
|
4025
3890
|
_$effect2((_p$) => {
|
|
4026
|
-
var _v$
|
|
4027
|
-
_v$
|
|
4028
|
-
_v$
|
|
3891
|
+
var _v$19 = theme().textMuted, _v$20 = theme().accent;
|
|
3892
|
+
_v$19 !== _p$.e && (_p$.e = _$setProp2(_el$51, "fg", _v$19, _p$.e));
|
|
3893
|
+
_v$20 !== _p$.t && (_p$.t = _$setProp2(_el$55, "fg", _v$20, _p$.t));
|
|
4029
3894
|
return _p$;
|
|
4030
3895
|
}, {
|
|
4031
3896
|
e: undefined,
|
|
@@ -4058,9 +3923,9 @@ function SizePickerDialog(props) {
|
|
|
4058
3923
|
_$insertNode2(_el$61, _el$63);
|
|
4059
3924
|
_$insert2(_el$61, HEIGHT_PERCENT_MAX, _el$63);
|
|
4060
3925
|
_$effect2((_p$) => {
|
|
4061
|
-
var _v$
|
|
4062
|
-
_v$
|
|
4063
|
-
_v$
|
|
3926
|
+
var _v$21 = theme().textMuted, _v$22 = theme().textMuted;
|
|
3927
|
+
_v$21 !== _p$.e && (_p$.e = _$setProp2(_el$59, "fg", _v$21, _p$.e));
|
|
3928
|
+
_v$22 !== _p$.t && (_p$.t = _$setProp2(_el$61, "fg", _v$22, _p$.t));
|
|
4064
3929
|
return _p$;
|
|
4065
3930
|
}, {
|
|
4066
3931
|
e: undefined,
|