@mirrowel/opencode-openshare 0.1.0 → 0.2.0-dev.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.
- package/README.md +36 -30
- package/ci/openshare.config.json +7 -2
- package/ci/openshare.js +625 -197
- package/dist/config.d.ts +5 -1
- package/dist/config.js +9 -2
- package/dist/contract.d.ts +156 -0
- package/dist/contract.js +263 -0
- package/dist/core.js +411 -55
- package/dist/dialogs.js +38 -0
- package/dist/share-flow.js +2 -2
- package/dist/sidebar.d.ts +3 -1
- package/dist/sidebar.js +3 -17
- package/dist/tui.js +73 -121
- package/dist/v2-server.d.ts +0 -27
- package/dist/v2-server.js +6 -121
- package/openshare.config.json +1 -1
- package/package.json +4 -2
- package/src/banner.txt +7 -4
- package/src/config.ts +8 -2
- package/src/contract.ts +320 -0
- package/src/core.ts +403 -56
- package/src/dialogs.tsx +34 -0
- package/src/share-flow.ts +2 -2
- package/src/sidebar.tsx +4 -21
- package/src/tui.tsx +1 -1
- package/src/v2-server.ts +7 -110
package/dist/dialogs.js
CHANGED
|
@@ -435,6 +435,36 @@ export function MenuDialog(props) {
|
|
|
435
435
|
props.onDone(undefined);
|
|
436
436
|
} })] }));
|
|
437
437
|
}
|
|
438
|
+
/** Guard against stacked plugin dialogs. `dialog.replace` is documented as
|
|
439
|
+
* an atomic swap, but on some host builds a second replace while a dialog is
|
|
440
|
+
* still pending leaves BOTH renderers mounted - the reader sees two menus
|
|
441
|
+
* with two highlighted rows and keys go to a stale layer ("/share does
|
|
442
|
+
* nothing"). Cancelling the pending dialog and clearing before the next
|
|
443
|
+
* replace keeps exactly one dialog alive. The normal single-dialog path is
|
|
444
|
+
* untouched, so the esc-to-parent flicker fix stays intact. */
|
|
445
|
+
let pendingDialog;
|
|
446
|
+
function claimDialog(api) {
|
|
447
|
+
if (pendingDialog) {
|
|
448
|
+
try {
|
|
449
|
+
pendingDialog();
|
|
450
|
+
}
|
|
451
|
+
catch { }
|
|
452
|
+
try {
|
|
453
|
+
api.ui.dialog.clear();
|
|
454
|
+
}
|
|
455
|
+
catch { }
|
|
456
|
+
}
|
|
457
|
+
let released = false;
|
|
458
|
+
const release = () => {
|
|
459
|
+
if (released)
|
|
460
|
+
return;
|
|
461
|
+
released = true;
|
|
462
|
+
if (pendingDialog === release)
|
|
463
|
+
pendingDialog = undefined;
|
|
464
|
+
};
|
|
465
|
+
pendingDialog = release;
|
|
466
|
+
return release;
|
|
467
|
+
}
|
|
438
468
|
/** showMenuOnce, family discipline with the flicker fix (2026-10-19):
|
|
439
469
|
* done() does NOT clear the host stack — the next dialog's replace() is an
|
|
440
470
|
* atomic swap, so clearing first paints one empty frame (the esc-to-parent
|
|
@@ -443,10 +473,12 @@ export function MenuDialog(props) {
|
|
|
443
473
|
export function showMenuOnce(api, props) {
|
|
444
474
|
return new Promise((resolve) => {
|
|
445
475
|
let settled = false;
|
|
476
|
+
const release = claimDialog(api);
|
|
446
477
|
const done = (value, _clear = false) => {
|
|
447
478
|
if (settled)
|
|
448
479
|
return;
|
|
449
480
|
settled = true;
|
|
481
|
+
release();
|
|
450
482
|
dbgLog(`menu(${props.title}) -> ${value === undefined ? "cancel" : String(value)}`);
|
|
451
483
|
resolve(value);
|
|
452
484
|
};
|
|
@@ -457,10 +489,12 @@ export function showMenuOnce(api, props) {
|
|
|
457
489
|
export function showInfoDialog(api, props) {
|
|
458
490
|
return new Promise((resolve) => {
|
|
459
491
|
let settled = false;
|
|
492
|
+
const release = claimDialog(api);
|
|
460
493
|
const done = (value) => {
|
|
461
494
|
if (settled)
|
|
462
495
|
return;
|
|
463
496
|
settled = true;
|
|
497
|
+
release();
|
|
464
498
|
resolve(value);
|
|
465
499
|
};
|
|
466
500
|
api.ui.dialog.replace(() => _jsx(InfoDialog, { api: api, title: props.title, message: props.message, actions: props.actions, onDone: done }), () => done(undefined));
|
|
@@ -596,10 +630,12 @@ export function PromptDialog(props) {
|
|
|
596
630
|
export function showPromptOnce(api, props) {
|
|
597
631
|
return new Promise((resolve) => {
|
|
598
632
|
let settled = false;
|
|
633
|
+
const release = claimDialog(api);
|
|
599
634
|
const done = (value, _clear = false) => {
|
|
600
635
|
if (settled)
|
|
601
636
|
return;
|
|
602
637
|
settled = true;
|
|
638
|
+
release();
|
|
603
639
|
dbgLog(`prompt(${props.title}) -> ${value === undefined ? "cancel" : `len=${value.length}`}`);
|
|
604
640
|
resolve(value);
|
|
605
641
|
};
|
|
@@ -614,10 +650,12 @@ export function toMenuOptions(options) {
|
|
|
614
650
|
export function showSizePickerOnce(api, current) {
|
|
615
651
|
return new Promise((resolve) => {
|
|
616
652
|
let settled = false;
|
|
653
|
+
const release = claimDialog(api);
|
|
617
654
|
const done = (value) => {
|
|
618
655
|
if (settled)
|
|
619
656
|
return;
|
|
620
657
|
settled = true;
|
|
658
|
+
release();
|
|
621
659
|
resolve(value);
|
|
622
660
|
};
|
|
623
661
|
api.ui.dialog.replace(() => _jsx(SizePickerDialog, { api: api, current: current, onDone: done }), () => done(undefined));
|
package/dist/share-flow.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// OpenShare share flows — host-agnostic logic behind /share
|
|
1
|
+
// OpenShare share flows — host-agnostic logic behind /share and
|
|
2
2
|
// the settings dialog's "this session" actions. Runs against the
|
|
3
3
|
// ShareHostApi facade (tui-host.ts) and the shared config module, so the
|
|
4
4
|
// same compiled code serves the v1 TUI and the v2 adapter.
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// (with per-session opts when present) -> wait for the server
|
|
10
10
|
// plugin to publish the binding URL into session metadata
|
|
11
11
|
// (~seconds; worker-down surfaces as a timeout toast) -> copy.
|
|
12
|
-
//
|
|
12
|
+
// stop-sharing resolve root -> confirm -> read binding -> clear flag ->
|
|
13
13
|
// DELETE the share directly via the worker (belt and braces:
|
|
14
14
|
// works even if the server plugin never saw the flag event) ->
|
|
15
15
|
// the server-side flag-removal path cleans up on its own.
|
package/dist/sidebar.d.ts
CHANGED
|
@@ -3,7 +3,9 @@ import type { JSX } from "@opentui/solid";
|
|
|
3
3
|
import type { TuiPluginApi, TuiSlotContext } from "@opencode-ai/plugin/tui";
|
|
4
4
|
/** value -> `kind:preview` so the log shows TYPES (the crash was a type bug) */
|
|
5
5
|
export declare function dbg(v: unknown, depth?: number): string;
|
|
6
|
-
|
|
6
|
+
/** no-op: the TUI debug log file was a crash-hunt aid and must not write
|
|
7
|
+
* to temp any more (owner request 2026-10-21) */
|
|
8
|
+
export declare function dbgLog(_line: string): void;
|
|
7
9
|
type SidebarTitleProps = {
|
|
8
10
|
session_id: string;
|
|
9
11
|
title: string;
|
package/dist/sidebar.js
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "@opentui/solid/jsx-runtime";
|
|
2
2
|
import { bindingsOf, bindingOf, verifyShare } from "./share-flow.js";
|
|
3
|
-
import { appendFileSync } from "node:fs";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { join } from "node:path";
|
|
6
|
-
const DBG_FILE = join(tmpdir(), "openshare-tui-debug.log");
|
|
7
3
|
/** value -> `kind:preview` so the log shows TYPES (the crash was a type bug) */
|
|
8
4
|
export function dbg(v, depth = 0) {
|
|
9
5
|
const t = typeof v;
|
|
@@ -50,13 +46,9 @@ function dbgEl(el, depth = 0) {
|
|
|
50
46
|
return `el?-${dbg(e)}`;
|
|
51
47
|
}
|
|
52
48
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
catch { }
|
|
58
|
-
}
|
|
59
|
-
let renderSeq = 0;
|
|
49
|
+
/** no-op: the TUI debug log file was a crash-hunt aid and must not write
|
|
50
|
+
* to temp any more (owner request 2026-10-21) */
|
|
51
|
+
export function dbgLog(_line) { }
|
|
60
52
|
const GONE_TTL_MS = 60_000;
|
|
61
53
|
const goneCache = new Map();
|
|
62
54
|
function goneVerdict(sid, server, shareId) {
|
|
@@ -70,13 +62,10 @@ function goneVerdict(sid, server, shareId) {
|
|
|
70
62
|
return cached;
|
|
71
63
|
}
|
|
72
64
|
export function createSidebarSlot(api, opts) {
|
|
73
|
-
dbgLog(`createSidebarSlot called; api.state=${!!api.state} api.theme=${!!api.theme} api.version=${dbg(api.app?.version)}`);
|
|
74
65
|
return {
|
|
75
66
|
slots: {
|
|
76
67
|
sidebar_title: (ctx, props) => {
|
|
77
|
-
const seq = ++renderSeq;
|
|
78
68
|
try {
|
|
79
|
-
dbgLog(`render#${seq} ctx=${dbg(ctx)} props=${dbg(props)}`);
|
|
80
69
|
const sid = typeof props?.session_id === "string" ? props.session_id : "";
|
|
81
70
|
const title = String(props?.title ?? "") || "Session";
|
|
82
71
|
const session = sid ? api.state.session.get(sid) : undefined;
|
|
@@ -90,7 +79,6 @@ export function createSidebarSlot(api, opts) {
|
|
|
90
79
|
const anyGone = verdicts.some((v) => v?.status === "gone");
|
|
91
80
|
const multi = liveCount > 1 ? `·${liveCount} ` : "";
|
|
92
81
|
const theme = ctx?.theme?.current ?? api.theme.current;
|
|
93
|
-
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)}`);
|
|
94
82
|
// chip on its own line under the title (takes the blank line that
|
|
95
83
|
// separated the title block from the context blocks); clicking it
|
|
96
84
|
// copies the share link — proven pattern: onMouseDown on a box.
|
|
@@ -131,11 +119,9 @@ export function createSidebarSlot(api, opts) {
|
|
|
131
119
|
: status === "live"
|
|
132
120
|
? `◉ re:share ${multi}— click to copy link`
|
|
133
121
|
: "◉ re:share (dormant)" }) })) : null] }));
|
|
134
|
-
dbgLog(`render#${seq} RETURNS ${dbgEl(el)}`);
|
|
135
122
|
return el;
|
|
136
123
|
}
|
|
137
124
|
catch (error) {
|
|
138
|
-
dbgLog(`render#${seq} THREW ${error?.stack ?? dbg(error)} — rethrowing`);
|
|
139
125
|
throw error;
|
|
140
126
|
}
|
|
141
127
|
},
|
package/dist/tui.js
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
configurable: true,
|
|
13
|
-
set: __exportSetter.bind(all, name)
|
|
14
|
-
});
|
|
2
|
+
var __esm = (fn, res, err) => () => {
|
|
3
|
+
if (fn)
|
|
4
|
+
try {
|
|
5
|
+
res = fn(fn = 0);
|
|
6
|
+
} catch (e) {
|
|
7
|
+
err = [e];
|
|
8
|
+
}
|
|
9
|
+
if (err)
|
|
10
|
+
throw err[0];
|
|
11
|
+
return res;
|
|
15
12
|
};
|
|
16
|
-
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
13
|
|
|
18
14
|
// src/config.ts
|
|
19
15
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
@@ -165,8 +161,8 @@ function resolveServer(cfg) {
|
|
|
165
161
|
if (cfg.workerUrl)
|
|
166
162
|
servers[cfg.workerUrl] = { token: cfg.createToken || "", adminToken: cfg.createToken || "", active: true };
|
|
167
163
|
if (cfg.servers)
|
|
168
|
-
for (const [
|
|
169
|
-
servers[
|
|
164
|
+
for (const [url, entry] of Object.entries(cfg.servers))
|
|
165
|
+
servers[url] = entry;
|
|
170
166
|
const urls = Object.keys(servers);
|
|
171
167
|
if (!urls.length)
|
|
172
168
|
return;
|
|
@@ -318,9 +314,6 @@ import { insertNode as _$insertNode } from "@opentui/solid";
|
|
|
318
314
|
import { insert as _$insert } from "@opentui/solid";
|
|
319
315
|
import { setProp as _$setProp } from "@opentui/solid";
|
|
320
316
|
import { createElement as _$createElement } from "@opentui/solid";
|
|
321
|
-
import { appendFileSync } from "fs";
|
|
322
|
-
import { tmpdir } from "os";
|
|
323
|
-
import { join as join2 } from "path";
|
|
324
317
|
function dbg(v, depth = 0) {
|
|
325
318
|
const t = typeof v;
|
|
326
319
|
if (v === null)
|
|
@@ -344,31 +337,7 @@ function dbg(v, depth = 0) {
|
|
|
344
337
|
return "obj:?}";
|
|
345
338
|
}
|
|
346
339
|
}
|
|
347
|
-
function
|
|
348
|
-
if (el === null || el === undefined || typeof el === "string" || typeof el === "number" || typeof el === "boolean")
|
|
349
|
-
return dbg(el);
|
|
350
|
-
if (Array.isArray(el))
|
|
351
|
-
return `[${el.map((x) => dbgEl(x, depth)).join(",")}]`;
|
|
352
|
-
if (typeof el === "function")
|
|
353
|
-
return "fn-node";
|
|
354
|
-
if (depth >= 4)
|
|
355
|
-
return "\u2026";
|
|
356
|
-
try {
|
|
357
|
-
const rec = el;
|
|
358
|
-
const t = dbg(rec.t ?? rec.type, 2);
|
|
359
|
-
const kids = rec.children;
|
|
360
|
-
const props = Object.keys(rec).filter((k) => !["t", "type", "children"].includes(k));
|
|
361
|
-
return `<${t} ${props.join(" ")}>${kids === undefined ? "" : dbgEl(kids, depth + 1)}</${t}>`;
|
|
362
|
-
} catch (e) {
|
|
363
|
-
return `el?-${dbg(e)}`;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
function dbgLog(line) {
|
|
367
|
-
try {
|
|
368
|
-
appendFileSync(DBG_FILE, `${new Date().toISOString()} ${line}
|
|
369
|
-
`);
|
|
370
|
-
} catch {}
|
|
371
|
-
}
|
|
340
|
+
function dbgLog(_line) {}
|
|
372
341
|
function goneVerdict(sid, server, shareId) {
|
|
373
342
|
const key = `${sid}|${server}`;
|
|
374
343
|
const cached = goneCache.get(key);
|
|
@@ -383,13 +352,10 @@ function goneVerdict(sid, server, shareId) {
|
|
|
383
352
|
return cached;
|
|
384
353
|
}
|
|
385
354
|
function createSidebarSlot(api, opts) {
|
|
386
|
-
dbgLog(`createSidebarSlot called; api.state=${!!api.state} api.theme=${!!api.theme} api.version=${dbg(api.app?.version)}`);
|
|
387
355
|
return {
|
|
388
356
|
slots: {
|
|
389
357
|
sidebar_title: (ctx, props) => {
|
|
390
|
-
const seq = ++renderSeq;
|
|
391
358
|
try {
|
|
392
|
-
dbgLog(`render#${seq} ctx=${dbg(ctx)} props=${dbg(props)}`);
|
|
393
359
|
const sid = typeof props?.session_id === "string" ? props.session_id : "";
|
|
394
360
|
const title = String(props?.title ?? "") || "Session";
|
|
395
361
|
const session = sid ? api.state.session.get(sid) : undefined;
|
|
@@ -402,7 +368,6 @@ function createSidebarSlot(api, opts) {
|
|
|
402
368
|
const anyGone = verdicts.some((v) => v?.status === "gone");
|
|
403
369
|
const multi = liveCount > 1 ? `\xB7${liveCount} ` : "";
|
|
404
370
|
const theme = ctx?.theme?.current ?? api.theme.current;
|
|
405
|
-
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)}`);
|
|
406
371
|
const copyLink = () => {
|
|
407
372
|
if (anyGone) {
|
|
408
373
|
opts?.onShareCommand?.();
|
|
@@ -449,64 +414,22 @@ function createSidebarSlot(api, opts) {
|
|
|
449
414
|
_$effect((_$p) => _$setProp(_el$2, "fg", theme.text, _$p));
|
|
450
415
|
return _el$;
|
|
451
416
|
})();
|
|
452
|
-
dbgLog(`render#${seq} RETURNS ${dbgEl(el)}`);
|
|
453
417
|
return el;
|
|
454
418
|
} catch (error) {
|
|
455
|
-
dbgLog(`render#${seq} THREW ${error?.stack ?? dbg(error)} \u2014 rethrowing`);
|
|
456
419
|
throw error;
|
|
457
420
|
}
|
|
458
421
|
}
|
|
459
422
|
}
|
|
460
423
|
};
|
|
461
424
|
}
|
|
462
|
-
var
|
|
425
|
+
var GONE_TTL_MS = 60000, goneCache;
|
|
463
426
|
var init_sidebar = __esm(() => {
|
|
464
427
|
init_share_flow();
|
|
465
|
-
DBG_FILE = join2(tmpdir(), "openshare-tui-debug.log");
|
|
466
428
|
goneCache = new Map;
|
|
467
429
|
});
|
|
468
430
|
|
|
469
431
|
// src/share-flow.ts
|
|
470
|
-
var exports_share_flow = {};
|
|
471
|
-
__export(exports_share_flow, {
|
|
472
|
-
workerDeleteShare: () => workerDeleteShare,
|
|
473
|
-
waitForBinding: () => waitForBinding,
|
|
474
|
-
verifyShare: () => verifyShare,
|
|
475
|
-
setShareFlag: () => setShareFlag,
|
|
476
|
-
serverEntries: () => serverEntries,
|
|
477
|
-
saveServerEntry: () => saveServerEntry,
|
|
478
|
-
saveConfig: () => saveConfig,
|
|
479
|
-
runUnshareFlow: () => runUnshareFlow,
|
|
480
|
-
runShareFlow: () => runShareFlow,
|
|
481
|
-
rotateAtServer: () => rotateAtServer,
|
|
482
|
-
resolveRoot: () => resolveRoot,
|
|
483
|
-
readBindings: () => readBindings,
|
|
484
|
-
readBinding: () => readBinding,
|
|
485
|
-
probeWorker: () => probeWorker,
|
|
486
|
-
probeAuthMode: () => probeAuthMode,
|
|
487
|
-
pluginConfigPaths: () => pluginConfigPaths,
|
|
488
|
-
patchSessionBinding: () => patchSessionBinding,
|
|
489
|
-
normServerUrl: () => normServerUrl,
|
|
490
|
-
isConfigured: () => isConfigured,
|
|
491
|
-
hostOf: () => hostOf,
|
|
492
|
-
enrollAtServer: () => enrollAtServer,
|
|
493
|
-
disconnectAtServer: () => disconnectAtServer,
|
|
494
|
-
deleteServerEntry: () => deleteServerEntry,
|
|
495
|
-
currentConfig: () => currentConfig,
|
|
496
|
-
confirmShare: () => confirmShare,
|
|
497
|
-
clearShareFlag: () => clearShareFlag,
|
|
498
|
-
clearGoneBindingEntries: () => clearGoneBindingEntries,
|
|
499
|
-
bindingsOf: () => bindingsOf,
|
|
500
|
-
bindingOf: () => bindingOf,
|
|
501
|
-
adminTokenFor: () => adminTokenFor,
|
|
502
|
-
adminClient: () => adminClient,
|
|
503
|
-
activeServer: () => activeServer,
|
|
504
|
-
CONSENT_KV_KEY: () => CONSENT_KV_KEY
|
|
505
|
-
});
|
|
506
432
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
507
|
-
function pluginConfigPaths(baseDir = moduleDir()) {
|
|
508
|
-
return configPaths(baseDir);
|
|
509
|
-
}
|
|
510
433
|
function currentConfig(baseDir = moduleDir()) {
|
|
511
434
|
return loadConfig(baseDir);
|
|
512
435
|
}
|
|
@@ -1169,14 +1092,14 @@ async function editKey(host, cfg, key) {
|
|
|
1169
1092
|
});
|
|
1170
1093
|
if (picked === undefined)
|
|
1171
1094
|
return;
|
|
1172
|
-
const
|
|
1095
|
+
const next = { ...cfg };
|
|
1173
1096
|
if (key === "shareMode")
|
|
1174
|
-
|
|
1097
|
+
next[key] = picked === "auto" ? undefined : picked;
|
|
1175
1098
|
else if (key === "openTasksInNewTab" || key === "announceStderr")
|
|
1176
|
-
|
|
1099
|
+
next[key] = picked === "true";
|
|
1177
1100
|
else
|
|
1178
|
-
|
|
1179
|
-
return
|
|
1101
|
+
next[key] = picked;
|
|
1102
|
+
return next;
|
|
1180
1103
|
}
|
|
1181
1104
|
const raw = await showPrompt(host.ui, {
|
|
1182
1105
|
title: `${meta.label} \u2014 ${displayValue(key, cfg)}`,
|
|
@@ -1681,15 +1604,15 @@ async function adminCodesMenu(host, client, codes) {
|
|
|
1681
1604
|
continue;
|
|
1682
1605
|
const n = Number(raw.trim() === "" ? "1" : raw.trim());
|
|
1683
1606
|
const activations = Number.isFinite(n) && n >= 1 ? Math.floor(n) : -1;
|
|
1684
|
-
const
|
|
1685
|
-
if (
|
|
1686
|
-
await host.clipboard.write(
|
|
1687
|
-
await showInfo(host.ui, { title: "Code created", message: `${label.trim() || "invite"}: ${
|
|
1607
|
+
const res = await client.createCode(label.trim() || "invite", activations);
|
|
1608
|
+
if (res.ok && res.data?.code) {
|
|
1609
|
+
await host.clipboard.write(res.data.code);
|
|
1610
|
+
await showInfo(host.ui, { title: "Code created", message: `${label.trim() || "invite"}: ${res.data.code}
|
|
1688
1611
|
(${activations === -1 ? "unlimited" : activations} activation${activations === 1 ? "" : "s"})
|
|
1689
1612
|
|
|
1690
1613
|
(copied to clipboard \u2014 shown once)` });
|
|
1691
1614
|
} else {
|
|
1692
|
-
await showInfo(host.ui, { title: "Create failed", message:
|
|
1615
|
+
await showInfo(host.ui, { title: "Create failed", message: res.error ?? "unknown error" });
|
|
1693
1616
|
}
|
|
1694
1617
|
continue;
|
|
1695
1618
|
}
|
|
@@ -2215,7 +2138,7 @@ async function showShareManager(host, sessionID) {
|
|
|
2215
2138
|
all = all.filter((b) => !gone.some((g) => g.server === b.server));
|
|
2216
2139
|
const list = gone.map((b) => `\u2022 ${hostOf(b.url || b.server)} \u2014 ${b.url}`).join(`
|
|
2217
2140
|
`);
|
|
2218
|
-
const
|
|
2141
|
+
const choice = await host.ui.showMenu({
|
|
2219
2142
|
title: "Share is gone",
|
|
2220
2143
|
hint: "The page was removed, expired, or purged on the server \u2014 sharing disabled for it.",
|
|
2221
2144
|
options: [
|
|
@@ -2229,7 +2152,7 @@ Gone:
|
|
|
2229
2152
|
${list}` }
|
|
2230
2153
|
]
|
|
2231
2154
|
});
|
|
2232
|
-
if (
|
|
2155
|
+
if (choice === "again") {
|
|
2233
2156
|
finish(host, await runShareFlow(host, sid));
|
|
2234
2157
|
return;
|
|
2235
2158
|
}
|
|
@@ -2266,13 +2189,13 @@ Anyone with the link can read the page (plus the login, if read auth is set).`
|
|
|
2266
2189
|
},
|
|
2267
2190
|
{ title: "Cancel", value: "cancel" }
|
|
2268
2191
|
]
|
|
2269
|
-
}).then(async (
|
|
2270
|
-
if (
|
|
2192
|
+
}).then(async (choice) => {
|
|
2193
|
+
if (choice === "copy") {
|
|
2271
2194
|
const ok = await host.clipboard.write(b.url);
|
|
2272
2195
|
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 });
|
|
2273
2196
|
return;
|
|
2274
2197
|
}
|
|
2275
|
-
if (
|
|
2198
|
+
if (choice === "stop") {
|
|
2276
2199
|
const how = await host.ui.showMenu({
|
|
2277
2200
|
title: "Stop sharing",
|
|
2278
2201
|
hint: b.url,
|
|
@@ -2411,18 +2334,18 @@ ${otherServers.map((s) => `\u2022 ${s.url}`).join(`
|
|
|
2411
2334
|
});
|
|
2412
2335
|
if (!pick || pick === "__cancel")
|
|
2413
2336
|
return;
|
|
2414
|
-
|
|
2337
|
+
await Promise.resolve().then(() => init_share_flow());
|
|
2415
2338
|
for (const s of serverEntries())
|
|
2416
2339
|
if (s.url !== pick)
|
|
2417
|
-
|
|
2418
|
-
|
|
2340
|
+
saveServerEntry(s.url, { active: undefined });
|
|
2341
|
+
saveServerEntry(pick, { active: true });
|
|
2419
2342
|
finish(host, { ok: true, message: `${hostOf(pick)} activated \u2014 this session mirrors there within seconds (new sessions share to it too).` });
|
|
2420
2343
|
return;
|
|
2421
2344
|
}
|
|
2422
2345
|
if (choice === "manage") {
|
|
2423
2346
|
const pick = await host.ui.showMenu({
|
|
2424
2347
|
title: "Manage server",
|
|
2425
|
-
options: [...all.map((
|
|
2348
|
+
options: [...all.map((b) => ({ title: hostOf(b.url || b.server), value: b.server, description: b.url })), { title: "Cancel", value: "__cancel" }]
|
|
2426
2349
|
});
|
|
2427
2350
|
if (!pick || pick === "__cancel")
|
|
2428
2351
|
return;
|
|
@@ -2952,20 +2875,20 @@ function MenuDialog(props) {
|
|
|
2952
2875
|
else
|
|
2953
2876
|
sections.at(-1)?.lines.push(line);
|
|
2954
2877
|
}
|
|
2955
|
-
for (const
|
|
2956
|
-
while (
|
|
2957
|
-
|
|
2958
|
-
while (
|
|
2959
|
-
|
|
2878
|
+
for (const section of sections) {
|
|
2879
|
+
while (section.lines.length && section.lines.at(-1) === "")
|
|
2880
|
+
section.lines.pop();
|
|
2881
|
+
while (section.lines.length && section.lines[0] === "")
|
|
2882
|
+
section.lines.shift();
|
|
2960
2883
|
}
|
|
2961
2884
|
const contentWidth = dialogContentWidth(props.api) - 2;
|
|
2962
|
-
return sections.filter((
|
|
2963
|
-
title:
|
|
2964
|
-
lines: preWrap(
|
|
2885
|
+
return sections.filter((section, index) => index === 0 ? section.lines.length > 0 : true).map((section) => ({
|
|
2886
|
+
title: section.title,
|
|
2887
|
+
lines: preWrap(section.lines.join(`
|
|
2965
2888
|
`), contentWidth)
|
|
2966
2889
|
}));
|
|
2967
2890
|
});
|
|
2968
|
-
const infoHeight = createMemo(() => cappedHeight(helpSections().reduce((rows,
|
|
2891
|
+
const infoHeight = createMemo(() => cappedHeight(helpSections().reduce((rows, section) => rows + section.lines.length + (section.title ? 2 : 0), 0) + 1, maxRows(props.api, dimensions().height, 8, 4), 4));
|
|
2969
2892
|
const [section, setSection] = createSignal(0);
|
|
2970
2893
|
const jumpSection = (delta) => {
|
|
2971
2894
|
const count = helpSections().length;
|
|
@@ -3380,13 +3303,36 @@ function MenuDialog(props) {
|
|
|
3380
3303
|
}
|
|
3381
3304
|
});
|
|
3382
3305
|
}
|
|
3306
|
+
var pendingDialog;
|
|
3307
|
+
function claimDialog(api) {
|
|
3308
|
+
if (pendingDialog) {
|
|
3309
|
+
try {
|
|
3310
|
+
pendingDialog();
|
|
3311
|
+
} catch {}
|
|
3312
|
+
try {
|
|
3313
|
+
api.ui.dialog.clear();
|
|
3314
|
+
} catch {}
|
|
3315
|
+
}
|
|
3316
|
+
let released = false;
|
|
3317
|
+
const release = () => {
|
|
3318
|
+
if (released)
|
|
3319
|
+
return;
|
|
3320
|
+
released = true;
|
|
3321
|
+
if (pendingDialog === release)
|
|
3322
|
+
pendingDialog = undefined;
|
|
3323
|
+
};
|
|
3324
|
+
pendingDialog = release;
|
|
3325
|
+
return release;
|
|
3326
|
+
}
|
|
3383
3327
|
function showMenuOnce(api, props) {
|
|
3384
3328
|
return new Promise((resolve) => {
|
|
3385
3329
|
let settled = false;
|
|
3330
|
+
const release = claimDialog(api);
|
|
3386
3331
|
const done = (value, _clear = false) => {
|
|
3387
3332
|
if (settled)
|
|
3388
3333
|
return;
|
|
3389
3334
|
settled = true;
|
|
3335
|
+
release();
|
|
3390
3336
|
dbgLog(`menu(${props.title}) -> ${value === undefined ? "cancel" : String(value)}`);
|
|
3391
3337
|
resolve(value);
|
|
3392
3338
|
};
|
|
@@ -3412,10 +3358,12 @@ function showMenuOnce(api, props) {
|
|
|
3412
3358
|
function showInfoDialog(api, props) {
|
|
3413
3359
|
return new Promise((resolve) => {
|
|
3414
3360
|
let settled = false;
|
|
3361
|
+
const release = claimDialog(api);
|
|
3415
3362
|
const done = (value) => {
|
|
3416
3363
|
if (settled)
|
|
3417
3364
|
return;
|
|
3418
3365
|
settled = true;
|
|
3366
|
+
release();
|
|
3419
3367
|
resolve(value);
|
|
3420
3368
|
};
|
|
3421
3369
|
api.ui.dialog.replace(() => _$createComponent(InfoDialog, {
|
|
@@ -3810,10 +3758,12 @@ function PromptDialog(props) {
|
|
|
3810
3758
|
function showPromptOnce(api, props) {
|
|
3811
3759
|
return new Promise((resolve) => {
|
|
3812
3760
|
let settled = false;
|
|
3761
|
+
const release = claimDialog(api);
|
|
3813
3762
|
const done = (value, _clear = false) => {
|
|
3814
3763
|
if (settled)
|
|
3815
3764
|
return;
|
|
3816
3765
|
settled = true;
|
|
3766
|
+
release();
|
|
3817
3767
|
dbgLog(`prompt(${props.title}) -> ${value === undefined ? "cancel" : `len=${value.length}`}`);
|
|
3818
3768
|
resolve(value);
|
|
3819
3769
|
};
|
|
@@ -3848,10 +3798,12 @@ function toMenuOptions(options) {
|
|
|
3848
3798
|
function showSizePickerOnce(api, current) {
|
|
3849
3799
|
return new Promise((resolve) => {
|
|
3850
3800
|
let settled = false;
|
|
3801
|
+
const release = claimDialog(api);
|
|
3851
3802
|
const done = (value) => {
|
|
3852
3803
|
if (settled)
|
|
3853
3804
|
return;
|
|
3854
3805
|
settled = true;
|
|
3806
|
+
release();
|
|
3855
3807
|
resolve(value);
|
|
3856
3808
|
};
|
|
3857
3809
|
api.ui.dialog.replace(() => _$createComponent(SizePickerDialog, {
|
|
@@ -4757,6 +4709,6 @@ var tui_default = {
|
|
|
4757
4709
|
setup: createV2TuiSetup()
|
|
4758
4710
|
};
|
|
4759
4711
|
export {
|
|
4760
|
-
|
|
4761
|
-
|
|
4712
|
+
buildV1HostApi,
|
|
4713
|
+
tui_default as default
|
|
4762
4714
|
};
|
package/dist/v2-server.d.ts
CHANGED
|
@@ -1,30 +1,8 @@
|
|
|
1
|
-
import { type MessageInfo, type PartInfo } from "./core.js";
|
|
2
1
|
type V2Event = {
|
|
3
2
|
type: string;
|
|
4
3
|
data?: Record<string, unknown>;
|
|
5
4
|
[key: string]: unknown;
|
|
6
5
|
};
|
|
7
|
-
type V2Message = {
|
|
8
|
-
id: string;
|
|
9
|
-
type: string;
|
|
10
|
-
time?: {
|
|
11
|
-
created?: number;
|
|
12
|
-
completed?: number;
|
|
13
|
-
streamed?: number;
|
|
14
|
-
};
|
|
15
|
-
text?: string;
|
|
16
|
-
agent?: string;
|
|
17
|
-
model?: {
|
|
18
|
-
providerID: string;
|
|
19
|
-
id?: string;
|
|
20
|
-
modelID?: string;
|
|
21
|
-
};
|
|
22
|
-
content?: Array<Record<string, unknown>>;
|
|
23
|
-
error?: {
|
|
24
|
-
message?: string;
|
|
25
|
-
} | null;
|
|
26
|
-
metadata?: Record<string, unknown>;
|
|
27
|
-
};
|
|
28
6
|
type V2SessionClient = {
|
|
29
7
|
get(input: {
|
|
30
8
|
sessionID: string;
|
|
@@ -72,10 +50,5 @@ type V2Context = {
|
|
|
72
50
|
};
|
|
73
51
|
signal?: AbortSignal;
|
|
74
52
|
};
|
|
75
|
-
/** v2 flat message -> { info, parts } in the v1 shapes the viewer speaks. */
|
|
76
|
-
export declare function convertV2Message(message: V2Message, sessionID: string): {
|
|
77
|
-
info: MessageInfo;
|
|
78
|
-
parts: PartInfo[];
|
|
79
|
-
} | undefined;
|
|
80
53
|
export declare function createV2ServerSetup(): (context: V2Context) => Promise<(() => void) | void>;
|
|
81
54
|
export {};
|