vibedate 0.4.1 → 0.6.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/README.md +14 -1
- package/dist/{chunk-ZB56ZBAR.js → chunk-JIBC3OHV.js} +1 -1
- package/dist/{chunk-43IYNWES.js → chunk-VZXPYU2C.js} +259 -1
- package/dist/cli.d.ts +23 -2
- package/dist/cli.js +631 -22
- package/dist/index.d.ts +136 -1
- package/dist/index.js +15 -1
- package/dist/mcp.js +2 -2
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runMcp
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-JIBC3OHV.js";
|
|
5
5
|
import {
|
|
6
6
|
CANDIDATES,
|
|
7
|
+
DEFAULT_HANDLE,
|
|
7
8
|
LIVE_NOTICE,
|
|
9
|
+
MAX_HANDLE_LEN,
|
|
8
10
|
TOPIC_PREFIX,
|
|
9
11
|
addBlock,
|
|
10
12
|
allLeagueNames,
|
|
11
13
|
canShareLive,
|
|
12
14
|
connectProfile,
|
|
15
|
+
createNostrPoolTransport,
|
|
16
|
+
createNostrRelayLink,
|
|
17
|
+
defaultStateDir,
|
|
13
18
|
grantLiveConsent,
|
|
14
19
|
isBlocked,
|
|
15
20
|
league,
|
|
@@ -17,7 +22,9 @@ import {
|
|
|
17
22
|
leagueTopic,
|
|
18
23
|
leaguesWithin,
|
|
19
24
|
loadBlocklist,
|
|
25
|
+
loadHandle,
|
|
20
26
|
loadOrCreateIdentity,
|
|
27
|
+
loadOrCreateNostrKey,
|
|
21
28
|
loadPeers,
|
|
22
29
|
loadProfile,
|
|
23
30
|
matches,
|
|
@@ -27,16 +34,356 @@ import {
|
|
|
27
34
|
removeBlock,
|
|
28
35
|
resolveHandle,
|
|
29
36
|
sameHandle,
|
|
37
|
+
sanitizePeerText,
|
|
30
38
|
saveHandle,
|
|
31
39
|
signHelloClaims,
|
|
32
40
|
startDiscovery
|
|
33
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-VZXPYU2C.js";
|
|
34
42
|
|
|
35
43
|
// src/cli.ts
|
|
36
44
|
import readline from "readline";
|
|
37
45
|
import "url";
|
|
38
46
|
import process2 from "process";
|
|
39
47
|
|
|
48
|
+
// src/handlegen.ts
|
|
49
|
+
import { randomBytes } from "crypto";
|
|
50
|
+
var FIRST = [
|
|
51
|
+
"segfault",
|
|
52
|
+
"yak",
|
|
53
|
+
"vibe",
|
|
54
|
+
"null",
|
|
55
|
+
"async",
|
|
56
|
+
"await",
|
|
57
|
+
"heap",
|
|
58
|
+
"stack",
|
|
59
|
+
"sudo",
|
|
60
|
+
"regex",
|
|
61
|
+
"token",
|
|
62
|
+
"prompt",
|
|
63
|
+
"context",
|
|
64
|
+
"merge",
|
|
65
|
+
"rebase",
|
|
66
|
+
"hotfix",
|
|
67
|
+
"flaky",
|
|
68
|
+
"cursed",
|
|
69
|
+
"based",
|
|
70
|
+
"quantum",
|
|
71
|
+
"turbo",
|
|
72
|
+
"neural",
|
|
73
|
+
"agentic",
|
|
74
|
+
"kernel",
|
|
75
|
+
"docker",
|
|
76
|
+
"kube",
|
|
77
|
+
"lambda",
|
|
78
|
+
"pointer",
|
|
79
|
+
"buffer",
|
|
80
|
+
"packet",
|
|
81
|
+
"syscall",
|
|
82
|
+
"runtime",
|
|
83
|
+
"monad",
|
|
84
|
+
"borrow",
|
|
85
|
+
"cache",
|
|
86
|
+
"deadlock",
|
|
87
|
+
"localhost",
|
|
88
|
+
"darkmode",
|
|
89
|
+
"wasm",
|
|
90
|
+
"diff"
|
|
91
|
+
];
|
|
92
|
+
var SECOND = [
|
|
93
|
+
"sommelier",
|
|
94
|
+
"shaver",
|
|
95
|
+
"goblin",
|
|
96
|
+
"nomad",
|
|
97
|
+
"gremlin",
|
|
98
|
+
"wizard",
|
|
99
|
+
"bard",
|
|
100
|
+
"pirate",
|
|
101
|
+
"ninja",
|
|
102
|
+
"monk",
|
|
103
|
+
"prophet",
|
|
104
|
+
"oracle",
|
|
105
|
+
"smith",
|
|
106
|
+
"farmer",
|
|
107
|
+
"whisperer",
|
|
108
|
+
"tamer",
|
|
109
|
+
"connoisseur",
|
|
110
|
+
"maximalist",
|
|
111
|
+
"enjoyer",
|
|
112
|
+
"dealer",
|
|
113
|
+
"herder",
|
|
114
|
+
"wrangler",
|
|
115
|
+
"juggler",
|
|
116
|
+
"mechanic",
|
|
117
|
+
"surgeon",
|
|
118
|
+
"detective",
|
|
119
|
+
"librarian",
|
|
120
|
+
"alchemist",
|
|
121
|
+
"overlord",
|
|
122
|
+
"apprentice",
|
|
123
|
+
"sensei",
|
|
124
|
+
"chef",
|
|
125
|
+
"dj",
|
|
126
|
+
"ranger",
|
|
127
|
+
"paladin",
|
|
128
|
+
"barbarian",
|
|
129
|
+
"summoner",
|
|
130
|
+
"cartographer",
|
|
131
|
+
"archivist",
|
|
132
|
+
"plumber"
|
|
133
|
+
];
|
|
134
|
+
var SUFFIX = [
|
|
135
|
+
"prime",
|
|
136
|
+
"9000",
|
|
137
|
+
"3000",
|
|
138
|
+
"max",
|
|
139
|
+
"ultra",
|
|
140
|
+
"xl",
|
|
141
|
+
"mk2",
|
|
142
|
+
"777",
|
|
143
|
+
"404",
|
|
144
|
+
"1337",
|
|
145
|
+
"2077",
|
|
146
|
+
"xd"
|
|
147
|
+
];
|
|
148
|
+
function cryptoRand() {
|
|
149
|
+
return randomBytes(4).readUInt32BE(0) / 2 ** 32;
|
|
150
|
+
}
|
|
151
|
+
function pick(list, rand) {
|
|
152
|
+
return list[Math.min(list.length - 1, Math.floor(rand() * list.length))];
|
|
153
|
+
}
|
|
154
|
+
function generateHandle(rand = cryptoRand) {
|
|
155
|
+
const first = pick(FIRST, rand);
|
|
156
|
+
const second = pick(SECOND, rand);
|
|
157
|
+
let body = `${first}_${second}`;
|
|
158
|
+
if (rand() < 0.5) {
|
|
159
|
+
const withSuffix = `${body}_${pick(SUFFIX, rand)}`;
|
|
160
|
+
if (withSuffix.length + 1 <= MAX_HANDLE_LEN) body = withSuffix;
|
|
161
|
+
}
|
|
162
|
+
const canonical = normalizeHandle(body);
|
|
163
|
+
if (canonical === null) throw new Error(`handle generator produced an invalid handle: ${body}`);
|
|
164
|
+
return canonical;
|
|
165
|
+
}
|
|
166
|
+
function ensureHandle(dir = defaultStateDir()) {
|
|
167
|
+
const env = process.env["VIBEDATING_HANDLE"];
|
|
168
|
+
if (env !== void 0 && env.trim() !== "") {
|
|
169
|
+
const canonical = normalizeHandle(env);
|
|
170
|
+
if (canonical !== null) return { handle: canonical, generated: false };
|
|
171
|
+
}
|
|
172
|
+
const persisted = loadHandle(dir);
|
|
173
|
+
if (persisted !== DEFAULT_HANDLE) return { handle: persisted, generated: false };
|
|
174
|
+
const generated = generateHandle();
|
|
175
|
+
return { handle: saveHandle(generated, dir), generated: true };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/daemon.ts
|
|
179
|
+
import { spawn, spawnSync } from "child_process";
|
|
180
|
+
import { mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
181
|
+
import os from "os";
|
|
182
|
+
import path from "path";
|
|
183
|
+
function daemonStatePath(dir) {
|
|
184
|
+
return path.join(dir, "daemon.json");
|
|
185
|
+
}
|
|
186
|
+
function daemonLogPath(dir) {
|
|
187
|
+
return path.join(dir, "daemon.log");
|
|
188
|
+
}
|
|
189
|
+
function readDaemonState(dir = defaultStateDir()) {
|
|
190
|
+
try {
|
|
191
|
+
const raw = readFileSync(daemonStatePath(dir), "utf8");
|
|
192
|
+
const data = JSON.parse(raw);
|
|
193
|
+
if (typeof data["pid"] !== "number" || !Number.isInteger(data["pid"]) || data["pid"] <= 0 || typeof data["startedAt"] !== "string" || typeof data["any"] !== "boolean" || typeof data["version"] !== "string") {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
return { pid: data["pid"], startedAt: data["startedAt"], any: data["any"], version: data["version"] };
|
|
197
|
+
} catch {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function writeDaemonState(dir, state) {
|
|
202
|
+
mkdirSync(dir, { recursive: true });
|
|
203
|
+
writeFileSync(daemonStatePath(dir), JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
204
|
+
}
|
|
205
|
+
function removeDaemonState(dir = defaultStateDir()) {
|
|
206
|
+
try {
|
|
207
|
+
rmSync(daemonStatePath(dir), { force: true });
|
|
208
|
+
} catch {
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
function isPidAlive(pid, kill = process.kill) {
|
|
212
|
+
try {
|
|
213
|
+
kill(pid, 0);
|
|
214
|
+
return true;
|
|
215
|
+
} catch (err) {
|
|
216
|
+
return err.code === "EPERM";
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function daemonStatus(dir = defaultStateDir(), alive = isPidAlive) {
|
|
220
|
+
const state = readDaemonState(dir);
|
|
221
|
+
if (state === null) return { running: false, state: null };
|
|
222
|
+
if (alive(state.pid)) return { running: true, state };
|
|
223
|
+
removeDaemonState(dir);
|
|
224
|
+
return { running: false, state: null };
|
|
225
|
+
}
|
|
226
|
+
function defaultSpawn(execPath, scriptPath, args, logPath) {
|
|
227
|
+
mkdirSync(path.dirname(logPath), { recursive: true });
|
|
228
|
+
const fd = openSync(logPath, "a");
|
|
229
|
+
const child = spawn(execPath, [scriptPath, ...args], {
|
|
230
|
+
detached: true,
|
|
231
|
+
stdio: ["ignore", fd, fd]
|
|
232
|
+
});
|
|
233
|
+
child.unref();
|
|
234
|
+
if (child.pid === void 0) throw new Error("could not spawn daemon child process");
|
|
235
|
+
return child.pid;
|
|
236
|
+
}
|
|
237
|
+
function startDaemon(opts) {
|
|
238
|
+
const dir = opts.dir ?? defaultStateDir();
|
|
239
|
+
const status = daemonStatus(dir, opts.alive);
|
|
240
|
+
if (status.running && status.state !== null) {
|
|
241
|
+
return { started: false, reason: `already running (pid ${status.state.pid})` };
|
|
242
|
+
}
|
|
243
|
+
const execPath = opts.execPath ?? process.execPath;
|
|
244
|
+
const scriptPath = opts.scriptPath ?? process.argv[1];
|
|
245
|
+
if (scriptPath === void 0) return { started: false, reason: "cannot locate the CLI entry point" };
|
|
246
|
+
const args = ["daemon", "run", ...opts.any ? ["--any"] : []];
|
|
247
|
+
const spawnProcess = opts.spawnProcess ?? ((a, logPath) => defaultSpawn(execPath, scriptPath, [...a], logPath));
|
|
248
|
+
const pid = spawnProcess(args, daemonLogPath(dir));
|
|
249
|
+
writeDaemonState(dir, { pid, startedAt: (/* @__PURE__ */ new Date()).toISOString(), any: opts.any, version: opts.version });
|
|
250
|
+
return { started: true, pid };
|
|
251
|
+
}
|
|
252
|
+
async function stopDaemon(opts = {}) {
|
|
253
|
+
const dir = opts.dir ?? defaultStateDir();
|
|
254
|
+
const kill = opts.kill ?? process.kill;
|
|
255
|
+
const alive = opts.alive ?? ((pid) => isPidAlive(pid, kill));
|
|
256
|
+
const state = readDaemonState(dir);
|
|
257
|
+
if (state === null) return { stopped: false, reason: "not running" };
|
|
258
|
+
if (!alive(state.pid)) {
|
|
259
|
+
removeDaemonState(dir);
|
|
260
|
+
return { stopped: false, reason: "not running (cleaned stale pidfile)" };
|
|
261
|
+
}
|
|
262
|
+
try {
|
|
263
|
+
kill(state.pid, "SIGTERM");
|
|
264
|
+
} catch {
|
|
265
|
+
}
|
|
266
|
+
const deadline = Date.now() + (opts.waitMs ?? 2e3);
|
|
267
|
+
while (Date.now() < deadline && alive(state.pid)) {
|
|
268
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
269
|
+
}
|
|
270
|
+
removeDaemonState(dir);
|
|
271
|
+
return { stopped: true, pid: state.pid };
|
|
272
|
+
}
|
|
273
|
+
var DAEMON_SERVICE_LABEL = "ai.vibedating.daemon";
|
|
274
|
+
var SYSTEMD_UNIT_NAME = "vibedating.service";
|
|
275
|
+
function daemonServicePath(platform = process.platform, homeDir = os.homedir()) {
|
|
276
|
+
if (platform === "darwin") {
|
|
277
|
+
return path.join(homeDir, "Library", "LaunchAgents", `${DAEMON_SERVICE_LABEL}.plist`);
|
|
278
|
+
}
|
|
279
|
+
if (platform === "linux") {
|
|
280
|
+
return path.join(homeDir, ".config", "systemd", "user", SYSTEMD_UNIT_NAME);
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
function serviceArgv(execPath, scriptPath, any) {
|
|
285
|
+
return [execPath, scriptPath, "daemon", "run", ...any ? ["--any"] : []];
|
|
286
|
+
}
|
|
287
|
+
function renderLaunchdPlist(opts) {
|
|
288
|
+
const args = serviceArgv(opts.execPath, opts.scriptPath, opts.any).map((a) => ` <string>${a}</string>`).join("\n");
|
|
289
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
290
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
291
|
+
<plist version="1.0">
|
|
292
|
+
<dict>
|
|
293
|
+
<key>Label</key>
|
|
294
|
+
<string>${DAEMON_SERVICE_LABEL}</string>
|
|
295
|
+
<key>ProgramArguments</key>
|
|
296
|
+
<array>
|
|
297
|
+
${args}
|
|
298
|
+
</array>
|
|
299
|
+
<key>RunAtLoad</key>
|
|
300
|
+
<true/>
|
|
301
|
+
<key>KeepAlive</key>
|
|
302
|
+
<false/>
|
|
303
|
+
<key>StandardOutPath</key>
|
|
304
|
+
<string>${opts.logPath}</string>
|
|
305
|
+
<key>StandardErrorPath</key>
|
|
306
|
+
<string>${opts.logPath}</string>
|
|
307
|
+
</dict>
|
|
308
|
+
</plist>
|
|
309
|
+
`;
|
|
310
|
+
}
|
|
311
|
+
function renderSystemdUnit(opts) {
|
|
312
|
+
const execStart = serviceArgv(opts.execPath, opts.scriptPath, opts.any).join(" ");
|
|
313
|
+
return `[Unit]
|
|
314
|
+
Description=vibedating notify-only daemon (alerts on new matches; never opens chat/video)
|
|
315
|
+
|
|
316
|
+
[Service]
|
|
317
|
+
ExecStart=${execStart}
|
|
318
|
+
Restart=on-failure
|
|
319
|
+
RestartSec=10
|
|
320
|
+
|
|
321
|
+
[Install]
|
|
322
|
+
WantedBy=default.target
|
|
323
|
+
`;
|
|
324
|
+
}
|
|
325
|
+
var defaultRun = (cmd, args) => spawnSync(cmd, args, { stdio: "inherit" }).status === 0;
|
|
326
|
+
function installDaemonService(opts) {
|
|
327
|
+
const platform = opts.platform ?? process.platform;
|
|
328
|
+
const homeDir = opts.homeDir ?? os.homedir();
|
|
329
|
+
const run = opts.run ?? defaultRun;
|
|
330
|
+
const servicePath = daemonServicePath(platform, homeDir);
|
|
331
|
+
if (servicePath === null) {
|
|
332
|
+
return {
|
|
333
|
+
installed: false,
|
|
334
|
+
servicePath: null,
|
|
335
|
+
detail: `unsupported platform (${platform}) \u2014 run \`vibedate daemon start\` manually instead`
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
const execPath = opts.execPath ?? process.execPath;
|
|
339
|
+
const scriptPath = opts.scriptPath ?? process.argv[1];
|
|
340
|
+
if (scriptPath === void 0) {
|
|
341
|
+
return { installed: false, servicePath, detail: "cannot locate the CLI entry point" };
|
|
342
|
+
}
|
|
343
|
+
mkdirSync(path.dirname(servicePath), { recursive: true });
|
|
344
|
+
if (platform === "darwin") {
|
|
345
|
+
const logPath = daemonLogPath(opts.dir ?? defaultStateDir());
|
|
346
|
+
writeFileSync(servicePath, renderLaunchdPlist({ execPath, scriptPath, any: opts.any, logPath }), "utf8");
|
|
347
|
+
const uid = process.getuid?.() ?? 501;
|
|
348
|
+
run("launchctl", ["bootout", `gui/${uid}`, servicePath]);
|
|
349
|
+
if (!run("launchctl", ["bootstrap", `gui/${uid}`, servicePath])) {
|
|
350
|
+
return { installed: false, servicePath, detail: "launchctl bootstrap failed \u2014 service written but not loaded" };
|
|
351
|
+
}
|
|
352
|
+
return { installed: true, servicePath, detail: "launchd agent installed \u2014 the daemon starts on login" };
|
|
353
|
+
}
|
|
354
|
+
writeFileSync(servicePath, renderSystemdUnit({ execPath, scriptPath, any: opts.any }), "utf8");
|
|
355
|
+
run("systemctl", ["--user", "daemon-reload"]);
|
|
356
|
+
if (!run("systemctl", ["--user", "enable", "--now", SYSTEMD_UNIT_NAME])) {
|
|
357
|
+
return { installed: false, servicePath, detail: "systemctl enable failed \u2014 unit written but not started" };
|
|
358
|
+
}
|
|
359
|
+
return { installed: true, servicePath, detail: "systemd --user service installed \u2014 the daemon starts on login" };
|
|
360
|
+
}
|
|
361
|
+
function uninstallDaemonService(opts) {
|
|
362
|
+
const platform = opts.platform ?? process.platform;
|
|
363
|
+
const homeDir = opts.homeDir ?? os.homedir();
|
|
364
|
+
const run = opts.run ?? defaultRun;
|
|
365
|
+
const servicePath = daemonServicePath(platform, homeDir);
|
|
366
|
+
if (servicePath === null) {
|
|
367
|
+
return {
|
|
368
|
+
installed: false,
|
|
369
|
+
servicePath: null,
|
|
370
|
+
detail: `unsupported platform (${platform})`
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
if (platform === "darwin") {
|
|
374
|
+
const uid = process.getuid?.() ?? 501;
|
|
375
|
+
run("launchctl", ["bootout", `gui/${uid}`, servicePath]);
|
|
376
|
+
} else {
|
|
377
|
+
run("systemctl", ["--user", "disable", "--now", SYSTEMD_UNIT_NAME]);
|
|
378
|
+
run("systemctl", ["--user", "daemon-reload"]);
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
rmSync(servicePath, { force: true });
|
|
382
|
+
} catch {
|
|
383
|
+
}
|
|
384
|
+
return { installed: false, servicePath, detail: "login service removed \u2014 the daemon no longer starts on login" };
|
|
385
|
+
}
|
|
386
|
+
|
|
40
387
|
// src/pairing.ts
|
|
41
388
|
function createPairing() {
|
|
42
389
|
const queue = [];
|
|
@@ -1499,7 +1846,7 @@ function createLiveBridge() {
|
|
|
1499
1846
|
link.onMessage((m) => {
|
|
1500
1847
|
const mb = boxes.get(handle2);
|
|
1501
1848
|
if (!mb) return;
|
|
1502
|
-
mb.messages.push(m);
|
|
1849
|
+
mb.messages.push({ ...m, text: sanitizePeerText(m.text) });
|
|
1503
1850
|
if (mb.messages.length > MAX_QUEUED_MESSAGES) {
|
|
1504
1851
|
mb.messages.splice(0, mb.messages.length - MAX_QUEUED_MESSAGES);
|
|
1505
1852
|
}
|
|
@@ -1768,7 +2115,7 @@ async function handle(req, res, opts) {
|
|
|
1768
2115
|
}
|
|
1769
2116
|
|
|
1770
2117
|
// src/cli.ts
|
|
1771
|
-
var VERSION = "0.
|
|
2118
|
+
var VERSION = "0.6.0";
|
|
1772
2119
|
function parsePort(raw) {
|
|
1773
2120
|
const n = Number(raw);
|
|
1774
2121
|
if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
|
|
@@ -1782,7 +2129,9 @@ function parseArgs(argv) {
|
|
|
1782
2129
|
dating: false,
|
|
1783
2130
|
any: false,
|
|
1784
2131
|
arg: void 0,
|
|
1785
|
-
to: void 0
|
|
2132
|
+
to: void 0,
|
|
2133
|
+
keepAlive: false,
|
|
2134
|
+
viaRelay: false
|
|
1786
2135
|
};
|
|
1787
2136
|
for (let i = 0; i < argv.length; i++) {
|
|
1788
2137
|
const a = argv[i];
|
|
@@ -1794,7 +2143,9 @@ function parseArgs(argv) {
|
|
|
1794
2143
|
dating: false,
|
|
1795
2144
|
any: false,
|
|
1796
2145
|
arg: void 0,
|
|
1797
|
-
to: void 0
|
|
2146
|
+
to: void 0,
|
|
2147
|
+
keepAlive: false,
|
|
2148
|
+
viaRelay: false
|
|
1798
2149
|
};
|
|
1799
2150
|
}
|
|
1800
2151
|
if (a === "--help" || a === "-h") {
|
|
@@ -1805,13 +2156,23 @@ function parseArgs(argv) {
|
|
|
1805
2156
|
dating: false,
|
|
1806
2157
|
any: false,
|
|
1807
2158
|
arg: void 0,
|
|
1808
|
-
to: void 0
|
|
2159
|
+
to: void 0,
|
|
2160
|
+
keepAlive: false,
|
|
2161
|
+
viaRelay: false
|
|
1809
2162
|
};
|
|
1810
2163
|
}
|
|
1811
2164
|
if (a === "--live") {
|
|
1812
2165
|
out = { ...out, live: true };
|
|
1813
2166
|
continue;
|
|
1814
2167
|
}
|
|
2168
|
+
if (a === "--keep-alive") {
|
|
2169
|
+
out = { ...out, keepAlive: true };
|
|
2170
|
+
continue;
|
|
2171
|
+
}
|
|
2172
|
+
if (a === "--via-relay") {
|
|
2173
|
+
out = { ...out, viaRelay: true };
|
|
2174
|
+
continue;
|
|
2175
|
+
}
|
|
1815
2176
|
if (a === "--any") {
|
|
1816
2177
|
out = { ...out, any: true };
|
|
1817
2178
|
continue;
|
|
@@ -1847,7 +2208,7 @@ function parseArgs(argv) {
|
|
|
1847
2208
|
continue;
|
|
1848
2209
|
}
|
|
1849
2210
|
if (a.startsWith("-")) continue;
|
|
1850
|
-
const known = a === "connect" || a === "matches" || a === "discover" || a === "open" || a === "live" || a === "find" || a === "handle" || a === "block" || a === "unblock" || a === "blocklist" || a === "mcp" || a === "help" ? a : null;
|
|
2211
|
+
const known = a === "connect" || a === "matches" || a === "discover" || a === "open" || a === "live" || a === "find" || a === "handle" || a === "block" || a === "unblock" || a === "blocklist" || a === "daemon" || a === "mcp" || a === "help" ? a : null;
|
|
1851
2212
|
if (known !== null && out.command === null) {
|
|
1852
2213
|
out = { ...out, command: known };
|
|
1853
2214
|
} else if (out.arg === void 0) {
|
|
@@ -1867,6 +2228,17 @@ function formatTokens(n) {
|
|
|
1867
2228
|
if (abs >= 1e3) return `${trim(n / 1e3)}k`;
|
|
1868
2229
|
return String(n);
|
|
1869
2230
|
}
|
|
2231
|
+
function formatAgo(iso, now = /* @__PURE__ */ new Date()) {
|
|
2232
|
+
const t = Date.parse(iso);
|
|
2233
|
+
if (Number.isNaN(t)) return "unknown";
|
|
2234
|
+
const s = Math.max(0, Math.floor((now.getTime() - t) / 1e3));
|
|
2235
|
+
if (s < 60) return "just now";
|
|
2236
|
+
const m = Math.floor(s / 60);
|
|
2237
|
+
if (m < 60) return `${m}m ago`;
|
|
2238
|
+
const h = Math.floor(m / 60);
|
|
2239
|
+
if (h < 24) return `${h}h ago`;
|
|
2240
|
+
return `${Math.floor(h / 24)}d ago`;
|
|
2241
|
+
}
|
|
1870
2242
|
function verificationText(snapshot) {
|
|
1871
2243
|
if (snapshot.source === "real") {
|
|
1872
2244
|
return `verified: real usage \u2014 ${formatTokens(snapshot.totalTokens)} tokens from ${snapshot.harness} logs`;
|
|
@@ -1913,7 +2285,9 @@ function idMark(peer) {
|
|
|
1913
2285
|
var MARKS_LEGEND = "marks: \u2713 usage verified (real local logs) \xB7 ~ unverified (self-report/demo/legacy) \xB7 \u{1F511} identity-verified (signed hello)";
|
|
1914
2286
|
async function cmdConnect() {
|
|
1915
2287
|
const harness = process2.env["VIBEDATING_HARNESS"] ?? "claude-code";
|
|
1916
|
-
const
|
|
2288
|
+
const ensured = ensureHandle();
|
|
2289
|
+
const handle2 = ensured.handle;
|
|
2290
|
+
const firstConnect = loadProfile() === null;
|
|
1917
2291
|
const snapshot = await readUsage(harness);
|
|
1918
2292
|
const profile = connectProfile(snapshot, handle2);
|
|
1919
2293
|
const identity = loadOrCreateIdentity();
|
|
@@ -1923,12 +2297,20 @@ async function cmdConnect() {
|
|
|
1923
2297
|
`);
|
|
1924
2298
|
process2.stdout.write(` handle: ${profile.handle} \xB7 harness: ${profile.harness}
|
|
1925
2299
|
`);
|
|
2300
|
+
if (ensured.generated) {
|
|
2301
|
+
process2.stdout.write(` assigned handle: ${profile.handle} \u2014 change it with: vibedate handle @name
|
|
2302
|
+
`);
|
|
2303
|
+
}
|
|
1926
2304
|
process2.stdout.write(` verification: ${verificationText(snapshot)}
|
|
1927
2305
|
`);
|
|
1928
2306
|
process2.stdout.write(` identity: ed25519 ${identity.publicKeyHex.slice(0, 12)}\u2026 \u2014 signs your hello (\u{1F511})
|
|
1929
2307
|
`);
|
|
1930
2308
|
process2.stdout.write("\n");
|
|
1931
|
-
process2.stdout.write(" \u2022 raw usage stays local \xB7 only league shared\n
|
|
2309
|
+
process2.stdout.write(" \u2022 raw usage stays local \xB7 only league shared\n");
|
|
2310
|
+
if (firstConnect) {
|
|
2311
|
+
process2.stdout.write(" \u2022 optional: get notified of new matches on login \u2014 `vibedate daemon install` (undo: daemon uninstall)\n");
|
|
2312
|
+
}
|
|
2313
|
+
process2.stdout.write("\n");
|
|
1932
2314
|
return 0;
|
|
1933
2315
|
}
|
|
1934
2316
|
async function cmdHandle(arg) {
|
|
@@ -1981,7 +2363,8 @@ async function cmdMatches(live) {
|
|
|
1981
2363
|
|
|
1982
2364
|
`);
|
|
1983
2365
|
for (const p of livePeers) {
|
|
1984
|
-
|
|
2366
|
+
const lastMsg = p.lastMessageAt !== void 0 ? ` \xB7 last msg ${formatAgo(p.lastMessageAt)}` : "";
|
|
2367
|
+
process2.stdout.write(` ${p.handle.padEnd(28)} ${p.league} \xB7 ${p.harness} ${usageMark(p)}${idMark(p)}${lastMsg}
|
|
1985
2368
|
`);
|
|
1986
2369
|
}
|
|
1987
2370
|
process2.stdout.write("\n");
|
|
@@ -2008,7 +2391,7 @@ async function cmdMatches(live) {
|
|
|
2008
2391
|
process2.stdout.write("\n");
|
|
2009
2392
|
return 0;
|
|
2010
2393
|
}
|
|
2011
|
-
async function cmdDiscover(live, any) {
|
|
2394
|
+
async function cmdDiscover(live, any, viaRelay) {
|
|
2012
2395
|
const profile = loadProfile();
|
|
2013
2396
|
if (!profile) {
|
|
2014
2397
|
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
@@ -2027,6 +2410,9 @@ async function cmdDiscover(live, any) {
|
|
|
2027
2410
|
`);
|
|
2028
2411
|
process2.stdout.write(` ${MARKS_LEGEND}
|
|
2029
2412
|
`);
|
|
2413
|
+
if (viaRelay) {
|
|
2414
|
+
process2.stdout.write(" \u2022 --via-relay: the relay fallback reaches a KNOWN peer over Nostr \u2014 use `live --via-relay --to @handle`\n");
|
|
2415
|
+
}
|
|
2030
2416
|
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
2031
2417
|
const session = await startDiscovery({
|
|
2032
2418
|
hello,
|
|
@@ -2113,7 +2499,103 @@ async function cmdOpen(port, any) {
|
|
|
2113
2499
|
await new Promise((resolve) => started.server.close(() => resolve()));
|
|
2114
2500
|
return 0;
|
|
2115
2501
|
}
|
|
2116
|
-
|
|
2502
|
+
function shouldKeepAlive(flag, stdinIsTTY) {
|
|
2503
|
+
return flag || stdinIsTTY !== true;
|
|
2504
|
+
}
|
|
2505
|
+
async function cmdLiveViaRelay(profile, to) {
|
|
2506
|
+
const target = to !== void 0 ? normalizeHandle(to) : null;
|
|
2507
|
+
if (to !== void 0 && target === null) {
|
|
2508
|
+
process2.stderr.write(`invalid target handle: ${to}
|
|
2509
|
+
`);
|
|
2510
|
+
return 1;
|
|
2511
|
+
}
|
|
2512
|
+
if (target === null) {
|
|
2513
|
+
process2.stderr.write(
|
|
2514
|
+
"relay mode (v0) targets one known peer \u2014 use: vibedating live --via-relay --to <@handle>\n(discover the peer first with `vibedating discover --live`, then reach them over the relay).\n"
|
|
2515
|
+
);
|
|
2516
|
+
return 1;
|
|
2517
|
+
}
|
|
2518
|
+
const peer = loadPeers().find((p) => sameHandle(p.handle, target) && typeof p.pubkey === "string");
|
|
2519
|
+
if (peer === void 0 || peer.pubkey === void 0) {
|
|
2520
|
+
process2.stderr.write(
|
|
2521
|
+
`${target} is not a known peer with an identity pubkey. Run \`vibedating discover --live\` first to meet them over the DHT, then retry --via-relay.
|
|
2522
|
+
`
|
|
2523
|
+
);
|
|
2524
|
+
return 1;
|
|
2525
|
+
}
|
|
2526
|
+
const identity = loadOrCreateIdentity();
|
|
2527
|
+
process2.stdout.write("\n");
|
|
2528
|
+
process2.stdout.write(` ${LIVE_NOTICE}
|
|
2529
|
+
`);
|
|
2530
|
+
process2.stdout.write(
|
|
2531
|
+
` routing e2e chat to ${sanitizePeerText(peer.handle)} through public Nostr relays (direct hole-punch fallback)
|
|
2532
|
+
`
|
|
2533
|
+
);
|
|
2534
|
+
process2.stdout.write(" \u2022 the relay stores only ciphertext \u2014 it cannot read your chat\n");
|
|
2535
|
+
const myNostr = await loadOrCreateNostrKey();
|
|
2536
|
+
const transport = await createNostrPoolTransport();
|
|
2537
|
+
const link = await createNostrRelayLink({
|
|
2538
|
+
myNostr,
|
|
2539
|
+
myEd25519Hex: identity.publicKeyHex,
|
|
2540
|
+
peerEd25519Hex: peer.pubkey,
|
|
2541
|
+
hello: peer,
|
|
2542
|
+
transport
|
|
2543
|
+
});
|
|
2544
|
+
const pairing = createPairing();
|
|
2545
|
+
pairing.onMatch((l) => {
|
|
2546
|
+
if (l === void 0) {
|
|
2547
|
+
process2.stdout.write(" \xB7 relay peer gone\n");
|
|
2548
|
+
return;
|
|
2549
|
+
}
|
|
2550
|
+
const { qual } = peerDirection(profile.league, l.hello.league);
|
|
2551
|
+
process2.stdout.write(
|
|
2552
|
+
` \xB7 relayed to ${sanitizePeerText(l.hello.handle)} (${l.hello.league}${qual} \xB7 ${l.hello.harness}) ${usageMark(l.hello)}${idMark(l.hello)}
|
|
2553
|
+
`
|
|
2554
|
+
);
|
|
2555
|
+
l.onMessage((m) => {
|
|
2556
|
+
process2.stdout.write(` <${sanitizePeerText(l.hello.handle)}> ${sanitizePeerText(m.text)}
|
|
2557
|
+
`);
|
|
2558
|
+
});
|
|
2559
|
+
});
|
|
2560
|
+
pairing.add(link);
|
|
2561
|
+
process2.stdout.write(" type to chat \xB7 /quit\n");
|
|
2562
|
+
process2.stdout.write(" (Ctrl+C to stop)\n\n");
|
|
2563
|
+
const rl = readline.createInterface({ input: process2.stdin, terminal: false });
|
|
2564
|
+
const stop = () => {
|
|
2565
|
+
rl.close();
|
|
2566
|
+
};
|
|
2567
|
+
process2.once("SIGINT", stop);
|
|
2568
|
+
process2.once("SIGTERM", stop);
|
|
2569
|
+
let quitRequested = false;
|
|
2570
|
+
for await (const line of rl) {
|
|
2571
|
+
const text = line.trim();
|
|
2572
|
+
if (text === "/quit") {
|
|
2573
|
+
quitRequested = true;
|
|
2574
|
+
break;
|
|
2575
|
+
}
|
|
2576
|
+
if (text === "/next") {
|
|
2577
|
+
process2.stdout.write(" \xB7 relay mode targets one peer \u2014 /next re-announces presence\n");
|
|
2578
|
+
continue;
|
|
2579
|
+
}
|
|
2580
|
+
if (text === "") continue;
|
|
2581
|
+
const cur2 = pairing.current();
|
|
2582
|
+
if (cur2 !== void 0) {
|
|
2583
|
+
cur2.send(text);
|
|
2584
|
+
} else {
|
|
2585
|
+
process2.stdout.write(" \xB7 no relay peer yet \u2014 waiting for the presence exchange\u2026\n");
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
process2.removeListener("SIGINT", stop);
|
|
2589
|
+
process2.removeListener("SIGTERM", stop);
|
|
2590
|
+
void quitRequested;
|
|
2591
|
+
const cur = pairing.current();
|
|
2592
|
+
if (cur !== void 0) cur.close();
|
|
2593
|
+
process2.stdout.write("\n closing relay link\u2026\n");
|
|
2594
|
+
link.close();
|
|
2595
|
+
process2.stdout.write("\n");
|
|
2596
|
+
return 0;
|
|
2597
|
+
}
|
|
2598
|
+
async function cmdLive(dating, any, to, keepAlive, viaRelay) {
|
|
2117
2599
|
const profile = loadProfile();
|
|
2118
2600
|
if (!profile) {
|
|
2119
2601
|
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
@@ -2130,6 +2612,7 @@ async function cmdLive(dating, any, to) {
|
|
|
2130
2612
|
process2.stderr.write("Could not enable live discovery. Try `vibedating discover --live`.\n");
|
|
2131
2613
|
return 1;
|
|
2132
2614
|
}
|
|
2615
|
+
if (viaRelay) return cmdLiveViaRelay(profile, to);
|
|
2133
2616
|
const hello = buildHello(profile);
|
|
2134
2617
|
process2.stdout.write("\n");
|
|
2135
2618
|
process2.stdout.write(` ${LIVE_NOTICE}
|
|
@@ -2148,11 +2631,11 @@ async function cmdLive(dating, any, to) {
|
|
|
2148
2631
|
}
|
|
2149
2632
|
const { qual } = peerDirection(profile.league, link.hello.league);
|
|
2150
2633
|
process2.stdout.write(
|
|
2151
|
-
` \xB7 matched ${link.hello.handle} (${link.hello.league}${qual} \xB7 ${link.hello.harness}) ${usageMark(link.hello)}${idMark(link.hello)}
|
|
2634
|
+
` \xB7 matched ${sanitizePeerText(link.hello.handle)} (${link.hello.league}${qual} \xB7 ${link.hello.harness}) ${usageMark(link.hello)}${idMark(link.hello)}
|
|
2152
2635
|
`
|
|
2153
2636
|
);
|
|
2154
2637
|
link.onMessage((m) => {
|
|
2155
|
-
process2.stdout.write(` <${link.hello.handle}> ${m.text}
|
|
2638
|
+
process2.stdout.write(` <${sanitizePeerText(link.hello.handle)}> ${sanitizePeerText(m.text)}
|
|
2156
2639
|
`);
|
|
2157
2640
|
});
|
|
2158
2641
|
});
|
|
@@ -2166,14 +2649,14 @@ async function cmdLive(dating, any, to) {
|
|
|
2166
2649
|
if (target !== null && !sameHandle(link.hello.handle, target)) {
|
|
2167
2650
|
const { qual } = peerDirection(profile.league, link.hello.league);
|
|
2168
2651
|
process2.stdout.write(
|
|
2169
|
-
` + ${link.hello.handle} (${link.hello.league}${qual}) ${usageMark(link.hello)}${idMark(link.hello)} \u2014 not your target
|
|
2652
|
+
` + ${sanitizePeerText(link.hello.handle)} (${link.hello.league}${qual}) ${usageMark(link.hello)}${idMark(link.hello)} \u2014 not your target
|
|
2170
2653
|
`
|
|
2171
2654
|
);
|
|
2172
2655
|
link.close();
|
|
2173
2656
|
return;
|
|
2174
2657
|
}
|
|
2175
2658
|
if (target !== null) {
|
|
2176
|
-
process2.stdout.write(` \u2605 found ${link.hello.handle} \u2014 auto-opening
|
|
2659
|
+
process2.stdout.write(` \u2605 found ${sanitizePeerText(link.hello.handle)} \u2014 auto-opening
|
|
2177
2660
|
`);
|
|
2178
2661
|
}
|
|
2179
2662
|
pairing.add(link);
|
|
@@ -2191,9 +2674,13 @@ async function cmdLive(dating, any, to) {
|
|
|
2191
2674
|
};
|
|
2192
2675
|
process2.once("SIGINT", stop);
|
|
2193
2676
|
process2.once("SIGTERM", stop);
|
|
2677
|
+
let quitRequested = false;
|
|
2194
2678
|
for await (const line of rl) {
|
|
2195
2679
|
const text = line.trim();
|
|
2196
|
-
if (text === "/quit")
|
|
2680
|
+
if (text === "/quit") {
|
|
2681
|
+
quitRequested = true;
|
|
2682
|
+
break;
|
|
2683
|
+
}
|
|
2197
2684
|
if (text === "/next") {
|
|
2198
2685
|
pairing.next();
|
|
2199
2686
|
continue;
|
|
@@ -2214,6 +2701,13 @@ async function cmdLive(dating, any, to) {
|
|
|
2214
2701
|
process2.stdout.write(" \xB7 no peer yet \u2014 waiting for a match\u2026\n");
|
|
2215
2702
|
}
|
|
2216
2703
|
}
|
|
2704
|
+
if (!quitRequested && shouldKeepAlive(keepAlive, process2.stdin.isTTY)) {
|
|
2705
|
+
process2.stdout.write(" stdin closed \u2014 staying in the swarm (Ctrl+C / SIGTERM to leave)\n");
|
|
2706
|
+
await new Promise((resolve) => {
|
|
2707
|
+
process2.once("SIGINT", () => resolve());
|
|
2708
|
+
process2.once("SIGTERM", () => resolve());
|
|
2709
|
+
});
|
|
2710
|
+
}
|
|
2217
2711
|
process2.removeListener("SIGINT", stop);
|
|
2218
2712
|
process2.removeListener("SIGTERM", stop);
|
|
2219
2713
|
const cur = pairing.current();
|
|
@@ -2351,18 +2845,129 @@ async function cmdBlocklist() {
|
|
|
2351
2845
|
`);
|
|
2352
2846
|
return 0;
|
|
2353
2847
|
}
|
|
2848
|
+
async function cmdDaemonRun(any) {
|
|
2849
|
+
const profile = loadProfile();
|
|
2850
|
+
if (!profile) {
|
|
2851
|
+
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
2852
|
+
return 1;
|
|
2853
|
+
}
|
|
2854
|
+
if (!canShareLive()) grantLiveConsent();
|
|
2855
|
+
const dir = defaultStateDir();
|
|
2856
|
+
writeDaemonState(dir, { pid: process2.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString(), any, version: VERSION });
|
|
2857
|
+
const hello = buildHello(profile);
|
|
2858
|
+
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
2859
|
+
const session = await startDiscovery({
|
|
2860
|
+
hello,
|
|
2861
|
+
topics,
|
|
2862
|
+
acceptLeague,
|
|
2863
|
+
isBlocked: blockedChecker(),
|
|
2864
|
+
onPeer: (peer, isNew) => {
|
|
2865
|
+
process2.stdout.write(
|
|
2866
|
+
` [${(/* @__PURE__ */ new Date()).toISOString()}] ${isNew ? "NEW match" : "peer seen"}: ${sanitizePeerText(peer.handle)} (${peer.league} \xB7 ${peer.harness})
|
|
2867
|
+
`
|
|
2868
|
+
);
|
|
2869
|
+
}
|
|
2870
|
+
});
|
|
2871
|
+
process2.stdout.write(
|
|
2872
|
+
` vibedating daemon running (pid ${process2.pid}) \u2014 notify-only${any ? " \xB7 --any" : ""}
|
|
2873
|
+
`
|
|
2874
|
+
);
|
|
2875
|
+
await new Promise((resolve) => {
|
|
2876
|
+
process2.once("SIGINT", () => resolve());
|
|
2877
|
+
process2.once("SIGTERM", () => resolve());
|
|
2878
|
+
});
|
|
2879
|
+
await session.close();
|
|
2880
|
+
removeDaemonState(dir);
|
|
2881
|
+
process2.stdout.write(" daemon stopped\n");
|
|
2882
|
+
return 0;
|
|
2883
|
+
}
|
|
2884
|
+
async function cmdDaemon(arg, any) {
|
|
2885
|
+
switch (arg) {
|
|
2886
|
+
case "start": {
|
|
2887
|
+
if (loadProfile() === null) {
|
|
2888
|
+
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
2889
|
+
return 1;
|
|
2890
|
+
}
|
|
2891
|
+
const r = startDaemon({ any, version: VERSION });
|
|
2892
|
+
if (!r.started) {
|
|
2893
|
+
process2.stdout.write(` ${r.reason}
|
|
2894
|
+
`);
|
|
2895
|
+
return 0;
|
|
2896
|
+
}
|
|
2897
|
+
process2.stdout.write(` daemon started (pid ${r.pid}) \u2014 notify-only: alerts on NEW matches, never opens chat/video
|
|
2898
|
+
`);
|
|
2899
|
+
process2.stdout.write(` logs: ~/.vibedating/daemon.log \xB7 stop: vibedate daemon stop
|
|
2900
|
+
`);
|
|
2901
|
+
return 0;
|
|
2902
|
+
}
|
|
2903
|
+
case "stop": {
|
|
2904
|
+
const r = await stopDaemon();
|
|
2905
|
+
process2.stdout.write(
|
|
2906
|
+
r.stopped ? ` daemon stopped (pid ${r.pid})
|
|
2907
|
+
` : ` ${r.reason}
|
|
2908
|
+
`
|
|
2909
|
+
);
|
|
2910
|
+
return 0;
|
|
2911
|
+
}
|
|
2912
|
+
case "status": {
|
|
2913
|
+
const s = daemonStatus();
|
|
2914
|
+
if (s.running && s.state !== null) {
|
|
2915
|
+
process2.stdout.write(
|
|
2916
|
+
` daemon running (pid ${s.state.pid}) since ${s.state.startedAt}${s.state.any ? " \xB7 --any" : ""}
|
|
2917
|
+
`
|
|
2918
|
+
);
|
|
2919
|
+
} else {
|
|
2920
|
+
process2.stdout.write(" daemon not running\n");
|
|
2921
|
+
}
|
|
2922
|
+
return 0;
|
|
2923
|
+
}
|
|
2924
|
+
case "run":
|
|
2925
|
+
return cmdDaemonRun(any);
|
|
2926
|
+
case "install": {
|
|
2927
|
+
if (loadProfile() === null) {
|
|
2928
|
+
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
2929
|
+
return 1;
|
|
2930
|
+
}
|
|
2931
|
+
const r = installDaemonService({ any });
|
|
2932
|
+
process2.stdout.write(` ${r.detail}
|
|
2933
|
+
`);
|
|
2934
|
+
if (r.servicePath !== null) process2.stdout.write(` service: ${r.servicePath}
|
|
2935
|
+
`);
|
|
2936
|
+
if (r.installed) process2.stdout.write(" undo anytime: vibedate daemon uninstall\n");
|
|
2937
|
+
return r.installed ? 0 : 1;
|
|
2938
|
+
}
|
|
2939
|
+
case "uninstall": {
|
|
2940
|
+
const r = uninstallDaemonService({});
|
|
2941
|
+
process2.stdout.write(` ${r.detail}
|
|
2942
|
+
`);
|
|
2943
|
+
return 0;
|
|
2944
|
+
}
|
|
2945
|
+
default:
|
|
2946
|
+
process2.stderr.write("usage: vibedating daemon [start|stop|status|install|uninstall] [--any]\n");
|
|
2947
|
+
return 1;
|
|
2948
|
+
}
|
|
2949
|
+
}
|
|
2354
2950
|
var HELP = `vibedating ${VERSION} \u2014 dating by tokens (local-first)
|
|
2355
2951
|
|
|
2356
2952
|
Usage:
|
|
2357
2953
|
vibedating connect Read your usage, compute + print your league
|
|
2358
2954
|
vibedating matches [--live] List candidates in your league (live peers if any)
|
|
2359
2955
|
vibedating discover [--live] [--any] Find live peers over the DHT (your league + adjacent; --any = everyone)
|
|
2360
|
-
vibedating live [--dating] [--any] [--to @handle] Live chat (your league
|
|
2956
|
+
vibedating live [--dating] [--any] [--to @handle] [--keep-alive] [--via-relay] Live chat (your league
|
|
2957
|
+
+ adjacent; --any = everyone; /next or --dating pick;
|
|
2958
|
+
--to targets one peer; --keep-alive survives stdin EOF \u2014
|
|
2959
|
+
automatic when stdin is not a TTY; --via-relay routes e2e chat
|
|
2960
|
+
through public Nostr relays when direct hole-punching fails)
|
|
2361
2961
|
vibedating find <@handle> [--any] Search the DHT for one specific handle (\u2605 highlights a match)
|
|
2362
2962
|
vibedating handle [@name] Print or set your handle (persisted; a leading '@' is optional)
|
|
2363
2963
|
vibedating block <@handle> Block a handle \u2014 their hello is dropped (never recorded/paired)
|
|
2364
2964
|
vibedating unblock <@handle> Remove a handle from the blocklist
|
|
2365
2965
|
vibedating blocklist List blocked handles
|
|
2966
|
+
vibedating daemon [start|stop|status|install|uninstall] [--any]
|
|
2967
|
+
Manage the notify-only background daemon \u2014 alerts on
|
|
2968
|
+
NEW matches, never opens chat/video. install adds a
|
|
2969
|
+
login service (launchd on macOS, systemd on Linux);
|
|
2970
|
+
uninstall removes it.
|
|
2366
2971
|
vibedating open [--port N] [--any] Serve the local web app (default: random port)
|
|
2367
2972
|
+ live video + chat with connected peers
|
|
2368
2973
|
(your league + adjacent; --any = every league)
|
|
@@ -2410,14 +3015,16 @@ async function main(argv) {
|
|
|
2410
3015
|
return cmdUnblock(parsed.arg);
|
|
2411
3016
|
case "blocklist":
|
|
2412
3017
|
return cmdBlocklist();
|
|
3018
|
+
case "daemon":
|
|
3019
|
+
return cmdDaemon(parsed.arg, parsed.any);
|
|
2413
3020
|
case "matches":
|
|
2414
3021
|
return cmdMatches(parsed.live);
|
|
2415
3022
|
case "discover":
|
|
2416
|
-
return cmdDiscover(parsed.live, parsed.any);
|
|
3023
|
+
return cmdDiscover(parsed.live, parsed.any, parsed.viaRelay);
|
|
2417
3024
|
case "open":
|
|
2418
3025
|
return cmdOpen(parsed.port, parsed.any);
|
|
2419
3026
|
case "live":
|
|
2420
|
-
return cmdLive(parsed.dating, parsed.any, parsed.to);
|
|
3027
|
+
return cmdLive(parsed.dating, parsed.any, parsed.to, parsed.keepAlive, parsed.viaRelay);
|
|
2421
3028
|
case "find":
|
|
2422
3029
|
return cmdFind(parsed.arg, parsed.any);
|
|
2423
3030
|
case "mcp":
|
|
@@ -2448,5 +3055,7 @@ if (entryUrl !== void 0) {
|
|
|
2448
3055
|
}
|
|
2449
3056
|
}
|
|
2450
3057
|
export {
|
|
2451
|
-
|
|
3058
|
+
formatAgo,
|
|
3059
|
+
parseArgs,
|
|
3060
|
+
shouldKeepAlive
|
|
2452
3061
|
};
|