@computesdk/cli 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -44,6 +44,17 @@ override, pass `--org <slug>` (on the root command or any `sandboxes` /
44
44
  on the request. Precedence: `--org` > `COMPUTE_ORG` > the stored login's org.
45
45
  The platform ignores the header for org API keys.
46
46
 
47
+ For `compute sandboxes exec` and `compute sandboxes spawn`, everything after
48
+ the command's first word goes to the sandbox untouched — flags like `uname
49
+ -a` or `node -e` are not CLI options. Pass CLI options (`--json`,
50
+ `--timeout-ms`, `--cwd`, `-e/--env`, `--stdin`, `--org`) before the command;
51
+ a bare `--` before the command is optional but supported.
52
+
53
+ ```bash
54
+ compute sandboxes exec sb1 uname -a
55
+ compute sandboxes spawn sb1 --cwd /app -e NODE_ENV=production -- npm run dev
56
+ ```
57
+
47
58
  ## Migrating from @computesdk/cli 1.x
48
59
 
49
60
  v2 removes the legacy console authentication and its commands:
package/dist/index.js CHANGED
@@ -835,15 +835,15 @@ function maxPriceFields(maxBid, maxBidPer) {
835
835
  throw new ActionsCliError("invalid_argument", "--max-bid-per requires --max-bid.");
836
836
  }
837
837
  if (maxBid === void 0) return void 0;
838
- const usd = Number(maxBid);
839
- if (!/^\d+(\.\d+)?([eE][+-]?\d+)?$/.test(maxBid) || !Number.isFinite(usd) || usd <= 0) {
838
+ const usd2 = Number(maxBid);
839
+ if (!/^\d+(\.\d+)?([eE][+-]?\d+)?$/.test(maxBid) || !Number.isFinite(usd2) || usd2 <= 0) {
840
840
  throw new ActionsCliError("invalid_argument", `Invalid --max-bid "${maxBid}". Expected a positive dollar amount (e.g. 0.12).`);
841
841
  }
842
842
  const per = maxBidPer ?? "second";
843
843
  if (per !== "second" && per !== "minute" && per !== "hour") {
844
844
  throw new ActionsCliError("invalid_argument", `--max-bid-per must be second, minute, or hour, got "${maxBidPer}".`);
845
845
  }
846
- return { maxPriceUsd: usd, maxPricePer: per };
846
+ return { maxPriceUsd: usd2, maxPricePer: per };
847
847
  }
848
848
  function parseVaultKind(kind) {
849
849
  if (kind === void 0 || kind === "secret" || kind === "variable") return kind ?? "secret";
@@ -1377,8 +1377,8 @@ function parseRate(price, per) {
1377
1377
  if (price === void 0) {
1378
1378
  throw new ActionsCliError("invalid_argument", "A price is required \u2014 pass --price <usd>.");
1379
1379
  }
1380
- const usd = Number(price);
1381
- if (!USD_RE.test(price) || !Number.isFinite(usd) || usd <= 0) {
1380
+ const usd2 = Number(price);
1381
+ if (!USD_RE.test(price) || !Number.isFinite(usd2) || usd2 <= 0) {
1382
1382
  throw new ActionsCliError(
1383
1383
  "invalid_argument",
1384
1384
  `Invalid --price "${price}". Expected a positive dollar amount (e.g. 0.12).`
@@ -1391,7 +1391,7 @@ function parseRate(price, per) {
1391
1391
  `--per must be second, minute, or hour, got "${per}".`
1392
1392
  );
1393
1393
  }
1394
- return { usd, per: unit };
1394
+ return { usd: usd2, per: unit };
1395
1395
  }
1396
1396
  function parseCapacity(value) {
1397
1397
  if (value === void 0) return void 0;
@@ -1493,11 +1493,11 @@ function credentialBody(fields, values) {
1493
1493
  }
1494
1494
  return { fields: values };
1495
1495
  }
1496
- function formatUsd(usd) {
1497
- return `$${usd.toFixed(6).replace(/\.?0+$/, "")}`;
1496
+ function formatUsd(usd2) {
1497
+ return `$${usd2.toFixed(6).replace(/\.?0+$/, "")}`;
1498
1498
  }
1499
- function formatRate(usd, per) {
1500
- return `${formatUsd(usd)}/${per}`;
1499
+ function formatRate(usd2, per) {
1500
+ return `${formatUsd(usd2)}/${per}`;
1501
1501
  }
1502
1502
  function formatListingStatus(ask) {
1503
1503
  let label;
@@ -1843,6 +1843,33 @@ ${hints}`
1843
1843
 
1844
1844
  // src/sandboxes.ts
1845
1845
  import pc3 from "picocolors";
1846
+ function parseMaxPrice(value, per) {
1847
+ if (value === void 0) {
1848
+ if (per !== void 0) throw new Error("--max-price-per requires --max-price.");
1849
+ return void 0;
1850
+ }
1851
+ let usdText = value;
1852
+ let unit = per;
1853
+ const slash = value.indexOf("/");
1854
+ if (slash !== -1) {
1855
+ usdText = value.slice(0, slash);
1856
+ const inline = value.slice(slash + 1);
1857
+ if (per !== void 0 && per !== inline) {
1858
+ throw new Error(`--max-price "${value}" conflicts with --max-price-per "${per}".`);
1859
+ }
1860
+ unit = inline;
1861
+ }
1862
+ const usd2 = Number(usdText);
1863
+ if (!/^\d+(\.\d+)?([eE][+-]?\d+)?$/.test(usdText) || !Number.isFinite(usd2) || usd2 <= 0) {
1864
+ throw new Error(`Invalid --max-price "${value}". Expected a positive dollar amount like 0.12/hour.`);
1865
+ }
1866
+ if (unit !== "second" && unit !== "minute" && unit !== "hour") {
1867
+ throw new Error(
1868
+ "--max-price needs a unit \u2014 write it as <usd>/<unit> (e.g. --max-price 0.12/hour) or pass --max-price-per hour."
1869
+ );
1870
+ }
1871
+ return { usd: usd2, per: unit };
1872
+ }
1846
1873
  function shellQuote(arg) {
1847
1874
  return `'${arg.replace(/'/g, `'\\''`)}'`;
1848
1875
  }
@@ -1854,12 +1881,40 @@ function printSandbox(s) {
1854
1881
  console.log(` image: ${s.image ?? "\u2014"}${s.requestedImage !== null && s.requestedImage !== s.image ? ` ${pc3.dim(`(requested: ${s.requestedImage})`)}` : ""}`);
1855
1882
  }
1856
1883
  if (s.destroyError) console.log(` destroyError: ${pc3.red(s.destroyError)}`);
1884
+ if (s.placement) {
1885
+ const p2 = s.placement;
1886
+ const box = p2.box ? ` / ${p2.box.sizeName ?? "custom"}${p2.box.resources ? ` (${p2.box.resources.cpus ?? "?"} vCPU / ${p2.box.resources.memoryMb ?? "?"} MB)` : ""}` : "";
1887
+ console.log(
1888
+ ` provider: ${p2.box?.provider ?? s.provider ?? "\u2014"}${box} size: ${p2.size ?? "\u2014"} order: ${p2.orderType ?? "\u2014"} rate: $${p2.rate.usd}/${p2.rate.per}`
1889
+ );
1890
+ }
1857
1891
  if (attach !== void 0) {
1858
1892
  console.log(
1859
1893
  attach === null ? " attach: none (market fill or ambient provider \u2014 platform drive only)" : ` attach: ${attach.provider} / ${attach.providerSandboxId}${attach.region ? ` (${attach.region})` : ""}`
1860
1894
  );
1861
1895
  }
1862
1896
  }
1897
+ var usd = (n, suffix = "") => n === void 0 ? "\u2014" : `$${n.toFixed(4).replace(/0+$/, "").replace(/\.$/, ".00")}${suffix}`;
1898
+ function printQuote(q, timeoutMs) {
1899
+ console.log(`quote: ${q.ok ? pc3.green("ok") : pc3.red(q.reason ?? "unfillable")}`);
1900
+ const box = q.placement?.box;
1901
+ console.log(
1902
+ ` size: ${q.placement?.size ?? "\u2014"} provider: ${q.placement?.provider ?? "\u2014"}` + (box ? ` box: ${box.sizeName ?? "custom"} (${box.provider}${box.resources ? `, ${box.resources.cpus ?? "?"} vCPU / ${box.resources.memoryMb ?? "?"} MB` : ""})` : "")
1903
+ );
1904
+ console.log(` order type: ${q.orderType} order: ${q.providerOrder.join(", ")}`);
1905
+ if (q.rateUsd) {
1906
+ console.log(` rate: ${usd(q.rateUsd.perHour, "/hour")} cheapest live: ${usd(q.cheapestLiveUsdPerHour, "/hr")} reference: ${usd(q.referenceUsdPerHour, "/hr")}`);
1907
+ } else if (q.rate) {
1908
+ console.log(` rate: $${q.rate.usd}/${q.rate.per}`);
1909
+ }
1910
+ const timeout = timeoutMs !== void 0 ? ` for ${timeoutMs}ms` : "";
1911
+ console.log(` est. cost${timeout}: ${usd(q.estimatedCostUsd)} required hold: ${usd(q.requiredHoldUsd)}`);
1912
+ console.log(
1913
+ ` cap: ${q.maxPrice ? `$${q.maxPrice.usd}/${q.maxPrice.per}` : usd(q.marketCapUsdPerHour, "/hr")} protection limit: ${usd(q.protectionLimitUsdPerHour, "/hr")} balance: ${usd(q.creditBalanceUsd)}`
1914
+ );
1915
+ if (!q.ok && q.topUpPath) console.log(` ${pc3.dim(`\u2192 ${q.topUpPath}`)}`);
1916
+ if (q.liveAskDepth !== void 0) console.log(` ${pc3.dim(`live asks: ${q.liveAskDepth}`)}`);
1917
+ }
1863
1918
  function printProcess(p2) {
1864
1919
  const exit = p2.status === "exited" ? `exit=${p2.exitCode ?? "?"}${p2.signal ? ` (${p2.signal})` : ""}` : "";
1865
1920
  const stdin = p2.stdin && p2.status === "running" ? " [stdin open]" : "";
@@ -1872,9 +1927,83 @@ function printSnapshot(s) {
1872
1927
  ` provider: ${s.provider}${s.region ? ` (${s.region})` : ""} label: ${s.label ?? "\u2014"} created: ${s.createdAt}${s.expiresAt ? ` expires: ${s.expiresAt}` : ""}`
1873
1928
  );
1874
1929
  }
1930
+ var AUTH_LEAD_OPTS = [
1931
+ { aliases: ["--api-key"], key: "apiKey", flag: "value" },
1932
+ { aliases: ["--base-url"], key: "baseUrl", flag: "value" },
1933
+ { aliases: ["--allow-untrusted-host"], key: "allowUntrustedHost", flag: "bool" },
1934
+ { aliases: ["--org"], key: "org", flag: "value" },
1935
+ { aliases: ["--json"], key: "json", flag: "bool" }
1936
+ ];
1937
+ var EXEC_LEAD_OPTS = [
1938
+ ...AUTH_LEAD_OPTS,
1939
+ { aliases: ["--timeout-ms"], key: "timeoutMs", flag: "value" }
1940
+ ];
1941
+ var SPAWN_LEAD_OPTS = [
1942
+ ...AUTH_LEAD_OPTS,
1943
+ { aliases: ["--cwd"], key: "cwd", flag: "value" },
1944
+ { aliases: ["-e", "--env"], key: "env", flag: "repeat" },
1945
+ { aliases: ["--stdin"], key: "stdin", flag: "bool" }
1946
+ ];
1947
+ function takeLeadingOptions(argv, specs) {
1948
+ var _a;
1949
+ const byAlias = /* @__PURE__ */ new Map();
1950
+ for (const spec of specs) for (const alias of spec.aliases) byAlias.set(alias, spec);
1951
+ const inline = {};
1952
+ let i = 0;
1953
+ for (; i < argv.length; i++) {
1954
+ const token = argv[i];
1955
+ if (token === "--") {
1956
+ i++;
1957
+ break;
1958
+ }
1959
+ if (!token.startsWith("-") || token === "-") break;
1960
+ const eq = token.indexOf("=");
1961
+ const flag = eq === -1 ? token : token.slice(0, eq);
1962
+ let spec = byAlias.get(flag);
1963
+ let attached;
1964
+ if (!spec && !flag.startsWith("--") && flag.length > 2) {
1965
+ const shortSpec = byAlias.get(flag.slice(0, 2));
1966
+ if (shortSpec && shortSpec.flag !== "bool") {
1967
+ spec = shortSpec;
1968
+ attached = flag.slice(2);
1969
+ if (attached.startsWith("=")) attached = attached.slice(1);
1970
+ if (eq !== -1) attached += token.slice(eq);
1971
+ }
1972
+ }
1973
+ if (!spec) {
1974
+ throw new Error(
1975
+ `unknown option '${flag}' \u2014 pass CLI options before the command, or use -- to separate them`
1976
+ );
1977
+ }
1978
+ if (spec.flag === "bool") {
1979
+ if (eq !== -1) throw new Error(`option '${flag}' takes no value`);
1980
+ inline[spec.key] = true;
1981
+ } else {
1982
+ const value = attached ?? (eq !== -1 ? token.slice(eq + 1) : argv[++i]);
1983
+ if (value === void 0) throw new Error(`option '${flag}' requires a value`);
1984
+ if (spec.flag === "repeat") {
1985
+ (inline[_a = spec.key] ?? (inline[_a] = [])).push(value);
1986
+ } else {
1987
+ inline[spec.key] = value;
1988
+ }
1989
+ }
1990
+ }
1991
+ return { command: argv.slice(i), inline };
1992
+ }
1993
+ function mergeLeadOpts(opts, inline) {
1994
+ const merged = { ...opts };
1995
+ for (const [key, value] of Object.entries(inline)) {
1996
+ if (Array.isArray(value)) {
1997
+ merged[key] = [...merged[key] ?? [], ...value];
1998
+ } else {
1999
+ merged[key] = value;
2000
+ }
2001
+ }
2002
+ return merged;
2003
+ }
1875
2004
  function registerSandboxesCommands(program2) {
1876
- const cmd = program2.command("sandboxes").alias("sbx").description("Create and drive sandboxes through the platform").configureOutput({ outputError: usageErrorOutput });
1877
- cmd.command("create").description('Place a sandbox (provider order walks 1,2,3; "market" bids first)').option("--order <providers>", 'provider order, comma-separated (e.g. "market,blaxel,vercel")').option("--label <label>", "label for the sandbox").option("--image <image>", "container image to boot").option("--snapshot-id <id>", "provider snapshot to resume").option("--cpus <n>", "CPU cores").option("--memory-mb <n>", "memory in MB").option("--disk-mb <n>", "ephemeral disk in MB").option("--timeout-ms <ms>", "sandbox timeout in ms").option("--secret <name>", "vault secret name to inject (repeatable)", (v, a) => a.concat(v), []).option("--api-key <key>", "platform API key (or COMPUTE_API_KEY)").option("--base-url <url>", "platform base URL").option("--allow-untrusted-host", "allow non-computesdk.com base URLs").option("--json", "print the raw response").action(async (opts) => {
2005
+ const cmd = program2.command("sandboxes").alias("sbx").description("Create and drive sandboxes through the platform").enablePositionalOptions().configureOutput({ outputError: usageErrorOutput });
2006
+ cmd.command("create").description('Place a sandbox (provider order walks 1,2,3; "market" bids first)').option("--order <providers>", 'provider order, comma-separated (e.g. "market,blaxel,vercel")').option("--size <size>", "platform size: small (1 vCPU/2 GB), medium, large, xlarge (8/16 GB); default medium").option("--label <label>", "label for the sandbox").option("--image <image>", "container image to boot").option("--snapshot-id <id>", "provider snapshot to resume").option("--cpus <n>", "CPU cores (raw resources \u2014 mutually exclusive with --size)").option("--memory-mb <n>", "memory in MB (raw resources)").option("--disk-mb <n>", "ephemeral disk in MB (raw resources)").option("--max-price <usd/unit>", "max price for a market fill (e.g. 0.12/hour); makes the create a limit order").option("--max-price-per <unit>", "unit for a bare --max-price usd (second, minute, or hour)").option("--market", 'place the create on the compute market (order type "market")').option("--order-type <type>", "explicit market order type: market or limit").option("--timeout-ms <ms>", "sandbox timeout in ms").option("--secret <name>", "vault secret name to inject (repeatable)", (v, a) => a.concat(v), []).option("--api-key <key>", "platform API key (or COMPUTE_API_KEY)").option("--base-url <url>", "platform base URL").option("--allow-untrusted-host", "allow non-computesdk.com base URLs").option("--json", "print the raw response").action(async (opts) => {
1878
2007
  try {
1879
2008
  const c = await client(opts);
1880
2009
  const resources = {};
@@ -1883,18 +2012,55 @@ function registerSandboxesCommands(program2) {
1883
2012
  if (opts.diskMb) resources.ephemeralDiskMb = Number(opts.diskMb);
1884
2013
  const body = {};
1885
2014
  if (opts.order) body.providerOrder = opts.order.split(",").map((s) => s.trim());
2015
+ if (opts.size) body.size = opts.size;
1886
2016
  if (opts.label) body.label = opts.label;
1887
2017
  if (opts.image) body.image = opts.image;
1888
2018
  if (opts.snapshotId) body.snapshotId = opts.snapshotId;
1889
2019
  if (opts.timeoutMs) body.timeoutMs = Number(opts.timeoutMs);
1890
2020
  if (opts.secret && opts.secret.length > 0) body.secrets = opts.secret;
1891
2021
  if (Object.keys(resources).length > 0) body.resources = resources;
2022
+ const maxPrice = parseMaxPrice(opts.maxPrice, opts.maxPricePer);
2023
+ if (maxPrice) body.maxPrice = { usd: maxPrice.usd, per: maxPrice.per };
2024
+ if (opts.orderType && opts.market) {
2025
+ throw new Error("--market and --order-type are mutually exclusive.");
2026
+ }
2027
+ if (opts.orderType) body.orderType = opts.orderType;
2028
+ if (opts.market) body.orderType = "market";
1892
2029
  const res = await c.post("/api/v1/sandboxes", body);
1893
2030
  output(opts, res.sandbox, printSandbox);
1894
2031
  } catch (e) {
1895
2032
  fail(e, opts);
1896
2033
  }
1897
2034
  });
2035
+ cmd.command("quote").description("Quote a sandbox create: placement, rate, caps and credits \u2014 creates nothing").option("--size <size>", "platform size (or raw resource flags; default medium)").option("--cpus <n>", "CPU cores").option("--memory-mb <n>", "memory in MB").option("--disk-mb <n>", "ephemeral disk in MB").option("--region <region>", "pin the placement region").option("--timeout-ms <ms>", "sandbox timeout in ms").option("--order-type <type>", "market order type: market or limit").option("--market", "quote a market order (fills at the live price, protection-bounded)").option("--max-price <usd/unit>", "max price for a market fill (e.g. 0.12/hour)").option("--max-price-per <unit>", "unit for a bare --max-price usd (second, minute, or hour)").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (opts) => {
2036
+ try {
2037
+ const c = await client(opts);
2038
+ const params = {
2039
+ size: opts.size,
2040
+ cpus: opts.cpus,
2041
+ memoryMb: opts.memoryMb,
2042
+ ephemeralDiskMb: opts.diskMb,
2043
+ region: opts.region,
2044
+ timeoutMs: opts.timeoutMs,
2045
+ orderType: opts.market ? "market" : opts.orderType
2046
+ };
2047
+ if (opts.orderType && opts.market) {
2048
+ throw new Error("--market and --order-type are mutually exclusive.");
2049
+ }
2050
+ const maxPrice = parseMaxPrice(opts.maxPrice, opts.maxPricePer);
2051
+ if (maxPrice) {
2052
+ params.maxPriceUsd = String(maxPrice.usd);
2053
+ params.maxPricePer = maxPrice.per;
2054
+ }
2055
+ const res = await c.get(
2056
+ "/api/v1/sandboxes/quote",
2057
+ params
2058
+ );
2059
+ output(opts, res.quote, (quote) => printQuote(quote, opts.timeoutMs));
2060
+ } catch (e) {
2061
+ fail(e, opts);
2062
+ }
2063
+ });
1898
2064
  cmd.command("list").description("List sandboxes").option("--status <status>", "creating | running | destroyed").option("--limit <n>", "page size").option("--cursor <cursor>", "page cursor from a previous response").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (opts) => {
1899
2065
  try {
1900
2066
  const c = await client(opts);
@@ -1928,8 +2094,13 @@ function registerSandboxesCommands(program2) {
1928
2094
  fail(e, opts);
1929
2095
  }
1930
2096
  });
1931
- cmd.command("exec <sandboxId> <command...>").description("Run a one-shot command and print its result").option("--timeout-ms <ms>", "command timeout in ms").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, command, opts) => {
2097
+ cmd.command("exec <sandboxId> <command...>").description("Run a one-shot command and print its result (options before the command; -- also works)").option("--timeout-ms <ms>", "command timeout in ms").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").passThroughOptions().action(async (sandboxId, rawCommand, rawOpts) => {
2098
+ let opts = rawOpts;
1932
2099
  try {
2100
+ const lead = takeLeadingOptions(rawCommand, EXEC_LEAD_OPTS);
2101
+ opts = mergeLeadOpts(rawOpts, lead.inline);
2102
+ const command = lead.command;
2103
+ if (command.length === 0) throw new Error("missing command to run");
1933
2104
  const c = await client(opts);
1934
2105
  const res = await c.post(
1935
2106
  `/api/v1/sandboxes/${sandboxId}/commands`,
@@ -1948,8 +2119,13 @@ function registerSandboxesCommands(program2) {
1948
2119
  fail(e, opts);
1949
2120
  }
1950
2121
  });
1951
- cmd.command("spawn <sandboxId> <command...>").description("Start a detached process that outlives this request").option("--cwd <dir>", "working directory").option("-e, --env <key=value>", "environment variable (repeatable)", (v, a) => a.concat(v), []).option("--stdin", "keep the process's stdin pipe open for `stdin`/`close-stdin`").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").action(async (sandboxId, command, opts) => {
2122
+ cmd.command("spawn <sandboxId> <command...>").description("Start a detached process that outlives this request (options before the command; -- also works)").option("--cwd <dir>", "working directory").option("-e, --env <key=value>", "environment variable (repeatable)", (v, a) => a.concat(v), []).option("--stdin", "keep the process's stdin pipe open for `stdin`/`close-stdin`").option("--api-key <key>").option("--base-url <url>").option("--allow-untrusted-host").option("--json", "print the raw response").passThroughOptions().action(async (sandboxId, rawCommand, rawOpts) => {
2123
+ let opts = rawOpts;
1952
2124
  try {
2125
+ const lead = takeLeadingOptions(rawCommand, SPAWN_LEAD_OPTS);
2126
+ opts = mergeLeadOpts(rawOpts, lead.inline);
2127
+ const command = lead.command;
2128
+ if (command.length === 0) throw new Error("missing command to spawn");
1953
2129
  const c = await client(opts);
1954
2130
  const res = await c.post(
1955
2131
  `/api/v1/sandboxes/${sandboxId}/processes`,