remote-pi 0.1.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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +384 -0
  3. package/dist/config.d.ts +18 -0
  4. package/dist/config.js +51 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/index.d.ts +77 -0
  7. package/dist/index.js +1285 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/pairing/crypto.d.ts +8 -0
  10. package/dist/pairing/crypto.js +22 -0
  11. package/dist/pairing/crypto.js.map +1 -0
  12. package/dist/pairing/handshake.d.ts +28 -0
  13. package/dist/pairing/handshake.js +113 -0
  14. package/dist/pairing/handshake.js.map +1 -0
  15. package/dist/pairing/noise-sha256.d.ts +16 -0
  16. package/dist/pairing/noise-sha256.js +103 -0
  17. package/dist/pairing/noise-sha256.js.map +1 -0
  18. package/dist/pairing/qr.d.ts +41 -0
  19. package/dist/pairing/qr.js +96 -0
  20. package/dist/pairing/qr.js.map +1 -0
  21. package/dist/pairing/storage.d.ts +10 -0
  22. package/dist/pairing/storage.js +65 -0
  23. package/dist/pairing/storage.js.map +1 -0
  24. package/dist/protocol/codec.d.ts +7 -0
  25. package/dist/protocol/codec.js +46 -0
  26. package/dist/protocol/codec.js.map +1 -0
  27. package/dist/protocol/types.d.ts +119 -0
  28. package/dist/protocol/types.js +2 -0
  29. package/dist/protocol/types.js.map +1 -0
  30. package/dist/rooms.d.ts +9 -0
  31. package/dist/rooms.js +22 -0
  32. package/dist/rooms.js.map +1 -0
  33. package/dist/session/agent_bridge.d.ts +55 -0
  34. package/dist/session/agent_bridge.js +146 -0
  35. package/dist/session/agent_bridge.js.map +1 -0
  36. package/dist/session/broker.d.ts +37 -0
  37. package/dist/session/broker.js +206 -0
  38. package/dist/session/broker.js.map +1 -0
  39. package/dist/session/envelope.d.ts +24 -0
  40. package/dist/session/envelope.js +89 -0
  41. package/dist/session/envelope.js.map +1 -0
  42. package/dist/session/global_config.d.ts +14 -0
  43. package/dist/session/global_config.js +51 -0
  44. package/dist/session/global_config.js.map +1 -0
  45. package/dist/session/leader_election.d.ts +16 -0
  46. package/dist/session/leader_election.js +78 -0
  47. package/dist/session/leader_election.js.map +1 -0
  48. package/dist/session/local_config.d.ts +18 -0
  49. package/dist/session/local_config.js +47 -0
  50. package/dist/session/local_config.js.map +1 -0
  51. package/dist/session/peer.d.ts +80 -0
  52. package/dist/session/peer.js +268 -0
  53. package/dist/session/peer.js.map +1 -0
  54. package/dist/session/setup_wizard.d.ts +32 -0
  55. package/dist/session/setup_wizard.js +60 -0
  56. package/dist/session/setup_wizard.js.map +1 -0
  57. package/dist/session/tool_gate.d.ts +5 -0
  58. package/dist/session/tool_gate.js +11 -0
  59. package/dist/session/tool_gate.js.map +1 -0
  60. package/dist/session/tools.d.ts +16 -0
  61. package/dist/session/tools.js +123 -0
  62. package/dist/session/tools.js.map +1 -0
  63. package/dist/session/wizard.d.ts +13 -0
  64. package/dist/session/wizard.js +20 -0
  65. package/dist/session/wizard.js.map +1 -0
  66. package/dist/settings.d.ts +15 -0
  67. package/dist/settings.js +52 -0
  68. package/dist/settings.js.map +1 -0
  69. package/dist/transport/peer_channel.d.ts +37 -0
  70. package/dist/transport/peer_channel.js +85 -0
  71. package/dist/transport/peer_channel.js.map +1 -0
  72. package/dist/transport/relay_client.d.ts +81 -0
  73. package/dist/transport/relay_client.js +154 -0
  74. package/dist/transport/relay_client.js.map +1 -0
  75. package/dist/ui/footer.d.ts +32 -0
  76. package/dist/ui/footer.js +32 -0
  77. package/dist/ui/footer.js.map +1 -0
  78. package/package.json +77 -0
  79. package/skills/agent-network/SKILL.md +429 -0
@@ -0,0 +1,20 @@
1
+ import { listSessions, sessionHasSock } from "./global_config.js";
2
+ const CREATE_SENTINEL = "━━━ Create new session ━━━";
3
+ export async function joinWizard(ui, defaultName) {
4
+ const sessions = listSessions();
5
+ const liveSessions = sessions.filter(sessionHasSock);
6
+ const options = [...liveSessions, CREATE_SENTINEL];
7
+ const picked = await ui.select(liveSessions.length
8
+ ? "Choose a session to join, or create a new one"
9
+ : "No active sessions. Create one?", options);
10
+ if (!picked)
11
+ return null;
12
+ if (picked === CREATE_SENTINEL) {
13
+ // Caller is expected to follow up with a name prompt via ctx.ui.select
14
+ // (the Pi SDK input dialog). For non-interactive contexts we return the
15
+ // default name.
16
+ return defaultName;
17
+ }
18
+ return picked;
19
+ }
20
+ //# sourceMappingURL=wizard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wizard.js","sourceRoot":"","sources":["../../src/session/wizard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAelE,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAErD,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,EAAY,EACZ,WAAmB;IAEnB,MAAM,QAAQ,GAAG,YAAY,EAAE,CAAC;IAChC,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IACrD,MAAM,OAAO,GAAG,CAAC,GAAG,YAAY,EAAE,eAAe,CAAC,CAAC;IACnD,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,MAAM,CAC5B,YAAY,CAAC,MAAM;QACjB,CAAC,CAAC,+CAA+C;QACjD,CAAC,CAAC,iCAAiC,EACrC,OAAO,CACR,CAAC;IACF,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,MAAM,KAAK,eAAe,EAAE,CAAC;QAC/B,uEAAuE;QACvE,wEAAwE;QACxE,gBAAgB;QAChB,OAAO,WAAW,CAAC;IACrB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,15 @@
1
+ export declare const DEFAULT_RELAY_URL = "ws://localhost:3000";
2
+ export interface Settings {
3
+ relay_url?: string;
4
+ }
5
+ export declare function loadSettings(): Promise<Settings>;
6
+ export declare function saveSettings(settings: Settings): Promise<void>;
7
+ /** Returns saved relay URL, env override, or default. */
8
+ export declare function getRelayUrl(): Promise<string>;
9
+ export declare function setRelayUrl(url: string): Promise<void>;
10
+ export declare function validateRelayUrl(url: string): {
11
+ ok: true;
12
+ } | {
13
+ ok: false;
14
+ reason: string;
15
+ };
@@ -0,0 +1,52 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ const SETTINGS_PATH = join(homedir(), ".pi", "remote", "settings.json");
5
+ export const DEFAULT_RELAY_URL = "ws://localhost:3000";
6
+ export async function loadSettings() {
7
+ try {
8
+ const raw = await readFile(SETTINGS_PATH, "utf8");
9
+ const parsed = JSON.parse(raw);
10
+ if (!parsed || typeof parsed !== "object")
11
+ return {};
12
+ return parsed;
13
+ }
14
+ catch {
15
+ return {};
16
+ }
17
+ }
18
+ export async function saveSettings(settings) {
19
+ await mkdir(dirname(SETTINGS_PATH), { recursive: true });
20
+ await writeFile(SETTINGS_PATH, JSON.stringify(settings, null, 2));
21
+ }
22
+ /** Returns saved relay URL, env override, or default. */
23
+ export async function getRelayUrl() {
24
+ const settings = await loadSettings();
25
+ if (settings.relay_url)
26
+ return settings.relay_url;
27
+ const env = process.env["REMOTE_PI_RELAY"];
28
+ if (env)
29
+ return env;
30
+ return DEFAULT_RELAY_URL;
31
+ }
32
+ export async function setRelayUrl(url) {
33
+ const settings = await loadSettings();
34
+ settings.relay_url = url;
35
+ await saveSettings(settings);
36
+ }
37
+ export function validateRelayUrl(url) {
38
+ if (!url)
39
+ return { ok: false, reason: "URL is empty" };
40
+ let parsed;
41
+ try {
42
+ parsed = new URL(url);
43
+ }
44
+ catch {
45
+ return { ok: false, reason: "malformed URL" };
46
+ }
47
+ if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
48
+ return { ok: false, reason: `expected ws:// or wss://, got ${parsed.protocol}` };
49
+ }
50
+ return { ok: true };
51
+ }
52
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;AAExE,MAAM,CAAC,MAAM,iBAAiB,GAAG,qBAAqB,CAAC;AAMvD,MAAM,CAAC,KAAK,UAAU,YAAY;IAChC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAY,CAAC;QAC1C,IAAI,CAAC,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QACrD,OAAO,MAAkB,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAkB;IACnD,MAAM,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACzD,MAAM,SAAS,CAAC,aAAa,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,yDAAyD;AACzD,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,IAAI,QAAQ,CAAC,SAAS;QAAE,OAAO,QAAQ,CAAC,SAAS,CAAC;IAClD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC3C,IAAI,GAAG;QAAE,OAAO,GAAG,CAAC;IACpB,OAAO,iBAAiB,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW;IAC3C,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IACtC,QAAQ,CAAC,SAAS,GAAG,GAAG,CAAC;IACzB,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;IACvD,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAChD,CAAC;IACD,IAAI,MAAM,CAAC,QAAQ,KAAK,KAAK,IAAI,MAAM,CAAC,QAAQ,KAAK,MAAM,EAAE,CAAC;QAC5D,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,iCAAiC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC;IACnF,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;AACtB,CAAC"}
@@ -0,0 +1,37 @@
1
+ import type { ClientMessage, ServerMessage } from "../protocol/types.js";
2
+ import type { RelayClient } from "./relay_client.js";
3
+ /** Sink for ServerMessage outbound to the remote app. */
4
+ export interface PeerChannel {
5
+ send(msg: ServerMessage): void;
6
+ }
7
+ /**
8
+ * Plaintext PeerChannel backed by a RelayClient WebSocket.
9
+ *
10
+ * Usage (after pair_request handshake completes):
11
+ * const channel = new PlainPeerChannel(relay, appPeerId, myRoomId, onMsg)
12
+ * channel.send(serverMessage) // base64-encodes JSON, routes via relay
13
+ * // incoming relay messages destined for appPeerId are auto-decoded
14
+ * // and delivered via onMessage callback
15
+ *
16
+ * `myRoomId` is the *local* Pi's room id — sent on every outbound envelope
17
+ * so the app can correlate which Pi sent it (multi-pi support, plano 17).
18
+ */
19
+ export declare class PlainPeerChannel implements PeerChannel {
20
+ private readonly relay;
21
+ private readonly remotePeerId;
22
+ private readonly onMessage;
23
+ private readonly _unsubscribe;
24
+ constructor(relay: RelayClient, remotePeerId: string,
25
+ /**
26
+ * This Pi's room id. Currently NOT injected in the outer envelope
27
+ * (defensive — relay/app not yet ready). Kept in the constructor for
28
+ * forward-compat so callers don't need to change again when we re-enable.
29
+ */
30
+ myRoomId: string | undefined, onMessage: (msg: ClientMessage) => void,
31
+ /** Called when this specific peer connection is considered lost. */
32
+ _onDisconnect?: () => void);
33
+ send(msg: ServerMessage): void;
34
+ /** Detaches from relay (does not close the relay itself). */
35
+ detach(): void;
36
+ private _onLine;
37
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Plaintext PeerChannel backed by a RelayClient WebSocket.
3
+ *
4
+ * Usage (after pair_request handshake completes):
5
+ * const channel = new PlainPeerChannel(relay, appPeerId, myRoomId, onMsg)
6
+ * channel.send(serverMessage) // base64-encodes JSON, routes via relay
7
+ * // incoming relay messages destined for appPeerId are auto-decoded
8
+ * // and delivered via onMessage callback
9
+ *
10
+ * `myRoomId` is the *local* Pi's room id — sent on every outbound envelope
11
+ * so the app can correlate which Pi sent it (multi-pi support, plano 17).
12
+ */
13
+ export class PlainPeerChannel {
14
+ relay;
15
+ remotePeerId;
16
+ onMessage;
17
+ _unsubscribe;
18
+ constructor(relay, remotePeerId,
19
+ /**
20
+ * This Pi's room id. Currently NOT injected in the outer envelope
21
+ * (defensive — relay/app not yet ready). Kept in the constructor for
22
+ * forward-compat so callers don't need to change again when we re-enable.
23
+ */
24
+ myRoomId, onMessage,
25
+ /** Called when this specific peer connection is considered lost. */
26
+ _onDisconnect) {
27
+ this.relay = relay;
28
+ this.remotePeerId = remotePeerId;
29
+ this.onMessage = onMessage;
30
+ const listener = (line) => this._onLine(line);
31
+ relay.on("message", listener);
32
+ this._unsubscribe = () => relay.off("message", listener);
33
+ void _onDisconnect;
34
+ void myRoomId; // intentionally unused — see send() comment
35
+ }
36
+ // ── PeerChannel interface ──────────────────────────────────────────────────
37
+ send(msg) {
38
+ const ct = Buffer.from(JSON.stringify(msg)).toString("base64");
39
+ // NOTE: `room` removed from the outer envelope until relay (W1.A) + app
40
+ // (W1.C) accept the field. Multi-Pi multiplexing already works via
41
+ // `room_id`/`room_meta` in the WS-level `hello` — outer routing stays by
42
+ // `peer` alone. Re-add the field once downstream is ready.
43
+ const outer = { peer: this.remotePeerId, ct };
44
+ this.relay.send(JSON.stringify(outer));
45
+ }
46
+ /** Detaches from relay (does not close the relay itself). */
47
+ detach() {
48
+ this._unsubscribe();
49
+ }
50
+ // ── Incoming line from relay ────────────────────────────────────────────────
51
+ _onLine(line) {
52
+ let outer;
53
+ try {
54
+ outer = JSON.parse(line);
55
+ }
56
+ catch {
57
+ return; // malformed line
58
+ }
59
+ if (outer.peer !== this.remotePeerId)
60
+ return;
61
+ if (!outer.ct)
62
+ return;
63
+ let plaintext;
64
+ try {
65
+ plaintext = Buffer.from(outer.ct, "base64").toString("utf8");
66
+ }
67
+ catch {
68
+ return;
69
+ }
70
+ let msg;
71
+ try {
72
+ msg = JSON.parse(plaintext);
73
+ }
74
+ catch {
75
+ return;
76
+ }
77
+ if (!msg ||
78
+ typeof msg !== "object" ||
79
+ typeof msg.type !== "string") {
80
+ return;
81
+ }
82
+ this.onMessage(msg);
83
+ }
84
+ }
85
+ //# sourceMappingURL=peer_channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"peer_channel.js","sourceRoot":"","sources":["../../src/transport/peer_channel.ts"],"names":[],"mappings":"AAyBA;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,gBAAgB;IAIR;IACA;IAOA;IAXF,YAAY,CAAa;IAE1C,YACmB,KAAkB,EAClB,YAAoB;IACrC;;;;OAIG;IACH,QAA4B,EACX,SAAuC;IACxD,oEAAoE;IACpE,aAA0B;QAVT,UAAK,GAAL,KAAK,CAAa;QAClB,iBAAY,GAAZ,YAAY,CAAQ;QAOpB,cAAS,GAAT,SAAS,CAA8B;QAIxD,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACtD,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAC9B,IAAI,CAAC,YAAY,GAAG,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QACzD,KAAK,aAAa,CAAC;QACnB,KAAK,QAAQ,CAAC,CAAE,4CAA4C;IAC9D,CAAC;IAED,8EAA8E;IAE9E,IAAI,CAAC,GAAkB;QACrB,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAC/D,wEAAwE;QACxE,mEAAmE;QACnE,yEAAyE;QACzE,2DAA2D;QAC3D,MAAM,KAAK,GAAkB,EAAE,IAAI,EAAE,IAAI,CAAC,YAAY,EAAE,EAAE,EAAE,CAAC;QAC7D,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;IACzC,CAAC;IAED,6DAA6D;IAC7D,MAAM;QACJ,IAAI,CAAC,YAAY,EAAE,CAAC;IACtB,CAAC;IAED,+EAA+E;IAEvE,OAAO,CAAC,IAAY;QAC1B,IAAI,KAAoB,CAAC;QACzB,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,CAAC;QAC5C,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,iBAAiB;QAC3B,CAAC;QAED,IAAI,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,YAAY;YAAE,OAAO;QAC7C,IAAI,CAAC,KAAK,CAAC,EAAE;YAAE,OAAO;QAEtB,IAAI,SAAiB,CAAC;QACtB,IAAI,CAAC;YACH,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAC/D,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QAED,IAAI,GAAY,CAAC;QACjB,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QAED,IACE,CAAC,GAAG;YACJ,OAAO,GAAG,KAAK,QAAQ;YACvB,OAAQ,GAA+B,CAAC,IAAI,KAAK,QAAQ,EACzD,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,GAAoB,CAAC,CAAC;IACvC,CAAC;CACF"}
@@ -0,0 +1,81 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { Ed25519Keypair } from "../pairing/crypto.js";
3
+ export interface RoomMeta {
4
+ name: string;
5
+ cwd: string;
6
+ /** Friendly model name (e.g. "claude-sonnet-4.5"). Optional — pi-ext sends
7
+ * when `ExtensionContext.model` is available; relay/app tolerate absence. */
8
+ model?: string;
9
+ }
10
+ /** Control frame sent to relay (not routed to app peer). */
11
+ export interface RoomMetaUpdateFrame {
12
+ type: "room_meta_update";
13
+ room_id: string;
14
+ meta: {
15
+ model?: string;
16
+ };
17
+ }
18
+ export interface ConnectOptions {
19
+ roomId?: string;
20
+ roomMeta?: RoomMeta;
21
+ }
22
+ /** Relay rejected hello because another peer already holds (pubkey, room_id). */
23
+ export declare class RoomAlreadyOpenError extends Error {
24
+ readonly roomId: string | undefined;
25
+ constructor(roomId: string | undefined);
26
+ }
27
+ export interface RelayClientEvents {
28
+ /** A single JSONL line delivered by the relay (outer envelope). */
29
+ message: [line: string];
30
+ close: [];
31
+ error: [err: Error];
32
+ }
33
+ /**
34
+ * Thin WebSocket client for the Remote Pi relay.
35
+ *
36
+ * Lifecycle:
37
+ * const relay = new RelayClient(url, ed25519Keypair)
38
+ * await relay.connect() // opens WS + runs Ed25519 challenge-response
39
+ * relay.on("message", line => …) // outer envelopes: { peer, ct }
40
+ * relay.send(jsonLine) // write to relay
41
+ * relay.close()
42
+ *
43
+ * Auth sequence (pairing.md §Challenge-response):
44
+ * → { type:"hello", pubkey: "<Ed25519 pubkey base64>" }
45
+ * ← { type:"challenge", nonce: "<32 bytes base64>" }
46
+ * → { type:"auth", sig: "<Ed25519 sig base64>" }
47
+ */
48
+ export declare class RelayClient extends EventEmitter {
49
+ private readonly url;
50
+ private readonly keypair;
51
+ private ws;
52
+ constructor(url: string, keypair: Ed25519Keypair);
53
+ /**
54
+ * Connects and completes Ed25519 auth. Resolves when relay is ready.
55
+ *
56
+ * `options.roomId` (12-char id derived from cwd, see `src/rooms.ts`) is
57
+ * included in the hello so the relay can multiplex N concurrent peers
58
+ * with the same Ed25519 pubkey but different rooms (one pi-ext per cwd).
59
+ * Omitting `roomId` is backward-compat with old relays (treated as the
60
+ * default "main" room server-side).
61
+ *
62
+ * Throws `RoomAlreadyOpenError` if the relay rejects the hello because
63
+ * another peer already holds the (pubkey, room_id) tuple. Caller (e.g.
64
+ * `_cmdStart`) maps that to a clearer UI message.
65
+ */
66
+ connect(options?: ConnectOptions): Promise<void>;
67
+ /** Sends a raw line to the relay (caller is responsible for framing). */
68
+ send(line: string): void;
69
+ /**
70
+ * Sends a control frame to the relay (not routed to app peer). Used for
71
+ * out-of-band metadata updates like `room_meta_update`. Silently no-ops if
72
+ * the WS isn't open (best-effort: control frames are observational; we
73
+ * don't want them throwing inside SDK event callbacks).
74
+ */
75
+ sendControl(frame: object): void;
76
+ close(): void;
77
+ private _authenticate;
78
+ /** Waits for the next single WS message with a timeout. */
79
+ private _nextMsg;
80
+ private _rawSend;
81
+ }
@@ -0,0 +1,154 @@
1
+ import { EventEmitter } from "node:events";
2
+ import WebSocket from "ws";
3
+ import { ed25519Sign } from "../pairing/crypto.js";
4
+ const AUTH_TIMEOUT_MS = 5_000;
5
+ /** Relay rejected hello because another peer already holds (pubkey, room_id). */
6
+ export class RoomAlreadyOpenError extends Error {
7
+ roomId;
8
+ constructor(roomId) {
9
+ super(roomId
10
+ ? `relay rejected hello: room ${roomId} already open for this peer`
11
+ : "relay rejected hello: peer already connected");
12
+ this.roomId = roomId;
13
+ this.name = "RoomAlreadyOpenError";
14
+ }
15
+ }
16
+ /**
17
+ * Thin WebSocket client for the Remote Pi relay.
18
+ *
19
+ * Lifecycle:
20
+ * const relay = new RelayClient(url, ed25519Keypair)
21
+ * await relay.connect() // opens WS + runs Ed25519 challenge-response
22
+ * relay.on("message", line => …) // outer envelopes: { peer, ct }
23
+ * relay.send(jsonLine) // write to relay
24
+ * relay.close()
25
+ *
26
+ * Auth sequence (pairing.md §Challenge-response):
27
+ * → { type:"hello", pubkey: "<Ed25519 pubkey base64>" }
28
+ * ← { type:"challenge", nonce: "<32 bytes base64>" }
29
+ * → { type:"auth", sig: "<Ed25519 sig base64>" }
30
+ */
31
+ export class RelayClient extends EventEmitter {
32
+ url;
33
+ keypair;
34
+ ws = null;
35
+ constructor(url, keypair) {
36
+ super();
37
+ this.url = url;
38
+ this.keypair = keypair;
39
+ }
40
+ // ── Public API ──────────────────────────────────────────────────────────────
41
+ /**
42
+ * Connects and completes Ed25519 auth. Resolves when relay is ready.
43
+ *
44
+ * `options.roomId` (12-char id derived from cwd, see `src/rooms.ts`) is
45
+ * included in the hello so the relay can multiplex N concurrent peers
46
+ * with the same Ed25519 pubkey but different rooms (one pi-ext per cwd).
47
+ * Omitting `roomId` is backward-compat with old relays (treated as the
48
+ * default "main" room server-side).
49
+ *
50
+ * Throws `RoomAlreadyOpenError` if the relay rejects the hello because
51
+ * another peer already holds the (pubkey, room_id) tuple. Caller (e.g.
52
+ * `_cmdStart`) maps that to a clearer UI message.
53
+ */
54
+ async connect(options = {}) {
55
+ return new Promise((resolve, reject) => {
56
+ const ws = new WebSocket(this.url);
57
+ this.ws = ws;
58
+ ws.on("error", (err) => reject(err));
59
+ ws.on("open", async () => {
60
+ try {
61
+ await this._authenticate(ws, options);
62
+ // Auth done — wire persistent message handler
63
+ ws.on("message", (raw) => {
64
+ const text = Buffer.isBuffer(raw) ? raw.toString() : String(raw);
65
+ for (const line of text.split("\n")) {
66
+ const trimmed = line.trim();
67
+ if (trimmed)
68
+ this.emit("message", trimmed);
69
+ }
70
+ });
71
+ ws.on("close", () => this.emit("close"));
72
+ resolve();
73
+ }
74
+ catch (err) {
75
+ ws.terminate();
76
+ reject(err);
77
+ }
78
+ });
79
+ });
80
+ }
81
+ /** Sends a raw line to the relay (caller is responsible for framing). */
82
+ send(line) {
83
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
84
+ throw new Error("relay: not connected");
85
+ }
86
+ this.ws.send(line);
87
+ }
88
+ /**
89
+ * Sends a control frame to the relay (not routed to app peer). Used for
90
+ * out-of-band metadata updates like `room_meta_update`. Silently no-ops if
91
+ * the WS isn't open (best-effort: control frames are observational; we
92
+ * don't want them throwing inside SDK event callbacks).
93
+ */
94
+ sendControl(frame) {
95
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
96
+ return;
97
+ this.ws.send(JSON.stringify(frame));
98
+ }
99
+ close() {
100
+ this.ws?.close();
101
+ this.ws = null;
102
+ }
103
+ // ── Auth ────────────────────────────────────────────────────────────────────
104
+ async _authenticate(ws, opts) {
105
+ const pubkeyB64 = Buffer.from(this.keypair.publicKey).toString("base64");
106
+ const hello = { type: "hello", pubkey: pubkeyB64 };
107
+ if (opts.roomId)
108
+ hello.room_id = opts.roomId;
109
+ if (opts.roomMeta)
110
+ hello.room_meta = opts.roomMeta;
111
+ this._rawSend(ws, JSON.stringify(hello));
112
+ const challengeRaw = await this._nextMsg(ws);
113
+ let challenge;
114
+ try {
115
+ challenge = JSON.parse(challengeRaw);
116
+ }
117
+ catch {
118
+ throw new Error(`relay auth_failed: not JSON: ${challengeRaw}`);
119
+ }
120
+ if (challenge.type === "error") {
121
+ const code = challenge.code ?? "";
122
+ if (code === "room_already_open") {
123
+ throw new RoomAlreadyOpenError(opts.roomId);
124
+ }
125
+ throw new Error(`relay rejected hello: ${code || challenge.message || "unknown"}`);
126
+ }
127
+ if (challenge.type !== "challenge" || !challenge.nonce) {
128
+ throw new Error(`relay auth_failed: expected challenge, got ${challengeRaw}`);
129
+ }
130
+ const nonce = Buffer.from(challenge.nonce, "base64");
131
+ const sig = ed25519Sign(this.keypair.secretKey, nonce);
132
+ const auth = {
133
+ type: "auth",
134
+ sig: Buffer.from(sig).toString("base64"),
135
+ };
136
+ this._rawSend(ws, JSON.stringify(auth));
137
+ // Relay does not send an explicit "ok" — it simply starts routing.
138
+ // Proceed immediately after sending auth.
139
+ }
140
+ /** Waits for the next single WS message with a timeout. */
141
+ _nextMsg(ws) {
142
+ return new Promise((resolve, reject) => {
143
+ const timer = setTimeout(() => reject(new Error("relay auth timeout")), AUTH_TIMEOUT_MS);
144
+ ws.once("message", (raw) => {
145
+ clearTimeout(timer);
146
+ resolve(Buffer.isBuffer(raw) ? raw.toString() : String(raw));
147
+ });
148
+ });
149
+ }
150
+ _rawSend(ws, data) {
151
+ ws.send(data);
152
+ }
153
+ }
154
+ //# sourceMappingURL=relay_client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relay_client.js","sourceRoot":"","sources":["../../src/transport/relay_client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Footer renderer for the Pi TUI. Three status slots + window title.
3
+ *
4
+ * Slot keys (intentionally namespaced so other extensions don't collide):
5
+ * - remote-pi:session — current local session + peer count
6
+ * - remote-pi:relay — relay state (off / on / paired)
7
+ * - remote-pi:peer-active — active mobile device, if paired
8
+ */
9
+ export interface FooterContext {
10
+ ui: {
11
+ setStatus(key: string, value: string | undefined): void;
12
+ setTitle(title: string): void;
13
+ };
14
+ }
15
+ export interface FooterState {
16
+ session?: string;
17
+ peerCount?: number;
18
+ relayOn?: boolean;
19
+ /** Active device session right now (drives the 📱 slot).
20
+ * Independent from `hasPairings` — a device may be paired globally
21
+ * in peers.json without being actively connected to THIS Pi process. */
22
+ devicePaired?: string;
23
+ /** At least one device has been paired with this machine before
24
+ * (peers.json is non-empty). Drives the 🟢/🟡 icon on the relay slot:
25
+ * 🟢 when true (ready — devices can connect), 🟡 when false (first
26
+ * pairing needed). Pairing is per-machine (global), not per-process. */
27
+ hasPairings?: boolean;
28
+ /** Assigned agent name in the current session. Becomes the title prefix
29
+ * (e.g. "backend · foo · relay") when set. Falls back to "Pi" otherwise. */
30
+ agentName?: string;
31
+ }
32
+ export declare function updateFooter(ctx: FooterContext, state: FooterState): void;
@@ -0,0 +1,32 @@
1
+ const K_SESSION = "remote-pi:session";
2
+ const K_RELAY = "remote-pi:relay";
3
+ const K_PEER = "remote-pi:peer-active";
4
+ export function updateFooter(ctx, state) {
5
+ if (state.session) {
6
+ const count = state.peerCount ?? 0;
7
+ ctx.ui.setStatus(K_SESSION, `📡 ${state.session} (${count})`);
8
+ }
9
+ else {
10
+ ctx.ui.setStatus(K_SESSION, undefined);
11
+ }
12
+ if (state.relayOn) {
13
+ ctx.ui.setStatus(K_RELAY, state.hasPairings ? "🟢 relay" : "🟡 relay waiting for pairing");
14
+ }
15
+ else {
16
+ ctx.ui.setStatus(K_RELAY, undefined);
17
+ }
18
+ if (state.devicePaired) {
19
+ ctx.ui.setStatus(K_PEER, `📱 ${state.devicePaired}`);
20
+ }
21
+ else {
22
+ ctx.ui.setStatus(K_PEER, undefined);
23
+ }
24
+ const titleParts = [];
25
+ if (state.session)
26
+ titleParts.push(state.session);
27
+ if (state.relayOn)
28
+ titleParts.push("relay");
29
+ const prefix = state.agentName?.trim() || "Pi";
30
+ ctx.ui.setTitle(titleParts.length ? `${prefix} · ${titleParts.join(" · ")}` : prefix);
31
+ }
32
+ //# sourceMappingURL=footer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"footer.js","sourceRoot":"","sources":["../../src/ui/footer.ts"],"names":[],"mappings":"AAiCA,MAAM,SAAS,GAAG,mBAAmB,CAAC;AACtC,MAAM,OAAO,GAAG,iBAAiB,CAAC;AAClC,MAAM,MAAM,GAAG,uBAAuB,CAAC;AAEvC,MAAM,UAAU,YAAY,CAAC,GAAkB,EAAE,KAAkB;IACjE,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC;QACnC,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,MAAM,KAAK,CAAC,OAAO,KAAK,KAAK,GAAG,CAAC,CAAC;IAChE,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;IACzC,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAClB,GAAG,CAAC,EAAE,CAAC,SAAS,CACd,OAAO,EACP,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,8BAA8B,CAChE,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IACvC,CAAC;IAED,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QACvB,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;IACvD,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACtC,CAAC;IAED,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,IAAI,KAAK,CAAC,OAAO;QAAE,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClD,IAAI,KAAK,CAAC,OAAO;QAAE,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;IAC/C,GAAG,CAAC,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,MAAM,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AACxF,CAAC"}
package/package.json ADDED
@@ -0,0 +1,77 @@
1
+ {
2
+ "name": "remote-pi",
3
+ "version": "0.1.0",
4
+ "description": "Mobile remote control and local agent mesh for the Pi coding agent. Pair your phone via QR over a relay, watch tool calls in real time, run multi-Pi sessions over a local Unix Domain Socket broker, and let agents talk to each other through structured request/reply.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "typecheck": "tsc --noEmit",
11
+ "dev": "tsx src/index.ts",
12
+ "test": "vitest run",
13
+ "pi": "tsc && REMOTE_PI_RELAY=ws://192.168.31.240:3000 pi --no-extensions -e $(pwd)/dist/index.js",
14
+ "prepublishOnly": "pnpm typecheck && pnpm test && pnpm build"
15
+ },
16
+ "keywords": [
17
+ "pi",
18
+ "pi-package",
19
+ "pi-extension",
20
+ "pi-coding-agent",
21
+ "remote-control",
22
+ "mobile",
23
+ "coding-agent",
24
+ "agent-mesh",
25
+ "agent-network",
26
+ "uds",
27
+ "broker",
28
+ "qr-pairing",
29
+ "websocket"
30
+ ],
31
+ "pi": {
32
+ "extensions": ["./dist"]
33
+ },
34
+ "author": "Jacob Moura <jacobmoura7@github>",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/jacobmoura7/remote-pi.git",
39
+ "directory": "pi-extension"
40
+ },
41
+ "homepage": "https://github.com/jacobmoura7/remote-pi#readme",
42
+ "bugs": {
43
+ "url": "https://github.com/jacobmoura7/remote-pi/issues"
44
+ },
45
+ "files": [
46
+ "dist",
47
+ "skills",
48
+ "README.md",
49
+ "LICENSE"
50
+ ],
51
+ "engines": {
52
+ "node": ">=20.0.0"
53
+ },
54
+ "pnpm": {
55
+ "onlyBuiltDependencies": [
56
+ "@google/genai",
57
+ "esbuild",
58
+ "koffi",
59
+ "protobufjs"
60
+ ]
61
+ },
62
+ "devDependencies": {
63
+ "@types/node": "^25.8.0",
64
+ "@types/ws": "^8.18.1",
65
+ "tsx": "^4.22.0",
66
+ "typescript": "^6.0.3",
67
+ "vitest": "^4.1.6"
68
+ },
69
+ "dependencies": {
70
+ "@mariozechner/pi-coding-agent": "^0.73.1",
71
+ "@noble/ed25519": "^3.1.0",
72
+ "keytar": "^7.9.0",
73
+ "qrcode-terminal": "^0.12.0",
74
+ "typebox": "^1.1.38",
75
+ "ws": "^8.20.1"
76
+ }
77
+ }