@mirrowel/opencode-openshare 0.0.0-stage → 0.1.0
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/LICENSE +21 -0
- package/README.md +175 -2
- package/ci/README.md +56 -0
- package/ci/openshare.config.json +5 -0
- package/ci/openshare.js +2265 -0
- package/dist/commands.d.ts +7 -0
- package/dist/commands.js +440 -0
- package/dist/config.d.ts +134 -0
- package/dist/config.js +517 -0
- package/dist/core.d.ts +117 -0
- package/dist/core.js +1944 -0
- package/dist/dialogs.d.ts +94 -0
- package/dist/dialogs.js +725 -0
- package/dist/server.d.ts +64 -0
- package/dist/server.js +30 -0
- package/dist/settings.d.ts +24 -0
- package/dist/settings.js +1226 -0
- package/dist/share-flow.d.ts +287 -0
- package/dist/share-flow.js +516 -0
- package/dist/sidebar.d.ts +19 -0
- package/dist/sidebar.js +144 -0
- package/dist/tui-host.d.ts +158 -0
- package/dist/tui-host.js +6 -0
- package/dist/tui.d.ts +17 -0
- package/dist/tui.js +4762 -0
- package/dist/ui-prefs.d.ts +16 -0
- package/dist/ui-prefs.js +27 -0
- package/dist/v2-server.d.ts +81 -0
- package/dist/v2-server.js +402 -0
- package/dist/v2-tui.d.ts +24 -0
- package/dist/v2-tui.js +313 -0
- package/dist/v2-types.d.ts +134 -0
- package/dist/v2-types.js +8 -0
- package/openshare.config.json +16 -0
- package/package.json +98 -4
- package/src/banner.txt +22 -0
- package/src/commands.ts +452 -0
- package/src/config.ts +518 -0
- package/src/core.ts +1997 -0
- package/src/dialogs.tsx +971 -0
- package/src/server.ts +47 -0
- package/src/settings.ts +1239 -0
- package/src/share-flow.ts +551 -0
- package/src/sidebar.tsx +187 -0
- package/src/tui-host.ts +134 -0
- package/src/tui.tsx +318 -0
- package/src/ui-prefs.ts +36 -0
- package/src/v2-server.ts +438 -0
- package/src/v2-tui.ts +315 -0
- package/src/v2-types.ts +102 -0
- package/tsconfig.json +18 -0
package/dist/v2-tui.js
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode v2 TUI plugin implementation: builds a ShareHostApi adapter
|
|
3
|
+
* over the v2 TUI plugin context so the unchanged share flows and settings
|
|
4
|
+
* dialog run on both hosts, and registers the same commands (one reactive
|
|
5
|
+
* keymap layer).
|
|
6
|
+
*
|
|
7
|
+
* Dialog bridging (family pattern): the v1 api exposes dialog components
|
|
8
|
+
* that RETURN JSX rendered inside an enclosing `dialog.replace(render)`
|
|
9
|
+
* entry; the v2 api only offers promise-based dialogs that open their own
|
|
10
|
+
* host entry. The bridge reconciles the two — `replace()` invokes the
|
|
11
|
+
* render thunk synchronously; if it returned nothing (a DialogX bridge
|
|
12
|
+
* already opened the host's own dialog), the caller's onClose is parked
|
|
13
|
+
* and invoked when that nested dialog is dismissed without a result.
|
|
14
|
+
*/
|
|
15
|
+
import { createSignal } from "solid-js";
|
|
16
|
+
import { registerCommands } from "./commands.js";
|
|
17
|
+
function pickThemeToken(source, ...path) {
|
|
18
|
+
let current = source;
|
|
19
|
+
for (const key of path) {
|
|
20
|
+
if (!current || typeof current !== "object")
|
|
21
|
+
return undefined;
|
|
22
|
+
current = current[key];
|
|
23
|
+
}
|
|
24
|
+
return current;
|
|
25
|
+
}
|
|
26
|
+
function buildV2Theme(context) {
|
|
27
|
+
// Read theme/mode live inside the getter so mid-session theme switches
|
|
28
|
+
// reach the flows.
|
|
29
|
+
return {
|
|
30
|
+
get current() {
|
|
31
|
+
const theme = context.theme;
|
|
32
|
+
const dark = context.themeMode === "dark";
|
|
33
|
+
const text = pickThemeToken(theme, "text", "default");
|
|
34
|
+
return {
|
|
35
|
+
text,
|
|
36
|
+
textMuted: pickThemeToken(theme, "text", "subdued") ?? text,
|
|
37
|
+
background: pickThemeToken(theme, "background", "default"),
|
|
38
|
+
backgroundPanel: pickThemeToken(theme, "background", "surface", "offset") ?? pickThemeToken(theme, "background", "default"),
|
|
39
|
+
primary: pickThemeToken(theme, "text", "action", "primary", "selected") ?? text,
|
|
40
|
+
secondary: pickThemeToken(theme, "hue", dark ? "interactive" : "neutral", dark ? 300 : 700) ?? text,
|
|
41
|
+
accent: pickThemeToken(theme, "hue", "accent", dark ? 200 : 800) ?? text,
|
|
42
|
+
success: pickThemeToken(theme, "text", "feedback", "success", "default") ?? text,
|
|
43
|
+
warning: pickThemeToken(theme, "text", "feedback", "warning", "default") ?? text,
|
|
44
|
+
error: pickThemeToken(theme, "text", "feedback", "error", "default") ?? text,
|
|
45
|
+
info: pickThemeToken(theme, "text", "feedback", "info", "default") ?? text,
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function convertKeymapLayer(layer) {
|
|
51
|
+
const bindingsByName = new Map();
|
|
52
|
+
for (const binding of layer.bindings ?? []) {
|
|
53
|
+
if (!binding?.cmd)
|
|
54
|
+
continue;
|
|
55
|
+
const keys = bindingsByName.get(binding.cmd) ?? [];
|
|
56
|
+
keys.push(binding.key);
|
|
57
|
+
bindingsByName.set(binding.cmd, keys);
|
|
58
|
+
}
|
|
59
|
+
const commands = (layer.commands ?? []).map((command) => ({
|
|
60
|
+
id: command.name,
|
|
61
|
+
title: command.title ?? command.name,
|
|
62
|
+
description: command.desc,
|
|
63
|
+
group: command.group ?? command.category,
|
|
64
|
+
palette: true,
|
|
65
|
+
...(command.slashName ? { slash: { name: command.slashName, ...(command.slashAliases?.length ? { aliases: command.slashAliases } : {}) } } : {}),
|
|
66
|
+
...(command.suggested !== undefined ? { suggested: command.suggested } : {}),
|
|
67
|
+
...(command.enabled !== undefined ? { enabled: command.enabled } : {}),
|
|
68
|
+
...(bindingsByName.get(command.name)?.length ? { bind: bindingsByName.get(command.name).join(",") } : {}),
|
|
69
|
+
run: (_input, event) => command.run({
|
|
70
|
+
event: event
|
|
71
|
+
? {
|
|
72
|
+
preventDefault: () => event.preventDefault?.(),
|
|
73
|
+
stopPropagation: () => event.stopPropagation?.(),
|
|
74
|
+
}
|
|
75
|
+
: undefined,
|
|
76
|
+
}),
|
|
77
|
+
}));
|
|
78
|
+
return { mode: layer.mode ?? "global", priority: layer.priority, commands };
|
|
79
|
+
}
|
|
80
|
+
/** Builds the v1-shaped ShareHostApi facade over a v2 TUI plugin context. */
|
|
81
|
+
export function buildV2HostApi(context) {
|
|
82
|
+
const disposeFns = [];
|
|
83
|
+
// --- kv: synchronous read-your-writes mirror over the persistent store ---
|
|
84
|
+
const [kvStore, kvMutate] = context.storage.store("kv", { initial: {} });
|
|
85
|
+
const kvMirror = {};
|
|
86
|
+
for (const [key, value] of Object.entries(kvStore))
|
|
87
|
+
kvMirror[key] = value;
|
|
88
|
+
const kv = {
|
|
89
|
+
get(key, fallback) {
|
|
90
|
+
const value = key in kvMirror ? kvMirror[key] : kvStore[key];
|
|
91
|
+
return (value === undefined ? fallback : value);
|
|
92
|
+
},
|
|
93
|
+
set(key, value) {
|
|
94
|
+
kvMirror[key] = value;
|
|
95
|
+
void kvMutate((draft) => {
|
|
96
|
+
draft[key] = value;
|
|
97
|
+
}).catch(() => undefined);
|
|
98
|
+
},
|
|
99
|
+
};
|
|
100
|
+
// --- dialog bridging (see module doc comment) ---
|
|
101
|
+
const dialog = context.ui.dialog;
|
|
102
|
+
let nestedOnClose;
|
|
103
|
+
const settleNested = (dismissed) => {
|
|
104
|
+
const onClose = nestedOnClose;
|
|
105
|
+
nestedOnClose = undefined;
|
|
106
|
+
if (dismissed)
|
|
107
|
+
onClose?.();
|
|
108
|
+
};
|
|
109
|
+
return {
|
|
110
|
+
hostVersion: 2,
|
|
111
|
+
app: { version: context.app?.version },
|
|
112
|
+
client: context.client,
|
|
113
|
+
kv,
|
|
114
|
+
lifecycle: {
|
|
115
|
+
onDispose(fn) {
|
|
116
|
+
disposeFns.push(fn);
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
theme: buildV2Theme(context),
|
|
120
|
+
ui: {
|
|
121
|
+
toast: (input) => {
|
|
122
|
+
try {
|
|
123
|
+
context.ui.toast.show({ title: input.title, message: input.message, variant: input.variant, duration: input.duration });
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
/* toasts must never crash the flows */
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
dialog: {
|
|
130
|
+
replace(renderer, onClose) {
|
|
131
|
+
nestedOnClose = undefined;
|
|
132
|
+
let content;
|
|
133
|
+
try {
|
|
134
|
+
content = typeof renderer === "function" ? renderer() : renderer;
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
content = undefined;
|
|
138
|
+
}
|
|
139
|
+
if (content === undefined || content === null) {
|
|
140
|
+
nestedOnClose = onClose;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
dialog.show(() => content, onClose);
|
|
144
|
+
},
|
|
145
|
+
clear() {
|
|
146
|
+
nestedOnClose = undefined;
|
|
147
|
+
dialog.clear();
|
|
148
|
+
},
|
|
149
|
+
setSize(size) {
|
|
150
|
+
try {
|
|
151
|
+
dialog.set({ size });
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
/* size hints are best-effort */
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
// Promise menus (v2 host dialogs are host-native and fully keyed).
|
|
159
|
+
async showMenu(props) {
|
|
160
|
+
const value = await dialog.select({
|
|
161
|
+
title: props.title,
|
|
162
|
+
placeholder: props.hint,
|
|
163
|
+
current: props.current,
|
|
164
|
+
options: props.options.map((option) => ({
|
|
165
|
+
title: option.title,
|
|
166
|
+
value: option.value,
|
|
167
|
+
description: option.description,
|
|
168
|
+
category: option.category,
|
|
169
|
+
disabled: option.disabled,
|
|
170
|
+
})),
|
|
171
|
+
});
|
|
172
|
+
return value;
|
|
173
|
+
},
|
|
174
|
+
async showConfirm(props) {
|
|
175
|
+
return (await dialog.confirm({ title: props.title, message: props.message, label: { confirm: props.confirmLabel } })) === true;
|
|
176
|
+
},
|
|
177
|
+
async showAlert(props) {
|
|
178
|
+
await dialog.alert({ title: props.title, message: props.message });
|
|
179
|
+
},
|
|
180
|
+
async showPrompt(props) {
|
|
181
|
+
return await dialog.prompt({ title: props.title, placeholder: props.placeholder, value: props.value });
|
|
182
|
+
},
|
|
183
|
+
DialogSelect(props) {
|
|
184
|
+
void dialog
|
|
185
|
+
.select({
|
|
186
|
+
title: props.title,
|
|
187
|
+
placeholder: props.placeholder,
|
|
188
|
+
current: props.current,
|
|
189
|
+
options: props.options.map((option) => ({
|
|
190
|
+
title: option.title,
|
|
191
|
+
value: option.value,
|
|
192
|
+
description: option.description,
|
|
193
|
+
category: option.category,
|
|
194
|
+
disabled: option.disabled,
|
|
195
|
+
})),
|
|
196
|
+
})
|
|
197
|
+
.then((value) => {
|
|
198
|
+
if (value === undefined) {
|
|
199
|
+
settleNested(true);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
settleNested(false);
|
|
203
|
+
const option = props.options.find((item) => item.value === value);
|
|
204
|
+
if (option)
|
|
205
|
+
props.onSelect(option);
|
|
206
|
+
})
|
|
207
|
+
.catch(() => settleNested(true));
|
|
208
|
+
},
|
|
209
|
+
DialogPrompt(props) {
|
|
210
|
+
void dialog
|
|
211
|
+
.prompt({ title: props.title, placeholder: props.placeholder, value: props.value })
|
|
212
|
+
.then((value) => {
|
|
213
|
+
settleNested(value === undefined);
|
|
214
|
+
if (value === undefined)
|
|
215
|
+
props.onCancel?.();
|
|
216
|
+
else
|
|
217
|
+
props.onConfirm(value);
|
|
218
|
+
})
|
|
219
|
+
.catch(() => {
|
|
220
|
+
settleNested(true);
|
|
221
|
+
props.onCancel?.();
|
|
222
|
+
});
|
|
223
|
+
},
|
|
224
|
+
DialogConfirm(props) {
|
|
225
|
+
void dialog
|
|
226
|
+
.confirm({ title: props.title, message: props.message, label: { confirm: props.confirmLabel } })
|
|
227
|
+
.then((result) => {
|
|
228
|
+
settleNested(result !== true);
|
|
229
|
+
if (result === true)
|
|
230
|
+
props.onConfirm();
|
|
231
|
+
else
|
|
232
|
+
props.onCancel?.();
|
|
233
|
+
})
|
|
234
|
+
.catch(() => {
|
|
235
|
+
settleNested(true);
|
|
236
|
+
props.onCancel?.();
|
|
237
|
+
});
|
|
238
|
+
},
|
|
239
|
+
DialogAlert(props) {
|
|
240
|
+
void dialog
|
|
241
|
+
.alert({ title: props.title, message: props.message })
|
|
242
|
+
.then(() => {
|
|
243
|
+
settleNested(false);
|
|
244
|
+
props.onConfirm?.();
|
|
245
|
+
})
|
|
246
|
+
.catch(() => {
|
|
247
|
+
settleNested(true);
|
|
248
|
+
props.onConfirm?.();
|
|
249
|
+
});
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
keymap: {
|
|
253
|
+
registerLayer(layer) {
|
|
254
|
+
// v2 layers are reactive: the input function re-evaluates when the
|
|
255
|
+
// signal flips, so "unregistering" disables the layer.
|
|
256
|
+
const [active, setActive] = createSignal(true);
|
|
257
|
+
try {
|
|
258
|
+
context.keymap.layer(() => active()
|
|
259
|
+
? convertKeymapLayer(layer)
|
|
260
|
+
: { mode: "global", enabled: false, commands: [], bindings: [] });
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return () => undefined;
|
|
264
|
+
}
|
|
265
|
+
return () => {
|
|
266
|
+
setActive(false);
|
|
267
|
+
};
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
sessionInfo: () => undefined,
|
|
271
|
+
currentSessionID: undefined,
|
|
272
|
+
clipboard: {
|
|
273
|
+
// same belt-and-braces as the v1 facade: navigator may resolve as a
|
|
274
|
+
// silent no-op in the TUI runtime; OSC52 is the reliable path
|
|
275
|
+
async write(text) {
|
|
276
|
+
let ok = false;
|
|
277
|
+
try {
|
|
278
|
+
const clip = navigator.clipboard;
|
|
279
|
+
if (typeof clip?.writeText === "function") {
|
|
280
|
+
await clip.writeText(text).then(() => {
|
|
281
|
+
ok = true;
|
|
282
|
+
}, () => { });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
catch { }
|
|
286
|
+
try {
|
|
287
|
+
process.stdout.write(`\x1b]52;c;${Buffer.from(text, "utf8").toString("base64")}\x07`);
|
|
288
|
+
ok = true;
|
|
289
|
+
}
|
|
290
|
+
catch { }
|
|
291
|
+
return ok;
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* v2 TUI plugin setup: registers the share commands (one reactive keymap
|
|
298
|
+
* layer). Session-context wiring (currentSessionID) finalizes in phase 4
|
|
299
|
+
* once the beta's session/route surface is verified.
|
|
300
|
+
*/
|
|
301
|
+
export function createV2TuiSetup() {
|
|
302
|
+
return async (context) => {
|
|
303
|
+
const host = buildV2HostApi(context);
|
|
304
|
+
const sessionID = () => host.currentSessionID?.() ?? undefined;
|
|
305
|
+
const unregister = registerCommands(host, sessionID);
|
|
306
|
+
return () => {
|
|
307
|
+
try {
|
|
308
|
+
unregister();
|
|
309
|
+
}
|
|
310
|
+
catch { }
|
|
311
|
+
};
|
|
312
|
+
};
|
|
313
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural types for the OpenCode v2 TUI plugin context subset
|
|
3
|
+
* used by this plugin. Hand-written on purpose (same policy as the server
|
|
4
|
+
* side and the family plugins): the v2 SDK is still beta and a v2 plugin
|
|
5
|
+
* module must not import the plugin SDK at runtime anyway. Keep in sync
|
|
6
|
+
* with the beta repo (packages/plugin/src/tui) when the API settles.
|
|
7
|
+
*/
|
|
8
|
+
export type V2TuiKeyEvent = {
|
|
9
|
+
preventDefault?(): void;
|
|
10
|
+
stopPropagation?(): void;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
};
|
|
13
|
+
export type V2KeymapCommand = {
|
|
14
|
+
id?: string;
|
|
15
|
+
title?: string;
|
|
16
|
+
description?: string;
|
|
17
|
+
group?: string;
|
|
18
|
+
enabled?: boolean | (() => boolean);
|
|
19
|
+
bind?: false | string;
|
|
20
|
+
palette?: true;
|
|
21
|
+
slash?: {
|
|
22
|
+
name: string;
|
|
23
|
+
aliases?: string[];
|
|
24
|
+
arguments?: true;
|
|
25
|
+
};
|
|
26
|
+
suggested?: boolean | (() => boolean);
|
|
27
|
+
run: (input?: string, event?: V2TuiKeyEvent) => void | false | Promise<void>;
|
|
28
|
+
};
|
|
29
|
+
export type V2KeymapLayer = {
|
|
30
|
+
mode?: string;
|
|
31
|
+
enabled?: boolean | (() => boolean);
|
|
32
|
+
priority?: number;
|
|
33
|
+
commands?: readonly V2KeymapCommand[];
|
|
34
|
+
bindings?: readonly string[];
|
|
35
|
+
};
|
|
36
|
+
export type V2DialogSelectOption<Value> = {
|
|
37
|
+
title: string;
|
|
38
|
+
value: Value;
|
|
39
|
+
description?: string;
|
|
40
|
+
category?: string;
|
|
41
|
+
disabled?: boolean;
|
|
42
|
+
};
|
|
43
|
+
export type V2LocationRef = {
|
|
44
|
+
directory: string;
|
|
45
|
+
workspaceID?: string;
|
|
46
|
+
};
|
|
47
|
+
export type V2TuiContext = {
|
|
48
|
+
options: Readonly<Record<string, unknown>>;
|
|
49
|
+
location?: unknown;
|
|
50
|
+
app: {
|
|
51
|
+
version?: string;
|
|
52
|
+
channel?: string;
|
|
53
|
+
};
|
|
54
|
+
renderer: {
|
|
55
|
+
root?: unknown;
|
|
56
|
+
};
|
|
57
|
+
client: any;
|
|
58
|
+
keymap: {
|
|
59
|
+
layer(input: () => V2KeymapLayer): void;
|
|
60
|
+
mode: {
|
|
61
|
+
current(): string;
|
|
62
|
+
push(mode: string): () => void;
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
storage: {
|
|
66
|
+
store<Value extends object>(key: string, options: {
|
|
67
|
+
initial: Value;
|
|
68
|
+
}): readonly [Value, (mutation: (draft: Value) => void) => Promise<void>];
|
|
69
|
+
};
|
|
70
|
+
theme: V2ResolvedTheme;
|
|
71
|
+
themeMode: "dark" | "light";
|
|
72
|
+
ui: {
|
|
73
|
+
dialog: {
|
|
74
|
+
show(render: () => unknown, onClose?: () => void): void;
|
|
75
|
+
set(options: {
|
|
76
|
+
size?: string;
|
|
77
|
+
centered?: boolean;
|
|
78
|
+
}): void;
|
|
79
|
+
clear(): void;
|
|
80
|
+
alert(options: {
|
|
81
|
+
title: string;
|
|
82
|
+
message: string;
|
|
83
|
+
}): Promise<void>;
|
|
84
|
+
confirm(options: {
|
|
85
|
+
title: string;
|
|
86
|
+
message: string;
|
|
87
|
+
label?: {
|
|
88
|
+
confirm?: string;
|
|
89
|
+
cancel?: string;
|
|
90
|
+
};
|
|
91
|
+
}): Promise<boolean | undefined>;
|
|
92
|
+
prompt(options: {
|
|
93
|
+
title: string;
|
|
94
|
+
description?: string;
|
|
95
|
+
placeholder?: string;
|
|
96
|
+
value?: string;
|
|
97
|
+
}): Promise<string | undefined>;
|
|
98
|
+
select<Value>(options: {
|
|
99
|
+
title: string;
|
|
100
|
+
placeholder?: string;
|
|
101
|
+
current?: Value;
|
|
102
|
+
options: readonly V2DialogSelectOption<Value>[];
|
|
103
|
+
}): Promise<Value | undefined>;
|
|
104
|
+
};
|
|
105
|
+
toast: {
|
|
106
|
+
show(options: {
|
|
107
|
+
title?: string;
|
|
108
|
+
message: string;
|
|
109
|
+
variant?: string;
|
|
110
|
+
duration?: number;
|
|
111
|
+
}): void;
|
|
112
|
+
};
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
export type V2ResolvedTheme = {
|
|
116
|
+
text: {
|
|
117
|
+
default: unknown;
|
|
118
|
+
subdued: unknown;
|
|
119
|
+
feedback: Record<string, {
|
|
120
|
+
default: unknown;
|
|
121
|
+
}>;
|
|
122
|
+
action: Record<string, Record<string, unknown>>;
|
|
123
|
+
};
|
|
124
|
+
background: {
|
|
125
|
+
default: unknown;
|
|
126
|
+
surface: {
|
|
127
|
+
offset: unknown;
|
|
128
|
+
overlay: unknown;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
hue: Record<string, Record<string | number, unknown>>;
|
|
132
|
+
categorical?: ReadonlyArray<unknown>;
|
|
133
|
+
};
|
|
134
|
+
export type V2TuiSetup = (context: V2TuiContext) => Promise<(() => void) | void> | (() => void) | void;
|
package/dist/v2-types.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal structural types for the OpenCode v2 TUI plugin context subset
|
|
3
|
+
* used by this plugin. Hand-written on purpose (same policy as the server
|
|
4
|
+
* side and the family plugins): the v2 SDK is still beta and a v2 plugin
|
|
5
|
+
* module must not import the plugin SDK at runtime anyway. Keep in sync
|
|
6
|
+
* with the beta repo (packages/plugin/src/tui) when the API settles.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Open(code)Share plugin config. Priority: OPENSHARE_* env vars > plugin options > this file > code defaults. Sync cadence keys are clamped into the worker's policy window (see worker SOCKET_POLICY/DEBOUNCE_MIN_MS/DEBOUNCE_MAX_MS/COMPLETED_DELAY_MAX_MS). Keys: workerUrl (your openshare-worker deployment; empty = plugin stays disabled), createToken (legacy: the worker's CREATE_TOKEN — keep OUT of git), servers (multi-server memberships: url -> {token, code, label, active}; the active entry (or the first) receives new sessions; with no token the plugin ENROLLS itself — public servers take anyone, private ones need a code from the hoster; enrolled tokens persist to ~/.config/opencode/openshare.config.json), debounceMs (sync interval: max time between syncs while streaming; default 10000), completedDelayMs (idle this long before the share says completed; default 5000, 0 = immediate), modelLabel ($Provider/$Model placeholders; literal text sends as-is; empty string hides the model), ttlDays (per-share TTL, capped by the worker), openTasksInNewTab (task rows open subagent pages in a new tab), authLogin/authPassword (optional read gate on the share pages; sent on create AND resume; empty on resume removes protection), spoolDir (temporary unacked-frame buffer; default XDG data home), urlFile (write each share URL to this file; empty = default openshare.urls.txt in the session's directory; \"none\" = no URL file), announceStderr (false silences the [openshare] live: / url file: log lines; the share itself is unaffected).",
|
|
3
|
+
"workerUrl": "",
|
|
4
|
+
"createToken": "",
|
|
5
|
+
"servers": {},
|
|
6
|
+
"debounceMs": 10000,
|
|
7
|
+
"completedDelayMs": 5000,
|
|
8
|
+
"modelLabel": "$Provider/$Model",
|
|
9
|
+
"ttlDays": 90,
|
|
10
|
+
"openTasksInNewTab": true,
|
|
11
|
+
"authLogin": "",
|
|
12
|
+
"authPassword": "",
|
|
13
|
+
"spoolDir": "",
|
|
14
|
+
"urlFile": "",
|
|
15
|
+
"announceStderr": true
|
|
16
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,100 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mirrowel/opencode-openshare",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Open(code)Share — re:share: self-hosted session sharing for opencode. Server plugin (single file or package), optional TUI for interactive opt-in sharing.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Mirrowel",
|
|
8
|
+
"homepage": "https://github.com/Mirrowel/OpenShare#readme",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/Mirrowel/OpenShare.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/Mirrowel/OpenShare/issues"
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/server.js",
|
|
17
|
+
"types": "dist/server.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/server.d.ts",
|
|
21
|
+
"import": "./dist/server.js"
|
|
22
|
+
},
|
|
23
|
+
"./server": {
|
|
24
|
+
"types": "./dist/server.d.ts",
|
|
25
|
+
"import": "./dist/server.js"
|
|
26
|
+
},
|
|
27
|
+
"./tui": {
|
|
28
|
+
"types": "./dist/tui.d.ts",
|
|
29
|
+
"import": "./dist/tui.js"
|
|
30
|
+
},
|
|
31
|
+
"./config": {
|
|
32
|
+
"types": "./dist/config.d.ts",
|
|
33
|
+
"import": "./dist/config.js"
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"src",
|
|
39
|
+
"ci",
|
|
40
|
+
"openshare.config.json",
|
|
41
|
+
"tsconfig.json",
|
|
42
|
+
"README.md",
|
|
43
|
+
"LICENSE"
|
|
44
|
+
],
|
|
45
|
+
"keywords": [
|
|
46
|
+
"opencode",
|
|
47
|
+
"opencode-plugin",
|
|
48
|
+
"share",
|
|
49
|
+
"session",
|
|
50
|
+
"sharing",
|
|
51
|
+
"collaboration"
|
|
52
|
+
],
|
|
53
|
+
"oc-plugin": [
|
|
54
|
+
"server",
|
|
55
|
+
"tui"
|
|
56
|
+
],
|
|
57
|
+
"publishConfig": {
|
|
58
|
+
"registry": "https://registry.npmjs.org/",
|
|
59
|
+
"access": "public"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
63
|
+
"build": "npm run clean && tsc --outDir dist --declaration && npm run build:tui && npm run build:standalone",
|
|
64
|
+
"build:tui": "bun scripts/build-tui.mjs",
|
|
65
|
+
"build:standalone": "bun scripts/build-standalone.mjs",
|
|
66
|
+
"typecheck": "tsc --noEmit",
|
|
67
|
+
"ci": "npm run release:intent:check && npm run ci:package",
|
|
68
|
+
"ci:package": "npm run typecheck && npm run build && npm run pack:dry-run",
|
|
69
|
+
"release:intent": "node scripts/set-release-intent.mjs",
|
|
70
|
+
"release:intent:check": "node scripts/check-release-intent.mjs",
|
|
71
|
+
"pack:dry-run": "node scripts/pack-dry-run.mjs",
|
|
72
|
+
"test:regression": "node scripts/regression-tests.mjs",
|
|
73
|
+
"prepublishOnly": "npm run typecheck && npm run build",
|
|
74
|
+
"test:manual": "node scripts/manual-tests.mjs",
|
|
75
|
+
"test:tui-flow": "node scripts/tui-flow-tests.mjs",
|
|
76
|
+
"test:v2": "node scripts/v2-mock-tests.mjs",
|
|
77
|
+
"test:auth": "node scripts/auth-tests.mjs",
|
|
78
|
+
"test:server-auth": "node scripts/auth-tests.mjs --backend bun",
|
|
79
|
+
"test:server-enroll": "node scripts/enroll-tests.mjs --backend bun",
|
|
80
|
+
"test:enroll": "node scripts/enroll-tests.mjs",
|
|
81
|
+
"test:fanout": "node scripts/fanout-tests.mjs",
|
|
82
|
+
"test": "node scripts/run-matrix.mjs",
|
|
83
|
+
"test:quick": "node scripts/run-matrix.mjs manual-core regression-tests tui-flow-tests v2-mock-tests auth-tests",
|
|
84
|
+
"test:cli": "node scripts/cli-test.mjs"
|
|
85
|
+
},
|
|
86
|
+
"devDependencies": {
|
|
87
|
+
"@types/node": "^22.0.0",
|
|
88
|
+
"typescript": "^5.8.0"
|
|
89
|
+
},
|
|
90
|
+
"dependencies": {
|
|
91
|
+
"@opencode-ai/plugin": ">=1.14.0",
|
|
92
|
+
"@opentui/solid": "^0.4.1"
|
|
93
|
+
},
|
|
94
|
+
"peerDependencies": {
|
|
95
|
+
"@opencode-ai/plugin": ">=1.14.0"
|
|
96
|
+
},
|
|
97
|
+
"engines": {
|
|
98
|
+
"node": ">=20"
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/banner.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Open(code)Share — re:share, session sharing for opencode. Self-hosted
|
|
2
|
+
// replacement for the discontinued opencode share. Streams session events
|
|
3
|
+
// over one WebSocket per main session to an openshare-worker; viewers
|
|
4
|
+
// watch via hibernating sockets.
|
|
5
|
+
//
|
|
6
|
+
// This is the SINGLE-FILE build of the server plugin (generated by
|
|
7
|
+
// scripts/build-standalone.mjs — do not edit; edit src/ and rebuild).
|
|
8
|
+
// It behaves exactly like the package's dist/server.js.
|
|
9
|
+
//
|
|
10
|
+
// Install:
|
|
11
|
+
// - CI / share-everything: reference this file in the `plugin` array of
|
|
12
|
+
// your opencode config (no TUI part -> every session shares, the URL
|
|
13
|
+
// lands in the log and optionally in `urlFile`).
|
|
14
|
+
// - Interactive opt-in: install the package's TUI part (tui.json) too —
|
|
15
|
+
// the server detects its presence and switches to manual mode
|
|
16
|
+
// (`/share`, `/unshare` in the TUI).
|
|
17
|
+
//
|
|
18
|
+
// Config: env (OPENSHARE_*) > openshare.config.json (next to this file)
|
|
19
|
+
// > code defaults. Keys: workerUrl, createToken, debounceMs, modelLabel,
|
|
20
|
+
// ttlDays, openTasksInNewTab, authLogin, authPassword, spoolDir, urlFile,
|
|
21
|
+
// shareMode ("all" | "manual"; default = TUI presence).
|
|
22
|
+
//
|