@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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +175 -2
  3. package/ci/README.md +56 -0
  4. package/ci/openshare.config.json +5 -0
  5. package/ci/openshare.js +2265 -0
  6. package/dist/commands.d.ts +7 -0
  7. package/dist/commands.js +440 -0
  8. package/dist/config.d.ts +134 -0
  9. package/dist/config.js +517 -0
  10. package/dist/core.d.ts +117 -0
  11. package/dist/core.js +1944 -0
  12. package/dist/dialogs.d.ts +94 -0
  13. package/dist/dialogs.js +725 -0
  14. package/dist/server.d.ts +64 -0
  15. package/dist/server.js +30 -0
  16. package/dist/settings.d.ts +24 -0
  17. package/dist/settings.js +1226 -0
  18. package/dist/share-flow.d.ts +287 -0
  19. package/dist/share-flow.js +516 -0
  20. package/dist/sidebar.d.ts +19 -0
  21. package/dist/sidebar.js +144 -0
  22. package/dist/tui-host.d.ts +158 -0
  23. package/dist/tui-host.js +6 -0
  24. package/dist/tui.d.ts +17 -0
  25. package/dist/tui.js +4762 -0
  26. package/dist/ui-prefs.d.ts +16 -0
  27. package/dist/ui-prefs.js +27 -0
  28. package/dist/v2-server.d.ts +81 -0
  29. package/dist/v2-server.js +402 -0
  30. package/dist/v2-tui.d.ts +24 -0
  31. package/dist/v2-tui.js +313 -0
  32. package/dist/v2-types.d.ts +134 -0
  33. package/dist/v2-types.js +8 -0
  34. package/openshare.config.json +16 -0
  35. package/package.json +98 -4
  36. package/src/banner.txt +22 -0
  37. package/src/commands.ts +452 -0
  38. package/src/config.ts +518 -0
  39. package/src/core.ts +1997 -0
  40. package/src/dialogs.tsx +971 -0
  41. package/src/server.ts +47 -0
  42. package/src/settings.ts +1239 -0
  43. package/src/share-flow.ts +551 -0
  44. package/src/sidebar.tsx +187 -0
  45. package/src/tui-host.ts +134 -0
  46. package/src/tui.tsx +318 -0
  47. package/src/ui-prefs.ts +36 -0
  48. package/src/v2-server.ts +438 -0
  49. package/src/v2-tui.ts +315 -0
  50. package/src/v2-types.ts +102 -0
  51. 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;
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ //