@poping/yome 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/dist/commands/mesh.d.ts +15 -0
  2. package/dist/commands/mesh.js +282 -0
  3. package/dist/commands/mesh.js.map +1 -0
  4. package/dist/daemon/index.js +7 -2
  5. package/dist/daemon/index.js.map +1 -1
  6. package/dist/daemon/systemd.d.ts +7 -0
  7. package/dist/daemon/systemd.js +87 -0
  8. package/dist/daemon/systemd.js.map +1 -0
  9. package/dist/index.js +26 -2
  10. package/dist/index.js.map +1 -1
  11. package/dist/llm.d.ts +14 -2
  12. package/dist/llm.js +266 -201
  13. package/dist/llm.js.map +1 -1
  14. package/dist/mesh/capabilities.d.ts +6 -0
  15. package/dist/mesh/capabilities.js +66 -0
  16. package/dist/mesh/capabilities.js.map +1 -0
  17. package/dist/mesh/device-id.d.ts +28 -0
  18. package/dist/mesh/device-id.js +105 -0
  19. package/dist/mesh/device-id.js.map +1 -0
  20. package/dist/mesh/device-registrar.d.ts +33 -0
  21. package/dist/mesh/device-registrar.js +131 -0
  22. package/dist/mesh/device-registrar.js.map +1 -0
  23. package/dist/mesh/index.d.ts +28 -0
  24. package/dist/mesh/index.js +71 -0
  25. package/dist/mesh/index.js.map +1 -0
  26. package/dist/mesh/partykit-client.d.ts +43 -0
  27. package/dist/mesh/partykit-client.js +195 -0
  28. package/dist/mesh/partykit-client.js.map +1 -0
  29. package/dist/mesh/rpc-handler.d.ts +28 -0
  30. package/dist/mesh/rpc-handler.js +201 -0
  31. package/dist/mesh/rpc-handler.js.map +1 -0
  32. package/dist/mesh/thread-stream.d.ts +109 -0
  33. package/dist/mesh/thread-stream.js +110 -0
  34. package/dist/mesh/thread-stream.js.map +1 -0
  35. package/dist/mesh/ticket.d.ts +33 -0
  36. package/dist/mesh/ticket.js +127 -0
  37. package/dist/mesh/ticket.js.map +1 -0
  38. package/dist/mesh/types.d.ts +83 -0
  39. package/dist/mesh/types.js +9 -0
  40. package/dist/mesh/types.js.map +1 -0
  41. package/dist/ui/MeshApp.d.ts +9 -0
  42. package/dist/ui/MeshApp.js +226 -0
  43. package/dist/ui/MeshApp.js.map +1 -0
  44. package/dist/withRetry.d.ts +14 -0
  45. package/dist/withRetry.js +106 -0
  46. package/dist/withRetry.js.map +1 -0
  47. package/package.json +4 -2
@@ -0,0 +1,66 @@
1
+ // Linux device capability detection.
2
+ //
3
+ // A "capability" is a domain key the Cloud agent's RpcRouter uses to
4
+ // pick which device to route a given command to (see
5
+ // Server/agent/RpcRouter.findDeviceForCapability). On macOS the list is
6
+ // app-bundle-driven (PowerPoint installed → "ppt" capability); on Linux
7
+ // the list is tool-binary-driven (docker installed → "docker" capability).
8
+ //
9
+ // We only declare capabilities whose handler actually exists in
10
+ // cli/src/mesh/rpc-handler.ts. Adding a probe here without a handler
11
+ // would silently advertise a capability we can't fulfill, which causes
12
+ // the Cloud agent to route a command to us and then time out.
13
+ import { existsSync, accessSync, constants } from 'fs';
14
+ import { execSync } from 'child_process';
15
+ /** Always-on capabilities every Linux box can provide. */
16
+ const ALWAYS_ON = ['bash', 'fs', 'net', 'log', 'chat'];
17
+ /** Each entry: capability key → predicate. Predicate must be cheap + sync. */
18
+ const PROBES = [
19
+ { cap: 'git', probe: () => onPath('git') },
20
+ { cap: 'docker', probe: () => onPath('docker') },
21
+ { cap: 'k8s', probe: () => onPath('kubectl') },
22
+ { cap: 'systemd', probe: () => existsSync('/run/systemd/system') || onPath('systemctl') },
23
+ { cap: 'pkg', probe: () => onPath('apt-get') || onPath('dnf') || onPath('yum') || onPath('apk') || onPath('pacman') },
24
+ { cap: 'svc', probe: () => onPath('systemctl') || onPath('service') },
25
+ ];
26
+ /**
27
+ * Run all probes once and return the union of always-on caps + detected
28
+ * caps. Called at registrar startup and on demand if the user invokes
29
+ * `yome mesh refresh-caps` (todo).
30
+ */
31
+ export function detectCapabilities() {
32
+ const detected = new Set(ALWAYS_ON);
33
+ for (const { cap, probe } of PROBES) {
34
+ try {
35
+ if (probe())
36
+ detected.add(cap);
37
+ }
38
+ catch {
39
+ // Probe failures must never throw; treat as "capability not present".
40
+ }
41
+ }
42
+ return Array.from(detected).sort();
43
+ }
44
+ /** Best-effort PATH lookup that does NOT spawn a child if avoidable. */
45
+ function onPath(bin) {
46
+ // 1. Walk $PATH directly. Cheap, no fork/exec.
47
+ const pathEnv = process.env.PATH ?? '';
48
+ for (const dir of pathEnv.split(':').filter(Boolean)) {
49
+ const candidate = `${dir}/${bin}`;
50
+ try {
51
+ accessSync(candidate, constants.X_OK);
52
+ return true;
53
+ }
54
+ catch { /* try next */ }
55
+ }
56
+ // 2. Final fallback to `command -v` in case PATH was weird (e.g. nss).
57
+ // Wrapped in try because non-zero exit throws synchronously.
58
+ try {
59
+ execSync(`command -v ${bin}`, { stdio: 'ignore' });
60
+ return true;
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/mesh/capabilities.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,EAAE;AACF,qEAAqE;AACrE,qDAAqD;AACrD,wEAAwE;AACxE,wEAAwE;AACxE,2EAA2E;AAC3E,EAAE;AACF,gEAAgE;AAChE,qEAAqE;AACrE,uEAAuE;AACvE,8DAA8D;AAE9D,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC,0DAA0D;AAC1D,MAAM,SAAS,GAAa,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AAEjE,8EAA8E;AAC9E,MAAM,MAAM,GAAiD;IAC3D,EAAE,GAAG,EAAE,KAAK,EAAM,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;IAC9C,EAAE,GAAG,EAAE,QAAQ,EAAG,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE;IACjD,EAAE,GAAG,EAAE,KAAK,EAAM,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE;IAClD,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,qBAAqB,CAAC,IAAI,MAAM,CAAC,WAAW,CAAC,EAAE;IACzF,EAAE,GAAG,EAAE,KAAK,EAAM,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,EAAE;IACzH,EAAE,GAAG,EAAE,KAAK,EAAM,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,EAAE;CAC1E,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,SAAS,CAAC,CAAC;IAC5C,KAAK,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,MAAM,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,IAAI,KAAK,EAAE;gBAAE,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC;YACP,sEAAsE;QACxE,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;AACrC,CAAC;AAED,wEAAwE;AACxE,SAAS,MAAM,CAAC,GAAW;IACzB,+CAA+C;IAC/C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;IACvC,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QACrD,MAAM,SAAS,GAAG,GAAG,GAAG,IAAI,GAAG,EAAE,CAAC;QAClC,IAAI,CAAC;YACH,UAAU,CAAC,SAAS,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;YACtC,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,MAAM,CAAC,CAAC,cAAc,CAAC,CAAC;IAC5B,CAAC;IACD,uEAAuE;IACvE,6DAA6D;IAC7D,IAAI,CAAC;QACH,QAAQ,CAAC,cAAc,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,28 @@
1
+ export interface DeviceState {
2
+ /** UUID v4 generated by this cli on first run. */
3
+ deviceId: string;
4
+ /** ISO-8601 timestamp of first generation. */
5
+ createdAt: string;
6
+ /** Hostname captured at first run, for logs / debugging. */
7
+ initialHostname: string;
8
+ /** Optional human-friendly name. Set via `yome mesh rename <alias>`. */
9
+ alias?: string;
10
+ /** Optional long-form description. */
11
+ description?: string;
12
+ }
13
+ export declare function getDeviceFilePath(): string;
14
+ export declare function loadDeviceState(): DeviceState | null;
15
+ export declare function writeDeviceState(state: DeviceState): void;
16
+ /**
17
+ * Return the persistent deviceId, creating ~/.yome/device.json on first
18
+ * call. Idempotent + safe to call from any subcommand.
19
+ */
20
+ export declare function getOrCreateDeviceId(): string;
21
+ /** Update alias + description; preserves deviceId / createdAt. */
22
+ export declare function updateDeviceMeta(meta: {
23
+ alias?: string;
24
+ description?: string;
25
+ }): DeviceState;
26
+ export declare function getModelString(): string;
27
+ export declare function safeHostname(): string;
28
+ export declare function currentPlatform(): NodeJS.Platform;
@@ -0,0 +1,105 @@
1
+ // Persistent deviceId for this Linux box.
2
+ //
3
+ // Lives at ~/.yome/device.json (0600), same dir as the auth.json that
4
+ // yome login writes. Generated once on first `yome mesh start`, never
5
+ // rotated unless the user explicitly removes the file.
6
+ //
7
+ // Strategy choice (see cli/MIGRATION.md): self-generated UUID, NOT
8
+ // /etc/machine-id hash. Reason: VM templates and Docker images are
9
+ // frequently cloned, and machine-id collisions would silently merge two
10
+ // servers in the mesh device list. Random UUID is collision-safe across
11
+ // clones; if a user really wants to keep a stable id when re-imaging,
12
+ // they can copy ~/.yome/device.json over.
13
+ //
14
+ // Matches macOS/iOS behaviour in Yome/Shared/Sync/DeviceRegistrar.swift
15
+ // (UUID stored in Keychain SecureStore).
16
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync, } from 'fs';
17
+ import { homedir, hostname as osHostname, platform, release } from 'os';
18
+ import { join, dirname } from 'path';
19
+ import { randomUUID } from 'crypto';
20
+ export function getDeviceFilePath() {
21
+ return join(homedir(), '.yome', 'device.json');
22
+ }
23
+ export function loadDeviceState() {
24
+ const f = getDeviceFilePath();
25
+ if (!existsSync(f))
26
+ return null;
27
+ try {
28
+ const obj = JSON.parse(readFileSync(f, 'utf-8'));
29
+ if (typeof obj?.deviceId !== 'string')
30
+ return null;
31
+ return obj;
32
+ }
33
+ catch {
34
+ return null;
35
+ }
36
+ }
37
+ export function writeDeviceState(state) {
38
+ const f = getDeviceFilePath();
39
+ mkdirSync(dirname(f), { recursive: true });
40
+ writeFileSync(f, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
41
+ // mode in writeFileSync only applies on CREATE; chmod again to be sure.
42
+ try {
43
+ chmodSync(f, 0o600);
44
+ }
45
+ catch { /* best effort on Windows */ }
46
+ }
47
+ /**
48
+ * Return the persistent deviceId, creating ~/.yome/device.json on first
49
+ * call. Idempotent + safe to call from any subcommand.
50
+ */
51
+ export function getOrCreateDeviceId() {
52
+ const existing = loadDeviceState();
53
+ if (existing)
54
+ return existing.deviceId;
55
+ const fresh = {
56
+ deviceId: randomUUID(),
57
+ createdAt: new Date().toISOString(),
58
+ initialHostname: safeHostname(),
59
+ };
60
+ writeDeviceState(fresh);
61
+ return fresh.deviceId;
62
+ }
63
+ /** Update alias + description; preserves deviceId / createdAt. */
64
+ export function updateDeviceMeta(meta) {
65
+ const current = loadDeviceState();
66
+ if (!current) {
67
+ // First run + rename in one shot: still generate the deviceId.
68
+ const fresh = {
69
+ deviceId: getOrCreateDeviceId(),
70
+ createdAt: new Date().toISOString(),
71
+ initialHostname: safeHostname(),
72
+ alias: meta.alias,
73
+ description: meta.description,
74
+ };
75
+ writeDeviceState(fresh);
76
+ return fresh;
77
+ }
78
+ const next = { ...current };
79
+ if (meta.alias !== undefined)
80
+ next.alias = meta.alias;
81
+ if (meta.description !== undefined)
82
+ next.description = meta.description;
83
+ writeDeviceState(next);
84
+ return next;
85
+ }
86
+ export function getModelString() {
87
+ // Format chosen to mirror DeviceRegistrar.swift's "{hwModel}, macOS {ver}"
88
+ // shape so the device list in iOS/macOS reads consistently.
89
+ // Linux: "{kernel-release}, {hostname}" — no useful "hw.model" sysctl
90
+ // equivalent on a generic VM; we'd otherwise have to parse
91
+ // /sys/class/dmi/id/product_name which most cloud VMs forge or hide.
92
+ return `Linux ${release()}`;
93
+ }
94
+ export function safeHostname() {
95
+ try {
96
+ return osHostname();
97
+ }
98
+ catch {
99
+ return 'linux-host';
100
+ }
101
+ }
102
+ export function currentPlatform() {
103
+ return platform();
104
+ }
105
+ //# sourceMappingURL=device-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-id.js","sourceRoot":"","sources":["../../src/mesh/device-id.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { PartyKitClient } from './partykit-client.js';
2
+ export interface RegistrarOpts {
3
+ /** Override hostname (useful for `yome mesh start --as <name>`). */
4
+ hostnameOverride?: string;
5
+ /** Optional log hook. */
6
+ log?: (level: 'info' | 'warn' | 'error', msg: string, meta?: Record<string, unknown>) => void;
7
+ }
8
+ export declare class DeviceRegistrar {
9
+ private client;
10
+ private opts;
11
+ private heartbeatTimer;
12
+ private detachConnect;
13
+ private detachDisconnect;
14
+ private readonly deviceId;
15
+ private readonly hostname;
16
+ private readonly model;
17
+ private capabilities;
18
+ constructor(client: PartyKitClient, opts?: RegistrarOpts);
19
+ getDeviceId(): string;
20
+ getHostname(): string;
21
+ getCapabilities(): string[];
22
+ start(): void;
23
+ stop(): void;
24
+ sendRegistration(): Promise<void>;
25
+ /** Apply local metadata update + push mesh:update-device. */
26
+ sendDeviceMetaUpdate(meta: {
27
+ alias?: string;
28
+ description?: string;
29
+ }): Promise<void>;
30
+ private scheduleHeartbeat;
31
+ private clearHeartbeat;
32
+ private log;
33
+ }
@@ -0,0 +1,131 @@
1
+ // Send mesh:register + mesh:heartbeat to the PartyKit room.
2
+ //
3
+ // Port of Yome/Shared/Sync/DeviceRegistrar.swift. Behaviour:
4
+ // - On connect: send mesh:register {hostname, model, capabilities, ...}.
5
+ // - Every 30s: send mesh:heartbeat {deviceId}.
6
+ // - On reconnect: re-send register (room may have evicted the stale
7
+ // entry while we were down).
8
+ // - On alias / description change: send mesh:update-device.
9
+ import { detectCapabilities } from './capabilities.js';
10
+ import { getOrCreateDeviceId, getModelString, loadDeviceState, safeHostname } from './device-id.js';
11
+ const HEARTBEAT_INTERVAL_MS = 30_000;
12
+ export class DeviceRegistrar {
13
+ client;
14
+ opts;
15
+ heartbeatTimer = null;
16
+ detachConnect = null;
17
+ detachDisconnect = null;
18
+ deviceId;
19
+ hostname;
20
+ model;
21
+ capabilities;
22
+ constructor(client, opts = {}) {
23
+ this.client = client;
24
+ this.opts = opts;
25
+ this.deviceId = getOrCreateDeviceId();
26
+ this.hostname = opts.hostnameOverride ?? safeHostname();
27
+ this.model = getModelString();
28
+ this.capabilities = detectCapabilities();
29
+ }
30
+ getDeviceId() { return this.deviceId; }
31
+ getHostname() { return this.hostname; }
32
+ getCapabilities() { return [...this.capabilities]; }
33
+ start() {
34
+ // Initial registration if we're already connected (caller's choice;
35
+ // usually they connect first, then call start()).
36
+ if (this.client.connected) {
37
+ void this.sendRegistration();
38
+ this.scheduleHeartbeat();
39
+ }
40
+ // On every (re)connect, push registration again. Stash the off-handles
41
+ // so stop() can detach.
42
+ const noopDetach = () => { };
43
+ this.detachConnect = noopDetach; // overridden below if we wire onConnect
44
+ this.client.onConnect(() => {
45
+ this.log('info', 'PartyKit connected — sending registration', {
46
+ deviceId: this.deviceId, capabilities: this.capabilities,
47
+ });
48
+ void this.sendRegistration();
49
+ this.scheduleHeartbeat();
50
+ });
51
+ this.client.onDisconnect(() => {
52
+ this.log('warn', 'PartyKit disconnected — pausing heartbeat');
53
+ this.clearHeartbeat();
54
+ });
55
+ }
56
+ stop() {
57
+ this.clearHeartbeat();
58
+ this.detachConnect?.();
59
+ this.detachDisconnect?.();
60
+ this.detachConnect = null;
61
+ this.detachDisconnect = null;
62
+ }
63
+ async sendRegistration() {
64
+ const state = loadDeviceState();
65
+ const frame = {
66
+ type: 'mesh:register',
67
+ hostname: this.hostname,
68
+ model: this.model,
69
+ capabilities: this.capabilities,
70
+ installedApps: [], // Linux has no app-bundle concept; capabilities cover it
71
+ ...(state?.alias ? { alias: state.alias } : {}),
72
+ ...(state?.description ? { deviceDescription: state.description } : {}),
73
+ };
74
+ try {
75
+ await this.client.send(frame);
76
+ }
77
+ catch (err) {
78
+ this.log('error', 'sendRegistration failed', { err: err.message });
79
+ }
80
+ }
81
+ /** Apply local metadata update + push mesh:update-device. */
82
+ async sendDeviceMetaUpdate(meta) {
83
+ const frame = {
84
+ type: 'mesh:update-device',
85
+ deviceId: this.deviceId,
86
+ ...(meta.alias !== undefined ? { alias: meta.alias } : {}),
87
+ ...(meta.description !== undefined ? { deviceDescription: meta.description } : {}),
88
+ };
89
+ try {
90
+ // Server expects register first, then update (see Swift comment in
91
+ // DeviceRegistrar.sendDeviceMetaUpdate). Honour the same ordering.
92
+ await this.sendRegistration();
93
+ await new Promise((r) => setTimeout(r, 200));
94
+ await this.client.send(frame);
95
+ }
96
+ catch (err) {
97
+ this.log('error', 'sendDeviceMetaUpdate failed', { err: err.message });
98
+ }
99
+ }
100
+ scheduleHeartbeat() {
101
+ this.clearHeartbeat();
102
+ this.heartbeatTimer = setInterval(() => {
103
+ const frame = { type: 'mesh:heartbeat', deviceId: this.deviceId };
104
+ // Fire-and-forget; PartyKitClient will surface fatal errors via
105
+ // its own 'close' handler.
106
+ void this.client.send(frame).catch(() => { });
107
+ }, HEARTBEAT_INTERVAL_MS);
108
+ if (typeof this.heartbeatTimer.unref === 'function')
109
+ this.heartbeatTimer.unref();
110
+ }
111
+ clearHeartbeat() {
112
+ if (this.heartbeatTimer) {
113
+ clearInterval(this.heartbeatTimer);
114
+ this.heartbeatTimer = null;
115
+ }
116
+ }
117
+ log(level, msg, meta) {
118
+ if (this.opts.log) {
119
+ this.opts.log(level, msg, meta);
120
+ return;
121
+ }
122
+ const line = meta ? `${msg} ${JSON.stringify(meta)}` : msg;
123
+ if (level === 'error')
124
+ console.error(`[registrar] ${line}`);
125
+ else if (level === 'warn')
126
+ console.warn(`[registrar] ${line}`);
127
+ else
128
+ console.log(`[registrar] ${line}`);
129
+ }
130
+ }
131
+ //# sourceMappingURL=device-registrar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-registrar.js","sourceRoot":"","sources":["../../src/mesh/device-registrar.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { PartyKitClient } from './partykit-client.js';
2
+ import { DeviceRegistrar } from './device-registrar.js';
3
+ import { RpcHandler } from './rpc-handler.js';
4
+ export interface MeshDaemonOpts {
5
+ /** Override hub URL (env YOME_HUB_BASE wins inside ticket.ts otherwise). */
6
+ hubBase?: string;
7
+ /** Optional PartyKit host override (staging / local dev). */
8
+ partykitHost?: string;
9
+ /** Hostname to advertise. Defaults to os.hostname(). */
10
+ asName?: string;
11
+ /** Log sink; if omitted we use the built-in console logger. */
12
+ log?: (level: 'info' | 'warn' | 'error', msg: string, meta?: Record<string, unknown>) => void;
13
+ }
14
+ export interface MeshDaemon {
15
+ client: PartyKitClient;
16
+ registrar: DeviceRegistrar;
17
+ rpc: RpcHandler;
18
+ shutdown: () => void;
19
+ }
20
+ /**
21
+ * Start the mesh daemon. Resolves once the WS connection has been
22
+ * INITIATED (not necessarily established); the registrar will (re)send
23
+ * the registration once the WS open handler fires.
24
+ *
25
+ * Returns a `shutdown()` so the cli command runner can install signal
26
+ * handlers (SIGINT / SIGTERM) that tear it down cleanly.
27
+ */
28
+ export declare function startMeshDaemon(opts?: MeshDaemonOpts): Promise<MeshDaemon>;
@@ -0,0 +1,71 @@
1
+ // Mesh subsystem entry point.
2
+ //
3
+ // Owns the lifetime of the PartyKit connection + DeviceRegistrar + RPC
4
+ // handler. Called by `yome mesh start --foreground` (and, indirectly,
5
+ // by the systemd unit installed via `yome daemon install` on Linux).
6
+ import { TicketCache, mintWsTicket } from './ticket.js';
7
+ import { PartyKitClient } from './partykit-client.js';
8
+ import { DeviceRegistrar } from './device-registrar.js';
9
+ import { RpcHandler } from './rpc-handler.js';
10
+ import { getOrCreateDeviceId, safeHostname } from './device-id.js';
11
+ /**
12
+ * Start the mesh daemon. Resolves once the WS connection has been
13
+ * INITIATED (not necessarily established); the registrar will (re)send
14
+ * the registration once the WS open handler fires.
15
+ *
16
+ * Returns a `shutdown()` so the cli command runner can install signal
17
+ * handlers (SIGINT / SIGTERM) that tear it down cleanly.
18
+ */
19
+ export async function startMeshDaemon(opts = {}) {
20
+ const log = opts.log ?? defaultLog;
21
+ log('info', 'Starting yome mesh', {
22
+ deviceId: getOrCreateDeviceId(),
23
+ hostname: opts.asName ?? safeHostname(),
24
+ });
25
+ const ticketCache = new TicketCache(() => mintWsTicket({ hubBase: opts.hubBase }));
26
+ // Warm the cache eagerly so a misconfigured hub fails fast with a
27
+ // human-readable error, instead of inside the WS reconnect loop.
28
+ const firstTicket = await ticketCache.get();
29
+ log('info', 'Hub minted WS ticket', {
30
+ userId: firstTicket.userId,
31
+ deviceId: firstTicket.deviceId,
32
+ expiresIn: firstTicket.expires_in,
33
+ partykitHost: firstTicket.partykit_host,
34
+ });
35
+ const client = new PartyKitClient({
36
+ ticketCache,
37
+ partykitHostOverride: opts.partykitHost,
38
+ clientType: 'desktop',
39
+ log,
40
+ });
41
+ const registrar = new DeviceRegistrar(client, {
42
+ hostnameOverride: opts.asName,
43
+ log,
44
+ });
45
+ registrar.start();
46
+ const rpc = new RpcHandler(client, { log });
47
+ rpc.start();
48
+ await client.connect();
49
+ return {
50
+ client,
51
+ registrar,
52
+ rpc,
53
+ shutdown: () => {
54
+ rpc.stop();
55
+ registrar.stop();
56
+ client.disconnect();
57
+ },
58
+ };
59
+ }
60
+ function defaultLog(level, msg, meta) {
61
+ const line = meta ? `${msg} ${JSON.stringify(meta)}` : msg;
62
+ const stamp = new Date().toISOString();
63
+ const out = `${stamp} [${level}] ${line}`;
64
+ if (level === 'error')
65
+ console.error(out);
66
+ else if (level === 'warn')
67
+ console.warn(out);
68
+ else
69
+ console.log(out);
70
+ }
71
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mesh/index.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,EAAE;AACF,uEAAuE;AACvE,sEAAsE;AACtE,qEAAqE;AAErE,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAoBnE;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,OAAuB,EAAE;IAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,UAAU,CAAC;IACnC,GAAG,CAAC,MAAM,EAAE,oBAAoB,EAAE;QAChC,QAAQ,EAAE,mBAAmB,EAAE;QAC/B,QAAQ,EAAE,IAAI,CAAC,MAAM,IAAI,YAAY,EAAE;KACxC,CAAC,CAAC;IAEH,MAAM,WAAW,GAAG,IAAI,WAAW,CAAC,GAAG,EAAE,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACnF,kEAAkE;IAClE,iEAAiE;IACjE,MAAM,WAAW,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,CAAC;IAC5C,GAAG,CAAC,MAAM,EAAE,sBAAsB,EAAE;QAClC,MAAM,EAAE,WAAW,CAAC,MAAM;QAC1B,QAAQ,EAAE,WAAW,CAAC,QAAQ;QAC9B,SAAS,EAAE,WAAW,CAAC,UAAU;QACjC,YAAY,EAAE,WAAW,CAAC,aAAa;KACxC,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC;QAChC,WAAW;QACX,oBAAoB,EAAE,IAAI,CAAC,YAAY;QACvC,UAAU,EAAE,SAAS;QACrB,GAAG;KACJ,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,IAAI,eAAe,CAAC,MAAM,EAAE;QAC5C,gBAAgB,EAAE,IAAI,CAAC,MAAM;QAC7B,GAAG;KACJ,CAAC,CAAC;IACH,SAAS,CAAC,KAAK,EAAE,CAAC;IAElB,MAAM,GAAG,GAAG,IAAI,UAAU,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;IAC5C,GAAG,CAAC,KAAK,EAAE,CAAC;IAEZ,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;IAEvB,OAAO;QACL,MAAM;QACN,SAAS;QACT,GAAG;QACH,QAAQ,EAAE,GAAG,EAAE;YACb,GAAG,CAAC,IAAI,EAAE,CAAC;YACX,SAAS,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,KAAgC,EAAE,GAAW,EAAE,IAA8B;IAC/F,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC;IAC3D,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACvC,MAAM,GAAG,GAAG,GAAG,KAAK,KAAK,KAAK,KAAK,IAAI,EAAE,CAAC;IAC1C,IAAI,KAAK,KAAK,OAAO;QAAE,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;SACrC,IAAI,KAAK,KAAK,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;;QACxC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC"}
@@ -0,0 +1,43 @@
1
+ import WebSocket from 'ws';
2
+ import type { TicketCache } from './ticket.js';
3
+ export interface PartyKitClientOpts {
4
+ ticketCache: TicketCache;
5
+ /** Override at boot for staging / local dev. */
6
+ partykitHostOverride?: string;
7
+ /** Override at boot for staging / local dev. */
8
+ roomIdOverride?: string;
9
+ clientType?: 'desktop' | 'mac' | 'ios' | 'web';
10
+ locale?: string;
11
+ /** Test seam: inject a custom WebSocket factory. */
12
+ wsFactory?: (url: string) => WebSocket;
13
+ /** Inject a logger; defaults to console. */
14
+ log?: (level: 'info' | 'warn' | 'error', msg: string, meta?: Record<string, unknown>) => void;
15
+ }
16
+ export type Listener = (frame: string) => void;
17
+ export declare class PartyKitClient {
18
+ private opts;
19
+ private ws;
20
+ private isConnected;
21
+ private reconnectAttempts;
22
+ private readonly maxReconnectAttempts;
23
+ private reconnectTimer;
24
+ private pingTimer;
25
+ private intentionalClose;
26
+ private readonly listeners;
27
+ private onConnectCb;
28
+ private onDisconnectCb;
29
+ constructor(opts: PartyKitClientOpts);
30
+ onMessage(cb: Listener): () => void;
31
+ onConnect(cb: () => void): void;
32
+ onDisconnect(cb: () => void): void;
33
+ connect(): Promise<void>;
34
+ disconnect(): void;
35
+ /** Throws if not connected. Caller chooses to await or fire-and-forget. */
36
+ send(frame: string | object): Promise<void>;
37
+ get connected(): boolean;
38
+ private log;
39
+ private establish;
40
+ private startPing;
41
+ private cleanupSocket;
42
+ private scheduleReconnect;
43
+ }