@sandblocks/cli 0.5.0-b.0 → 0.5.0-b.2

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/cli.js CHANGED
@@ -11735,7 +11735,7 @@ var init_src = __esm(() => {
11735
11735
  init_src();
11736
11736
  import { randomUUID as randomUUID4 } from "crypto";
11737
11737
  import { lstat, readFile as readFile8, stat as stat3, writeFile as writeFile5 } from "fs/promises";
11738
- import path5 from "path";
11738
+ import path6 from "path";
11739
11739
 
11740
11740
  // src/hooks.ts
11741
11741
  import { chmod, copyFile, mkdir, readFile as readFile3, rm, writeFile } from "fs/promises";
@@ -11877,11 +11877,125 @@ async function text(file) {
11877
11877
  }
11878
11878
  }
11879
11879
 
11880
+ // src/runtime.ts
11881
+ import { spawn } from "child_process";
11882
+ import { access, chmod as chmod2, mkdir as mkdir2, readdir, rm as rm2 } from "fs/promises";
11883
+ import path4 from "path";
11884
+ import { pathToFileURL } from "url";
11885
+ var RUNTIME_PATTERN = /^[a-z][a-z0-9-]{0,62}$/;
11886
+ var NODE_ENV_RUNTIME = {
11887
+ production: "prod",
11888
+ development: "dev",
11889
+ test: "local"
11890
+ };
11891
+ async function runRuntimeCommand(args) {
11892
+ const [action, ...rest] = args;
11893
+ if (action !== "prepare")
11894
+ throw new Error("runtime action must be prepare");
11895
+ const options = parse(rest);
11896
+ const root = path4.resolve(options.positionals[0] ?? process.cwd());
11897
+ const runtime = normalizedRuntime(option(options, "runtime") ?? process.env.SANDBLOCKS_RUNTIME ?? "development" ?? "development");
11898
+ const targetRoot = contained(root, option(options, "target") ?? ".locker", "runtime target");
11899
+ const runtimeRoot = path4.join(targetRoot, runtime);
11900
+ const config = contained(root, option(options, "config") ?? "config/dotlocker.config.ts", "Dotlocker config");
11901
+ const dotlocker = path4.resolve(root, option(options, "dotlocker") ?? path4.join("node_modules", ".bin", "dotlocker"));
11902
+ if (!await exists2(config))
11903
+ throw new Error(`Dotlocker config does not exist: ${config}`);
11904
+ if (!await exists2(dotlocker)) {
11905
+ throw new Error("Dotlocker is not installed; add @dotlocker/dotlocker to the workspace");
11906
+ }
11907
+ await rm2(targetRoot, { recursive: true, force: true });
11908
+ await mkdir2(runtimeRoot, { recursive: true, mode: 493 });
11909
+ await run(dotlocker, ["pull", "--config", config, "--runtime", runtime, "--out", runtimeRoot], root);
11910
+ const envFile = path4.join(runtimeRoot, ".env");
11911
+ if (!await exists2(envFile)) {
11912
+ throw new Error(`Dotlocker did not materialize ${path4.relative(root, envFile)}`);
11913
+ }
11914
+ await normalizePermissions(targetRoot);
11915
+ console.log(`runtime ${runtime}
11916
+ environment ${path4.relative(root, envFile)}`);
11917
+ }
11918
+ function run(executable, args, cwd) {
11919
+ return new Promise((resolve, reject) => {
11920
+ const child = spawn(executable, args, { cwd, env: process.env, stdio: "inherit" });
11921
+ child.once("error", reject);
11922
+ child.once("exit", (code, signal) => {
11923
+ if (code === 0)
11924
+ resolve();
11925
+ else
11926
+ reject(new Error(`Dotlocker pull failed (${signal ? `signal ${signal}` : `exit ${code ?? "unknown"}`})`));
11927
+ });
11928
+ });
11929
+ }
11930
+ async function exists2(file) {
11931
+ try {
11932
+ await access(file);
11933
+ return true;
11934
+ } catch {
11935
+ return false;
11936
+ }
11937
+ }
11938
+ function normalizedRuntime(value) {
11939
+ const runtime = NODE_ENV_RUNTIME[value.trim()] ?? value.trim();
11940
+ if (!RUNTIME_PATTERN.test(runtime))
11941
+ throw new Error(`invalid runtime '${value}'`);
11942
+ return runtime;
11943
+ }
11944
+ function contained(root, value, label) {
11945
+ const resolved = path4.resolve(root, value);
11946
+ const relative = path4.relative(root, resolved);
11947
+ if (!relative || !relative.startsWith("..") && !path4.isAbsolute(relative))
11948
+ return resolved;
11949
+ throw new Error(`${label} must remain within the repository`);
11950
+ }
11951
+ async function normalizePermissions(directory) {
11952
+ await chmod2(directory, 493);
11953
+ for (const entry of await readdir(directory, { withFileTypes: true })) {
11954
+ const child = path4.join(directory, entry.name);
11955
+ if (entry.isSymbolicLink())
11956
+ throw new Error("Dotlocker output must not contain symbolic links");
11957
+ if (entry.isDirectory())
11958
+ await normalizePermissions(child);
11959
+ else if (entry.isFile())
11960
+ await chmod2(child, 420);
11961
+ else
11962
+ throw new Error("Dotlocker output contains an unsupported file type");
11963
+ }
11964
+ }
11965
+ function parse(args) {
11966
+ const options = { positionals: [] };
11967
+ for (let index = 0;index < args.length; index += 1) {
11968
+ const value = args[index] ?? "";
11969
+ if (!value.startsWith("--")) {
11970
+ options.positionals.push(value);
11971
+ continue;
11972
+ }
11973
+ const key = value.slice(2);
11974
+ const next = args[index + 1];
11975
+ if (next && !next.startsWith("--")) {
11976
+ options[key] = next;
11977
+ index += 1;
11978
+ } else
11979
+ options[key] = true;
11980
+ }
11981
+ return options;
11982
+ }
11983
+ function option(options, key) {
11984
+ const value = options[key];
11985
+ return typeof value === "string" ? value : undefined;
11986
+ }
11987
+ if (process.argv[1] && import.meta.url === pathToFileURL(path4.resolve(process.argv[1])).href) {
11988
+ runRuntimeCommand(process.argv.slice(2)).catch((error) => {
11989
+ console.error(`sandblocks-runtime: ${error instanceof Error ? error.message : String(error)}`);
11990
+ process.exitCode = 1;
11991
+ });
11992
+ }
11993
+
11880
11994
  // src/sandbox.ts
11881
11995
  init_src();
11882
11996
  import { createHash as createHash2, randomUUID } from "crypto";
11883
- import { chmod as chmod2, mkdir as mkdir2, readFile as readFile4, rename, rm as rm2 } from "fs/promises";
11884
- import path4 from "path";
11997
+ import { chmod as chmod3, mkdir as mkdir3, readFile as readFile4, rename, rm as rm3 } from "fs/promises";
11998
+ import path5 from "path";
11885
11999
  var COMMANDS = new Set([
11886
12000
  "up",
11887
12001
  "create",
@@ -11933,7 +12047,7 @@ async function up(args, dependencies) {
11933
12047
  }
11934
12048
  async function create(args, dependencies) {
11935
12049
  const options = dependencies.parse(args);
11936
- const root = path4.resolve(options.positionals[0] ?? process.cwd());
12050
+ const root = path5.resolve(options.positionals[0] ?? process.cwd());
11937
12051
  await loadLocalEnvironment(root);
11938
12052
  const environment = dependencies.option(options, "environment") ?? "preview";
11939
12053
  const file = statePath(root, environment);
@@ -11942,7 +12056,7 @@ async function create(args, dependencies) {
11942
12056
  const loaded = await dependencies.load(root, dependencies.option(options, "config") ?? dependencies.option(options, "manifest"));
11943
12057
  const preview = selectPreview(loaded, environment);
11944
12058
  const projectId = dependencies.option(options, "project") ?? process.env.SANDBLOCKS_PROJECT_ID ?? "default";
11945
- const repository = loaded.manifest.name ?? path4.basename(root).toLowerCase();
12059
+ const repository = loaded.manifest.name ?? path5.basename(root).toLowerCase();
11946
12060
  const sandboxId = randomUUID();
11947
12061
  const workspaceId = `${sandboxId.replaceAll("-", "").slice(0, 8)}-${repository}`.slice(0, 63);
11948
12062
  const { apiUrl, apiKey } = client(options, dependencies.option, projectId);
@@ -12010,7 +12124,7 @@ async function status2(args, dependencies) {
12010
12124
  console.log(JSON.stringify({ state: redactedState(context.state), sandbox: sandbox.sandbox, session: session.session }, null, 2));
12011
12125
  return;
12012
12126
  }
12013
- console.log(`state ${path4.relative(context.root, context.file)}`);
12127
+ console.log(`state ${path5.relative(context.root, context.file)}`);
12014
12128
  console.log(`sandbox ${context.state.sandboxId} (${sandbox.sandbox.status})`);
12015
12129
  console.log(`workspace ${context.state.workspaceId}`);
12016
12130
  console.log(`host ${context.state.hostId}`);
@@ -12023,12 +12137,12 @@ async function deploy(args, dependencies) {
12023
12137
  }
12024
12138
  async function withDeploymentLock(args, dependencies, action) {
12025
12139
  const options = dependencies.parse(args);
12026
- const root = path4.resolve(options.positionals[0] ?? process.cwd());
12140
+ const root = path5.resolve(options.positionals[0] ?? process.cwd());
12027
12141
  const environment = dependencies.option(options, "environment") ?? "preview";
12028
12142
  const lock = `${statePath(root, environment)}.deploy.lock`;
12029
- await mkdir2(path4.dirname(lock), { recursive: true, mode: 448 });
12143
+ await mkdir3(path5.dirname(lock), { recursive: true, mode: 448 });
12030
12144
  try {
12031
- await mkdir2(lock);
12145
+ await mkdir3(lock);
12032
12146
  } catch (error) {
12033
12147
  if (error.code === "EEXIST") {
12034
12148
  throw new Error(`sandbox '${environment}' deployment is already running`);
@@ -12038,7 +12152,7 @@ async function withDeploymentLock(args, dependencies, action) {
12038
12152
  try {
12039
12153
  await action();
12040
12154
  } finally {
12041
- await rm2(lock, { recursive: true, force: true });
12155
+ await rm3(lock, { recursive: true, force: true });
12042
12156
  }
12043
12157
  }
12044
12158
  async function deployUnlocked(args, dependencies) {
@@ -12271,7 +12385,7 @@ async function down(args, dependencies) {
12271
12385
  await request(context.apiUrl, context.apiKey, `/v1/projects/${encodeURIComponent(context.state.projectId)}/sandboxes/${context.state.sandboxId}`, { method: "DELETE" }).catch(() => {
12272
12386
  return;
12273
12387
  });
12274
- await rm2(context.file, { force: true });
12388
+ await rm3(context.file, { force: true });
12275
12389
  console.log(`sandbox ${context.state.sandboxId} destroyed`);
12276
12390
  }
12277
12391
  async function resolveAssignedTargetHost(context, fallback) {
@@ -12406,7 +12520,7 @@ async function dataOperation(context, action, body) {
12406
12520
  }
12407
12521
  async function getContext(args, dependencies) {
12408
12522
  const options = dependencies.parse(args);
12409
- const root = path4.resolve(options.positionals[0] ?? process.cwd());
12523
+ const root = path5.resolve(options.positionals[0] ?? process.cwd());
12410
12524
  await loadLocalEnvironment(root);
12411
12525
  const environment = dependencies.option(options, "environment") ?? "preview";
12412
12526
  const file = statePath(root, environment);
@@ -12422,9 +12536,9 @@ async function getContext(args, dependencies) {
12422
12536
  function selectPreview(loaded, id2) {
12423
12537
  return resolveEnvironment(loaded.manifest, id2);
12424
12538
  }
12425
- function client(options, option, projectId) {
12426
- const apiUrl = (option(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
12427
- const apiKey = option(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY ?? "";
12539
+ function client(options, option2, projectId) {
12540
+ const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
12541
+ const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY ?? "";
12428
12542
  if (!apiUrl || !apiKey)
12429
12543
  throw new Error("SANDBLOCKS_API_URL and SANDBLOCKS_API_KEY are required");
12430
12544
  return { apiUrl, apiKey, projectId };
@@ -12525,7 +12639,7 @@ async function waitLease(context, operationId) {
12525
12639
  throw new Error(`sandbox operation ${operationId} timed out`);
12526
12640
  }
12527
12641
  async function loadLocalEnvironment(root) {
12528
- const file = path4.join(root, ".sandblocks", "config.env");
12642
+ const file = path5.join(root, ".sandblocks", "config.env");
12529
12643
  let contents;
12530
12644
  try {
12531
12645
  contents = await readFile4(file, "utf8");
@@ -12589,15 +12703,15 @@ function resolvedServiceDomain(sandboxId, deploymentId, service, preview) {
12589
12703
  function statePath(root, environment) {
12590
12704
  if (!/^[a-z][a-z0-9-]{0,62}$/.test(environment))
12591
12705
  throw new Error("sandbox environment is invalid");
12592
- return path4.join(root, ".sandblocks", `sandbox-${environment}.json`);
12706
+ return path5.join(root, ".sandblocks", `sandbox-${environment}.json`);
12593
12707
  }
12594
12708
  async function writeState(file, state) {
12595
- await mkdir2(path4.dirname(file), { recursive: true, mode: 448 });
12596
- await chmod2(path4.dirname(file), 448);
12709
+ await mkdir3(path5.dirname(file), { recursive: true, mode: 448 });
12710
+ await chmod3(path5.dirname(file), 448);
12597
12711
  const temporary = `${file}.${process.pid}.tmp`;
12598
12712
  await Bun.write(temporary, `${JSON.stringify(state, null, 2)}
12599
12713
  `, { mode: 384 });
12600
- await chmod2(temporary, 384);
12714
+ await chmod3(temporary, 384);
12601
12715
  await rename(temporary, file);
12602
12716
  }
12603
12717
  function redactedState(state) {
@@ -12613,15 +12727,15 @@ function printOutput(operation) {
12613
12727
  }
12614
12728
 
12615
12729
  // src/sdk.ts
12616
- import { mkdir as mkdir4, readFile as readFile7, rm as rm5, writeFile as writeFile4 } from "fs/promises";
12730
+ import { mkdir as mkdir5, readFile as readFile7, rm as rm6, writeFile as writeFile4 } from "fs/promises";
12617
12731
  import { dirname as dirname2, resolve as resolve7 } from "path";
12618
12732
 
12619
12733
  // ../sdk/dist/index.js
12620
12734
  import { randomUUID as randomUUID2 } from "crypto";
12621
- import { appendFile, cp, mkdir as mkdir3, readFile as readFile5, rm as rm3 } from "fs/promises";
12735
+ import { appendFile, cp, mkdir as mkdir4, readFile as readFile5, rm as rm4 } from "fs/promises";
12622
12736
  import { dirname, resolve } from "path";
12623
- import { spawn } from "child_process";
12624
- import { access, mkdir as mkdir22, writeFile as writeFile2 } from "fs/promises";
12737
+ import { spawn as spawn2 } from "child_process";
12738
+ import { access as access2, mkdir as mkdir22, writeFile as writeFile2 } from "fs/promises";
12625
12739
  import { resolve as resolve2 } from "path";
12626
12740
 
12627
12741
  class CommandAgent {
@@ -12877,7 +12991,7 @@ async function executeProcess(command, options = {}) {
12877
12991
  const [executable, ...args] = command;
12878
12992
  if (!executable)
12879
12993
  throw new Error("command executable is required");
12880
- const child = spawn(executable, args, {
12994
+ const child = spawn2(executable, args, {
12881
12995
  cwd: options.cwd ?? options.hostCwd,
12882
12996
  env: options.env ? { ...process.env, ...options.env } : process.env,
12883
12997
  stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
@@ -13062,7 +13176,7 @@ class SandblocksSandbox {
13062
13176
  "--force",
13063
13177
  this.git.worktree
13064
13178
  ]);
13065
- await rm3(this.git.worktree, { recursive: true, force: true });
13179
+ await rm4(this.git.worktree, { recursive: true, force: true });
13066
13180
  }
13067
13181
  }
13068
13182
  async[Symbol.asyncDispose]() {
@@ -13092,7 +13206,7 @@ async function createSandbox(options) {
13092
13206
  for (const file of options.copyToWorktree ?? []) {
13093
13207
  const source = resolve(hostCwd, file);
13094
13208
  const destination = resolve(git3.runtimeCwd, file);
13095
- await mkdir3(dirname(destination), { recursive: true });
13209
+ await mkdir4(dirname(destination), { recursive: true });
13096
13210
  await withTimeout(cp(source, destination, { recursive: true }), options.timeouts?.copyToWorktreeMs ?? 60000, `copying '${file}'`);
13097
13211
  }
13098
13212
  const id2 = options.id ?? randomUUID2();
@@ -13134,7 +13248,7 @@ async function connectSandbox(options) {
13134
13248
  const runtime = await options.provider.reconnect({ id: options.id, cwd });
13135
13249
  return new SandblocksSandbox(runtime, { provider: options.provider, cwd, retention: options.retention ?? "always" }, { hostCwd: cwd, runtimeCwd: cwd });
13136
13250
  }
13137
- async function run(options) {
13251
+ async function run2(options) {
13138
13252
  const sandbox = await createSandbox(options);
13139
13253
  try {
13140
13254
  return await sandbox.run(options);
@@ -13161,7 +13275,7 @@ async function prepareGit(hostCwd, strategy, timeoutMs = 30000) {
13161
13275
  }
13162
13276
  const branch = strategy.name ?? `sandblocks/${randomUUID2().slice(0, 8)}`;
13163
13277
  const worktree = resolve(hostCwd, ".sandblocks", "worktrees", branch.replace(/[^A-Za-z0-9_.-]/g, "-"));
13164
- await mkdir3(dirname(worktree), { recursive: true });
13278
+ await mkdir4(dirname(worktree), { recursive: true });
13165
13279
  await checked(["git", "-C", hostCwd, "worktree", "add", "-B", branch, worktree, "HEAD"], timeoutMs);
13166
13280
  return {
13167
13281
  hostCwd,
@@ -13244,7 +13358,7 @@ function createLogger(options, cwd) {
13244
13358
  process.stdout.write(event.type === "text" ? event.text : event.type === "raw" ? `${event.line}
13245
13359
  ` : line);
13246
13360
  if (config.type !== "stdout" && config.type !== "silent") {
13247
- await mkdir3(dirname(file), { recursive: true });
13361
+ await mkdir4(dirname(file), { recursive: true });
13248
13362
  await appendFile(file, line);
13249
13363
  }
13250
13364
  try {
@@ -13315,12 +13429,12 @@ SANDBLOCKS_API_KEY=
13315
13429
  };
13316
13430
  const written = [];
13317
13431
  for (const [relative, content] of Object.entries(files)) {
13318
- const path5 = resolve2(root, relative);
13319
- await mkdir22(resolve2(path5, ".."), { recursive: true });
13320
- if (!options.force && await access(path5).then(() => true).catch(() => false))
13432
+ const path6 = resolve2(root, relative);
13433
+ await mkdir22(resolve2(path6, ".."), { recursive: true });
13434
+ if (!options.force && await access2(path6).then(() => true).catch(() => false))
13321
13435
  continue;
13322
- await writeFile2(path5, content, { mode: relative.includes(".env") ? 384 : 420 });
13323
- written.push(path5);
13436
+ await writeFile2(path6, content, { mode: relative.includes(".env") ? 384 : 420 });
13437
+ written.push(path6);
13324
13438
  }
13325
13439
  return written;
13326
13440
  }
@@ -13328,7 +13442,7 @@ SANDBLOCKS_API_KEY=
13328
13442
  // ../sdk/dist/providers/docker.js
13329
13443
  import { realpath as realpath3 } from "fs/promises";
13330
13444
  import { resolve as resolve3 } from "path";
13331
- import { spawn as spawn2 } from "child_process";
13445
+ import { spawn as spawn3 } from "child_process";
13332
13446
  async function executeProcess2(command, options = {}) {
13333
13447
  if (!command.length || command.some((part) => !part || part.includes("\x00"))) {
13334
13448
  throw new Error("command must contain safe non-empty arguments");
@@ -13337,7 +13451,7 @@ async function executeProcess2(command, options = {}) {
13337
13451
  const [executable, ...args] = command;
13338
13452
  if (!executable)
13339
13453
  throw new Error("command executable is required");
13340
- const child = spawn2(executable, args, {
13454
+ const child = spawn3(executable, args, {
13341
13455
  cwd: options.cwd ?? options.hostCwd,
13342
13456
  env: options.env ? { ...process.env, ...options.env } : process.env,
13343
13457
  stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
@@ -13390,12 +13504,12 @@ async function executeProcess2(command, options = {}) {
13390
13504
  options.signal?.removeEventListener("abort", stop);
13391
13505
  }
13392
13506
  }
13393
- function expandHome(path5) {
13394
- if (path5 === "~")
13395
- return process.env.HOME ?? path5;
13396
- if (path5.startsWith("~/"))
13397
- return `${process.env.HOME ?? "~"}/${path5.slice(2)}`;
13398
- return path5;
13507
+ function expandHome(path6) {
13508
+ if (path6 === "~")
13509
+ return process.env.HOME ?? path6;
13510
+ if (path6.startsWith("~/"))
13511
+ return `${process.env.HOME ?? "~"}/${path6.slice(2)}`;
13512
+ return path6;
13399
13513
  }
13400
13514
  var capabilities = {
13401
13515
  bindMounts: true,
@@ -13574,7 +13688,7 @@ class DockerProvider extends OciProvider {
13574
13688
  // ../sdk/dist/providers/podman.js
13575
13689
  import { realpath as realpath4 } from "fs/promises";
13576
13690
  import { resolve as resolve5 } from "path";
13577
- import { spawn as spawn3 } from "child_process";
13691
+ import { spawn as spawn4 } from "child_process";
13578
13692
  async function executeProcess3(command, options = {}) {
13579
13693
  if (!command.length || command.some((part) => !part || part.includes("\x00"))) {
13580
13694
  throw new Error("command must contain safe non-empty arguments");
@@ -13583,7 +13697,7 @@ async function executeProcess3(command, options = {}) {
13583
13697
  const [executable, ...args] = command;
13584
13698
  if (!executable)
13585
13699
  throw new Error("command executable is required");
13586
- const child = spawn3(executable, args, {
13700
+ const child = spawn4(executable, args, {
13587
13701
  cwd: options.cwd ?? options.hostCwd,
13588
13702
  env: options.env ? { ...process.env, ...options.env } : process.env,
13589
13703
  stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
@@ -13636,12 +13750,12 @@ async function executeProcess3(command, options = {}) {
13636
13750
  options.signal?.removeEventListener("abort", stop);
13637
13751
  }
13638
13752
  }
13639
- function expandHome2(path5) {
13640
- if (path5 === "~")
13641
- return process.env.HOME ?? path5;
13642
- if (path5.startsWith("~/"))
13643
- return `${process.env.HOME ?? "~"}/${path5.slice(2)}`;
13644
- return path5;
13753
+ function expandHome2(path6) {
13754
+ if (path6 === "~")
13755
+ return process.env.HOME ?? path6;
13756
+ if (path6.startsWith("~/"))
13757
+ return `${process.env.HOME ?? "~"}/${path6.slice(2)}`;
13758
+ return path6;
13645
13759
  }
13646
13760
  var capabilities2 = {
13647
13761
  bindMounts: true,
@@ -13819,10 +13933,10 @@ class PodmanProvider extends OciProvider2 {
13819
13933
 
13820
13934
  // ../sdk/dist/providers/remote.js
13821
13935
  import { randomUUID as randomUUID3 } from "crypto";
13822
- import { mkdtemp, readFile as readFile6, rm as rm4, stat as stat2, writeFile as writeFile3 } from "fs/promises";
13936
+ import { mkdtemp, readFile as readFile6, rm as rm5, stat as stat2, writeFile as writeFile3 } from "fs/promises";
13823
13937
  import { tmpdir } from "os";
13824
13938
  import { join } from "path";
13825
- import { spawn as spawn4 } from "child_process";
13939
+ import { spawn as spawn5 } from "child_process";
13826
13940
  async function executeProcess4(command, options = {}) {
13827
13941
  if (!command.length || command.some((part) => !part || part.includes("\x00"))) {
13828
13942
  throw new Error("command must contain safe non-empty arguments");
@@ -13831,7 +13945,7 @@ async function executeProcess4(command, options = {}) {
13831
13945
  const [executable, ...args] = command;
13832
13946
  if (!executable)
13833
13947
  throw new Error("command executable is required");
13834
- const child = spawn4(executable, args, {
13948
+ const child = spawn5(executable, args, {
13835
13949
  cwd: options.cwd ?? options.hostCwd,
13836
13950
  env: options.env ? { ...process.env, ...options.env } : process.env,
13837
13951
  stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
@@ -14097,12 +14211,12 @@ class RemoteSandblocksProvider {
14097
14211
  if (result.exitCode !== 0)
14098
14212
  throw new Error(result.stderr || "remote operation failed");
14099
14213
  }
14100
- async request(path5, init = {}) {
14214
+ async request(path6, init = {}) {
14101
14215
  const headers = new Headers(init.headers);
14102
14216
  headers.set("x-sandblocks-api-key", this.options.apiKey);
14103
14217
  if (init.body)
14104
14218
  headers.set("content-type", "application/json");
14105
- const response = await this.fetchImpl(`${this.baseUrl}${path5}`, { ...init, headers });
14219
+ const response = await this.fetchImpl(`${this.baseUrl}${path6}`, { ...init, headers });
14106
14220
  const body = await response.json().catch(() => ({}));
14107
14221
  if (!response.ok)
14108
14222
  throw new Error(body.error ?? `Sandblocks request failed (${response.status})`);
@@ -14126,7 +14240,7 @@ async function sourceBundle(cwd) {
14126
14240
  ]);
14127
14241
  if (listed.exitCode !== 0)
14128
14242
  throw new Error("remote source directory must be a Git worktree");
14129
- const files = listed.stdout.split("\x00").filter((path5) => path5 && path5 !== ".sandblocks" && !path5.startsWith(".sandblocks/") && path5 !== ".git");
14243
+ const files = listed.stdout.split("\x00").filter((path6) => path6 && path6 !== ".sandblocks" && !path6.startsWith(".sandblocks/") && path6 !== ".git");
14130
14244
  if (!files.length || files.length > 50000)
14131
14245
  throw new Error("remote source bundle file count is invalid");
14132
14246
  await writeFile3(list, `${files.join("\x00")}\x00`);
@@ -14137,14 +14251,14 @@ async function sourceBundle(cwd) {
14137
14251
  throw new Error("remote source bundle exceeds 512 MiB");
14138
14252
  return new Uint8Array(await readFile6(file));
14139
14253
  } finally {
14140
- await rm4(temporary, { recursive: true, force: true });
14254
+ await rm5(temporary, { recursive: true, force: true });
14141
14255
  }
14142
14256
  }
14143
14257
 
14144
14258
  // ../sdk/dist/providers/unsafe-host.js
14145
14259
  import { cp as cp2, realpath as realpath5 } from "fs/promises";
14146
14260
  import { resolve as resolve6 } from "path";
14147
- import { spawn as spawn5 } from "child_process";
14261
+ import { spawn as spawn6 } from "child_process";
14148
14262
  async function executeProcess5(command, options = {}) {
14149
14263
  if (!command.length || command.some((part) => !part || part.includes("\x00"))) {
14150
14264
  throw new Error("command must contain safe non-empty arguments");
@@ -14153,7 +14267,7 @@ async function executeProcess5(command, options = {}) {
14153
14267
  const [executable, ...args] = command;
14154
14268
  if (!executable)
14155
14269
  throw new Error("command executable is required");
14156
- const child = spawn5(executable, args, {
14270
+ const child = spawn6(executable, args, {
14157
14271
  cwd: options.cwd ?? options.hostCwd,
14158
14272
  env: options.env ? { ...process.env, ...options.env } : process.env,
14159
14273
  stdio: [options.stdin === undefined ? "ignore" : "pipe", "pipe", "pipe"]
@@ -14266,7 +14380,7 @@ async function runSdkCommand(args, helpers) {
14266
14380
  const provider = providerFrom(options, helpers, cwd);
14267
14381
  const agent = agentFrom(options, helpers);
14268
14382
  const outputTag = helpers.option(options, "output-tag");
14269
- const result = await run({
14383
+ const result = await run2({
14270
14384
  provider,
14271
14385
  cwd,
14272
14386
  image: helpers.option(options, "image"),
@@ -14317,7 +14431,7 @@ async function sdkSandbox(args, helpers, defaultCwd) {
14317
14431
  hooks: hooksFrom(options),
14318
14432
  copyToWorktree: multi(options["copy-to-worktree"])
14319
14433
  });
14320
- await mkdir4(dirname2(stateFile), { recursive: true });
14434
+ await mkdir5(dirname2(stateFile), { recursive: true });
14321
14435
  const strategy = branchFrom(options, helpers);
14322
14436
  const worktree = strategy.type === "worktree" || strategy.type === "merge-to-head" ? sandbox2.cwd : undefined;
14323
14437
  await writeFile4(stateFile, JSON.stringify({
@@ -14392,7 +14506,7 @@ async function sdkSandbox(args, helpers, defaultCwd) {
14392
14506
  const child = Bun.spawn(["git", "-C", state.cwd, "worktree", "remove", "--force", state.worktree]);
14393
14507
  await child.exited;
14394
14508
  }
14395
- await rm5(stateFile, { force: true });
14509
+ await rm6(stateFile, { force: true });
14396
14510
  console.log(`sandbox ${state.id} destroyed`);
14397
14511
  } else
14398
14512
  throw new Error("unsupported sdk sandbox command");
@@ -14504,10 +14618,10 @@ function hooksFrom(options) {
14504
14618
  };
14505
14619
  }
14506
14620
  async function withSdkConfig(options, cwd, file) {
14507
- const path5 = resolve7(cwd, file ?? ".sandblocks/config.json");
14508
- if (!await Bun.file(path5).exists())
14621
+ const path6 = resolve7(cwd, file ?? ".sandblocks/config.json");
14622
+ if (!await Bun.file(path6).exists())
14509
14623
  return options;
14510
- const config = JSON.parse(await readFile7(path5, "utf8"));
14624
+ const config = JSON.parse(await readFile7(path6, "utf8"));
14511
14625
  const mapped = {};
14512
14626
  if (typeof config.provider === "string")
14513
14627
  mapped.provider = config.provider;
@@ -14575,6 +14689,8 @@ var HELP = `Sandblocks CLI
14575
14689
  sandblocks whoami [directory] [--api-url <url>] [--api-key <key>] [--json]
14576
14690
  sandblocks hooks <install|status|uninstall> [directory]
14577
14691
  sandblocks hooks run post-commit [directory]
14692
+ sandblocks runtime prepare [directory] [--runtime <id>]
14693
+ [--config <path>] [--target <path>]
14578
14694
  sandblocks stack <list|get|settings|settings-set> --project <id> [--stack <id>] [--file <json>] [--json]
14579
14695
  sandblocks app <list|get|settings|settings-set> --project <id> [--stack <id>] [--app <id>] [--file <json>] [--json]
14580
14696
  sandblocks secret <list|set|delete> --project <id> --stack <id> --app <id>
@@ -14617,7 +14733,7 @@ Low-level deploy remains available with --workspace, --sandbox, --host,
14617
14733
  Authentication: --api-key or SANDBLOCKS_API_KEY
14618
14734
  API endpoint: --api-url or SANDBLOCKS_API_URL
14619
14735
  `;
14620
- async function run2(argv = process.argv.slice(2)) {
14736
+ async function run3(argv = process.argv.slice(2)) {
14621
14737
  await loadCliEnvironment(process.cwd());
14622
14738
  const [command = "help", ...args] = argv;
14623
14739
  if (command === "configure" || command === "init")
@@ -14634,8 +14750,8 @@ async function run2(argv = process.argv.slice(2)) {
14634
14750
  await runHooksCommand(args, {
14635
14751
  load,
14636
14752
  runSandbox: (hookCommand2, hookArgs) => runStatefulSandboxCommand(hookCommand2, hookArgs, {
14637
- parse,
14638
- option,
14753
+ parse: parse2,
14754
+ option: option2,
14639
14755
  load,
14640
14756
  source: async (root) => {
14641
14757
  const bundle = await createSourceTar(root);
@@ -14643,6 +14759,8 @@ async function run2(argv = process.argv.slice(2)) {
14643
14759
  }
14644
14760
  })
14645
14761
  });
14762
+ else if (command === "runtime")
14763
+ await runRuntimeCommand(args);
14646
14764
  else if (command === "stack" || command === "app")
14647
14765
  await resource(command, args);
14648
14766
  else if (command === "secret")
@@ -14650,7 +14768,7 @@ async function run2(argv = process.argv.slice(2)) {
14650
14768
  else if (command === "sandbox")
14651
14769
  await sandbox(args);
14652
14770
  else if (command === "sdk")
14653
- await runSdkCommand(args, { parse, option });
14771
+ await runSdkCommand(args, { parse: parse2, option: option2 });
14654
14772
  else {
14655
14773
  console.log(HELP);
14656
14774
  if (!["help", "--help", "-h"].includes(command))
@@ -14658,9 +14776,9 @@ async function run2(argv = process.argv.slice(2)) {
14658
14776
  }
14659
14777
  }
14660
14778
  async function configure(args) {
14661
- const options = parse(args);
14662
- const root = path5.resolve(options.positionals[0] ?? process.cwd());
14663
- const manifestPath = path5.join(root, "sandblocks.yml");
14779
+ const options = parse2(args);
14780
+ const root = path6.resolve(options.positionals[0] ?? process.cwd());
14781
+ const manifestPath = path6.join(root, "sandblocks.yml");
14664
14782
  if (await Bun.file(manifestPath).exists() && !options.force) {
14665
14783
  const loaded2 = await load(root, manifestPathOption(options));
14666
14784
  console.log(`Sandblocks already configured
@@ -14668,8 +14786,8 @@ async function configure(args) {
14668
14786
  digest: ${loaded2.digest}`);
14669
14787
  return;
14670
14788
  }
14671
- const packageJson = await json(path5.join(root, "package.json"));
14672
- const name = slug(option(options, "name") ?? path5.basename(root));
14789
+ const packageJson = await json(path6.join(root, "package.json"));
14790
+ const name = slug(option2(options, "name") ?? path6.basename(root));
14673
14791
  const checks = ["build", "typecheck", "test"].filter((script) => packageJson?.scripts?.[script]);
14674
14792
  const manager = packageJson?.packageManager?.split("@")[0] ?? "npm";
14675
14793
  const command = (script) => `[${manager}, ${manager === "npm" ? "run, " : ""}${script}]`;
@@ -14690,8 +14808,8 @@ async function configure(args) {
14690
14808
  digest: ${loaded.digest}`);
14691
14809
  }
14692
14810
  async function validate(args) {
14693
- const options = parse(args);
14694
- const root = path5.resolve(options.positionals[0] ?? process.cwd());
14811
+ const options = parse2(args);
14812
+ const root = path6.resolve(options.positionals[0] ?? process.cwd());
14695
14813
  const loaded = await load(root, manifestPathOption(options));
14696
14814
  if (options.json) {
14697
14815
  console.log(JSON.stringify({ manifest: loaded.entrypoint, digest: loaded.digest, sources: loaded.sources }, null, 2));
@@ -14701,11 +14819,11 @@ async function validate(args) {
14701
14819
  digest ${loaded.digest}`);
14702
14820
  }
14703
14821
  async function register(args) {
14704
- const options = parse(args);
14705
- const root = path5.resolve(options.positionals[0] ?? process.cwd());
14706
- const projectId = option(options, "project");
14707
- const apiUrl = (option(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
14708
- const apiKey = option(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
14822
+ const options = parse2(args);
14823
+ const root = path6.resolve(options.positionals[0] ?? process.cwd());
14824
+ const projectId = option2(options, "project");
14825
+ const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
14826
+ const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
14709
14827
  if (!projectId)
14710
14828
  throw new Error("register requires --project");
14711
14829
  if (!apiUrl)
@@ -14713,7 +14831,7 @@ async function register(args) {
14713
14831
  if (!apiKey)
14714
14832
  throw new Error("register requires --api-key or SANDBLOCKS_API_KEY");
14715
14833
  const loaded = await load(root, manifestPathOption(options));
14716
- const repositoryUrl = option(options, "repository-url") ?? await git3(root, ["config", "--get", "remote.origin.url"]);
14834
+ const repositoryUrl = option2(options, "repository-url") ?? await git3(root, ["config", "--get", "remote.origin.url"]);
14717
14835
  if (!repositoryUrl)
14718
14836
  throw new Error("register requires --repository-url or git remote.origin.url");
14719
14837
  const branch = await git3(root, ["branch", "--show-current"]) || "main";
@@ -14721,7 +14839,7 @@ async function register(args) {
14721
14839
  method: "POST",
14722
14840
  headers: { "content-type": "application/json", "x-sandblocks-api-key": apiKey },
14723
14841
  body: JSON.stringify({
14724
- slug: loaded.manifest.name ?? slug(path5.basename(root)),
14842
+ slug: loaded.manifest.name ?? slug(path6.basename(root)),
14725
14843
  name: loaded.manifest.name,
14726
14844
  url: repositoryUrl,
14727
14845
  defaultBranch: branch,
@@ -14737,24 +14855,24 @@ async function register(args) {
14737
14855
  console.log(response.status === 409 ? "repository already registered" : "repository registered");
14738
14856
  }
14739
14857
  async function doctor(args) {
14740
- const options = parse(args);
14741
- const root = path5.resolve(options.positionals[0] ?? process.cwd());
14858
+ const options = parse2(args);
14859
+ const root = path6.resolve(options.positionals[0] ?? process.cwd());
14742
14860
  const loaded = await load(root, manifestPathOption(options));
14743
14861
  console.log(`manifest ok (${loaded.entrypoint})`);
14744
- const apiUrl = (option(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
14862
+ const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
14745
14863
  if (apiUrl) {
14746
14864
  const response = await fetch(`${apiUrl}/v1/health`);
14747
14865
  if (!response.ok)
14748
14866
  throw new Error(`API health failed (${response.status})`);
14749
14867
  console.log(`api ok (${apiUrl})`);
14750
14868
  }
14751
- const gitDetected = await stat3(path5.join(root, ".git")).then(() => true).catch(() => false);
14869
+ const gitDetected = await stat3(path6.join(root, ".git")).then(() => true).catch(() => false);
14752
14870
  console.log(`git ${gitDetected ? "ok" : "not detected"}`);
14753
14871
  }
14754
14872
  async function whoami(args) {
14755
- const options = parse(args);
14756
- const apiUrl = (option(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
14757
- const apiKey = option(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
14873
+ const options = parse2(args);
14874
+ const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
14875
+ const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
14758
14876
  if (!apiUrl)
14759
14877
  throw new Error("whoami requires --api-url or SANDBLOCKS_API_URL");
14760
14878
  if (!apiKey)
@@ -14777,12 +14895,12 @@ async function whoami(args) {
14777
14895
  }
14778
14896
  async function resource(kind, args) {
14779
14897
  const [action = "list", ...rest] = args;
14780
- const options = parse(rest);
14781
- const projectId = option(options, "project");
14782
- const stack = option(options, "stack");
14783
- const app = option(options, "app");
14784
- const apiUrl = (option(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
14785
- const apiKey = option(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
14898
+ const options = parse2(rest);
14899
+ const projectId = option2(options, "project");
14900
+ const stack = option2(options, "stack");
14901
+ const app = option2(options, "app");
14902
+ const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
14903
+ const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
14786
14904
  if (!projectId)
14787
14905
  throw new Error(`${kind} ${action} requires --project`);
14788
14906
  if (!apiUrl)
@@ -14802,10 +14920,10 @@ async function resource(kind, args) {
14802
14920
  throw new Error(`unsupported ${kind} command`);
14803
14921
  let body;
14804
14922
  if (action === "settings-set") {
14805
- const file = option(options, "file");
14923
+ const file = option2(options, "file");
14806
14924
  if (!file)
14807
14925
  throw new Error(`${kind} settings-set requires --file <json>`);
14808
- const values = JSON.parse(await readFile8(path5.resolve(file), "utf8"));
14926
+ const values = JSON.parse(await readFile8(path6.resolve(file), "utf8"));
14809
14927
  const current = await sandblocksRequest(apiUrl, apiKey, requestPath);
14810
14928
  body = await sandblocksRequest(apiUrl, apiKey, requestPath, {
14811
14929
  method: "PUT",
@@ -14820,12 +14938,12 @@ async function resource(kind, args) {
14820
14938
  }
14821
14939
  async function secret(args) {
14822
14940
  const [action = "list", ...rest] = args;
14823
- const options = parse(rest);
14824
- const projectId = option(options, "project");
14825
- const stack = option(options, "stack");
14826
- const app = option(options, "app");
14827
- const apiUrl = (option(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
14828
- const apiKey = option(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
14941
+ const options = parse2(rest);
14942
+ const projectId = option2(options, "project");
14943
+ const stack = option2(options, "stack");
14944
+ const app = option2(options, "app");
14945
+ const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
14946
+ const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
14829
14947
  if (!projectId || !stack || !app)
14830
14948
  throw new Error("secret commands require --project, --stack, and --app");
14831
14949
  if (!apiUrl || !apiKey)
@@ -14835,9 +14953,9 @@ async function secret(args) {
14835
14953
  if (action === "list")
14836
14954
  body = await sandblocksRequest(apiUrl, apiKey, root);
14837
14955
  else if (action === "set") {
14838
- const environment = option(options, "environment");
14839
- const name = option(options, "name");
14840
- const value = option(options, "value");
14956
+ const environment = option2(options, "environment");
14957
+ const name = option2(options, "name");
14958
+ const value = option2(options, "value");
14841
14959
  if (!environment || !name || value === undefined)
14842
14960
  throw new Error("secret set requires --environment, --name, and --value");
14843
14961
  body = await sandblocksRequest(apiUrl, apiKey, root, {
@@ -14845,7 +14963,7 @@ async function secret(args) {
14845
14963
  body: JSON.stringify({ environment, name, value })
14846
14964
  });
14847
14965
  } else if (action === "delete") {
14848
- const id2 = option(options, "id");
14966
+ const id2 = option2(options, "id");
14849
14967
  if (!id2)
14850
14968
  throw new Error("secret delete requires --id");
14851
14969
  await sandblocksRequest(apiUrl, apiKey, `${root}/${encodeURIComponent(id2)}`, { method: "DELETE" });
@@ -14885,8 +15003,8 @@ async function sandbox(args) {
14885
15003
  const [subcommand, ...rest] = args;
14886
15004
  if (isStatefulSandboxCommand(subcommand) && !(subcommand === "deploy" && rest.includes("--workspace"))) {
14887
15005
  return runStatefulSandboxCommand(subcommand, rest, {
14888
- parse,
14889
- option,
15006
+ parse: parse2,
15007
+ option: option2,
14890
15008
  load,
14891
15009
  source: async (root2) => {
14892
15010
  const bundle2 = await createSourceTar(root2);
@@ -14902,12 +15020,12 @@ async function sandbox(args) {
14902
15020
  return sandboxAgent(rest);
14903
15021
  if (subcommand !== "import")
14904
15022
  throw new Error("unsupported sandbox command");
14905
- const options = parse(rest);
14906
- const root = path5.resolve(options.positionals[0] ?? process.cwd());
14907
- const projectId = option(options, "project");
14908
- const workspaceId = option(options, "workspace");
14909
- const apiUrl = (option(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
14910
- const apiKey = option(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
15023
+ const options = parse2(rest);
15024
+ const root = path6.resolve(options.positionals[0] ?? process.cwd());
15025
+ const projectId = option2(options, "project");
15026
+ const workspaceId = option2(options, "workspace");
15027
+ const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
15028
+ const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
14911
15029
  if (!projectId)
14912
15030
  throw new Error("sandbox import requires --project");
14913
15031
  if (!workspaceId)
@@ -14924,8 +15042,8 @@ async function sandbox(args) {
14924
15042
  "content-length": String(bundle.byteLength),
14925
15043
  "x-sandblocks-api-key": apiKey,
14926
15044
  "x-sandblocks-workspace-id": workspaceId,
14927
- "x-sandblocks-worker-pool": option(options, "pool") ?? "sandbox-development",
14928
- "idempotency-key": option(options, "idempotency-key") ?? `local-import:${workspaceId}`
15045
+ "x-sandblocks-worker-pool": option2(options, "pool") ?? "sandbox-development",
15046
+ "idempotency-key": option2(options, "idempotency-key") ?? `local-import:${workspaceId}`
14929
15047
  },
14930
15048
  body: new Blob([Uint8Array.from(bundle)])
14931
15049
  });
@@ -14945,12 +15063,12 @@ async function sandbox(args) {
14945
15063
  }
14946
15064
  }
14947
15065
  async function sandboxAgent(args) {
14948
- const options = parse(args);
15066
+ const options = parse2(args);
14949
15067
  const action = options.positionals[0] ?? "list";
14950
- const projectId = option(options, "project");
14951
- const sandboxId = option(options, "sandbox");
14952
- const apiUrl = (option(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
14953
- const apiKey = option(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
15068
+ const projectId = option2(options, "project");
15069
+ const sandboxId = option2(options, "sandbox");
15070
+ const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
15071
+ const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
14954
15072
  if (!projectId || !sandboxId)
14955
15073
  throw new Error("sandbox agent requires --project and --sandbox");
14956
15074
  if (!apiUrl || !apiKey)
@@ -14958,25 +15076,25 @@ async function sandboxAgent(args) {
14958
15076
  const collection = `/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}/agent-sessions`;
14959
15077
  let result;
14960
15078
  if (action === "run") {
14961
- const promptFile = option(options, "prompt-file");
14962
- const prompt = option(options, "prompt") ?? (promptFile ? await readFile8(path5.resolve(promptFile), "utf8") : undefined);
15079
+ const promptFile = option2(options, "prompt-file");
15080
+ const prompt = option2(options, "prompt") ?? (promptFile ? await readFile8(path6.resolve(promptFile), "utf8") : undefined);
14963
15081
  if (!prompt)
14964
15082
  throw new Error("sandbox agent run requires --prompt or --prompt-file");
14965
- const repository = option(options, "repository");
14966
- const provider = option(options, "provider") ?? "codex";
14967
- const model = option(options, "model");
15083
+ const repository = option2(options, "repository");
15084
+ const provider = option2(options, "provider") ?? "codex";
15085
+ const model = option2(options, "model");
14968
15086
  if (!repository || !model)
14969
15087
  throw new Error("sandbox agent run requires --repository and --model");
14970
15088
  result = await sandblocksRequest(apiUrl, apiKey, collection, {
14971
15089
  method: "POST",
14972
- headers: { "idempotency-key": option(options, "idempotency-key") ?? randomUUID4() },
15090
+ headers: { "idempotency-key": option2(options, "idempotency-key") ?? randomUUID4() },
14973
15091
  body: JSON.stringify({
14974
15092
  repository,
14975
15093
  provider,
14976
15094
  model,
14977
15095
  prompt,
14978
- credentialEnvironment: option(options, "credential-env"),
14979
- timeoutSeconds: option(options, "timeout-seconds") ? Number(option(options, "timeout-seconds")) : undefined
15096
+ credentialEnvironment: option2(options, "credential-env"),
15097
+ timeoutSeconds: option2(options, "timeout-seconds") ? Number(option2(options, "timeout-seconds")) : undefined
14980
15098
  })
14981
15099
  });
14982
15100
  if (options.wait)
@@ -14984,12 +15102,12 @@ async function sandboxAgent(args) {
14984
15102
  } else if (action === "list") {
14985
15103
  result = await sandblocksRequest(apiUrl, apiKey, collection);
14986
15104
  } else if (action === "get" || action === "cancel" || action === "redeploy") {
14987
- const sessionId = option(options, "session");
15105
+ const sessionId = option2(options, "session");
14988
15106
  if (!sessionId)
14989
15107
  throw new Error(`sandbox agent ${action} requires --session`);
14990
15108
  result = await sandblocksRequest(apiUrl, apiKey, `${collection}/${encodeURIComponent(sessionId)}${action === "cancel" ? "/cancel" : action === "redeploy" ? "/redeploy" : ""}`, action === "cancel" || action === "redeploy" ? {
14991
15109
  method: "POST",
14992
- headers: action === "redeploy" ? { "idempotency-key": option(options, "idempotency-key") ?? randomUUID4() } : undefined
15110
+ headers: action === "redeploy" ? { "idempotency-key": option2(options, "idempotency-key") ?? randomUUID4() } : undefined
14993
15111
  } : undefined);
14994
15112
  } else
14995
15113
  throw new Error("sandbox agent action must be run, list, get, cancel, or redeploy");
@@ -15002,20 +15120,20 @@ async function sandboxAgent(args) {
15002
15120
  }
15003
15121
  }
15004
15122
  async function waitForAgentSession(apiUrl, apiKey, projectId, sandboxId, sessionId) {
15005
- const path6 = `/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}/agent-sessions/${encodeURIComponent(sessionId)}`;
15123
+ const path7 = `/v1/projects/${encodeURIComponent(projectId)}/sandboxes/${encodeURIComponent(sandboxId)}/agent-sessions/${encodeURIComponent(sessionId)}`;
15006
15124
  for (;; ) {
15007
- const result = await sandblocksRequest(apiUrl, apiKey, path6);
15125
+ const result = await sandblocksRequest(apiUrl, apiKey, path7);
15008
15126
  if (!["queued", "running"].includes(String(result.session?.state)))
15009
15127
  return result.session;
15010
15128
  await Bun.sleep(1000);
15011
15129
  }
15012
15130
  }
15013
15131
  async function sandboxPromote(args) {
15014
- const options = parse(args);
15015
- const projectId = option(options, "project");
15016
- const sandboxId = option(options, "sandbox");
15017
- const apiUrl = (option(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
15018
- const apiKey = option(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
15132
+ const options = parse2(args);
15133
+ const projectId = option2(options, "project");
15134
+ const sandboxId = option2(options, "sandbox");
15135
+ const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
15136
+ const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
15019
15137
  if (!projectId)
15020
15138
  throw new Error("sandbox promote requires --project");
15021
15139
  if (!sandboxId)
@@ -15057,7 +15175,7 @@ async function createSourceTar(root) {
15057
15175
  if (name.includes("\x00") || name.includes("\\") || name.startsWith("/") || name.split("/").includes("..")) {
15058
15176
  throw new Error(`local source path is unsafe: ${name}`);
15059
15177
  }
15060
- const file = path5.join(root, name);
15178
+ const file = path6.join(root, name);
15061
15179
  let info;
15062
15180
  try {
15063
15181
  info = await lstat(file);
@@ -15150,25 +15268,25 @@ function tarOctal(header, offset, length, value) {
15150
15268
  header[offset + length - 1] = 0;
15151
15269
  }
15152
15270
  async function sandboxDeploy(args) {
15153
- const options = parse(args);
15154
- const root = path5.resolve(options.positionals[0] ?? process.cwd());
15155
- const projectId = option(options, "project");
15156
- const workspaceId = option(options, "workspace");
15157
- const sandboxId = option(options, "sandbox");
15158
- const hostId = option(options, "host");
15159
- const targetHost = option(options, "target-host");
15160
- const specPath = option(options, "spec");
15161
- const apiUrl = (option(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
15162
- const apiKey = option(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
15271
+ const options = parse2(args);
15272
+ const root = path6.resolve(options.positionals[0] ?? process.cwd());
15273
+ const projectId = option2(options, "project");
15274
+ const workspaceId = option2(options, "workspace");
15275
+ const sandboxId = option2(options, "sandbox");
15276
+ const hostId = option2(options, "host");
15277
+ const targetHost = option2(options, "target-host");
15278
+ const specPath = option2(options, "spec");
15279
+ const apiUrl = (option2(options, "api-url") ?? process.env.SANDBLOCKS_API_URL ?? "").replace(/\/$/, "");
15280
+ const apiKey = option2(options, "api-key") ?? process.env.SANDBLOCKS_API_KEY;
15163
15281
  if (!projectId || !workspaceId || !sandboxId || !hostId || !targetHost || !specPath) {
15164
15282
  throw new Error("sandbox deploy requires --project, --workspace, --sandbox, --host, --target-host, and --spec");
15165
15283
  }
15166
15284
  if (!apiUrl || !apiKey)
15167
15285
  throw new Error("sandbox deploy requires Sandblocks API URL and key");
15168
- const spec = JSON.parse(await readFile8(path5.resolve(root, specPath), "utf8"));
15286
+ const spec = JSON.parse(await readFile8(path6.resolve(root, specPath), "utf8"));
15169
15287
  if (!Array.isArray(spec.services) || !spec.services.length)
15170
15288
  throw new Error("preview spec requires services");
15171
- const deploymentId = option(options, "deployment") ?? randomUUID4();
15289
+ const deploymentId = option2(options, "deployment") ?? randomUUID4();
15172
15290
  const submitted = await sandblocksRequest(apiUrl, apiKey, `/v1/projects/${encodeURIComponent(projectId)}/operations`, {
15173
15291
  method: "POST",
15174
15292
  headers: { "idempotency-key": `preview:${deploymentId}:deploy` },
@@ -15249,7 +15367,7 @@ async function waitForOperation(apiUrl, apiKey, id2) {
15249
15367
  async function load(root, entrypoint) {
15250
15368
  return loadSandblocksManifest({ repositoryRoot: root, ...entrypoint ? { entrypoint } : {} });
15251
15369
  }
15252
- function parse(args) {
15370
+ function parse2(args) {
15253
15371
  const output = { positionals: [] };
15254
15372
  for (let index = 0;index < args.length; index++) {
15255
15373
  const value = args[index];
@@ -15287,11 +15405,11 @@ function parse(args) {
15287
15405
  }
15288
15406
  return output;
15289
15407
  }
15290
- function option(options, key) {
15408
+ function option2(options, key) {
15291
15409
  return typeof options[key] === "string" ? options[key] : undefined;
15292
15410
  }
15293
15411
  function manifestPathOption(options) {
15294
- return option(options, "config") ?? option(options, "manifest");
15412
+ return option2(options, "config") ?? option2(options, "manifest");
15295
15413
  }
15296
15414
  function slug(value) {
15297
15415
  const normalized = value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
@@ -15314,7 +15432,7 @@ async function git3(cwd, args) {
15314
15432
  async function loadCliEnvironment(root) {
15315
15433
  let contents;
15316
15434
  try {
15317
- contents = await readFile8(path5.join(root, ".sandblocks", "config.env"), "utf8");
15435
+ contents = await readFile8(path6.join(root, ".sandblocks", "config.env"), "utf8");
15318
15436
  } catch {
15319
15437
  return;
15320
15438
  }
@@ -15330,15 +15448,15 @@ async function loadCliEnvironment(root) {
15330
15448
  }
15331
15449
  if (import.meta.main) {
15332
15450
  try {
15333
- await run2();
15451
+ await run3();
15334
15452
  } catch (error) {
15335
15453
  console.error(`sandblocks: ${error instanceof Error ? error.message : String(error)}`);
15336
15454
  process.exitCode = 1;
15337
15455
  }
15338
15456
  }
15339
15457
  export {
15340
- run2 as run,
15341
- parse
15458
+ run3 as run,
15459
+ parse2 as parse
15342
15460
  };
15343
15461
 
15344
- //# debugId=64EF599DFAEB049264756E2164756E21
15462
+ //# debugId=96A687238FDBFB8A64756E2164756E21