@byfriends/kaos 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BYF PROPRIETARY LICENSE
2
+
3
+ Copyright (c) 2026-2027 ByronFinn
4
+
5
+ Permission is hereby granted to copy and redistribute this software in its
6
+ unmodified form, without charge. This permission does not extend to modified
7
+ versions of the software.
8
+
9
+ Local modification of this software for personal use is permitted, provided
10
+ that such modifications are not distributed to third parties.
11
+
12
+ Restrictions:
13
+ 1. You may NOT redistribute modified versions of this software.
14
+ 2. You may NOT use this software for commercial purposes.
15
+ 3. You may NOT sublicense, sell, or offer this software as a service.
16
+
17
+ This software is source-available but NOT open source. The source code is
18
+ made publicly visible for review purposes only.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ For questions about licensing, contact: https://github.com/ByronFinn/byf/issues
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # @byfriends/kaos
2
+
3
+ Execution environment abstraction used by BYF.
4
+
5
+ Part of the [BYF](https://github.com/ByronFinn/byf) monorepo.
6
+
7
+ See the main repository for documentation, issues, and contribution guidelines.
8
+
9
+ ## License
10
+
11
+ Proprietary — see the root LICENSE file.
@@ -0,0 +1,344 @@
1
+
2
+ import { Readable, Writable } from "node:stream";
3
+
4
+ //#region src/process.d.ts
5
+ /**
6
+ * A running process spawned by a {@link Kaos} environment.
7
+ *
8
+ * Provides access to standard I/O streams, the process ID, and lifecycle
9
+ * management (wait / kill). The interface is intentionally minimal so it
10
+ * can be backed by local child processes, SSH sessions, or container runtimes.
11
+ */
12
+ interface KaosProcess {
13
+ /** Writable stream connected to the process's standard input. */
14
+ readonly stdin: Writable;
15
+ /** Readable stream for the process's standard output. */
16
+ readonly stdout: Readable;
17
+ /** Readable stream for the process's standard error. */
18
+ readonly stderr: Readable;
19
+ /** Operating-system process ID. */
20
+ readonly pid: number;
21
+ /** Exit code if the process has already terminated, otherwise `null`. */
22
+ readonly exitCode: number | null;
23
+ /** Wait for the process to exit and return its exit code. */
24
+ wait(): Promise<number>;
25
+ /** Send a signal to the process (defaults to `SIGTERM`). */
26
+ kill(signal?: NodeJS.Signals): Promise<void>;
27
+ }
28
+ //#endregion
29
+ //#region src/types.d.ts
30
+ /**
31
+ * KAOS stat result, mirroring Python's os.stat_result fields.
32
+ */
33
+ interface StatResult {
34
+ stMode: number;
35
+ stIno: number;
36
+ stDev: number;
37
+ stNlink: number;
38
+ stUid: number;
39
+ stGid: number;
40
+ stSize: number;
41
+ stAtime: number;
42
+ stMtime: number;
43
+ stCtime: number;
44
+ }
45
+ //#endregion
46
+ //#region src/kaos.d.ts
47
+ /**
48
+ * Byf Agent Operating System (KAOS) interface.
49
+ *
50
+ * This is the core abstraction that allows the agent to interact with
51
+ * different execution environments (local, SSH, containers, etc.)
52
+ * through a unified API.
53
+ */
54
+ interface Kaos {
55
+ /** Human-readable name for this environment (e.g. `"local"`, `"ssh:host"`). */
56
+ readonly name: string;
57
+ /** Return the path style used by this environment. */
58
+ pathClass(): 'posix' | 'win32';
59
+ /** Normalize the given path string (resolve `.` / `..` segments). */
60
+ normpath(path: string): string;
61
+ /** Return the home directory of the current user. */
62
+ gethome(): string;
63
+ /** Return the current working directory. */
64
+ getcwd(): string;
65
+ /** Change the working directory to `path`. */
66
+ chdir(path: string): Promise<void>;
67
+ /** Return stat metadata for `path`. */
68
+ stat(path: string, options?: {
69
+ followSymlinks?: boolean;
70
+ }): Promise<StatResult>;
71
+ /** Yield entry names in the directory at `path`. */
72
+ iterdir(path: string): AsyncGenerator<string>;
73
+ /** Yield paths matching `pattern` under `path`. */
74
+ glob(path: string, pattern: string, options?: {
75
+ caseSensitive?: boolean;
76
+ }): AsyncGenerator<string>;
77
+ /** Read up to `n` bytes from `path` (all bytes if `n` is omitted). */
78
+ readBytes(path: string, n?: number): Promise<Buffer>;
79
+ /**
80
+ * Read the file at `path` as a string.
81
+ *
82
+ * `errors` controls how decode errors are handled — mirrors Python's
83
+ * `open(..., errors=)` parameter:
84
+ * - `'strict'` (default): throw on any invalid byte for the encoding
85
+ * - `'replace'`: substitute each invalid byte with U+FFFD (REPLACEMENT CHARACTER)
86
+ * - `'ignore'`: drop invalid bytes silently
87
+ */
88
+ readText(path: string, options?: {
89
+ encoding?: BufferEncoding;
90
+ errors?: 'strict' | 'replace' | 'ignore';
91
+ }): Promise<string>;
92
+ /** Yield lines from the file at `path` one by one. */
93
+ readLines(path: string, options?: {
94
+ encoding?: BufferEncoding;
95
+ errors?: 'strict' | 'replace' | 'ignore';
96
+ }): AsyncGenerator<string>;
97
+ /** Write raw bytes to `path`, returning the number of bytes written. */
98
+ writeBytes(path: string, data: Buffer): Promise<number>;
99
+ /** Write text to `path`, returning the number of characters written. */
100
+ writeText(path: string, data: string, options?: {
101
+ mode?: 'w' | 'a';
102
+ encoding?: BufferEncoding;
103
+ }): Promise<number>;
104
+ /** Create a directory at `path`. */
105
+ mkdir(path: string, options?: {
106
+ parents?: boolean;
107
+ existOk?: boolean;
108
+ }): Promise<void>;
109
+ /** Spawn a process with the given arguments. */
110
+ exec(...args: string[]): Promise<KaosProcess>;
111
+ /** Spawn a process with explicit environment variables. */
112
+ execWithEnv(args: string[], env?: Record<string, string>): Promise<KaosProcess>;
113
+ }
114
+ //#endregion
115
+ //#region src/current.d.ts
116
+ /**
117
+ * Return the {@link Kaos} instance for the current async context.
118
+ *
119
+ * If {@link runWithKaos} has bound an instance for this context it is
120
+ * returned; otherwise a lazily-created {@link LocalKaos} default is used.
121
+ */
122
+ declare function getCurrentKaos(): Kaos;
123
+ declare function runWithKaos<T>(kaos: Kaos, fn: () => T): T;
124
+ /**
125
+ * Token returned by setCurrentKaos, used to restore the previous instance.
126
+ * Mirrors Python's ContextVar Token pattern.
127
+ */
128
+ interface KaosToken {
129
+ readonly previousKaos: Kaos | null;
130
+ }
131
+ /**
132
+ * Set the current kaos instance and return a token for restoring the previous one.
133
+ *
134
+ * Unlike a plain module-level global, this binds the override to the current
135
+ * async context so concurrent tasks do not pollute each other. The returned
136
+ * token can later be passed to {@link resetCurrentKaos} to restore the
137
+ * previously-visible instance, mirroring Python's ContextVar token pattern.
138
+ */
139
+ declare function setCurrentKaos(kaos: Kaos): KaosToken;
140
+ declare function resetCurrentKaos(token: KaosToken): void;
141
+ //#endregion
142
+ //#region src/errors.d.ts
143
+ /**
144
+ * Base error class for the kaos package.
145
+ */
146
+ declare class KaosError extends Error {
147
+ constructor(message: string);
148
+ }
149
+ /**
150
+ * Equivalent to Python's ValueError — indicates an invalid argument was passed.
151
+ */
152
+ declare class KaosValueError extends KaosError {
153
+ constructor(message: string);
154
+ }
155
+ /**
156
+ * Equivalent to Python's FileExistsError — indicates a file or directory already exists.
157
+ */
158
+ declare class KaosFileExistsError extends KaosError {
159
+ constructor(message: string);
160
+ }
161
+ //#endregion
162
+ //#region src/path.d.ts
163
+ /**
164
+ * A path wrapper class that delegates all I/O operations to the current Kaos instance.
165
+ * The path string is interpreted with the path class active at construction time.
166
+ */
167
+ declare class KaosPath {
168
+ private _path;
169
+ private _pathClass;
170
+ constructor(...args: string[]);
171
+ private static _from;
172
+ private _currentKaos;
173
+ /** The final component of this path (like Python's Path.name). */
174
+ get name(): string;
175
+ /** The logical parent of this path (like Python's Path.parent). */
176
+ get parent(): KaosPath;
177
+ isAbsolute(): boolean;
178
+ joinpath(...other: string[]): KaosPath;
179
+ /** Division operator equivalent: join with another path segment. */
180
+ div(other: string | KaosPath): KaosPath;
181
+ /**
182
+ * Canonicalize the path without touching the filesystem.
183
+ * Makes the path absolute (relative to cwd) and resolves '..' segments.
184
+ */
185
+ canonical(): KaosPath;
186
+ /** Compute a relative path from `other` to this path. */
187
+ relativeTo(other: KaosPath): KaosPath;
188
+ /** Expand leading ~ to the home directory. */
189
+ expanduser(): KaosPath;
190
+ static home(): KaosPath;
191
+ static cwd(): KaosPath;
192
+ /** Create a KaosPath from a local filesystem path string. */
193
+ static fromLocalPath(localPath: string): KaosPath;
194
+ /** Return the underlying path string for local filesystem use. */
195
+ toLocalPath(): string;
196
+ toString(): string;
197
+ equals(other: KaosPath): boolean;
198
+ /** Get stat information for this path. */
199
+ stat(options?: {
200
+ followSymlinks?: boolean;
201
+ }): Promise<StatResult>;
202
+ /** Check if this path exists on the filesystem. */
203
+ exists(options?: {
204
+ followSymlinks?: boolean;
205
+ }): Promise<boolean>;
206
+ /** Check if this path points to a regular file. */
207
+ isFile(options?: {
208
+ followSymlinks?: boolean;
209
+ }): Promise<boolean>;
210
+ /** Check if this path points to a directory. */
211
+ isDir(options?: {
212
+ followSymlinks?: boolean;
213
+ }): Promise<boolean>;
214
+ /** Iterate over entries in this directory. */
215
+ iterdir(): AsyncGenerator<KaosPath>;
216
+ /** Glob for entries matching a pattern under this path. */
217
+ glob(pattern: string, options?: {
218
+ caseSensitive?: boolean;
219
+ }): AsyncGenerator<KaosPath>;
220
+ /** Read the file content as a Buffer. */
221
+ readBytes(n?: number): Promise<Buffer>;
222
+ /** Read the file content as a string. */
223
+ readText(options?: {
224
+ encoding?: BufferEncoding;
225
+ errors?: 'strict' | 'replace' | 'ignore';
226
+ }): Promise<string>;
227
+ /** Yield lines from the file one by one. */
228
+ readLines(options?: {
229
+ encoding?: BufferEncoding;
230
+ errors?: 'strict' | 'replace' | 'ignore';
231
+ }): AsyncGenerator<string>;
232
+ /** Write binary data to this path, return the number of bytes written. */
233
+ writeBytes(data: Buffer): Promise<number>;
234
+ /** Write text to this path, return the number of characters written. */
235
+ writeText(data: string, options?: {
236
+ mode?: 'w' | 'a';
237
+ encoding?: BufferEncoding;
238
+ }): Promise<number>;
239
+ /** Append text to this path, return the number of characters written. */
240
+ appendText(data: string, options?: {
241
+ encoding?: BufferEncoding;
242
+ }): Promise<number>;
243
+ /** Create this path as a directory. */
244
+ mkdir(options?: {
245
+ parents?: boolean;
246
+ existOk?: boolean;
247
+ }): Promise<void>;
248
+ }
249
+ //#endregion
250
+ //#region src/local.d.ts
251
+ /**
252
+ * A KAOS implementation that directly interacts with the local filesystem.
253
+ *
254
+ * Note: LocalKaos maintains its own per-instance working directory (`_cwd`)
255
+ * rather than mutating `process.cwd()`. This lets multiple LocalKaos instances
256
+ * coexist with independent cwds (e.g. when switching contexts via
257
+ * `runWithKaos`) without cross-polluting each other's relative-path resolution.
258
+ */
259
+ declare class LocalKaos implements Kaos {
260
+ readonly name: string;
261
+ private _cwd;
262
+ constructor();
263
+ private _resolvePath;
264
+ pathClass(): 'posix' | 'win32';
265
+ normpath(path: string): string;
266
+ gethome(): string;
267
+ getcwd(): string;
268
+ /**
269
+ * Change the working directory of this LocalKaos instance.
270
+ *
271
+ * Unlike Python's `os.chdir`, this is instance-scoped and never touches
272
+ * `process.cwd()`. Child processes spawned via {@link exec} inherit this
273
+ * instance's `_cwd`; concurrent LocalKaos instances each carry their own
274
+ * independent cwd. If you need Python-compatible process-global cwd,
275
+ * call `process.chdir(x)` directly.
276
+ */
277
+ chdir(path: string): Promise<void>;
278
+ stat(path: string, options?: {
279
+ followSymlinks?: boolean;
280
+ }): Promise<StatResult>;
281
+ iterdir(path: string): AsyncGenerator<string>;
282
+ glob(path: string, pattern: string, options?: {
283
+ caseSensitive?: boolean;
284
+ }): AsyncGenerator<string>;
285
+ private _globWalk;
286
+ readBytes(path: string, n?: number): Promise<Buffer>;
287
+ readText(path: string, options?: {
288
+ encoding?: BufferEncoding;
289
+ errors?: 'strict' | 'replace' | 'ignore';
290
+ }): Promise<string>;
291
+ readLines(path: string, options?: {
292
+ encoding?: BufferEncoding;
293
+ errors?: 'strict' | 'replace' | 'ignore';
294
+ }): AsyncGenerator<string>;
295
+ writeBytes(path: string, data: Buffer): Promise<number>;
296
+ writeText(path: string, data: string, options?: {
297
+ mode?: 'w' | 'a';
298
+ encoding?: BufferEncoding;
299
+ }): Promise<number>;
300
+ mkdir(path: string, options?: {
301
+ parents?: boolean;
302
+ existOk?: boolean;
303
+ }): Promise<void>;
304
+ exec(...args: string[]): Promise<KaosProcess>;
305
+ execWithEnv(args: string[], env?: Record<string, string>): Promise<KaosProcess>;
306
+ }
307
+ /** The default local KAOS instance. */
308
+ declare const localKaos: LocalKaos;
309
+ //#endregion
310
+ //#region src/index.d.ts
311
+ declare function readText(path: string, options?: {
312
+ encoding?: BufferEncoding;
313
+ errors?: 'strict' | 'replace' | 'ignore';
314
+ }): Promise<string>;
315
+ declare function writeText(path: string, data: string, options?: {
316
+ mode?: 'w' | 'a';
317
+ encoding?: BufferEncoding;
318
+ }): Promise<number>;
319
+ declare function readLines(path: string, options?: {
320
+ encoding?: BufferEncoding;
321
+ errors?: 'strict' | 'replace' | 'ignore';
322
+ }): AsyncGenerator<string>;
323
+ declare function exec(...args: string[]): Promise<KaosProcess>;
324
+ declare function readBytes(path: string, n?: number): Promise<Buffer>;
325
+ declare function writeBytes(path: string, data: Buffer): Promise<number>;
326
+ declare function stat(path: string, options?: {
327
+ followSymlinks?: boolean;
328
+ }): Promise<StatResult>;
329
+ declare function mkdir(path: string, options?: {
330
+ parents?: boolean;
331
+ existOk?: boolean;
332
+ }): Promise<void>;
333
+ declare function iterdir(path: string): AsyncGenerator<string>;
334
+ declare function glob(path: string, pattern: string, options?: {
335
+ caseSensitive?: boolean;
336
+ }): AsyncGenerator<string>;
337
+ declare function chdir(path: string): Promise<void>;
338
+ declare function getcwd(): string;
339
+ declare function gethome(): string;
340
+ declare function normpath(path: string): string;
341
+ declare function pathClass(): 'posix' | 'win32';
342
+ declare function execWithEnv(args: string[], env?: Record<string, string>): Promise<KaosProcess>;
343
+ //#endregion
344
+ export { type Kaos, KaosError, KaosFileExistsError, KaosPath, type KaosProcess, type KaosToken, KaosValueError, LocalKaos, type StatResult, chdir, exec, execWithEnv, getCurrentKaos, getcwd, gethome, glob, iterdir, localKaos, mkdir, normpath, pathClass, readBytes, readLines, readText, resetCurrentKaos, runWithKaos, setCurrentKaos, stat, writeBytes, writeText };
package/dist/index.mjs ADDED
@@ -0,0 +1,925 @@
1
+ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
2
+ import { dirname as __cjsShimDirname } from 'node:path';
3
+ const __filename = __cjsShimFileURLToPath(import.meta.url);
4
+ const __dirname = __cjsShimDirname(__filename);
5
+ import { AsyncLocalStorage } from "node:async_hooks";
6
+ import { spawn } from "node:child_process";
7
+ import { appendFile, lstat, mkdir as mkdir$1, open, readFile, readdir, stat as stat$1, writeFile } from "node:fs/promises";
8
+ import { homedir } from "node:os";
9
+ import { isAbsolute, join, normalize } from "node:path";
10
+ import { Readable } from "node:stream";
11
+ import * as posixPath from "node:path/posix";
12
+ import * as win32Path from "node:path/win32";
13
+ //#region src/errors.ts
14
+ /**
15
+ * Base error class for the kaos package.
16
+ */
17
+ var KaosError = class extends Error {
18
+ constructor(message) {
19
+ super(message);
20
+ this.name = "KaosError";
21
+ }
22
+ };
23
+ /**
24
+ * Equivalent to Python's ValueError — indicates an invalid argument was passed.
25
+ */
26
+ var KaosValueError = class extends KaosError {
27
+ constructor(message) {
28
+ super(message);
29
+ this.name = "KaosValueError";
30
+ }
31
+ };
32
+ /**
33
+ * Equivalent to Python's FileExistsError — indicates a file or directory already exists.
34
+ */
35
+ var KaosFileExistsError = class extends KaosError {
36
+ constructor(message) {
37
+ super(message);
38
+ this.name = "KaosFileExistsError";
39
+ }
40
+ };
41
+ //#endregion
42
+ //#region src/internal.ts
43
+ function isUtf8Continuation(byte) {
44
+ return byte >= 128 && byte <= 191;
45
+ }
46
+ function decodeUtf8Ignore(data) {
47
+ let output = "";
48
+ let i = 0;
49
+ while (i < data.length) {
50
+ const b0 = data[i];
51
+ if (b0 === void 0) break;
52
+ if (b0 <= 127) {
53
+ output += String.fromCodePoint(b0);
54
+ i += 1;
55
+ continue;
56
+ }
57
+ if (b0 >= 194 && b0 <= 223) {
58
+ const b1 = data[i + 1];
59
+ if (b1 !== void 0 && isUtf8Continuation(b1)) {
60
+ output += String.fromCodePoint((b0 & 31) << 6 | b1 & 63);
61
+ i += 2;
62
+ continue;
63
+ }
64
+ i += 1;
65
+ continue;
66
+ }
67
+ if (b0 >= 224 && b0 <= 239) {
68
+ const b1 = data[i + 1];
69
+ const b2 = data[i + 2];
70
+ if (b1 !== void 0 && (b0 === 224 && b1 >= 160 && b1 <= 191 || b0 >= 225 && b0 <= 236 && isUtf8Continuation(b1) || b0 === 237 && b1 >= 128 && b1 <= 159 || b0 >= 238 && b0 <= 239 && isUtf8Continuation(b1)) && b2 !== void 0 && isUtf8Continuation(b2)) {
71
+ output += String.fromCodePoint((b0 & 15) << 12 | (b1 & 63) << 6 | b2 & 63);
72
+ i += 3;
73
+ continue;
74
+ }
75
+ i += 1;
76
+ continue;
77
+ }
78
+ if (b0 >= 240 && b0 <= 244) {
79
+ const b1 = data[i + 1];
80
+ const b2 = data[i + 2];
81
+ const b3 = data[i + 3];
82
+ if (b1 !== void 0 && (b0 === 240 && b1 >= 144 && b1 <= 191 || b0 >= 241 && b0 <= 243 && isUtf8Continuation(b1) || b0 === 244 && b1 >= 128 && b1 <= 143) && b2 !== void 0 && b3 !== void 0 && isUtf8Continuation(b2) && isUtf8Continuation(b3)) {
83
+ output += String.fromCodePoint((b0 & 7) << 18 | (b1 & 63) << 12 | (b2 & 63) << 6 | b3 & 63);
84
+ i += 4;
85
+ continue;
86
+ }
87
+ i += 1;
88
+ continue;
89
+ }
90
+ i += 1;
91
+ }
92
+ return output;
93
+ }
94
+ function decodeUtf16LeIgnore(data) {
95
+ let output = "";
96
+ let i = 0;
97
+ while (i + 1 < data.length) {
98
+ const first = data[i];
99
+ const second = data[i + 1];
100
+ if (first === void 0 || second === void 0) break;
101
+ const codeUnit = first | second << 8;
102
+ if (codeUnit >= 55296 && codeUnit <= 56319) {
103
+ const lowFirst = data[i + 2];
104
+ const lowSecond = data[i + 3];
105
+ if (lowFirst !== void 0 && lowSecond !== void 0) {
106
+ const low = lowFirst | lowSecond << 8;
107
+ if (low >= 56320 && low <= 57343) {
108
+ const codePoint = 65536 + (codeUnit - 55296 << 10) + (low - 56320);
109
+ output += String.fromCodePoint(codePoint);
110
+ i += 4;
111
+ continue;
112
+ }
113
+ }
114
+ i += 2;
115
+ continue;
116
+ }
117
+ if (codeUnit >= 56320 && codeUnit <= 57343) {
118
+ i += 2;
119
+ continue;
120
+ }
121
+ output += String.fromCodePoint(codeUnit);
122
+ i += 2;
123
+ }
124
+ return output;
125
+ }
126
+ /**
127
+ * Decode a Buffer into a string with Python-compatible `errors` handling.
128
+ *
129
+ * - `'strict'` (default): throw on invalid sequences (via TextDecoder `fatal: true`)
130
+ * - `'replace'`: substitute each invalid sequence with U+FFFD (TextDecoder default)
131
+ * - `'ignore'`: drop invalid input sequences while preserving valid U+FFFD characters
132
+ *
133
+ * Falls back to `Buffer.toString(encoding)` for encodings TextDecoder does not
134
+ * support (e.g. `hex`, `base64`, `binary`, `latin1`) — those are lossless
135
+ * byte-to-character mappings so `errors` has no effect.
136
+ * @internal
137
+ */
138
+ function decodeTextWithErrors(data, encoding, errors = "strict") {
139
+ let webLabel;
140
+ switch (encoding) {
141
+ case "utf-8":
142
+ case "utf8":
143
+ webLabel = "utf-8";
144
+ break;
145
+ case "utf16le":
146
+ case "ucs2":
147
+ case "ucs-2":
148
+ webLabel = "utf-16le";
149
+ break;
150
+ default: webLabel = void 0;
151
+ }
152
+ if (webLabel === void 0) return data.toString(encoding);
153
+ if (errors === "strict") return new TextDecoder(webLabel, { fatal: true }).decode(data);
154
+ if (errors === "ignore") return webLabel === "utf-8" ? decodeUtf8Ignore(data) : decodeUtf16LeIgnore(data);
155
+ return new TextDecoder(webLabel, { fatal: false }).decode(data);
156
+ }
157
+ /**
158
+ * Convert a glob pattern segment (e.g. "*.txt", "file?.log") into a RegExp.
159
+ * Mirrors Python pathlib behavior: includes dotfiles, case-sensitive by default.
160
+ * @internal
161
+ */
162
+ function globPatternToRegex(pattern, caseSensitive) {
163
+ let regex = "^";
164
+ for (let i = 0; i < pattern.length; i++) {
165
+ const ch = pattern[i];
166
+ if (ch === void 0) break;
167
+ switch (ch) {
168
+ case "*":
169
+ regex += "[^/]*";
170
+ break;
171
+ case "?":
172
+ regex += "[^/]";
173
+ break;
174
+ case "[": {
175
+ const end = pattern.indexOf("]", i + 1);
176
+ if (end === -1) regex += "\\[";
177
+ else {
178
+ let charClass = pattern.slice(i + 1, end);
179
+ if (charClass.startsWith("!")) charClass = "^" + charClass.slice(1);
180
+ else if (charClass.startsWith("^")) charClass = "\\" + charClass;
181
+ regex += "[" + charClass + "]";
182
+ i = end;
183
+ }
184
+ break;
185
+ }
186
+ default: regex += ch.replaceAll(/[{}()+.\\^$|]/g, "\\$&");
187
+ }
188
+ }
189
+ regex += "$";
190
+ return new RegExp(regex, caseSensitive ? "" : "i");
191
+ }
192
+ /**
193
+ * A Readable wrapper that preserves source backpressure while still allowing
194
+ * consumers to read buffered output after the source has ended.
195
+ * @internal
196
+ */
197
+ var BufferedReadable = class extends Readable {
198
+ _source;
199
+ _ended = false;
200
+ constructor(source) {
201
+ super({ highWaterMark: 128 * 1024 });
202
+ this._source = source;
203
+ this._source.on("data", this._onData);
204
+ this._source.on("end", this._onEnd);
205
+ this._source.on("close", this._onClose);
206
+ this._source.on("error", this._onError);
207
+ }
208
+ _read() {
209
+ if (!this._ended && !this.destroyed) this._source.resume();
210
+ }
211
+ _destroy(error, callback) {
212
+ this._source.off("data", this._onData);
213
+ this._source.off("end", this._onEnd);
214
+ this._source.off("close", this._onClose);
215
+ this._source.off("error", this._onError);
216
+ callback(error);
217
+ }
218
+ _onData = (chunk) => {
219
+ if (!this.push(chunk)) this._source.pause();
220
+ };
221
+ _onEnd = () => {
222
+ this._ended = true;
223
+ this.push(null);
224
+ };
225
+ _onClose = () => {
226
+ if (!this._ended) {
227
+ this._ended = true;
228
+ this.push(null);
229
+ }
230
+ };
231
+ _onError = (error) => {
232
+ this.destroy(error);
233
+ };
234
+ };
235
+ //#endregion
236
+ //#region src/local.ts
237
+ const isWindows = process.platform === "win32";
238
+ /**
239
+ * Build the `(dev, ino)` cycle-detection key used by `_globWalk`'s
240
+ * visited set. Returns `null` when `ino` is 0, which Node returns on
241
+ * filesystems that don't carry inodes (Windows FAT/exFAT, some SMB/NFS
242
+ * mounts). A null key signals "no reliable identity for this dir" so
243
+ * the caller skips visited tracking for that descent — cycle safety
244
+ * is weakened on those filesystems, but normal walking works instead
245
+ * of every directory colliding on the shared key `"<dev>:0"`.
246
+ */
247
+ function cycleKey(s) {
248
+ if (s.ino === 0) return null;
249
+ return `${String(s.dev)}:${String(s.ino)}`;
250
+ }
251
+ var LocalProcess = class {
252
+ stdin;
253
+ stdout;
254
+ stderr;
255
+ pid;
256
+ _child;
257
+ _exitCode = null;
258
+ _exitPromise;
259
+ constructor(child) {
260
+ if (child.stdin === null || child.stdout === null || child.stderr === null) throw new Error("Process must be created with stdin/stdout/stderr pipes.");
261
+ this._child = child;
262
+ this.stdin = child.stdin;
263
+ this.stdout = new BufferedReadable(child.stdout);
264
+ this.stderr = new BufferedReadable(child.stderr);
265
+ this.pid = child.pid ?? -1;
266
+ this._exitPromise = new Promise((resolve, reject) => {
267
+ child.on("exit", (code) => {
268
+ this._exitCode = code ?? -1;
269
+ resolve(this._exitCode);
270
+ });
271
+ child.on("error", (error) => {
272
+ reject(error);
273
+ });
274
+ });
275
+ }
276
+ get exitCode() {
277
+ return this._exitCode;
278
+ }
279
+ async wait() {
280
+ return this._exitPromise;
281
+ }
282
+ kill(signal) {
283
+ if (this.pid <= 0) return Promise.resolve();
284
+ if (isWindows) {
285
+ const taskkillArgs = signal === "SIGKILL" ? [
286
+ "/T",
287
+ "/F",
288
+ "/PID",
289
+ String(this.pid)
290
+ ] : [
291
+ "/T",
292
+ "/PID",
293
+ String(this.pid)
294
+ ];
295
+ return new Promise((resolve) => {
296
+ const killer = spawn("taskkill", taskkillArgs, {
297
+ stdio: "ignore",
298
+ windowsHide: true
299
+ });
300
+ const done = () => {
301
+ resolve();
302
+ };
303
+ killer.once("error", done);
304
+ killer.once("close", done);
305
+ });
306
+ }
307
+ try {
308
+ process.kill(-this.pid, signal ?? "SIGTERM");
309
+ } catch (error) {
310
+ const err = error;
311
+ if (err.code === "ESRCH") return Promise.resolve();
312
+ if (err.code === "EPERM") {
313
+ try {
314
+ this._child.kill(signal ?? "SIGTERM");
315
+ } catch {}
316
+ return Promise.resolve();
317
+ }
318
+ throw error;
319
+ }
320
+ return Promise.resolve();
321
+ }
322
+ };
323
+ /**
324
+ * A KAOS implementation that directly interacts with the local filesystem.
325
+ *
326
+ * Note: LocalKaos maintains its own per-instance working directory (`_cwd`)
327
+ * rather than mutating `process.cwd()`. This lets multiple LocalKaos instances
328
+ * coexist with independent cwds (e.g. when switching contexts via
329
+ * `runWithKaos`) without cross-polluting each other's relative-path resolution.
330
+ */
331
+ var LocalKaos = class {
332
+ name = "local";
333
+ _cwd;
334
+ constructor() {
335
+ this._cwd = process.cwd();
336
+ }
337
+ _resolvePath(path) {
338
+ if (isAbsolute(path)) return path;
339
+ return join(this._cwd, path);
340
+ }
341
+ pathClass() {
342
+ return isWindows ? "win32" : "posix";
343
+ }
344
+ normpath(path) {
345
+ return normalize(path);
346
+ }
347
+ gethome() {
348
+ return homedir();
349
+ }
350
+ getcwd() {
351
+ return this._cwd;
352
+ }
353
+ /**
354
+ * Change the working directory of this LocalKaos instance.
355
+ *
356
+ * Unlike Python's `os.chdir`, this is instance-scoped and never touches
357
+ * `process.cwd()`. Child processes spawned via {@link exec} inherit this
358
+ * instance's `_cwd`; concurrent LocalKaos instances each carry their own
359
+ * independent cwd. If you need Python-compatible process-global cwd,
360
+ * call `process.chdir(x)` directly.
361
+ */
362
+ async chdir(path) {
363
+ const resolved = this._resolvePath(path);
364
+ if (!(await stat$1(resolved)).isDirectory()) throw new Error(`Not a directory: ${resolved}`);
365
+ this._cwd = resolved;
366
+ }
367
+ async stat(path, options) {
368
+ const resolved = this._resolvePath(path);
369
+ const s = options?.followSymlinks ?? true ? await stat$1(resolved) : await lstat(resolved);
370
+ return {
371
+ stMode: s.mode,
372
+ stIno: s.ino,
373
+ stDev: s.dev,
374
+ stNlink: s.nlink,
375
+ stUid: s.uid,
376
+ stGid: s.gid,
377
+ stSize: s.size,
378
+ stAtime: s.atimeMs / 1e3,
379
+ stMtime: s.mtimeMs / 1e3,
380
+ stCtime: isWindows ? s.birthtimeMs / 1e3 : s.ctimeMs / 1e3
381
+ };
382
+ }
383
+ async *iterdir(path) {
384
+ const resolved = this._resolvePath(path);
385
+ const entries = await readdir(resolved);
386
+ for (const entry of entries) yield join(resolved, entry);
387
+ }
388
+ async *glob(path, pattern, options) {
389
+ const resolved = this._resolvePath(path);
390
+ const caseSensitive = options?.caseSensitive ?? true;
391
+ const patternParts = pattern.split("/");
392
+ const initVisited = /* @__PURE__ */ new Set();
393
+ try {
394
+ const rootKey = cycleKey(await stat$1(resolved));
395
+ if (rootKey !== null) initVisited.add(rootKey);
396
+ } catch {}
397
+ yield* this._globWalk(resolved, patternParts, caseSensitive, initVisited);
398
+ }
399
+ async *_globWalk(basePath, patternParts, caseSensitive, visited) {
400
+ if (patternParts.length === 0) return;
401
+ const [currentPattern, ...remainingParts] = patternParts;
402
+ if (currentPattern === "**") {
403
+ if (remainingParts.length > 0) yield* this._globWalk(basePath, remainingParts, caseSensitive, visited);
404
+ else yield basePath;
405
+ let entries;
406
+ try {
407
+ entries = await readdir(basePath);
408
+ } catch {
409
+ return;
410
+ }
411
+ for (const entry of entries) {
412
+ const fullPath = join(basePath, entry);
413
+ let entryStat;
414
+ try {
415
+ entryStat = await stat$1(fullPath);
416
+ } catch {
417
+ continue;
418
+ }
419
+ if (entryStat.isDirectory()) {
420
+ const key = cycleKey(entryStat);
421
+ if (key !== null && visited.has(key)) continue;
422
+ yield* this._globWalk(fullPath, patternParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
423
+ } else if (remainingParts.length === 0) yield fullPath;
424
+ }
425
+ } else {
426
+ const regex = globPatternToRegex(currentPattern ?? "", caseSensitive);
427
+ let entries;
428
+ try {
429
+ entries = await readdir(basePath);
430
+ } catch {
431
+ return;
432
+ }
433
+ for (const entry of entries) {
434
+ if (!regex.test(entry)) continue;
435
+ const fullPath = join(basePath, entry);
436
+ if (remainingParts.length === 0) yield fullPath;
437
+ else {
438
+ let entryStat;
439
+ try {
440
+ entryStat = await stat$1(fullPath);
441
+ } catch {
442
+ continue;
443
+ }
444
+ if (entryStat.isDirectory()) {
445
+ const key = cycleKey(entryStat);
446
+ if (key !== null && visited.has(key)) continue;
447
+ yield* this._globWalk(fullPath, remainingParts, caseSensitive, key !== null ? new Set([...visited, key]) : visited);
448
+ }
449
+ }
450
+ }
451
+ }
452
+ }
453
+ async readBytes(path, n) {
454
+ const resolved = this._resolvePath(path);
455
+ if (n === void 0) return Buffer.from(await readFile(resolved));
456
+ const fh = await open(resolved, "r");
457
+ try {
458
+ const buf = Buffer.alloc(n);
459
+ const { bytesRead } = await fh.read(buf, 0, n, 0);
460
+ return buf.subarray(0, bytesRead);
461
+ } finally {
462
+ await fh.close();
463
+ }
464
+ }
465
+ async readText(path, options) {
466
+ const resolved = this._resolvePath(path);
467
+ const encoding = options?.encoding ?? "utf-8";
468
+ const errors = options?.errors ?? "strict";
469
+ return decodeTextWithErrors(await readFile(resolved), encoding, errors);
470
+ }
471
+ async *readLines(path, options) {
472
+ const resolved = this._resolvePath(path);
473
+ const encoding = options?.encoding ?? "utf-8";
474
+ const errors = options?.errors ?? "strict";
475
+ const lines = decodeTextWithErrors(await readFile(resolved), encoding, errors).split("\n");
476
+ for (let i = 0; i < lines.length; i++) {
477
+ const line = lines[i];
478
+ if (line === void 0) continue;
479
+ if (i < lines.length - 1) yield line + "\n";
480
+ else if (line !== "") yield line;
481
+ }
482
+ }
483
+ async writeBytes(path, data) {
484
+ await writeFile(this._resolvePath(path), data);
485
+ return data.length;
486
+ }
487
+ async writeText(path, data, options) {
488
+ const resolved = this._resolvePath(path);
489
+ const encoding = options?.encoding ?? "utf-8";
490
+ if ((options?.mode ?? "w") === "a") await appendFile(resolved, data, encoding);
491
+ else await writeFile(resolved, data, encoding);
492
+ return data.length;
493
+ }
494
+ async mkdir(path, options) {
495
+ const resolved = this._resolvePath(path);
496
+ const parents = options?.parents ?? false;
497
+ const existOk = options?.existOk ?? false;
498
+ if (parents) {
499
+ if (!existOk) try {
500
+ if ((await stat$1(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists`);
501
+ } catch (error) {
502
+ if (error instanceof KaosFileExistsError) throw error;
503
+ if (error.code !== "ENOENT") throw error;
504
+ }
505
+ await mkdir$1(resolved, { recursive: true });
506
+ return;
507
+ }
508
+ try {
509
+ await mkdir$1(resolved);
510
+ } catch (error) {
511
+ if (existOk && error instanceof Error && "code" in error && error.code === "EEXIST") {
512
+ if (!(await stat$1(resolved)).isDirectory()) throw new KaosFileExistsError(`${resolved} already exists but is not a directory`);
513
+ return;
514
+ }
515
+ throw error;
516
+ }
517
+ }
518
+ async exec(...args) {
519
+ const command = args[0];
520
+ if (command === void 0) throw new Error("LocalKaos.exec(): at least one argument (the command to run) is required.");
521
+ const child = spawn(command, args.slice(1), {
522
+ cwd: this._cwd,
523
+ stdio: [
524
+ "pipe",
525
+ "pipe",
526
+ "pipe"
527
+ ],
528
+ detached: !isWindows
529
+ });
530
+ await waitForSpawn(child);
531
+ return new LocalProcess(child);
532
+ }
533
+ async execWithEnv(args, env) {
534
+ const command = args[0];
535
+ if (command === void 0) throw new Error("LocalKaos.execWithEnv(): at least one argument (the command to run) is required.");
536
+ const child = spawn(command, args.slice(1), {
537
+ cwd: this._cwd,
538
+ stdio: [
539
+ "pipe",
540
+ "pipe",
541
+ "pipe"
542
+ ],
543
+ detached: !isWindows,
544
+ env
545
+ });
546
+ await waitForSpawn(child);
547
+ return new LocalProcess(child);
548
+ }
549
+ };
550
+ function waitForSpawn(child) {
551
+ return new Promise((resolve, reject) => {
552
+ const onSpawn = () => {
553
+ child.off("error", onError);
554
+ resolve();
555
+ };
556
+ const onError = (err) => {
557
+ child.off("spawn", onSpawn);
558
+ reject(err);
559
+ };
560
+ child.once("spawn", onSpawn);
561
+ child.once("error", onError);
562
+ });
563
+ }
564
+ /** The default local KAOS instance. */
565
+ const localKaos = new LocalKaos();
566
+ //#endregion
567
+ //#region src/current.ts
568
+ const kaosStorage = new AsyncLocalStorage();
569
+ function getDefaultKaos() {
570
+ return localKaos;
571
+ }
572
+ /**
573
+ * Return the {@link Kaos} instance for the current async context.
574
+ *
575
+ * If {@link runWithKaos} has bound an instance for this context it is
576
+ * returned; otherwise a lazily-created {@link LocalKaos} default is used.
577
+ */
578
+ function getCurrentKaos() {
579
+ return kaosStorage.getStore() ?? getDefaultKaos();
580
+ }
581
+ function runWithKaos(kaos, fn) {
582
+ return kaosStorage.run(kaos, fn);
583
+ }
584
+ /**
585
+ * Set the current kaos instance and return a token for restoring the previous one.
586
+ *
587
+ * Unlike a plain module-level global, this binds the override to the current
588
+ * async context so concurrent tasks do not pollute each other. The returned
589
+ * token can later be passed to {@link resetCurrentKaos} to restore the
590
+ * previously-visible instance, mirroring Python's ContextVar token pattern.
591
+ */
592
+ function setCurrentKaos(kaos) {
593
+ const token = { previousKaos: getCurrentKaos() };
594
+ kaosStorage.enterWith(kaos);
595
+ return token;
596
+ }
597
+ function resetCurrentKaos(token) {
598
+ kaosStorage.enterWith(token.previousKaos ?? getDefaultKaos());
599
+ }
600
+ //#endregion
601
+ //#region src/path.ts
602
+ const S_IFMT = 61440;
603
+ const S_IFDIR = 16384;
604
+ const S_IFREG = 32768;
605
+ /**
606
+ * Return the path module matching the current Kaos path class.
607
+ */
608
+ function getPathMod(pathClass = getCurrentKaos().pathClass()) {
609
+ return pathClass === "win32" ? win32Path : posixPath;
610
+ }
611
+ function splitPathLexically(pathMod, path) {
612
+ const root = pathMod.parse(path).root;
613
+ return {
614
+ root,
615
+ parts: (root.length > 0 ? path.slice(root.length) : path).split(pathMod.sep).filter((part) => part.length > 0)
616
+ };
617
+ }
618
+ function splitPosixPart(path) {
619
+ const root = path.startsWith("//") && !path.startsWith("///") ? "//" : path.startsWith("/") ? "/" : "";
620
+ return {
621
+ root,
622
+ parts: (root.length > 0 ? path.slice(root.length) : path).split("/").filter((part) => part.length > 0 && part !== ".")
623
+ };
624
+ }
625
+ function joinPosixPure(parts) {
626
+ let root = "";
627
+ let pathParts = [];
628
+ for (const part of parts) {
629
+ if (part === "") continue;
630
+ const parsed = splitPosixPart(part);
631
+ if (parsed.root !== "") {
632
+ root = parsed.root;
633
+ pathParts = parsed.parts;
634
+ } else pathParts.push(...parsed.parts);
635
+ }
636
+ if (root !== "") return pathParts.length === 0 ? root : root + pathParts.join("/");
637
+ return pathParts.length === 0 ? "." : pathParts.join("/");
638
+ }
639
+ function splitWin32Part(path) {
640
+ const normalized = path.replaceAll("/", "\\");
641
+ const parsed = win32Path.parse(normalized);
642
+ let drive = "";
643
+ let root = "";
644
+ if (/^[A-Za-z]:/.test(parsed.root)) {
645
+ drive = parsed.root.slice(0, 2);
646
+ root = parsed.root.length > 2 ? "\\" : "";
647
+ } else if (parsed.root.startsWith("\\\\")) {
648
+ drive = parsed.root.endsWith("\\") ? parsed.root.slice(0, -1) : parsed.root;
649
+ root = "\\";
650
+ } else if (parsed.root === "\\" || parsed.root === "/") root = "\\";
651
+ const tail = normalized.slice(parsed.root.length);
652
+ return {
653
+ drive,
654
+ root,
655
+ parts: tail.split("\\").filter((part) => part.length > 0 && part !== ".")
656
+ };
657
+ }
658
+ function formatWin32Pure(drive, root, parts) {
659
+ const anchor = drive + root;
660
+ if (anchor !== "") return parts.length === 0 ? anchor : anchor + parts.join("\\");
661
+ return parts.length === 0 ? "." : parts.join("\\");
662
+ }
663
+ function joinWin32Pure(parts) {
664
+ let drive = "";
665
+ let root = "";
666
+ let pathParts = [];
667
+ for (const part of parts) {
668
+ if (part === "") continue;
669
+ const parsed = splitWin32Part(part);
670
+ if (parsed.root !== "") {
671
+ drive = parsed.drive !== "" ? parsed.drive : drive;
672
+ root = parsed.root;
673
+ pathParts = parsed.parts;
674
+ continue;
675
+ }
676
+ if (parsed.drive !== "") {
677
+ if (drive.toLowerCase() !== parsed.drive.toLowerCase()) {
678
+ drive = parsed.drive;
679
+ root = "";
680
+ pathParts = parsed.parts;
681
+ } else pathParts.push(...parsed.parts);
682
+ continue;
683
+ }
684
+ pathParts.push(...parsed.parts);
685
+ }
686
+ return formatWin32Pure(drive, root, pathParts);
687
+ }
688
+ function joinPure(pathClass, parts) {
689
+ return pathClass === "win32" ? joinWin32Pure(parts) : joinPosixPure(parts);
690
+ }
691
+ function isWin32DriveRelative(path) {
692
+ return /^[A-Za-z]:(?:$|[^\\/])/.test(path);
693
+ }
694
+ /**
695
+ * A path wrapper class that delegates all I/O operations to the current Kaos instance.
696
+ * The path string is interpreted with the path class active at construction time.
697
+ */
698
+ var KaosPath = class KaosPath {
699
+ _path;
700
+ _pathClass;
701
+ constructor(...args) {
702
+ this._pathClass = getCurrentKaos().pathClass();
703
+ if (args.length === 0) this._path = ".";
704
+ else this._path = joinPure(this._pathClass, args);
705
+ }
706
+ static _from(path, pathClass) {
707
+ const ret = new KaosPath();
708
+ ret._path = path;
709
+ ret._pathClass = pathClass;
710
+ return ret;
711
+ }
712
+ _currentKaos(operation) {
713
+ const kaos = getCurrentKaos();
714
+ const currentPathClass = kaos.pathClass();
715
+ if (currentPathClass !== this._pathClass) throw new KaosValueError(`Cannot ${operation} ${this._pathClass} path ${this._path} with ${currentPathClass} kaos`);
716
+ if (this._pathClass === "win32" && isWin32DriveRelative(this._path)) throw new KaosValueError(`Cannot ${operation} drive-relative win32 path ${this._path}; use an absolute path like C:\\\\path or a path relative to cwd`);
717
+ return kaos;
718
+ }
719
+ /** The final component of this path (like Python's Path.name). */
720
+ get name() {
721
+ return getPathMod(this._pathClass).basename(this._path);
722
+ }
723
+ /** The logical parent of this path (like Python's Path.parent). */
724
+ get parent() {
725
+ const dir = getPathMod(this._pathClass).dirname(this._path);
726
+ return KaosPath._from(dir, this._pathClass);
727
+ }
728
+ isAbsolute() {
729
+ return getPathMod(this._pathClass).isAbsolute(this._path);
730
+ }
731
+ joinpath(...other) {
732
+ return KaosPath._from(joinPure(this._pathClass, [this._path, ...other]), this._pathClass);
733
+ }
734
+ /** Division operator equivalent: join with another path segment. */
735
+ div(other) {
736
+ if (other instanceof KaosPath && other._pathClass !== this._pathClass) throw new KaosValueError(`Cannot join ${other._pathClass} path to ${this._pathClass} path`);
737
+ const otherStr = other instanceof KaosPath ? other.toString() : other;
738
+ return this.joinpath(otherStr);
739
+ }
740
+ /**
741
+ * Canonicalize the path without touching the filesystem.
742
+ * Makes the path absolute (relative to cwd) and resolves '..' segments.
743
+ */
744
+ canonical() {
745
+ const kaos = this._currentKaos("canonicalize");
746
+ const pathMod = getPathMod(this._pathClass);
747
+ if (pathMod.isAbsolute(this._path)) return KaosPath._from(pathMod.normalize(this._path), this._pathClass);
748
+ const cwd = kaos.getcwd();
749
+ if (!pathMod.isAbsolute(cwd)) throw new KaosValueError(`Cannot canonicalize ${this._path} against non-absolute cwd ${cwd}`);
750
+ const abs = pathMod.resolve(cwd, this._path);
751
+ return KaosPath._from(pathMod.normalize(abs), this._pathClass);
752
+ }
753
+ /** Compute a relative path from `other` to this path. */
754
+ relativeTo(other) {
755
+ if (other._pathClass !== this._pathClass) throw new KaosValueError(`${this._path} is not within ${other.toString()}`);
756
+ const pathMod = getPathMod(this._pathClass);
757
+ const target = splitPathLexically(pathMod, this._path);
758
+ const base = splitPathLexically(pathMod, other.toString());
759
+ if (!(this._pathClass === "win32" ? target.root.toLowerCase() === base.root.toLowerCase() : target.root === base.root)) throw new KaosValueError(`${this._path} is not within ${other.toString()}`);
760
+ if (base.parts.length > target.parts.length) throw new KaosValueError(`${this._path} is not within ${other.toString()}`);
761
+ for (let i = 0; i < base.parts.length; i++) {
762
+ const targetPart = target.parts[i];
763
+ const basePart = base.parts[i];
764
+ if (!(this._pathClass === "win32" ? targetPart?.toLowerCase() === basePart?.toLowerCase() : targetPart === basePart)) throw new KaosValueError(`${this._path} is not within ${other.toString()}`);
765
+ }
766
+ const relParts = target.parts.slice(base.parts.length);
767
+ return KaosPath._from(relParts.length === 0 ? "." : relParts.join(pathMod.sep), this._pathClass);
768
+ }
769
+ /** Expand leading ~ to the home directory. */
770
+ expanduser() {
771
+ if (this._path === "~" || this._path.startsWith("~/") || this._path.startsWith("~\\")) {
772
+ const home = this._currentKaos("expand").gethome();
773
+ if (this._path === "~") return KaosPath._from(home, this._pathClass);
774
+ const rest = this._path.slice(2);
775
+ return KaosPath._from(joinPure(this._pathClass, [home, rest]), this._pathClass);
776
+ }
777
+ return KaosPath._from(this._path, this._pathClass);
778
+ }
779
+ static home() {
780
+ return new KaosPath(getCurrentKaos().gethome());
781
+ }
782
+ static cwd() {
783
+ return new KaosPath(getCurrentKaos().getcwd());
784
+ }
785
+ /** Create a KaosPath from a local filesystem path string. */
786
+ static fromLocalPath(localPath) {
787
+ return new KaosPath(localPath);
788
+ }
789
+ /** Return the underlying path string for local filesystem use. */
790
+ toLocalPath() {
791
+ return this._path;
792
+ }
793
+ toString() {
794
+ return this._path;
795
+ }
796
+ equals(other) {
797
+ return this._pathClass === other._pathClass && this._path === other.toString();
798
+ }
799
+ /** Get stat information for this path. */
800
+ async stat(options) {
801
+ return this._currentKaos("stat").stat(this._path, options);
802
+ }
803
+ /** Check if this path exists on the filesystem. */
804
+ async exists(options) {
805
+ const kaos = this._currentKaos("check");
806
+ try {
807
+ await kaos.stat(this._path, options);
808
+ return true;
809
+ } catch {
810
+ return false;
811
+ }
812
+ }
813
+ /** Check if this path points to a regular file. */
814
+ async isFile(options) {
815
+ const kaos = this._currentKaos("check");
816
+ try {
817
+ return ((await kaos.stat(this._path, options)).stMode & S_IFMT) === S_IFREG;
818
+ } catch {
819
+ return false;
820
+ }
821
+ }
822
+ /** Check if this path points to a directory. */
823
+ async isDir(options) {
824
+ const kaos = this._currentKaos("check");
825
+ try {
826
+ return ((await kaos.stat(this._path, options)).stMode & S_IFMT) === S_IFDIR;
827
+ } catch {
828
+ return false;
829
+ }
830
+ }
831
+ /** Iterate over entries in this directory. */
832
+ async *iterdir() {
833
+ const kaos = this._currentKaos("iterate");
834
+ for await (const entry of kaos.iterdir(this._path)) yield KaosPath._from(entry, this._pathClass);
835
+ }
836
+ /** Glob for entries matching a pattern under this path. */
837
+ async *glob(pattern, options) {
838
+ const kaos = this._currentKaos("glob");
839
+ for await (const match of kaos.glob(this._path, pattern, options)) yield KaosPath._from(match, this._pathClass);
840
+ }
841
+ /** Read the file content as a Buffer. */
842
+ async readBytes(n) {
843
+ return this._currentKaos("read").readBytes(this._path, n);
844
+ }
845
+ /** Read the file content as a string. */
846
+ async readText(options) {
847
+ return this._currentKaos("read").readText(this._path, options);
848
+ }
849
+ /** Yield lines from the file one by one. */
850
+ async *readLines(options) {
851
+ const kaos = this._currentKaos("read");
852
+ for await (const line of kaos.readLines(this._path, options)) yield line;
853
+ }
854
+ /** Write binary data to this path, return the number of bytes written. */
855
+ async writeBytes(data) {
856
+ return this._currentKaos("write").writeBytes(this._path, data);
857
+ }
858
+ /** Write text to this path, return the number of characters written. */
859
+ async writeText(data, options) {
860
+ return this._currentKaos("write").writeText(this._path, data, options);
861
+ }
862
+ /** Append text to this path, return the number of characters written. */
863
+ async appendText(data, options) {
864
+ const kaos = this._currentKaos("append");
865
+ const writeOpts = { mode: "a" };
866
+ if (options?.encoding !== void 0) writeOpts.encoding = options.encoding;
867
+ return kaos.writeText(this._path, data, writeOpts);
868
+ }
869
+ /** Create this path as a directory. */
870
+ async mkdir(options) {
871
+ await this._currentKaos("mkdir").mkdir(this._path, options);
872
+ }
873
+ };
874
+ //#endregion
875
+ //#region src/index.ts
876
+ function readText(path, options) {
877
+ return getCurrentKaos().readText(path, options);
878
+ }
879
+ function writeText(path, data, options) {
880
+ return getCurrentKaos().writeText(path, data, options);
881
+ }
882
+ function readLines(path, options) {
883
+ return getCurrentKaos().readLines(path, options);
884
+ }
885
+ function exec(...args) {
886
+ return getCurrentKaos().exec(...args);
887
+ }
888
+ function readBytes(path, n) {
889
+ return getCurrentKaos().readBytes(path, n);
890
+ }
891
+ function writeBytes(path, data) {
892
+ return getCurrentKaos().writeBytes(path, data);
893
+ }
894
+ function stat(path, options) {
895
+ return getCurrentKaos().stat(path, options);
896
+ }
897
+ function mkdir(path, options) {
898
+ return getCurrentKaos().mkdir(path, options);
899
+ }
900
+ function iterdir(path) {
901
+ return getCurrentKaos().iterdir(path);
902
+ }
903
+ function glob(path, pattern, options) {
904
+ return getCurrentKaos().glob(path, pattern, options);
905
+ }
906
+ function chdir(path) {
907
+ return getCurrentKaos().chdir(path);
908
+ }
909
+ function getcwd() {
910
+ return getCurrentKaos().getcwd();
911
+ }
912
+ function gethome() {
913
+ return getCurrentKaos().gethome();
914
+ }
915
+ function normpath(path) {
916
+ return getCurrentKaos().normpath(path);
917
+ }
918
+ function pathClass() {
919
+ return getCurrentKaos().pathClass();
920
+ }
921
+ function execWithEnv(args, env) {
922
+ return getCurrentKaos().execWithEnv(args, env);
923
+ }
924
+ //#endregion
925
+ export { KaosError, KaosFileExistsError, KaosPath, KaosValueError, LocalKaos, chdir, exec, execWithEnv, getCurrentKaos, getcwd, gethome, glob, iterdir, localKaos, mkdir, normpath, pathClass, readBytes, readLines, readText, resetCurrentKaos, runWithKaos, setCurrentKaos, stat, writeBytes, writeText };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@byfriends/kaos",
3
+ "version": "0.0.1",
4
+ "description": "Execution environment and filesystem/process abstractions for BYF",
5
+ "license": "Proprietary",
6
+ "author": "ByronFinn",
7
+ "homepage": "https://github.com/ByronFinn/byf",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/ByronFinn/byf.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/ByronFinn/byf/issues"
14
+ },
15
+ "keywords": [
16
+ "byf",
17
+ "agent",
18
+ "execution",
19
+ "sandbox",
20
+ "process",
21
+ "filesystem"
22
+ ],
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "type": "module",
27
+ "imports": {
28
+ "#/*": [
29
+ "./src/*.ts",
30
+ "./src/*/index.ts"
31
+ ]
32
+ },
33
+ "exports": {
34
+ ".": {
35
+ "types": "./dist/index.d.mts",
36
+ "import": "./dist/index.mjs",
37
+ "default": "./dist/index.mjs"
38
+ }
39
+ },
40
+ "publishConfig": {
41
+ "access": "public"
42
+ },
43
+ "dependencies": {
44
+ "ssh2": "^1.17.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/ssh2": "^1.15.5"
48
+ },
49
+ "scripts": {
50
+ "build": "tsdown",
51
+ "typecheck": "tsc -p tsconfig.json --noEmit",
52
+ "clean": "rm -rf dist"
53
+ }
54
+ }