remote-pi 0.1.2 → 0.2.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 +195 -36
- package/dist/bin/supervisord.d.ts +2 -0
- package/dist/bin/supervisord.js +44 -0
- package/dist/bin/supervisord.js.map +1 -0
- package/dist/config.d.ts +49 -5
- package/dist/config.js +73 -9
- package/dist/config.js.map +1 -1
- package/dist/daemon/client.d.ts +20 -0
- package/dist/daemon/client.js +128 -0
- package/dist/daemon/client.js.map +1 -0
- package/dist/daemon/control_protocol.d.ts +100 -0
- package/dist/daemon/control_protocol.js +63 -0
- package/dist/daemon/control_protocol.js.map +1 -0
- package/dist/daemon/id.d.ts +18 -0
- package/dist/daemon/id.js +30 -0
- package/dist/daemon/id.js.map +1 -0
- package/dist/daemon/install.d.ts +132 -0
- package/dist/daemon/install.js +312 -0
- package/dist/daemon/install.js.map +1 -0
- package/dist/daemon/registry.d.ts +47 -0
- package/dist/daemon/registry.js +123 -0
- package/dist/daemon/registry.js.map +1 -0
- package/dist/daemon/rpc_child.d.ts +76 -0
- package/dist/daemon/rpc_child.js +130 -0
- package/dist/daemon/rpc_child.js.map +1 -0
- package/dist/daemon/supervisor.d.ts +38 -0
- package/dist/daemon/supervisor.js +301 -0
- package/dist/daemon/supervisor.js.map +1 -0
- package/dist/index.d.ts +62 -8
- package/dist/index.js +1232 -304
- package/dist/index.js.map +1 -1
- package/dist/mesh/canonical.d.ts +30 -0
- package/dist/mesh/canonical.js +61 -0
- package/dist/mesh/canonical.js.map +1 -0
- package/dist/mesh/client.d.ts +31 -0
- package/dist/mesh/client.js +56 -0
- package/dist/mesh/client.js.map +1 -0
- package/dist/mesh/encoding.d.ts +36 -0
- package/dist/mesh/encoding.js +53 -0
- package/dist/mesh/encoding.js.map +1 -0
- package/dist/mesh/self_revoke.d.ts +111 -0
- package/dist/mesh/self_revoke.js +182 -0
- package/dist/mesh/self_revoke.js.map +1 -0
- package/dist/mesh/siblings.d.ts +62 -0
- package/dist/mesh/siblings.js +95 -0
- package/dist/mesh/siblings.js.map +1 -0
- package/dist/mesh/types.d.ts +34 -0
- package/dist/mesh/types.js +11 -0
- package/dist/mesh/types.js.map +1 -0
- package/dist/mesh/verify.d.ts +17 -0
- package/dist/mesh/verify.js +77 -0
- package/dist/mesh/verify.js.map +1 -0
- package/dist/pairing/qr.d.ts +16 -5
- package/dist/pairing/qr.js +27 -8
- package/dist/pairing/qr.js.map +1 -1
- package/dist/pairing/storage.d.ts +41 -0
- package/dist/pairing/storage.js +158 -21
- package/dist/pairing/storage.js.map +1 -1
- package/dist/protocol/types.d.ts +23 -0
- package/dist/session/broker.d.ts +74 -0
- package/dist/session/broker.js +142 -4
- package/dist/session/broker.js.map +1 -1
- package/dist/session/broker_remote.d.ts +110 -0
- package/dist/session/broker_remote.js +397 -0
- package/dist/session/broker_remote.js.map +1 -0
- package/dist/session/cwd_lock.d.ts +28 -0
- package/dist/session/cwd_lock.js +89 -0
- package/dist/session/cwd_lock.js.map +1 -0
- package/dist/session/global_config.d.ts +9 -0
- package/dist/session/global_config.js +9 -0
- package/dist/session/global_config.js.map +1 -1
- package/dist/session/leader_election.d.ts +16 -0
- package/dist/session/leader_election.js +22 -0
- package/dist/session/leader_election.js.map +1 -1
- package/dist/session/local_config.d.ts +12 -5
- package/dist/session/local_config.js +24 -3
- package/dist/session/local_config.js.map +1 -1
- package/dist/session/peer.d.ts +28 -1
- package/dist/session/peer.js +69 -2
- package/dist/session/peer.js.map +1 -1
- package/dist/session/peer_inventory.d.ts +13 -0
- package/dist/session/peer_inventory.js +48 -0
- package/dist/session/peer_inventory.js.map +1 -0
- package/dist/session/setup_wizard.d.ts +32 -8
- package/dist/session/setup_wizard.js +45 -33
- package/dist/session/setup_wizard.js.map +1 -1
- package/dist/session/tools.d.ts +15 -7
- package/dist/session/tools.js +145 -31
- package/dist/session/tools.js.map +1 -1
- package/dist/transport/pi_forward_client.d.ts +29 -0
- package/dist/transport/pi_forward_client.js +62 -0
- package/dist/transport/pi_forward_client.js.map +1 -0
- package/dist/ui/footer.js +8 -6
- package/dist/ui/footer.js.map +1 -1
- package/docs/daemon.md +289 -0
- package/package.json +10 -3
- package/service-templates/launchd.plist.template +35 -0
- package/service-templates/systemd.service.template +19 -0
- package/skills/agent-network/SKILL.md +273 -294
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { createConnection } from "node:net";
|
|
3
|
+
import { encodeRequest, parseReply, } from "./control_protocol.js";
|
|
4
|
+
import { getSupervisorSockPath } from "./supervisor.js";
|
|
5
|
+
/**
|
|
6
|
+
* Tiny client for the `remote-pi` CLI to call the supervisor over the
|
|
7
|
+
* `~/.pi/remote/supervisor.sock` UDS.
|
|
8
|
+
*
|
|
9
|
+
* Each call opens a fresh connection, sends one request line, reads one
|
|
10
|
+
* reply line, closes. No connection pooling — the CLI is short-lived,
|
|
11
|
+
* latency is dominated by the socket round-trip (<1ms).
|
|
12
|
+
*
|
|
13
|
+
* `SupervisorOfflineError` is the common error: thrown when the socket
|
|
14
|
+
* file is missing OR the connect fails (no listener). The CLI handler
|
|
15
|
+
* formats it as a friendly "Run `remote-pi install` first" message.
|
|
16
|
+
*/
|
|
17
|
+
const CONNECT_TIMEOUT_MS = 1000;
|
|
18
|
+
const REPLY_TIMEOUT_MS = 5000;
|
|
19
|
+
export class SupervisorOfflineError extends Error {
|
|
20
|
+
sockPath;
|
|
21
|
+
constructor(sockPath) {
|
|
22
|
+
super(`Supervisor is not running. UDS not responding at ${sockPath}.\n` +
|
|
23
|
+
"Run `remote-pi install` to set it up, or start it manually with `pi-supervisord`.");
|
|
24
|
+
this.sockPath = sockPath;
|
|
25
|
+
this.name = "SupervisorOfflineError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Sends a single request and returns the typed reply data.
|
|
30
|
+
*
|
|
31
|
+
* Throws:
|
|
32
|
+
* - `SupervisorOfflineError` when the supervisor isn't reachable.
|
|
33
|
+
* - `Error` from `parseReply` when the reply line is malformed.
|
|
34
|
+
* - The supervisor's own error string when `ok: false`.
|
|
35
|
+
*/
|
|
36
|
+
export async function callSupervisor(req) {
|
|
37
|
+
const sockPath = getSupervisorSockPath();
|
|
38
|
+
if (!existsSync(sockPath))
|
|
39
|
+
throw new SupervisorOfflineError(sockPath);
|
|
40
|
+
const sock = await _connect(sockPath);
|
|
41
|
+
try {
|
|
42
|
+
sock.write(encodeRequest(req));
|
|
43
|
+
const line = await _readLine(sock);
|
|
44
|
+
const reply = parseReply(line);
|
|
45
|
+
if (!reply.ok)
|
|
46
|
+
throw new Error(reply.error);
|
|
47
|
+
return reply.data;
|
|
48
|
+
}
|
|
49
|
+
finally {
|
|
50
|
+
sock.destroy();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/** Returns true when the supervisor is reachable. Used by `/remote-pi
|
|
54
|
+
* daemons` to decide whether to query runtime state or fall back to
|
|
55
|
+
* registry-only listing. */
|
|
56
|
+
export async function supervisorOnline() {
|
|
57
|
+
const sockPath = getSupervisorSockPath();
|
|
58
|
+
if (!existsSync(sockPath))
|
|
59
|
+
return false;
|
|
60
|
+
try {
|
|
61
|
+
const sock = await _connect(sockPath);
|
|
62
|
+
sock.destroy();
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// ── internals ───────────────────────────────────────────────────────────────
|
|
70
|
+
function _connect(sockPath) {
|
|
71
|
+
return new Promise((resolve, reject) => {
|
|
72
|
+
const sock = createConnection({ path: sockPath });
|
|
73
|
+
let settled = false;
|
|
74
|
+
const timer = setTimeout(() => {
|
|
75
|
+
if (settled)
|
|
76
|
+
return;
|
|
77
|
+
settled = true;
|
|
78
|
+
sock.destroy();
|
|
79
|
+
reject(new SupervisorOfflineError(sockPath));
|
|
80
|
+
}, CONNECT_TIMEOUT_MS);
|
|
81
|
+
sock.once("connect", () => {
|
|
82
|
+
if (settled)
|
|
83
|
+
return;
|
|
84
|
+
settled = true;
|
|
85
|
+
clearTimeout(timer);
|
|
86
|
+
sock.setEncoding("utf8");
|
|
87
|
+
resolve(sock);
|
|
88
|
+
});
|
|
89
|
+
sock.once("error", () => {
|
|
90
|
+
if (settled)
|
|
91
|
+
return;
|
|
92
|
+
settled = true;
|
|
93
|
+
clearTimeout(timer);
|
|
94
|
+
reject(new SupervisorOfflineError(sockPath));
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
function _readLine(sock) {
|
|
99
|
+
return new Promise((resolve, reject) => {
|
|
100
|
+
let buf = "";
|
|
101
|
+
const timer = setTimeout(() => {
|
|
102
|
+
reject(new Error("supervisor reply timeout"));
|
|
103
|
+
sock.destroy();
|
|
104
|
+
}, REPLY_TIMEOUT_MS);
|
|
105
|
+
sock.on("data", (chunk) => {
|
|
106
|
+
buf += chunk;
|
|
107
|
+
const nl = buf.indexOf("\n");
|
|
108
|
+
if (nl >= 0) {
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
resolve(buf.slice(0, nl));
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
sock.on("end", () => {
|
|
114
|
+
clearTimeout(timer);
|
|
115
|
+
const nl = buf.indexOf("\n");
|
|
116
|
+
if (nl >= 0)
|
|
117
|
+
return resolve(buf.slice(0, nl));
|
|
118
|
+
if (buf.length > 0)
|
|
119
|
+
return resolve(buf);
|
|
120
|
+
reject(new Error("supervisor closed connection without replying"));
|
|
121
|
+
});
|
|
122
|
+
sock.on("error", (err) => {
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
reject(err);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/daemon/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI ↔ supervisor IPC contract for `~/.pi/remote/supervisor.sock`.
|
|
3
|
+
*
|
|
4
|
+
* Framing: one JSON object per line, newline-terminated. The CLI sends a
|
|
5
|
+
* single `ControlRequest`, the supervisor sends a single `ControlReply`,
|
|
6
|
+
* both close the connection. No multiplexing, no streaming — each command
|
|
7
|
+
* is a short round-trip.
|
|
8
|
+
*
|
|
9
|
+
* Plan/26 W2. The Pi RPC protocol (`pi --mode rpc`) used by the daemon
|
|
10
|
+
* children themselves is a separate contract — see
|
|
11
|
+
* `node_modules/@mariozechner/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts`.
|
|
12
|
+
* This file is strictly the supervisor's own control plane.
|
|
13
|
+
*/
|
|
14
|
+
/** Per-daemon runtime state observable through the supervisor. */
|
|
15
|
+
export type DaemonState = "running" | "stopped" | "starting" | "crashed";
|
|
16
|
+
export interface DaemonInfo {
|
|
17
|
+
id: string;
|
|
18
|
+
cwd: string;
|
|
19
|
+
name: string;
|
|
20
|
+
state: DaemonState;
|
|
21
|
+
pid?: number;
|
|
22
|
+
uptime_s?: number;
|
|
23
|
+
restart_count?: number;
|
|
24
|
+
}
|
|
25
|
+
/** Requests sent CLI → supervisor. */
|
|
26
|
+
export type ControlRequest = {
|
|
27
|
+
op: "list";
|
|
28
|
+
} | {
|
|
29
|
+
op: "status";
|
|
30
|
+
} | {
|
|
31
|
+
op: "start_all";
|
|
32
|
+
} | {
|
|
33
|
+
op: "stop_all";
|
|
34
|
+
} | {
|
|
35
|
+
op: "restart_all";
|
|
36
|
+
} | {
|
|
37
|
+
op: "send";
|
|
38
|
+
id: string;
|
|
39
|
+
text: string;
|
|
40
|
+
} | {
|
|
41
|
+
op: "register";
|
|
42
|
+
cwd: string;
|
|
43
|
+
} | {
|
|
44
|
+
op: "unregister";
|
|
45
|
+
id: string;
|
|
46
|
+
};
|
|
47
|
+
/** Replies sent supervisor → CLI. Tagged by `ok` boolean. */
|
|
48
|
+
export type ControlReply<T = unknown> = {
|
|
49
|
+
ok: true;
|
|
50
|
+
data?: T;
|
|
51
|
+
} | {
|
|
52
|
+
ok: false;
|
|
53
|
+
error: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Response shapes per op. Keep in sync with the supervisor handlers in
|
|
57
|
+
* `daemon/supervisor.ts`. Used for typed client calls.
|
|
58
|
+
*/
|
|
59
|
+
export interface ControlReplyShapes {
|
|
60
|
+
list: {
|
|
61
|
+
daemons: DaemonInfo[];
|
|
62
|
+
};
|
|
63
|
+
status: {
|
|
64
|
+
daemons: DaemonInfo[];
|
|
65
|
+
};
|
|
66
|
+
start_all: {
|
|
67
|
+
started: string[];
|
|
68
|
+
already_running: string[];
|
|
69
|
+
};
|
|
70
|
+
stop_all: {
|
|
71
|
+
stopped: string[];
|
|
72
|
+
already_stopped: string[];
|
|
73
|
+
};
|
|
74
|
+
restart_all: {
|
|
75
|
+
restarted: string[];
|
|
76
|
+
};
|
|
77
|
+
send: {
|
|
78
|
+
id: string;
|
|
79
|
+
delivered: boolean;
|
|
80
|
+
};
|
|
81
|
+
register: {
|
|
82
|
+
id: string;
|
|
83
|
+
cwd: string;
|
|
84
|
+
};
|
|
85
|
+
unregister: {
|
|
86
|
+
removed: boolean;
|
|
87
|
+
cwd?: string;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/** Convenience for typed `Client.request<...>("op")` calls. */
|
|
91
|
+
export type ControlReplyFor<Op extends ControlRequest["op"]> = Op extends keyof ControlReplyShapes ? ControlReplyShapes[Op] : never;
|
|
92
|
+
export declare function encodeRequest(req: ControlRequest): string;
|
|
93
|
+
export declare function encodeReply<T>(reply: ControlReply<T>): string;
|
|
94
|
+
/**
|
|
95
|
+
* Parses a single JSON line into a request. Throws on malformed input —
|
|
96
|
+
* the supervisor catches and replies `{ok:false, error}` so the client
|
|
97
|
+
* gets a clean error rather than an unframed disconnect.
|
|
98
|
+
*/
|
|
99
|
+
export declare function parseRequest(line: string): ControlRequest;
|
|
100
|
+
export declare function parseReply(line: string): ControlReply<unknown>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI ↔ supervisor IPC contract for `~/.pi/remote/supervisor.sock`.
|
|
3
|
+
*
|
|
4
|
+
* Framing: one JSON object per line, newline-terminated. The CLI sends a
|
|
5
|
+
* single `ControlRequest`, the supervisor sends a single `ControlReply`,
|
|
6
|
+
* both close the connection. No multiplexing, no streaming — each command
|
|
7
|
+
* is a short round-trip.
|
|
8
|
+
*
|
|
9
|
+
* Plan/26 W2. The Pi RPC protocol (`pi --mode rpc`) used by the daemon
|
|
10
|
+
* children themselves is a separate contract — see
|
|
11
|
+
* `node_modules/@mariozechner/pi-coding-agent/dist/modes/rpc/rpc-types.d.ts`.
|
|
12
|
+
* This file is strictly the supervisor's own control plane.
|
|
13
|
+
*/
|
|
14
|
+
// ── Serialization helpers ────────────────────────────────────────────────────
|
|
15
|
+
const TRAILING_NEWLINE = "\n";
|
|
16
|
+
export function encodeRequest(req) {
|
|
17
|
+
return JSON.stringify(req) + TRAILING_NEWLINE;
|
|
18
|
+
}
|
|
19
|
+
export function encodeReply(reply) {
|
|
20
|
+
return JSON.stringify(reply) + TRAILING_NEWLINE;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Parses a single JSON line into a request. Throws on malformed input —
|
|
24
|
+
* the supervisor catches and replies `{ok:false, error}` so the client
|
|
25
|
+
* gets a clean error rather than an unframed disconnect.
|
|
26
|
+
*/
|
|
27
|
+
export function parseRequest(line) {
|
|
28
|
+
let obj;
|
|
29
|
+
try {
|
|
30
|
+
obj = JSON.parse(line);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
throw new Error(`malformed control request: ${e.message}`);
|
|
34
|
+
}
|
|
35
|
+
if (!obj || typeof obj !== "object") {
|
|
36
|
+
throw new Error("control request must be a JSON object");
|
|
37
|
+
}
|
|
38
|
+
const op = obj.op;
|
|
39
|
+
if (typeof op !== "string") {
|
|
40
|
+
throw new Error("control request missing string `op` field");
|
|
41
|
+
}
|
|
42
|
+
// We don't validate every field shape here — supervisor handlers do it
|
|
43
|
+
// per-op since the error messages are more specific that way.
|
|
44
|
+
return obj;
|
|
45
|
+
}
|
|
46
|
+
export function parseReply(line) {
|
|
47
|
+
let obj;
|
|
48
|
+
try {
|
|
49
|
+
obj = JSON.parse(line);
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
throw new Error(`malformed control reply: ${e.message}`);
|
|
53
|
+
}
|
|
54
|
+
if (!obj || typeof obj !== "object") {
|
|
55
|
+
throw new Error("control reply must be a JSON object");
|
|
56
|
+
}
|
|
57
|
+
const ok = obj.ok;
|
|
58
|
+
if (typeof ok !== "boolean") {
|
|
59
|
+
throw new Error("control reply missing boolean `ok` field");
|
|
60
|
+
}
|
|
61
|
+
return obj;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=control_protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control_protocol.js","sourceRoot":"","sources":["../../src/daemon/control_protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAkDH,gFAAgF;AAEhF,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAE9B,MAAM,UAAU,aAAa,CAAC,GAAmB;IAC/C,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,gBAAgB,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,WAAW,CAAI,KAAsB;IACnD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,gBAAgB,CAAC;AAClD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC;QAAC,MAAM,IAAI,KAAK,CAAC,8BAA+B,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAAC,CAAC;IACpF,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC3D,CAAC;IACD,MAAM,EAAE,GAAI,GAAwB,CAAC,EAAE,CAAC;IACxC,IAAI,OAAO,EAAE,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,2CAA2C,CAAC,CAAC;IAC/D,CAAC;IACD,uEAAuE;IACvE,8DAA8D;IAC9D,OAAO,GAAqB,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,IAAI,GAAY,CAAC;IACjB,IAAI,CAAC;QAAC,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,CAAC;QAAC,MAAM,IAAI,KAAK,CAAC,4BAA6B,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IAAC,CAAC;IAClF,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QACpC,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACzD,CAAC;IACD,MAAM,EAAE,GAAI,GAAwB,CAAC,EAAE,CAAC;IACxC,IAAI,OAAO,EAAE,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,GAA4B,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stable 8-character hex id for a daemon, derived from the cwd it manages.
|
|
3
|
+
*
|
|
4
|
+
* Derivation: `sha256(realpath(cwd))` truncated to 8 hex chars (32 bits ≈
|
|
5
|
+
* 4 billion). Collision risk is negligible at fleet sizes a single user
|
|
6
|
+
* will ever have (<1000 daemons), and 8 hex characters are short enough
|
|
7
|
+
* to type on the CLI (`/remote-pi send a1b2c3d4 "..."`).
|
|
8
|
+
*
|
|
9
|
+
* Same scheme as `roomIdForCwd` in `src/rooms.ts` — but we use hex
|
|
10
|
+
* instead of base64url so the id has no `_`/`-` (cleaner double-click
|
|
11
|
+
* selection in terminals).
|
|
12
|
+
*
|
|
13
|
+
* Symlinks resolve to a single canonical id via `realpath`, so
|
|
14
|
+
* `/Users/x/Movies` and `/Users/x/link-to-Movies` map to the same daemon.
|
|
15
|
+
* Falls back to the raw path when realpath fails (cwd doesn't exist —
|
|
16
|
+
* shouldn't happen in production but covers test sandboxes).
|
|
17
|
+
*/
|
|
18
|
+
export declare function daemonIdForCwd(cwd: string): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { realpathSync } from "node:fs";
|
|
3
|
+
/**
|
|
4
|
+
* Stable 8-character hex id for a daemon, derived from the cwd it manages.
|
|
5
|
+
*
|
|
6
|
+
* Derivation: `sha256(realpath(cwd))` truncated to 8 hex chars (32 bits ≈
|
|
7
|
+
* 4 billion). Collision risk is negligible at fleet sizes a single user
|
|
8
|
+
* will ever have (<1000 daemons), and 8 hex characters are short enough
|
|
9
|
+
* to type on the CLI (`/remote-pi send a1b2c3d4 "..."`).
|
|
10
|
+
*
|
|
11
|
+
* Same scheme as `roomIdForCwd` in `src/rooms.ts` — but we use hex
|
|
12
|
+
* instead of base64url so the id has no `_`/`-` (cleaner double-click
|
|
13
|
+
* selection in terminals).
|
|
14
|
+
*
|
|
15
|
+
* Symlinks resolve to a single canonical id via `realpath`, so
|
|
16
|
+
* `/Users/x/Movies` and `/Users/x/link-to-Movies` map to the same daemon.
|
|
17
|
+
* Falls back to the raw path when realpath fails (cwd doesn't exist —
|
|
18
|
+
* shouldn't happen in production but covers test sandboxes).
|
|
19
|
+
*/
|
|
20
|
+
export function daemonIdForCwd(cwd) {
|
|
21
|
+
let target;
|
|
22
|
+
try {
|
|
23
|
+
target = realpathSync(cwd);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
target = cwd;
|
|
27
|
+
}
|
|
28
|
+
return createHash("sha256").update(target).digest("hex").slice(0, 8);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id.js","sourceRoot":"","sources":["../../src/daemon/id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,GAAG,GAAG,CAAC;IACf,CAAC;IACD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACvE,CAAC"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates and activates a system service for `pi-supervisord` so the
|
|
3
|
+
* daemon fleet survives reboots (plan/26 W3).
|
|
4
|
+
*
|
|
5
|
+
* Platform support:
|
|
6
|
+
* - **macOS**: writes `~/Library/LaunchAgents/dev.remotepi.supervisord.plist`
|
|
7
|
+
* and runs `launchctl bootstrap gui/<uid> <plist>` (modern API) with a
|
|
8
|
+
* fallback to `launchctl load` for older macOS.
|
|
9
|
+
* - **Linux**: writes `~/.config/systemd/user/remote-pi-supervisord.service`
|
|
10
|
+
* and runs `systemctl --user daemon-reload && systemctl --user enable
|
|
11
|
+
* --now remote-pi-supervisord.service`.
|
|
12
|
+
*
|
|
13
|
+
* Uninstall reverses both. Idempotent — re-running install over an existing
|
|
14
|
+
* unit refreshes it (paths could have changed if user moved node_modules).
|
|
15
|
+
*
|
|
16
|
+
* **What does NOT happen here**: the actual `npm install -g remote-pi` step.
|
|
17
|
+
* The user has to make the supervisor bin reachable on disk before install
|
|
18
|
+
* can wire up the service. The `findSupervisorScript` resolver detects
|
|
19
|
+
* common cases (npm global, pnpm global, local dev clone) and yields a
|
|
20
|
+
* clear error otherwise.
|
|
21
|
+
*/
|
|
22
|
+
export type SupervisorPlatform = "macos" | "linux" | "unsupported";
|
|
23
|
+
export declare function detectPlatform(): SupervisorPlatform;
|
|
24
|
+
/**
|
|
25
|
+
* Absolute path to the supervisor's compiled entry. We resolve from
|
|
26
|
+
* `import.meta.url` (this file's location) since wherever the daemon
|
|
27
|
+
* module lives, `bin/supervisord.js` is a sibling of `daemon/` under
|
|
28
|
+
* `dist/`.
|
|
29
|
+
*
|
|
30
|
+
* After build: `dist/daemon/install.js` → `dist/bin/supervisord.js`.
|
|
31
|
+
* In dev (`tsx`): same path resolution still lands inside `src/`, which
|
|
32
|
+
* isn't directly runnable by `node` — dev install isn't expected.
|
|
33
|
+
*/
|
|
34
|
+
export declare function findSupervisorScript(): string;
|
|
35
|
+
/**
|
|
36
|
+
* Absolute path to the extension's CLI entry (`dist/index.js`). This is
|
|
37
|
+
* the file we symlink to `~/.local/bin/remote-pi` so the user can run
|
|
38
|
+
* `remote-pi <subcommand>` from any shell after installing the extension
|
|
39
|
+
* through Pi (`pi install npm:remote-pi`).
|
|
40
|
+
*
|
|
41
|
+
* Same resolution strategy as `findSupervisorScript`: from
|
|
42
|
+
* `dist/daemon/install.js` → `dist/index.js`.
|
|
43
|
+
*/
|
|
44
|
+
export declare function findRemotePiScript(): string;
|
|
45
|
+
export declare function findNodeBinary(): string;
|
|
46
|
+
export declare function findTemplate(name: "systemd" | "launchd"): string;
|
|
47
|
+
export declare function systemdUnitPath(): string;
|
|
48
|
+
export declare function launchdPlistPath(): string;
|
|
49
|
+
export interface RenderVars {
|
|
50
|
+
node: string;
|
|
51
|
+
supervisor: string;
|
|
52
|
+
home: string;
|
|
53
|
+
user: string;
|
|
54
|
+
/** PATH inherited so `pi --mode rpc` resolves the same way it does
|
|
55
|
+
* interactively. We snapshot `process.env.PATH` at install time. */
|
|
56
|
+
path: string;
|
|
57
|
+
}
|
|
58
|
+
export declare function defaultRenderVars(): RenderVars;
|
|
59
|
+
/** Replace `{NODE}` / `{SUPERVISOR}` / `{USER}` / `{HOME}` / `{PATH}`. */
|
|
60
|
+
export declare function renderTemplate(template: string, vars: RenderVars): string;
|
|
61
|
+
export interface InstallResult {
|
|
62
|
+
platform: SupervisorPlatform;
|
|
63
|
+
unitPath: string;
|
|
64
|
+
/** Lines describing each step taken — surfaced to the user via notify. */
|
|
65
|
+
log: string[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Writes the unit/plist, runs the platform's activation command. Throws
|
|
69
|
+
* on unsupported OS or when the supervisor script isn't found.
|
|
70
|
+
*
|
|
71
|
+
* Idempotent: re-running re-writes the unit (paths could have changed)
|
|
72
|
+
* and re-activates via the platform tool's idempotent flag.
|
|
73
|
+
*/
|
|
74
|
+
export declare function installService(vars?: RenderVars): InstallResult;
|
|
75
|
+
export interface UninstallResult {
|
|
76
|
+
platform: SupervisorPlatform;
|
|
77
|
+
unitPath: string;
|
|
78
|
+
removed: boolean;
|
|
79
|
+
log: string[];
|
|
80
|
+
}
|
|
81
|
+
export declare function uninstallService(): UninstallResult;
|
|
82
|
+
export interface LinkBinariesResult {
|
|
83
|
+
/** `~/.local/bin/`. The two symlinks land here. */
|
|
84
|
+
binDir: string;
|
|
85
|
+
/** Paths of the two symlinks we created/refreshed. */
|
|
86
|
+
links: Array<{
|
|
87
|
+
name: string;
|
|
88
|
+
path: string;
|
|
89
|
+
target: string;
|
|
90
|
+
}>;
|
|
91
|
+
/** True when `binDir` is already on `$PATH`. False → caller surfaces the
|
|
92
|
+
* "add this line to your shell rc" hint to the user. */
|
|
93
|
+
onPath: boolean;
|
|
94
|
+
log: string[];
|
|
95
|
+
}
|
|
96
|
+
export declare function userLocalBinDir(home?: string): string;
|
|
97
|
+
/**
|
|
98
|
+
* Check whether `dir` is on `process.env.PATH`. Tolerates trailing
|
|
99
|
+
* slashes and relative entries (which we treat as not matching — `~/.local/bin`
|
|
100
|
+
* is always absolute on our end).
|
|
101
|
+
*/
|
|
102
|
+
export declare function isOnPath(dir: string, envPath?: string): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Create (or refresh) the `remote-pi` + `pi-supervisord` symlinks in
|
|
105
|
+
* `~/.local/bin/`. Idempotent — replaces stale links pointing at old
|
|
106
|
+
* extension paths (Pi can reinstall the extension to a different hash dir
|
|
107
|
+
* on upgrades, so this MUST overwrite).
|
|
108
|
+
*
|
|
109
|
+
* Returns `onPath: false` when `~/.local/bin` isn't in the user's `$PATH`.
|
|
110
|
+
* The caller is responsible for surfacing the shell-rc instruction —
|
|
111
|
+
* we don't edit the user's shell config files automatically.
|
|
112
|
+
*/
|
|
113
|
+
export declare function linkCliBinaries(home?: string, paths?: {
|
|
114
|
+
remotePi?: string;
|
|
115
|
+
supervisord?: string;
|
|
116
|
+
}): LinkBinariesResult;
|
|
117
|
+
/**
|
|
118
|
+
* Remove the symlinks `linkCliBinaries` created. Idempotent — missing
|
|
119
|
+
* links are a no-op. Returns whether each link was actually present so
|
|
120
|
+
* the caller can render a useful summary. Targets (the extension files)
|
|
121
|
+
* are NOT touched here — they live outside this dir and belong to Pi.
|
|
122
|
+
*/
|
|
123
|
+
export interface UnlinkBinariesResult {
|
|
124
|
+
binDir: string;
|
|
125
|
+
removed: Array<{
|
|
126
|
+
name: string;
|
|
127
|
+
path: string;
|
|
128
|
+
existed: boolean;
|
|
129
|
+
}>;
|
|
130
|
+
log: string[];
|
|
131
|
+
}
|
|
132
|
+
export declare function unlinkCliBinaries(home?: string): UnlinkBinariesResult;
|