@easybits.cloud/cli 0.13.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +14 -0
  2. package/dist/index.js +264 -145
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -50,6 +50,8 @@ easybits sandboxes destroy sb_abc123 --yes
50
50
 
51
51
  # Hosting: permanent machines (alias: deploy)
52
52
  easybits machines ls
53
+ easybits machines launch --repo https://github.com/you/shop.git --tier micro --domain shop.example.com
54
+ easybits machines launch --machine shop --archive ./build.tgz --prebuilt -m v2 # redeploy
53
55
  easybits deploy sb_abc123 -m "v1.2" # = machines deploy
54
56
  easybits machines releases sb_abc123 --limit 5
55
57
  easybits machines logs sb_abc123 --lines 100 --grep ERROR
@@ -164,6 +166,18 @@ Host *.ghosty
164
166
 
165
167
  Full reference: https://www.easybits.cloud/docs#cli · https://www.easybits.cloud/en/docs/cli.md
166
168
 
169
+ ## Changes in 0.14.1
170
+
171
+ - `machines launch --machine` takes a name too, like every other command.
172
+
173
+ ## Changes in 0.14.0
174
+
175
+ - `machines launch` (aliases `create`, `new`): create a permanent machine and put an app on it in
176
+ one step — the CLI side of `launch_app`. Source is `--repo`, `--archive <url|file>` (a local file
177
+ is uploaded first) or `--machine` (redeploy onto an existing one). `--tier`, `--prebuilt`,
178
+ `--start`, `--port`, `--env K=V`, `--secret NAME`, `--data <path>`, `--domain`.
179
+ Without a plan it prints the checkout link instead of failing.
180
+
167
181
  ## Changes in 0.13.0
168
182
 
169
183
  - Catalog photos: `db photos put <db> --table T --key-column sku --dir photos/ [--column C]
package/dist/index.js CHANGED
@@ -1377,6 +1377,110 @@ var sandboxes = {
1377
1377
  }
1378
1378
  };
1379
1379
 
1380
+ // src/commands/machines.ts
1381
+ import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
1382
+ import { basename as basename2 } from "path";
1383
+
1384
+ // src/refs.ts
1385
+ var ID_FORMAT = {
1386
+ agent: /^[0-9a-f]{24}$/i,
1387
+ db: /^[0-9a-f]{24}$/i,
1388
+ sandbox: /^sb_[0-9a-z-]+$/i
1389
+ };
1390
+ var looksLikeId = (kind, ref) => ID_FORMAT[kind].test(ref);
1391
+ var REF_ARGS = {
1392
+ "agents get": { index: 0, kind: "agent" },
1393
+ "agents message": { index: 0, kind: "agent" },
1394
+ "agents destroy": { index: 0, kind: "agent" },
1395
+ "agents set": { index: 0, kind: "agent" },
1396
+ "agents files": { index: 1, kind: "agent" },
1397
+ "agents skills": { index: 1, kind: "agent" },
1398
+ "agents mcp": { index: 1, kind: "agent" },
1399
+ "agents restart": { index: 0, kind: "agent" },
1400
+ "agents try": { index: 0, kind: "agent" },
1401
+ "agents logs": { index: 0, kind: "agent" },
1402
+ "agents doctor": { index: 0, kind: "agent" },
1403
+ "agents export": { index: 0, kind: "agent" },
1404
+ "sandboxes get": { index: 0, kind: "sandbox" },
1405
+ "sandboxes exec": { index: 0, kind: "sandbox" },
1406
+ "sandboxes logs": { index: 0, kind: "sandbox" },
1407
+ "sandboxes files": { index: 1, kind: "sandbox" },
1408
+ "sandboxes suspend": { index: 0, kind: "sandbox" },
1409
+ "sandboxes resume": { index: 0, kind: "sandbox" },
1410
+ "sandboxes destroy": { index: 0, kind: "sandbox" },
1411
+ "sandboxes snapshot": { index: 0, kind: "sandbox" },
1412
+ "machines deploy": { index: 0, kind: "sandbox" },
1413
+ "machines releases": { index: 0, kind: "sandbox" },
1414
+ "machines logs": { index: 0, kind: "sandbox" },
1415
+ "machines rollback": { index: 0, kind: "sandbox" },
1416
+ "machines secrets": { index: 1, kind: "sandbox" },
1417
+ "domains ls": { index: 0, kind: "sandbox" },
1418
+ "domains add": { index: 0, kind: "sandbox" },
1419
+ "domains verify": { index: 0, kind: "sandbox" },
1420
+ "domains rm": { index: 0, kind: "sandbox" },
1421
+ "db rm": { index: 0, kind: "db" },
1422
+ "db tables": { index: 0, kind: "db" },
1423
+ "db query": { index: 0, kind: "db" },
1424
+ "db photos": { index: 1, kind: "db" }
1425
+ };
1426
+ function matchRef(kind, ref, items) {
1427
+ if (looksLikeId(kind, ref)) return { ok: true, id: ref };
1428
+ const list2 = items ?? [];
1429
+ if (list2.some((x) => x.id === ref)) return { ok: true, id: ref };
1430
+ const want = ref.trim().toLowerCase();
1431
+ const matches = list2.filter((x) => (x.name ?? "").trim().toLowerCase() === want);
1432
+ if (matches.length === 1) return { ok: true, id: matches[0].id };
1433
+ if (matches.length === 0) return { ok: false, reason: "not_found" };
1434
+ return { ok: false, reason: "ambiguous", matches };
1435
+ }
1436
+
1437
+ // src/resolve.ts
1438
+ var LS_HINT = {
1439
+ agent: "easybits agents ls",
1440
+ sandbox: "easybits sandboxes ls",
1441
+ db: "easybits db ls"
1442
+ };
1443
+ var NOUN = () => ({ agent: t("agent", "agente"), sandbox: t("sandbox or machine", "sandbox o m\xE1quina"), db: t("database", "base de datos") });
1444
+ var NOUNS = () => ({ agent: t("agents", "agentes"), sandbox: t("sandboxes", "sandboxes"), db: t("databases", "bases de datos") });
1445
+ var cache = /* @__PURE__ */ new Map();
1446
+ function listRefs(ctx, kind) {
1447
+ let p = cache.get(kind);
1448
+ if (!p) {
1449
+ p = (async () => {
1450
+ const eb = await getClient(ctx);
1451
+ if (kind === "agent") return (await eb.listAgents()).map((a) => ({ id: a.agentId, name: a.name, status: a.status }));
1452
+ if (kind === "db") return (await eb.listDatabases()).items.map((d) => ({ id: d.id, name: d.name }));
1453
+ return (await eb.sandboxes.list()).map((s) => ({ id: s.sandboxId, name: s.name, status: s.status }));
1454
+ })();
1455
+ p.catch(() => cache.delete(kind));
1456
+ cache.set(kind, p);
1457
+ }
1458
+ return p;
1459
+ }
1460
+ async function resolveRef(ctx, kind, ref) {
1461
+ if (looksLikeId(kind, ref)) return ref;
1462
+ const r = matchRef(kind, ref, await listRefs(ctx, kind));
1463
+ if (r.ok) return r.id;
1464
+ if (r.reason === "not_found") {
1465
+ throw new CliError(t(`No ${NOUN()[kind]} with id or name "${ref}".`, `No hay ${NOUN()[kind]} con id o nombre "${ref}".`), EXIT.API, `${t("List them with", "Enl\xEDstalos con")}: ${LS_HINT[kind]}`, "not_found", 404);
1466
+ }
1467
+ const shown = r.matches.slice(0, 10).map((m) => ` ${m.id}${m.status ? ` (${m.status})` : ""}`);
1468
+ if (r.matches.length > 10) shown.push(t(` \u2026and ${r.matches.length - 10} more`, ` \u2026y ${r.matches.length - 10} m\xE1s`));
1469
+ throw new CliError(
1470
+ t(`"${ref}" matches ${r.matches.length} ${NOUNS()[kind]}. Use the id:`, `"${ref}" coincide con ${r.matches.length} ${NOUNS()[kind]}. Usa el id:`),
1471
+ EXIT.USAGE,
1472
+ `${shown.join("\n")}
1473
+ ${t("List them with", "Enl\xEDstalos con")}: ${LS_HINT[kind]}`,
1474
+ "ambiguous"
1475
+ );
1476
+ }
1477
+ async function resolveRefs(ctx, key) {
1478
+ const spec = REF_ARGS[key];
1479
+ const ref = spec ? ctx.args[spec.index] : void 0;
1480
+ if (!spec || !ref) return;
1481
+ ctx.args[spec.index] = await resolveRef(ctx, spec.kind, ref);
1482
+ }
1483
+
1380
1484
  // src/commands/machines.ts
1381
1485
  var SECRETS_USAGE = "easybits machines secrets <ls|set|unset> <machine> [--dotenv <path> | NAME]";
1382
1486
  var machines = {
@@ -1405,6 +1509,104 @@ var machines = {
1405
1509
  );
1406
1510
  }
1407
1511
  },
1512
+ launch: {
1513
+ aliases: ["create", "new"],
1514
+ summary: "Create a machine and put an app on it in one step (or redeploy onto an existing one)",
1515
+ usage: "easybits machines launch (--repo <url> | --archive <url|file> | --machine <machine>) [--tier micro] [--name <name>] [--prebuilt] [--start <cmd>] [--port 3000] [--env K=V]... [--secret NAME]... [--data <path>]... [--domain <host>]",
1516
+ options: {
1517
+ repo: { type: "string", value: "url", description: "Git repo to clone (clean URL; a token goes in --repo-token)" },
1518
+ branch: { type: "string", value: "name", description: "Branch to clone" },
1519
+ "repo-token": { type: "string", value: "$secret:NAME", description: "Token for a private repo; best as $secret:NAME from your vault" },
1520
+ archive: { type: "string", value: "url|file", description: "A .tar.gz/.zip of the app: URL, or a local file that gets uploaded first" },
1521
+ machine: { type: "string", value: "machine", description: "Deploy onto this existing machine instead of creating one" },
1522
+ tier: { type: "string", value: "tier", description: "Size for a new machine: nano, micro (default), estandar, focus, performance\u2026" },
1523
+ name: { type: "string", value: "name", description: "Human label" },
1524
+ "app-dir": { type: "string", value: "dir", description: "Where the app lives in the machine (default /app)" },
1525
+ build: { type: "string", value: "cmd", description: "Build command (default: npm ci && npm run build)" },
1526
+ start: { type: "string", value: "cmd", description: "Start command (default: npm start)" },
1527
+ port: { type: "string", value: "port", description: "Port the app listens on (default 3000)" },
1528
+ prebuilt: { type: "boolean", description: "The code is already built: skip the build, just start it" },
1529
+ env: { type: "string", multiple: true, value: "K=V", description: "Environment variable, non-secret (repeatable)" },
1530
+ secret: { type: "string", multiple: true, value: "NAME", description: "Vault secret the app needs, by name (repeatable)" },
1531
+ data: { type: "string", multiple: true, value: "path", description: "Path the nightly backup copies (repeatable). Without it nothing is backed up" },
1532
+ domain: { type: "string", value: "host", description: "Domain to attach; the output says which DNS record to create" },
1533
+ message: { type: "string", short: "m", value: "text", description: "Release note" }
1534
+ },
1535
+ examples: [
1536
+ "easybits machines launch --repo https://github.com/you/shop.git --tier micro --domain shop.example.com",
1537
+ "easybits machines launch --archive ./build.tgz --prebuilt --tier nano --name shop --start 'npm start'",
1538
+ "easybits machines launch --machine shop --archive ./build.tgz --prebuilt -m v2 # redeploy"
1539
+ ],
1540
+ async run(ctx) {
1541
+ const repo = str(ctx, "repo");
1542
+ let archiveUrl = str(ctx, "archive");
1543
+ const machineRef = str(ctx, "machine");
1544
+ if (!repo && !archiveUrl && !machineRef) {
1545
+ throw usageError(t("Give a source: --repo, --archive or --machine.", "Pasa una fuente: --repo, --archive o --machine."), this.usage);
1546
+ }
1547
+ if (repo && archiveUrl) throw usageError(t("--repo and --archive are exclusive.", "--repo y --archive se excluyen."), this.usage);
1548
+ if (machineRef && str(ctx, "tier")) {
1549
+ throw usageError(t("--tier is only for a new machine (resize = redeploy_machine).", "--tier s\xF3lo aplica a una m\xE1quina nueva (cambiar tama\xF1o = redeploy_machine)."), this.usage);
1550
+ }
1551
+ const eb = await getClient(ctx);
1552
+ const sandboxId = machineRef ? await resolveRef(ctx, "sandbox", machineRef) : void 0;
1553
+ if (archiveUrl && !/^https?:\/\//.test(archiveUrl)) {
1554
+ if (!existsSync6(archiveUrl)) throw usageError(t(`File not found: ${archiveUrl}`, `No existe el archivo: ${archiveUrl}`), this.usage);
1555
+ const size = statSync2(archiveUrl).size;
1556
+ const contentType = archiveUrl.endsWith(".zip") ? "application/zip" : "application/gzip";
1557
+ const up = await eb.uploadFile({ fileName: basename2(archiveUrl), contentType, size, access: "public" });
1558
+ const res = await fetch(up.putUrl, { method: "PUT", body: readFileSync6(archiveUrl), headers: { "Content-Type": contentType } });
1559
+ if (!res.ok) throw new CliError(t(`Upload failed: storage answered ${res.status}`, `Fall\xF3 la subida: el almacenamiento contest\xF3 ${res.status}`), 1, void 0, "upload_failed", res.status);
1560
+ archiveUrl = up.file.url;
1561
+ if (!archiveUrl) throw new CliError(t("The upload returned no public URL.", "La subida no devolvi\xF3 una URL p\xFAblica."), 1, void 0, "upload_failed");
1562
+ }
1563
+ const env = pairs(list(ctx, "env"), this.usage);
1564
+ const secretNames = list(ctx, "secret");
1565
+ const dataPaths = list(ctx, "data");
1566
+ const r = await eb.machines.launch({
1567
+ repo,
1568
+ branch: str(ctx, "branch"),
1569
+ repoToken: str(ctx, "repo-token"),
1570
+ archiveUrl,
1571
+ sandboxId,
1572
+ tier: str(ctx, "tier"),
1573
+ name: str(ctx, "name"),
1574
+ appDir: str(ctx, "app-dir"),
1575
+ buildCommand: str(ctx, "build"),
1576
+ startCommand: str(ctx, "start"),
1577
+ port: int(ctx, "port", this.usage),
1578
+ prebuilt: bool(ctx, "prebuilt") || void 0,
1579
+ env: Object.keys(env).length ? env : void 0,
1580
+ secretNames: secretNames.length ? secretNames : void 0,
1581
+ dataPaths: dataPaths.length ? dataPaths : void 0,
1582
+ domain: str(ctx, "domain"),
1583
+ message: str(ctx, "message")
1584
+ });
1585
+ if (r.checkoutUrl) {
1586
+ emit(ctx, r, () => {
1587
+ console.log(t("Your account has no plan: pay the machine here and it is created by itself:", "Tu cuenta no tiene plan: paga la m\xE1quina aqu\xED y se crea sola:"));
1588
+ console.log(` ${r.checkoutUrl}`);
1589
+ console.log(t("Then deploy onto it: easybits machines launch --machine <id> \u2026", "Luego despliega encima: easybits machines launch --machine <id> \u2026"));
1590
+ });
1591
+ return;
1592
+ }
1593
+ if (r.exitCode !== 0) {
1594
+ throw new CliError(
1595
+ t(`Launch failed (exit ${r.exitCode}) on ${r.sandboxId}`, `Fall\xF3 el lanzamiento (exit ${r.exitCode}) en ${r.sandboxId}`),
1596
+ 1,
1597
+ r.buildOutput?.slice(-2e3),
1598
+ "launch_failed"
1599
+ );
1600
+ }
1601
+ emit(ctx, r, () => {
1602
+ console.log(`${r.sandboxId} v${r.version} ${r.url}`);
1603
+ if (r.domain) {
1604
+ console.log(`${t("Domain", "Dominio")}: ${r.domain.domain} \u2192 ${r.domain.url}`);
1605
+ console.log(`DNS: ${JSON.stringify(r.domain.dns)}`);
1606
+ }
1607
+ });
1608
+ }
1609
+ },
1408
1610
  deploy: {
1409
1611
  summary: "Publish the machine's current app code as a new release",
1410
1612
  usage: "easybits machines deploy <machine> [--message <text>]",
@@ -1576,11 +1778,11 @@ var domains = {
1576
1778
  };
1577
1779
 
1578
1780
  // src/commands/db.ts
1579
- import { existsSync as existsSync6, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
1781
+ import { existsSync as existsSync7, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
1580
1782
 
1581
1783
  // src/photos.ts
1582
1784
  import { createHash as createHash2 } from "crypto";
1583
- import { readFileSync as readFileSync6, readdirSync, statSync as statSync2 } from "fs";
1785
+ import { readFileSync as readFileSync7, readdirSync, statSync as statSync3 } from "fs";
1584
1786
  import { extname as extname2, join as join3 } from "path";
1585
1787
  var PHOTO_MAX_BYTES = 10 * 1024 * 1024;
1586
1788
  var WHATSAPP_MAX_BYTES = 5 * 1024 * 1024;
@@ -1600,7 +1802,7 @@ function photoFiles(dir) {
1600
1802
  for (const name of readdirSync(dir).sort()) {
1601
1803
  if (name.startsWith(".")) continue;
1602
1804
  const path = join3(dir, name);
1603
- const st = statSync2(path);
1805
+ const st = statSync3(path);
1604
1806
  if (!st.isFile()) continue;
1605
1807
  if (st.size === 0) {
1606
1808
  rejected.push({ file: name, reason: "empty" });
@@ -1610,7 +1812,7 @@ function photoFiles(dir) {
1610
1812
  rejected.push({ file: name, reason: "too_large" });
1611
1813
  continue;
1612
1814
  }
1613
- const bytes = readFileSync6(path);
1815
+ const bytes = readFileSync7(path);
1614
1816
  const type = imageTypeOf(bytes);
1615
1817
  if (!type) {
1616
1818
  rejected.push({ file: name, reason: "not_an_image" });
@@ -1670,106 +1872,6 @@ function parseLinkStats(columns, row) {
1670
1872
  return columns.map((column, i) => ({ column, permanent: n(i * 4), expiring: n(i * 4 + 1), external: n(i * 4 + 2), empty: n(i * 4 + 3) })).filter((s) => s.permanent + s.expiring + s.external > 0);
1671
1873
  }
1672
1874
 
1673
- // src/refs.ts
1674
- var ID_FORMAT = {
1675
- agent: /^[0-9a-f]{24}$/i,
1676
- db: /^[0-9a-f]{24}$/i,
1677
- sandbox: /^sb_[0-9a-z-]+$/i
1678
- };
1679
- var looksLikeId = (kind, ref) => ID_FORMAT[kind].test(ref);
1680
- var REF_ARGS = {
1681
- "agents get": { index: 0, kind: "agent" },
1682
- "agents message": { index: 0, kind: "agent" },
1683
- "agents destroy": { index: 0, kind: "agent" },
1684
- "agents set": { index: 0, kind: "agent" },
1685
- "agents files": { index: 1, kind: "agent" },
1686
- "agents skills": { index: 1, kind: "agent" },
1687
- "agents mcp": { index: 1, kind: "agent" },
1688
- "agents restart": { index: 0, kind: "agent" },
1689
- "agents try": { index: 0, kind: "agent" },
1690
- "agents logs": { index: 0, kind: "agent" },
1691
- "agents doctor": { index: 0, kind: "agent" },
1692
- "agents export": { index: 0, kind: "agent" },
1693
- "sandboxes get": { index: 0, kind: "sandbox" },
1694
- "sandboxes exec": { index: 0, kind: "sandbox" },
1695
- "sandboxes logs": { index: 0, kind: "sandbox" },
1696
- "sandboxes files": { index: 1, kind: "sandbox" },
1697
- "sandboxes suspend": { index: 0, kind: "sandbox" },
1698
- "sandboxes resume": { index: 0, kind: "sandbox" },
1699
- "sandboxes destroy": { index: 0, kind: "sandbox" },
1700
- "sandboxes snapshot": { index: 0, kind: "sandbox" },
1701
- "machines deploy": { index: 0, kind: "sandbox" },
1702
- "machines releases": { index: 0, kind: "sandbox" },
1703
- "machines logs": { index: 0, kind: "sandbox" },
1704
- "machines rollback": { index: 0, kind: "sandbox" },
1705
- "machines secrets": { index: 1, kind: "sandbox" },
1706
- "domains ls": { index: 0, kind: "sandbox" },
1707
- "domains add": { index: 0, kind: "sandbox" },
1708
- "domains verify": { index: 0, kind: "sandbox" },
1709
- "domains rm": { index: 0, kind: "sandbox" },
1710
- "db rm": { index: 0, kind: "db" },
1711
- "db tables": { index: 0, kind: "db" },
1712
- "db query": { index: 0, kind: "db" },
1713
- "db photos": { index: 1, kind: "db" }
1714
- };
1715
- function matchRef(kind, ref, items) {
1716
- if (looksLikeId(kind, ref)) return { ok: true, id: ref };
1717
- const list2 = items ?? [];
1718
- if (list2.some((x) => x.id === ref)) return { ok: true, id: ref };
1719
- const want = ref.trim().toLowerCase();
1720
- const matches = list2.filter((x) => (x.name ?? "").trim().toLowerCase() === want);
1721
- if (matches.length === 1) return { ok: true, id: matches[0].id };
1722
- if (matches.length === 0) return { ok: false, reason: "not_found" };
1723
- return { ok: false, reason: "ambiguous", matches };
1724
- }
1725
-
1726
- // src/resolve.ts
1727
- var LS_HINT = {
1728
- agent: "easybits agents ls",
1729
- sandbox: "easybits sandboxes ls",
1730
- db: "easybits db ls"
1731
- };
1732
- var NOUN = () => ({ agent: t("agent", "agente"), sandbox: t("sandbox or machine", "sandbox o m\xE1quina"), db: t("database", "base de datos") });
1733
- var NOUNS = () => ({ agent: t("agents", "agentes"), sandbox: t("sandboxes", "sandboxes"), db: t("databases", "bases de datos") });
1734
- var cache = /* @__PURE__ */ new Map();
1735
- function listRefs(ctx, kind) {
1736
- let p = cache.get(kind);
1737
- if (!p) {
1738
- p = (async () => {
1739
- const eb = await getClient(ctx);
1740
- if (kind === "agent") return (await eb.listAgents()).map((a) => ({ id: a.agentId, name: a.name, status: a.status }));
1741
- if (kind === "db") return (await eb.listDatabases()).items.map((d) => ({ id: d.id, name: d.name }));
1742
- return (await eb.sandboxes.list()).map((s) => ({ id: s.sandboxId, name: s.name, status: s.status }));
1743
- })();
1744
- p.catch(() => cache.delete(kind));
1745
- cache.set(kind, p);
1746
- }
1747
- return p;
1748
- }
1749
- async function resolveRef(ctx, kind, ref) {
1750
- if (looksLikeId(kind, ref)) return ref;
1751
- const r = matchRef(kind, ref, await listRefs(ctx, kind));
1752
- if (r.ok) return r.id;
1753
- if (r.reason === "not_found") {
1754
- throw new CliError(t(`No ${NOUN()[kind]} with id or name "${ref}".`, `No hay ${NOUN()[kind]} con id o nombre "${ref}".`), EXIT.API, `${t("List them with", "Enl\xEDstalos con")}: ${LS_HINT[kind]}`, "not_found", 404);
1755
- }
1756
- const shown = r.matches.slice(0, 10).map((m) => ` ${m.id}${m.status ? ` (${m.status})` : ""}`);
1757
- if (r.matches.length > 10) shown.push(t(` \u2026and ${r.matches.length - 10} more`, ` \u2026y ${r.matches.length - 10} m\xE1s`));
1758
- throw new CliError(
1759
- t(`"${ref}" matches ${r.matches.length} ${NOUNS()[kind]}. Use the id:`, `"${ref}" coincide con ${r.matches.length} ${NOUNS()[kind]}. Usa el id:`),
1760
- EXIT.USAGE,
1761
- `${shown.join("\n")}
1762
- ${t("List them with", "Enl\xEDstalos con")}: ${LS_HINT[kind]}`,
1763
- "ambiguous"
1764
- );
1765
- }
1766
- async function resolveRefs(ctx, key) {
1767
- const spec = REF_ARGS[key];
1768
- const ref = spec ? ctx.args[spec.index] : void 0;
1769
- if (!spec || !ref) return;
1770
- ctx.args[spec.index] = await resolveRef(ctx, spec.kind, ref);
1771
- }
1772
-
1773
1875
  // src/commands/db.ts
1774
1876
  async function findDb(ctx, id) {
1775
1877
  const hit = (await listRefs(ctx, "db")).find((d) => d.id === id);
@@ -1901,7 +2003,7 @@ var db = {
1901
2003
  const keyCol = str(ctx, "key-column");
1902
2004
  const dir = str(ctx, "dir");
1903
2005
  if (!tableName || !keyCol || !dir) throw usageError(t("Missing --table, --key-column or --dir.", "Faltan --table, --key-column o --dir."), this.usage);
1904
- if (!existsSync6(dir) || !statSync3(dir).isDirectory()) throw usageError(t(`Not a folder: ${dir}`, `No es una carpeta: ${dir}`), this.usage);
2006
+ if (!existsSync7(dir) || !statSync4(dir).isDirectory()) throw usageError(t(`Not a folder: ${dir}`, `No es una carpeta: ${dir}`), this.usage);
1905
2007
  const { photos, rejected } = photoFiles(dir);
1906
2008
  const eb = await getClient(ctx);
1907
2009
  const hit = await findDb(ctx, ref);
@@ -1948,7 +2050,7 @@ var db = {
1948
2050
  let url = urls.get(photo.sha);
1949
2051
  if (!url) {
1950
2052
  const up = await eb.uploadFile({ fileName: `${photo.sha.slice(0, 32)}.${photo.ext}`, contentType: photo.mime, size: photo.bytes, access: "public" });
1951
- const res = await fetch(up.putUrl, { method: "PUT", body: readFileSync7(photo.path), headers: { "Content-Type": photo.mime } });
2053
+ const res = await fetch(up.putUrl, { method: "PUT", body: readFileSync8(photo.path), headers: { "Content-Type": photo.mime } });
1952
2054
  if (!res.ok) throw new Error(t(`storage answered ${res.status}`, `el almacenamiento contest\xF3 ${res.status}`));
1953
2055
  url = up.file.url;
1954
2056
  if (!url) throw new Error(t("the API returned no public link", "la API no devolvi\xF3 liga p\xFAblica"));
@@ -2010,7 +2112,7 @@ var db = {
2010
2112
  };
2011
2113
 
2012
2114
  // src/commands/agents.ts
2013
- import { existsSync as existsSync10, readFileSync as readFileSync11, statSync as statSync6 } from "fs";
2115
+ import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync7 } from "fs";
2014
2116
 
2015
2117
  // src/api.ts
2016
2118
  import { EasybitsError as EasybitsError2, resolveBaseUrl as resolveBaseUrl4 } from "@easybits.cloud/sdk";
@@ -2040,8 +2142,8 @@ async function api(ctx, method, path, body, opts = {}) {
2040
2142
  var encodePath = (p) => p.replace(/^\/+/, "").split("/").map(encodeURIComponent).join("/");
2041
2143
 
2042
2144
  // src/commands/agents-config.ts
2043
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync as readdirSync2, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
2044
- import { basename as basename2, dirname, join as join4, relative, resolve } from "path";
2145
+ import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync9, readdirSync as readdirSync2, statSync as statSync5, writeFileSync as writeFileSync4 } from "fs";
2146
+ import { basename as basename3, dirname, join as join4, relative, resolve } from "path";
2045
2147
  var WORK_DIR = "/data/work";
2046
2148
  var MACHINE_TEMPLATES = /* @__PURE__ */ new Set(["ghosty-lite", "goose"]);
2047
2149
  var SKILLS_DIR = "/data/agent/skills";
@@ -2120,8 +2222,8 @@ function readLocalText(path, usage2) {
2120
2222
  if (process.stdin.isTTY) throw usageError(t("Expected the content on stdin.", "Se esperaba el contenido por stdin."), usage2);
2121
2223
  return readStdin().then((b) => b.toString("utf8"));
2122
2224
  }
2123
- if (!existsSync7(path)) throw usageError(t(`File not found: ${path}`, `No existe el archivo: ${path}`), usage2);
2124
- return readFileSync8(path, "utf8");
2225
+ if (!existsSync8(path)) throw usageError(t(`File not found: ${path}`, `No existe el archivo: ${path}`), usage2);
2226
+ return readFileSync9(path, "utf8");
2125
2227
  }
2126
2228
  function parseMcpFile(text, usage2) {
2127
2229
  let j;
@@ -2336,8 +2438,8 @@ var files2 = {
2336
2438
  if (!rest.length) throw usageError(t("Give one or more local files.", "Pasa uno o m\xE1s archivos locales."), FILES_USAGE2);
2337
2439
  const to = (str(ctx, "to") ?? "").replace(/^\/+|\/+$/g, "");
2338
2440
  const plan = rest.map((local) => {
2339
- if (!existsSync7(local) || !statSync4(local).isFile()) throw usageError(t(`File not found: ${local}`, `No existe el archivo: ${local}`), FILES_USAGE2);
2340
- return { local, path: to ? `${to}/${basename2(local)}` : basename2(local), bytes: statSync4(local).size };
2441
+ if (!existsSync8(local) || !statSync5(local).isFile()) throw usageError(t(`File not found: ${local}`, `No existe el archivo: ${local}`), FILES_USAGE2);
2442
+ return { local, path: to ? `${to}/${basename3(local)}` : basename3(local), bytes: statSync5(local).size };
2341
2443
  });
2342
2444
  if (bool(ctx, "dry-run")) {
2343
2445
  emit(ctx, { agentId: id, dryRun: true, upload: plan }, () => {
@@ -2348,7 +2450,7 @@ var files2 = {
2348
2450
  }
2349
2451
  const done = [];
2350
2452
  for (const p of plan) {
2351
- const r = await api(ctx, "PUT", `/agents/${id}/files/${encodePath(p.path)}`, readFileSync8(p.local), { raw: true });
2453
+ const r = await api(ctx, "PUT", `/agents/${id}/files/${encodePath(p.path)}`, readFileSync9(p.local), { raw: true });
2352
2454
  done.push({ local: p.local, ...r });
2353
2455
  if (!ctx.json) console.log(`${t("Uploaded", "Subido")} ${p.local} \u2192 ${WORK_DIR}/${p.path} (${fmtBytes(p.bytes)})`);
2354
2456
  }
@@ -2378,7 +2480,7 @@ function readSkillDir(dir) {
2378
2480
  if (e.name.startsWith(".") || e.name === "node_modules") continue;
2379
2481
  const p = join4(d, e.name);
2380
2482
  if (e.isDirectory()) visit(p);
2381
- else if (e.isFile() && relative(dir, p) !== "SKILL.md") assets.push({ name: relative(dir, p).split("\\").join("/"), contentBase64: readFileSync8(p).toString("base64") });
2483
+ else if (e.isFile() && relative(dir, p) !== "SKILL.md") assets.push({ name: relative(dir, p).split("\\").join("/"), contentBase64: readFileSync9(p).toString("base64") });
2382
2484
  }
2383
2485
  };
2384
2486
  visit(dir);
@@ -2442,11 +2544,11 @@ var skills = {
2442
2544
  const dir = str(ctx, "dir");
2443
2545
  if (!!file === !!dir) throw usageError(t("Pass --file SKILL.md or --dir <folder>.", "Pasa --file SKILL.md o --dir <carpeta>."), SKILLS_USAGE);
2444
2546
  const mdPath = file ?? join4(dir, "SKILL.md");
2445
- if (!existsSync7(mdPath)) throw usageError(t(`File not found: ${mdPath}`, `No existe el archivo: ${mdPath}`), SKILLS_USAGE);
2446
- const markdown = readFileSync8(mdPath, "utf8");
2547
+ if (!existsSync8(mdPath)) throw usageError(t(`File not found: ${mdPath}`, `No existe el archivo: ${mdPath}`), SKILLS_USAGE);
2548
+ const markdown = readFileSync9(mdPath, "utf8");
2447
2549
  const fm = skillFrontmatter(markdown);
2448
2550
  if (fm.error) throw usageError(`${mdPath}: ${fm.error}`, SKILLS_USAGE);
2449
- const slug = str(ctx, "slug") ?? (fm.name && SLUG_RE.test(fm.name) ? fm.name : basename2(resolve(dir ?? dirname(mdPath))).toLowerCase());
2551
+ const slug = str(ctx, "slug") ?? (fm.name && SLUG_RE.test(fm.name) ? fm.name : basename3(resolve(dir ?? dirname(mdPath))).toLowerCase());
2450
2552
  if (!SLUG_RE.test(slug)) throw usageError(t(`Invalid slug "${slug}" ([a-z0-9-], max 64). Pass --slug.`, `Slug inv\xE1lido "${slug}" ([a-z0-9-], m\xE1x 64). Pasa --slug.`), SKILLS_USAGE);
2451
2553
  const assets = dir ? readSkillDir(dir) : [];
2452
2554
  const plan = { agentId: id, slug, files: ["SKILL.md", ...assets.map((a) => a.name)], restart: bool(ctx, "restart") };
@@ -2703,10 +2805,10 @@ var exportAgent = {
2703
2805
  }
2704
2806
  };
2705
2807
  function readExport(path, usage2) {
2706
- if (!existsSync7(path)) throw usageError(t(`File not found: ${path}`, `No existe el archivo: ${path}`), usage2);
2808
+ if (!existsSync8(path)) throw usageError(t(`File not found: ${path}`, `No existe el archivo: ${path}`), usage2);
2707
2809
  let x;
2708
2810
  try {
2709
- x = JSON.parse(readFileSync8(path, "utf8"));
2811
+ x = JSON.parse(readFileSync9(path, "utf8"));
2710
2812
  } catch (e) {
2711
2813
  throw usageError(t(`${path} is not JSON: ${e.message}`, `${path} no es JSON: ${e.message}`), usage2);
2712
2814
  }
@@ -2751,12 +2853,12 @@ async function applyExport(ctx, id, x, opts) {
2751
2853
  }
2752
2854
 
2753
2855
  // src/commands/spec.ts
2754
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync10, statSync as statSync5, writeFileSync as writeFileSync5 } from "fs";
2856
+ import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync11, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
2755
2857
  import { homedir as homedir3 } from "os";
2756
2858
  import { dirname as dirname2, join as join6, resolve as resolve2 } from "path";
2757
2859
 
2758
2860
  // src/specfile.ts
2759
- import { existsSync as existsSync8, readFileSync as readFileSync9, readdirSync as readdirSync3 } from "fs";
2861
+ import { existsSync as existsSync9, readFileSync as readFileSync10, readdirSync as readdirSync3 } from "fs";
2760
2862
  import { join as join5, relative as relative2 } from "path";
2761
2863
  function walkLocal(root) {
2762
2864
  const out = [];
@@ -2768,20 +2870,20 @@ function walkLocal(root) {
2768
2870
  else if (e.isFile()) out.push(relative2(root, p).split("\\").join("/"));
2769
2871
  }
2770
2872
  };
2771
- if (existsSync8(root)) visit(root);
2873
+ if (existsSync9(root)) visit(root);
2772
2874
  return out;
2773
2875
  }
2774
2876
  function readUploads(dir) {
2775
2877
  const skills2 = {};
2776
2878
  const skillsRoot = join5(dir, "skills");
2777
- if (existsSync8(skillsRoot)) {
2879
+ if (existsSync9(skillsRoot)) {
2778
2880
  for (const e of readdirSync3(skillsRoot, { withFileTypes: true })) {
2779
2881
  if (!e.isDirectory() || e.name.startsWith(".")) continue;
2780
- skills2[e.name] = walkLocal(join5(skillsRoot, e.name)).map((path) => ({ path, contentBase64: readFileSync9(join5(skillsRoot, e.name, path)).toString("base64") }));
2882
+ skills2[e.name] = walkLocal(join5(skillsRoot, e.name)).map((path) => ({ path, contentBase64: readFileSync10(join5(skillsRoot, e.name, path)).toString("base64") }));
2781
2883
  }
2782
2884
  }
2783
2885
  const filesRoot = join5(dir, "files");
2784
- const files3 = Object.fromEntries(walkLocal(filesRoot).map((p) => [p, readFileSync9(join5(filesRoot, p)).toString("base64")]));
2886
+ const files3 = Object.fromEntries(walkLocal(filesRoot).map((p) => [p, readFileSync10(join5(filesRoot, p)).toString("base64")]));
2785
2887
  return { ...Object.keys(skills2).length ? { skills: skills2 } : {}, ...Object.keys(files3).length ? { files: files3 } : {} };
2786
2888
  }
2787
2889
  function secretsFor(text, env = process.env) {
@@ -2857,13 +2959,13 @@ ${t("Edit it, then", "Ed\xEDtalo y luego")}: easybits apply ${out} --dry-run`
2857
2959
  }
2858
2960
  };
2859
2961
  function readTarget(target, usage2) {
2860
- if (!existsSync9(target)) throw usageError(t(`Not found: ${target}`, `No existe: ${target}`), usage2);
2861
- if (statSync5(target).isDirectory()) {
2862
- const name = SPEC_NAMES.find((n) => existsSync9(join6(target, n)));
2962
+ if (!existsSync10(target)) throw usageError(t(`Not found: ${target}`, `No existe: ${target}`), usage2);
2963
+ if (statSync6(target).isDirectory()) {
2964
+ const name = SPEC_NAMES.find((n) => existsSync10(join6(target, n)));
2863
2965
  if (!name) throw usageError(t(`${target} has no agent.yaml.`, `${target} no tiene agent.yaml.`), usage2);
2864
- return { text: readFileSync10(join6(target, name), "utf8"), dir: target };
2966
+ return { text: readFileSync11(join6(target, name), "utf8"), dir: target };
2865
2967
  }
2866
- return { text: readFileSync10(target, "utf8") };
2968
+ return { text: readFileSync11(target, "utf8") };
2867
2969
  }
2868
2970
  var line = (c) => `${c.op} ${c.what}`;
2869
2971
  var printPlan = (plan) => console.log(plan.map(line).join("\n"));
@@ -2984,7 +3086,7 @@ var apply = {
2984
3086
  // src/commands/agents.ts
2985
3087
  function isLegacyExport(path) {
2986
3088
  try {
2987
- return existsSync10(path) && !statSync6(path).isDirectory() && /"kind"\s*:\s*"easybits\.agent"/.test(readFileSync11(path, "utf8"));
3089
+ return existsSync11(path) && !statSync7(path).isDirectory() && /"kind"\s*:\s*"easybits\.agent"/.test(readFileSync12(path, "utf8"));
2988
3090
  } catch {
2989
3091
  return false;
2990
3092
  }
@@ -3051,9 +3153,9 @@ var agents = {
3051
3153
  if (promptInline != null && promptFile) throw usageError(t("Use --prompt or --prompt-file, not both.", "Usa --prompt o --prompt-file, no los dos."), this.usage);
3052
3154
  const mode = str(ctx, "prompt-mode");
3053
3155
  if (mode && mode !== "append" && mode !== "replace") throw usageError(t("--prompt-mode must be append or replace.", "--prompt-mode debe ser append o replace."), this.usage);
3054
- if (promptFile && !existsSync10(promptFile)) throw usageError(t(`File not found: ${promptFile}`, `No existe el archivo: ${promptFile}`), this.usage);
3156
+ if (promptFile && !existsSync11(promptFile)) throw usageError(t(`File not found: ${promptFile}`, `No existe el archivo: ${promptFile}`), this.usage);
3055
3157
  const mcpFile = str(ctx, "mcp-file");
3056
- if (mcpFile && !existsSync10(mcpFile)) throw usageError(t(`File not found: ${mcpFile}`, `No existe el archivo: ${mcpFile}`), this.usage);
3158
+ if (mcpFile && !existsSync11(mcpFile)) throw usageError(t(`File not found: ${mcpFile}`, `No existe el archivo: ${mcpFile}`), this.usage);
3057
3159
  if (bool(ctx, "copy-files") && !like && !from) throw usageError(t("--copy-files needs --like or --from.", "--copy-files necesita --like o --from."), this.usage);
3058
3160
  if (from && !isLegacyExport(from)) {
3059
3161
  await applyTarget(ctx, from, { create: true, name: str(ctx, "name"), dryRun: bool(ctx, "dry-run"), usage: this.usage });
@@ -3063,7 +3165,7 @@ var agents = {
3063
3165
  const template = str(ctx, "template") ?? source?.template;
3064
3166
  if (!template) throw usageError(t("Missing --template (or --like / --from).", "Falta --template (o --like / --from)."), this.usage);
3065
3167
  const env = await envFrom(ctx, this.usage);
3066
- const prompt = promptInline ?? (promptFile ? readFileSync11(promptFile, "utf8") : source?.systemPrompt);
3168
+ const prompt = promptInline ?? (promptFile ? readFileSync12(promptFile, "utf8") : source?.systemPrompt);
3067
3169
  const promptMode = mode ?? source?.systemPromptMode;
3068
3170
  const multiline = !!prompt && /[\r\n]/.test(prompt.trimEnd());
3069
3171
  const promptAfter = multiline ? prompt : void 0;
@@ -3072,7 +3174,7 @@ var agents = {
3072
3174
  }
3073
3175
  if (prompt && !multiline) env.SYSTEM_PROMPT = prompt.trimEnd();
3074
3176
  if (promptMode && (prompt || mode) && !multiline) env.SYSTEM_PROMPT_MODE = promptMode;
3075
- const mcpServers = mcpFile ? parseMcpFile(readFileSync11(mcpFile, "utf8"), this.usage) : source?.mcpServers;
3177
+ const mcpServers = mcpFile ? parseMcpFile(readFileSync12(mcpFile, "utf8"), this.usage) : source?.mcpServers;
3076
3178
  if (mcpServers && hasMasked(mcpServers)) {
3077
3179
  throw usageError(t(`${from ?? "the source"} has masked MCP values (\u2026***).`, `${from ?? "la fuente"} trae valores del MCP tapados (\u2026***).`), t("Re-export with --show-secrets, or pass --mcp-file", "Vuelve a exportar con --show-secrets, o pasa --mcp-file"));
3078
3180
  }
@@ -3164,7 +3266,7 @@ var agents = {
3164
3266
  };
3165
3267
 
3166
3268
  // src/commands/completion.ts
3167
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
3269
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
3168
3270
  import { homedir as homedir4 } from "os";
3169
3271
  import { dirname as dirname3, join as join7 } from "path";
3170
3272
  var GLOBAL_FLAGS = ["--json", "--token", "--lang", "--help", "--version"];
@@ -3207,7 +3309,7 @@ var cacheFile = () => join7(process.env.XDG_CACHE_HOME || join7(homedir4(), ".ca
3207
3309
  async function namesOf(ctx, kind) {
3208
3310
  let cache2 = {};
3209
3311
  try {
3210
- cache2 = JSON.parse(readFileSync12(cacheFile(), "utf8"));
3312
+ cache2 = JSON.parse(readFileSync13(cacheFile(), "utf8"));
3211
3313
  const c = cache2[kind];
3212
3314
  if (c && Date.now() - c.at < 5 * 6e4) return c.names;
3213
3315
  } catch {
@@ -3334,13 +3436,13 @@ var docs = {
3334
3436
  };
3335
3437
 
3336
3438
  // src/commands/doctor.ts
3337
- import { existsSync as existsSync11, statSync as statSync7 } from "fs";
3439
+ import { existsSync as existsSync12, statSync as statSync8 } from "fs";
3338
3440
  import { homedir as homedir6 } from "os";
3339
3441
  import { join as join9 } from "path";
3340
3442
  import { resolveBaseUrl as resolveBaseUrl6 } from "@easybits.cloud/sdk";
3341
3443
 
3342
3444
  // src/update.ts
3343
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync13, writeFileSync as writeFileSync7 } from "fs";
3445
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
3344
3446
  import { homedir as homedir5 } from "os";
3345
3447
  import { dirname as dirname4, join as join8 } from "path";
3346
3448
  var FILE = join8(process.env.XDG_CONFIG_HOME || join8(homedir5(), ".config"), "easybits", "update.json");
@@ -3355,7 +3457,7 @@ function newer(a, b) {
3355
3457
  var updateCheckDisabled = () => Boolean(process.env.EASYBITS_NO_UPDATE_CHECK || process.env.CI);
3356
3458
  function readCache() {
3357
3459
  try {
3358
- return JSON.parse(readFileSync13(FILE, "utf8"));
3460
+ return JSON.parse(readFileSync14(FILE, "utf8"));
3359
3461
  } catch {
3360
3462
  return {};
3361
3463
  }
@@ -3390,7 +3492,7 @@ async function updateNotice(current2) {
3390
3492
  }
3391
3493
 
3392
3494
  // src/commands/doctor.ts
3393
- var VERSION = true ? "0.13.0" : "dev";
3495
+ var VERSION = true ? "0.14.1" : "dev";
3394
3496
  var doctor2 = {
3395
3497
  name: "doctor",
3396
3498
  group: "Account",
@@ -3434,8 +3536,8 @@ var doctor2 = {
3434
3536
  }
3435
3537
  }
3436
3538
  const rcPath = join9(homedir6(), ".easybitsrc");
3437
- if (existsSync11(rcPath)) {
3438
- const mode = statSync7(rcPath).mode & 511;
3539
+ if (existsSync12(rcPath)) {
3540
+ const mode = statSync8(rcPath).mode & 511;
3439
3541
  const ok2 = (mode & 63) === 0;
3440
3542
  checks.push({ check: "rc-file", ok: ok2, detail: `${rcPath} ${mode.toString(8).padStart(3, "0")}`, ...ok2 ? {} : { hint: t(`It holds tokens. Run: chmod 600 ${rcPath}`, `Guarda tokens. Corre: chmod 600 ${rcPath}`) } });
3441
3543
  }
@@ -3620,6 +3722,23 @@ var HELP_ES = {
3620
3722
  "List your permanent machines": "Enlista tus m\xE1quinas permanentes",
3621
3723
  "Publish the machine's current app code as a new release": "Publica el c\xF3digo actual de la app como un release nuevo",
3622
3724
  "Release note": "Nota del release",
3725
+ "Create a machine and put an app on it in one step (or redeploy onto an existing one)": "Crea una m\xE1quina y le pone la app en un paso (o redespliega sobre una existente)",
3726
+ "Git repo to clone (clean URL; a token goes in --repo-token)": "Repo git a clonar (URL limpia; el token va en --repo-token)",
3727
+ "Branch to clone": "Rama a clonar",
3728
+ "Token for a private repo; best as $secret:NAME from your vault": "Token de un repo privado; mejor como $secret:NOMBRE de tu vault",
3729
+ "A .tar.gz/.zip of the app: URL, or a local file that gets uploaded first": "Un .tar.gz/.zip de la app: URL, o un archivo local que se sube primero",
3730
+ "Deploy onto this existing machine instead of creating one": "Despliega sobre esta m\xE1quina existente en vez de crear una",
3731
+ "Size for a new machine: nano, micro (default), estandar, focus, performance\u2026": "Tama\xF1o de una m\xE1quina nueva: nano, micro (default), estandar, focus, performance\u2026",
3732
+ "Human label": "Nombre para personas",
3733
+ "Where the app lives in the machine (default /app)": "D\xF3nde vive la app en la m\xE1quina (default /app)",
3734
+ "Build command (default: npm ci && npm run build)": "Comando de build (default: npm ci && npm run build)",
3735
+ "Start command (default: npm start)": "Comando de arranque (default: npm start)",
3736
+ "Port the app listens on (default 3000)": "Puerto en el que escucha la app (default 3000)",
3737
+ "The code is already built: skip the build, just start it": "El c\xF3digo ya viene compilado: sin build, s\xF3lo arranca",
3738
+ "Vault secret the app needs, by name (repeatable)": "Secreto del vault que necesita la app, por nombre (repetible)",
3739
+ "Path the nightly backup copies (repeatable). Without it nothing is backed up": "Ruta que copia el respaldo nocturno (repetible). Sin ella no se respalda nada",
3740
+ "Domain to attach; the output says which DNS record to create": "Dominio a pegar; la salida dice qu\xE9 registro DNS crear",
3741
+ "redeploy": "redespliegue",
3623
3742
  "same thing, shorter": "lo mismo, m\xE1s corto",
3624
3743
  "List releases, newest first": "Enlista los releases, el m\xE1s nuevo primero",
3625
3744
  "How many": "Cu\xE1ntos",
@@ -3871,7 +3990,7 @@ function normalizeArgs(input) {
3871
3990
  }
3872
3991
 
3873
3992
  // src/index.ts
3874
- var VERSION2 = true ? "0.13.0" : "dev";
3993
+ var VERSION2 = true ? "0.14.1" : "dev";
3875
3994
  var GLOBAL_OPTIONS = {
3876
3995
  json: { type: "boolean" },
3877
3996
  token: { type: "string" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easybits.cloud/cli",
3
- "version": "0.13.0",
3
+ "version": "0.14.1",
4
4
  "description": "Official CLI for EasyBits, the cloud for AI agents — sandboxes, hosting, domains, databases, agents and files from your terminal",
5
5
  "type": "module",
6
6
  "bin": {