@demicodes/shell 0.14.2 → 0.15.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/dist/{host-DIg4RxcL.d.mts → host-B-oKG7x5.d.mts} +9 -0
- package/dist/host-fs.d.mts +2 -24
- package/dist/host-fs.mjs +5 -103
- package/dist/index.d.mts +17 -8
- package/dist/index.mjs +91 -123
- package/dist/{storage-CNMBL9_j.d.mts → storage-DYvEwSc9.d.mts} +1 -1
- package/dist/storage.d.mts +1 -1
- package/package.json +3 -3
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
//#region src/host.d.ts
|
|
2
2
|
interface Host {
|
|
3
3
|
defaultCwd: string;
|
|
4
|
+
/**
|
|
5
|
+
* Directory where command artifacts (stdout.txt / stderr.txt / stdout.bin /
|
|
6
|
+
* meta.json) are written as plain files, laid out as
|
|
7
|
+
* `<dir>/<storageId>/<commandId>/`. Contract: the path is reachable through
|
|
8
|
+
* `fs` AND visible to processes started via `process.spawn` — one shared
|
|
9
|
+
* filesystem namespace, so any tool (portable or real) can read and search
|
|
10
|
+
* artifacts with ordinary file operations.
|
|
11
|
+
*/
|
|
12
|
+
commandArtifactsDir: string;
|
|
4
13
|
fs: HostFileSystem;
|
|
5
14
|
process: HostProcess;
|
|
6
15
|
store: HostStore;
|
package/dist/host-fs.d.mts
CHANGED
|
@@ -1,22 +1,7 @@
|
|
|
1
|
-
import { t as Host } from "./host-
|
|
1
|
+
import { t as Host } from "./host-B-oKG7x5.mjs";
|
|
2
2
|
import { BufferEncoding, CpOptions, DirentEntry, FileContent, FsStat, IFileSystem, MkdirOptions, ReadFileOptions, RmOptions, WriteFileOptions } from "@demicodes/just-bash/fs/interface";
|
|
3
3
|
//#region src/host-fs.d.ts
|
|
4
|
-
type VirtualFileSystemNode = {
|
|
5
|
-
kind: 'file';
|
|
6
|
-
content: Uint8Array;
|
|
7
|
-
mode?: number;
|
|
8
|
-
mtime?: Date;
|
|
9
|
-
} | {
|
|
10
|
-
kind: 'directory';
|
|
11
|
-
entries: DirentEntry[];
|
|
12
|
-
mode?: number;
|
|
13
|
-
mtime?: Date;
|
|
14
|
-
};
|
|
15
|
-
interface VirtualFileSystemProvider {
|
|
16
|
-
lookup(path: string): Promise<VirtualFileSystemNode | null> | VirtualFileSystemNode | null;
|
|
17
|
-
}
|
|
18
4
|
interface HostBackedFileSystemOptions {
|
|
19
|
-
lookup?: VirtualFileSystemProvider['lookup'];
|
|
20
5
|
/**
|
|
21
6
|
* Ceiling for a single file read through this filesystem. Portable commands
|
|
22
7
|
* (cat/grep/rg/head/...) run in-process and load files whole, so one read of
|
|
@@ -27,13 +12,8 @@ interface HostBackedFileSystemOptions {
|
|
|
27
12
|
*/
|
|
28
13
|
maxFileReadBytes?: number;
|
|
29
14
|
}
|
|
30
|
-
/** Builds a virtual directory node; entry names containing a dot are treated as files. */
|
|
31
|
-
declare function virtualDirectory(names: string[]): VirtualFileSystemNode;
|
|
32
|
-
/** Builds a virtual file node from raw bytes. */
|
|
33
|
-
declare function virtualFile(content: Uint8Array): VirtualFileSystemNode;
|
|
34
15
|
declare class HostBackedFileSystem implements IFileSystem {
|
|
35
16
|
private readonly host;
|
|
36
|
-
private readonly options;
|
|
37
17
|
private readonly maxFileReadBytes;
|
|
38
18
|
constructor(host: Host, options?: HostBackedFileSystemOptions);
|
|
39
19
|
readFile(path: string, options?: ReadFileOptions | BufferEncoding): Promise<string>;
|
|
@@ -57,8 +37,6 @@ declare class HostBackedFileSystem implements IFileSystem {
|
|
|
57
37
|
readlink(path: string): Promise<string>;
|
|
58
38
|
realpath(path: string): Promise<string>;
|
|
59
39
|
utimes(path: string, atime: Date, mtime: Date): Promise<void>;
|
|
60
|
-
private lookupVirtual;
|
|
61
|
-
private assertWritablePath;
|
|
62
40
|
}
|
|
63
41
|
//#endregion
|
|
64
|
-
export { HostBackedFileSystem, HostBackedFileSystemOptions
|
|
42
|
+
export { HostBackedFileSystem, HostBackedFileSystemOptions };
|
package/dist/host-fs.mjs
CHANGED
|
@@ -1,83 +1,40 @@
|
|
|
1
1
|
import { decodeUtf8, encodeUtf8, isAbsolutePath, normalizePath } from "@demicodes/utils";
|
|
2
2
|
//#region src/host-fs.ts
|
|
3
3
|
const DEFAULT_FILE_READ_LIMIT_BYTES = 67108864;
|
|
4
|
-
/** Builds a virtual directory node; entry names containing a dot are treated as files. */
|
|
5
|
-
function virtualDirectory(names) {
|
|
6
|
-
return {
|
|
7
|
-
kind: "directory",
|
|
8
|
-
entries: names.sort().map((name) => ({
|
|
9
|
-
name,
|
|
10
|
-
isFile: name.includes("."),
|
|
11
|
-
isDirectory: !name.includes("."),
|
|
12
|
-
isSymbolicLink: false
|
|
13
|
-
}))
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
/** Builds a virtual file node from raw bytes. */
|
|
17
|
-
function virtualFile(content) {
|
|
18
|
-
return {
|
|
19
|
-
kind: "file",
|
|
20
|
-
content
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
4
|
var HostBackedFileSystem = class {
|
|
24
5
|
host;
|
|
25
|
-
options;
|
|
26
6
|
maxFileReadBytes;
|
|
27
7
|
constructor(host, options = {}) {
|
|
28
8
|
this.host = host;
|
|
29
|
-
this.options = options;
|
|
30
9
|
this.maxFileReadBytes = options.maxFileReadBytes ?? DEFAULT_FILE_READ_LIMIT_BYTES;
|
|
31
10
|
}
|
|
32
11
|
async readFile(path, options) {
|
|
33
12
|
return decodeBytes(await this.readFileBuffer(path), encodingFrom(options));
|
|
34
13
|
}
|
|
35
14
|
async readFileBuffer(path) {
|
|
36
|
-
const virtual = await this.lookupVirtual(path);
|
|
37
|
-
if (virtual) {
|
|
38
|
-
if (virtual.kind !== "file") throw new Error(`EISDIR: illegal operation on a directory, read '${path}'`);
|
|
39
|
-
return virtual.content;
|
|
40
|
-
}
|
|
41
15
|
const stat = await this.host.fs.stat(path, { cwd: this.host.defaultCwd });
|
|
42
16
|
if (stat.isFile && stat.size > this.maxFileReadBytes) throw new Error(`EFBIG: file exceeds the ${this.maxFileReadBytes}-byte in-shell read limit (${stat.size} bytes), read '${path}'; read a slice instead, or process it with a real process via 'bash -c'`);
|
|
43
17
|
return this.host.fs.readFile(path, { cwd: this.host.defaultCwd });
|
|
44
18
|
}
|
|
45
19
|
async writeFile(path, content, options) {
|
|
46
|
-
this.assertWritablePath(path);
|
|
47
20
|
await this.host.fs.writeFile(path, encodeContent(content, encodingFrom(options)), { cwd: this.host.defaultCwd });
|
|
48
21
|
}
|
|
49
22
|
async appendFile(path, content, options) {
|
|
50
|
-
this.assertWritablePath(path);
|
|
51
23
|
await this.host.fs.appendFile(path, encodeContent(content, encodingFrom(options)), { cwd: this.host.defaultCwd });
|
|
52
24
|
}
|
|
53
25
|
async exists(path) {
|
|
54
|
-
if (isVirtualPath(path)) return await this.lookupVirtual(path) !== null;
|
|
55
26
|
return this.host.fs.exists(path, { cwd: this.host.defaultCwd });
|
|
56
27
|
}
|
|
57
28
|
async stat(path) {
|
|
58
|
-
const virtual = await this.lookupVirtual(path);
|
|
59
|
-
if (virtual) return virtualStat(virtual);
|
|
60
29
|
return toFsStat(await this.host.fs.stat(path, { cwd: this.host.defaultCwd }));
|
|
61
30
|
}
|
|
62
31
|
async lstat(path) {
|
|
63
|
-
const virtual = await this.lookupVirtual(path);
|
|
64
|
-
if (virtual) return virtualStat(virtual);
|
|
65
32
|
return toFsStat(await this.host.fs.lstat(path, { cwd: this.host.defaultCwd }));
|
|
66
33
|
}
|
|
67
34
|
async readdir(path) {
|
|
68
|
-
const virtual = await this.lookupVirtual(path);
|
|
69
|
-
if (virtual) {
|
|
70
|
-
if (virtual.kind !== "directory") throw new Error(`ENOTDIR: not a directory, scandir '${path}'`);
|
|
71
|
-
return virtual.entries.map((entry) => entry.name);
|
|
72
|
-
}
|
|
73
35
|
return this.host.fs.readdir(path, { cwd: this.host.defaultCwd });
|
|
74
36
|
}
|
|
75
37
|
async readdirWithFileTypes(path) {
|
|
76
|
-
const virtual = await this.lookupVirtual(path);
|
|
77
|
-
if (virtual) {
|
|
78
|
-
if (virtual.kind !== "directory") throw new Error(`ENOTDIR: not a directory, scandir '${path}'`);
|
|
79
|
-
return virtual.entries;
|
|
80
|
-
}
|
|
81
38
|
return (await this.host.fs.readdir(path, {
|
|
82
39
|
cwd: this.host.defaultCwd,
|
|
83
40
|
withFileTypes: true
|
|
@@ -91,14 +48,12 @@ var HostBackedFileSystem = class {
|
|
|
91
48
|
return [];
|
|
92
49
|
}
|
|
93
50
|
async mkdir(path, options) {
|
|
94
|
-
this.assertWritablePath(path);
|
|
95
51
|
await this.host.fs.mkdir(path, {
|
|
96
52
|
cwd: this.host.defaultCwd,
|
|
97
53
|
recursive: options?.recursive
|
|
98
54
|
});
|
|
99
55
|
}
|
|
100
56
|
async rm(path, options) {
|
|
101
|
-
this.assertWritablePath(path);
|
|
102
57
|
await this.host.fs.rm(path, {
|
|
103
58
|
cwd: this.host.defaultCwd,
|
|
104
59
|
recursive: options?.recursive,
|
|
@@ -106,62 +61,32 @@ var HostBackedFileSystem = class {
|
|
|
106
61
|
});
|
|
107
62
|
}
|
|
108
63
|
async cp(src, dest, options) {
|
|
109
|
-
this.assertWritablePath(dest);
|
|
110
|
-
if (isVirtualPath(src)) {
|
|
111
|
-
const virtual = await this.lookupVirtual(src);
|
|
112
|
-
if (!virtual) throw new Error(`ENOENT: no such file or directory, copyfile '${src}'`);
|
|
113
|
-
if (virtual.kind !== "file") throw new Error(`EISDIR: illegal operation on a directory, copyfile '${src}'`);
|
|
114
|
-
await this.host.fs.writeFile(dest, virtual.content, {
|
|
115
|
-
cwd: this.host.defaultCwd,
|
|
116
|
-
createParents: options?.recursive
|
|
117
|
-
});
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
64
|
await this.host.fs.cp(src, dest, {
|
|
121
65
|
cwd: this.host.defaultCwd,
|
|
122
66
|
recursive: options?.recursive
|
|
123
67
|
});
|
|
124
68
|
}
|
|
125
69
|
async mv(src, dest) {
|
|
126
|
-
this.assertWritablePath(src);
|
|
127
|
-
this.assertWritablePath(dest);
|
|
128
70
|
await this.host.fs.mv(src, dest, { cwd: this.host.defaultCwd });
|
|
129
71
|
}
|
|
130
72
|
async chmod(path, mode) {
|
|
131
|
-
this.assertWritablePath(path);
|
|
132
73
|
await this.host.fs.chmod(path, mode, { cwd: this.host.defaultCwd });
|
|
133
74
|
}
|
|
134
75
|
async symlink(target, linkPath) {
|
|
135
|
-
this.assertWritablePath(linkPath);
|
|
136
76
|
await this.host.fs.symlink(target, linkPath, { cwd: this.host.defaultCwd });
|
|
137
77
|
}
|
|
138
78
|
async link(existingPath, newPath) {
|
|
139
|
-
this.assertWritablePath(existingPath);
|
|
140
|
-
this.assertWritablePath(newPath);
|
|
141
79
|
await this.host.fs.link(existingPath, newPath, { cwd: this.host.defaultCwd });
|
|
142
80
|
}
|
|
143
81
|
async readlink(path) {
|
|
144
|
-
if (isVirtualPath(path)) throw new Error(`EINVAL: invalid argument, readlink '${path}'`);
|
|
145
82
|
return this.host.fs.readlink(path, { cwd: this.host.defaultCwd });
|
|
146
83
|
}
|
|
147
84
|
async realpath(path) {
|
|
148
|
-
if (isVirtualPath(path)) {
|
|
149
|
-
if (!await this.lookupVirtual(path)) throw new Error(`ENOENT: no such file or directory, realpath '${path}'`);
|
|
150
|
-
return normalizePath(path);
|
|
151
|
-
}
|
|
152
85
|
return this.host.fs.realpath(path, { cwd: this.host.defaultCwd });
|
|
153
86
|
}
|
|
154
87
|
async utimes(path, atime, mtime) {
|
|
155
|
-
this.assertWritablePath(path);
|
|
156
88
|
await this.host.fs.utimes(path, atime, mtime, { cwd: this.host.defaultCwd });
|
|
157
89
|
}
|
|
158
|
-
async lookupVirtual(path) {
|
|
159
|
-
if (!this.options.lookup || !isVirtualPath(path)) return null;
|
|
160
|
-
return await this.options.lookup(normalizePath(path)) ?? null;
|
|
161
|
-
}
|
|
162
|
-
assertWritablePath(path) {
|
|
163
|
-
if (isVirtualPath(path)) throw new Error(`EROFS: read-only virtual filesystem, '${path}'`);
|
|
164
|
-
}
|
|
165
90
|
};
|
|
166
91
|
function encodingFrom(options) {
|
|
167
92
|
if (!options) return void 0;
|
|
@@ -171,13 +96,13 @@ function encodingFrom(options) {
|
|
|
171
96
|
function encodeContent(content, encoding) {
|
|
172
97
|
if (content instanceof Uint8Array) return content;
|
|
173
98
|
if (encoding === "binary" || encoding === "latin1") return latin1ToBytes(content);
|
|
174
|
-
if (encoding === "base64") return base64ToBytes
|
|
99
|
+
if (encoding === "base64") return base64ToBytes(content);
|
|
175
100
|
if (encoding === "hex") return hexToBytes(content);
|
|
176
101
|
return encodeUtf8(content);
|
|
177
102
|
}
|
|
178
103
|
function decodeBytes(bytes, encoding) {
|
|
179
104
|
if (encoding === "binary" || encoding === "latin1") return bytesToLatin1(bytes);
|
|
180
|
-
if (encoding === "base64") return bytesToBase64
|
|
105
|
+
if (encoding === "base64") return bytesToBase64(bytes);
|
|
181
106
|
if (encoding === "hex") return [...bytes].map((byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
182
107
|
return decodeUtf8(bytes);
|
|
183
108
|
}
|
|
@@ -191,10 +116,10 @@ function bytesToLatin1(bytes) {
|
|
|
191
116
|
for (let index = 0; index < bytes.length; index += 8192) result += String.fromCharCode(...bytes.subarray(index, index + 8192));
|
|
192
117
|
return result;
|
|
193
118
|
}
|
|
194
|
-
function base64ToBytes
|
|
119
|
+
function base64ToBytes(content) {
|
|
195
120
|
return latin1ToBytes(atob(content));
|
|
196
121
|
}
|
|
197
|
-
function bytesToBase64
|
|
122
|
+
function bytesToBase64(bytes) {
|
|
198
123
|
return btoa(bytesToLatin1(bytes));
|
|
199
124
|
}
|
|
200
125
|
function hexToBytes(content) {
|
|
@@ -220,28 +145,5 @@ function toDirentEntry(value) {
|
|
|
220
145
|
isSymbolicLink: value.isSymbolicLink
|
|
221
146
|
};
|
|
222
147
|
}
|
|
223
|
-
function virtualStat(node) {
|
|
224
|
-
const now = /* @__PURE__ */ new Date(0);
|
|
225
|
-
if (node.kind === "directory") return {
|
|
226
|
-
isFile: false,
|
|
227
|
-
isDirectory: true,
|
|
228
|
-
isSymbolicLink: false,
|
|
229
|
-
mode: node.mode ?? 365,
|
|
230
|
-
size: 0,
|
|
231
|
-
mtime: node.mtime ?? now
|
|
232
|
-
};
|
|
233
|
-
return {
|
|
234
|
-
isFile: true,
|
|
235
|
-
isDirectory: false,
|
|
236
|
-
isSymbolicLink: false,
|
|
237
|
-
mode: node.mode ?? 292,
|
|
238
|
-
size: node.content.byteLength,
|
|
239
|
-
mtime: node.mtime ?? now
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
function isVirtualPath(path) {
|
|
243
|
-
const normalized = normalizePath(path);
|
|
244
|
-
return normalized === "/@" || normalized.startsWith("/@/");
|
|
245
|
-
}
|
|
246
148
|
//#endregion
|
|
247
|
-
export { HostBackedFileSystem
|
|
149
|
+
export { HostBackedFileSystem };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as HostProcess, c as HostSpawnHandle, i as HostFileSystem, l as HostSpawnParams, n as HostDirent, o as HostProcessOutputChunk, r as HostFileStat, s as HostSpawnExit, t as Host, u as HostStore } from "./host-
|
|
2
|
-
import { HostBackedFileSystem, HostBackedFileSystemOptions
|
|
3
|
-
import { _ as runRegisteredCommand, a as CommandIO, c as CommandRegistry, d as CommandStdin, f as CommandStorage, g as renderCommandHelp, h as parseCommandInput, i as CommandExecutionContext, l as CommandRunContext, m as emptyStdin, n as COMMAND_HELP_DEFAULTS, o as CommandInputSpec, p as ParsedCommandInput, r as Command, s as CommandOutputSpec, t as AgentSessionCommandStorage, u as CommandRunResult } from "./storage-
|
|
1
|
+
import { a as HostProcess, c as HostSpawnHandle, i as HostFileSystem, l as HostSpawnParams, n as HostDirent, o as HostProcessOutputChunk, r as HostFileStat, s as HostSpawnExit, t as Host, u as HostStore } from "./host-B-oKG7x5.mjs";
|
|
2
|
+
import { HostBackedFileSystem, HostBackedFileSystemOptions } from "./host-fs.mjs";
|
|
3
|
+
import { _ as runRegisteredCommand, a as CommandIO, c as CommandRegistry, d as CommandStdin, f as CommandStorage, g as renderCommandHelp, h as parseCommandInput, i as CommandExecutionContext, l as CommandRunContext, m as emptyStdin, n as COMMAND_HELP_DEFAULTS, o as CommandInputSpec, p as ParsedCommandInput, r as Command, s as CommandOutputSpec, t as AgentSessionCommandStorage, u as CommandRunResult } from "./storage-DYvEwSc9.mjs";
|
|
4
4
|
import { CommandName } from "@demicodes/just-bash/commands";
|
|
5
5
|
import { Interpreter, InterpreterState } from "@demicodes/just-bash/interpreter";
|
|
6
6
|
import { CommandRegistry as CommandRegistry$1, ExecResult } from "@demicodes/just-bash/types";
|
|
@@ -227,6 +227,8 @@ type ShellCommandStatus = {
|
|
|
227
227
|
status: 'exited';
|
|
228
228
|
shellId: string;
|
|
229
229
|
commandId: string;
|
|
230
|
+
/** Real directory holding this command's artifact files. */
|
|
231
|
+
artifactDir: string;
|
|
230
232
|
exitCode: number;
|
|
231
233
|
stdout: ShellStreamView;
|
|
232
234
|
stderr: ShellStreamView;
|
|
@@ -241,6 +243,7 @@ type ShellCommandStatus = {
|
|
|
241
243
|
status: 'running';
|
|
242
244
|
shellId: string;
|
|
243
245
|
commandId: string;
|
|
246
|
+
artifactDir: string;
|
|
244
247
|
stdout: ShellStreamView;
|
|
245
248
|
stderr: ShellStreamView;
|
|
246
249
|
output: ShellOutputView;
|
|
@@ -250,6 +253,7 @@ type ShellCommandStatus = {
|
|
|
250
253
|
status: 'aborted';
|
|
251
254
|
shellId: string;
|
|
252
255
|
commandId: string;
|
|
256
|
+
artifactDir: string;
|
|
253
257
|
stdout: ShellStreamView;
|
|
254
258
|
stderr: ShellStreamView;
|
|
255
259
|
output: ShellOutputView;
|
|
@@ -304,11 +308,7 @@ declare class BashEnvironment {
|
|
|
304
308
|
private collectAborted;
|
|
305
309
|
private collectAbortedWithoutForeground;
|
|
306
310
|
private commandStatus;
|
|
307
|
-
private lookupVirtualArtifact;
|
|
308
|
-
private commandArtifactIds;
|
|
309
|
-
private commandArtifact;
|
|
310
311
|
private persistCommandArtifact;
|
|
311
|
-
private syncRunningRecord;
|
|
312
312
|
}
|
|
313
313
|
//#endregion
|
|
314
314
|
//#region src/portable-commands.d.ts
|
|
@@ -329,6 +329,15 @@ declare class BashEnvironment {
|
|
|
329
329
|
* we call portable.
|
|
330
330
|
*/
|
|
331
331
|
declare const DEMI_PORTABLE_COMMANDS: readonly CommandName[];
|
|
332
|
+
/**
|
|
333
|
+
* Portable commands routed real-binary-first (`preferHostSpawn`): recursive
|
|
334
|
+
* tree scanners whose in-process implementations read files whole and burn
|
|
335
|
+
* the embedding host's main thread for minutes on large trees. A real
|
|
336
|
+
* process runs off-thread with its output bounded by the capture limit;
|
|
337
|
+
* hosts without the binary fall back to the portable implementation on
|
|
338
|
+
* first use.
|
|
339
|
+
*/
|
|
340
|
+
declare const HOST_PREFERRED_SCAN_COMMANDS: ReadonlySet<string>;
|
|
332
341
|
/**
|
|
333
342
|
* Names registered commands must not shadow, derived from the actual portable
|
|
334
343
|
* command set plus interpreter builtins and pass-through system tools — not a
|
|
@@ -342,4 +351,4 @@ declare function shellQuote(value: string): string;
|
|
|
342
351
|
/** Picks a heredoc delimiter that does not collide with any line already in `body`. */
|
|
343
352
|
declare function heredocDelimiter(body: string): string;
|
|
344
353
|
//#endregion
|
|
345
|
-
export { AgentSessionCommandStorage, BashAuditEvent, BashEnvironment, BashEnvironmentOptions, BinaryStdout, COMMAND_HELP_DEFAULTS, Command, CommandExecutionContext, CommandIO, CommandInputSpec, CommandMetadataRecord, CommandOutputSpec, CommandRegistry, CommandRunContext, CommandRunResult, CommandStdin, CommandStorage, DEMI_PORTABLE_COMMANDS, Host, HostBackedFileSystem, HostBackedFileSystemOptions, HostDirent, HostFileStat, HostFileSystem, HostProcess, HostProcessOutputChunk, HostSpawnExit, HostSpawnHandle, HostSpawnParams, HostStore, MAX_TIMEOUT_MS, ParsedCommandInput, RESERVED_COMMAND_NAMES, ShellAbortInput, ShellCommandStatus, ShellExecInput, ShellOutputChunk, ShellOutputRecordChunk, ShellOutputView, ShellStatusInput, ShellStreamView, ShellWriteInput,
|
|
354
|
+
export { AgentSessionCommandStorage, BashAuditEvent, BashEnvironment, BashEnvironmentOptions, BinaryStdout, COMMAND_HELP_DEFAULTS, Command, CommandExecutionContext, CommandIO, CommandInputSpec, CommandMetadataRecord, CommandOutputSpec, CommandRegistry, CommandRunContext, CommandRunResult, CommandStdin, CommandStorage, DEMI_PORTABLE_COMMANDS, HOST_PREFERRED_SCAN_COMMANDS, Host, HostBackedFileSystem, HostBackedFileSystemOptions, HostDirent, HostFileStat, HostFileSystem, HostProcess, HostProcessOutputChunk, HostSpawnExit, HostSpawnHandle, HostSpawnParams, HostStore, MAX_TIMEOUT_MS, ParsedCommandInput, RESERVED_COMMAND_NAMES, ShellAbortInput, ShellCommandStatus, ShellExecInput, ShellOutputChunk, ShellOutputRecordChunk, ShellOutputView, ShellStatusInput, ShellStreamView, ShellWriteInput, emptyStdin, heredocDelimiter, parseCommandInput, renderCommandHelp, runRegisteredCommand, shellQuote };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { HostBackedFileSystem
|
|
1
|
+
import { HostBackedFileSystem } from "./host-fs.mjs";
|
|
2
2
|
import { AgentSessionCommandStorage } from "./storage.mjs";
|
|
3
|
-
import { asError,
|
|
3
|
+
import { asError, concatBytes, decodeLatin1, decodeUtf8, decodeUtf8Strict, encodeLatin1, encodeUtf8, tail, utf8Bytes, utf8Slice } from "@demicodes/utils";
|
|
4
4
|
import { createLazyCommands, getCommandNames } from "@demicodes/just-bash/commands";
|
|
5
5
|
import { ArithmeticError, BadSubstitutionError, ExecutionLimitError, ExitError, Interpreter } from "@demicodes/just-bash/interpreter";
|
|
6
6
|
import { decodeBytesToUtf8, unsafeBytesFromLatin1 } from "@demicodes/just-bash/encoding";
|
|
@@ -46,6 +46,19 @@ const REAL_SPAWN_DEPENDENT_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
46
46
|
* we call portable.
|
|
47
47
|
*/
|
|
48
48
|
const DEMI_PORTABLE_COMMANDS = getCommandNames().filter((name) => !REAL_SPAWN_DEPENDENT_COMMANDS.has(name));
|
|
49
|
+
/**
|
|
50
|
+
* Portable commands routed real-binary-first (`preferHostSpawn`): recursive
|
|
51
|
+
* tree scanners whose in-process implementations read files whole and burn
|
|
52
|
+
* the embedding host's main thread for minutes on large trees. A real
|
|
53
|
+
* process runs off-thread with its output bounded by the capture limit;
|
|
54
|
+
* hosts without the binary fall back to the portable implementation on
|
|
55
|
+
* first use.
|
|
56
|
+
*/
|
|
57
|
+
const HOST_PREFERRED_SCAN_COMMANDS = /* @__PURE__ */ new Set([
|
|
58
|
+
"rg",
|
|
59
|
+
"grep",
|
|
60
|
+
"find"
|
|
61
|
+
]);
|
|
49
62
|
/** Shell language words and builtins the interpreter itself owns. */
|
|
50
63
|
const SHELL_BUILTIN_NAMES = [
|
|
51
64
|
".",
|
|
@@ -618,39 +631,63 @@ function appendVisibleChunk(foreground, targetFd, text, byteLength) {
|
|
|
618
631
|
//#endregion
|
|
619
632
|
//#region src/command-artifact-store.ts
|
|
620
633
|
/**
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
634
|
+
* Writes shell command artifacts as plain files under
|
|
635
|
+
* `host.commandArtifactsDir/<storageId>/<commandId>/` — one shared filesystem
|
|
636
|
+
* namespace with the processes the shell spawns, so any tool (portable or
|
|
637
|
+
* real) reads and searches artifacts with ordinary file operations.
|
|
625
638
|
*/
|
|
626
639
|
var CommandArtifactStore = class {
|
|
627
|
-
|
|
628
|
-
storageById = /* @__PURE__ */ new Map();
|
|
640
|
+
host;
|
|
629
641
|
released = /* @__PURE__ */ new Set();
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
this.storageById.set(commandStorageId, storage);
|
|
639
|
-
return storage;
|
|
640
|
-
}
|
|
641
|
-
/** Whether a command's artifact has been released (tombstoned). */
|
|
642
|
+
writeChains = /* @__PURE__ */ new Map();
|
|
643
|
+
constructor(host) {
|
|
644
|
+
this.host = host;
|
|
645
|
+
}
|
|
646
|
+
dirFor(commandStorageId, commandId) {
|
|
647
|
+
return `${this.host.commandArtifactsDir}/${commandStorageId}/${commandId}`;
|
|
648
|
+
}
|
|
649
|
+
/** Whether a command's artifact has been released (removed from disk). */
|
|
642
650
|
isReleased(commandStorageId, commandId) {
|
|
643
651
|
return this.released.has(this.key(commandStorageId, commandId));
|
|
644
652
|
}
|
|
645
|
-
/** Persists
|
|
646
|
-
persist(commandStorageId, commandId,
|
|
647
|
-
|
|
648
|
-
this.
|
|
653
|
+
/** Persists the artifact files unless the command has already been released. */
|
|
654
|
+
persist(commandStorageId, commandId, files) {
|
|
655
|
+
const key = this.key(commandStorageId, commandId);
|
|
656
|
+
if (this.released.has(key)) return;
|
|
657
|
+
const dir = this.dirFor(commandStorageId, commandId);
|
|
658
|
+
this.chain(key, async () => {
|
|
659
|
+
if (this.released.has(key)) return;
|
|
660
|
+
const writeOptions = {
|
|
661
|
+
cwd: this.host.defaultCwd,
|
|
662
|
+
createParents: true
|
|
663
|
+
};
|
|
664
|
+
await this.host.fs.writeFile(`${dir}/meta.json`, encodeUtf8(files.meta), writeOptions);
|
|
665
|
+
await this.host.fs.writeFile(`${dir}/stdout.txt`, encodeUtf8(files.stdout), writeOptions);
|
|
666
|
+
await this.host.fs.writeFile(`${dir}/stderr.txt`, encodeUtf8(files.stderr), writeOptions);
|
|
667
|
+
if (files.stdoutBin) await this.host.fs.writeFile(`${dir}/stdout.bin`, files.stdoutBin, writeOptions);
|
|
668
|
+
});
|
|
649
669
|
}
|
|
650
|
-
/** Tombstones a command and removes its
|
|
670
|
+
/** Tombstones a command and removes its artifact directory. */
|
|
651
671
|
async release(commandStorageId, commandId) {
|
|
652
|
-
this.
|
|
653
|
-
|
|
672
|
+
const key = this.key(commandStorageId, commandId);
|
|
673
|
+
this.released.add(key);
|
|
674
|
+
await new Promise((resolve) => {
|
|
675
|
+
this.chain(key, async () => {
|
|
676
|
+
await this.host.fs.rm(this.dirFor(commandStorageId, commandId), {
|
|
677
|
+
cwd: this.host.defaultCwd,
|
|
678
|
+
recursive: true,
|
|
679
|
+
force: true
|
|
680
|
+
}).catch(() => {});
|
|
681
|
+
resolve();
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
}
|
|
685
|
+
chain(key, work) {
|
|
686
|
+
const next = (this.writeChains.get(key) ?? Promise.resolve()).then(work).catch(() => {});
|
|
687
|
+
this.writeChains.set(key, next);
|
|
688
|
+
next.finally(() => {
|
|
689
|
+
if (this.writeChains.get(key) === next) this.writeChains.delete(key);
|
|
690
|
+
});
|
|
654
691
|
}
|
|
655
692
|
key(commandStorageId, commandId) {
|
|
656
693
|
return `${commandStorageId}\0${commandId}`;
|
|
@@ -811,7 +848,7 @@ var BashEnvironment = class {
|
|
|
811
848
|
artifacts;
|
|
812
849
|
constructor(options) {
|
|
813
850
|
this.host = options.host;
|
|
814
|
-
this.artifacts = new CommandArtifactStore(this.host
|
|
851
|
+
this.artifacts = new CommandArtifactStore(this.host);
|
|
815
852
|
this.commands = options.commands ?? new CommandRegistry();
|
|
816
853
|
this.shellIdFactory = options.shellIdFactory ?? (() => globalThis.crypto.randomUUID());
|
|
817
854
|
this.commandIdFactory = options.commandIdFactory ?? (() => globalThis.crypto.randomUUID());
|
|
@@ -949,7 +986,7 @@ var BashEnvironment = class {
|
|
|
949
986
|
const id = this.shellIdFactory();
|
|
950
987
|
const commandStorageId = agentSessionId ?? id;
|
|
951
988
|
const cwd = initialCwd ?? this.host.defaultCwd;
|
|
952
|
-
const fs = new HostBackedFileSystem(this.host
|
|
989
|
+
const fs = new HostBackedFileSystem(this.host);
|
|
953
990
|
const env = /* @__PURE__ */ new Map();
|
|
954
991
|
for (const [key, value] of Object.entries(this.initialEnv)) env.set(key, value);
|
|
955
992
|
env.set("PWD", cwd);
|
|
@@ -1115,6 +1152,7 @@ var BashEnvironment = class {
|
|
|
1115
1152
|
id,
|
|
1116
1153
|
shellId: session.id,
|
|
1117
1154
|
commandStorageId: session.commandStorageId,
|
|
1155
|
+
artifactDir: this.artifacts.dirFor(session.commandStorageId, id),
|
|
1118
1156
|
script,
|
|
1119
1157
|
startedAt: now,
|
|
1120
1158
|
lastOutputAt: now,
|
|
@@ -1463,7 +1501,8 @@ var BashEnvironment = class {
|
|
|
1463
1501
|
totalBytes: bytes.length,
|
|
1464
1502
|
limitBytes: cap
|
|
1465
1503
|
};
|
|
1466
|
-
stdoutText = `<binary stdout: ${bytes.length} bytes${truncated ? `, exceeds the ${cap}-byte binary limit` : ""}; raw bytes at
|
|
1504
|
+
stdoutText = `<binary stdout: ${bytes.length} bytes${truncated ? `, exceeds the ${cap}-byte binary limit` : ""}; raw bytes at ${record.artifactDir}/stdout.bin>\n`;
|
|
1505
|
+
record.pendingBinaryArtifact = bytes;
|
|
1467
1506
|
}
|
|
1468
1507
|
}
|
|
1469
1508
|
const stderrText = foreground ? resultOrError.stderr : decodeBytesToUtf8(unsafeBytesFromLatin1(resultOrError.stderr));
|
|
@@ -1543,6 +1582,7 @@ var BashEnvironment = class {
|
|
|
1543
1582
|
const base = {
|
|
1544
1583
|
shellId: record.shellId,
|
|
1545
1584
|
commandId: record.id,
|
|
1585
|
+
artifactDir: record.artifactDir,
|
|
1546
1586
|
stdout,
|
|
1547
1587
|
stderr,
|
|
1548
1588
|
output,
|
|
@@ -1570,70 +1610,24 @@ var BashEnvironment = class {
|
|
|
1570
1610
|
status: "running"
|
|
1571
1611
|
};
|
|
1572
1612
|
}
|
|
1573
|
-
async lookupVirtualArtifact(commandStorageId, path) {
|
|
1574
|
-
const parts = path.split("/").filter(Boolean);
|
|
1575
|
-
if (parts.length === 1 && parts[0] === "@") return virtualDirectory(["commands"]);
|
|
1576
|
-
if (parts.length === 2 && parts[0] === "@" && parts[1] === "commands") return virtualDirectory(await this.commandArtifactIds(commandStorageId));
|
|
1577
|
-
if (parts.length === 3 && parts[0] === "@" && parts[1] === "commands") {
|
|
1578
|
-
const artifact = await this.commandArtifact(commandStorageId, parts[2]);
|
|
1579
|
-
if (!artifact) return null;
|
|
1580
|
-
const entries = [
|
|
1581
|
-
"meta.json",
|
|
1582
|
-
"stderr.txt",
|
|
1583
|
-
"stdout.txt"
|
|
1584
|
-
];
|
|
1585
|
-
if (artifact.stdoutBinary) entries.push("stdout.bin");
|
|
1586
|
-
return virtualDirectory(entries);
|
|
1587
|
-
}
|
|
1588
|
-
if (parts.length !== 4 || parts[0] !== "@" || parts[1] !== "commands") return null;
|
|
1589
|
-
const artifact = await this.commandArtifact(commandStorageId, parts[2]);
|
|
1590
|
-
if (!artifact) return null;
|
|
1591
|
-
const fileName = parts[3];
|
|
1592
|
-
if (fileName === "stdout.txt") return virtualFile(encodeUtf8(artifact.stdout));
|
|
1593
|
-
if (fileName === "stdout.bin" && artifact.stdoutBinary) return virtualFile(base64ToBytes(artifact.stdoutBinary.base64));
|
|
1594
|
-
if (fileName === "stderr.txt") return virtualFile(encodeUtf8(artifact.stderr));
|
|
1595
|
-
if (fileName === "meta.json") return virtualFile(encodeUtf8(`${JSON.stringify(commandArtifactMeta(artifact), null, 2)}\n`));
|
|
1596
|
-
return null;
|
|
1597
|
-
}
|
|
1598
|
-
async commandArtifactIds(commandStorageId) {
|
|
1599
|
-
const ids = /* @__PURE__ */ new Set();
|
|
1600
|
-
for (const record of this.commandsById.values()) if (record.commandStorageId === commandStorageId && !this.artifacts.isReleased(commandStorageId, record.id)) ids.add(record.id);
|
|
1601
|
-
const keys = await this.artifacts.storageFor(commandStorageId).list("commands").catch(() => []);
|
|
1602
|
-
for (const key of keys) {
|
|
1603
|
-
const match = /^commands\/([^/]+)\/artifact\.json$/.exec(key);
|
|
1604
|
-
if (match && !this.artifacts.isReleased(commandStorageId, match[1])) ids.add(match[1]);
|
|
1605
|
-
}
|
|
1606
|
-
return [...ids];
|
|
1607
|
-
}
|
|
1608
|
-
async commandArtifact(commandStorageId, commandId) {
|
|
1609
|
-
if (this.artifacts.isReleased(commandStorageId, commandId)) return null;
|
|
1610
|
-
const record = this.commandsById.get(commandId);
|
|
1611
|
-
if (record?.commandStorageId === commandStorageId) {
|
|
1612
|
-
this.syncRunningRecord(record);
|
|
1613
|
-
return commandArtifactFromRecord(record);
|
|
1614
|
-
}
|
|
1615
|
-
const value = await this.artifacts.storageFor(commandStorageId).readJson(`commands/${commandId}/artifact.json`).catch(() => null);
|
|
1616
|
-
return isCommandArtifact(value) ? value : null;
|
|
1617
|
-
}
|
|
1618
1613
|
persistCommandArtifact(record) {
|
|
1619
1614
|
const fingerprint = `${record.status}:${record.exitCode ?? ""}:${record.stdout.length}:${record.stderr.length}:${record.binaryStdout?.totalBytes ?? ""}`;
|
|
1620
1615
|
if (record.persistedFingerprint === fingerprint) return;
|
|
1621
1616
|
record.persistedFingerprint = fingerprint;
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
record.
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
record.lastOutputAt = foreground.lastOutputAt;
|
|
1631
|
-
}
|
|
1617
|
+
const stdoutBin = record.pendingBinaryArtifact;
|
|
1618
|
+
record.pendingBinaryArtifact = void 0;
|
|
1619
|
+
this.artifacts.persist(record.commandStorageId, record.id, {
|
|
1620
|
+
meta: `${JSON.stringify(commandArtifactMeta(record), null, 2)}\n`,
|
|
1621
|
+
stdout: record.stdout,
|
|
1622
|
+
stderr: record.stderr,
|
|
1623
|
+
...stdoutBin ? { stdoutBin } : {}
|
|
1624
|
+
});
|
|
1632
1625
|
}
|
|
1633
1626
|
};
|
|
1634
1627
|
function createPortableCommands(session) {
|
|
1635
1628
|
return createLazyCommands([...DEMI_PORTABLE_COMMANDS]).map((command) => ({
|
|
1636
1629
|
...command,
|
|
1630
|
+
preferHostSpawn: HOST_PREFERRED_SCAN_COMMANDS.has(command.name),
|
|
1637
1631
|
execute: async (args, ctx) => {
|
|
1638
1632
|
const result = await command.execute(args, ctx);
|
|
1639
1633
|
session.accumulator.audit.push({
|
|
@@ -1668,7 +1662,7 @@ function streamView(record, stream, explicitOffset, maxOutputBytes) {
|
|
|
1668
1662
|
if (explicitOffset === void 0) if (stream === "stdout") record.stdoutOffset = nextOffset;
|
|
1669
1663
|
else record.stderrOffset = nextOffset;
|
|
1670
1664
|
return {
|
|
1671
|
-
path:
|
|
1665
|
+
path: `${record.artifactDir}/${stream}.txt`,
|
|
1672
1666
|
offset: nextOffset,
|
|
1673
1667
|
delta,
|
|
1674
1668
|
tail: tailString(text),
|
|
@@ -1703,7 +1697,7 @@ function mergedOutputView(record, explicitOffset, maxOutputBytes) {
|
|
|
1703
1697
|
const truncated = nextOffset < totalBytes;
|
|
1704
1698
|
if (explicitOffset === void 0) record.outputOffset = nextOffset;
|
|
1705
1699
|
return {
|
|
1706
|
-
path:
|
|
1700
|
+
path: record.artifactDir,
|
|
1707
1701
|
offset: nextOffset,
|
|
1708
1702
|
text,
|
|
1709
1703
|
tail: tailOutputText(record.outputChunks),
|
|
@@ -1730,55 +1724,29 @@ function ensureRecordOutputCoverage(record) {
|
|
|
1730
1724
|
appendRecordOutput(record, "stdout", record.stdout);
|
|
1731
1725
|
appendRecordOutput(record, "stderr", record.stderr);
|
|
1732
1726
|
}
|
|
1733
|
-
function
|
|
1727
|
+
function commandArtifactMeta(record) {
|
|
1734
1728
|
return {
|
|
1735
1729
|
status: record.status,
|
|
1736
1730
|
shellId: record.shellId,
|
|
1737
1731
|
commandId: record.id,
|
|
1732
|
+
script: record.script,
|
|
1738
1733
|
startedAt: record.startedAt,
|
|
1739
1734
|
lastOutputAt: record.lastOutputAt,
|
|
1740
1735
|
exitCode: record.exitCode ?? null,
|
|
1741
|
-
stdout: record.stdout,
|
|
1742
|
-
stderr: record.stderr,
|
|
1743
|
-
...record.binaryStdout ? { stdoutBinary: {
|
|
1744
|
-
base64: bytesToBase64(record.binaryStdout.data),
|
|
1745
|
-
truncated: record.binaryStdout.truncated,
|
|
1746
|
-
totalBytes: record.binaryStdout.totalBytes
|
|
1747
|
-
} } : {}
|
|
1748
|
-
};
|
|
1749
|
-
}
|
|
1750
|
-
function commandArtifactMeta(artifact) {
|
|
1751
|
-
const stdoutPath = `/@/commands/${artifact.commandId}/stdout.txt`;
|
|
1752
|
-
const stderrPath = `/@/commands/${artifact.commandId}/stderr.txt`;
|
|
1753
|
-
return {
|
|
1754
|
-
status: artifact.status,
|
|
1755
|
-
shellId: artifact.shellId,
|
|
1756
|
-
commandId: artifact.commandId,
|
|
1757
|
-
startedAt: artifact.startedAt,
|
|
1758
|
-
lastOutputAt: artifact.lastOutputAt,
|
|
1759
|
-
runningMs: Date.now() - artifact.startedAt,
|
|
1760
|
-
idleMs: Date.now() - artifact.lastOutputAt,
|
|
1761
|
-
exitCode: artifact.exitCode,
|
|
1762
1736
|
stdout: {
|
|
1763
|
-
path:
|
|
1764
|
-
bytes: utf8Bytes(
|
|
1737
|
+
path: `${record.artifactDir}/stdout.txt`,
|
|
1738
|
+
bytes: utf8Bytes(record.stdout)
|
|
1765
1739
|
},
|
|
1766
1740
|
stderr: {
|
|
1767
|
-
path:
|
|
1768
|
-
bytes: utf8Bytes(
|
|
1741
|
+
path: `${record.artifactDir}/stderr.txt`,
|
|
1742
|
+
bytes: utf8Bytes(record.stderr)
|
|
1769
1743
|
},
|
|
1770
|
-
...
|
|
1771
|
-
path:
|
|
1772
|
-
bytes:
|
|
1773
|
-
truncated: artifact.stdoutBinary.truncated
|
|
1744
|
+
...record.binaryStdout ? { stdoutBinary: {
|
|
1745
|
+
path: `${record.artifactDir}/stdout.bin`,
|
|
1746
|
+
bytes: record.binaryStdout.totalBytes
|
|
1774
1747
|
} } : {}
|
|
1775
1748
|
};
|
|
1776
1749
|
}
|
|
1777
|
-
function isCommandArtifact(value) {
|
|
1778
|
-
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
|
|
1779
|
-
const record = value;
|
|
1780
|
-
return (record.status === "running" || record.status === "exited" || record.status === "aborted") && typeof record.shellId === "string" && typeof record.commandId === "string" && typeof record.startedAt === "number" && typeof record.lastOutputAt === "number" && (typeof record.exitCode === "number" || record.exitCode === null) && typeof record.stdout === "string" && typeof record.stderr === "string";
|
|
1781
|
-
}
|
|
1782
1750
|
function tailOutputText(chunks) {
|
|
1783
1751
|
const maxChars = 4096;
|
|
1784
1752
|
let text = "";
|
|
@@ -1806,4 +1774,4 @@ function heredocDelimiter(body) {
|
|
|
1806
1774
|
return delimiter;
|
|
1807
1775
|
}
|
|
1808
1776
|
//#endregion
|
|
1809
|
-
export { AgentSessionCommandStorage, BashEnvironment, COMMAND_HELP_DEFAULTS, CommandRegistry, DEMI_PORTABLE_COMMANDS, HostBackedFileSystem, MAX_TIMEOUT_MS, RESERVED_COMMAND_NAMES, emptyStdin, heredocDelimiter, parseCommandInput, renderCommandHelp, runRegisteredCommand, shellQuote
|
|
1777
|
+
export { AgentSessionCommandStorage, BashEnvironment, COMMAND_HELP_DEFAULTS, CommandRegistry, DEMI_PORTABLE_COMMANDS, HOST_PREFERRED_SCAN_COMMANDS, HostBackedFileSystem, MAX_TIMEOUT_MS, RESERVED_COMMAND_NAMES, emptyStdin, heredocDelimiter, parseCommandInput, renderCommandHelp, runRegisteredCommand, shellQuote };
|
package/dist/storage.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as AgentSessionCommandStorage } from "./storage-
|
|
1
|
+
import { t as AgentSessionCommandStorage } from "./storage-DYvEwSc9.mjs";
|
|
2
2
|
export { AgentSessionCommandStorage };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@demicodes/shell",
|
|
3
3
|
"description": "Sandboxable bash engine and Host contract for Demi.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.15.0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@demicodes/just-bash": "^3.0
|
|
23
|
-
"@demicodes/utils": "^0.
|
|
22
|
+
"@demicodes/just-bash": "^3.1.0-demi.3",
|
|
23
|
+
"@demicodes/utils": "^0.15.0",
|
|
24
24
|
"zod": "^4.0.0"
|
|
25
25
|
},
|
|
26
26
|
"license": "Apache-2.0",
|