remote-pi 0.1.3 → 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.
Files changed (99) hide show
  1. package/README.md +160 -40
  2. package/dist/bin/supervisord.d.ts +2 -0
  3. package/dist/bin/supervisord.js +44 -0
  4. package/dist/bin/supervisord.js.map +1 -0
  5. package/dist/config.d.ts +44 -13
  6. package/dist/config.js +61 -22
  7. package/dist/config.js.map +1 -1
  8. package/dist/daemon/client.d.ts +20 -0
  9. package/dist/daemon/client.js +128 -0
  10. package/dist/daemon/client.js.map +1 -0
  11. package/dist/daemon/control_protocol.d.ts +100 -0
  12. package/dist/daemon/control_protocol.js +63 -0
  13. package/dist/daemon/control_protocol.js.map +1 -0
  14. package/dist/daemon/id.d.ts +18 -0
  15. package/dist/daemon/id.js +30 -0
  16. package/dist/daemon/id.js.map +1 -0
  17. package/dist/daemon/install.d.ts +132 -0
  18. package/dist/daemon/install.js +312 -0
  19. package/dist/daemon/install.js.map +1 -0
  20. package/dist/daemon/registry.d.ts +47 -0
  21. package/dist/daemon/registry.js +123 -0
  22. package/dist/daemon/registry.js.map +1 -0
  23. package/dist/daemon/rpc_child.d.ts +76 -0
  24. package/dist/daemon/rpc_child.js +130 -0
  25. package/dist/daemon/rpc_child.js.map +1 -0
  26. package/dist/daemon/supervisor.d.ts +38 -0
  27. package/dist/daemon/supervisor.js +301 -0
  28. package/dist/daemon/supervisor.js.map +1 -0
  29. package/dist/index.d.ts +62 -8
  30. package/dist/index.js +1226 -301
  31. package/dist/index.js.map +1 -1
  32. package/dist/mesh/canonical.d.ts +30 -0
  33. package/dist/mesh/canonical.js +61 -0
  34. package/dist/mesh/canonical.js.map +1 -0
  35. package/dist/mesh/client.d.ts +31 -0
  36. package/dist/mesh/client.js +56 -0
  37. package/dist/mesh/client.js.map +1 -0
  38. package/dist/mesh/encoding.d.ts +36 -0
  39. package/dist/mesh/encoding.js +53 -0
  40. package/dist/mesh/encoding.js.map +1 -0
  41. package/dist/mesh/self_revoke.d.ts +111 -0
  42. package/dist/mesh/self_revoke.js +182 -0
  43. package/dist/mesh/self_revoke.js.map +1 -0
  44. package/dist/mesh/siblings.d.ts +62 -0
  45. package/dist/mesh/siblings.js +95 -0
  46. package/dist/mesh/siblings.js.map +1 -0
  47. package/dist/mesh/types.d.ts +34 -0
  48. package/dist/mesh/types.js +11 -0
  49. package/dist/mesh/types.js.map +1 -0
  50. package/dist/mesh/verify.d.ts +17 -0
  51. package/dist/mesh/verify.js +77 -0
  52. package/dist/mesh/verify.js.map +1 -0
  53. package/dist/pairing/qr.d.ts +16 -5
  54. package/dist/pairing/qr.js +27 -8
  55. package/dist/pairing/qr.js.map +1 -1
  56. package/dist/pairing/storage.d.ts +41 -0
  57. package/dist/pairing/storage.js +158 -21
  58. package/dist/pairing/storage.js.map +1 -1
  59. package/dist/protocol/types.d.ts +23 -0
  60. package/dist/session/broker.d.ts +74 -0
  61. package/dist/session/broker.js +142 -4
  62. package/dist/session/broker.js.map +1 -1
  63. package/dist/session/broker_remote.d.ts +110 -0
  64. package/dist/session/broker_remote.js +397 -0
  65. package/dist/session/broker_remote.js.map +1 -0
  66. package/dist/session/cwd_lock.d.ts +28 -0
  67. package/dist/session/cwd_lock.js +89 -0
  68. package/dist/session/cwd_lock.js.map +1 -0
  69. package/dist/session/global_config.d.ts +9 -0
  70. package/dist/session/global_config.js +9 -0
  71. package/dist/session/global_config.js.map +1 -1
  72. package/dist/session/leader_election.d.ts +16 -0
  73. package/dist/session/leader_election.js +22 -0
  74. package/dist/session/leader_election.js.map +1 -1
  75. package/dist/session/local_config.d.ts +12 -5
  76. package/dist/session/local_config.js +24 -3
  77. package/dist/session/local_config.js.map +1 -1
  78. package/dist/session/peer.d.ts +28 -1
  79. package/dist/session/peer.js +69 -2
  80. package/dist/session/peer.js.map +1 -1
  81. package/dist/session/peer_inventory.d.ts +13 -0
  82. package/dist/session/peer_inventory.js +48 -0
  83. package/dist/session/peer_inventory.js.map +1 -0
  84. package/dist/session/setup_wizard.d.ts +32 -8
  85. package/dist/session/setup_wizard.js +45 -33
  86. package/dist/session/setup_wizard.js.map +1 -1
  87. package/dist/session/tools.d.ts +15 -7
  88. package/dist/session/tools.js +145 -31
  89. package/dist/session/tools.js.map +1 -1
  90. package/dist/transport/pi_forward_client.d.ts +29 -0
  91. package/dist/transport/pi_forward_client.js +62 -0
  92. package/dist/transport/pi_forward_client.js.map +1 -0
  93. package/dist/ui/footer.js +8 -6
  94. package/dist/ui/footer.js.map +1 -1
  95. package/docs/daemon.md +289 -0
  96. package/package.json +8 -2
  97. package/service-templates/launchd.plist.template +35 -0
  98. package/service-templates/systemd.service.template +19 -0
  99. 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;