vibeshare-live 0.1.1 → 0.2.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 +53 -67
- package/dist/chunk-RY2ANOPC.js +1018 -0
- package/dist/cli.d.ts +33 -35
- package/dist/cli.js +393 -239
- package/dist/index.d.ts +106 -47
- package/dist/index.js +53 -22
- package/dist/manager-uhMBJXtu.d.ts +259 -0
- package/dist/mcp.d.ts +27 -38
- package/dist/mcp.js +199 -14616
- package/docs/launch-video-16x9.html +673 -0
- package/docs/launch-video-9x16.html +673 -0
- package/docs/launch-video.html +490 -0
- package/docs/prototype.html +964 -0
- package/docs/spec.md +61 -0
- package/package.json +28 -36
- package/dist/chunk-SBAGPPGC.js +0 -233
- package/dist/share-t_zYXkQB.d.ts +0 -188
package/dist/cli.js
CHANGED
|
@@ -1,278 +1,432 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
ConsentRequiredError,
|
|
4
|
+
LocalHttpTransport,
|
|
5
|
+
SHARE_SCOPE,
|
|
6
|
+
ShareManager,
|
|
3
7
|
VERSION,
|
|
4
|
-
|
|
5
|
-
|
|
8
|
+
clearActiveShare,
|
|
9
|
+
listActiveShares,
|
|
10
|
+
loadLedger,
|
|
11
|
+
readActiveShare,
|
|
12
|
+
writeActiveShare
|
|
13
|
+
} from "./chunk-RY2ANOPC.js";
|
|
6
14
|
|
|
7
15
|
// src/cli.ts
|
|
8
|
-
import {
|
|
9
|
-
import { createInterface } from "readline";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
var err = (message) => ({ cmd: "error", message });
|
|
16
|
-
function parseArgs(argv) {
|
|
17
|
-
const dd = argv.indexOf("--");
|
|
18
|
-
const head = dd >= 0 ? argv.slice(0, dd) : [...argv];
|
|
19
|
-
const command = dd >= 0 ? argv.slice(dd + 1) : [];
|
|
20
|
-
if (head.includes("--help") || head.includes("-h")) return { cmd: "help" };
|
|
21
|
-
if (head.includes("--version") || head.includes("-v")) return { cmd: "version" };
|
|
22
|
-
const sub = head[0];
|
|
23
|
-
if (sub === "stop") return { cmd: "stop" };
|
|
24
|
-
if (sub === "viewers") return { cmd: "viewers" };
|
|
25
|
-
if (sub === "mcp") return { cmd: "mcp" };
|
|
26
|
-
if (head.length === 0 && command.length === 0) return { cmd: "help" };
|
|
27
|
-
let flagTokens;
|
|
28
|
-
if (sub === "host") {
|
|
29
|
-
flagTokens = head.slice(1);
|
|
30
|
-
} else if (sub === void 0 || sub.startsWith("-")) {
|
|
31
|
-
flagTokens = head;
|
|
32
|
-
} else {
|
|
33
|
-
return err(`unknown command: ${sub}`);
|
|
16
|
+
import { spawn } from "child_process";
|
|
17
|
+
import { createInterface } from "readline/promises";
|
|
18
|
+
import { badge, createHookBus, parseConfirm, watchCwd } from "@pooriaarab/vibe-core";
|
|
19
|
+
var CliUsageError = class extends Error {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = "CliUsageError";
|
|
34
23
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
let pass;
|
|
38
|
-
let name;
|
|
39
|
-
for (let i = 0; i < flagTokens.length; i++) {
|
|
40
|
-
const t = flagTokens[i];
|
|
41
|
-
if (t === void 0) break;
|
|
42
|
-
const next = flagTokens[i + 1];
|
|
43
|
-
if (t === "--spectate") {
|
|
44
|
-
access = "spectate";
|
|
45
|
-
} else if (t === "--invite") {
|
|
46
|
-
access = "invite";
|
|
47
|
-
} else if (t === "--expire") {
|
|
48
|
-
if (next === void 0) return err("--expire requires a value (1h | 24h)");
|
|
49
|
-
if (next !== "1h" && next !== "24h") {
|
|
50
|
-
return err(`--expire must be "1h" or "24h", got: ${next}`);
|
|
51
|
-
}
|
|
52
|
-
expire = next;
|
|
53
|
-
i++;
|
|
54
|
-
} else if (t === "--pass") {
|
|
55
|
-
if (next === void 0) return err("--pass requires a value");
|
|
56
|
-
pass = next;
|
|
57
|
-
i++;
|
|
58
|
-
} else if (t === "--name") {
|
|
59
|
-
if (next === void 0) return err("--name requires a value");
|
|
60
|
-
name = next;
|
|
61
|
-
i++;
|
|
62
|
-
} else {
|
|
63
|
-
return err(`unknown flag: ${t}`);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
if (command.length === 0) {
|
|
67
|
-
return err('vibeshare needs a command after "--", e.g. `vibeshare --invite -- claude`');
|
|
68
|
-
}
|
|
69
|
-
return { cmd: "host", command, access, expire, pass, name };
|
|
70
|
-
}
|
|
71
|
-
var HELP = `vibeshare ${VERSION} \u2014 share your live agent session by URL
|
|
72
|
-
|
|
73
|
-
USAGE
|
|
74
|
-
vibeshare [--spectate|--invite] [--expire 1h|24h] [--pass <p>] -- <command...>
|
|
75
|
-
vibeshare host [the flags above] -- <command...>
|
|
76
|
-
Host a session running <command> (e.g. \`vibeshare --invite -- claude\`)
|
|
77
|
-
and print its share URL. You start as the driver.
|
|
24
|
+
};
|
|
25
|
+
var USAGE = `vibeshare \u2014 share your live agent coding session by URL
|
|
78
26
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
--name <s> display name for the host participant
|
|
27
|
+
usage:
|
|
28
|
+
vibeshare [options] [-- <cmd\u2026>] start sharing (default: your shell)
|
|
29
|
+
vibeshare viewers [shareId] list viewers; act on join requests
|
|
30
|
+
vibeshare stop [shareId] end the active share
|
|
84
31
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
32
|
+
options:
|
|
33
|
+
--spectate viewers watch read-only (default)
|
|
34
|
+
--invite viewers may request to join as collaborators
|
|
35
|
+
--expire <when> 1h, 24h, 7d, \u2026 or "stop" (default: until you stop)
|
|
36
|
+
--pass <phrase> require a passphrase to watch
|
|
37
|
+
--port <n> port to serve on (default: random)
|
|
38
|
+
--host <addr> bind address (default: 127.0.0.1; 0.0.0.0 shares on LAN)
|
|
39
|
+
--name <label> what to call the session
|
|
40
|
+
--yes, -y grant share:session consent without prompting
|
|
41
|
+
--json machine-readable output (viewers)
|
|
42
|
+
--approve <id> approve a viewer's join request (viewers)
|
|
43
|
+
--deny <id> deny a join request (viewers)
|
|
44
|
+
--kick <id> remove a viewer (viewers)
|
|
45
|
+
--version, -v print version
|
|
46
|
+
--help, -h this help
|
|
90
47
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
48
|
+
local-first: the stream is served from this machine; nothing is stored on a
|
|
49
|
+
server. Consent scope "share:session" is recorded in ~/.vibeshare/consent.json.`;
|
|
50
|
+
function parseArgv(argv) {
|
|
51
|
+
const rest = [...argv];
|
|
52
|
+
const sub = rest[0];
|
|
53
|
+
if (sub === "--help" || sub === "-h" || sub === "help") return { cmd: "help" };
|
|
54
|
+
if (sub === "--version" || sub === "-v" || sub === "version") return { cmd: "version" };
|
|
55
|
+
if (sub === "stop") {
|
|
56
|
+
const extra = positionalArgs(rest.slice(1));
|
|
57
|
+
if (extra.length > 1) throw new CliUsageError("stop takes at most one share id");
|
|
58
|
+
return { cmd: "stop", ...extra[0] !== void 0 ? { share: extra[0] } : {} };
|
|
59
|
+
}
|
|
60
|
+
if (sub === "viewers") {
|
|
61
|
+
const args = rest.slice(1);
|
|
62
|
+
const out = {
|
|
63
|
+
cmd: "viewers",
|
|
64
|
+
json: false
|
|
65
|
+
};
|
|
66
|
+
let actions = 0;
|
|
67
|
+
for (let i = 0; i < args.length; i++) {
|
|
68
|
+
const a = args[i];
|
|
69
|
+
if (a === "--json") out.json = true;
|
|
70
|
+
else if (a === "--approve" || a === "--deny" || a === "--kick") {
|
|
71
|
+
const id = args[++i];
|
|
72
|
+
if (id === void 0) throw new CliUsageError(`${a} needs a viewer id`);
|
|
73
|
+
const key = a.slice(2);
|
|
74
|
+
out[key] = id;
|
|
75
|
+
actions++;
|
|
76
|
+
} else if (a.startsWith("-")) throw new CliUsageError(`unknown option for viewers: ${a}`);
|
|
77
|
+
else if (out.share === void 0) out.share = a;
|
|
78
|
+
else throw new CliUsageError(`unexpected argument: ${a}`);
|
|
104
79
|
}
|
|
80
|
+
if (actions > 1) throw new CliUsageError("use only one of --approve / --deny / --kick");
|
|
81
|
+
return out;
|
|
105
82
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const relay = await createRelay({
|
|
83
|
+
if (sub === "start") rest.shift();
|
|
84
|
+
const options = {
|
|
85
|
+
access: "spectate",
|
|
86
|
+
expiry: "stop",
|
|
111
87
|
port: 0,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
88
|
+
host: "127.0.0.1",
|
|
89
|
+
yes: false,
|
|
90
|
+
command: []
|
|
91
|
+
};
|
|
92
|
+
for (let i = 0; i < rest.length; i++) {
|
|
93
|
+
const a = rest[i];
|
|
94
|
+
const value = (flag) => {
|
|
95
|
+
const v = rest[++i];
|
|
96
|
+
if (v === void 0) throw new CliUsageError(`${flag} needs a value`);
|
|
97
|
+
return v;
|
|
98
|
+
};
|
|
99
|
+
if (a === "--") {
|
|
100
|
+
options.command = rest.slice(i + 1);
|
|
101
|
+
break;
|
|
102
|
+
} else if (a === "--spectate") options.access = "spectate";
|
|
103
|
+
else if (a === "--invite") options.access = "invite";
|
|
104
|
+
else if (a === "--expire" || a === "--expiry") options.expiry = value(a);
|
|
105
|
+
else if (a === "--pass") options.passphrase = value(a);
|
|
106
|
+
else if (a === "--port") {
|
|
107
|
+
const n = Number(value(a));
|
|
108
|
+
if (!Number.isInteger(n) || n < 0 || n > 65535) throw new CliUsageError("--port must be 0\u201365535");
|
|
109
|
+
options.port = n;
|
|
110
|
+
} else if (a === "--host") options.host = value(a);
|
|
111
|
+
else if (a === "--name") options.name = value(a);
|
|
112
|
+
else if (a === "--yes" || a === "-y") options.yes = true;
|
|
113
|
+
else if (a === "--help" || a === "-h") return { cmd: "help" };
|
|
114
|
+
else if (a.startsWith("-")) throw new CliUsageError(`unknown option: ${a}`);
|
|
115
|
+
else options.command = rest.slice(i);
|
|
116
|
+
if (!a.startsWith("-")) break;
|
|
141
117
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
118
|
+
return { cmd: "start", options };
|
|
119
|
+
}
|
|
120
|
+
function positionalArgs(args) {
|
|
121
|
+
const out = [];
|
|
122
|
+
for (const a of args) {
|
|
123
|
+
if (a.startsWith("-")) throw new CliUsageError(`unknown option: ${a}`);
|
|
124
|
+
out.push(a);
|
|
125
|
+
}
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
var stdio = {
|
|
129
|
+
out: (t) => process.stdout.write(t + "\n"),
|
|
130
|
+
err: (t) => process.stderr.write(t + "\n")
|
|
131
|
+
};
|
|
132
|
+
var HOOK_KINDS = [
|
|
133
|
+
"task-done",
|
|
134
|
+
"pr-opened",
|
|
135
|
+
"prototype-finished",
|
|
136
|
+
"spec-completed",
|
|
137
|
+
"tests-pass",
|
|
138
|
+
"tests-fail",
|
|
139
|
+
"error",
|
|
140
|
+
"session-end",
|
|
141
|
+
"manual"
|
|
142
|
+
];
|
|
143
|
+
async function ensureConsent(io, yes) {
|
|
144
|
+
const ledger = loadLedger();
|
|
145
|
+
if (ledger.allows(SHARE_SCOPE)) return true;
|
|
146
|
+
if (yes) {
|
|
147
|
+
ledger.grant(SHARE_SCOPE, "granted via vibeshare --yes");
|
|
148
|
+
return true;
|
|
149
|
+
}
|
|
150
|
+
if (!process.stdin.isTTY) {
|
|
151
|
+
io.err(`consent required: re-run with --yes to grant "${SHARE_SCOPE}" (recorded locally in ~/.vibeshare/consent.json)`);
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
155
|
+
try {
|
|
156
|
+
const answer = await rl.question(
|
|
157
|
+
`Share this session live by URL? Anyone with the link can view its output. (y/N) `
|
|
158
|
+
);
|
|
159
|
+
if (!parseConfirm(answer)) {
|
|
160
|
+
io.err("aborted \u2014 no consent granted");
|
|
161
|
+
return false;
|
|
158
162
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
163
|
+
ledger.grant(SHARE_SCOPE, "granted via vibeshare CLI prompt");
|
|
164
|
+
return true;
|
|
165
|
+
} finally {
|
|
166
|
+
rl.close();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function lineBuffer(publish) {
|
|
170
|
+
let buf = "";
|
|
171
|
+
return {
|
|
172
|
+
push(chunk) {
|
|
173
|
+
buf += chunk;
|
|
174
|
+
let idx;
|
|
175
|
+
while ((idx = buf.indexOf("\n")) !== -1) {
|
|
176
|
+
publish(buf.slice(0, idx).replace(/\r$/, ""));
|
|
177
|
+
buf = buf.slice(idx + 1);
|
|
178
|
+
}
|
|
179
|
+
if (buf.length > 8192) {
|
|
180
|
+
publish(buf);
|
|
181
|
+
buf = "";
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
flush() {
|
|
185
|
+
if (buf.length > 0) {
|
|
186
|
+
publish(buf);
|
|
187
|
+
buf = "";
|
|
188
|
+
}
|
|
162
189
|
}
|
|
163
190
|
};
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const ok = share.approve(id);
|
|
176
|
-
process.stderr.write(ok ? ` promoted ${id}.
|
|
177
|
-
` : ` can't promote ${id} (unknown / spectate-only).
|
|
178
|
-
`);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
if (line === "/release") {
|
|
182
|
-
relay.localReleaseControl(HOST_ID);
|
|
183
|
-
process.stderr.write(" control released.\n");
|
|
184
|
-
return;
|
|
185
|
-
}
|
|
186
|
-
if (line === "/drive") {
|
|
187
|
-
relay.localRequestControl(HOST_ID);
|
|
188
|
-
process.stderr.write(" control requested.\n");
|
|
189
|
-
return;
|
|
191
|
+
}
|
|
192
|
+
async function startShare(options, io) {
|
|
193
|
+
if (!await ensureConsent(io, options.yes)) return 1;
|
|
194
|
+
const bus = createHookBus({ onError: (e) => io.err(`[vibeshare] hook error: ${String(e)}`) });
|
|
195
|
+
const watcher = watchCwd(process.cwd(), bus);
|
|
196
|
+
let created = null;
|
|
197
|
+
const transport = new LocalHttpTransport({
|
|
198
|
+
host: options.host,
|
|
199
|
+
port: options.port,
|
|
200
|
+
onStopRequested: () => {
|
|
201
|
+
shutdown(0);
|
|
190
202
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
203
|
+
});
|
|
204
|
+
const manager = new ShareManager({ consent: loadLedger(), transport });
|
|
205
|
+
try {
|
|
206
|
+
created = await manager.createShare({
|
|
207
|
+
access: options.access,
|
|
208
|
+
expiry: options.expiry,
|
|
209
|
+
...options.passphrase !== void 0 ? { passphrase: options.passphrase } : {},
|
|
210
|
+
...options.name !== void 0 ? { name: options.name } : {},
|
|
211
|
+
session: options.command.length > 0 ? options.command.join(" ") : void 0
|
|
212
|
+
});
|
|
213
|
+
} catch (err) {
|
|
214
|
+
watcher.stop();
|
|
215
|
+
await transport.close();
|
|
216
|
+
if (err instanceof ConsentRequiredError || err instanceof Error) {
|
|
217
|
+
io.err(`vibeshare: ${err.message}`);
|
|
218
|
+
return err instanceof ConsentRequiredError ? 1 : 2;
|
|
196
219
|
}
|
|
220
|
+
throw err;
|
|
221
|
+
}
|
|
222
|
+
for (const kind of HOOK_KINDS) {
|
|
223
|
+
bus.on(kind, (e) => {
|
|
224
|
+
created?.feed.publishEvent(e);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
const record = {
|
|
228
|
+
id: created.share.id,
|
|
229
|
+
url: created.url,
|
|
230
|
+
port: transport.port,
|
|
231
|
+
hostToken: transport.hostToken,
|
|
232
|
+
pid: process.pid,
|
|
233
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
234
|
+
};
|
|
235
|
+
writeActiveShare(record);
|
|
236
|
+
const loopback = options.host === "127.0.0.1" || options.host === "localhost" || options.host === "::1";
|
|
237
|
+
io.out(`${badge(loopback ? "local" : "p2p")}`);
|
|
238
|
+
io.out(` sharing: ${created.share.name}`);
|
|
239
|
+
io.out(` url: ${created.url}`);
|
|
240
|
+
io.out(` access: ${created.share.access}${created.share.access === "spectate" ? " (read-only)" : " (viewers may request to join)"}`);
|
|
241
|
+
io.out(` expires: ${created.share.expiresAt ?? "until you stop"}`);
|
|
242
|
+
if (created.share.passphraseHash) io.out(` pass: required`);
|
|
243
|
+
io.out(` manage: vibeshare viewers \xB7 vibeshare stop`);
|
|
244
|
+
if (!loopback) io.err("note: bound to a non-loopback address \u2014 anyone who can reach this host with the link can watch.");
|
|
245
|
+
const cmd = options.command.length > 0 ? options.command : [process.env["SHELL"] ?? "/bin/sh"];
|
|
246
|
+
const child = spawn(cmd[0], cmd.slice(1), {
|
|
247
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
248
|
+
env: process.env
|
|
249
|
+
});
|
|
250
|
+
const outLines = lineBuffer((l) => created?.feed.publish(l, { stream: "stdout" }));
|
|
251
|
+
const errLines = lineBuffer((l) => created?.feed.publish(l, { stream: "stderr" }));
|
|
252
|
+
child.stdout?.on("data", (d) => {
|
|
253
|
+
process.stdout.write(d);
|
|
254
|
+
outLines.push(d.toString("utf8"));
|
|
255
|
+
});
|
|
256
|
+
child.stderr?.on("data", (d) => {
|
|
257
|
+
process.stderr.write(d);
|
|
258
|
+
errLines.push(d.toString("utf8"));
|
|
197
259
|
});
|
|
198
|
-
rl.on("SIGINT", () => void shutdown(0));
|
|
199
260
|
let shuttingDown = false;
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
};
|
|
208
|
-
process.on("SIGINT", () => void shutdown(130));
|
|
209
|
-
process.on("SIGTERM", () => void shutdown(143));
|
|
210
|
-
if (p.expire !== void 0) {
|
|
211
|
-
void promiseTrue(() => share.revoked).then(async () => {
|
|
212
|
-
if (expired) {
|
|
213
|
-
process.stderr.write(" share expired \u2014 tearing down.\n");
|
|
214
|
-
await shutdown(0);
|
|
215
|
-
}
|
|
261
|
+
const exitCode = await new Promise((resolve) => {
|
|
262
|
+
child.on("error", (err) => {
|
|
263
|
+
io.err(`vibeshare: could not start ${cmd[0]}: ${err.message}`);
|
|
264
|
+
resolve(2);
|
|
265
|
+
});
|
|
266
|
+
child.on("exit", (code, signal) => {
|
|
267
|
+
resolve(code ?? (signal !== null ? 128 : 0));
|
|
216
268
|
});
|
|
269
|
+
process.on("SIGINT", () => shutdown2(130));
|
|
270
|
+
process.on("SIGTERM", () => shutdown2(143));
|
|
271
|
+
function shutdown2(code) {
|
|
272
|
+
if (shuttingDown) return;
|
|
273
|
+
shuttingDown = true;
|
|
274
|
+
child.kill("SIGTERM");
|
|
275
|
+
resolve(code);
|
|
276
|
+
}
|
|
277
|
+
shutdownRef = shutdown2;
|
|
278
|
+
});
|
|
279
|
+
outLines.flush();
|
|
280
|
+
errLines.flush();
|
|
281
|
+
watcher.stop();
|
|
282
|
+
clearActiveShare(record.id);
|
|
283
|
+
await manager.stopAll();
|
|
284
|
+
return exitCode;
|
|
285
|
+
}
|
|
286
|
+
var shutdownRef = null;
|
|
287
|
+
function shutdown(code) {
|
|
288
|
+
shutdownRef?.(code);
|
|
289
|
+
}
|
|
290
|
+
async function controlFetch(record, path, init) {
|
|
291
|
+
let res;
|
|
292
|
+
try {
|
|
293
|
+
res = await fetch(`http://127.0.0.1:${record.port}${path}`, {
|
|
294
|
+
method: init?.method ?? "GET",
|
|
295
|
+
headers: {
|
|
296
|
+
authorization: `Bearer ${record.hostToken}`,
|
|
297
|
+
...init?.body !== void 0 ? { "content-type": "application/json" } : {}
|
|
298
|
+
},
|
|
299
|
+
...init?.body !== void 0 ? { body: JSON.stringify(init.body) } : {}
|
|
300
|
+
});
|
|
301
|
+
} catch {
|
|
302
|
+
return { ok: false, status: 0, message: "connection refused" };
|
|
303
|
+
}
|
|
304
|
+
const body = await res.json().catch(() => ({}));
|
|
305
|
+
if (!res.ok) {
|
|
306
|
+
const message = typeof body["error"] === "string" ? body["error"] : `HTTP ${res.status}`;
|
|
307
|
+
return { ok: false, status: res.status, message };
|
|
217
308
|
}
|
|
218
|
-
|
|
219
|
-
await shutdown(code ?? 0);
|
|
220
|
-
return code ?? 0;
|
|
309
|
+
return { ok: true, data: body };
|
|
221
310
|
}
|
|
222
|
-
function
|
|
223
|
-
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
311
|
+
function resolveRecord(shareId, io) {
|
|
312
|
+
if (shareId !== void 0) {
|
|
313
|
+
const rec = readActiveShare(shareId);
|
|
314
|
+
if (rec) return rec;
|
|
315
|
+
io.err(`no recorded share ${shareId}`);
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
const [latest] = listActiveShares();
|
|
319
|
+
if (!latest) {
|
|
320
|
+
io.err("no active vibeshare share \u2014 start one with `vibeshare`");
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
return latest;
|
|
230
324
|
}
|
|
231
|
-
async function
|
|
232
|
-
|
|
233
|
-
|
|
325
|
+
async function viewersCommand(cmd, io) {
|
|
326
|
+
const record = resolveRecord(cmd.share, io);
|
|
327
|
+
if (!record) return 1;
|
|
328
|
+
const action = cmd.approve !== void 0 ? "approve" : cmd.deny !== void 0 ? "deny" : cmd.kick !== void 0 ? "kick" : null;
|
|
329
|
+
const target = cmd.approve ?? cmd.deny ?? cmd.kick;
|
|
330
|
+
if (action !== null && target !== void 0) {
|
|
331
|
+
const res2 = await controlFetch(record, `/control/${action}`, {
|
|
332
|
+
method: "POST",
|
|
333
|
+
body: { share: record.id, viewer: target }
|
|
334
|
+
});
|
|
335
|
+
if (!res2.ok) {
|
|
336
|
+
if (res2.status === 0) clearActiveShare(record.id);
|
|
337
|
+
io.err(`vibeshare: could not ${action} ${target}: ${res2.message}`);
|
|
338
|
+
return 1;
|
|
339
|
+
}
|
|
340
|
+
const v = res2.data.viewer;
|
|
341
|
+
io.out(
|
|
342
|
+
action === "approve" ? `\u2713 approved ${v.name} \u2014 now a collaborator` : action === "deny" ? `\u2713 denied ${v.name} \u2014 stays a spectator` : `\u2713 kicked ${v.name}`
|
|
343
|
+
);
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
const res = await controlFetch(
|
|
347
|
+
record,
|
|
348
|
+
`/control/viewers?share=${encodeURIComponent(record.id)}`
|
|
234
349
|
);
|
|
350
|
+
if (!res.ok) {
|
|
351
|
+
if (res.status === 0) {
|
|
352
|
+
clearActiveShare(record.id);
|
|
353
|
+
io.err("vibeshare: the share process is not running (stale record cleaned up)");
|
|
354
|
+
} else {
|
|
355
|
+
io.err(`vibeshare: ${res.message}`);
|
|
356
|
+
}
|
|
357
|
+
return 1;
|
|
358
|
+
}
|
|
359
|
+
if (cmd.json) {
|
|
360
|
+
io.out(JSON.stringify(res.data, null, 2));
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
io.out(`${record.url}`);
|
|
364
|
+
if (res.data.viewers.length === 0) {
|
|
365
|
+
io.out(" no viewers yet");
|
|
366
|
+
return 0;
|
|
367
|
+
}
|
|
368
|
+
for (const v of res.data.viewers) {
|
|
369
|
+
const pending = v.joinRequest === "pending" ? " \u23F3 requested to join \u2014 vibeshare viewers --approve " + v.id : "";
|
|
370
|
+
io.out(` ${v.id} ${v.name} [${v.role}]${pending}`);
|
|
371
|
+
}
|
|
372
|
+
io.out(` ${res.data.watching} watching now`);
|
|
235
373
|
return 0;
|
|
236
374
|
}
|
|
237
|
-
async function
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
375
|
+
async function stopCommand(cmd, io) {
|
|
376
|
+
const record = resolveRecord(cmd.share, io);
|
|
377
|
+
if (!record) return 1;
|
|
378
|
+
const res = await controlFetch(record, "/control/stop", {
|
|
379
|
+
method: "POST",
|
|
380
|
+
body: { share: record.id }
|
|
381
|
+
});
|
|
382
|
+
clearActiveShare(record.id);
|
|
383
|
+
if (!res.ok) {
|
|
384
|
+
io.err(res.status === 0 ? "vibeshare: the share process was not running (record cleaned up)" : `vibeshare: ${res.message}`);
|
|
385
|
+
return res.status === 0 ? 0 : 1;
|
|
386
|
+
}
|
|
387
|
+
io.out(`\u2713 stopped ${record.url}`);
|
|
241
388
|
return 0;
|
|
242
389
|
}
|
|
243
|
-
async function
|
|
244
|
-
|
|
245
|
-
|
|
390
|
+
async function run(argv, io = stdio) {
|
|
391
|
+
let command;
|
|
392
|
+
try {
|
|
393
|
+
command = parseArgv(argv);
|
|
394
|
+
} catch (err) {
|
|
395
|
+
if (err instanceof CliUsageError) {
|
|
396
|
+
io.err(`vibeshare: ${err.message}
|
|
397
|
+
`);
|
|
398
|
+
io.err(USAGE);
|
|
399
|
+
return 2;
|
|
400
|
+
}
|
|
401
|
+
throw err;
|
|
402
|
+
}
|
|
403
|
+
switch (command.cmd) {
|
|
246
404
|
case "help":
|
|
247
|
-
|
|
405
|
+
io.out(USAGE);
|
|
248
406
|
return 0;
|
|
249
407
|
case "version":
|
|
250
|
-
|
|
251
|
-
`);
|
|
408
|
+
io.out(`vibeshare ${VERSION}`);
|
|
252
409
|
return 0;
|
|
253
|
-
case "
|
|
254
|
-
|
|
255
|
-
await runMcpStdio();
|
|
256
|
-
return 0;
|
|
257
|
-
}
|
|
258
|
-
case "stop":
|
|
259
|
-
return runStop();
|
|
410
|
+
case "start":
|
|
411
|
+
return startShare(command.options, io);
|
|
260
412
|
case "viewers":
|
|
261
|
-
return
|
|
262
|
-
case "
|
|
263
|
-
return
|
|
264
|
-
case "error":
|
|
265
|
-
process.stderr.write(`vibeshare: ${parsed.message}
|
|
266
|
-
`);
|
|
267
|
-
printHelp(process.stderr);
|
|
268
|
-
return 2;
|
|
413
|
+
return viewersCommand(command, io);
|
|
414
|
+
case "stop":
|
|
415
|
+
return stopCommand(command, io);
|
|
269
416
|
}
|
|
270
417
|
}
|
|
271
|
-
|
|
272
|
-
|
|
418
|
+
var isMain = process.argv[1] !== void 0 && import.meta.url === new URL(`file://${process.argv[1]}`).href;
|
|
419
|
+
if (isMain) {
|
|
420
|
+
run(process.argv.slice(2)).then(
|
|
421
|
+
(code) => process.exit(code),
|
|
422
|
+
(err) => {
|
|
423
|
+
console.error("vibeshare:", err);
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
);
|
|
273
427
|
}
|
|
274
428
|
export {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
429
|
+
CliUsageError,
|
|
430
|
+
parseArgv,
|
|
431
|
+
run
|
|
278
432
|
};
|