@axiom-lattice/microsandbox-service 0.0.2 → 0.0.4

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.
@@ -1,6 +1,71 @@
1
+ import fs from "node:fs";
2
+ import { createRequire } from "node:module";
3
+ import path from "node:path";
4
+ import vm from "node:vm";
1
5
  import packageJson from "../../package.json";
6
+ import ts from "typescript";
2
7
  import { parseServerArgs, resolveServerConfig } from "../lib/server-cli";
3
8
 
9
+ function loadServerModule({
10
+ buildApp,
11
+ processMock,
12
+ mainModule,
13
+ }: {
14
+ buildApp: jest.Mock;
15
+ processMock: {
16
+ argv: string[];
17
+ env: NodeJS.ProcessEnv;
18
+ exit: jest.Mock;
19
+ once: jest.Mock;
20
+ off?: jest.Mock;
21
+ };
22
+ mainModule: object;
23
+ }): {
24
+ startServer: (options: { argv?: string[]; env?: NodeJS.ProcessEnv }) => Promise<{
25
+ listen: jest.Mock;
26
+ close: jest.Mock;
27
+ }>;
28
+ } {
29
+ const serverPath = path.resolve(__dirname, "../server.ts");
30
+ const serverRequire = createRequire(serverPath);
31
+ const serverSource = fs.readFileSync(serverPath, "utf8");
32
+ const transpiled = ts.transpileModule(serverSource, {
33
+ compilerOptions: {
34
+ module: ts.ModuleKind.CommonJS,
35
+ target: ts.ScriptTarget.ES2020,
36
+ },
37
+ fileName: serverPath,
38
+ });
39
+ const module = { exports: {}, filename: serverPath };
40
+ const customRequire = Object.assign(
41
+ (specifier: string) => {
42
+ if (specifier === "./app") {
43
+ return { buildApp };
44
+ }
45
+
46
+ return serverRequire(specifier);
47
+ },
48
+ { main: mainModule }
49
+ );
50
+
51
+ vm.runInNewContext(transpiled.outputText, {
52
+ module,
53
+ exports: module.exports,
54
+ require: customRequire,
55
+ __dirname: path.dirname(serverPath),
56
+ __filename: serverPath,
57
+ process: processMock,
58
+ console,
59
+ });
60
+
61
+ return module.exports as {
62
+ startServer: (options: { argv?: string[]; env?: NodeJS.ProcessEnv }) => Promise<{
63
+ listen: jest.Mock;
64
+ close: jest.Mock;
65
+ }>;
66
+ };
67
+ }
68
+
4
69
  describe("server CLI parsing", () => {
5
70
  it("publishes a dedicated CLI entrypoint", () => {
6
71
  expect(packageJson.bin["lattice-microsandbox-service"]).toBe("./dist/cli.js");
@@ -25,7 +90,147 @@ describe("server CLI parsing", () => {
25
90
  });
26
91
  });
27
92
 
93
+ it("reads api key from env", () => {
94
+ expect(
95
+ resolveServerConfig({
96
+ args: {},
97
+ env: { MICROSANDBOX_API_KEY: "test-api-key" },
98
+ })
99
+ ).toEqual({
100
+ host: "0.0.0.0",
101
+ port: 4002,
102
+ apiKey: "test-api-key",
103
+ });
104
+ });
105
+
28
106
  it("rejects invalid port values", () => {
29
107
  expect(() => parseServerArgs(["--port", "abc"])).toThrow("Invalid value for --port: abc");
30
108
  });
109
+
110
+ it("rejects option-like values for host and port flags", () => {
111
+ expect(() => parseServerArgs(["--host", "--port"])).toThrow("Missing value for --host");
112
+ expect(() => parseServerArgs(["--port", "--host"])).toThrow("Missing value for --port");
113
+ });
114
+
115
+ it("rejects port values above 65535", () => {
116
+ expect(() => parseServerArgs(["--port", "65536"])).toThrow(
117
+ "Invalid value for --port: 65536"
118
+ );
119
+ });
120
+
121
+ it("does not start listening when server.ts is executed directly", async () => {
122
+ const buildApp = jest.fn();
123
+ const listen = jest.fn();
124
+ const close = jest.fn();
125
+ const serverPath = path.resolve(__dirname, "../server.ts");
126
+ const processMock = {
127
+ argv: ["node", serverPath],
128
+ env: {},
129
+ exit: jest.fn(),
130
+ once: jest.fn(),
131
+ };
132
+
133
+ buildApp.mockReturnValue({ listen, close });
134
+
135
+ loadServerModule({
136
+ buildApp,
137
+ processMock,
138
+ mainModule: { exports: {}, filename: serverPath },
139
+ });
140
+
141
+ await Promise.resolve();
142
+
143
+ expect(buildApp).not.toHaveBeenCalled();
144
+ expect(listen).not.toHaveBeenCalled();
145
+ });
146
+
147
+ it("passes resolved api key into buildApp during startup", async () => {
148
+ const buildApp = jest.fn();
149
+ const listen = jest.fn().mockResolvedValue(undefined);
150
+ const close = jest.fn().mockResolvedValue(undefined);
151
+ const serverPath = path.resolve(__dirname, "../server.ts");
152
+ const processMock = {
153
+ argv: ["node", serverPath],
154
+ env: {},
155
+ exit: jest.fn(),
156
+ once: jest.fn(),
157
+ };
158
+
159
+ buildApp.mockReturnValue({ listen, close });
160
+
161
+ const { startServer } = loadServerModule({
162
+ buildApp,
163
+ processMock,
164
+ mainModule: {},
165
+ });
166
+
167
+ await startServer({ env: { MICROSANDBOX_API_KEY: "test-api-key" } });
168
+
169
+ expect(buildApp).toHaveBeenCalledWith({ apiKey: "test-api-key" });
170
+ });
171
+
172
+ it("removes signal listeners when the started server is closed", async () => {
173
+ const buildApp = jest.fn();
174
+ const listen = jest.fn().mockResolvedValue(undefined);
175
+ const close = jest.fn().mockResolvedValue(undefined);
176
+ const serverPath = path.resolve(__dirname, "../server.ts");
177
+ const processMock = {
178
+ argv: ["node", serverPath],
179
+ env: {},
180
+ exit: jest.fn(),
181
+ once: jest.fn(),
182
+ off: jest.fn(),
183
+ };
184
+
185
+ buildApp.mockReturnValue({ listen, close });
186
+
187
+ const { startServer } = loadServerModule({
188
+ buildApp,
189
+ processMock,
190
+ mainModule: {},
191
+ });
192
+
193
+ const startedServer = await startServer({ env: { MICROSANDBOX_API_KEY: "test-api-key" } });
194
+
195
+ await startedServer.close();
196
+
197
+ expect(processMock.once).toHaveBeenCalledTimes(2);
198
+ expect(processMock.off).toHaveBeenCalledTimes(2);
199
+ expect(processMock.off).toHaveBeenNthCalledWith(1, "SIGINT", expect.any(Function));
200
+ expect(processMock.off).toHaveBeenNthCalledWith(2, "SIGTERM", expect.any(Function));
201
+ expect(close).toHaveBeenCalledTimes(1);
202
+ });
203
+
204
+ it("removes signal listeners when startup listen fails", async () => {
205
+ const buildApp = jest.fn();
206
+ const listenError = new Error("listen failed");
207
+ const listen = jest.fn().mockRejectedValue(listenError);
208
+ const close = jest.fn().mockResolvedValue(undefined);
209
+ const serverPath = path.resolve(__dirname, "../server.ts");
210
+ const processMock = {
211
+ argv: ["node", serverPath],
212
+ env: {},
213
+ exit: jest.fn(),
214
+ once: jest.fn(),
215
+ off: jest.fn(),
216
+ };
217
+
218
+ buildApp.mockReturnValue({ listen, close });
219
+
220
+ const { startServer } = loadServerModule({
221
+ buildApp,
222
+ processMock,
223
+ mainModule: {},
224
+ });
225
+
226
+ await expect(startServer({ env: { MICROSANDBOX_API_KEY: "test-api-key" } })).rejects.toThrow(
227
+ listenError
228
+ );
229
+
230
+ expect(processMock.once).toHaveBeenCalledTimes(2);
231
+ expect(processMock.off).toHaveBeenCalledTimes(2);
232
+ expect(processMock.off).toHaveBeenNthCalledWith(1, "SIGINT", expect.any(Function));
233
+ expect(processMock.off).toHaveBeenNthCalledWith(2, "SIGTERM", expect.any(Function));
234
+ expect(close).not.toHaveBeenCalled();
235
+ });
31
236
  });
package/src/app.ts CHANGED
@@ -13,16 +13,44 @@ import type { RuntimeService } from "./types/runtime-service";
13
13
  export function buildApp({
14
14
  runtimeService = new MicrosandboxRuntimeService(),
15
15
  imageService = new ImageService(),
16
+ apiKey,
16
17
  }: {
17
18
  runtimeService?: RuntimeService;
18
19
  imageService?: ImageService;
20
+ apiKey?: string;
19
21
  } = {}): FastifyInstance {
20
22
  const app = fastify({ logger: false });
21
23
 
22
- app.register(cors, { origin: true });
24
+ app.register(cors, {
25
+ delegator: (request, callback) => {
26
+ callback(null, {
27
+ origin: true,
28
+ methods: request.headers["access-control-request-method"] ?? "*",
29
+ allowedHeaders: request.headers["access-control-request-headers"],
30
+ });
31
+ },
32
+ });
23
33
  app.register(sensible);
24
34
  app.register(multipart);
25
35
 
36
+ if (apiKey) {
37
+ app.addHook("onRequest", async (request, reply) => {
38
+ if (request.method === "OPTIONS" || !request.url.startsWith("/api/")) {
39
+ return;
40
+ }
41
+
42
+ if (request.headers.authorization !== `Bearer ${apiKey}`) {
43
+ await reply.code(401).send({
44
+ success: false,
45
+ error: {
46
+ code: "UNAUTHORIZED",
47
+ message: "Unauthorized",
48
+ },
49
+ });
50
+ }
51
+ });
52
+ }
53
+
26
54
  registerHealthRoutes(app);
27
55
  registerSandboxRoutes(app, runtimeService);
28
56
  registerImageRoutes(app, imageService);
@@ -6,16 +6,21 @@ export interface ParsedServerArgs {
6
6
  export interface ResolvedServerConfig {
7
7
  host: string;
8
8
  port: number;
9
+ apiKey?: string;
9
10
  }
10
11
 
11
12
  function parsePort(raw: string, source: string): number {
12
13
  const value = Number(raw);
13
- if (!Number.isInteger(value) || value <= 0) {
14
+ if (!Number.isInteger(value) || value <= 0 || value > 65535) {
14
15
  throw new Error(`Invalid value for ${source}: ${raw}`);
15
16
  }
16
17
  return value;
17
18
  }
18
19
 
20
+ function isMissingOptionValue(raw: string | undefined): raw is undefined {
21
+ return !raw || raw.startsWith("--");
22
+ }
23
+
19
24
  export function parseServerArgs(argv: string[]): ParsedServerArgs {
20
25
  const parsed: ParsedServerArgs = {};
21
26
 
@@ -24,7 +29,7 @@ export function parseServerArgs(argv: string[]): ParsedServerArgs {
24
29
 
25
30
  if (token === "--port") {
26
31
  const raw = argv[index + 1];
27
- if (!raw) {
32
+ if (isMissingOptionValue(raw)) {
28
33
  throw new Error("Missing value for --port");
29
34
  }
30
35
  parsed.port = parsePort(raw, "--port");
@@ -34,7 +39,7 @@ export function parseServerArgs(argv: string[]): ParsedServerArgs {
34
39
 
35
40
  if (token === "--host") {
36
41
  const raw = argv[index + 1];
37
- if (!raw) {
42
+ if (isMissingOptionValue(raw)) {
38
43
  throw new Error("Missing value for --host");
39
44
  }
40
45
  parsed.host = raw;
@@ -55,5 +60,6 @@ export function resolveServerConfig({
55
60
  return {
56
61
  host: args.host ?? env.HOST ?? "0.0.0.0",
57
62
  port: args.port ?? (env.PORT ? parsePort(env.PORT, "PORT") : 4002),
63
+ apiKey: env.MICROSANDBOX_API_KEY,
58
64
  };
59
65
  }
package/src/server.ts CHANGED
@@ -5,17 +5,41 @@ import { parseServerArgs, resolveServerConfig } from "./lib/server-cli";
5
5
  export async function startServer({
6
6
  argv = process.argv.slice(2),
7
7
  env = process.env,
8
- app = buildApp(),
8
+ app,
9
9
  }: {
10
10
  argv?: string[];
11
11
  env?: NodeJS.ProcessEnv;
12
12
  app?: Pick<FastifyInstance, "listen" | "close">;
13
13
  } = {}): Promise<Pick<FastifyInstance, "listen" | "close">> {
14
14
  const config = resolveServerConfig({ args: parseServerArgs(argv), env });
15
+ const resolvedApp = app ?? buildApp({ apiKey: config.apiKey });
16
+
17
+ const onSigInt = () => {
18
+ void shutdown(0);
19
+ };
20
+
21
+ const onSigTerm = () => {
22
+ void shutdown(0);
23
+ };
24
+
25
+ const removeSignalHandlers = (): void => {
26
+ process.off("SIGINT", onSigInt);
27
+ process.off("SIGTERM", onSigTerm);
28
+ };
29
+
30
+ const close = async (): Promise<undefined> => {
31
+ removeSignalHandlers();
32
+ return resolvedApp.close();
33
+ };
34
+
35
+ const startedApp: Pick<FastifyInstance, "listen" | "close"> = {
36
+ listen: resolvedApp.listen.bind(resolvedApp),
37
+ close: close as FastifyInstance["close"],
38
+ };
15
39
 
16
40
  const shutdown = async (code: number): Promise<void> => {
17
41
  try {
18
- await app.close();
42
+ await close();
19
43
  process.exit(code);
20
44
  } catch (error) {
21
45
  console.error("Failed to shutdown microsandbox-service cleanly", error);
@@ -23,21 +47,14 @@ export async function startServer({
23
47
  }
24
48
  };
25
49
 
26
- process.once("SIGINT", () => {
27
- void shutdown(0);
28
- });
29
-
30
- process.once("SIGTERM", () => {
31
- void shutdown(0);
32
- });
33
-
34
- await app.listen({ port: config.port, host: config.host });
35
- return app;
36
- }
50
+ process.once("SIGINT", onSigInt);
51
+ process.once("SIGTERM", onSigTerm);
37
52
 
38
- if (require.main === module) {
39
- void startServer().catch((error) => {
40
- console.error("Failed to start microsandbox-service", error);
41
- process.exit(1);
42
- });
53
+ try {
54
+ await resolvedApp.listen({ port: config.port, host: config.host });
55
+ return startedApp;
56
+ } catch (error) {
57
+ removeSignalHandlers();
58
+ throw error;
59
+ }
43
60
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/server-cli.ts","../src/server.ts"],"sourcesContent":["export interface ParsedServerArgs {\n host?: string;\n port?: number;\n}\n\nexport interface ResolvedServerConfig {\n host: string;\n port: number;\n}\n\nfunction parsePort(raw: string, source: string): number {\n const value = Number(raw);\n if (!Number.isInteger(value) || value <= 0) {\n throw new Error(`Invalid value for ${source}: ${raw}`);\n }\n return value;\n}\n\nexport function parseServerArgs(argv: string[]): ParsedServerArgs {\n const parsed: ParsedServerArgs = {};\n\n for (let index = 0; index < argv.length; index += 1) {\n const token = argv[index];\n\n if (token === \"--port\") {\n const raw = argv[index + 1];\n if (!raw) {\n throw new Error(\"Missing value for --port\");\n }\n parsed.port = parsePort(raw, \"--port\");\n index += 1;\n continue;\n }\n\n if (token === \"--host\") {\n const raw = argv[index + 1];\n if (!raw) {\n throw new Error(\"Missing value for --host\");\n }\n parsed.host = raw;\n index += 1;\n }\n }\n\n return parsed;\n}\n\nexport function resolveServerConfig({\n args,\n env,\n}: {\n args: ParsedServerArgs;\n env: NodeJS.ProcessEnv;\n}): ResolvedServerConfig {\n return {\n host: args.host ?? env.HOST ?? \"0.0.0.0\",\n port: args.port ?? (env.PORT ? parsePort(env.PORT, \"PORT\") : 4002),\n };\n}\n","import type { FastifyInstance } from \"fastify\";\nimport { buildApp } from \"./app\";\nimport { parseServerArgs, resolveServerConfig } from \"./lib/server-cli\";\n\nexport async function startServer({\n argv = process.argv.slice(2),\n env = process.env,\n app = buildApp(),\n}: {\n argv?: string[];\n env?: NodeJS.ProcessEnv;\n app?: Pick<FastifyInstance, \"listen\" | \"close\">;\n} = {}): Promise<Pick<FastifyInstance, \"listen\" | \"close\">> {\n const config = resolveServerConfig({ args: parseServerArgs(argv), env });\n\n const shutdown = async (code: number): Promise<void> => {\n try {\n await app.close();\n process.exit(code);\n } catch (error) {\n console.error(\"Failed to shutdown microsandbox-service cleanly\", error);\n process.exit(1);\n }\n };\n\n process.once(\"SIGINT\", () => {\n void shutdown(0);\n });\n\n process.once(\"SIGTERM\", () => {\n void shutdown(0);\n });\n\n await app.listen({ port: config.port, host: config.host });\n return app;\n}\n\nif (require.main === module) {\n void startServer().catch((error) => {\n console.error(\"Failed to start microsandbox-service\", error);\n process.exit(1);\n });\n}\n"],"mappings":";;;;;;AAUA,SAAS,UAAU,KAAa,QAAwB;AACtD,QAAM,QAAQ,OAAO,GAAG;AACxB,MAAI,CAAC,OAAO,UAAU,KAAK,KAAK,SAAS,GAAG;AAC1C,UAAM,IAAI,MAAM,qBAAqB,MAAM,KAAK,GAAG,EAAE;AAAA,EACvD;AACA,SAAO;AACT;AAEO,SAAS,gBAAgB,MAAkC;AAChE,QAAM,SAA2B,CAAC;AAElC,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,GAAG;AACnD,UAAM,QAAQ,KAAK,KAAK;AAExB,QAAI,UAAU,UAAU;AACtB,YAAM,MAAM,KAAK,QAAQ,CAAC;AAC1B,UAAI,CAAC,KAAK;AACR,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,aAAO,OAAO,UAAU,KAAK,QAAQ;AACrC,eAAS;AACT;AAAA,IACF;AAEA,QAAI,UAAU,UAAU;AACtB,YAAM,MAAM,KAAK,QAAQ,CAAC;AAC1B,UAAI,CAAC,KAAK;AACR,cAAM,IAAI,MAAM,0BAA0B;AAAA,MAC5C;AACA,aAAO,OAAO;AACd,eAAS;AAAA,IACX;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AACF,GAGyB;AACvB,SAAO;AAAA,IACL,MAAM,KAAK,QAAQ,IAAI,QAAQ;AAAA,IAC/B,MAAM,KAAK,SAAS,IAAI,OAAO,UAAU,IAAI,MAAM,MAAM,IAAI;AAAA,EAC/D;AACF;;;ACtDA,eAAsB,YAAY;AAAA,EAChC,OAAO,QAAQ,KAAK,MAAM,CAAC;AAAA,EAC3B,MAAM,QAAQ;AAAA,EACd,MAAM,SAAS;AACjB,IAII,CAAC,GAAuD;AAC1D,QAAM,SAAS,oBAAoB,EAAE,MAAM,gBAAgB,IAAI,GAAG,IAAI,CAAC;AAEvE,QAAM,WAAW,OAAO,SAAgC;AACtD,QAAI;AACF,YAAM,IAAI,MAAM;AAChB,cAAQ,KAAK,IAAI;AAAA,IACnB,SAAS,OAAO;AACd,cAAQ,MAAM,mDAAmD,KAAK;AACtE,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AAEA,UAAQ,KAAK,UAAU,MAAM;AAC3B,SAAK,SAAS,CAAC;AAAA,EACjB,CAAC;AAED,UAAQ,KAAK,WAAW,MAAM;AAC5B,SAAK,SAAS,CAAC;AAAA,EACjB,CAAC;AAED,QAAM,IAAI,OAAO,EAAE,MAAM,OAAO,MAAM,MAAM,OAAO,KAAK,CAAC;AACzD,SAAO;AACT;AAEA,IAAI,UAAQ,SAAS,QAAQ;AAC3B,OAAK,YAAY,EAAE,MAAM,CAAC,UAAU;AAClC,YAAQ,MAAM,wCAAwC,KAAK;AAC3D,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACH;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/services/SandboxRegistry.ts","../src/services/MicrosandboxRuntimeService.ts","../src/services/SandboxMetadataStore.ts","../src/app.ts","../src/lib/errors.ts","../src/lib/http.ts","../src/routes/health.ts","../src/controllers/images.ts","../src/schemas/images.ts","../src/routes/images.ts","../src/schemas/sandbox.ts","../src/controllers/sandbox.ts","../src/routes/sandbox.ts","../src/services/ImageService.ts"],"sourcesContent":["import { Sandbox } from \"microsandbox\";\n\nconst DEFAULT_IMAGE = process.env.MICROSANDBOX_IMAGE ?? \"daytonaio/sandbox:0.6.0\";\n\nexport class SandboxRegistry {\n private handles = new Map<string, unknown>();\n private creating = new Map<string, Promise<unknown>>();\n\n async ensure(name: string, createOptions?: Record<string, unknown>): Promise<unknown> {\n const cached = this.handles.get(name);\n if (cached) {\n return cached;\n }\n\n const inflight = this.creating.get(name);\n if (inflight) {\n return inflight;\n }\n\n const creation = (async () => {\n let native: unknown;\n const createConfig = {\n name,\n image: DEFAULT_IMAGE,\n ...(createOptions ?? {}),\n };\n\n try {\n const handle = await Sandbox.get(name);\n\n if (handle.status === \"running\") {\n native = await handle.connect();\n } else if (handle.status === \"stopped\") {\n native = await Sandbox.start(name);\n } else if (handle.status === \"crashed\" || handle.status === \"draining\") {\n await Sandbox.remove(name);\n }\n } catch {\n native = undefined;\n }\n\n if (!native) {\n native = await Sandbox.createDetached(createConfig);\n }\n\n this.handles.set(name, native);\n return native;\n })();\n\n this.creating.set(name, creation);\n void creation.finally(() => this.creating.delete(name));\n\n return creation;\n }\n\n get(name: string): unknown {\n const handle = this.handles.get(name);\n\n if (!handle) {\n throw new Error(`Sandbox ${name} not found`);\n }\n\n return handle;\n }\n\n delete(name: string): void {\n this.handles.delete(name);\n }\n}\n","import { Mount, Sandbox } from \"microsandbox\";\nimport type { EnsureSandboxInput, ListSandboxesQuery, ShellExecInput } from \"../schemas/sandbox\";\nimport { SandboxRegistry } from \"./SandboxRegistry\";\nimport {\n SandboxMetadataStore,\n type SandboxMetadataRecord,\n type SandboxMetadataStoreLike,\n} from \"./SandboxMetadataStore\";\n\ntype VolumeConfig = NonNullable<EnsureSandboxInput[\"volumes\"]>[string];\n\ntype RuntimeNative = {\n fs(): {\n readString(path: string): Promise<string>;\n write(path: string, data: Buffer): Promise<void>;\n list(\n path: string,\n options?: { recursive?: boolean }\n ): Promise<Array<{ path: string; kind: string; size?: number; modified?: unknown }>>;\n read(path: string): Promise<Buffer | Uint8Array | string>;\n };\n execWithConfig(config: {\n cmd: string;\n args?: string[];\n cwd?: string;\n timeoutMs?: number;\n }): Promise<{\n stdout(): string;\n stderr?(): string;\n code?: number;\n }>;\n stop(): Promise<void>;\n kill(): Promise<void>;\n};\n\nexport class MicrosandboxRuntimeService {\n constructor(private deps: { registry?: SandboxRegistry; metadataStore?: SandboxMetadataStoreLike } = {}) {}\n\n private get registry(): SandboxRegistry {\n return this.deps.registry ?? (this.deps.registry = new SandboxRegistry());\n }\n\n private get metadataStore(): SandboxMetadataStoreLike {\n return this.deps.metadataStore ?? (this.deps.metadataStore = new SandboxMetadataStore());\n }\n\n private toListItem(record: SandboxMetadataRecord): {\n name: string;\n status: string;\n image?: string;\n cpus?: number;\n memoryMib?: number;\n envCount: number;\n volumeCount: number;\n createdAt: string;\n updatedAt: string;\n } {\n return {\n name: record.name,\n status: record.status,\n image: record.image,\n cpus: record.cpus,\n memoryMib: record.memoryMib,\n envCount: Object.keys(record.env).length,\n volumeCount: Object.keys(record.volumes).length,\n createdAt: record.createdAt,\n updatedAt: record.updatedAt,\n };\n }\n\n private buildVolumes(volumes?: EnsureSandboxInput[\"volumes\"]): Record<string, unknown> | undefined {\n if (!volumes) {\n return undefined;\n }\n\n return Object.fromEntries(\n Object.entries(volumes).map(([mountPath, definition]) => [mountPath, this.buildVolume(definition)])\n );\n }\n\n private buildVolume(definition: VolumeConfig): unknown {\n if (definition.type === \"bind\") {\n return Mount.bind(definition.source, { readonly: definition.readonly ?? false });\n }\n\n if (definition.type === \"named\") {\n return Mount.named(definition.name, { readonly: definition.readonly ?? false });\n }\n\n return Mount.tmpfs({ sizeMib: definition.sizeMib });\n }\n\n private getNative(name: string): RuntimeNative {\n return this.registry.get(name) as RuntimeNative;\n }\n\n private async getOrEnsureNative(name: string): Promise<RuntimeNative> {\n try {\n return this.getNative(name);\n } catch {\n return (await this.registry.ensure(name)) as RuntimeNative;\n }\n }\n\n async ensureSandbox(name: string, input: EnsureSandboxInput): Promise<{\n name: string;\n status: string;\n }> {\n const image = input.image ?? process.env.MICROSANDBOX_IMAGE ?? \"python:3.11-slim\";\n const cpus = input.cpus ?? Number(process.env.MICROSANDBOX_CPUS ?? \"1\");\n const memoryMib = input.memoryMib ?? Number(process.env.MICROSANDBOX_MEMORY ?? \"512\");\n\n await this.registry.ensure(name, {\n image,\n cpus,\n memoryMib,\n env: input.env,\n volumes: this.buildVolumes(input.volumes),\n });\n\n const existing = await this.metadataStore.get(name);\n const now = new Date().toISOString();\n\n await this.metadataStore.upsert({\n name,\n status: \"running\",\n image,\n cpus,\n memoryMib,\n env: input.env ?? {},\n volumes: input.volumes ?? {},\n createdAt: existing?.createdAt ?? now,\n updatedAt: now,\n });\n\n return {\n name,\n status: \"running\",\n };\n }\n\n async startSandbox(name: string): Promise<{ name: string; status: string }> {\n await Sandbox.start(name);\n this.registry.delete(name);\n await this.registry.ensure(name);\n await this.metadataStore.patchStatus(name, \"running\");\n return { name, status: \"running\" };\n }\n\n async stopSandbox(name: string): Promise<{ name: string; status: string }> {\n const native = await this.getOrEnsureNative(name);\n await native.stop();\n this.registry.delete(name);\n await this.metadataStore.patchStatus(name, \"stopped\");\n return { name, status: \"stopped\" };\n }\n\n async killSandbox(name: string): Promise<{ name: string; status: string }> {\n const native = await this.getOrEnsureNative(name);\n await native.kill();\n this.registry.delete(name);\n await this.metadataStore.patchStatus(name, \"unknown\");\n return { name, status: \"unknown\" };\n }\n\n async deleteSandbox(name: string): Promise<{ name: string; status: string }> {\n try {\n await this.getNative(name).kill();\n } catch {\n // Ignore missing cached native handle before remove.\n }\n\n await Sandbox.remove(name);\n this.registry.delete(name);\n await this.metadataStore.remove(name);\n\n return { name, status: \"unknown\" };\n }\n\n async listSandboxes(_query: ListSandboxesQuery): Promise<{\n items: Array<{\n name: string;\n status: string;\n image?: string;\n cpus?: number;\n memoryMib?: number;\n envCount: number;\n volumeCount: number;\n createdAt: string;\n updatedAt: string;\n }>;\n total: number;\n }> {\n const items = (await this.metadataStore.list())\n .filter((record) => {\n if (_query.status && record.status !== _query.status) {\n return false;\n }\n\n if (_query.image && record.image !== _query.image) {\n return false;\n }\n\n if (_query.search) {\n const haystack = [record.name, record.image, ...Object.keys(record.env)]\n .filter((value): value is string => Boolean(value))\n .join(\" \")\n .toLowerCase();\n\n if (!haystack.includes(_query.search.toLowerCase())) {\n return false;\n }\n }\n\n return true;\n })\n .map((record) => this.toListItem(record));\n\n return { items, total: items.length };\n }\n\n async getSandbox(name: string): Promise<SandboxMetadataRecord | undefined> {\n return await this.metadataStore.get(name);\n }\n\n async getStatus(name: string): Promise<{ name: string; status: string }> {\n try {\n this.getNative(name);\n return { name, status: \"running\" };\n } catch {\n return { name, status: \"unknown\" };\n }\n }\n\n async readFile(sandboxName: string, path: string): Promise<{ path: string; content: string }> {\n const content = await this.getNative(sandboxName).fs().readString(path);\n return { path, content };\n }\n\n async writeFile(sandboxName: string, path: string, content: string): Promise<{ path: string }> {\n await this.getNative(sandboxName).fs().write(path, Buffer.from(content));\n return { path };\n }\n\n async listPath(\n sandboxName: string,\n path: string,\n recursive?: boolean\n ): Promise<{ entries: Array<{ path: string; type: string }> }> {\n const entries = await this.getNative(sandboxName).fs().list(path, { recursive });\n return {\n entries: entries.map((entry) => ({\n path: entry.path,\n type: entry.kind,\n })),\n };\n }\n\n async findFiles(sandboxName: string, path: string, pattern: string): Promise<{ files: string[] }> {\n const output = await this.getNative(sandboxName).execWithConfig({\n cmd: \"find\",\n args: [path, \"-name\", pattern, \"-type\", \"f\"],\n });\n return { files: output.stdout().split(\"\\n\").filter(Boolean) };\n }\n\n async searchInFile(\n sandboxName: string,\n path: string,\n query: string\n ): Promise<{ matches: Array<{ line: number; content: string }> }> {\n let stdout: string;\n\n try {\n const output = await this.getNative(sandboxName).execWithConfig({\n cmd: \"grep\",\n args: [\"-n\", \"-E\", query, path],\n });\n stdout = output.stdout();\n } catch (error) {\n if (typeof error === \"object\" && error !== null && \"code\" in error && error.code === 1) {\n return { matches: [] };\n }\n\n throw error;\n }\n\n return {\n matches: stdout\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => {\n const separator = line.indexOf(\":\");\n return {\n line: Number(line.slice(0, separator)),\n content: line.slice(separator + 1),\n };\n }),\n };\n }\n\n async replaceInFile(\n sandboxName: string,\n input: { path: string; search: string; replace: string }\n ): Promise<{ replaced: number }> {\n const fs = this.getNative(sandboxName).fs();\n const original = await fs.readString(input.path);\n\n if (!input.search) {\n return { replaced: 0 };\n }\n\n const occurrences = original.split(input.search).length - 1;\n if (occurrences === 0) {\n return { replaced: 0 };\n }\n\n const updated = original.split(input.search).join(input.replace);\n await fs.write(input.path, Buffer.from(updated));\n\n return { replaced: occurrences };\n }\n\n async uploadFile(sandboxName: string, path: string, contentBase64: string): Promise<{ path: string }> {\n await this.getNative(sandboxName).fs().write(path, Buffer.from(contentBase64, \"base64\"));\n return { path };\n }\n\n async downloadFile(sandboxName: string, path: string): Promise<{ path: string; contentBase64: string }> {\n const data = await this.getNative(sandboxName).fs().read(path);\n const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);\n return { path, contentBase64: buffer.toString(\"base64\") };\n }\n\n async execCommand(input: ShellExecInput): Promise<{ stdout: string; stderr: string; exitCode: number }> {\n const output = await this.getNative(input.sandboxName).execWithConfig({\n cmd: \"sh\",\n args: [\"-c\", input.command],\n cwd: input.exec_dir,\n timeoutMs: input.timeout ? input.timeout * 1000 : undefined,\n });\n\n return {\n stdout: output.stdout(),\n stderr: output.stderr ? output.stderr() : \"\",\n exitCode: output.code ?? 0,\n };\n }\n}\n","import { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname } from \"node:path\";\nimport type { EnsureSandboxInput } from \"../schemas/sandbox\";\n\nexport type SandboxStatus = \"running\" | \"stopped\" | \"unknown\";\n\nexport type SandboxMetadataRecord = {\n name: string;\n status: SandboxStatus;\n image?: string;\n cpus?: number;\n memoryMib?: number;\n env: Record<string, string>;\n volumes: NonNullable<EnsureSandboxInput[\"volumes\"]>;\n createdAt: string;\n updatedAt: string;\n};\n\ntype SandboxMetadataFile = {\n sandboxes: SandboxMetadataRecord[];\n};\n\nfunction isSandboxMetadataFile(value: unknown): value is SandboxMetadataFile {\n return typeof value === \"object\" && value !== null && \"sandboxes\" in value && Array.isArray(value.sandboxes);\n}\n\nexport interface SandboxMetadataStoreLike {\n list(): Promise<SandboxMetadataRecord[]>;\n get(name: string): Promise<SandboxMetadataRecord | undefined>;\n upsert(record: SandboxMetadataRecord): Promise<SandboxMetadataRecord>;\n patchStatus(name: string, status: SandboxStatus): Promise<void>;\n remove(name: string): Promise<void>;\n}\n\nexport class SandboxMetadataStore implements SandboxMetadataStoreLike {\n constructor(private readonly options: { filePath?: string } = {}) {}\n\n private get filePath(): string {\n return this.options.filePath ?? process.env.MICROSANDBOX_METADATA_FILE ?? \".microsandbox/metadata.json\";\n }\n\n private async readAll(): Promise<SandboxMetadataFile> {\n try {\n const content = await readFile(this.filePath, \"utf8\");\n const parsed: unknown = JSON.parse(content);\n\n if (!isSandboxMetadataFile(parsed)) {\n throw new Error(\"Invalid sandbox metadata file\");\n }\n\n return parsed;\n } catch (error) {\n if (typeof error === \"object\" && error !== null && \"code\" in error && error.code === \"ENOENT\") {\n return { sandboxes: [] };\n }\n\n throw error;\n }\n }\n\n private async writeAll(file: SandboxMetadataFile): Promise<void> {\n await mkdir(dirname(this.filePath), { recursive: true });\n await writeFile(this.filePath, JSON.stringify(file, null, 2));\n }\n\n async list(): Promise<SandboxMetadataRecord[]> {\n const file = await this.readAll();\n return file.sandboxes.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));\n }\n\n async get(name: string): Promise<SandboxMetadataRecord | undefined> {\n const file = await this.readAll();\n return file.sandboxes.find((entry) => entry.name === name);\n }\n\n async upsert(record: SandboxMetadataRecord): Promise<SandboxMetadataRecord> {\n const file = await this.readAll();\n const next = file.sandboxes.filter((entry) => entry.name !== record.name);\n next.push(record);\n await this.writeAll({ sandboxes: next });\n return record;\n }\n\n async patchStatus(name: string, status: SandboxStatus): Promise<void> {\n const current = await this.get(name);\n if (!current) {\n return;\n }\n\n await this.upsert({ ...current, status, updatedAt: new Date().toISOString() });\n }\n\n async remove(name: string): Promise<void> {\n const file = await this.readAll();\n await this.writeAll({ sandboxes: file.sandboxes.filter((entry) => entry.name !== name) });\n }\n}\n","import cors from \"@fastify/cors\";\nimport multipart from \"@fastify/multipart\";\nimport sensible from \"@fastify/sensible\";\nimport fastify, { type FastifyInstance } from \"fastify\";\nimport { toErrorResponse } from \"./lib/errors\";\nimport { registerHealthRoutes } from \"./routes/health\";\nimport { registerImageRoutes } from \"./routes/images\";\nimport { registerSandboxRoutes } from \"./routes/sandbox\";\nimport { ImageService } from \"./services/ImageService\";\nimport { MicrosandboxRuntimeService } from \"./services/MicrosandboxRuntimeService\";\nimport type { RuntimeService } from \"./types/runtime-service\";\n\nexport function buildApp({\n runtimeService = new MicrosandboxRuntimeService(),\n imageService = new ImageService(),\n}: {\n runtimeService?: RuntimeService;\n imageService?: ImageService;\n} = {}): FastifyInstance {\n const app = fastify({ logger: false });\n\n app.register(cors, { origin: true });\n app.register(sensible);\n app.register(multipart);\n\n registerHealthRoutes(app);\n registerSandboxRoutes(app, runtimeService);\n registerImageRoutes(app, imageService);\n\n app.setErrorHandler((error, _request, reply) => {\n const { statusCode, body } = toErrorResponse(error);\n reply.status(statusCode).send(body);\n });\n\n return app;\n}\n","export class HttpError extends Error {\n constructor(\n public statusCode: number,\n public code: string,\n message: string\n ) {\n super(message);\n }\n}\n\nexport function toErrorResponse(error: unknown): {\n statusCode: number;\n body: {\n success: false;\n error: {\n code: string;\n message: string;\n };\n };\n} {\n if (error instanceof HttpError) {\n return {\n statusCode: error.statusCode,\n body: {\n success: false,\n error: {\n code: error.code,\n message: error.message,\n },\n },\n };\n }\n\n return {\n statusCode: 500,\n body: {\n success: false,\n error: {\n code: \"INTERNAL_ERROR\",\n message: error instanceof Error ? error.message : String(error),\n },\n },\n };\n}\n","export type SuccessResponse<T> = {\n success: true;\n data: T;\n};\n\nexport function ok<T>(data: T): SuccessResponse<T> {\n return {\n success: true,\n data,\n };\n}\n","import type { FastifyInstance } from \"fastify\";\nimport { ok } from \"../lib/http\";\n\nexport function registerHealthRoutes(app: FastifyInstance): void {\n app.get(\"/health\", async () => ok({ status: \"ok\" }));\n}\n","import type { FastifyReply, FastifyRequest } from \"fastify\";\nimport { ZodError } from \"zod\";\nimport { HttpError } from \"../lib/errors\";\nimport { ok } from \"../lib/http\";\nimport { ImageService } from \"../services/ImageService\";\nimport { imageRefQuerySchema, pullImageSchema } from \"../schemas/images\";\n\nfunction parseOrThrow<T>(parse: () => T): T {\n try {\n return parse();\n } catch (error) {\n if (error instanceof ZodError) {\n throw new HttpError(400, \"INVALID_REQUEST\", error.issues[0]?.message ?? \"Invalid request\");\n }\n\n throw error;\n }\n}\n\nexport function createImageController(imageService: ImageService) {\n return {\n listImages: async (_request: FastifyRequest, reply: FastifyReply) => {\n return reply.send(ok(await imageService.listImages()));\n },\n pullImage: async (request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply) => {\n const body = parseOrThrow(() => pullImageSchema.parse(request.body ?? {}));\n return reply.send(ok(await imageService.pullImage(body)));\n },\n getImage: async (request: FastifyRequest<{ Querystring: unknown }>, reply: FastifyReply) => {\n const query = parseOrThrow(() => imageRefQuerySchema.parse(request.query ?? {}));\n return reply.send(ok(await imageService.getImage(query.ref)));\n },\n deleteImage: async (request: FastifyRequest<{ Querystring: unknown }>, reply: FastifyReply) => {\n const query = parseOrThrow(() => imageRefQuerySchema.parse(request.query ?? {}));\n return reply.send(ok(await imageService.deleteImage(query.ref)));\n },\n };\n}\n","import z from \"zod\";\n\nexport const imageRefQuerySchema = z.object({\n ref: z.string().min(1),\n});\n\nexport const pullImageSchema = z.object({\n ref: z.string().min(1),\n});\n\nexport type ImageRefQuery = z.infer<typeof imageRefQuerySchema>;\nexport type PullImageInput = z.infer<typeof pullImageSchema>;\n","import type { FastifyInstance } from \"fastify\";\nimport { createImageController } from \"../controllers/images\";\nimport { ImageService } from \"../services/ImageService\";\n\nexport function registerImageRoutes(app: FastifyInstance, imageService: ImageService): void {\n const controller = createImageController(imageService);\n\n app.get(\"/api/images\", controller.listImages);\n app.post(\"/api/images/pull\", controller.pullImage);\n app.get(\"/api/images/detail\", controller.getImage);\n app.delete(\"/api/images/detail\", controller.deleteImage);\n}\n","import z from \"zod\";\n\nconst bindMountSchema = z.object({\n type: z.literal(\"bind\"),\n source: z.string().min(1),\n readonly: z.boolean().optional(),\n});\n\nconst namedMountSchema = z.object({\n type: z.literal(\"named\"),\n name: z.string().min(1),\n readonly: z.boolean().optional(),\n});\n\nconst tmpfsMountSchema = z.object({\n type: z.literal(\"tmpfs\"),\n sizeMib: z.number().int().positive().optional(),\n});\n\nexport const volumeSchema = z.union([\n bindMountSchema,\n namedMountSchema,\n tmpfsMountSchema,\n]);\n\nexport const ensureSandboxSchema = z.object({\n image: z.string().optional(),\n cpus: z.number().int().positive().optional(),\n memoryMib: z.number().int().positive().optional(),\n env: z.record(z.string()).optional(),\n volumes: z.record(volumeSchema).optional(),\n});\n\nexport const sandboxNameParamsSchema = z.object({\n name: z.string().min(1),\n});\n\nexport const listSandboxesQuerySchema = z.object({\n status: z.enum([\"running\", \"stopped\", \"unknown\"]).optional(),\n image: z.string().min(1).optional(),\n search: z.string().min(1).optional(),\n});\n\nconst sandboxAndPathSchema = z.object({\n sandboxName: z.string().min(1),\n path: z.string().min(1),\n});\n\nexport const readFileSchema = sandboxAndPathSchema;\n\nexport const writeFileSchema = sandboxAndPathSchema.extend({\n content: z.string(),\n});\n\nexport const listPathSchema = sandboxAndPathSchema.extend({\n recursive: z.boolean().optional(),\n});\n\nexport const findFilesSchema = sandboxAndPathSchema.extend({\n pattern: z.string().min(1),\n});\n\nexport const searchInFileSchema = sandboxAndPathSchema.extend({\n query: z.string().min(1),\n});\n\nexport const replaceInFileSchema = sandboxAndPathSchema.extend({\n search: z.string(),\n replace: z.string(),\n});\n\nexport const uploadFileSchema = sandboxAndPathSchema.and(\n z.union([\n z.object({ contentBase64: z.string() }),\n z.object({ content: z.string().transform((content) => Buffer.from(content).toString(\"base64\")) }),\n ])\n);\n\nexport const downloadFileSchema = sandboxAndPathSchema;\n\nexport const shellExecSchema = z.object({\n sandboxName: z.string().min(1),\n command: z.string().min(1),\n exec_dir: z.string().optional(),\n timeout: z.number().int().positive().optional(),\n});\n\nexport type EnsureSandboxInput = z.infer<typeof ensureSandboxSchema>;\nexport type ListSandboxesQuery = z.infer<typeof listSandboxesQuerySchema>;\nexport type ShellExecInput = z.infer<typeof shellExecSchema>;\n","import type { FastifyReply, FastifyRequest } from \"fastify\";\nimport {\n downloadFileSchema,\n ensureSandboxSchema,\n findFilesSchema,\n listSandboxesQuerySchema,\n listPathSchema,\n readFileSchema,\n replaceInFileSchema,\n sandboxNameParamsSchema,\n searchInFileSchema,\n shellExecSchema,\n uploadFileSchema,\n writeFileSchema,\n} from \"../schemas/sandbox\";\nimport { HttpError } from \"../lib/errors\";\nimport { ok } from \"../lib/http\";\nimport type { RuntimeService } from \"../types/runtime-service\";\n\nexport function createSandboxController(runtimeService: RuntimeService): {\n ensureSandbox(\n request: FastifyRequest<{ Params: { name: string }; Body: unknown }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n startSandbox(\n request: FastifyRequest<{ Params: { name: string } }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n stopSandbox(\n request: FastifyRequest<{ Params: { name: string } }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n killSandbox(\n request: FastifyRequest<{ Params: { name: string } }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n deleteSandbox(\n request: FastifyRequest<{ Params: { name: string } }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n listSandboxes(\n request: FastifyRequest<{ Querystring: unknown }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n getSandbox(\n request: FastifyRequest<{ Params: { name: string } }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n getStatus(\n request: FastifyRequest<{ Params: { name: string } }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n readFile(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n writeFile(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n listPath(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n findFiles(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n searchInFile(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n replaceInFile(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n uploadFile(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n downloadFile(\n request: FastifyRequest<{ Querystring: unknown }>,\n reply: FastifyReply\n ): Promise<FastifyReply>;\n execCommand(request: FastifyRequest<{ Body: unknown }>, reply: FastifyReply): Promise<FastifyReply>;\n} {\n return {\n ensureSandbox: async (\n request: FastifyRequest<{ Params: { name: string }; Body: unknown }>,\n reply: FastifyReply\n ) => {\n const body = ensureSandboxSchema.parse(request.body ?? {});\n const result = await runtimeService.ensureSandbox(request.params.name, body);\n return reply.send(ok(result));\n },\n\n startSandbox: async (request, reply) => {\n return reply.send(ok(await runtimeService.startSandbox(request.params.name)));\n },\n\n stopSandbox: async (request, reply) => {\n return reply.send(ok(await runtimeService.stopSandbox(request.params.name)));\n },\n\n killSandbox: async (request, reply) => {\n return reply.send(ok(await runtimeService.killSandbox(request.params.name)));\n },\n\n deleteSandbox: async (request, reply) => {\n return reply.send(ok(await runtimeService.deleteSandbox(request.params.name)));\n },\n\n listSandboxes: async (request, reply) => {\n const query = listSandboxesQuerySchema.parse(request.query ?? {});\n return reply.send(ok(await runtimeService.listSandboxes(query)));\n },\n\n getSandbox: async (request, reply) => {\n const params = sandboxNameParamsSchema.parse(request.params ?? {});\n const sandbox = await runtimeService.getSandbox(params.name);\n\n if (!sandbox) {\n throw new HttpError(404, \"SANDBOX_NOT_FOUND\", `Sandbox '${params.name}' not found`);\n }\n\n return reply.send(ok(sandbox));\n },\n\n getStatus: async (request, reply) => {\n return reply.send(ok(await runtimeService.getStatus(request.params.name)));\n },\n\n readFile: async (request, reply) => {\n const body = readFileSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.readFile(body.sandboxName, body.path)));\n },\n\n writeFile: async (request, reply) => {\n const body = writeFileSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.writeFile(body.sandboxName, body.path, body.content)));\n },\n\n listPath: async (request, reply) => {\n const body = listPathSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.listPath(body.sandboxName, body.path, body.recursive)));\n },\n\n findFiles: async (request, reply) => {\n const body = findFilesSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.findFiles(body.sandboxName, body.path, body.pattern)));\n },\n\n searchInFile: async (request, reply) => {\n const body = searchInFileSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.searchInFile(body.sandboxName, body.path, body.query)));\n },\n\n replaceInFile: async (request, reply) => {\n const body = replaceInFileSchema.parse(request.body ?? {});\n return reply.send(\n ok(\n await runtimeService.replaceInFile(body.sandboxName, {\n path: body.path,\n search: body.search,\n replace: body.replace,\n })\n )\n );\n },\n\n uploadFile: async (request, reply) => {\n const body = uploadFileSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.uploadFile(body.sandboxName, body.path, body.contentBase64)));\n },\n\n downloadFile: async (request, reply) => {\n const query = downloadFileSchema.parse(request.query ?? {});\n return reply.send(ok(await runtimeService.downloadFile(query.sandboxName, query.path)));\n },\n\n execCommand: async (request, reply) => {\n const body = shellExecSchema.parse(request.body ?? {});\n return reply.send(ok(await runtimeService.execCommand(body)));\n },\n };\n}\n","import type { FastifyInstance } from \"fastify\";\nimport { createSandboxController } from \"../controllers/sandbox\";\nimport type { RuntimeService } from \"../types/runtime-service\";\n\nexport function registerSandboxRoutes(\n app: FastifyInstance,\n runtimeService: RuntimeService\n): void {\n const controller = createSandboxController(runtimeService);\n\n app.get(\"/api/sandboxes\", controller.listSandboxes);\n app.get(\"/api/sandboxes/:name\", controller.getSandbox);\n app.put(\"/api/sandboxes/:name\", controller.ensureSandbox);\n app.post(\"/api/sandboxes/:name/start\", controller.startSandbox);\n app.post(\"/api/sandboxes/:name/stop\", controller.stopSandbox);\n app.post(\"/api/sandboxes/:name/kill\", controller.killSandbox);\n app.delete(\"/api/sandboxes/:name\", controller.deleteSandbox);\n app.get(\"/api/sandboxes/:name/status\", controller.getStatus);\n\n app.post(\"/api/files/read\", controller.readFile);\n app.post(\"/api/files/write\", controller.writeFile);\n app.post(\"/api/files/list\", controller.listPath);\n app.post(\"/api/files/find\", controller.findFiles);\n app.post(\"/api/files/search\", controller.searchInFile);\n app.post(\"/api/files/replace\", controller.replaceInFile);\n app.post(\"/api/files/upload\", controller.uploadFile);\n app.get(\"/api/files/download\", controller.downloadFile);\n\n app.post(\"/api/shell/exec\", controller.execCommand);\n}\n","import type { PullImageInput } from \"../schemas/images\";\n\nexport type ImageRecord = {\n ref: string;\n sourceType: string;\n cached: boolean;\n size?: number;\n createdAt?: string;\n lastUsedAt?: string;\n};\n\ntype ImageBindings = {\n list(): Promise<ImageRecord[]>;\n pull(ref: string): Promise<ImageRecord>;\n inspect(ref: string): Promise<ImageRecord>;\n remove(ref: string): Promise<void>;\n};\n\nexport class ImageService {\n constructor(\n private readonly deps: {\n bindings?: ImageBindings;\n } = {}\n ) {}\n\n private get bindings(): ImageBindings {\n return (\n this.deps.bindings ?? {\n list: async () => [],\n pull: async (ref: string) => ({ ref, sourceType: \"oci\", cached: true }),\n inspect: async (ref: string) => ({ ref, sourceType: \"oci\", cached: true }),\n remove: async () => undefined,\n }\n );\n }\n\n async listImages(): Promise<{ items: ImageRecord[]; total: number }> {\n const items = await this.bindings.list();\n return { items, total: items.length };\n }\n\n async pullImage(input: PullImageInput): Promise<ImageRecord> {\n return await this.bindings.pull(input.ref);\n }\n\n async getImage(ref: string): Promise<ImageRecord> {\n return await this.bindings.inspect(ref);\n }\n\n async deleteImage(ref: string): Promise<{ ref: string }> {\n await this.bindings.remove(ref);\n return { ref };\n }\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,eAAe;AAExB,IAAM,gBAAgB,QAAQ,IAAI,sBAAsB;AAEjD,IAAM,kBAAN,MAAsB;AAAA,EAAtB;AACL,SAAQ,UAAU,oBAAI,IAAqB;AAC3C,SAAQ,WAAW,oBAAI,IAA8B;AAAA;AAAA,EAErD,MAAM,OAAO,MAAc,eAA2D;AACpF,UAAM,SAAS,KAAK,QAAQ,IAAI,IAAI;AACpC,QAAI,QAAQ;AACV,aAAO;AAAA,IACT;AAEA,UAAM,WAAW,KAAK,SAAS,IAAI,IAAI;AACvC,QAAI,UAAU;AACZ,aAAO;AAAA,IACT;AAEA,UAAM,YAAY,YAAY;AAC5B,UAAI;AACJ,YAAM,eAAe;AAAA,QACnB;AAAA,QACA,OAAO;AAAA,QACP,GAAI,iBAAiB,CAAC;AAAA,MACxB;AAEA,UAAI;AACF,cAAM,SAAS,MAAM,QAAQ,IAAI,IAAI;AAErC,YAAI,OAAO,WAAW,WAAW;AAC/B,mBAAS,MAAM,OAAO,QAAQ;AAAA,QAChC,WAAW,OAAO,WAAW,WAAW;AACtC,mBAAS,MAAM,QAAQ,MAAM,IAAI;AAAA,QACnC,WAAW,OAAO,WAAW,aAAa,OAAO,WAAW,YAAY;AACtE,gBAAM,QAAQ,OAAO,IAAI;AAAA,QAC3B;AAAA,MACF,QAAQ;AACN,iBAAS;AAAA,MACX;AAEA,UAAI,CAAC,QAAQ;AACX,iBAAS,MAAM,QAAQ,eAAe,YAAY;AAAA,MACpD;AAEA,WAAK,QAAQ,IAAI,MAAM,MAAM;AAC7B,aAAO;AAAA,IACT,GAAG;AAEH,SAAK,SAAS,IAAI,MAAM,QAAQ;AAChC,SAAK,SAAS,QAAQ,MAAM,KAAK,SAAS,OAAO,IAAI,CAAC;AAEtD,WAAO;AAAA,EACT;AAAA,EAEA,IAAI,MAAuB;AACzB,UAAM,SAAS,KAAK,QAAQ,IAAI,IAAI;AAEpC,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI,MAAM,WAAW,IAAI,YAAY;AAAA,IAC7C;AAEA,WAAO;AAAA,EACT;AAAA,EAEA,OAAO,MAAoB;AACzB,SAAK,QAAQ,OAAO,IAAI;AAAA,EAC1B;AACF;;;ACpEA,SAAS,OAAO,WAAAA,gBAAe;;;ACA/B,SAAS,OAAO,UAAU,iBAAiB;AAC3C,SAAS,eAAe;AAqBxB,SAAS,sBAAsB,OAA8C;AAC3E,SAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,eAAe,SAAS,MAAM,QAAQ,MAAM,SAAS;AAC7G;AAUO,IAAM,uBAAN,MAA+D;AAAA,EACpE,YAA6B,UAAiC,CAAC,GAAG;AAArC;AAAA,EAAsC;AAAA,EAEnE,IAAY,WAAmB;AAC7B,WAAO,KAAK,QAAQ,YAAY,QAAQ,IAAI,8BAA8B;AAAA,EAC5E;AAAA,EAEA,MAAc,UAAwC;AACpD,QAAI;AACF,YAAM,UAAU,MAAM,SAAS,KAAK,UAAU,MAAM;AACpD,YAAM,SAAkB,KAAK,MAAM,OAAO;AAE1C,UAAI,CAAC,sBAAsB,MAAM,GAAG;AAClC,cAAM,IAAI,MAAM,+BAA+B;AAAA,MACjD;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,UAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU,SAAS,MAAM,SAAS,UAAU;AAC7F,eAAO,EAAE,WAAW,CAAC,EAAE;AAAA,MACzB;AAEA,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAc,SAAS,MAA0C;AAC/D,UAAM,MAAM,QAAQ,KAAK,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AACvD,UAAM,UAAU,KAAK,UAAU,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,EAC9D;AAAA,EAEA,MAAM,OAAyC;AAC7C,UAAM,OAAO,MAAM,KAAK,QAAQ;AAChC,WAAO,KAAK,UAAU,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAAA,EAC7E;AAAA,EAEA,MAAM,IAAI,MAA0D;AAClE,UAAM,OAAO,MAAM,KAAK,QAAQ;AAChC,WAAO,KAAK,UAAU,KAAK,CAAC,UAAU,MAAM,SAAS,IAAI;AAAA,EAC3D;AAAA,EAEA,MAAM,OAAO,QAA+D;AAC1E,UAAM,OAAO,MAAM,KAAK,QAAQ;AAChC,UAAM,OAAO,KAAK,UAAU,OAAO,CAAC,UAAU,MAAM,SAAS,OAAO,IAAI;AACxE,SAAK,KAAK,MAAM;AAChB,UAAM,KAAK,SAAS,EAAE,WAAW,KAAK,CAAC;AACvC,WAAO;AAAA,EACT;AAAA,EAEA,MAAM,YAAY,MAAc,QAAsC;AACpE,UAAM,UAAU,MAAM,KAAK,IAAI,IAAI;AACnC,QAAI,CAAC,SAAS;AACZ;AAAA,IACF;AAEA,UAAM,KAAK,OAAO,EAAE,GAAG,SAAS,QAAQ,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,CAAC;AAAA,EAC/E;AAAA,EAEA,MAAM,OAAO,MAA6B;AACxC,UAAM,OAAO,MAAM,KAAK,QAAQ;AAChC,UAAM,KAAK,SAAS,EAAE,WAAW,KAAK,UAAU,OAAO,CAAC,UAAU,MAAM,SAAS,IAAI,EAAE,CAAC;AAAA,EAC1F;AACF;;;AD7DO,IAAM,6BAAN,MAAiC;AAAA,EACtC,YAAoB,OAAiF,CAAC,GAAG;AAArF;AAAA,EAAsF;AAAA,EAE1G,IAAY,WAA4B;AACtC,WAAO,KAAK,KAAK,aAAa,KAAK,KAAK,WAAW,IAAI,gBAAgB;AAAA,EACzE;AAAA,EAEA,IAAY,gBAA0C;AACpD,WAAO,KAAK,KAAK,kBAAkB,KAAK,KAAK,gBAAgB,IAAI,qBAAqB;AAAA,EACxF;AAAA,EAEQ,WAAW,QAUjB;AACA,WAAO;AAAA,MACL,MAAM,OAAO;AAAA,MACb,QAAQ,OAAO;AAAA,MACf,OAAO,OAAO;AAAA,MACd,MAAM,OAAO;AAAA,MACb,WAAW,OAAO;AAAA,MAClB,UAAU,OAAO,KAAK,OAAO,GAAG,EAAE;AAAA,MAClC,aAAa,OAAO,KAAK,OAAO,OAAO,EAAE;AAAA,MACzC,WAAW,OAAO;AAAA,MAClB,WAAW,OAAO;AAAA,IACpB;AAAA,EACF;AAAA,EAEQ,aAAa,SAA8E;AACjG,QAAI,CAAC,SAAS;AACZ,aAAO;AAAA,IACT;AAEA,WAAO,OAAO;AAAA,MACZ,OAAO,QAAQ,OAAO,EAAE,IAAI,CAAC,CAAC,WAAW,UAAU,MAAM,CAAC,WAAW,KAAK,YAAY,UAAU,CAAC,CAAC;AAAA,IACpG;AAAA,EACF;AAAA,EAEQ,YAAY,YAAmC;AACrD,QAAI,WAAW,SAAS,QAAQ;AAC9B,aAAO,MAAM,KAAK,WAAW,QAAQ,EAAE,UAAU,WAAW,YAAY,MAAM,CAAC;AAAA,IACjF;AAEA,QAAI,WAAW,SAAS,SAAS;AAC/B,aAAO,MAAM,MAAM,WAAW,MAAM,EAAE,UAAU,WAAW,YAAY,MAAM,CAAC;AAAA,IAChF;AAEA,WAAO,MAAM,MAAM,EAAE,SAAS,WAAW,QAAQ,CAAC;AAAA,EACpD;AAAA,EAEQ,UAAU,MAA6B;AAC7C,WAAO,KAAK,SAAS,IAAI,IAAI;AAAA,EAC/B;AAAA,EAEA,MAAc,kBAAkB,MAAsC;AACpE,QAAI;AACF,aAAO,KAAK,UAAU,IAAI;AAAA,IAC5B,QAAQ;AACN,aAAQ,MAAM,KAAK,SAAS,OAAO,IAAI;AAAA,IACzC;AAAA,EACF;AAAA,EAEA,MAAM,cAAc,MAAc,OAG/B;AACD,UAAM,QAAQ,MAAM,SAAS,QAAQ,IAAI,sBAAsB;AAC/D,UAAM,OAAO,MAAM,QAAQ,OAAO,QAAQ,IAAI,qBAAqB,GAAG;AACtE,UAAM,YAAY,MAAM,aAAa,OAAO,QAAQ,IAAI,uBAAuB,KAAK;AAEpF,UAAM,KAAK,SAAS,OAAO,MAAM;AAAA,MAC/B;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK,MAAM;AAAA,MACX,SAAS,KAAK,aAAa,MAAM,OAAO;AAAA,IAC1C,CAAC;AAED,UAAM,WAAW,MAAM,KAAK,cAAc,IAAI,IAAI;AAClD,UAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AAEnC,UAAM,KAAK,cAAc,OAAO;AAAA,MAC9B;AAAA,MACA,QAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK,MAAM,OAAO,CAAC;AAAA,MACnB,SAAS,MAAM,WAAW,CAAC;AAAA,MAC3B,WAAW,UAAU,aAAa;AAAA,MAClC,WAAW;AAAA,IACb,CAAC;AAED,WAAO;AAAA,MACL;AAAA,MACA,QAAQ;AAAA,IACV;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,MAAyD;AAC1E,UAAMC,SAAQ,MAAM,IAAI;AACxB,SAAK,SAAS,OAAO,IAAI;AACzB,UAAM,KAAK,SAAS,OAAO,IAAI;AAC/B,UAAM,KAAK,cAAc,YAAY,MAAM,SAAS;AACpD,WAAO,EAAE,MAAM,QAAQ,UAAU;AAAA,EACnC;AAAA,EAEA,MAAM,YAAY,MAAyD;AACzE,UAAM,SAAS,MAAM,KAAK,kBAAkB,IAAI;AAChD,UAAM,OAAO,KAAK;AAClB,SAAK,SAAS,OAAO,IAAI;AACzB,UAAM,KAAK,cAAc,YAAY,MAAM,SAAS;AACpD,WAAO,EAAE,MAAM,QAAQ,UAAU;AAAA,EACnC;AAAA,EAEA,MAAM,YAAY,MAAyD;AACzE,UAAM,SAAS,MAAM,KAAK,kBAAkB,IAAI;AAChD,UAAM,OAAO,KAAK;AAClB,SAAK,SAAS,OAAO,IAAI;AACzB,UAAM,KAAK,cAAc,YAAY,MAAM,SAAS;AACpD,WAAO,EAAE,MAAM,QAAQ,UAAU;AAAA,EACnC;AAAA,EAEA,MAAM,cAAc,MAAyD;AAC3E,QAAI;AACF,YAAM,KAAK,UAAU,IAAI,EAAE,KAAK;AAAA,IAClC,QAAQ;AAAA,IAER;AAEA,UAAMA,SAAQ,OAAO,IAAI;AACzB,SAAK,SAAS,OAAO,IAAI;AACzB,UAAM,KAAK,cAAc,OAAO,IAAI;AAEpC,WAAO,EAAE,MAAM,QAAQ,UAAU;AAAA,EACnC;AAAA,EAEA,MAAM,cAAc,QAajB;AACD,UAAM,SAAS,MAAM,KAAK,cAAc,KAAK,GAC1C,OAAO,CAAC,WAAW;AAClB,UAAI,OAAO,UAAU,OAAO,WAAW,OAAO,QAAQ;AACpD,eAAO;AAAA,MACT;AAEA,UAAI,OAAO,SAAS,OAAO,UAAU,OAAO,OAAO;AACjD,eAAO;AAAA,MACT;AAEA,UAAI,OAAO,QAAQ;AACjB,cAAM,WAAW,CAAC,OAAO,MAAM,OAAO,OAAO,GAAG,OAAO,KAAK,OAAO,GAAG,CAAC,EACpE,OAAO,CAAC,UAA2B,QAAQ,KAAK,CAAC,EACjD,KAAK,GAAG,EACR,YAAY;AAEf,YAAI,CAAC,SAAS,SAAS,OAAO,OAAO,YAAY,CAAC,GAAG;AACnD,iBAAO;AAAA,QACT;AAAA,MACF;AAEA,aAAO;AAAA,IACT,CAAC,EACA,IAAI,CAAC,WAAW,KAAK,WAAW,MAAM,CAAC;AAE1C,WAAO,EAAE,OAAO,OAAO,MAAM,OAAO;AAAA,EACtC;AAAA,EAEA,MAAM,WAAW,MAA0D;AACzE,WAAO,MAAM,KAAK,cAAc,IAAI,IAAI;AAAA,EAC1C;AAAA,EAEA,MAAM,UAAU,MAAyD;AACvE,QAAI;AACF,WAAK,UAAU,IAAI;AACnB,aAAO,EAAE,MAAM,QAAQ,UAAU;AAAA,IACnC,QAAQ;AACN,aAAO,EAAE,MAAM,QAAQ,UAAU;AAAA,IACnC;AAAA,EACF;AAAA,EAEA,MAAM,SAAS,aAAqB,MAA0D;AAC5F,UAAM,UAAU,MAAM,KAAK,UAAU,WAAW,EAAE,GAAG,EAAE,WAAW,IAAI;AACtE,WAAO,EAAE,MAAM,QAAQ;AAAA,EACzB;AAAA,EAEA,MAAM,UAAU,aAAqB,MAAc,SAA4C;AAC7F,UAAM,KAAK,UAAU,WAAW,EAAE,GAAG,EAAE,MAAM,MAAM,OAAO,KAAK,OAAO,CAAC;AACvE,WAAO,EAAE,KAAK;AAAA,EAChB;AAAA,EAEA,MAAM,SACJ,aACA,MACA,WAC6D;AAC7D,UAAM,UAAU,MAAM,KAAK,UAAU,WAAW,EAAE,GAAG,EAAE,KAAK,MAAM,EAAE,UAAU,CAAC;AAC/E,WAAO;AAAA,MACL,SAAS,QAAQ,IAAI,CAAC,WAAW;AAAA,QAC/B,MAAM,MAAM;AAAA,QACZ,MAAM,MAAM;AAAA,MACd,EAAE;AAAA,IACJ;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,aAAqB,MAAc,SAA+C;AAChG,UAAM,SAAS,MAAM,KAAK,UAAU,WAAW,EAAE,eAAe;AAAA,MAC9D,KAAK;AAAA,MACL,MAAM,CAAC,MAAM,SAAS,SAAS,SAAS,GAAG;AAAA,IAC7C,CAAC;AACD,WAAO,EAAE,OAAO,OAAO,OAAO,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO,EAAE;AAAA,EAC9D;AAAA,EAEA,MAAM,aACJ,aACA,MACA,OACgE;AAChE,QAAI;AAEJ,QAAI;AACF,YAAM,SAAS,MAAM,KAAK,UAAU,WAAW,EAAE,eAAe;AAAA,QAC9D,KAAK;AAAA,QACL,MAAM,CAAC,MAAM,MAAM,OAAO,IAAI;AAAA,MAChC,CAAC;AACD,eAAS,OAAO,OAAO;AAAA,IACzB,SAAS,OAAO;AACd,UAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,UAAU,SAAS,MAAM,SAAS,GAAG;AACtF,eAAO,EAAE,SAAS,CAAC,EAAE;AAAA,MACvB;AAEA,YAAM;AAAA,IACR;AAEA,WAAO;AAAA,MACL,SAAS,OACN,MAAM,IAAI,EACV,OAAO,OAAO,EACd,IAAI,CAAC,SAAS;AACb,cAAM,YAAY,KAAK,QAAQ,GAAG;AAClC,eAAO;AAAA,UACL,MAAM,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC;AAAA,UACrC,SAAS,KAAK,MAAM,YAAY,CAAC;AAAA,QACnC;AAAA,MACF,CAAC;AAAA,IACL;AAAA,EACF;AAAA,EAEA,MAAM,cACJ,aACA,OAC+B;AAC/B,UAAM,KAAK,KAAK,UAAU,WAAW,EAAE,GAAG;AAC1C,UAAM,WAAW,MAAM,GAAG,WAAW,MAAM,IAAI;AAE/C,QAAI,CAAC,MAAM,QAAQ;AACjB,aAAO,EAAE,UAAU,EAAE;AAAA,IACvB;AAEA,UAAM,cAAc,SAAS,MAAM,MAAM,MAAM,EAAE,SAAS;AAC1D,QAAI,gBAAgB,GAAG;AACrB,aAAO,EAAE,UAAU,EAAE;AAAA,IACvB;AAEA,UAAM,UAAU,SAAS,MAAM,MAAM,MAAM,EAAE,KAAK,MAAM,OAAO;AAC/D,UAAM,GAAG,MAAM,MAAM,MAAM,OAAO,KAAK,OAAO,CAAC;AAE/C,WAAO,EAAE,UAAU,YAAY;AAAA,EACjC;AAAA,EAEA,MAAM,WAAW,aAAqB,MAAc,eAAkD;AACpG,UAAM,KAAK,UAAU,WAAW,EAAE,GAAG,EAAE,MAAM,MAAM,OAAO,KAAK,eAAe,QAAQ,CAAC;AACvF,WAAO,EAAE,KAAK;AAAA,EAChB;AAAA,EAEA,MAAM,aAAa,aAAqB,MAAgE;AACtG,UAAM,OAAO,MAAM,KAAK,UAAU,WAAW,EAAE,GAAG,EAAE,KAAK,IAAI;AAC7D,UAAM,SAAS,OAAO,SAAS,IAAI,IAAI,OAAO,OAAO,KAAK,IAAI;AAC9D,WAAO,EAAE,MAAM,eAAe,OAAO,SAAS,QAAQ,EAAE;AAAA,EAC1D;AAAA,EAEA,MAAM,YAAY,OAAsF;AACtG,UAAM,SAAS,MAAM,KAAK,UAAU,MAAM,WAAW,EAAE,eAAe;AAAA,MACpE,KAAK;AAAA,MACL,MAAM,CAAC,MAAM,MAAM,OAAO;AAAA,MAC1B,KAAK,MAAM;AAAA,MACX,WAAW,MAAM,UAAU,MAAM,UAAU,MAAO;AAAA,IACpD,CAAC;AAED,WAAO;AAAA,MACL,QAAQ,OAAO,OAAO;AAAA,MACtB,QAAQ,OAAO,SAAS,OAAO,OAAO,IAAI;AAAA,MAC1C,UAAU,OAAO,QAAQ;AAAA,IAC3B;AAAA,EACF;AACF;;;AE5VA,OAAO,UAAU;AACjB,OAAO,eAAe;AACtB,OAAO,cAAc;AACrB,OAAO,aAAuC;;;ACHvC,IAAM,YAAN,cAAwB,MAAM;AAAA,EACnC,YACS,YACA,MACP,SACA;AACA,UAAM,OAAO;AAJN;AACA;AAAA,EAIT;AACF;AAEO,SAAS,gBAAgB,OAS9B;AACA,MAAI,iBAAiB,WAAW;AAC9B,WAAO;AAAA,MACL,YAAY,MAAM;AAAA,MAClB,MAAM;AAAA,QACJ,SAAS;AAAA,QACT,OAAO;AAAA,UACL,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM;AAAA,QACjB;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,MAAM;AAAA,MACJ,SAAS;AAAA,MACT,OAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AACF;;;ACtCO,SAAS,GAAM,MAA6B;AACjD,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,EACF;AACF;;;ACPO,SAAS,qBAAqB,KAA4B;AAC/D,MAAI,IAAI,WAAW,YAAY,GAAG,EAAE,QAAQ,KAAK,CAAC,CAAC;AACrD;;;ACJA,SAAS,gBAAgB;;;ACDzB,OAAO,OAAO;AAEP,IAAM,sBAAsB,EAAE,OAAO;AAAA,EAC1C,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC;AACvB,CAAC;AAEM,IAAM,kBAAkB,EAAE,OAAO;AAAA,EACtC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC;AACvB,CAAC;;;ADDD,SAAS,aAAgB,OAAmB;AAC1C,MAAI;AACF,WAAO,MAAM;AAAA,EACf,SAAS,OAAO;AACd,QAAI,iBAAiB,UAAU;AAC7B,YAAM,IAAI,UAAU,KAAK,mBAAmB,MAAM,OAAO,CAAC,GAAG,WAAW,iBAAiB;AAAA,IAC3F;AAEA,UAAM;AAAA,EACR;AACF;AAEO,SAAS,sBAAsB,cAA4B;AAChE,SAAO;AAAA,IACL,YAAY,OAAO,UAA0B,UAAwB;AACnE,aAAO,MAAM,KAAK,GAAG,MAAM,aAAa,WAAW,CAAC,CAAC;AAAA,IACvD;AAAA,IACA,WAAW,OAAO,SAA4C,UAAwB;AACpF,YAAM,OAAO,aAAa,MAAM,gBAAgB,MAAM,QAAQ,QAAQ,CAAC,CAAC,CAAC;AACzE,aAAO,MAAM,KAAK,GAAG,MAAM,aAAa,UAAU,IAAI,CAAC,CAAC;AAAA,IAC1D;AAAA,IACA,UAAU,OAAO,SAAmD,UAAwB;AAC1F,YAAM,QAAQ,aAAa,MAAM,oBAAoB,MAAM,QAAQ,SAAS,CAAC,CAAC,CAAC;AAC/E,aAAO,MAAM,KAAK,GAAG,MAAM,aAAa,SAAS,MAAM,GAAG,CAAC,CAAC;AAAA,IAC9D;AAAA,IACA,aAAa,OAAO,SAAmD,UAAwB;AAC7F,YAAM,QAAQ,aAAa,MAAM,oBAAoB,MAAM,QAAQ,SAAS,CAAC,CAAC,CAAC;AAC/E,aAAO,MAAM,KAAK,GAAG,MAAM,aAAa,YAAY,MAAM,GAAG,CAAC,CAAC;AAAA,IACjE;AAAA,EACF;AACF;;;AEjCO,SAAS,oBAAoB,KAAsB,cAAkC;AAC1F,QAAM,aAAa,sBAAsB,YAAY;AAErD,MAAI,IAAI,eAAe,WAAW,UAAU;AAC5C,MAAI,KAAK,oBAAoB,WAAW,SAAS;AACjD,MAAI,IAAI,sBAAsB,WAAW,QAAQ;AACjD,MAAI,OAAO,sBAAsB,WAAW,WAAW;AACzD;;;ACXA,OAAOC,QAAO;AAEd,IAAM,kBAAkBA,GAAE,OAAO;AAAA,EAC/B,MAAMA,GAAE,QAAQ,MAAM;AAAA,EACtB,QAAQA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACxB,UAAUA,GAAE,QAAQ,EAAE,SAAS;AACjC,CAAC;AAED,IAAM,mBAAmBA,GAAE,OAAO;AAAA,EAChC,MAAMA,GAAE,QAAQ,OAAO;AAAA,EACvB,MAAMA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACtB,UAAUA,GAAE,QAAQ,EAAE,SAAS;AACjC,CAAC;AAED,IAAM,mBAAmBA,GAAE,OAAO;AAAA,EAChC,MAAMA,GAAE,QAAQ,OAAO;AAAA,EACvB,SAASA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;AAChD,CAAC;AAEM,IAAM,eAAeA,GAAE,MAAM;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,sBAAsBA,GAAE,OAAO;AAAA,EAC1C,OAAOA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,MAAMA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;AAAA,EAC3C,WAAWA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;AAAA,EAChD,KAAKA,GAAE,OAAOA,GAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EACnC,SAASA,GAAE,OAAO,YAAY,EAAE,SAAS;AAC3C,CAAC;AAEM,IAAM,0BAA0BA,GAAE,OAAO;AAAA,EAC9C,MAAMA,GAAE,OAAO,EAAE,IAAI,CAAC;AACxB,CAAC;AAEM,IAAM,2BAA2BA,GAAE,OAAO;AAAA,EAC/C,QAAQA,GAAE,KAAK,CAAC,WAAW,WAAW,SAAS,CAAC,EAAE,SAAS;AAAA,EAC3D,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EAClC,QAAQA,GAAE,OAAO,EAAE,IAAI,CAAC,EAAE,SAAS;AACrC,CAAC;AAED,IAAM,uBAAuBA,GAAE,OAAO;AAAA,EACpC,aAAaA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,MAAMA,GAAE,OAAO,EAAE,IAAI,CAAC;AACxB,CAAC;AAEM,IAAM,iBAAiB;AAEvB,IAAM,kBAAkB,qBAAqB,OAAO;AAAA,EACzD,SAASA,GAAE,OAAO;AACpB,CAAC;AAEM,IAAM,iBAAiB,qBAAqB,OAAO;AAAA,EACxD,WAAWA,GAAE,QAAQ,EAAE,SAAS;AAClC,CAAC;AAEM,IAAM,kBAAkB,qBAAqB,OAAO;AAAA,EACzD,SAASA,GAAE,OAAO,EAAE,IAAI,CAAC;AAC3B,CAAC;AAEM,IAAM,qBAAqB,qBAAqB,OAAO;AAAA,EAC5D,OAAOA,GAAE,OAAO,EAAE,IAAI,CAAC;AACzB,CAAC;AAEM,IAAM,sBAAsB,qBAAqB,OAAO;AAAA,EAC7D,QAAQA,GAAE,OAAO;AAAA,EACjB,SAASA,GAAE,OAAO;AACpB,CAAC;AAEM,IAAM,mBAAmB,qBAAqB;AAAA,EACnDA,GAAE,MAAM;AAAA,IACNA,GAAE,OAAO,EAAE,eAAeA,GAAE,OAAO,EAAE,CAAC;AAAA,IACtCA,GAAE,OAAO,EAAE,SAASA,GAAE,OAAO,EAAE,UAAU,CAAC,YAAY,OAAO,KAAK,OAAO,EAAE,SAAS,QAAQ,CAAC,EAAE,CAAC;AAAA,EAClG,CAAC;AACH;AAEO,IAAM,qBAAqB;AAE3B,IAAM,kBAAkBA,GAAE,OAAO;AAAA,EACtC,aAAaA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC7B,SAASA,GAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EACzB,UAAUA,GAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,SAASA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;AAChD,CAAC;;;AClEM,SAAS,wBAAwB,gBA6CtC;AACA,SAAO;AAAA,IACL,eAAe,OACb,SACA,UACG;AACH,YAAM,OAAO,oBAAoB,MAAM,QAAQ,QAAQ,CAAC,CAAC;AACzD,YAAM,SAAS,MAAM,eAAe,cAAc,QAAQ,OAAO,MAAM,IAAI;AAC3E,aAAO,MAAM,KAAK,GAAG,MAAM,CAAC;AAAA,IAC9B;AAAA,IAEA,cAAc,OAAO,SAAS,UAAU;AACtC,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,aAAa,QAAQ,OAAO,IAAI,CAAC,CAAC;AAAA,IAC9E;AAAA,IAEA,aAAa,OAAO,SAAS,UAAU;AACrC,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,YAAY,QAAQ,OAAO,IAAI,CAAC,CAAC;AAAA,IAC7E;AAAA,IAEA,aAAa,OAAO,SAAS,UAAU;AACrC,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,YAAY,QAAQ,OAAO,IAAI,CAAC,CAAC;AAAA,IAC7E;AAAA,IAEA,eAAe,OAAO,SAAS,UAAU;AACvC,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,cAAc,QAAQ,OAAO,IAAI,CAAC,CAAC;AAAA,IAC/E;AAAA,IAEA,eAAe,OAAO,SAAS,UAAU;AACvC,YAAM,QAAQ,yBAAyB,MAAM,QAAQ,SAAS,CAAC,CAAC;AAChE,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,cAAc,KAAK,CAAC,CAAC;AAAA,IACjE;AAAA,IAEA,YAAY,OAAO,SAAS,UAAU;AACpC,YAAM,SAAS,wBAAwB,MAAM,QAAQ,UAAU,CAAC,CAAC;AACjE,YAAM,UAAU,MAAM,eAAe,WAAW,OAAO,IAAI;AAE3D,UAAI,CAAC,SAAS;AACZ,cAAM,IAAI,UAAU,KAAK,qBAAqB,YAAY,OAAO,IAAI,aAAa;AAAA,MACpF;AAEA,aAAO,MAAM,KAAK,GAAG,OAAO,CAAC;AAAA,IAC/B;AAAA,IAEA,WAAW,OAAO,SAAS,UAAU;AACnC,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,UAAU,QAAQ,OAAO,IAAI,CAAC,CAAC;AAAA,IAC3E;AAAA,IAEA,UAAU,OAAO,SAAS,UAAU;AAClC,YAAM,OAAO,eAAe,MAAM,QAAQ,QAAQ,CAAC,CAAC;AACpD,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,SAAS,KAAK,aAAa,KAAK,IAAI,CAAC,CAAC;AAAA,IAClF;AAAA,IAEA,WAAW,OAAO,SAAS,UAAU;AACnC,YAAM,OAAO,gBAAgB,MAAM,QAAQ,QAAQ,CAAC,CAAC;AACrD,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,UAAU,KAAK,aAAa,KAAK,MAAM,KAAK,OAAO,CAAC,CAAC;AAAA,IACjG;AAAA,IAEA,UAAU,OAAO,SAAS,UAAU;AAClC,YAAM,OAAO,eAAe,MAAM,QAAQ,QAAQ,CAAC,CAAC;AACpD,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,SAAS,KAAK,aAAa,KAAK,MAAM,KAAK,SAAS,CAAC,CAAC;AAAA,IAClG;AAAA,IAEA,WAAW,OAAO,SAAS,UAAU;AACnC,YAAM,OAAO,gBAAgB,MAAM,QAAQ,QAAQ,CAAC,CAAC;AACrD,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,UAAU,KAAK,aAAa,KAAK,MAAM,KAAK,OAAO,CAAC,CAAC;AAAA,IACjG;AAAA,IAEA,cAAc,OAAO,SAAS,UAAU;AACtC,YAAM,OAAO,mBAAmB,MAAM,QAAQ,QAAQ,CAAC,CAAC;AACxD,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,aAAa,KAAK,aAAa,KAAK,MAAM,KAAK,KAAK,CAAC,CAAC;AAAA,IAClG;AAAA,IAEA,eAAe,OAAO,SAAS,UAAU;AACvC,YAAM,OAAO,oBAAoB,MAAM,QAAQ,QAAQ,CAAC,CAAC;AACzD,aAAO,MAAM;AAAA,QACX;AAAA,UACE,MAAM,eAAe,cAAc,KAAK,aAAa;AAAA,YACnD,MAAM,KAAK;AAAA,YACX,QAAQ,KAAK;AAAA,YACb,SAAS,KAAK;AAAA,UAChB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,IAEA,YAAY,OAAO,SAAS,UAAU;AACpC,YAAM,OAAO,iBAAiB,MAAM,QAAQ,QAAQ,CAAC,CAAC;AACtD,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,WAAW,KAAK,aAAa,KAAK,MAAM,KAAK,aAAa,CAAC,CAAC;AAAA,IACxG;AAAA,IAEA,cAAc,OAAO,SAAS,UAAU;AACtC,YAAM,QAAQ,mBAAmB,MAAM,QAAQ,SAAS,CAAC,CAAC;AAC1D,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,aAAa,MAAM,aAAa,MAAM,IAAI,CAAC,CAAC;AAAA,IACxF;AAAA,IAEA,aAAa,OAAO,SAAS,UAAU;AACrC,YAAM,OAAO,gBAAgB,MAAM,QAAQ,QAAQ,CAAC,CAAC;AACrD,aAAO,MAAM,KAAK,GAAG,MAAM,eAAe,YAAY,IAAI,CAAC,CAAC;AAAA,IAC9D;AAAA,EACF;AACF;;;AChKO,SAAS,sBACd,KACA,gBACM;AACN,QAAM,aAAa,wBAAwB,cAAc;AAEzD,MAAI,IAAI,kBAAkB,WAAW,aAAa;AAClD,MAAI,IAAI,wBAAwB,WAAW,UAAU;AACrD,MAAI,IAAI,wBAAwB,WAAW,aAAa;AACxD,MAAI,KAAK,8BAA8B,WAAW,YAAY;AAC9D,MAAI,KAAK,6BAA6B,WAAW,WAAW;AAC5D,MAAI,KAAK,6BAA6B,WAAW,WAAW;AAC5D,MAAI,OAAO,wBAAwB,WAAW,aAAa;AAC3D,MAAI,IAAI,+BAA+B,WAAW,SAAS;AAE3D,MAAI,KAAK,mBAAmB,WAAW,QAAQ;AAC/C,MAAI,KAAK,oBAAoB,WAAW,SAAS;AACjD,MAAI,KAAK,mBAAmB,WAAW,QAAQ;AAC/C,MAAI,KAAK,mBAAmB,WAAW,SAAS;AAChD,MAAI,KAAK,qBAAqB,WAAW,YAAY;AACrD,MAAI,KAAK,sBAAsB,WAAW,aAAa;AACvD,MAAI,KAAK,qBAAqB,WAAW,UAAU;AACnD,MAAI,IAAI,uBAAuB,WAAW,YAAY;AAEtD,MAAI,KAAK,mBAAmB,WAAW,WAAW;AACpD;;;ACXO,IAAM,eAAN,MAAmB;AAAA,EACxB,YACmB,OAEb,CAAC,GACL;AAHiB;AAAA,EAGhB;AAAA,EAEH,IAAY,WAA0B;AACpC,WACE,KAAK,KAAK,YAAY;AAAA,MACpB,MAAM,YAAY,CAAC;AAAA,MACnB,MAAM,OAAO,SAAiB,EAAE,KAAK,YAAY,OAAO,QAAQ,KAAK;AAAA,MACrE,SAAS,OAAO,SAAiB,EAAE,KAAK,YAAY,OAAO,QAAQ,KAAK;AAAA,MACxE,QAAQ,YAAY;AAAA,IACtB;AAAA,EAEJ;AAAA,EAEA,MAAM,aAA+D;AACnE,UAAM,QAAQ,MAAM,KAAK,SAAS,KAAK;AACvC,WAAO,EAAE,OAAO,OAAO,MAAM,OAAO;AAAA,EACtC;AAAA,EAEA,MAAM,UAAU,OAA6C;AAC3D,WAAO,MAAM,KAAK,SAAS,KAAK,MAAM,GAAG;AAAA,EAC3C;AAAA,EAEA,MAAM,SAAS,KAAmC;AAChD,WAAO,MAAM,KAAK,SAAS,QAAQ,GAAG;AAAA,EACxC;AAAA,EAEA,MAAM,YAAY,KAAuC;AACvD,UAAM,KAAK,SAAS,OAAO,GAAG;AAC9B,WAAO,EAAE,IAAI;AAAA,EACf;AACF;;;AVzCO,SAAS,SAAS;AAAA,EACvB,iBAAiB,IAAI,2BAA2B;AAAA,EAChD,eAAe,IAAI,aAAa;AAClC,IAGI,CAAC,GAAoB;AACvB,QAAM,MAAM,QAAQ,EAAE,QAAQ,MAAM,CAAC;AAErC,MAAI,SAAS,MAAM,EAAE,QAAQ,KAAK,CAAC;AACnC,MAAI,SAAS,QAAQ;AACrB,MAAI,SAAS,SAAS;AAEtB,uBAAqB,GAAG;AACxB,wBAAsB,KAAK,cAAc;AACzC,sBAAoB,KAAK,YAAY;AAErC,MAAI,gBAAgB,CAAC,OAAO,UAAU,UAAU;AAC9C,UAAM,EAAE,YAAY,KAAK,IAAI,gBAAgB,KAAK;AAClD,UAAM,OAAO,UAAU,EAAE,KAAK,IAAI;AAAA,EACpC,CAAC;AAED,SAAO;AACT;","names":["Sandbox","Sandbox","z"]}