@earendil-works/pi-env 0.0.1 → 1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## [1.1.0] - 2026-10-07
4
+
5
+ ## [1.0.4] - 2026-10-05
6
+
7
+ ### Added
8
+
9
+ - Initial release of `@earendil-works/pi-env`: `RemoteExecutionEnv`, a Durable `ExecutionEnv` on another machine, and `Connection` to the `pi-env` daemon that runs there. Results match `NodeExecutionEnv` on that machine on Linux, macOS, Android and Windows. The daemon watches files natively (polling on Windows and network file systems), honors output windows so dropped command output is not transferred, and keeps control replies ahead of bulk output; watchers report `overflow` and resume after a lost connection.
10
+ - SSH bootstrap: `connectSsh` detects the remote system, deploys the daemon this package ships for it (named by its SHA-256 and verified before every start), and connects; `sshConnection` does the same lazily at the first operation, so building an environment never needs the network without forwarding, shared connections or configured commands; `loginShell` starts it through the user's login shell, and Termux hosts get setup warnings. `scanHostKey`, `acceptHostKey` and `forgetHostKey` manage trusted host keys; a changed key is never accepted without removing the old one.
package/README.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # @earendil-works/pi-env
2
2
 
3
- Placeholder release that reserves the package name. The first real release ships with the next pi release.
3
+ Remote execution environments for [Pi Durable](../durable): an agent's tools run on another machine, usually over SSH,
4
+ while the Durable worker, its storage and credentials stay local.
4
5
 
5
- See https://github.com/earendil-works/pi/tree/main/packages/env
6
+ - `pi-env` (`daemon/`): a small Rust program that runs on the remote machine. It speaks a framed protocol on stdin and
7
+ stdout ([docs/protocol.md](docs/protocol.md)) and performs file operations and commands there.
8
+ - `RemoteExecutionEnv`: a Durable `ExecutionEnv` that talks to the daemon through a `Connection`. Its results match
9
+ `NodeExecutionEnv` running on the remote machine; only error messages may differ
10
+ ([docs/semantics.md](docs/semantics.md)).
11
+
12
+ ```ts
13
+ import { acceptHostKey, connectSsh, RemoteExecutionEnv, scanHostKey, sshConnection } from "@earendil-works/pi-env";
14
+
15
+ const target = { host: "gpu-box", knownHostsFile: "/data/ssh/known_hosts", hostKeyAlias: "pi-env-gpu" };
16
+ // Once: show the host's key fingerprint to the owner, who compares it out of band and accepts it.
17
+ const { lines, fingerprints } = await scanHostKey(target);
18
+ await acceptHostKey(target, lines);
19
+
20
+ // Detects the remote system, deploys the daemon if missing (named by its SHA-256 and verified before every start),
21
+ // and returns a connection that starts it over ssh.
22
+ const { connection } = await connectSsh(target);
23
+ const env = new RemoteExecutionEnv({ connection, id: "pi-env:gpu", cwd: "/home/me/project" });
24
+
25
+ // Or lazily: nothing happens until the first operation, which detects, deploys and connects. A failure (no network,
26
+ // untrusted host key) is that operation's error, and the next operation tries again.
27
+ const lazy = sshConnection(target);
28
+ const lazyEnv = new RemoteExecutionEnv({ connection: lazy.connection, id: "pi-env:gpu", cwd: "/home/me/project" });
29
+ ```
30
+
31
+ The package ships the daemon for every supported remote system in `bin/`. `ssh` runs with `BatchMode`, strict host-key
32
+ checking against the application's own known-hosts file under a fixed alias, no forwarding of any kind, no shared
33
+ connections or configured commands, and without forwarding the local locale. On Windows, detection and deployment go
34
+ through PowerShell, and the daemon starts through the server's default shell (cmd.exe or PowerShell).
35
+
36
+ Supported remote systems: Linux, macOS, Android (Termux) and Windows, on x86-64 and arm64. On Windows, string commands
37
+ run through Git Bash as `NodeExecutionEnv` runs them there; argv commands run directly.
38
+
39
+ ## Development
40
+
41
+ `npm run build:daemon` builds the daemon with Cargo; the tests talk to `daemon/target/debug/pi-env` over a pipe, or to
42
+ the binary named by `PI_ENV_DAEMON` (CI tests the release builds this way). They run Durable's env conformance suite
43
+ against `RemoteExecutionEnv` and compare random operation sequences and Durable's tools against `NodeExecutionEnv` on
44
+ the same machine. `test/ssh-external.test.ts` runs the suite over a real SSH server when `PI_ENV_SSH_HOST` is set.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,74 @@
1
+ export type Json = Record<string, unknown>;
2
+ /** A failure reported by the daemon, with its Node-style code. */
3
+ export declare class RemoteError extends Error {
4
+ readonly code: string;
5
+ readonly path?: string;
6
+ readonly fields: Json;
7
+ constructor(fields: Json);
8
+ }
9
+ /** What `hello` reports about the remote machine. */
10
+ export interface RemoteInfo {
11
+ protocol: number;
12
+ version: string;
13
+ os: string;
14
+ arch: string;
15
+ home: string;
16
+ tmpdir: string;
17
+ /** Path separator: `/`, or `\\` on Windows. */
18
+ separator: string;
19
+ /** The daemon's working directory, where Node's `path.resolve` would fall back to `process.cwd()`. */
20
+ cwd: string;
21
+ /** Windows: per-drive working directories (`C:` → `C:\\work`) of the `=C:` variables. */
22
+ driveCwds: Record<string, string>;
23
+ pid: number;
24
+ }
25
+ export interface ConnectionOptions {
26
+ /**
27
+ * Command that starts the daemon, before its `serve --token <hex>` arguments, e.g. `["ssh", "-T", "--", "host",
28
+ * "~/.pi/mobile/tools/pi-env"]`. A function computes it at each start (detecting and deploying first, for example);
29
+ * its failure fails that start, and the next request tries again.
30
+ */
31
+ command: readonly string[] | (() => Promise<readonly string[]>);
32
+ /** Receives the daemon's and the transport's diagnostic output. */
33
+ onLog?: (text: string) => void;
34
+ }
35
+ export interface Reply {
36
+ json: Json;
37
+ payload: Uint8Array;
38
+ /** The daemon session that answered; handles from it are only valid while it lives. */
39
+ session: number;
40
+ }
41
+ export interface RequestOptions {
42
+ payload?: Uint8Array;
43
+ /** Aborting sends `cancel`; the request still settles with the daemon's result or error. */
44
+ signal?: AbortSignal;
45
+ /** Progress events of the request, such as `exec` output. */
46
+ onEvent?: (json: Json, payload: Uint8Array) => void;
47
+ /** The request's id and session, for `kill`. */
48
+ onStart?: (id: number, session: number) => void;
49
+ /**
50
+ * Only run in this daemon session, for requests on handles it opened. After the connection was lost and started
51
+ * again, such requests fail instead of reaching a daemon that never opened the handle.
52
+ */
53
+ session?: number;
54
+ }
55
+ /** Whether an error means the connection was lost, not that the daemon refused the request. */
56
+ export declare function isConnectionLost(error: unknown): boolean;
57
+ /**
58
+ * One connection to a pi-env daemon: started lazily on the first request and started again after it is lost. Requests
59
+ * in flight when it is lost fail with code `unknown`; for mutations their outcome is then unknown.
60
+ */
61
+ export declare class Connection {
62
+ #private;
63
+ constructor(options: ConnectionOptions);
64
+ /** Connect if needed and return what the daemon reported about its machine. */
65
+ info(): Promise<RemoteInfo>;
66
+ /** Connect if needed and return the live session's id. */
67
+ session(): Promise<number>;
68
+ request(op: string, json: Json, options?: RequestOptions): Promise<Reply>;
69
+ /** Stop the daemon; it kills everything it started. */
70
+ close(): void;
71
+ /** Kill one `exec` without aborting it. */
72
+ kill(id: number, session: number): void;
73
+ }
74
+ //# sourceMappingURL=connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE3C,kEAAkE;AAClE,qBAAa,WAAY,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC;IAEtB,YAAY,MAAM,EAAE,IAAI,EAMvB;CACD;AAED,qDAAqD;AACrD,MAAM,WAAW,UAAU;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,sGAAsG;IACtG,GAAG,EAAE,MAAM,CAAC;IACZ,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,GAAG,EAAE,MAAM,CAAC;CACZ;AAED,MAAM,WAAW,iBAAiB;IACjC;;;;OAIG;IACH,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,CAAC,MAAM,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IAChE,mEAAmE;IACnE,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,KAAK;IACrB,IAAI,EAAE,IAAI,CAAC;IACX,OAAO,EAAE,UAAU,CAAC;IACpB,uFAAuF;IACvF,OAAO,EAAE,MAAM,CAAC;CAChB;AAQD,MAAM,WAAW,cAAc;IAC9B,OAAO,CAAC,EAAE,UAAU,CAAC;IACrB,4FAA4F;IAC5F,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,6DAA6D;IAC7D,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;IACpD,gDAAgD;IAChD,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;CACjB;AAsBD,+FAA+F;AAC/F,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAExD;AAiDD;;;GAGG;AACH,qBAAa,UAAU;;IAQtB,YAAY,OAAO,EAAE,iBAAiB,EAErC;IAED,+EAA+E;IACzE,IAAI,IAAI,OAAO,CAAC,UAAU,CAAC,CAEhC;IAED,0DAA0D;IACpD,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAE/B;IAEK,OAAO,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,KAAK,CAAC,CAUlF;IAED,uDAAuD;IACvD,KAAK,IAAI,IAAI,CAGZ;IAED,2CAA2C;IAC3C,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,CAGtC;CA4KD"}
@@ -0,0 +1,321 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomBytes } from "node:crypto";
3
+ /** Frame types (docs/protocol.md). */
4
+ const REQUEST = 1;
5
+ const RESULT = 2;
6
+ const ERROR = 3;
7
+ const EVENT = 4;
8
+ const CANCEL = 5;
9
+ const PING = 6;
10
+ const MAX_FRAME = 16 * 1024 * 1024;
11
+ const PING_INTERVAL_MS = 5000;
12
+ /** The daemon pings every five seconds; this much silence means the connection is gone. */
13
+ const SILENCE_LIMIT_MS = 30_000;
14
+ /** How long starting the daemon and its `hello` may take. */
15
+ const START_TIMEOUT_MS = 60_000;
16
+ /** A failure reported by the daemon, with its Node-style code. */
17
+ export class RemoteError extends Error {
18
+ code;
19
+ path;
20
+ fields;
21
+ constructor(fields) {
22
+ super(typeof fields.message === "string" ? fields.message : "Remote operation failed");
23
+ this.name = "RemoteError";
24
+ this.code = typeof fields.code === "string" ? fields.code : "unknown";
25
+ if (typeof fields.path === "string")
26
+ this.path = fields.path;
27
+ this.fields = fields;
28
+ }
29
+ }
30
+ /** Lone surrogates become U+FFFD, as Node encodes strings for system calls. */
31
+ function wellFormed(_key, value) {
32
+ return typeof value === "string" ? value.toWellFormed() : value;
33
+ }
34
+ function frame(type, id, json, payload = new Uint8Array(0)) {
35
+ const body = Buffer.from(JSON.stringify(json, wellFormed), "utf8");
36
+ const header = Buffer.alloc(13);
37
+ header.writeUInt32BE(9 + body.length + payload.length, 0);
38
+ header.writeUInt8(type, 4);
39
+ header.writeUInt32BE(id, 5);
40
+ header.writeUInt32BE(body.length, 9);
41
+ return Buffer.concat([header, body, payload]);
42
+ }
43
+ /** The connection failed or ended; a request may succeed once it is started again. */
44
+ function lost(message) {
45
+ return new RemoteError({ code: "unknown", message, lost: true });
46
+ }
47
+ /** Whether an error means the connection was lost, not that the daemon refused the request. */
48
+ export function isConnectionLost(error) {
49
+ return error instanceof RemoteError && error.fields.lost === true;
50
+ }
51
+ function closed() {
52
+ return new RemoteError({ code: "unknown", message: "Connection closed" });
53
+ }
54
+ /** One running daemon. */
55
+ class Session {
56
+ id;
57
+ child;
58
+ token;
59
+ pending = new Map();
60
+ /** Received bytes not yet parsed, without concatenating on every chunk. */
61
+ chunks = [];
62
+ buffered = 0;
63
+ synced = false;
64
+ lastSeen = Date.now();
65
+ timer;
66
+ live = true;
67
+ constructor(id, child, token) {
68
+ this.id = id;
69
+ this.child = child;
70
+ this.token = token;
71
+ }
72
+ /** The first `length` buffered bytes as one buffer, without consuming them. */
73
+ peek(length) {
74
+ if (this.chunks[0].length < length) {
75
+ this.chunks = [Buffer.concat(this.chunks)];
76
+ }
77
+ return this.chunks[0].subarray(0, length);
78
+ }
79
+ consume(length) {
80
+ this.buffered -= length;
81
+ while (length > 0) {
82
+ const first = this.chunks[0];
83
+ if (first.length <= length) {
84
+ this.chunks.shift();
85
+ length -= first.length;
86
+ }
87
+ else {
88
+ this.chunks[0] = first.subarray(length);
89
+ length = 0;
90
+ }
91
+ }
92
+ }
93
+ }
94
+ /**
95
+ * One connection to a pi-env daemon: started lazily on the first request and started again after it is lost. Requests
96
+ * in flight when it is lost fail with code `unknown`; for mutations their outcome is then unknown.
97
+ */
98
+ export class Connection {
99
+ #options;
100
+ #session;
101
+ #ready;
102
+ #sessions = 0;
103
+ #nextId = 1;
104
+ #closed = false;
105
+ constructor(options) {
106
+ this.#options = options;
107
+ }
108
+ /** Connect if needed and return what the daemon reported about its machine. */
109
+ async info() {
110
+ return (await this.#connect()).info;
111
+ }
112
+ /** Connect if needed and return the live session's id. */
113
+ async session() {
114
+ return (await this.#connect()).session.id;
115
+ }
116
+ async request(op, json, options = {}) {
117
+ if (options.session !== undefined) {
118
+ const current = this.#session;
119
+ if (current === undefined || current.id !== options.session || !current.live) {
120
+ throw lost("pi-env connection lost");
121
+ }
122
+ return this.#send(current, op, json, options);
123
+ }
124
+ const { session } = await this.#connect();
125
+ return this.#send(session, op, json, options);
126
+ }
127
+ /** Stop the daemon; it kills everything it started. */
128
+ close() {
129
+ this.#closed = true;
130
+ if (this.#session)
131
+ this.#teardown(this.#session, closed());
132
+ }
133
+ /** Kill one `exec` without aborting it. */
134
+ kill(id, session) {
135
+ const current = this.#session;
136
+ if (current?.id === session && current.live)
137
+ this.#write(current, frame(CANCEL, id, { mode: "kill" }));
138
+ }
139
+ #connect() {
140
+ if (this.#closed)
141
+ return Promise.reject(closed());
142
+ if (this.#ready === undefined) {
143
+ const ready = this.#start();
144
+ this.#ready = ready;
145
+ // A failed start is not remembered: the next request tries again.
146
+ ready.catch(() => {
147
+ if (this.#ready === ready)
148
+ this.#ready = undefined;
149
+ });
150
+ }
151
+ return this.#ready;
152
+ }
153
+ #send(session, op, json, options) {
154
+ if (!session.live)
155
+ return Promise.reject(lost("pi-env connection lost"));
156
+ const id = this.#nextId++;
157
+ return new Promise((resolve, reject) => {
158
+ const onAbort = () => this.#write(session, frame(CANCEL, id, {}));
159
+ const done = () => options.signal?.removeEventListener("abort", onAbort);
160
+ session.pending.set(id, {
161
+ resolve: (value) => {
162
+ done();
163
+ resolve(value);
164
+ },
165
+ reject: (error) => {
166
+ done();
167
+ reject(error);
168
+ },
169
+ ...(options.onEvent ? { onEvent: options.onEvent } : {}),
170
+ });
171
+ options.signal?.addEventListener("abort", onAbort, { once: true });
172
+ options.onStart?.(id, session.id);
173
+ this.#write(session, frame(REQUEST, id, { ...json, op }, options.payload));
174
+ if (options.signal?.aborted)
175
+ onAbort();
176
+ });
177
+ }
178
+ #write(session, data) {
179
+ if (session.live)
180
+ session.child.stdin.write(data);
181
+ }
182
+ async #start() {
183
+ let command;
184
+ try {
185
+ const option = this.#options.command;
186
+ command = typeof option === "function" ? await option() : option;
187
+ }
188
+ catch (error) {
189
+ const message = error instanceof Error ? error.message : String(error);
190
+ throw new RemoteError({ code: "spawn_error", message, lost: true });
191
+ }
192
+ if (this.#closed)
193
+ throw closed();
194
+ const [program, ...args] = command;
195
+ if (program === undefined)
196
+ throw new RemoteError({ code: "spawn_error", message: "No daemon command" });
197
+ const token = randomBytes(16).toString("hex");
198
+ const child = spawn(program, [...args, "serve", "--token", token], { stdio: ["pipe", "pipe", "pipe"] });
199
+ const session = new Session(++this.#sessions, child, token);
200
+ this.#session = session;
201
+ let startTimer;
202
+ const failed = new Promise((_resolve, reject) => {
203
+ startTimer = setTimeout(() => reject(lost(`pi-env did not answer within ${START_TIMEOUT_MS / 1000} s`)), START_TIMEOUT_MS);
204
+ child.once("error", (error) => reject(new RemoteError({ code: "spawn_error", message: error.message, lost: true })));
205
+ child.once("exit", (code) => reject(lost(`pi-env exited with code ${code} before it was ready`)));
206
+ });
207
+ failed.catch(() => { });
208
+ child.stdout.on("data", (chunk) => this.#onData(session, chunk));
209
+ child.stderr.on("data", (chunk) => this.#options.onLog?.(chunk.toString("utf8")));
210
+ child.on("error", () => this.#teardown(session, lost("pi-env connection lost")));
211
+ child.on("exit", () => this.#teardown(session, lost("pi-env connection lost")));
212
+ child.stdin.on("error", () => { });
213
+ session.timer = setInterval(() => {
214
+ this.#write(session, frame(PING, 0, {}));
215
+ if (Date.now() - session.lastSeen > SILENCE_LIMIT_MS) {
216
+ this.#teardown(session, lost("pi-env connection timed out"));
217
+ }
218
+ }, PING_INTERVAL_MS);
219
+ session.timer.unref();
220
+ try {
221
+ const { json } = await Promise.race([this.#send(session, "hello", { protocol: 1 }, {}), failed]);
222
+ if (json.protocol !== 1)
223
+ throw lost(`Unsupported protocol ${json.protocol}`);
224
+ return { info: json, session };
225
+ }
226
+ catch (error) {
227
+ this.#teardown(session, error instanceof Error ? error : lost(String(error)));
228
+ throw error;
229
+ }
230
+ finally {
231
+ clearTimeout(startTimer);
232
+ }
233
+ }
234
+ #teardown(session, error) {
235
+ if (!session.live)
236
+ return;
237
+ session.live = false;
238
+ clearInterval(session.timer);
239
+ if (this.#session === session) {
240
+ this.#session = undefined;
241
+ this.#ready = undefined;
242
+ }
243
+ session.child.stdin.end();
244
+ session.child.kill();
245
+ const pending = [...session.pending.values()];
246
+ session.pending.clear();
247
+ for (const request of pending)
248
+ request.reject(error);
249
+ }
250
+ #onData(session, chunk) {
251
+ if (!session.live)
252
+ return;
253
+ session.lastSeen = Date.now();
254
+ session.chunks.push(chunk);
255
+ session.buffered += chunk.length;
256
+ if (!session.synced) {
257
+ // Shell startup files may print before the daemon runs; skip everything before its sync line.
258
+ const marker = Buffer.from(`PI-ENV ${session.token}\n`, "utf8");
259
+ const all = session.peek(session.buffered);
260
+ const index = all.indexOf(marker);
261
+ if (index === -1) {
262
+ if (session.buffered > 1024 * 1024) {
263
+ session.consume(session.buffered - marker.length);
264
+ }
265
+ return;
266
+ }
267
+ const noise = all.subarray(0, index).toString("utf8").trim();
268
+ if (noise !== "")
269
+ this.#options.onLog?.(noise);
270
+ session.consume(index + marker.length);
271
+ session.synced = true;
272
+ }
273
+ while (session.live && session.buffered >= 4) {
274
+ const length = session.peek(4).readUInt32BE(0);
275
+ if (length < 9 || length > MAX_FRAME) {
276
+ this.#teardown(session, lost("Corrupt frame from pi-env"));
277
+ return;
278
+ }
279
+ if (session.buffered < 4 + length)
280
+ return;
281
+ const body = session.peek(4 + length).subarray(4);
282
+ const type = body.readUInt8(0);
283
+ const id = body.readUInt32BE(1);
284
+ const jsonLength = body.readUInt32BE(5);
285
+ let json;
286
+ try {
287
+ if (9 + jsonLength > length)
288
+ throw new Error("JSON length out of range");
289
+ const parsed = JSON.parse(body.subarray(9, 9 + jsonLength).toString("utf8"));
290
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
291
+ throw new Error("not an object");
292
+ json = parsed;
293
+ }
294
+ catch {
295
+ this.#teardown(session, lost("Corrupt frame from pi-env"));
296
+ return;
297
+ }
298
+ // A plain Uint8Array copy, like `NodeExecutionEnv`'s reader results, not a view of the receive buffer.
299
+ const payload = new Uint8Array(body.subarray(9 + jsonLength));
300
+ session.consume(4 + length);
301
+ this.#dispatch(session, type, id, json, payload);
302
+ }
303
+ }
304
+ #dispatch(session, type, id, json, payload) {
305
+ const pending = session.pending.get(id);
306
+ if (type === EVENT) {
307
+ pending?.onEvent?.(json, payload);
308
+ return;
309
+ }
310
+ if (type !== RESULT && type !== ERROR)
311
+ return;
312
+ if (pending === undefined)
313
+ return;
314
+ session.pending.delete(id);
315
+ if (type === RESULT)
316
+ pending.resolve({ json, payload, session: session.id });
317
+ else
318
+ pending.reject(new RemoteError(json));
319
+ }
320
+ }
321
+ //# sourceMappingURL=connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection.js","sourceRoot":"","sources":["../src/connection.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ChildProcessWithoutNullStreams, spawn } from \"node:child_process\";\nimport { randomBytes } from \"node:crypto\";\n\n/** Frame types (docs/protocol.md). */\nconst REQUEST = 1;\nconst RESULT = 2;\nconst ERROR = 3;\nconst EVENT = 4;\nconst CANCEL = 5;\nconst PING = 6;\n\nconst MAX_FRAME = 16 * 1024 * 1024;\nconst PING_INTERVAL_MS = 5000;\n/** The daemon pings every five seconds; this much silence means the connection is gone. */\nconst SILENCE_LIMIT_MS = 30_000;\n/** How long starting the daemon and its `hello` may take. */\nconst START_TIMEOUT_MS = 60_000;\n\nexport type Json = Record<string, unknown>;\n\n/** A failure reported by the daemon, with its Node-style code. */\nexport class RemoteError extends Error {\n\treadonly code: string;\n\treadonly path?: string;\n\treadonly fields: Json;\n\n\tconstructor(fields: Json) {\n\t\tsuper(typeof fields.message === \"string\" ? fields.message : \"Remote operation failed\");\n\t\tthis.name = \"RemoteError\";\n\t\tthis.code = typeof fields.code === \"string\" ? fields.code : \"unknown\";\n\t\tif (typeof fields.path === \"string\") this.path = fields.path;\n\t\tthis.fields = fields;\n\t}\n}\n\n/** What `hello` reports about the remote machine. */\nexport interface RemoteInfo {\n\tprotocol: number;\n\tversion: string;\n\tos: string;\n\tarch: string;\n\thome: string;\n\ttmpdir: string;\n\t/** Path separator: `/`, or `\\\\` on Windows. */\n\tseparator: string;\n\t/** The daemon's working directory, where Node's `path.resolve` would fall back to `process.cwd()`. */\n\tcwd: string;\n\t/** Windows: per-drive working directories (`C:` → `C:\\\\work`) of the `=C:` variables. */\n\tdriveCwds: Record<string, string>;\n\tpid: number;\n}\n\nexport interface ConnectionOptions {\n\t/**\n\t * Command that starts the daemon, before its `serve --token <hex>` arguments, e.g. `[\"ssh\", \"-T\", \"--\", \"host\",\n\t * \"~/.pi/mobile/tools/pi-env\"]`. A function computes it at each start (detecting and deploying first, for example);\n\t * its failure fails that start, and the next request tries again.\n\t */\n\tcommand: readonly string[] | (() => Promise<readonly string[]>);\n\t/** Receives the daemon's and the transport's diagnostic output. */\n\tonLog?: (text: string) => void;\n}\n\nexport interface Reply {\n\tjson: Json;\n\tpayload: Uint8Array;\n\t/** The daemon session that answered; handles from it are only valid while it lives. */\n\tsession: number;\n}\n\ntype Pending = {\n\tresolve: (value: Reply) => void;\n\treject: (error: Error) => void;\n\tonEvent?: (json: Json, payload: Uint8Array) => void;\n};\n\nexport interface RequestOptions {\n\tpayload?: Uint8Array;\n\t/** Aborting sends `cancel`; the request still settles with the daemon's result or error. */\n\tsignal?: AbortSignal;\n\t/** Progress events of the request, such as `exec` output. */\n\tonEvent?: (json: Json, payload: Uint8Array) => void;\n\t/** The request's id and session, for `kill`. */\n\tonStart?: (id: number, session: number) => void;\n\t/**\n\t * Only run in this daemon session, for requests on handles it opened. After the connection was lost and started\n\t * again, such requests fail instead of reaching a daemon that never opened the handle.\n\t */\n\tsession?: number;\n}\n\n/** Lone surrogates become U+FFFD, as Node encodes strings for system calls. */\nfunction wellFormed(_key: string, value: unknown): unknown {\n\treturn typeof value === \"string\" ? value.toWellFormed() : value;\n}\n\nfunction frame(type: number, id: number, json: Json, payload: Uint8Array = new Uint8Array(0)): Buffer {\n\tconst body = Buffer.from(JSON.stringify(json, wellFormed), \"utf8\");\n\tconst header = Buffer.alloc(13);\n\theader.writeUInt32BE(9 + body.length + payload.length, 0);\n\theader.writeUInt8(type, 4);\n\theader.writeUInt32BE(id, 5);\n\theader.writeUInt32BE(body.length, 9);\n\treturn Buffer.concat([header, body, payload]);\n}\n\n/** The connection failed or ended; a request may succeed once it is started again. */\nfunction lost(message: string): RemoteError {\n\treturn new RemoteError({ code: \"unknown\", message, lost: true });\n}\n\n/** Whether an error means the connection was lost, not that the daemon refused the request. */\nexport function isConnectionLost(error: unknown): boolean {\n\treturn error instanceof RemoteError && error.fields.lost === true;\n}\n\nfunction closed(): RemoteError {\n\treturn new RemoteError({ code: \"unknown\", message: \"Connection closed\" });\n}\n\n/** One running daemon. */\nclass Session {\n\treadonly id: number;\n\treadonly child: ChildProcessWithoutNullStreams;\n\treadonly token: string;\n\treadonly pending = new Map<number, Pending>();\n\t/** Received bytes not yet parsed, without concatenating on every chunk. */\n\tchunks: Buffer[] = [];\n\tbuffered = 0;\n\tsynced = false;\n\tlastSeen = Date.now();\n\ttimer: ReturnType<typeof setInterval> | undefined;\n\tlive = true;\n\n\tconstructor(id: number, child: ChildProcessWithoutNullStreams, token: string) {\n\t\tthis.id = id;\n\t\tthis.child = child;\n\t\tthis.token = token;\n\t}\n\n\t/** The first `length` buffered bytes as one buffer, without consuming them. */\n\tpeek(length: number): Buffer {\n\t\tif (this.chunks[0]!.length < length) {\n\t\t\tthis.chunks = [Buffer.concat(this.chunks)];\n\t\t}\n\t\treturn this.chunks[0]!.subarray(0, length);\n\t}\n\n\tconsume(length: number): void {\n\t\tthis.buffered -= length;\n\t\twhile (length > 0) {\n\t\t\tconst first = this.chunks[0]!;\n\t\t\tif (first.length <= length) {\n\t\t\t\tthis.chunks.shift();\n\t\t\t\tlength -= first.length;\n\t\t\t} else {\n\t\t\t\tthis.chunks[0] = first.subarray(length);\n\t\t\t\tlength = 0;\n\t\t\t}\n\t\t}\n\t}\n}\n\n/**\n * One connection to a pi-env daemon: started lazily on the first request and started again after it is lost. Requests\n * in flight when it is lost fail with code `unknown`; for mutations their outcome is then unknown.\n */\nexport class Connection {\n\treadonly #options: ConnectionOptions;\n\t#session: Session | undefined;\n\t#ready: Promise<{ info: RemoteInfo; session: Session }> | undefined;\n\t#sessions = 0;\n\t#nextId = 1;\n\t#closed = false;\n\n\tconstructor(options: ConnectionOptions) {\n\t\tthis.#options = options;\n\t}\n\n\t/** Connect if needed and return what the daemon reported about its machine. */\n\tasync info(): Promise<RemoteInfo> {\n\t\treturn (await this.#connect()).info;\n\t}\n\n\t/** Connect if needed and return the live session's id. */\n\tasync session(): Promise<number> {\n\t\treturn (await this.#connect()).session.id;\n\t}\n\n\tasync request(op: string, json: Json, options: RequestOptions = {}): Promise<Reply> {\n\t\tif (options.session !== undefined) {\n\t\t\tconst current = this.#session;\n\t\t\tif (current === undefined || current.id !== options.session || !current.live) {\n\t\t\t\tthrow lost(\"pi-env connection lost\");\n\t\t\t}\n\t\t\treturn this.#send(current, op, json, options);\n\t\t}\n\t\tconst { session } = await this.#connect();\n\t\treturn this.#send(session, op, json, options);\n\t}\n\n\t/** Stop the daemon; it kills everything it started. */\n\tclose(): void {\n\t\tthis.#closed = true;\n\t\tif (this.#session) this.#teardown(this.#session, closed());\n\t}\n\n\t/** Kill one `exec` without aborting it. */\n\tkill(id: number, session: number): void {\n\t\tconst current = this.#session;\n\t\tif (current?.id === session && current.live) this.#write(current, frame(CANCEL, id, { mode: \"kill\" }));\n\t}\n\n\t#connect(): Promise<{ info: RemoteInfo; session: Session }> {\n\t\tif (this.#closed) return Promise.reject(closed());\n\t\tif (this.#ready === undefined) {\n\t\t\tconst ready = this.#start();\n\t\t\tthis.#ready = ready;\n\t\t\t// A failed start is not remembered: the next request tries again.\n\t\t\tready.catch(() => {\n\t\t\t\tif (this.#ready === ready) this.#ready = undefined;\n\t\t\t});\n\t\t}\n\t\treturn this.#ready;\n\t}\n\n\t#send(session: Session, op: string, json: Json, options: RequestOptions): Promise<Reply> {\n\t\tif (!session.live) return Promise.reject(lost(\"pi-env connection lost\"));\n\t\tconst id = this.#nextId++;\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst onAbort = () => this.#write(session, frame(CANCEL, id, {}));\n\t\t\tconst done = () => options.signal?.removeEventListener(\"abort\", onAbort);\n\t\t\tsession.pending.set(id, {\n\t\t\t\tresolve: (value) => {\n\t\t\t\t\tdone();\n\t\t\t\t\tresolve(value);\n\t\t\t\t},\n\t\t\t\treject: (error) => {\n\t\t\t\t\tdone();\n\t\t\t\t\treject(error);\n\t\t\t\t},\n\t\t\t\t...(options.onEvent ? { onEvent: options.onEvent } : {}),\n\t\t\t});\n\t\t\toptions.signal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\t\toptions.onStart?.(id, session.id);\n\t\t\tthis.#write(session, frame(REQUEST, id, { ...json, op }, options.payload));\n\t\t\tif (options.signal?.aborted) onAbort();\n\t\t});\n\t}\n\n\t#write(session: Session, data: Buffer): void {\n\t\tif (session.live) session.child.stdin.write(data);\n\t}\n\n\tasync #start(): Promise<{ info: RemoteInfo; session: Session }> {\n\t\tlet command: readonly string[];\n\t\ttry {\n\t\t\tconst option = this.#options.command;\n\t\t\tcommand = typeof option === \"function\" ? await option() : option;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tthrow new RemoteError({ code: \"spawn_error\", message, lost: true });\n\t\t}\n\t\tif (this.#closed) throw closed();\n\t\tconst [program, ...args] = command;\n\t\tif (program === undefined) throw new RemoteError({ code: \"spawn_error\", message: \"No daemon command\" });\n\t\tconst token = randomBytes(16).toString(\"hex\");\n\t\tconst child = spawn(program, [...args, \"serve\", \"--token\", token], { stdio: [\"pipe\", \"pipe\", \"pipe\"] });\n\t\tconst session = new Session(++this.#sessions, child, token);\n\t\tthis.#session = session;\n\t\tlet startTimer: ReturnType<typeof setTimeout> | undefined;\n\t\tconst failed = new Promise<never>((_resolve, reject) => {\n\t\t\tstartTimer = setTimeout(\n\t\t\t\t() => reject(lost(`pi-env did not answer within ${START_TIMEOUT_MS / 1000} s`)),\n\t\t\t\tSTART_TIMEOUT_MS,\n\t\t\t);\n\t\t\tchild.once(\"error\", (error) =>\n\t\t\t\treject(new RemoteError({ code: \"spawn_error\", message: error.message, lost: true })),\n\t\t\t);\n\t\t\tchild.once(\"exit\", (code) => reject(lost(`pi-env exited with code ${code} before it was ready`)));\n\t\t});\n\t\tfailed.catch(() => {});\n\t\tchild.stdout.on(\"data\", (chunk: Buffer) => this.#onData(session, chunk));\n\t\tchild.stderr.on(\"data\", (chunk: Buffer) => this.#options.onLog?.(chunk.toString(\"utf8\")));\n\t\tchild.on(\"error\", () => this.#teardown(session, lost(\"pi-env connection lost\")));\n\t\tchild.on(\"exit\", () => this.#teardown(session, lost(\"pi-env connection lost\")));\n\t\tchild.stdin.on(\"error\", () => {});\n\t\tsession.timer = setInterval(() => {\n\t\t\tthis.#write(session, frame(PING, 0, {}));\n\t\t\tif (Date.now() - session.lastSeen > SILENCE_LIMIT_MS) {\n\t\t\t\tthis.#teardown(session, lost(\"pi-env connection timed out\"));\n\t\t\t}\n\t\t}, PING_INTERVAL_MS);\n\t\tsession.timer.unref();\n\t\ttry {\n\t\t\tconst { json } = await Promise.race([this.#send(session, \"hello\", { protocol: 1 }, {}), failed]);\n\t\t\tif (json.protocol !== 1) throw lost(`Unsupported protocol ${json.protocol}`);\n\t\t\treturn { info: json as unknown as RemoteInfo, session };\n\t\t} catch (error) {\n\t\t\tthis.#teardown(session, error instanceof Error ? error : lost(String(error)));\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tclearTimeout(startTimer);\n\t\t}\n\t}\n\n\t#teardown(session: Session, error: Error): void {\n\t\tif (!session.live) return;\n\t\tsession.live = false;\n\t\tclearInterval(session.timer);\n\t\tif (this.#session === session) {\n\t\t\tthis.#session = undefined;\n\t\t\tthis.#ready = undefined;\n\t\t}\n\t\tsession.child.stdin.end();\n\t\tsession.child.kill();\n\t\tconst pending = [...session.pending.values()];\n\t\tsession.pending.clear();\n\t\tfor (const request of pending) request.reject(error);\n\t}\n\n\t#onData(session: Session, chunk: Buffer): void {\n\t\tif (!session.live) return;\n\t\tsession.lastSeen = Date.now();\n\t\tsession.chunks.push(chunk);\n\t\tsession.buffered += chunk.length;\n\t\tif (!session.synced) {\n\t\t\t// Shell startup files may print before the daemon runs; skip everything before its sync line.\n\t\t\tconst marker = Buffer.from(`PI-ENV ${session.token}\\n`, \"utf8\");\n\t\t\tconst all = session.peek(session.buffered);\n\t\t\tconst index = all.indexOf(marker);\n\t\t\tif (index === -1) {\n\t\t\t\tif (session.buffered > 1024 * 1024) {\n\t\t\t\t\tsession.consume(session.buffered - marker.length);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst noise = all.subarray(0, index).toString(\"utf8\").trim();\n\t\t\tif (noise !== \"\") this.#options.onLog?.(noise);\n\t\t\tsession.consume(index + marker.length);\n\t\t\tsession.synced = true;\n\t\t}\n\t\twhile (session.live && session.buffered >= 4) {\n\t\t\tconst length = session.peek(4).readUInt32BE(0);\n\t\t\tif (length < 9 || length > MAX_FRAME) {\n\t\t\t\tthis.#teardown(session, lost(\"Corrupt frame from pi-env\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tif (session.buffered < 4 + length) return;\n\t\t\tconst body = session.peek(4 + length).subarray(4);\n\t\t\tconst type = body.readUInt8(0);\n\t\t\tconst id = body.readUInt32BE(1);\n\t\t\tconst jsonLength = body.readUInt32BE(5);\n\t\t\tlet json: Json;\n\t\t\ttry {\n\t\t\t\tif (9 + jsonLength > length) throw new Error(\"JSON length out of range\");\n\t\t\t\tconst parsed: unknown = JSON.parse(body.subarray(9, 9 + jsonLength).toString(\"utf8\"));\n\t\t\t\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed))\n\t\t\t\t\tthrow new Error(\"not an object\");\n\t\t\t\tjson = parsed as Json;\n\t\t\t} catch {\n\t\t\t\tthis.#teardown(session, lost(\"Corrupt frame from pi-env\"));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\t// A plain Uint8Array copy, like `NodeExecutionEnv`'s reader results, not a view of the receive buffer.\n\t\t\tconst payload = new Uint8Array(body.subarray(9 + jsonLength));\n\t\t\tsession.consume(4 + length);\n\t\t\tthis.#dispatch(session, type, id, json, payload);\n\t\t}\n\t}\n\n\t#dispatch(session: Session, type: number, id: number, json: Json, payload: Uint8Array): void {\n\t\tconst pending = session.pending.get(id);\n\t\tif (type === EVENT) {\n\t\t\tpending?.onEvent?.(json, payload);\n\t\t\treturn;\n\t\t}\n\t\tif (type !== RESULT && type !== ERROR) return;\n\t\tif (pending === undefined) return;\n\t\tsession.pending.delete(id);\n\t\tif (type === RESULT) pending.resolve({ json, payload, session: session.id });\n\t\telse pending.reject(new RemoteError(json));\n\t}\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import { FileError, type Result } from "@earendil-works/pi-durable/env";
2
+ export declare function abortResult<T>(signal: AbortSignal | undefined, path?: string): Result<T, FileError> | undefined;
3
+ /** `NodeExecutionEnv`'s mapping of Node error codes to `FileError` codes. */
4
+ export declare function toFileError(error: unknown, fallbackPath?: string): FileError;
5
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,SAAS,EAAE,KAAK,MAAM,EAAE,MAAM,gCAAgC,CAAC;AAG7E,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,GAAG,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,SAAS,CAE/G;AAED,6EAA6E;AAC7E,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAyB5E"}
package/dist/errors.js ADDED
@@ -0,0 +1,34 @@
1
+ import { err, FileError } from "@earendil-works/pi-durable/env";
2
+ import { RemoteError } from "./connection.js";
3
+ export function abortResult(signal, path) {
4
+ return signal?.aborted ? err(new FileError("aborted", "aborted", path)) : undefined;
5
+ }
6
+ /** `NodeExecutionEnv`'s mapping of Node error codes to `FileError` codes. */
7
+ export function toFileError(error, fallbackPath) {
8
+ if (error instanceof FileError)
9
+ return error;
10
+ if (!(error instanceof RemoteError)) {
11
+ const cause = error instanceof Error ? error : new Error(String(error));
12
+ return new FileError("unknown", cause.message, fallbackPath, cause);
13
+ }
14
+ const path = error.path ?? fallbackPath;
15
+ switch (error.code) {
16
+ case "aborted":
17
+ return new FileError("aborted", error.message, path, error);
18
+ case "ENOENT":
19
+ return new FileError("not_found", error.message, path, error);
20
+ case "EACCES":
21
+ case "EPERM":
22
+ return new FileError("permission_denied", error.message, path, error);
23
+ case "ENOTDIR":
24
+ return new FileError("not_directory", error.message, path, error);
25
+ case "EISDIR":
26
+ return new FileError("is_directory", error.message, path, error);
27
+ case "EINVAL":
28
+ case "SYMLINK":
29
+ case "NOT_REGULAR":
30
+ return new FileError("invalid", error.message, path, error);
31
+ }
32
+ return new FileError("unknown", error.message, path, error);
33
+ }
34
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,SAAS,EAAe,MAAM,gCAAgC,CAAC;AAC7E,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAE9C,MAAM,UAAU,WAAW,CAAI,MAA+B,EAAE,IAAa;IAC5E,OAAO,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,SAAS,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACrF,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,WAAW,CAAC,KAAc,EAAE,YAAqB;IAChE,IAAI,KAAK,YAAY,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7C,IAAI,CAAC,CAAC,KAAK,YAAY,WAAW,CAAC,EAAE,CAAC;QACrC,MAAM,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACxE,OAAO,IAAI,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,YAAY,EAAE,KAAK,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,YAAY,CAAC;IACxC,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,SAAS;YACb,OAAO,IAAI,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAC7D,KAAK,QAAQ;YACZ,OAAO,IAAI,SAAS,CAAC,WAAW,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAC/D,KAAK,QAAQ,CAAC;QACd,KAAK,OAAO;YACX,OAAO,IAAI,SAAS,CAAC,mBAAmB,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QACvE,KAAK,SAAS;YACb,OAAO,IAAI,SAAS,CAAC,eAAe,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QACnE,KAAK,QAAQ;YACZ,OAAO,IAAI,SAAS,CAAC,cAAc,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QAClE,KAAK,QAAQ,CAAC;QACd,KAAK,SAAS,CAAC;QACf,KAAK,aAAa;YACjB,OAAO,IAAI,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9D,CAAC;IACD,OAAO,IAAI,SAAS,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;AAC7D,CAAC","sourcesContent":["import { err, FileError, type Result } from \"@earendil-works/pi-durable/env\";\nimport { RemoteError } from \"./connection.ts\";\n\nexport function abortResult<T>(signal: AbortSignal | undefined, path?: string): Result<T, FileError> | undefined {\n\treturn signal?.aborted ? err(new FileError(\"aborted\", \"aborted\", path)) : undefined;\n}\n\n/** `NodeExecutionEnv`'s mapping of Node error codes to `FileError` codes. */\nexport function toFileError(error: unknown, fallbackPath?: string): FileError {\n\tif (error instanceof FileError) return error;\n\tif (!(error instanceof RemoteError)) {\n\t\tconst cause = error instanceof Error ? error : new Error(String(error));\n\t\treturn new FileError(\"unknown\", cause.message, fallbackPath, cause);\n\t}\n\tconst path = error.path ?? fallbackPath;\n\tswitch (error.code) {\n\t\tcase \"aborted\":\n\t\t\treturn new FileError(\"aborted\", error.message, path, error);\n\t\tcase \"ENOENT\":\n\t\t\treturn new FileError(\"not_found\", error.message, path, error);\n\t\tcase \"EACCES\":\n\t\tcase \"EPERM\":\n\t\t\treturn new FileError(\"permission_denied\", error.message, path, error);\n\t\tcase \"ENOTDIR\":\n\t\t\treturn new FileError(\"not_directory\", error.message, path, error);\n\t\tcase \"EISDIR\":\n\t\t\treturn new FileError(\"is_directory\", error.message, path, error);\n\t\tcase \"EINVAL\":\n\t\tcase \"SYMLINK\":\n\t\tcase \"NOT_REGULAR\":\n\t\t\treturn new FileError(\"invalid\", error.message, path, error);\n\t}\n\treturn new FileError(\"unknown\", error.message, path, error);\n}\n"]}
@@ -0,0 +1,5 @@
1
+ export { Connection, type ConnectionOptions, isConnectionLost, RemoteError, type RemoteInfo, type Reply, type RequestOptions, } from "./connection.ts";
2
+ export { RemoteExecutionEnv, type RemoteExecutionEnvOptions } from "./remote-env.ts";
3
+ export { acceptHostKey, connectSsh, deployDaemon, detectPlatform, forgetHostKey, HostKeyChangedError, HostKeyUnknownError, packagedDaemon, type RemotePlatform, type SshConnectOptions, SshError, type SshTarget, scanHostKey, sshArguments, sshConnection, } from "./ssh.ts";
4
+ export { RemoteWatcher, type RemoteWatchOptions } from "./watch.ts";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EACV,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,WAAW,EACX,KAAK,UAAU,EACf,KAAK,KAAK,EACV,KAAK,cAAc,GACnB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAE,KAAK,yBAAyB,EAAE,MAAM,iBAAiB,CAAC;AACrF,OAAO,EACN,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,QAAQ,EACR,KAAK,SAAS,EACd,WAAW,EACX,YAAY,EACZ,aAAa,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { Connection, isConnectionLost, RemoteError, } from "./connection.js";
2
+ export { RemoteExecutionEnv } from "./remote-env.js";
3
+ export { acceptHostKey, connectSsh, deployDaemon, detectPlatform, forgetHostKey, HostKeyChangedError, HostKeyUnknownError, packagedDaemon, SshError, scanHostKey, sshArguments, sshConnection, } from "./ssh.js";
4
+ export { RemoteWatcher } from "./watch.js";
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,UAAU,EAEV,gBAAgB,EAChB,WAAW,GAIX,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,kBAAkB,EAAkC,MAAM,iBAAiB,CAAC;AACrF,OAAO,EACN,aAAa,EACb,UAAU,EACV,YAAY,EACZ,cAAc,EACd,aAAa,EACb,mBAAmB,EACnB,mBAAmB,EACnB,cAAc,EAGd,QAAQ,EAER,WAAW,EACX,YAAY,EACZ,aAAa,GACb,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,aAAa,EAA2B,MAAM,YAAY,CAAC","sourcesContent":["export {\n\tConnection,\n\ttype ConnectionOptions,\n\tisConnectionLost,\n\tRemoteError,\n\ttype RemoteInfo,\n\ttype Reply,\n\ttype RequestOptions,\n} from \"./connection.ts\";\nexport { RemoteExecutionEnv, type RemoteExecutionEnvOptions } from \"./remote-env.ts\";\nexport {\n\tacceptHostKey,\n\tconnectSsh,\n\tdeployDaemon,\n\tdetectPlatform,\n\tforgetHostKey,\n\tHostKeyChangedError,\n\tHostKeyUnknownError,\n\tpackagedDaemon,\n\ttype RemotePlatform,\n\ttype SshConnectOptions,\n\tSshError,\n\ttype SshTarget,\n\tscanHostKey,\n\tsshArguments,\n\tsshConnection,\n} from \"./ssh.ts\";\nexport { RemoteWatcher, type RemoteWatchOptions } from \"./watch.ts\";\n"]}