vibedate 0.4.1 → 0.5.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-I5U3O4RH.js} +1 -1
- package/dist/{chunk-43IYNWES.js → chunk-KKWP4DLY.js} +16 -1
- package/dist/cli.d.ts +17 -2
- package/dist/cli.js +521 -20
- package/dist/index.js +1 -1
- package/dist/mcp.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,6 +9,12 @@ Part of the **Vibe Suite** — companion tools for agentic coding CLIs. Ships as
|
|
|
9
9
|
**CLI + npm package + MCP server**, plus a local web app. Built on
|
|
10
10
|
[`@pooriaarab/vibe-core`](https://www.npmjs.com/package/@pooriaarab/vibe-core).
|
|
11
11
|
|
|
12
|
+
## Demo
|
|
13
|
+
|
|
14
|
+
[▶ Watch the launch video](branding/launch-video.mp4) — she matched with the man of her dreams. He was coding in a basement.
|
|
15
|
+
|
|
16
|
+
https://github.com/pooriaarab/vibedating/raw/main/branding/launch-video.mp4
|
|
17
|
+
|
|
12
18
|
> **Local-first.** Raw token usage is read and stored on your own machine and
|
|
13
19
|
> **never leaves it.** Only the coarse league *bucket* is ever shared — never the
|
|
14
20
|
> raw number, never per-project breakdowns. Live matching is peer-to-peer over a
|
|
@@ -62,8 +68,15 @@ VIBEDATING_TOKENS=23400000 vibedating connect # also accepts 12M / 1.2B / 500k
|
|
|
62
68
|
|
|
63
69
|
```
|
|
64
70
|
vibedating connect Read usage, compute + print your league
|
|
65
|
-
|
|
71
|
+
(first run auto-assigns a memetic handle — never @you)
|
|
72
|
+
vibedating matches [--live] List candidates in your league (live peers if any,
|
|
73
|
+
with last-message time)
|
|
66
74
|
vibedating discover [--live] Find live same-league peers over the DHT (opt-in)
|
|
75
|
+
vibedating live [--keep-alive] Live text chat (--keep-alive / non-TTY survives stdin EOF)
|
|
76
|
+
vibedating handle [@name] Print or set your handle
|
|
77
|
+
vibedating daemon [start|stop|status|install|uninstall]
|
|
78
|
+
Notify-only background daemon — alerts on NEW matches,
|
|
79
|
+
never opens chat/video; install = run on login (launchd/systemd)
|
|
67
80
|
vibedating open [--port N] Serve the local web app (default: random free port)
|
|
68
81
|
vibedating mcp Run the stdio MCP server (tools: profile, matches)
|
|
69
82
|
vibedating --version
|
|
@@ -656,6 +656,14 @@ function createPeerLink(socket, hello, initialBuffer = "", linkOpts = {}) {
|
|
|
656
656
|
};
|
|
657
657
|
}
|
|
658
658
|
|
|
659
|
+
// src/untrusted.ts
|
|
660
|
+
var MAX_DISPLAY_TEXT_LEN = MAX_TEXT_LEN;
|
|
661
|
+
var UNSAFE_DISPLAY_CHARS = /[\u0000-\u0008\u000B-\u001F\u007F-\u009F\u200E\u200F\u202A-\u202E\u2066-\u2069\uFEFF]/g;
|
|
662
|
+
function sanitizePeerText(text, maxLen = MAX_DISPLAY_TEXT_LEN) {
|
|
663
|
+
const cleaned = text.replace(UNSAFE_DISPLAY_CHARS, "");
|
|
664
|
+
return cleaned.length > maxLen ? cleaned.slice(0, maxLen) : cleaned;
|
|
665
|
+
}
|
|
666
|
+
|
|
659
667
|
// src/p2p.ts
|
|
660
668
|
var TOPIC_PREFIX = "vibedate:";
|
|
661
669
|
function leagueTopic(leagueName) {
|
|
@@ -838,7 +846,9 @@ async function startDiscovery(opts) {
|
|
|
838
846
|
try {
|
|
839
847
|
notify(
|
|
840
848
|
makeEvent("match", hello.harness, process.cwd(), {
|
|
841
|
-
|
|
849
|
+
// AEGIS-lite: the handle is untrusted wire data — sanitized for
|
|
850
|
+
// display (the structured `handle` field below stays verbatim).
|
|
851
|
+
summary: `matched with ${sanitizePeerText(peer.handle)} - LIVE SAME LEAGUE`,
|
|
842
852
|
handle: peer.handle,
|
|
843
853
|
league: peer.league,
|
|
844
854
|
harness: peer.harness
|
|
@@ -1036,12 +1046,16 @@ function matches(myLeague, candidates = CANDIDATES) {
|
|
|
1036
1046
|
|
|
1037
1047
|
export {
|
|
1038
1048
|
parseFrame,
|
|
1049
|
+
defaultStateDir,
|
|
1039
1050
|
connectProfile,
|
|
1040
1051
|
loadProfile,
|
|
1041
1052
|
grantLiveConsent,
|
|
1042
1053
|
canShareLive,
|
|
1054
|
+
DEFAULT_HANDLE,
|
|
1055
|
+
MAX_HANDLE_LEN,
|
|
1043
1056
|
sameHandle,
|
|
1044
1057
|
normalizeHandle,
|
|
1058
|
+
loadHandle,
|
|
1045
1059
|
saveHandle,
|
|
1046
1060
|
resolveHandle,
|
|
1047
1061
|
loadBlocklist,
|
|
@@ -1053,6 +1067,7 @@ export {
|
|
|
1053
1067
|
signHelloClaims,
|
|
1054
1068
|
verifyHelloClaims,
|
|
1055
1069
|
classifyHelloIdentity,
|
|
1070
|
+
sanitizePeerText,
|
|
1056
1071
|
TOPIC_PREFIX,
|
|
1057
1072
|
leagueTopic,
|
|
1058
1073
|
LIVE_NOTICE,
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/** Recognized top-level commands, plus the synthetic help/version. */
|
|
3
|
-
type Command = 'connect' | 'matches' | 'discover' | 'open' | 'live' | 'find' | 'handle' | 'block' | 'unblock' | 'blocklist' | 'mcp' | 'help' | 'version' | null;
|
|
3
|
+
type Command = 'connect' | 'matches' | 'discover' | 'open' | 'live' | 'find' | 'handle' | 'block' | 'unblock' | 'blocklist' | 'daemon' | 'mcp' | 'help' | 'version' | null;
|
|
4
4
|
interface ParsedArgs {
|
|
5
5
|
readonly command: Command;
|
|
6
6
|
/** Port for `open --port`; undefined means "let the OS pick". */
|
|
@@ -15,11 +15,26 @@ interface ParsedArgs {
|
|
|
15
15
|
readonly arg: string | undefined;
|
|
16
16
|
/** `live --to <@handle>`: targeted match — auto-open that specific peer. */
|
|
17
17
|
readonly to: string | undefined;
|
|
18
|
+
/** `live --keep-alive`: stay in the swarm after stdin EOF. Default false. */
|
|
19
|
+
readonly keepAlive: boolean;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* Parse argv (the slice AFTER the program name) into a command + options.
|
|
21
23
|
* Pure: no IO, no process access — trivially unit-testable.
|
|
22
24
|
*/
|
|
23
25
|
declare function parseArgs(argv: readonly string[]): ParsedArgs;
|
|
26
|
+
/**
|
|
27
|
+
* Compact relative time for local lists: an ISO timestamp → "just now" /
|
|
28
|
+
* "5m ago" / "3h ago" / "2d ago". Unparseable input → "unknown". Pure.
|
|
29
|
+
*/
|
|
30
|
+
declare function formatAgo(iso: string, now?: Date): string;
|
|
31
|
+
/**
|
|
32
|
+
* Whether `live` should stay in the swarm after stdin closes. An explicit
|
|
33
|
+
* `--keep-alive` always keeps it; otherwise AUTO-detect: a non-TTY stdin
|
|
34
|
+
* (piped, `</dev/null`, backgrounded) hits EOF immediately, and exiting there
|
|
35
|
+
* would kill an unattended session — so it stays up until SIGINT/SIGTERM.
|
|
36
|
+
* Interactive TTY behavior is unchanged: Ctrl+D / EOF still exits.
|
|
37
|
+
*/
|
|
38
|
+
declare function shouldKeepAlive(flag: boolean, stdinIsTTY: boolean | undefined): boolean;
|
|
24
39
|
|
|
25
|
-
export { type Command, type ParsedArgs, parseArgs };
|
|
40
|
+
export { type Command, type ParsedArgs, formatAgo, parseArgs, shouldKeepAlive };
|
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runMcp
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-I5U3O4RH.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
|
+
defaultStateDir,
|
|
13
16
|
grantLiveConsent,
|
|
14
17
|
isBlocked,
|
|
15
18
|
league,
|
|
@@ -17,6 +20,7 @@ import {
|
|
|
17
20
|
leagueTopic,
|
|
18
21
|
leaguesWithin,
|
|
19
22
|
loadBlocklist,
|
|
23
|
+
loadHandle,
|
|
20
24
|
loadOrCreateIdentity,
|
|
21
25
|
loadPeers,
|
|
22
26
|
loadProfile,
|
|
@@ -27,16 +31,356 @@ import {
|
|
|
27
31
|
removeBlock,
|
|
28
32
|
resolveHandle,
|
|
29
33
|
sameHandle,
|
|
34
|
+
sanitizePeerText,
|
|
30
35
|
saveHandle,
|
|
31
36
|
signHelloClaims,
|
|
32
37
|
startDiscovery
|
|
33
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-KKWP4DLY.js";
|
|
34
39
|
|
|
35
40
|
// src/cli.ts
|
|
36
41
|
import readline from "readline";
|
|
37
42
|
import "url";
|
|
38
43
|
import process2 from "process";
|
|
39
44
|
|
|
45
|
+
// src/handlegen.ts
|
|
46
|
+
import { randomBytes } from "crypto";
|
|
47
|
+
var FIRST = [
|
|
48
|
+
"segfault",
|
|
49
|
+
"yak",
|
|
50
|
+
"vibe",
|
|
51
|
+
"null",
|
|
52
|
+
"async",
|
|
53
|
+
"await",
|
|
54
|
+
"heap",
|
|
55
|
+
"stack",
|
|
56
|
+
"sudo",
|
|
57
|
+
"regex",
|
|
58
|
+
"token",
|
|
59
|
+
"prompt",
|
|
60
|
+
"context",
|
|
61
|
+
"merge",
|
|
62
|
+
"rebase",
|
|
63
|
+
"hotfix",
|
|
64
|
+
"flaky",
|
|
65
|
+
"cursed",
|
|
66
|
+
"based",
|
|
67
|
+
"quantum",
|
|
68
|
+
"turbo",
|
|
69
|
+
"neural",
|
|
70
|
+
"agentic",
|
|
71
|
+
"kernel",
|
|
72
|
+
"docker",
|
|
73
|
+
"kube",
|
|
74
|
+
"lambda",
|
|
75
|
+
"pointer",
|
|
76
|
+
"buffer",
|
|
77
|
+
"packet",
|
|
78
|
+
"syscall",
|
|
79
|
+
"runtime",
|
|
80
|
+
"monad",
|
|
81
|
+
"borrow",
|
|
82
|
+
"cache",
|
|
83
|
+
"deadlock",
|
|
84
|
+
"localhost",
|
|
85
|
+
"darkmode",
|
|
86
|
+
"wasm",
|
|
87
|
+
"diff"
|
|
88
|
+
];
|
|
89
|
+
var SECOND = [
|
|
90
|
+
"sommelier",
|
|
91
|
+
"shaver",
|
|
92
|
+
"goblin",
|
|
93
|
+
"nomad",
|
|
94
|
+
"gremlin",
|
|
95
|
+
"wizard",
|
|
96
|
+
"bard",
|
|
97
|
+
"pirate",
|
|
98
|
+
"ninja",
|
|
99
|
+
"monk",
|
|
100
|
+
"prophet",
|
|
101
|
+
"oracle",
|
|
102
|
+
"smith",
|
|
103
|
+
"farmer",
|
|
104
|
+
"whisperer",
|
|
105
|
+
"tamer",
|
|
106
|
+
"connoisseur",
|
|
107
|
+
"maximalist",
|
|
108
|
+
"enjoyer",
|
|
109
|
+
"dealer",
|
|
110
|
+
"herder",
|
|
111
|
+
"wrangler",
|
|
112
|
+
"juggler",
|
|
113
|
+
"mechanic",
|
|
114
|
+
"surgeon",
|
|
115
|
+
"detective",
|
|
116
|
+
"librarian",
|
|
117
|
+
"alchemist",
|
|
118
|
+
"overlord",
|
|
119
|
+
"apprentice",
|
|
120
|
+
"sensei",
|
|
121
|
+
"chef",
|
|
122
|
+
"dj",
|
|
123
|
+
"ranger",
|
|
124
|
+
"paladin",
|
|
125
|
+
"barbarian",
|
|
126
|
+
"summoner",
|
|
127
|
+
"cartographer",
|
|
128
|
+
"archivist",
|
|
129
|
+
"plumber"
|
|
130
|
+
];
|
|
131
|
+
var SUFFIX = [
|
|
132
|
+
"prime",
|
|
133
|
+
"9000",
|
|
134
|
+
"3000",
|
|
135
|
+
"max",
|
|
136
|
+
"ultra",
|
|
137
|
+
"xl",
|
|
138
|
+
"mk2",
|
|
139
|
+
"777",
|
|
140
|
+
"404",
|
|
141
|
+
"1337",
|
|
142
|
+
"2077",
|
|
143
|
+
"xd"
|
|
144
|
+
];
|
|
145
|
+
function cryptoRand() {
|
|
146
|
+
return randomBytes(4).readUInt32BE(0) / 2 ** 32;
|
|
147
|
+
}
|
|
148
|
+
function pick(list, rand) {
|
|
149
|
+
return list[Math.min(list.length - 1, Math.floor(rand() * list.length))];
|
|
150
|
+
}
|
|
151
|
+
function generateHandle(rand = cryptoRand) {
|
|
152
|
+
const first = pick(FIRST, rand);
|
|
153
|
+
const second = pick(SECOND, rand);
|
|
154
|
+
let body = `${first}_${second}`;
|
|
155
|
+
if (rand() < 0.5) {
|
|
156
|
+
const withSuffix = `${body}_${pick(SUFFIX, rand)}`;
|
|
157
|
+
if (withSuffix.length + 1 <= MAX_HANDLE_LEN) body = withSuffix;
|
|
158
|
+
}
|
|
159
|
+
const canonical = normalizeHandle(body);
|
|
160
|
+
if (canonical === null) throw new Error(`handle generator produced an invalid handle: ${body}`);
|
|
161
|
+
return canonical;
|
|
162
|
+
}
|
|
163
|
+
function ensureHandle(dir = defaultStateDir()) {
|
|
164
|
+
const env = process.env["VIBEDATING_HANDLE"];
|
|
165
|
+
if (env !== void 0 && env.trim() !== "") {
|
|
166
|
+
const canonical = normalizeHandle(env);
|
|
167
|
+
if (canonical !== null) return { handle: canonical, generated: false };
|
|
168
|
+
}
|
|
169
|
+
const persisted = loadHandle(dir);
|
|
170
|
+
if (persisted !== DEFAULT_HANDLE) return { handle: persisted, generated: false };
|
|
171
|
+
const generated = generateHandle();
|
|
172
|
+
return { handle: saveHandle(generated, dir), generated: true };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/daemon.ts
|
|
176
|
+
import { spawn, spawnSync } from "child_process";
|
|
177
|
+
import { mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
178
|
+
import os from "os";
|
|
179
|
+
import path from "path";
|
|
180
|
+
function daemonStatePath(dir) {
|
|
181
|
+
return path.join(dir, "daemon.json");
|
|
182
|
+
}
|
|
183
|
+
function daemonLogPath(dir) {
|
|
184
|
+
return path.join(dir, "daemon.log");
|
|
185
|
+
}
|
|
186
|
+
function readDaemonState(dir = defaultStateDir()) {
|
|
187
|
+
try {
|
|
188
|
+
const raw = readFileSync(daemonStatePath(dir), "utf8");
|
|
189
|
+
const data = JSON.parse(raw);
|
|
190
|
+
if (typeof data["pid"] !== "number" || !Number.isInteger(data["pid"]) || data["pid"] <= 0 || typeof data["startedAt"] !== "string" || typeof data["any"] !== "boolean" || typeof data["version"] !== "string") {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return { pid: data["pid"], startedAt: data["startedAt"], any: data["any"], version: data["version"] };
|
|
194
|
+
} catch {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
function writeDaemonState(dir, state) {
|
|
199
|
+
mkdirSync(dir, { recursive: true });
|
|
200
|
+
writeFileSync(daemonStatePath(dir), JSON.stringify(state, null, 2) + "\n", "utf8");
|
|
201
|
+
}
|
|
202
|
+
function removeDaemonState(dir = defaultStateDir()) {
|
|
203
|
+
try {
|
|
204
|
+
rmSync(daemonStatePath(dir), { force: true });
|
|
205
|
+
} catch {
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function isPidAlive(pid, kill = process.kill) {
|
|
209
|
+
try {
|
|
210
|
+
kill(pid, 0);
|
|
211
|
+
return true;
|
|
212
|
+
} catch (err) {
|
|
213
|
+
return err.code === "EPERM";
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function daemonStatus(dir = defaultStateDir(), alive = isPidAlive) {
|
|
217
|
+
const state = readDaemonState(dir);
|
|
218
|
+
if (state === null) return { running: false, state: null };
|
|
219
|
+
if (alive(state.pid)) return { running: true, state };
|
|
220
|
+
removeDaemonState(dir);
|
|
221
|
+
return { running: false, state: null };
|
|
222
|
+
}
|
|
223
|
+
function defaultSpawn(execPath, scriptPath, args, logPath) {
|
|
224
|
+
mkdirSync(path.dirname(logPath), { recursive: true });
|
|
225
|
+
const fd = openSync(logPath, "a");
|
|
226
|
+
const child = spawn(execPath, [scriptPath, ...args], {
|
|
227
|
+
detached: true,
|
|
228
|
+
stdio: ["ignore", fd, fd]
|
|
229
|
+
});
|
|
230
|
+
child.unref();
|
|
231
|
+
if (child.pid === void 0) throw new Error("could not spawn daemon child process");
|
|
232
|
+
return child.pid;
|
|
233
|
+
}
|
|
234
|
+
function startDaemon(opts) {
|
|
235
|
+
const dir = opts.dir ?? defaultStateDir();
|
|
236
|
+
const status = daemonStatus(dir, opts.alive);
|
|
237
|
+
if (status.running && status.state !== null) {
|
|
238
|
+
return { started: false, reason: `already running (pid ${status.state.pid})` };
|
|
239
|
+
}
|
|
240
|
+
const execPath = opts.execPath ?? process.execPath;
|
|
241
|
+
const scriptPath = opts.scriptPath ?? process.argv[1];
|
|
242
|
+
if (scriptPath === void 0) return { started: false, reason: "cannot locate the CLI entry point" };
|
|
243
|
+
const args = ["daemon", "run", ...opts.any ? ["--any"] : []];
|
|
244
|
+
const spawnProcess = opts.spawnProcess ?? ((a, logPath) => defaultSpawn(execPath, scriptPath, [...a], logPath));
|
|
245
|
+
const pid = spawnProcess(args, daemonLogPath(dir));
|
|
246
|
+
writeDaemonState(dir, { pid, startedAt: (/* @__PURE__ */ new Date()).toISOString(), any: opts.any, version: opts.version });
|
|
247
|
+
return { started: true, pid };
|
|
248
|
+
}
|
|
249
|
+
async function stopDaemon(opts = {}) {
|
|
250
|
+
const dir = opts.dir ?? defaultStateDir();
|
|
251
|
+
const kill = opts.kill ?? process.kill;
|
|
252
|
+
const alive = opts.alive ?? ((pid) => isPidAlive(pid, kill));
|
|
253
|
+
const state = readDaemonState(dir);
|
|
254
|
+
if (state === null) return { stopped: false, reason: "not running" };
|
|
255
|
+
if (!alive(state.pid)) {
|
|
256
|
+
removeDaemonState(dir);
|
|
257
|
+
return { stopped: false, reason: "not running (cleaned stale pidfile)" };
|
|
258
|
+
}
|
|
259
|
+
try {
|
|
260
|
+
kill(state.pid, "SIGTERM");
|
|
261
|
+
} catch {
|
|
262
|
+
}
|
|
263
|
+
const deadline = Date.now() + (opts.waitMs ?? 2e3);
|
|
264
|
+
while (Date.now() < deadline && alive(state.pid)) {
|
|
265
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
266
|
+
}
|
|
267
|
+
removeDaemonState(dir);
|
|
268
|
+
return { stopped: true, pid: state.pid };
|
|
269
|
+
}
|
|
270
|
+
var DAEMON_SERVICE_LABEL = "ai.vibedating.daemon";
|
|
271
|
+
var SYSTEMD_UNIT_NAME = "vibedating.service";
|
|
272
|
+
function daemonServicePath(platform = process.platform, homeDir = os.homedir()) {
|
|
273
|
+
if (platform === "darwin") {
|
|
274
|
+
return path.join(homeDir, "Library", "LaunchAgents", `${DAEMON_SERVICE_LABEL}.plist`);
|
|
275
|
+
}
|
|
276
|
+
if (platform === "linux") {
|
|
277
|
+
return path.join(homeDir, ".config", "systemd", "user", SYSTEMD_UNIT_NAME);
|
|
278
|
+
}
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
281
|
+
function serviceArgv(execPath, scriptPath, any) {
|
|
282
|
+
return [execPath, scriptPath, "daemon", "run", ...any ? ["--any"] : []];
|
|
283
|
+
}
|
|
284
|
+
function renderLaunchdPlist(opts) {
|
|
285
|
+
const args = serviceArgv(opts.execPath, opts.scriptPath, opts.any).map((a) => ` <string>${a}</string>`).join("\n");
|
|
286
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
287
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
288
|
+
<plist version="1.0">
|
|
289
|
+
<dict>
|
|
290
|
+
<key>Label</key>
|
|
291
|
+
<string>${DAEMON_SERVICE_LABEL}</string>
|
|
292
|
+
<key>ProgramArguments</key>
|
|
293
|
+
<array>
|
|
294
|
+
${args}
|
|
295
|
+
</array>
|
|
296
|
+
<key>RunAtLoad</key>
|
|
297
|
+
<true/>
|
|
298
|
+
<key>KeepAlive</key>
|
|
299
|
+
<false/>
|
|
300
|
+
<key>StandardOutPath</key>
|
|
301
|
+
<string>${opts.logPath}</string>
|
|
302
|
+
<key>StandardErrorPath</key>
|
|
303
|
+
<string>${opts.logPath}</string>
|
|
304
|
+
</dict>
|
|
305
|
+
</plist>
|
|
306
|
+
`;
|
|
307
|
+
}
|
|
308
|
+
function renderSystemdUnit(opts) {
|
|
309
|
+
const execStart = serviceArgv(opts.execPath, opts.scriptPath, opts.any).join(" ");
|
|
310
|
+
return `[Unit]
|
|
311
|
+
Description=vibedating notify-only daemon (alerts on new matches; never opens chat/video)
|
|
312
|
+
|
|
313
|
+
[Service]
|
|
314
|
+
ExecStart=${execStart}
|
|
315
|
+
Restart=on-failure
|
|
316
|
+
RestartSec=10
|
|
317
|
+
|
|
318
|
+
[Install]
|
|
319
|
+
WantedBy=default.target
|
|
320
|
+
`;
|
|
321
|
+
}
|
|
322
|
+
var defaultRun = (cmd, args) => spawnSync(cmd, args, { stdio: "inherit" }).status === 0;
|
|
323
|
+
function installDaemonService(opts) {
|
|
324
|
+
const platform = opts.platform ?? process.platform;
|
|
325
|
+
const homeDir = opts.homeDir ?? os.homedir();
|
|
326
|
+
const run = opts.run ?? defaultRun;
|
|
327
|
+
const servicePath = daemonServicePath(platform, homeDir);
|
|
328
|
+
if (servicePath === null) {
|
|
329
|
+
return {
|
|
330
|
+
installed: false,
|
|
331
|
+
servicePath: null,
|
|
332
|
+
detail: `unsupported platform (${platform}) \u2014 run \`vibedate daemon start\` manually instead`
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const execPath = opts.execPath ?? process.execPath;
|
|
336
|
+
const scriptPath = opts.scriptPath ?? process.argv[1];
|
|
337
|
+
if (scriptPath === void 0) {
|
|
338
|
+
return { installed: false, servicePath, detail: "cannot locate the CLI entry point" };
|
|
339
|
+
}
|
|
340
|
+
mkdirSync(path.dirname(servicePath), { recursive: true });
|
|
341
|
+
if (platform === "darwin") {
|
|
342
|
+
const logPath = daemonLogPath(opts.dir ?? defaultStateDir());
|
|
343
|
+
writeFileSync(servicePath, renderLaunchdPlist({ execPath, scriptPath, any: opts.any, logPath }), "utf8");
|
|
344
|
+
const uid = process.getuid?.() ?? 501;
|
|
345
|
+
run("launchctl", ["bootout", `gui/${uid}`, servicePath]);
|
|
346
|
+
if (!run("launchctl", ["bootstrap", `gui/${uid}`, servicePath])) {
|
|
347
|
+
return { installed: false, servicePath, detail: "launchctl bootstrap failed \u2014 service written but not loaded" };
|
|
348
|
+
}
|
|
349
|
+
return { installed: true, servicePath, detail: "launchd agent installed \u2014 the daemon starts on login" };
|
|
350
|
+
}
|
|
351
|
+
writeFileSync(servicePath, renderSystemdUnit({ execPath, scriptPath, any: opts.any }), "utf8");
|
|
352
|
+
run("systemctl", ["--user", "daemon-reload"]);
|
|
353
|
+
if (!run("systemctl", ["--user", "enable", "--now", SYSTEMD_UNIT_NAME])) {
|
|
354
|
+
return { installed: false, servicePath, detail: "systemctl enable failed \u2014 unit written but not started" };
|
|
355
|
+
}
|
|
356
|
+
return { installed: true, servicePath, detail: "systemd --user service installed \u2014 the daemon starts on login" };
|
|
357
|
+
}
|
|
358
|
+
function uninstallDaemonService(opts) {
|
|
359
|
+
const platform = opts.platform ?? process.platform;
|
|
360
|
+
const homeDir = opts.homeDir ?? os.homedir();
|
|
361
|
+
const run = opts.run ?? defaultRun;
|
|
362
|
+
const servicePath = daemonServicePath(platform, homeDir);
|
|
363
|
+
if (servicePath === null) {
|
|
364
|
+
return {
|
|
365
|
+
installed: false,
|
|
366
|
+
servicePath: null,
|
|
367
|
+
detail: `unsupported platform (${platform})`
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (platform === "darwin") {
|
|
371
|
+
const uid = process.getuid?.() ?? 501;
|
|
372
|
+
run("launchctl", ["bootout", `gui/${uid}`, servicePath]);
|
|
373
|
+
} else {
|
|
374
|
+
run("systemctl", ["--user", "disable", "--now", SYSTEMD_UNIT_NAME]);
|
|
375
|
+
run("systemctl", ["--user", "daemon-reload"]);
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
rmSync(servicePath, { force: true });
|
|
379
|
+
} catch {
|
|
380
|
+
}
|
|
381
|
+
return { installed: false, servicePath, detail: "login service removed \u2014 the daemon no longer starts on login" };
|
|
382
|
+
}
|
|
383
|
+
|
|
40
384
|
// src/pairing.ts
|
|
41
385
|
function createPairing() {
|
|
42
386
|
const queue = [];
|
|
@@ -1499,7 +1843,7 @@ function createLiveBridge() {
|
|
|
1499
1843
|
link.onMessage((m) => {
|
|
1500
1844
|
const mb = boxes.get(handle2);
|
|
1501
1845
|
if (!mb) return;
|
|
1502
|
-
mb.messages.push(m);
|
|
1846
|
+
mb.messages.push({ ...m, text: sanitizePeerText(m.text) });
|
|
1503
1847
|
if (mb.messages.length > MAX_QUEUED_MESSAGES) {
|
|
1504
1848
|
mb.messages.splice(0, mb.messages.length - MAX_QUEUED_MESSAGES);
|
|
1505
1849
|
}
|
|
@@ -1768,7 +2112,7 @@ async function handle(req, res, opts) {
|
|
|
1768
2112
|
}
|
|
1769
2113
|
|
|
1770
2114
|
// src/cli.ts
|
|
1771
|
-
var VERSION = "0.
|
|
2115
|
+
var VERSION = "0.5.0";
|
|
1772
2116
|
function parsePort(raw) {
|
|
1773
2117
|
const n = Number(raw);
|
|
1774
2118
|
if (!Number.isInteger(n) || n < 1 || n > 65535) return void 0;
|
|
@@ -1782,7 +2126,8 @@ function parseArgs(argv) {
|
|
|
1782
2126
|
dating: false,
|
|
1783
2127
|
any: false,
|
|
1784
2128
|
arg: void 0,
|
|
1785
|
-
to: void 0
|
|
2129
|
+
to: void 0,
|
|
2130
|
+
keepAlive: false
|
|
1786
2131
|
};
|
|
1787
2132
|
for (let i = 0; i < argv.length; i++) {
|
|
1788
2133
|
const a = argv[i];
|
|
@@ -1794,7 +2139,8 @@ function parseArgs(argv) {
|
|
|
1794
2139
|
dating: false,
|
|
1795
2140
|
any: false,
|
|
1796
2141
|
arg: void 0,
|
|
1797
|
-
to: void 0
|
|
2142
|
+
to: void 0,
|
|
2143
|
+
keepAlive: false
|
|
1798
2144
|
};
|
|
1799
2145
|
}
|
|
1800
2146
|
if (a === "--help" || a === "-h") {
|
|
@@ -1805,13 +2151,18 @@ function parseArgs(argv) {
|
|
|
1805
2151
|
dating: false,
|
|
1806
2152
|
any: false,
|
|
1807
2153
|
arg: void 0,
|
|
1808
|
-
to: void 0
|
|
2154
|
+
to: void 0,
|
|
2155
|
+
keepAlive: false
|
|
1809
2156
|
};
|
|
1810
2157
|
}
|
|
1811
2158
|
if (a === "--live") {
|
|
1812
2159
|
out = { ...out, live: true };
|
|
1813
2160
|
continue;
|
|
1814
2161
|
}
|
|
2162
|
+
if (a === "--keep-alive") {
|
|
2163
|
+
out = { ...out, keepAlive: true };
|
|
2164
|
+
continue;
|
|
2165
|
+
}
|
|
1815
2166
|
if (a === "--any") {
|
|
1816
2167
|
out = { ...out, any: true };
|
|
1817
2168
|
continue;
|
|
@@ -1847,7 +2198,7 @@ function parseArgs(argv) {
|
|
|
1847
2198
|
continue;
|
|
1848
2199
|
}
|
|
1849
2200
|
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;
|
|
2201
|
+
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
2202
|
if (known !== null && out.command === null) {
|
|
1852
2203
|
out = { ...out, command: known };
|
|
1853
2204
|
} else if (out.arg === void 0) {
|
|
@@ -1867,6 +2218,17 @@ function formatTokens(n) {
|
|
|
1867
2218
|
if (abs >= 1e3) return `${trim(n / 1e3)}k`;
|
|
1868
2219
|
return String(n);
|
|
1869
2220
|
}
|
|
2221
|
+
function formatAgo(iso, now = /* @__PURE__ */ new Date()) {
|
|
2222
|
+
const t = Date.parse(iso);
|
|
2223
|
+
if (Number.isNaN(t)) return "unknown";
|
|
2224
|
+
const s = Math.max(0, Math.floor((now.getTime() - t) / 1e3));
|
|
2225
|
+
if (s < 60) return "just now";
|
|
2226
|
+
const m = Math.floor(s / 60);
|
|
2227
|
+
if (m < 60) return `${m}m ago`;
|
|
2228
|
+
const h = Math.floor(m / 60);
|
|
2229
|
+
if (h < 24) return `${h}h ago`;
|
|
2230
|
+
return `${Math.floor(h / 24)}d ago`;
|
|
2231
|
+
}
|
|
1870
2232
|
function verificationText(snapshot) {
|
|
1871
2233
|
if (snapshot.source === "real") {
|
|
1872
2234
|
return `verified: real usage \u2014 ${formatTokens(snapshot.totalTokens)} tokens from ${snapshot.harness} logs`;
|
|
@@ -1913,7 +2275,9 @@ function idMark(peer) {
|
|
|
1913
2275
|
var MARKS_LEGEND = "marks: \u2713 usage verified (real local logs) \xB7 ~ unverified (self-report/demo/legacy) \xB7 \u{1F511} identity-verified (signed hello)";
|
|
1914
2276
|
async function cmdConnect() {
|
|
1915
2277
|
const harness = process2.env["VIBEDATING_HARNESS"] ?? "claude-code";
|
|
1916
|
-
const
|
|
2278
|
+
const ensured = ensureHandle();
|
|
2279
|
+
const handle2 = ensured.handle;
|
|
2280
|
+
const firstConnect = loadProfile() === null;
|
|
1917
2281
|
const snapshot = await readUsage(harness);
|
|
1918
2282
|
const profile = connectProfile(snapshot, handle2);
|
|
1919
2283
|
const identity = loadOrCreateIdentity();
|
|
@@ -1923,12 +2287,20 @@ async function cmdConnect() {
|
|
|
1923
2287
|
`);
|
|
1924
2288
|
process2.stdout.write(` handle: ${profile.handle} \xB7 harness: ${profile.harness}
|
|
1925
2289
|
`);
|
|
2290
|
+
if (ensured.generated) {
|
|
2291
|
+
process2.stdout.write(` assigned handle: ${profile.handle} \u2014 change it with: vibedate handle @name
|
|
2292
|
+
`);
|
|
2293
|
+
}
|
|
1926
2294
|
process2.stdout.write(` verification: ${verificationText(snapshot)}
|
|
1927
2295
|
`);
|
|
1928
2296
|
process2.stdout.write(` identity: ed25519 ${identity.publicKeyHex.slice(0, 12)}\u2026 \u2014 signs your hello (\u{1F511})
|
|
1929
2297
|
`);
|
|
1930
2298
|
process2.stdout.write("\n");
|
|
1931
|
-
process2.stdout.write(" \u2022 raw usage stays local \xB7 only league shared\n
|
|
2299
|
+
process2.stdout.write(" \u2022 raw usage stays local \xB7 only league shared\n");
|
|
2300
|
+
if (firstConnect) {
|
|
2301
|
+
process2.stdout.write(" \u2022 optional: get notified of new matches on login \u2014 `vibedate daemon install` (undo: daemon uninstall)\n");
|
|
2302
|
+
}
|
|
2303
|
+
process2.stdout.write("\n");
|
|
1932
2304
|
return 0;
|
|
1933
2305
|
}
|
|
1934
2306
|
async function cmdHandle(arg) {
|
|
@@ -1981,7 +2353,8 @@ async function cmdMatches(live) {
|
|
|
1981
2353
|
|
|
1982
2354
|
`);
|
|
1983
2355
|
for (const p of livePeers) {
|
|
1984
|
-
|
|
2356
|
+
const lastMsg = p.lastMessageAt !== void 0 ? ` \xB7 last msg ${formatAgo(p.lastMessageAt)}` : "";
|
|
2357
|
+
process2.stdout.write(` ${p.handle.padEnd(28)} ${p.league} \xB7 ${p.harness} ${usageMark(p)}${idMark(p)}${lastMsg}
|
|
1985
2358
|
`);
|
|
1986
2359
|
}
|
|
1987
2360
|
process2.stdout.write("\n");
|
|
@@ -2113,7 +2486,10 @@ async function cmdOpen(port, any) {
|
|
|
2113
2486
|
await new Promise((resolve) => started.server.close(() => resolve()));
|
|
2114
2487
|
return 0;
|
|
2115
2488
|
}
|
|
2116
|
-
|
|
2489
|
+
function shouldKeepAlive(flag, stdinIsTTY) {
|
|
2490
|
+
return flag || stdinIsTTY !== true;
|
|
2491
|
+
}
|
|
2492
|
+
async function cmdLive(dating, any, to, keepAlive) {
|
|
2117
2493
|
const profile = loadProfile();
|
|
2118
2494
|
if (!profile) {
|
|
2119
2495
|
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
@@ -2148,11 +2524,11 @@ async function cmdLive(dating, any, to) {
|
|
|
2148
2524
|
}
|
|
2149
2525
|
const { qual } = peerDirection(profile.league, link.hello.league);
|
|
2150
2526
|
process2.stdout.write(
|
|
2151
|
-
` \xB7 matched ${link.hello.handle} (${link.hello.league}${qual} \xB7 ${link.hello.harness}) ${usageMark(link.hello)}${idMark(link.hello)}
|
|
2527
|
+
` \xB7 matched ${sanitizePeerText(link.hello.handle)} (${link.hello.league}${qual} \xB7 ${link.hello.harness}) ${usageMark(link.hello)}${idMark(link.hello)}
|
|
2152
2528
|
`
|
|
2153
2529
|
);
|
|
2154
2530
|
link.onMessage((m) => {
|
|
2155
|
-
process2.stdout.write(` <${link.hello.handle}> ${m.text}
|
|
2531
|
+
process2.stdout.write(` <${sanitizePeerText(link.hello.handle)}> ${sanitizePeerText(m.text)}
|
|
2156
2532
|
`);
|
|
2157
2533
|
});
|
|
2158
2534
|
});
|
|
@@ -2166,14 +2542,14 @@ async function cmdLive(dating, any, to) {
|
|
|
2166
2542
|
if (target !== null && !sameHandle(link.hello.handle, target)) {
|
|
2167
2543
|
const { qual } = peerDirection(profile.league, link.hello.league);
|
|
2168
2544
|
process2.stdout.write(
|
|
2169
|
-
` + ${link.hello.handle} (${link.hello.league}${qual}) ${usageMark(link.hello)}${idMark(link.hello)} \u2014 not your target
|
|
2545
|
+
` + ${sanitizePeerText(link.hello.handle)} (${link.hello.league}${qual}) ${usageMark(link.hello)}${idMark(link.hello)} \u2014 not your target
|
|
2170
2546
|
`
|
|
2171
2547
|
);
|
|
2172
2548
|
link.close();
|
|
2173
2549
|
return;
|
|
2174
2550
|
}
|
|
2175
2551
|
if (target !== null) {
|
|
2176
|
-
process2.stdout.write(` \u2605 found ${link.hello.handle} \u2014 auto-opening
|
|
2552
|
+
process2.stdout.write(` \u2605 found ${sanitizePeerText(link.hello.handle)} \u2014 auto-opening
|
|
2177
2553
|
`);
|
|
2178
2554
|
}
|
|
2179
2555
|
pairing.add(link);
|
|
@@ -2191,9 +2567,13 @@ async function cmdLive(dating, any, to) {
|
|
|
2191
2567
|
};
|
|
2192
2568
|
process2.once("SIGINT", stop);
|
|
2193
2569
|
process2.once("SIGTERM", stop);
|
|
2570
|
+
let quitRequested = false;
|
|
2194
2571
|
for await (const line of rl) {
|
|
2195
2572
|
const text = line.trim();
|
|
2196
|
-
if (text === "/quit")
|
|
2573
|
+
if (text === "/quit") {
|
|
2574
|
+
quitRequested = true;
|
|
2575
|
+
break;
|
|
2576
|
+
}
|
|
2197
2577
|
if (text === "/next") {
|
|
2198
2578
|
pairing.next();
|
|
2199
2579
|
continue;
|
|
@@ -2214,6 +2594,13 @@ async function cmdLive(dating, any, to) {
|
|
|
2214
2594
|
process2.stdout.write(" \xB7 no peer yet \u2014 waiting for a match\u2026\n");
|
|
2215
2595
|
}
|
|
2216
2596
|
}
|
|
2597
|
+
if (!quitRequested && shouldKeepAlive(keepAlive, process2.stdin.isTTY)) {
|
|
2598
|
+
process2.stdout.write(" stdin closed \u2014 staying in the swarm (Ctrl+C / SIGTERM to leave)\n");
|
|
2599
|
+
await new Promise((resolve) => {
|
|
2600
|
+
process2.once("SIGINT", () => resolve());
|
|
2601
|
+
process2.once("SIGTERM", () => resolve());
|
|
2602
|
+
});
|
|
2603
|
+
}
|
|
2217
2604
|
process2.removeListener("SIGINT", stop);
|
|
2218
2605
|
process2.removeListener("SIGTERM", stop);
|
|
2219
2606
|
const cur = pairing.current();
|
|
@@ -2351,18 +2738,128 @@ async function cmdBlocklist() {
|
|
|
2351
2738
|
`);
|
|
2352
2739
|
return 0;
|
|
2353
2740
|
}
|
|
2741
|
+
async function cmdDaemonRun(any) {
|
|
2742
|
+
const profile = loadProfile();
|
|
2743
|
+
if (!profile) {
|
|
2744
|
+
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
2745
|
+
return 1;
|
|
2746
|
+
}
|
|
2747
|
+
if (!canShareLive()) grantLiveConsent();
|
|
2748
|
+
const dir = defaultStateDir();
|
|
2749
|
+
writeDaemonState(dir, { pid: process2.pid, startedAt: (/* @__PURE__ */ new Date()).toISOString(), any, version: VERSION });
|
|
2750
|
+
const hello = buildHello(profile);
|
|
2751
|
+
const { topics, acceptLeague } = discoveryScope(profile.league, any);
|
|
2752
|
+
const session = await startDiscovery({
|
|
2753
|
+
hello,
|
|
2754
|
+
topics,
|
|
2755
|
+
acceptLeague,
|
|
2756
|
+
isBlocked: blockedChecker(),
|
|
2757
|
+
onPeer: (peer, isNew) => {
|
|
2758
|
+
process2.stdout.write(
|
|
2759
|
+
` [${(/* @__PURE__ */ new Date()).toISOString()}] ${isNew ? "NEW match" : "peer seen"}: ${sanitizePeerText(peer.handle)} (${peer.league} \xB7 ${peer.harness})
|
|
2760
|
+
`
|
|
2761
|
+
);
|
|
2762
|
+
}
|
|
2763
|
+
});
|
|
2764
|
+
process2.stdout.write(
|
|
2765
|
+
` vibedating daemon running (pid ${process2.pid}) \u2014 notify-only${any ? " \xB7 --any" : ""}
|
|
2766
|
+
`
|
|
2767
|
+
);
|
|
2768
|
+
await new Promise((resolve) => {
|
|
2769
|
+
process2.once("SIGINT", () => resolve());
|
|
2770
|
+
process2.once("SIGTERM", () => resolve());
|
|
2771
|
+
});
|
|
2772
|
+
await session.close();
|
|
2773
|
+
removeDaemonState(dir);
|
|
2774
|
+
process2.stdout.write(" daemon stopped\n");
|
|
2775
|
+
return 0;
|
|
2776
|
+
}
|
|
2777
|
+
async function cmdDaemon(arg, any) {
|
|
2778
|
+
switch (arg) {
|
|
2779
|
+
case "start": {
|
|
2780
|
+
if (loadProfile() === null) {
|
|
2781
|
+
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
2782
|
+
return 1;
|
|
2783
|
+
}
|
|
2784
|
+
const r = startDaemon({ any, version: VERSION });
|
|
2785
|
+
if (!r.started) {
|
|
2786
|
+
process2.stdout.write(` ${r.reason}
|
|
2787
|
+
`);
|
|
2788
|
+
return 0;
|
|
2789
|
+
}
|
|
2790
|
+
process2.stdout.write(` daemon started (pid ${r.pid}) \u2014 notify-only: alerts on NEW matches, never opens chat/video
|
|
2791
|
+
`);
|
|
2792
|
+
process2.stdout.write(` logs: ~/.vibedating/daemon.log \xB7 stop: vibedate daemon stop
|
|
2793
|
+
`);
|
|
2794
|
+
return 0;
|
|
2795
|
+
}
|
|
2796
|
+
case "stop": {
|
|
2797
|
+
const r = await stopDaemon();
|
|
2798
|
+
process2.stdout.write(
|
|
2799
|
+
r.stopped ? ` daemon stopped (pid ${r.pid})
|
|
2800
|
+
` : ` ${r.reason}
|
|
2801
|
+
`
|
|
2802
|
+
);
|
|
2803
|
+
return 0;
|
|
2804
|
+
}
|
|
2805
|
+
case "status": {
|
|
2806
|
+
const s = daemonStatus();
|
|
2807
|
+
if (s.running && s.state !== null) {
|
|
2808
|
+
process2.stdout.write(
|
|
2809
|
+
` daemon running (pid ${s.state.pid}) since ${s.state.startedAt}${s.state.any ? " \xB7 --any" : ""}
|
|
2810
|
+
`
|
|
2811
|
+
);
|
|
2812
|
+
} else {
|
|
2813
|
+
process2.stdout.write(" daemon not running\n");
|
|
2814
|
+
}
|
|
2815
|
+
return 0;
|
|
2816
|
+
}
|
|
2817
|
+
case "run":
|
|
2818
|
+
return cmdDaemonRun(any);
|
|
2819
|
+
case "install": {
|
|
2820
|
+
if (loadProfile() === null) {
|
|
2821
|
+
process2.stderr.write("Not connected yet. Run `vibedating connect` first.\n");
|
|
2822
|
+
return 1;
|
|
2823
|
+
}
|
|
2824
|
+
const r = installDaemonService({ any });
|
|
2825
|
+
process2.stdout.write(` ${r.detail}
|
|
2826
|
+
`);
|
|
2827
|
+
if (r.servicePath !== null) process2.stdout.write(` service: ${r.servicePath}
|
|
2828
|
+
`);
|
|
2829
|
+
if (r.installed) process2.stdout.write(" undo anytime: vibedate daemon uninstall\n");
|
|
2830
|
+
return r.installed ? 0 : 1;
|
|
2831
|
+
}
|
|
2832
|
+
case "uninstall": {
|
|
2833
|
+
const r = uninstallDaemonService({});
|
|
2834
|
+
process2.stdout.write(` ${r.detail}
|
|
2835
|
+
`);
|
|
2836
|
+
return 0;
|
|
2837
|
+
}
|
|
2838
|
+
default:
|
|
2839
|
+
process2.stderr.write("usage: vibedating daemon [start|stop|status|install|uninstall] [--any]\n");
|
|
2840
|
+
return 1;
|
|
2841
|
+
}
|
|
2842
|
+
}
|
|
2354
2843
|
var HELP = `vibedating ${VERSION} \u2014 dating by tokens (local-first)
|
|
2355
2844
|
|
|
2356
2845
|
Usage:
|
|
2357
2846
|
vibedating connect Read your usage, compute + print your league
|
|
2358
2847
|
vibedating matches [--live] List candidates in your league (live peers if any)
|
|
2359
2848
|
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
|
|
2849
|
+
vibedating live [--dating] [--any] [--to @handle] [--keep-alive] Live chat (your league
|
|
2850
|
+
+ adjacent; --any = everyone; /next or --dating pick;
|
|
2851
|
+
--to targets one peer; --keep-alive survives stdin EOF \u2014
|
|
2852
|
+
automatic when stdin is not a TTY)
|
|
2361
2853
|
vibedating find <@handle> [--any] Search the DHT for one specific handle (\u2605 highlights a match)
|
|
2362
2854
|
vibedating handle [@name] Print or set your handle (persisted; a leading '@' is optional)
|
|
2363
2855
|
vibedating block <@handle> Block a handle \u2014 their hello is dropped (never recorded/paired)
|
|
2364
2856
|
vibedating unblock <@handle> Remove a handle from the blocklist
|
|
2365
2857
|
vibedating blocklist List blocked handles
|
|
2858
|
+
vibedating daemon [start|stop|status|install|uninstall] [--any]
|
|
2859
|
+
Manage the notify-only background daemon \u2014 alerts on
|
|
2860
|
+
NEW matches, never opens chat/video. install adds a
|
|
2861
|
+
login service (launchd on macOS, systemd on Linux);
|
|
2862
|
+
uninstall removes it.
|
|
2366
2863
|
vibedating open [--port N] [--any] Serve the local web app (default: random port)
|
|
2367
2864
|
+ live video + chat with connected peers
|
|
2368
2865
|
(your league + adjacent; --any = every league)
|
|
@@ -2410,6 +2907,8 @@ async function main(argv) {
|
|
|
2410
2907
|
return cmdUnblock(parsed.arg);
|
|
2411
2908
|
case "blocklist":
|
|
2412
2909
|
return cmdBlocklist();
|
|
2910
|
+
case "daemon":
|
|
2911
|
+
return cmdDaemon(parsed.arg, parsed.any);
|
|
2413
2912
|
case "matches":
|
|
2414
2913
|
return cmdMatches(parsed.live);
|
|
2415
2914
|
case "discover":
|
|
@@ -2417,7 +2916,7 @@ async function main(argv) {
|
|
|
2417
2916
|
case "open":
|
|
2418
2917
|
return cmdOpen(parsed.port, parsed.any);
|
|
2419
2918
|
case "live":
|
|
2420
|
-
return cmdLive(parsed.dating, parsed.any, parsed.to);
|
|
2919
|
+
return cmdLive(parsed.dating, parsed.any, parsed.to, parsed.keepAlive);
|
|
2421
2920
|
case "find":
|
|
2422
2921
|
return cmdFind(parsed.arg, parsed.any);
|
|
2423
2922
|
case "mcp":
|
|
@@ -2448,5 +2947,7 @@ if (entryUrl !== void 0) {
|
|
|
2448
2947
|
}
|
|
2449
2948
|
}
|
|
2450
2949
|
export {
|
|
2451
|
-
|
|
2950
|
+
formatAgo,
|
|
2951
|
+
parseArgs,
|
|
2952
|
+
shouldKeepAlive
|
|
2452
2953
|
};
|
package/dist/index.js
CHANGED
package/dist/mcp.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibedate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Dating by tokens — matched by usage league across agentic CLIs. Raw usage stays local; only your league is shared. CLI + local web app + MCP. Local-first.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|