@demicodes/shell 0.5.0 → 0.6.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/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
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-DIg4RxcL.mjs";
|
|
2
2
|
import { HostBackedFileSystem, VirtualFileSystemNode, VirtualFileSystemProvider, virtualDirectory, virtualFile } 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-
|
|
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-DB9hjZ2G.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";
|
|
@@ -13,7 +13,8 @@ interface ExecAccumulator {
|
|
|
13
13
|
}
|
|
14
14
|
interface ShellSession {
|
|
15
15
|
id: string;
|
|
16
|
-
|
|
16
|
+
agentSessionId: string | null;
|
|
17
|
+
commandStorageId: string;
|
|
17
18
|
state: InterpreterState;
|
|
18
19
|
fs: HostBackedFileSystem;
|
|
19
20
|
interpreter: Interpreter;
|
|
@@ -90,14 +91,6 @@ interface BashEnvironmentOptions {
|
|
|
90
91
|
shellIdFactory?: () => string;
|
|
91
92
|
commandIdFactory?: () => string;
|
|
92
93
|
initialEnv?: Record<string, string>;
|
|
93
|
-
/**
|
|
94
|
-
* Per-exec env injection for agent-owned shells. Evaluated on every exec with
|
|
95
|
-
* the owning agent session id; returned vars are set and exported, so both
|
|
96
|
-
* registered commands (ctx.env) and spawned external processes observe them.
|
|
97
|
-
* Lets a product harness expose session-scoped context (identity, routing)
|
|
98
|
-
* that changes between execs, which static initialEnv cannot express.
|
|
99
|
-
*/
|
|
100
|
-
execEnv?: (agentSessionId: string) => Record<string, string>;
|
|
101
94
|
maxOutputBytes?: number;
|
|
102
95
|
}
|
|
103
96
|
interface ShellExecInput {
|
|
@@ -235,7 +228,6 @@ declare class BashEnvironment {
|
|
|
235
228
|
private readonly shellIdFactory;
|
|
236
229
|
private readonly commandIdFactory;
|
|
237
230
|
private readonly initialEnv;
|
|
238
|
-
private readonly execEnv?;
|
|
239
231
|
private readonly defaultOutputLimitBytes;
|
|
240
232
|
private readonly shells;
|
|
241
233
|
private readonly defaultShellByAgentSessionId;
|
|
@@ -243,6 +235,7 @@ declare class BashEnvironment {
|
|
|
243
235
|
private readonly artifacts;
|
|
244
236
|
constructor(options: BashEnvironmentOptions);
|
|
245
237
|
getShell(shellId: string): ShellSession | null;
|
|
238
|
+
hasCommand(commandId: string): boolean;
|
|
246
239
|
registerCommand(command: Command): void;
|
|
247
240
|
registeredCommands(): Command[];
|
|
248
241
|
exec(input: ShellExecInput): Promise<ShellCommandStatus>;
|
package/dist/index.mjs
CHANGED
|
@@ -249,7 +249,8 @@ async function runRegisteredCommand(root, ctx) {
|
|
|
249
249
|
env: ctx.env,
|
|
250
250
|
cwd: ctx.cwd,
|
|
251
251
|
io: parsed.json ? capture : ctx.io,
|
|
252
|
-
storage: ctx.storage
|
|
252
|
+
storage: ctx.storage,
|
|
253
|
+
host: ctx.host
|
|
253
254
|
});
|
|
254
255
|
if (parsed.json && result.exitCode === 0) {
|
|
255
256
|
const raw = capture.stdoutText();
|
|
@@ -620,47 +621,47 @@ function appendVisibleChunk(foreground, targetFd, text, byteLength) {
|
|
|
620
621
|
//#endregion
|
|
621
622
|
//#region src/command-artifact-store.ts
|
|
622
623
|
/**
|
|
623
|
-
* Owns persistence of shell command artifacts: a per-
|
|
624
|
+
* Owns persistence of shell command artifacts: a per-storage-id cache plus the
|
|
624
625
|
* set of released (tombstoned) commands. `BashEnvironment` delegates the storage
|
|
625
626
|
* and release-tracking side of the `/@` virtual filesystem here, keeping only the
|
|
626
627
|
* in-memory record lookups that need live command state.
|
|
627
628
|
*/
|
|
628
629
|
var CommandArtifactStore = class {
|
|
629
630
|
store;
|
|
630
|
-
|
|
631
|
+
storageById = /* @__PURE__ */ new Map();
|
|
631
632
|
released = /* @__PURE__ */ new Set();
|
|
632
633
|
constructor(store) {
|
|
633
634
|
this.store = store;
|
|
634
635
|
}
|
|
635
|
-
/** The artifact storage for
|
|
636
|
-
storageFor(
|
|
637
|
-
const existing = this.
|
|
636
|
+
/** The artifact storage for one agent session or anonymous shell. */
|
|
637
|
+
storageFor(commandStorageId) {
|
|
638
|
+
const existing = this.storageById.get(commandStorageId);
|
|
638
639
|
if (existing) return existing;
|
|
639
|
-
const storage = new AgentSessionCommandStorage(this.store,
|
|
640
|
-
this.
|
|
640
|
+
const storage = new AgentSessionCommandStorage(this.store, commandStorageId);
|
|
641
|
+
this.storageById.set(commandStorageId, storage);
|
|
641
642
|
return storage;
|
|
642
643
|
}
|
|
643
644
|
/** Whether a command's artifact has been released (tombstoned). */
|
|
644
|
-
isReleased(
|
|
645
|
-
return this.released.has(this.key(
|
|
645
|
+
isReleased(commandStorageId, commandId) {
|
|
646
|
+
return this.released.has(this.key(commandStorageId, commandId));
|
|
646
647
|
}
|
|
647
648
|
/** Persists an artifact unless its command has already been released. */
|
|
648
|
-
persist(
|
|
649
|
-
if (this.isReleased(
|
|
650
|
-
this.storageFor(
|
|
649
|
+
persist(commandStorageId, commandId, artifact) {
|
|
650
|
+
if (this.isReleased(commandStorageId, commandId)) return;
|
|
651
|
+
this.storageFor(commandStorageId).writeJson(`commands/${commandId}/artifact.json`, artifact).catch(() => {});
|
|
651
652
|
}
|
|
652
653
|
/** Tombstones a command and removes its persisted artifact. */
|
|
653
|
-
async release(
|
|
654
|
-
this.released.add(this.key(
|
|
655
|
-
await this.storageFor(
|
|
654
|
+
async release(commandStorageId, commandId) {
|
|
655
|
+
this.released.add(this.key(commandStorageId, commandId));
|
|
656
|
+
await this.storageFor(commandStorageId).delete(`commands/${commandId}/artifact.json`).catch(() => {});
|
|
656
657
|
}
|
|
657
|
-
key(
|
|
658
|
-
return `${
|
|
658
|
+
key(commandStorageId, commandId) {
|
|
659
|
+
return `${commandStorageId}\0${commandId}`;
|
|
659
660
|
}
|
|
660
661
|
};
|
|
661
662
|
//#endregion
|
|
662
663
|
//#region src/registered-command-adapter.ts
|
|
663
|
-
function commandToForkCommand(session, command, storage) {
|
|
664
|
+
function commandToForkCommand(session, command, storage, host) {
|
|
664
665
|
return {
|
|
665
666
|
name: command.name,
|
|
666
667
|
consumesStdin: treeConsumesStdin(command),
|
|
@@ -675,7 +676,8 @@ function commandToForkCommand(session, command, storage) {
|
|
|
675
676
|
env: mapToRecord(ctx.env),
|
|
676
677
|
cwd: ctx.cwd,
|
|
677
678
|
io,
|
|
678
|
-
storage
|
|
679
|
+
storage,
|
|
680
|
+
host
|
|
679
681
|
});
|
|
680
682
|
session.accumulator.audit.push({
|
|
681
683
|
kind: "registered-command",
|
|
@@ -790,7 +792,6 @@ var BashEnvironment = class {
|
|
|
790
792
|
shellIdFactory;
|
|
791
793
|
commandIdFactory;
|
|
792
794
|
initialEnv;
|
|
793
|
-
execEnv;
|
|
794
795
|
defaultOutputLimitBytes;
|
|
795
796
|
shells = /* @__PURE__ */ new Map();
|
|
796
797
|
defaultShellByAgentSessionId = /* @__PURE__ */ new Map();
|
|
@@ -803,12 +804,14 @@ var BashEnvironment = class {
|
|
|
803
804
|
this.shellIdFactory = options.shellIdFactory ?? (() => globalThis.crypto.randomUUID());
|
|
804
805
|
this.commandIdFactory = options.commandIdFactory ?? (() => globalThis.crypto.randomUUID());
|
|
805
806
|
this.initialEnv = options.initialEnv ?? {};
|
|
806
|
-
this.execEnv = options.execEnv;
|
|
807
807
|
this.defaultOutputLimitBytes = options.maxOutputBytes ?? DEFAULT_OUTPUT_LIMIT_BYTES;
|
|
808
808
|
}
|
|
809
809
|
getShell(shellId) {
|
|
810
810
|
return this.shells.get(shellId) ?? null;
|
|
811
811
|
}
|
|
812
|
+
hasCommand(commandId) {
|
|
813
|
+
return this.commandsById.has(commandId);
|
|
814
|
+
}
|
|
812
815
|
registerCommand(command) {
|
|
813
816
|
if (this.commands.get(command.name)) return;
|
|
814
817
|
this.commands.register(command);
|
|
@@ -825,17 +828,6 @@ var BashEnvironment = class {
|
|
|
825
828
|
}
|
|
826
829
|
const session = input.shellId ? this.requireShell(input.shellId) : input.ephemeral ? this.createShell(input.agentSessionId, input.cwd) : this.availableDefaultShell(input.agentSessionId);
|
|
827
830
|
if (session.exited) throw new Error(`Shell session "${session.id}" has exited`);
|
|
828
|
-
if (input.agentSessionId) {
|
|
829
|
-
const extraEnv = this.execEnv?.(input.agentSessionId);
|
|
830
|
-
if (extraEnv) {
|
|
831
|
-
const exported = session.state.exportedVars ?? /* @__PURE__ */ new Set();
|
|
832
|
-
session.state.exportedVars = exported;
|
|
833
|
-
for (const [key, value] of Object.entries(extraEnv)) {
|
|
834
|
-
session.state.env.set(key, value);
|
|
835
|
-
exported.add(key);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
831
|
if (session.pendingExec || session.foreground) {
|
|
840
832
|
const commandId = session.activeCommandId ?? session.foreground?.commandId ?? "unknown";
|
|
841
833
|
throw new Error(`Shell session "${session.id}" is already running command "${commandId}"`);
|
|
@@ -873,7 +865,7 @@ var BashEnvironment = class {
|
|
|
873
865
|
const record = this.commandsById.get(commandId);
|
|
874
866
|
if (!record || record.status === "running") return false;
|
|
875
867
|
this.commandsById.delete(commandId);
|
|
876
|
-
await this.artifacts.release(record.
|
|
868
|
+
await this.artifacts.release(record.commandStorageId, commandId);
|
|
877
869
|
return true;
|
|
878
870
|
}
|
|
879
871
|
async disposeShell(shellId) {
|
|
@@ -941,23 +933,20 @@ var BashEnvironment = class {
|
|
|
941
933
|
}
|
|
942
934
|
createShell(agentSessionId, initialCwd) {
|
|
943
935
|
const id = this.shellIdFactory();
|
|
944
|
-
const
|
|
936
|
+
const commandStorageId = agentSessionId ?? id;
|
|
945
937
|
const cwd = initialCwd ?? this.host.defaultCwd;
|
|
946
|
-
const fs = new HostBackedFileSystem(this.host, { lookup: (path) => this.lookupVirtualArtifact(
|
|
938
|
+
const fs = new HostBackedFileSystem(this.host, { lookup: (path) => this.lookupVirtualArtifact(commandStorageId, path) });
|
|
947
939
|
const env = /* @__PURE__ */ new Map();
|
|
948
940
|
for (const [key, value] of Object.entries(this.initialEnv)) env.set(key, value);
|
|
949
941
|
env.set("PWD", cwd);
|
|
950
|
-
env.set("DEMI_SESSION_ID",
|
|
942
|
+
if (agentSessionId) env.set("DEMI_SESSION_ID", agentSessionId);
|
|
951
943
|
env.set("DEMI_SHELL_ID", id);
|
|
952
944
|
if (!env.has("IFS")) env.set("IFS", " \n");
|
|
953
945
|
if (!env.has("PS1")) env.set("PS1", "");
|
|
954
946
|
if (!env.has("PS2")) env.set("PS2", "> ");
|
|
955
947
|
if (!env.has("SHLVL")) env.set("SHLVL", "1");
|
|
956
|
-
const exportedVars = /* @__PURE__ */ new Set([
|
|
957
|
-
|
|
958
|
-
"DEMI_SESSION_ID",
|
|
959
|
-
"DEMI_SHELL_ID"
|
|
960
|
-
]);
|
|
948
|
+
const exportedVars = /* @__PURE__ */ new Set(["PWD", "DEMI_SHELL_ID"]);
|
|
949
|
+
if (agentSessionId) exportedVars.add("DEMI_SESSION_ID");
|
|
961
950
|
for (const key of env.keys()) if (key !== key.toLowerCase()) exportedVars.add(key);
|
|
962
951
|
for (const key of Object.keys(this.initialEnv)) exportedVars.add(key);
|
|
963
952
|
const state = {
|
|
@@ -1018,7 +1007,8 @@ var BashEnvironment = class {
|
|
|
1018
1007
|
const forkCommands = /* @__PURE__ */ new Map();
|
|
1019
1008
|
const session = {
|
|
1020
1009
|
id,
|
|
1021
|
-
|
|
1010
|
+
agentSessionId: agentSessionId ?? null,
|
|
1011
|
+
commandStorageId,
|
|
1022
1012
|
state,
|
|
1023
1013
|
fs,
|
|
1024
1014
|
interpreter: void 0,
|
|
@@ -1035,8 +1025,8 @@ var BashEnvironment = class {
|
|
|
1035
1025
|
exited: false
|
|
1036
1026
|
};
|
|
1037
1027
|
for (const command of createPortableCommands(session)) forkCommands.set(command.name, command);
|
|
1038
|
-
const storage = new AgentSessionCommandStorage(this.host.store,
|
|
1039
|
-
for (const command of this.commands.list()) forkCommands.set(command.name, commandToForkCommand(session, command, storage));
|
|
1028
|
+
const storage = new AgentSessionCommandStorage(this.host.store, commandStorageId);
|
|
1029
|
+
for (const command of this.commands.list()) forkCommands.set(command.name, commandToForkCommand(session, command, storage, this.host));
|
|
1040
1030
|
session.abortController = new AbortController();
|
|
1041
1031
|
session.interpreter = new Interpreter({
|
|
1042
1032
|
fs,
|
|
@@ -1109,7 +1099,7 @@ var BashEnvironment = class {
|
|
|
1109
1099
|
const record = {
|
|
1110
1100
|
id,
|
|
1111
1101
|
shellId: session.id,
|
|
1112
|
-
|
|
1102
|
+
commandStorageId: session.commandStorageId,
|
|
1113
1103
|
script,
|
|
1114
1104
|
startedAt: now,
|
|
1115
1105
|
lastOutputAt: now,
|
|
@@ -1550,12 +1540,12 @@ var BashEnvironment = class {
|
|
|
1550
1540
|
status: "running"
|
|
1551
1541
|
};
|
|
1552
1542
|
}
|
|
1553
|
-
async lookupVirtualArtifact(
|
|
1543
|
+
async lookupVirtualArtifact(commandStorageId, path) {
|
|
1554
1544
|
const parts = path.split("/").filter(Boolean);
|
|
1555
1545
|
if (parts.length === 1 && parts[0] === "@") return virtualDirectory(["commands"]);
|
|
1556
|
-
if (parts.length === 2 && parts[0] === "@" && parts[1] === "commands") return virtualDirectory(await this.commandArtifactIds(
|
|
1546
|
+
if (parts.length === 2 && parts[0] === "@" && parts[1] === "commands") return virtualDirectory(await this.commandArtifactIds(commandStorageId));
|
|
1557
1547
|
if (parts.length === 3 && parts[0] === "@" && parts[1] === "commands") {
|
|
1558
|
-
const artifact = await this.commandArtifact(
|
|
1548
|
+
const artifact = await this.commandArtifact(commandStorageId, parts[2]);
|
|
1559
1549
|
if (!artifact) return null;
|
|
1560
1550
|
const entries = [
|
|
1561
1551
|
"meta.json",
|
|
@@ -1566,7 +1556,7 @@ var BashEnvironment = class {
|
|
|
1566
1556
|
return virtualDirectory(entries);
|
|
1567
1557
|
}
|
|
1568
1558
|
if (parts.length !== 4 || parts[0] !== "@" || parts[1] !== "commands") return null;
|
|
1569
|
-
const artifact = await this.commandArtifact(
|
|
1559
|
+
const artifact = await this.commandArtifact(commandStorageId, parts[2]);
|
|
1570
1560
|
if (!artifact) return null;
|
|
1571
1561
|
const fileName = parts[3];
|
|
1572
1562
|
if (fileName === "stdout.txt") return virtualFile(encodeUtf8(artifact.stdout));
|
|
@@ -1575,31 +1565,31 @@ var BashEnvironment = class {
|
|
|
1575
1565
|
if (fileName === "meta.json") return virtualFile(encodeUtf8(`${JSON.stringify(commandArtifactMeta(artifact), null, 2)}\n`));
|
|
1576
1566
|
return null;
|
|
1577
1567
|
}
|
|
1578
|
-
async commandArtifactIds(
|
|
1568
|
+
async commandArtifactIds(commandStorageId) {
|
|
1579
1569
|
const ids = /* @__PURE__ */ new Set();
|
|
1580
|
-
for (const record of this.commandsById.values()) if (record.
|
|
1581
|
-
const keys = await this.artifacts.storageFor(
|
|
1570
|
+
for (const record of this.commandsById.values()) if (record.commandStorageId === commandStorageId && !this.artifacts.isReleased(commandStorageId, record.id)) ids.add(record.id);
|
|
1571
|
+
const keys = await this.artifacts.storageFor(commandStorageId).list("commands").catch(() => []);
|
|
1582
1572
|
for (const key of keys) {
|
|
1583
1573
|
const match = /^commands\/([^/]+)\/artifact\.json$/.exec(key);
|
|
1584
|
-
if (match && !this.artifacts.isReleased(
|
|
1574
|
+
if (match && !this.artifacts.isReleased(commandStorageId, match[1])) ids.add(match[1]);
|
|
1585
1575
|
}
|
|
1586
1576
|
return [...ids];
|
|
1587
1577
|
}
|
|
1588
|
-
async commandArtifact(
|
|
1589
|
-
if (this.artifacts.isReleased(
|
|
1578
|
+
async commandArtifact(commandStorageId, commandId) {
|
|
1579
|
+
if (this.artifacts.isReleased(commandStorageId, commandId)) return null;
|
|
1590
1580
|
const record = this.commandsById.get(commandId);
|
|
1591
|
-
if (record?.
|
|
1581
|
+
if (record?.commandStorageId === commandStorageId) {
|
|
1592
1582
|
this.syncRunningRecord(record);
|
|
1593
1583
|
return commandArtifactFromRecord(record);
|
|
1594
1584
|
}
|
|
1595
|
-
const value = await this.artifacts.storageFor(
|
|
1585
|
+
const value = await this.artifacts.storageFor(commandStorageId).readJson(`commands/${commandId}/artifact.json`).catch(() => null);
|
|
1596
1586
|
return isCommandArtifact(value) ? value : null;
|
|
1597
1587
|
}
|
|
1598
1588
|
persistCommandArtifact(record) {
|
|
1599
1589
|
const fingerprint = `${record.status}:${record.exitCode ?? ""}:${record.stdout.length}:${record.stderr.length}:${record.binaryStdout?.totalBytes ?? ""}`;
|
|
1600
1590
|
if (record.persistedFingerprint === fingerprint) return;
|
|
1601
1591
|
record.persistedFingerprint = fingerprint;
|
|
1602
|
-
this.artifacts.persist(record.
|
|
1592
|
+
this.artifacts.persist(record.commandStorageId, record.id, commandArtifactFromRecord(record));
|
|
1603
1593
|
}
|
|
1604
1594
|
syncRunningRecord(record) {
|
|
1605
1595
|
const foreground = this.shells.get(record.shellId)?.foreground;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { u as HostStore } from "./host-DIg4RxcL.mjs";
|
|
1
|
+
import { t as Host, u as HostStore } from "./host-DIg4RxcL.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
//#region src/command.d.ts
|
|
4
4
|
type CommandInputSpec = Record<string, z.ZodType>;
|
|
@@ -48,6 +48,8 @@ interface CommandRunContext {
|
|
|
48
48
|
cwd: string;
|
|
49
49
|
io: CommandIO;
|
|
50
50
|
storage: CommandStorage;
|
|
51
|
+
/** Host of the BashEnvironment executing this command. */
|
|
52
|
+
host: Host;
|
|
51
53
|
}
|
|
52
54
|
interface CommandRunResult {
|
|
53
55
|
exitCode: number;
|
|
@@ -77,6 +79,7 @@ interface CommandExecutionContext {
|
|
|
77
79
|
cwd: string;
|
|
78
80
|
io: CommandIO;
|
|
79
81
|
storage: CommandStorage;
|
|
82
|
+
host: Host;
|
|
80
83
|
}
|
|
81
84
|
declare class CommandRegistry {
|
|
82
85
|
private readonly commands;
|
package/dist/storage.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as AgentSessionCommandStorage } from "./storage-
|
|
1
|
+
import { t as AgentSessionCommandStorage } from "./storage-DB9hjZ2G.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.6.1",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
}
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@demicodes/utils": "^0.
|
|
23
|
-
"@demicodes/just-bash": "^3.0
|
|
22
|
+
"@demicodes/utils": "^0.6.1",
|
|
23
|
+
"@demicodes/just-bash": "^3.1.0-demi.2",
|
|
24
24
|
"zod": "^4.0.0"
|
|
25
25
|
},
|
|
26
26
|
"license": "Apache-2.0",
|