@easybits.cloud/cli 0.10.0 → 0.12.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.
Files changed (3) hide show
  1. package/README.md +15 -0
  2. package/dist/index.js +783 -307
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -11,6 +11,73 @@ import { existsSync, readFileSync } from "fs";
11
11
 
12
12
  // src/errors.ts
13
13
  import { EasybitsError } from "@easybits.cloud/sdk";
14
+
15
+ // src/i18n.ts
16
+ var current = null;
17
+ function detectLang(flag, env = process.env) {
18
+ const pick = (v) => v && /^es\b|^es[_-]/i.test(v) ? "es" : v && /^en\b|^en[_-]|^c$|^posix$/i.test(v) ? "en" : null;
19
+ return pick(flag) ?? pick(env.EASYBITS_LANG) ?? pick(env.LC_ALL) ?? pick(env.LC_MESSAGES) ?? pick(env.LANG) ?? "en";
20
+ }
21
+ function setLang(l) {
22
+ current = l;
23
+ }
24
+ var lang = () => current ?? (current = detectLang());
25
+ var t = (en, es) => lang() === "es" ? es : en;
26
+ function langFlag(argv2) {
27
+ const end = argv2.indexOf("--");
28
+ const head = end === -1 ? argv2 : argv2.slice(0, end);
29
+ const i = head.indexOf("--lang");
30
+ if (i !== -1) return head[i + 1];
31
+ return head.find((a) => a.startsWith("--lang="))?.slice(7);
32
+ }
33
+ var FROM_ES = [
34
+ ["ruta inv\xE1lida", "invalid path"],
35
+ ["cuerpo vac\xEDo", "empty body"],
36
+ ["archivo demasiado grande (m\xE1x 10 MB)", "file too large (max 10 MB)"],
37
+ ["skill demasiado grande (m\xE1x 25 MB)", "skill too large (max 25 MB)"],
38
+ ["slug inv\xE1lido ([a-z0-9-], m\xE1x 64)", "invalid slug ([a-z0-9-], max 64)"],
39
+ ["slug inv\xE1lido", "invalid slug"],
40
+ ["SKILL.md sin frontmatter (--- name / description ---)", "SKILL.md has no frontmatter (--- name / description ---)"],
41
+ ["frontmatter sin `{}:`", "frontmatter has no `{}:`"],
42
+ ["`{}:` vac\xEDo", "`{}:` is empty"],
43
+ ['`{}:` lleva un ": " sin comillas; entrecomilla el valor o la skill se descarta', '`{}:` has an unquoted ": "; quote the value or the skill is dropped'],
44
+ ["asset con nombre inv\xE1lido: {}", "asset with an invalid name: {}"],
45
+ ["asset {} sin contentBase64", "asset {} has no contentBase64"],
46
+ ["este agente es anterior al revive: recr\xE9alo", "this agent predates revive: recreate it"],
47
+ ["falta markdown (el contenido de SKILL.md)", "missing markdown (the SKILL.md content)"],
48
+ ["nada que cambiar: systemPrompt y/o systemPromptMode", "nothing to change: systemPrompt and/or systemPromptMode"],
49
+ ['agente_sin_maquina: template "{}" no expone el system prompt por archivo', 'agent without a machine: template "{}" does not expose its system prompt as a file'],
50
+ ["el cerebro la ve tras el siguiente reinicio: POST \u2026/restart", "the engine sees it after the next restart: POST \u2026/restart"],
51
+ ["deja de verla tras el siguiente reinicio: POST \u2026/restart", "it stops seeing it after the next restart: POST \u2026/restart"]
52
+ ];
53
+ var FROM_EN = [
54
+ ["agent not found", "agente no encontrado"],
55
+ ["sandbox not found", "sandbox no encontrado"],
56
+ ["Method not allowed", "M\xE9todo no permitido"],
57
+ ["template and env required", "faltan template y env"],
58
+ ["the file has no template", "el archivo no trae template"],
59
+ ["missing values for {}: export them in your environment (a new agent has nothing to keep)", "faltan valores para {}: exp\xF3rtalos en tu entorno (un agente nuevo no tiene nada que conservar)"],
60
+ ["agent failed to start: {}", "el agente no arranc\xF3: {}"],
61
+ ["Unauthorized", "No autorizado"],
62
+ ["Forbidden", "Prohibido"],
63
+ ["Rate limit exceeded", "L\xEDmite de peticiones excedido"]
64
+ ];
65
+ var compiled = {};
66
+ var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
67
+ var compile = (s) => new RegExp(`^${s.split("{}").map(escape).join("(.+?)")}$`, "s");
68
+ function serverText(msg, l = lang()) {
69
+ if (!msg) return msg;
70
+ compiled[l] ??= (l === "en" ? FROM_ES : FROM_EN).map(([from, to]) => ({ re: compile(from), to }));
71
+ for (const c of compiled[l]) {
72
+ const m = c.re.exec(msg);
73
+ if (!m) continue;
74
+ let i = 1;
75
+ return c.to.replace(/\{\}/g, () => m[i++] ?? "");
76
+ }
77
+ return msg;
78
+ }
79
+
80
+ // src/errors.ts
14
81
  var EXIT = { OK: 0, API: 1, USAGE: 2, AUTH: 3 };
15
82
  var CliError = class extends Error {
16
83
  constructor(message, exitCode = EXIT.API, hint, code = "error", status) {
@@ -29,54 +96,54 @@ function usageError(message, usage2) {
29
96
  return new CliError(
30
97
  message,
31
98
  EXIT.USAGE,
32
- usage2 ? `Usage: ${usage2}` : "Run: easybits --help",
99
+ usage2 ? `${t("Usage", "Uso")}: ${usage2}` : t("Run: easybits --help", "Corre: easybits --help"),
33
100
  "usage"
34
101
  );
35
102
  }
36
103
  function notLoggedIn() {
37
104
  return new CliError(
38
- "Not logged in.",
105
+ t("Not logged in.", "No has iniciado sesi\xF3n."),
39
106
  EXIT.AUTH,
40
- "Run: easybits login (or: easybits login - < key-file, or set EASYBITS_API_KEY)",
107
+ t("Run: easybits login (or: easybits login - < key-file, or set EASYBITS_API_KEY)", "Corre: easybits login (o: easybits login - < archivo-con-la-key, o define EASYBITS_API_KEY)"),
41
108
  "not_logged_in"
42
109
  );
43
110
  }
44
111
  function apiMessage(body) {
45
112
  try {
46
113
  const j = JSON.parse(body);
47
- if (typeof j.error === "string" && typeof j.message === "string") return { message: `${j.error}: ${j.message}`, kind: j.error };
48
- if (typeof j.code === "string" && typeof j.error === "string") return { message: `${j.code}: ${j.error}`, kind: j.code };
114
+ if (typeof j.error === "string" && typeof j.message === "string") return { message: `${j.error}: ${serverText(j.message)}`, kind: j.error };
115
+ if (typeof j.code === "string" && typeof j.error === "string") return { message: `${j.code}: ${serverText(j.error)}`, kind: j.code };
49
116
  const msg = j.message ?? j.error ?? j.detail;
50
- if (typeof msg === "string") return { message: msg };
117
+ if (typeof msg === "string") return { message: serverText(msg) };
51
118
  if (msg) return { message: JSON.stringify(msg) };
52
119
  } catch {
53
120
  }
54
- return { message: body.trim() || "empty response" };
121
+ return { message: serverText(body.trim()) || t("empty response", "respuesta vac\xEDa") };
55
122
  }
56
- var KIND_HINTS = {
57
- SandboxBusy: "A snapshot or fork is running on this sandbox. Wait a few minutes and retry (easybits sb get <id> shows `activity`).",
58
- SandboxUnreachable: "The sandbox is not answering inside. Wait and retry; if it persists, destroy it.",
59
- SandboxHostTimeout: "The operation may still be running. Check `easybits sb get <id>` and retry in a few minutes.",
60
- SandboxHostError: "The sandbox host failed. Retry; if it persists, contact support.",
61
- SQL_ERROR: "Fix the SQL statement.",
62
- DATABASE_STORAGE_MISSING: "This database has no storage; its data is unavailable. Delete it (easybits db rm <db> --yes) and create a new one.",
63
- DATABASE_BACKEND_ERROR: "The database backend failed. Retry in a moment."
64
- };
123
+ var KIND_HINTS = () => ({
124
+ SandboxBusy: t("A snapshot or fork is running on this sandbox. Wait a few minutes and retry (easybits sb get <id> shows `activity`).", "Hay un snapshot o fork en curso en este sandbox. Espera unos minutos y reintenta (easybits sb get <id> ense\xF1a `activity`)."),
125
+ SandboxUnreachable: t("The sandbox is not answering inside. Wait and retry; if it persists, destroy it.", "El sandbox no contesta por dentro. Espera y reintenta; si sigue, destr\xFAyelo."),
126
+ SandboxHostTimeout: t("The operation may still be running. Check `easybits sb get <id>` and retry in a few minutes.", "La operaci\xF3n puede seguir en curso. Revisa `easybits sb get <id>` y reintenta en unos minutos."),
127
+ SandboxHostError: t("The sandbox host failed. Retry; if it persists, contact support.", "Fall\xF3 el host de sandboxes. Reintenta; si sigue, contacta a soporte."),
128
+ SQL_ERROR: t("Fix the SQL statement.", "Corrige la sentencia SQL."),
129
+ DATABASE_STORAGE_MISSING: t("This database has no storage; its data is unavailable. Delete it (easybits db rm <db> --yes) and create a new one.", "Esta base no tiene almacenamiento; sus datos no est\xE1n disponibles. B\xF3rrala (easybits db rm <db> --yes) y crea otra."),
130
+ DATABASE_BACKEND_ERROR: t("The database backend failed. Retry in a moment.", "Fall\xF3 el backend de la base. Reintenta en un momento.")
131
+ });
65
132
  function toCliError(err) {
66
133
  if (err instanceof CliError) return err;
67
134
  if (err instanceof EasybitsError) {
68
135
  const { message: message2, kind } = apiMessage(err.body);
69
136
  if (err.status === 401) {
70
137
  return new CliError(
71
- `Credentials rejected (401): ${message2}`,
138
+ `${t("Credentials rejected", "Credenciales rechazadas")} (401): ${message2}`,
72
139
  EXIT.AUTH,
73
- "Run: easybits login (or pass a valid API key from https://www.easybits.cloud/dash/developer)",
140
+ t("Run: easybits login (or pass a valid API key from https://www.easybits.cloud/dash/developer)", "Corre: easybits login (o pasa una API key v\xE1lida de https://www.easybits.cloud/dash/developer)"),
74
141
  "unauthorized",
75
142
  401
76
143
  );
77
144
  }
78
- const hint = (kind && KIND_HINTS[kind]) ?? (err.status === 403 ? "Your key may lack the scope for this action (READ/WRITE/DELETE)." : err.status === 404 ? "Check the id. List what you have with the matching `ls` command." : err.status === 402 ? "Plan limit reached. See: easybits usage" : void 0);
79
- return new CliError(`API error ${err.status}: ${message2}`, EXIT.API, hint, "api_error", err.status);
145
+ const hint = (kind && KIND_HINTS()[kind]) ?? (err.status === 403 ? t("Your key may lack the scope for this action (READ/WRITE/DELETE).", "Tu key quiz\xE1 no tiene el permiso para esto (READ/WRITE/DELETE).") : err.status === 404 ? t("Check the id. List what you have with the matching `ls` command.", "Revisa el id. Enlista lo que tienes con el `ls` que corresponda.") : err.status === 402 ? t("Plan limit reached. See: easybits usage", "Llegaste al l\xEDmite de tu plan. Mira: easybits usage") : void 0);
146
+ return new CliError(`${t("API error", "Error de la API")} ${err.status}: ${message2}`, EXIT.API, hint, "api_error", err.status);
80
147
  }
81
148
  const message = err instanceof Error ? err.message : String(err);
82
149
  return new CliError(message, EXIT.API);
@@ -85,7 +152,7 @@ function toCliError(err) {
85
152
  // src/args.ts
86
153
  function need(ctx, i, name, usage2) {
87
154
  const v = ctx.args[i];
88
- if (v == null || v === "") throw usageError(`Missing <${name}>.`, usage2);
155
+ if (v == null || v === "") throw usageError(t(`Missing <${name}>.`, `Falta <${name}>.`), usage2);
89
156
  return v;
90
157
  }
91
158
  function str(ctx, key) {
@@ -103,14 +170,14 @@ function int(ctx, key, usage2) {
103
170
  const v = str(ctx, key);
104
171
  if (v == null) return void 0;
105
172
  const n = Number(v);
106
- if (!Number.isInteger(n) || n < 0) throw usageError(`--${key} must be a non-negative integer.`, usage2);
173
+ if (!Number.isInteger(n) || n < 0) throw usageError(t(`--${key} must be a non-negative integer.`, `--${key} debe ser un entero no negativo.`), usage2);
107
174
  return n;
108
175
  }
109
176
  function pairs(items, usage2) {
110
177
  const out = {};
111
178
  for (const item of items) {
112
179
  const eq = item.indexOf("=");
113
- if (eq < 1) throw usageError(`Expected KEY=VALUE, got "${item}".`, usage2);
180
+ if (eq < 1) throw usageError(t(`Expected KEY=VALUE, got "${item}".`, `Se esperaba KEY=VALUE y lleg\xF3 "${item}".`), usage2);
114
181
  out[item.slice(0, eq)] = item.slice(eq + 1);
115
182
  }
116
183
  return out;
@@ -126,10 +193,10 @@ var ENV_FILE_OPTION = {
126
193
  async function readEnvFile(path, usage2) {
127
194
  let text;
128
195
  if (path === "-") {
129
- if (process.stdin.isTTY) throw usageError("--dotenv - expects KEY=VALUE lines on stdin.", usage2);
196
+ if (process.stdin.isTTY) throw usageError(t("--dotenv - expects KEY=VALUE lines on stdin.", "--dotenv - espera renglones KEY=VALUE por stdin."), usage2);
130
197
  text = (await readStdin()).toString("utf8");
131
198
  } else {
132
- if (!existsSync(path)) throw usageError(`File not found: ${path}`, usage2);
199
+ if (!existsSync(path)) throw usageError(t(`File not found: ${path}`, `No existe el archivo: ${path}`), usage2);
133
200
  text = readFileSync(path, "utf8");
134
201
  }
135
202
  const out = {};
@@ -137,7 +204,7 @@ async function readEnvFile(path, usage2) {
137
204
  const line2 = raw.trim();
138
205
  if (!line2 || line2.startsWith("#")) continue;
139
206
  const m = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.*)$/.exec(line2);
140
- if (!m) throw usageError(`${path === "-" ? "stdin" : path}:${n + 1}: expected KEY=VALUE.`, usage2);
207
+ if (!m) throw usageError(`${path === "-" ? "stdin" : path}:${n + 1}: ${t("expected KEY=VALUE.", "se esperaba KEY=VALUE.")}`, usage2);
141
208
  let v = m[2];
142
209
  if (v.length >= 2 && (v[0] === '"' || v[0] === "'") && v.endsWith(v[0])) v = v.slice(1, -1);
143
210
  out[m[1]] = v;
@@ -166,7 +233,29 @@ function fmtDate(s) {
166
233
  const d = new Date(s);
167
234
  return Number.isNaN(d.getTime()) ? s : d.toISOString().slice(0, 16).replace("T", " ");
168
235
  }
169
- function table(rows, cols, empty = "Nothing here yet.") {
236
+ var HEADERS_ES = {
237
+ COLUMNS: "COLUMNAS",
238
+ CREATED: "CREADO",
239
+ DESCRIPTION: "DESCRIPCI\xD3N",
240
+ DOMAIN: "DOMINIO",
241
+ EXPIRES: "VENCE",
242
+ FILES: "ARCHIVOS",
243
+ MESSAGE: "MENSAJE",
244
+ MODIFIED: "MODIFICADO",
245
+ "MXN/MONTH": "MXN/MES",
246
+ NAME: "NOMBRE",
247
+ PATH: "RUTA",
248
+ PORT: "PUERTO",
249
+ ROWS: "FILAS",
250
+ SIZE: "TAMA\xD1O",
251
+ STATUS: "ESTADO",
252
+ TABLE: "TABLA",
253
+ TYPE: "TIPO",
254
+ "URL / COMMAND": "URL / COMANDO",
255
+ VERSION: "VERSI\xD3N"
256
+ };
257
+ function table(rows, cols, empty = t("Nothing here yet.", "Todav\xEDa no hay nada.")) {
258
+ if (lang() === "es") cols = cols.map(([k, l]) => [k, HEADERS_ES[l] ?? l]);
170
259
  if (rows.length === 0) {
171
260
  console.log(empty);
172
261
  return;
@@ -235,7 +324,7 @@ async function tokenRequest(base, form) {
235
324
  });
236
325
  const body = await res.json().catch(() => ({}));
237
326
  if (!res.ok || !body.access_token) {
238
- throw new CliError(`Login failed (${body.error ?? res.status}).`, EXIT.AUTH, "Run: easybits login", "login_failed", res.status);
327
+ throw new CliError(t(`Login failed (${body.error ?? res.status}).`, `No se pudo entrar (${body.error ?? res.status}).`), EXIT.AUTH, t("Run: easybits login", "Corre: easybits login"), "login_failed", res.status);
239
328
  }
240
329
  return body;
241
330
  }
@@ -249,7 +338,7 @@ async function fetchEmail(token) {
249
338
  const base = (await resolveBaseUrl()).replace(/\/+$/, "");
250
339
  const res = await fetch(`${base}/api/v2/me`, { headers: { authorization: `Bearer ${token}` } });
251
340
  if (res.status === 401) {
252
- throw new CliError("Credentials rejected (401).", EXIT.AUTH, "Check the key at https://www.easybits.cloud/dash/developer", "unauthorized", 401);
341
+ throw new CliError(t("Credentials rejected (401).", "Credenciales rechazadas (401)."), EXIT.AUTH, t("Check the key at https://www.easybits.cloud/dash/developer", "Revisa la key en https://www.easybits.cloud/dash/developer"), "unauthorized", 401);
253
342
  }
254
343
  if (!res.ok) return void 0;
255
344
  return (await res.json().catch(() => ({}))).email;
@@ -276,7 +365,7 @@ async function oauthLogin(ctx, opts = {}) {
276
365
  });
277
366
  const code = await new Promise((resolve3, reject) => {
278
367
  const timer = setTimeout(
279
- () => reject(new CliError("Login timed out after 5 minutes.", EXIT.AUTH, "Run: easybits login", "login_timeout")),
368
+ () => reject(new CliError(t("Login timed out after 5 minutes.", "El login se venci\xF3 a los 5 minutos."), EXIT.AUTH, t("Run: easybits login", "Corre: easybits login"), "login_timeout")),
280
369
  LOGIN_TIMEOUT_MS
281
370
  );
282
371
  server.on("request", (req, res) => {
@@ -300,12 +389,13 @@ async function oauthLogin(ctx, opts = {}) {
300
389
  });
301
390
  if (ctx.json) process.stdout.write(JSON.stringify({ event: "login_url", url }) + "\n");
302
391
  if (opts.openBrowser !== false) {
303
- info("Opening your browser to sign in to EasyBits\u2026");
392
+ info(t("Opening your browser to sign in to EasyBits\u2026", "Abriendo tu navegador para entrar a EasyBits\u2026"));
304
393
  openUrl(url);
305
394
  }
306
- info(`If it doesn't open, visit:
307
- ${url}`);
308
- info("Waiting for the browser\u2026");
395
+ info(t(`If it doesn't open, visit:
396
+ ${url}`, `Si no se abre, entra a:
397
+ ${url}`));
398
+ info(t("Waiting for the browser\u2026", "Esperando al navegador\u2026"));
309
399
  });
310
400
  const tok = await tokenRequest(base, {
311
401
  grant_type: "authorization_code",
@@ -370,14 +460,14 @@ async function resolveCredential(ctx) {
370
460
  fromSession = false;
371
461
  if (rc.apiKey) return rc.apiKey;
372
462
  if (!interactive(ctx)) {
373
- throw new CliError("Your session expired.", EXIT.AUTH, "Run: easybits login (or set EASYBITS_API_KEY)", "session_expired");
463
+ throw new CliError(t("Your session expired.", "Tu sesi\xF3n venci\xF3."), EXIT.AUTH, t("Run: easybits login (or set EASYBITS_API_KEY)", "Corre: easybits login (o define EASYBITS_API_KEY)"), "session_expired");
374
464
  }
375
465
  } else if (rc.apiKey) {
376
466
  return rc.apiKey;
377
467
  } else if (!interactive(ctx)) {
378
468
  throw notLoggedIn();
379
469
  }
380
- process.stderr.write("Not logged in yet \u2014 let's fix that.\n");
470
+ process.stderr.write(t("Not logged in yet \u2014 let's fix that.\n", "Todav\xEDa no inicias sesi\xF3n \u2014 vamos a arreglarlo.\n"));
381
471
  fromSession = true;
382
472
  return (await oauthLogin(ctx)).accessToken;
383
473
  }
@@ -409,22 +499,22 @@ var login = {
409
499
  async run(ctx) {
410
500
  let key = ctx.args[0];
411
501
  if (key === "-" || bool(ctx, "with-token")) {
412
- if (process.stdin.isTTY) throw usageError("Pipe the API key on stdin: easybits login - < key.txt", this.usage);
502
+ if (process.stdin.isTTY) throw usageError(t("Pipe the API key on stdin: easybits login - < key.txt", "Pasa la API key por stdin: easybits login - < key.txt"), this.usage);
413
503
  key = (await readStdin()).toString("utf8").trim();
414
- if (!key) throw usageError("Empty API key on stdin.", this.usage);
504
+ if (!key) throw usageError(t("Empty API key on stdin.", "La API key lleg\xF3 vac\xEDa por stdin."), this.usage);
415
505
  }
416
506
  if (key) {
417
507
  const email2 = await fetchEmail(key);
418
508
  const { oauth: _drop, ...rest } = readRc();
419
509
  writeRc({ ...rest, apiKey: key });
420
510
  if (ctx.json) process.stdout.write(JSON.stringify({ event: "logged_in", email: email2, method: "apiKey" }) + "\n");
421
- else console.log(`Logged in${email2 ? ` as ${email2}` : ""}. API key saved to ~/.easybitsrc`);
511
+ else console.log(t(`Logged in${email2 ? ` as ${email2}` : ""}. API key saved to ~/.easybitsrc`, `Sesi\xF3n iniciada${email2 ? ` como ${email2}` : ""}. API key guardada en ~/.easybitsrc`));
422
512
  return;
423
513
  }
424
514
  const session = await oauthLogin(ctx, { openBrowser: !bool(ctx, "no-browser") });
425
515
  const email = await fetchEmail(session.accessToken);
426
516
  if (ctx.json) process.stdout.write(JSON.stringify({ event: "logged_in", email, method: "oauth" }) + "\n");
427
- else console.log(`Logged in${email ? ` as ${email}` : ""}. Session saved to ~/.easybitsrc`);
517
+ else console.log(t(`Logged in${email ? ` as ${email}` : ""}. Session saved to ~/.easybitsrc`, `Sesi\xF3n iniciada${email ? ` como ${email}` : ""}. Sesi\xF3n guardada en ~/.easybitsrc`));
428
518
  }
429
519
  }
430
520
  };
@@ -440,7 +530,7 @@ var logout = {
440
530
  async run(ctx) {
441
531
  const { oauth: _o, apiKey: _k, ...rest } = readRc();
442
532
  writeRc(rest);
443
- emit(ctx, { ok: true }, () => console.log("Logged out. Removed credentials from ~/.easybitsrc"));
533
+ emit(ctx, { ok: true }, () => console.log(t("Logged out. Removed credentials from ~/.easybitsrc", "Sesi\xF3n cerrada. Se quitaron las credenciales de ~/.easybitsrc")));
444
534
  }
445
535
  }
446
536
  };
@@ -459,12 +549,17 @@ var usage = {
459
549
  emit(ctx, u, () => {
460
550
  console.log(`Plan: ${u.plan}`);
461
551
  console.log(
462
- `Storage: ${fmtBytes(u.storage.usedBytes)} of ${fmtBytes(u.storage.maxBytes)} (${u.storage.percentUsed}%)`
552
+ t(`Storage: ${fmtBytes(u.storage.usedBytes)} of ${fmtBytes(u.storage.maxBytes)} (${u.storage.percentUsed}%)`, `Espacio: ${fmtBytes(u.storage.usedBytes)} de ${fmtBytes(u.storage.maxBytes)} (${u.storage.percentUsed}%)`)
463
553
  );
464
554
  console.log(
465
- `Files: ${u.counts.files} (trash: ${u.counts.deletedFiles})
555
+ t(
556
+ `Files: ${u.counts.files} (trash: ${u.counts.deletedFiles})
466
557
  Websites: ${u.counts.websites}
558
+ Webhooks: ${u.counts.webhooks}`,
559
+ `Archivos: ${u.counts.files} (papelera: ${u.counts.deletedFiles})
560
+ Sitios: ${u.counts.websites}
467
561
  Webhooks: ${u.counts.webhooks}`
562
+ )
468
563
  );
469
564
  });
470
565
  }
@@ -490,7 +585,7 @@ var websites = {
490
585
  () => table(
491
586
  items.map((w) => ({ ...w, size: fmtBytes(w.totalSize), createdAt: fmtDate(w.createdAt) })),
492
587
  [["id", "ID"], ["name", "NAME"], ["status", "STATUS"], ["fileCount", "FILES"], ["size", "SIZE"], ["url", "URL"]],
493
- "No websites yet."
588
+ t("No websites yet.", "Todav\xEDa no hay sitios.")
494
589
  )
495
590
  );
496
591
  }
@@ -513,8 +608,8 @@ var providers = {
513
608
  note: "Use the Developer Dashboard to add custom providers."
514
609
  };
515
610
  emit(ctx, data, () => {
516
- console.log("Default provider: Tigris (platform)");
517
- console.log("Use the Developer Dashboard to add custom providers.");
611
+ console.log(t("Default provider: Tigris (platform)", "Proveedor por default: Tigris (plataforma)"));
612
+ console.log(t("Use the Developer Dashboard to add custom providers.", "Agrega proveedores propios en el Developer Dashboard."));
518
613
  });
519
614
  }
520
615
  }
@@ -569,7 +664,7 @@ var whoami = {
569
664
  const source = process.env.EASYBITS_API_KEY ? "env EASYBITS_API_KEY" : ctx.token ? "--token" : readRc().oauth?.accessToken ? "browser session (~/.easybitsrc)" : readRc().apiKey ? "API key (~/.easybitsrc)" : void 0;
570
665
  if (!source) throw notLoggedIn();
571
666
  const email = await fetchEmail(await resolveCredential(ctx));
572
- emit(ctx, { email, source, baseUrl: await resolveBaseUrl3() }, () => console.log(`${email ?? "(unknown email)"} via ${source}`));
667
+ emit(ctx, { email, source, baseUrl: await resolveBaseUrl3() }, () => console.log(`${email ?? t("(unknown email)", "(correo desconocido)")} ${t("via", "por")} ${source}`));
573
668
  }
574
669
  }
575
670
  };
@@ -600,21 +695,21 @@ async function ask(question) {
600
695
  }
601
696
  function requireYesIfHeadless(ctx) {
602
697
  if (bool(ctx, "yes") || interactive(ctx)) return;
603
- throw new CliError("--yes required when not running interactively.", EXIT.USAGE, `Run: ${sameCommand()} --yes`, "usage");
698
+ throw new CliError(t("--yes required when not running interactively.", "Sin terminal hace falta --yes."), EXIT.USAGE, `${t("Run", "Corre")}: ${sameCommand()} --yes`, "usage");
604
699
  }
605
700
  async function confirm(ctx, action, opts = {}) {
606
701
  if (bool(ctx, "yes")) return;
607
702
  requireYesIfHeadless(ctx);
608
703
  if (opts.typeName) {
609
704
  process.stderr.write(`${action}
610
- This cannot be undone.
705
+ ${t("This cannot be undone.", "No se puede deshacer.")}
611
706
  `);
612
- const answer2 = await ask(`Type "${opts.typeName}" to confirm: `);
613
- if (answer2 !== opts.typeName) throw new CliError("Aborted: the name did not match.", EXIT.USAGE, void 0, "aborted");
707
+ const answer2 = await ask(t(`Type "${opts.typeName}" to confirm: `, `Teclea "${opts.typeName}" para confirmar: `));
708
+ if (answer2 !== opts.typeName) throw new CliError(t("Aborted: the name did not match.", "Cancelado: el nombre no coincide."), EXIT.USAGE, void 0, "aborted");
614
709
  return;
615
710
  }
616
- const answer = await ask(`${action} [y/N] `);
617
- if (!/^y(es)?$/i.test(answer)) throw new CliError("Aborted.", EXIT.USAGE, void 0, "aborted");
711
+ const answer = await ask(`${action} ${t("[y/N]", "[s/N]")} `);
712
+ if (!/^(y(es)?|s[ií]?)$/i.test(answer)) throw new CliError(t("Aborted.", "Cancelado."), EXIT.USAGE, void 0, "aborted");
618
713
  }
619
714
 
620
715
  // src/commands/files.ts
@@ -657,7 +752,7 @@ var files = {
657
752
  () => table(
658
753
  data.items.map((f) => ({ ...f, size: fmtBytes(f.size) })),
659
754
  [["name", "NAME"], ["size", "SIZE"], ["status", "STATUS"], ["id", "ID"]],
660
- "No files found"
755
+ t("No files found", "No hay archivos")
661
756
  )
662
757
  );
663
758
  }
@@ -668,7 +763,7 @@ var files = {
668
763
  examples: ["easybits files upload ./report.pdf", "easybits files upload ./logo.png --json"],
669
764
  async run(ctx) {
670
765
  const path = need(ctx, 0, "path", this.usage);
671
- if (!existsSync2(path)) throw usageError(`File not found: ${path}`, this.usage);
766
+ if (!existsSync2(path)) throw usageError(t(`File not found: ${path}`, `No existe el archivo: ${path}`), this.usage);
672
767
  const size = statSync(path).size;
673
768
  const contentType = MIME[extname(path).slice(1).toLowerCase()] || "application/octet-stream";
674
769
  const eb = await getClient(ctx);
@@ -678,8 +773,8 @@ var files = {
678
773
  body: readFileSync3(path),
679
774
  headers: { "Content-Type": contentType }
680
775
  });
681
- if (!res.ok) throw new CliError(`Upload failed: storage answered ${res.status}`, 1, void 0, "upload_failed", res.status);
682
- emit(ctx, data.file, () => console.log(`Uploaded: ${data.file.id}`));
776
+ 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);
777
+ emit(ctx, data.file, () => console.log(`${t("Uploaded", "Subido")}: ${data.file.id}`));
683
778
  }
684
779
  },
685
780
  rm: {
@@ -691,10 +786,10 @@ var files = {
691
786
  async run(ctx) {
692
787
  const id = need(ctx, 0, "file-id", this.usage);
693
788
  requireYesIfHeadless(ctx);
694
- await confirm(ctx, `Delete file ${id}? (recoverable from trash for 7 days)`);
789
+ await confirm(ctx, t(`Delete file ${id}? (recoverable from trash for 7 days)`, `\xBFBorrar el archivo ${id}? (se recupera de la papelera por 7 d\xEDas)`));
695
790
  const eb = await getClient(ctx);
696
791
  const r = await eb.deleteFile(id);
697
- emit(ctx, r, () => console.log("Deleted"));
792
+ emit(ctx, r, () => console.log(t("Deleted", "Borrado")));
698
793
  }
699
794
  }
700
795
  }
@@ -719,7 +814,7 @@ var init = {
719
814
  const appDir = str(ctx, "app-dir") ?? "/srv/app";
720
815
  const port = str(ctx, "port") ?? "3000";
721
816
  if (!existsSync3("package.json")) {
722
- throw usageError("No package.json here. Run this at the root of your app's repo.", this.usage);
817
+ throw usageError(t("No package.json here. Run this at the root of your app's repo.", "Aqu\xED no hay package.json. C\xF3rrelo en la ra\xEDz del repo de tu app."), this.usage);
723
818
  }
724
819
  const workflow = `# Despliegue a EasyBits en cada push a main.
725
820
  #
@@ -857,7 +952,7 @@ jobs:
857
952
  writeFileSync2(".github/workflows/easybits-deploy.yml", workflow);
858
953
  writeFileSync2(".github/scripts/easybits-deploy.mjs", script);
859
954
  const written = [".github/workflows/easybits-deploy.yml", ".github/scripts/easybits-deploy.mjs"];
860
- emit(ctx, { written, appDir, port: Number(port) }, () => console.log(`Done. Wrote:
955
+ emit(ctx, { written, appDir, port: Number(port) }, () => console.log(t(`Done. Wrote:
861
956
  .github/workflows/easybits-deploy.yml
862
957
  .github/scripts/easybits-deploy.mjs
863
958
 
@@ -887,7 +982,37 @@ One-time setup left:
887
982
 
888
983
  easybits machines secrets set SANDBOX_ID --dotenv .env.production
889
984
 
890
- From then on, every push to main deploys.`));
985
+ From then on, every push to main deploys.`, `Listo. Escrib\xED:
986
+ .github/workflows/easybits-deploy.yml
987
+ .github/scripts/easybits-deploy.mjs
988
+
989
+ Falta configurar una sola vez:
990
+
991
+ 1. Crea la m\xE1quina (si todav\xEDa no tienes una):
992
+
993
+ curl -X POST https://www.easybits.cloud/api/v2/machines/launch \\
994
+ -H "Authorization: Bearer $EASYBITS_API_KEY" \\
995
+ -H "Content-Type: application/json" \\
996
+ -d '{"repo":"https://github.com/TU/REPO.git","branch":"main",
997
+ "tier":"micro","template":"node","appDir":"${appDir}","port":${port}}'
998
+
999
+ Repo privado: deja la URL limpia y pasa el token aparte,
1000
+ "repoToken":"github_pat_\u2026"
1001
+ (un token dentro de la URL se rechaza).
1002
+
1003
+ \xBFTu build cabe en la m\xE1quina? Entonces no necesitas este workflow:
1004
+ POST /machines/SANDBOX_ID/push-deploy te da un webhook de GitHub
1005
+ y cada push se despliega solo. Docs: https://www.easybits.cloud/docs
1006
+
1007
+ 2. Agrega estos secretos del repo (Settings \u2192 Secrets and variables \u2192 Actions):
1008
+ EASYBITS_API_KEY tu key
1009
+ EASYBITS_SANDBOX_ID el id que devolvi\xF3 el paso 1
1010
+
1011
+ 3. Si tu app usa variables de entorno secretas:
1012
+
1013
+ easybits machines secrets set SANDBOX_ID --dotenv .env.production
1014
+
1015
+ Desde ah\xED, cada push a main despliega.`)));
891
1016
  }
892
1017
  }
893
1018
  };
@@ -918,7 +1043,7 @@ var sshKey = {
918
1043
  execFileSync("ssh-keygen", ["-t", "ed25519", "-N", "", "-f", key, "-C", "easybits"], {
919
1044
  stdio: ["ignore", "ignore", "inherit"]
920
1045
  });
921
- console.error(`easybits: key created at ${key}`);
1046
+ console.error(t(`easybits: key created at ${key}`, `easybits: llave creada en ${key}`));
922
1047
  }
923
1048
  const publicKey = readFileSync4(pub, "utf8").trim();
924
1049
  if (ctx.json) process.stdout.write(JSON.stringify({ publicKey, path: pub }, null, 2) + "\n");
@@ -941,9 +1066,9 @@ var sshProxy = {
941
1066
  const eb = await getClient(ctx);
942
1067
  if (!sandboxId.startsWith("sb_")) {
943
1068
  const hits = (await eb.sandboxes.list()).filter((s) => s.name === sandboxId);
944
- if (hits.length === 0) throw usageError(`No sandbox named "${sandboxId}".`, this.usage);
1069
+ if (hits.length === 0) throw usageError(t(`No sandbox named "${sandboxId}".`, `No hay un sandbox llamado "${sandboxId}".`), this.usage);
945
1070
  if (hits.length > 1)
946
- throw usageError(`"${sandboxId}" is ambiguous: ${hits.length} sandboxes use it; pass the sandboxId.`, this.usage);
1071
+ throw usageError(t(`"${sandboxId}" is ambiguous: ${hits.length} sandboxes use it; pass the sandboxId.`, `"${sandboxId}" es ambiguo: lo usan ${hits.length} sandboxes; pasa el sandboxId.`), this.usage);
947
1072
  sandboxId = hits[0].sandboxId;
948
1073
  }
949
1074
  const sb = await eb.sandboxes.get(sandboxId);
@@ -952,10 +1077,10 @@ var sshProxy = {
952
1077
  ws.binaryType = "arraybuffer";
953
1078
  await new Promise((resolve3, reject) => {
954
1079
  ws.onopen = () => resolve3();
955
- ws.onerror = () => reject(new CliError("easybits ssh-proxy: could not open the tunnel"));
1080
+ ws.onerror = () => reject(new CliError(t("easybits ssh-proxy: could not open the tunnel", "easybits ssh-proxy: no se pudo abrir el t\xFAnel")));
956
1081
  });
957
1082
  ws.onerror = () => {
958
- console.error("easybits ssh-proxy: tunnel error");
1083
+ console.error(t("easybits ssh-proxy: tunnel error", "easybits ssh-proxy: error del t\xFAnel"));
959
1084
  process.exit(1);
960
1085
  };
961
1086
  ws.onclose = () => process.exit(0);
@@ -991,13 +1116,13 @@ function sandboxRecord(s) {
991
1116
  }
992
1117
  function printSandbox(s) {
993
1118
  console.log(`ID: ${s.sandboxId}`);
994
- if (s.name) console.log(`Name: ${s.name}`);
1119
+ if (s.name) console.log(`${t("Name: ", "Nombre: ")} ${s.name}`);
995
1120
  console.log(`Template: ${s.template}`);
996
- console.log(`Status: ${s.status}`);
997
- if (s.activity) console.log(`Activity: ${s.activity} (busy: exec/suspend/destroy answer 409 SandboxBusy until it ends)`);
998
- console.log(`Created: ${fmtDate(s.createdAt)}`);
999
- console.log(`Expires: ${s.persistent ? "never (permanent)" : fmtDate(s.expiresAt)}`);
1000
- if (s.tier) console.log(`Tier: ${s.tier}${s.monthlyMxn != null ? ` ($${s.monthlyMxn} MXN/month)` : ""}`);
1121
+ console.log(`${t("Status: ", "Estado: ")} ${s.status}`);
1122
+ if (s.activity) console.log(t(`Activity: ${s.activity} (busy: exec/suspend/destroy answer 409 SandboxBusy until it ends)`, `Actividad: ${s.activity} (ocupado: exec/suspend/destroy contestan 409 SandboxBusy hasta que termine)`));
1123
+ console.log(`${t("Created: ", "Creado: ")} ${fmtDate(s.createdAt)}`);
1124
+ console.log(`${t("Expires: ", "Vence: ")} ${s.persistent ? t("never (permanent)", "nunca (permanente)") : fmtDate(s.expiresAt)}`);
1125
+ if (s.tier) console.log(`Tier: ${s.tier}${s.monthlyMxn != null ? ` ($${s.monthlyMxn} MXN/${t("month", "mes")})` : ""}`);
1001
1126
  }
1002
1127
  async function sandbox(ctx, usage2) {
1003
1128
  const id = need(ctx, 0, "sandbox", usage2);
@@ -1026,7 +1151,7 @@ var sandboxes = {
1026
1151
  () => table(
1027
1152
  rows.map((r) => ({ ...r, expires: r.persistent ? "permanent" : fmtDate(r.expiresAt) })),
1028
1153
  [["sandboxId", "ID"], ["name", "NAME"], ["template", "TEMPLATE"], ["status", "STATUS"], ["expires", "EXPIRES"]],
1029
- "No sandboxes. Create one: easybits sandboxes create --template node"
1154
+ t("No sandboxes. Create one: easybits sandboxes create --template node", "No hay sandboxes. Crea uno: easybits sandboxes create --template node")
1030
1155
  )
1031
1156
  );
1032
1157
  }
@@ -1051,7 +1176,7 @@ var sandboxes = {
1051
1176
  ],
1052
1177
  async run(ctx) {
1053
1178
  const size = str(ctx, "size");
1054
- if (size && !["s", "m", "l", "xl"].includes(size)) throw usageError("--size must be s, m, l or xl.", this.usage);
1179
+ if (size && !["s", "m", "l", "xl"].includes(size)) throw usageError(t("--size must be s, m, l or xl.", "--size debe ser s, m, l o xl."), this.usage);
1055
1180
  const env = await envFrom(ctx, this.usage);
1056
1181
  const eb = await getClient(ctx);
1057
1182
  const sb = await eb.sandboxes.create({
@@ -1094,7 +1219,7 @@ var sandboxes = {
1094
1219
  async run(ctx) {
1095
1220
  need(ctx, 0, "sandbox", this.usage);
1096
1221
  const command = ctx.args.slice(1).join(" ");
1097
- if (!command) throw usageError("Missing <command>.", this.usage);
1222
+ if (!command) throw usageError(t("Missing <command>.", "Falta <command>."), this.usage);
1098
1223
  const env = await envFrom(ctx, this.usage);
1099
1224
  const sb = await sandbox(ctx, this.usage);
1100
1225
  const r = await sb.exec(command, {
@@ -1109,7 +1234,7 @@ var sandboxes = {
1109
1234
  }
1110
1235
  if (r.stdout) process.stdout.write(r.stdout);
1111
1236
  if (r.stderr) process.stderr.write(r.stderr);
1112
- if (r.truncated) console.error("easybits: output truncated");
1237
+ if (r.truncated) console.error(t("easybits: output truncated", "easybits: salida recortada"));
1113
1238
  process.exitCode = r.exitCode;
1114
1239
  }
1115
1240
  },
@@ -1170,7 +1295,7 @@ var sandboxes = {
1170
1295
  if (out) {
1171
1296
  const r = await sb.files.read(path, { encoding: "base64" });
1172
1297
  writeFileSync3(out, Buffer.from(r.content, "base64"));
1173
- emit(ctx, { path, out, size: r.size }, () => console.error(`Saved ${fmtBytes(r.size)} to ${out}`));
1298
+ emit(ctx, { path, out, size: r.size }, () => console.error(t(`Saved ${fmtBytes(r.size)} to ${out}`, `Guard\xE9 ${fmtBytes(r.size)} en ${out}`)));
1174
1299
  } else {
1175
1300
  const r = await sb.files.read(path);
1176
1301
  emit(ctx, r, () => process.stdout.write(r.content));
@@ -1181,14 +1306,14 @@ var sandboxes = {
1181
1306
  let buf;
1182
1307
  if (inline != null) buf = Buffer.from(inline, "utf8");
1183
1308
  else if (local) {
1184
- if (!existsSync5(local)) throw usageError(`File not found: ${local}`, FILES_USAGE);
1309
+ if (!existsSync5(local)) throw usageError(t(`File not found: ${local}`, `No existe el archivo: ${local}`), FILES_USAGE);
1185
1310
  buf = readFileSync5(local);
1186
1311
  } else if (!process.stdin.isTTY) buf = await readStdin();
1187
- else throw usageError("Give a local file, --content, or pipe data on stdin.", FILES_USAGE);
1312
+ else throw usageError(t("Give a local file, --content, or pipe data on stdin.", "Pasa un archivo local, --content, o datos por stdin."), FILES_USAGE);
1188
1313
  const r = await sb.files.write(path, buf.toString("base64"), { encoding: "base64" });
1189
- emit(ctx, { path, ...r }, () => console.log(`Wrote ${fmtBytes(r.bytes)} to ${path}`));
1314
+ emit(ctx, { path, ...r }, () => console.log(t(`Wrote ${fmtBytes(r.bytes)} to ${path}`, `Escrib\xED ${fmtBytes(r.bytes)} en ${path}`)));
1190
1315
  } else {
1191
- throw usageError(`Unknown files action "${action}".`, FILES_USAGE);
1316
+ throw usageError(t(`Unknown files action "${action}".`, `Acci\xF3n de files desconocida "${action}".`), FILES_USAGE);
1192
1317
  }
1193
1318
  }
1194
1319
  },
@@ -1198,7 +1323,7 @@ var sandboxes = {
1198
1323
  examples: ["easybits sandboxes suspend sb_abc123"],
1199
1324
  async run(ctx) {
1200
1325
  const r = await (await sandbox(ctx, this.usage)).suspend();
1201
- emit(ctx, r, () => console.log(`Suspended ${r.sandboxId ?? ctx.args[0]} (${r.status ?? "suspended"})`));
1326
+ emit(ctx, r, () => console.log(t(`Suspended ${r.sandboxId ?? ctx.args[0]} (${r.status ?? "suspended"})`, `Suspendido ${r.sandboxId ?? ctx.args[0]} (${r.status ?? "suspended"})`)));
1202
1327
  }
1203
1328
  },
1204
1329
  resume: {
@@ -1212,7 +1337,7 @@ var sandboxes = {
1212
1337
  async run(ctx) {
1213
1338
  const env = await envFrom(ctx, this.usage);
1214
1339
  const r = await (await sandbox(ctx, this.usage)).resume(Object.keys(env).length ? { env } : {});
1215
- emit(ctx, r, () => console.log(`Resumed ${r.sandboxId ?? ctx.args[0]} (${r.status ?? "running"})`));
1340
+ emit(ctx, r, () => console.log(t(`Resumed ${r.sandboxId ?? ctx.args[0]} (${r.status ?? "running"})`, `Despierto ${r.sandboxId ?? ctx.args[0]} (${r.status ?? "running"})`)));
1216
1341
  }
1217
1342
  },
1218
1343
  destroy: {
@@ -1227,11 +1352,11 @@ var sandboxes = {
1227
1352
  const sb = await sandbox(ctx, this.usage);
1228
1353
  await confirm(
1229
1354
  ctx,
1230
- sb.persistent ? `Destroy permanent sandbox ${sb.sandboxId}${sb.name ? ` (${sb.name})` : ""} and its disk?` : `Destroy sandbox ${sb.sandboxId}?`,
1355
+ sb.persistent ? t(`Destroy permanent sandbox ${sb.sandboxId}${sb.name ? ` (${sb.name})` : ""} and its disk?`, `\xBFDestruir el sandbox permanente ${sb.sandboxId}${sb.name ? ` (${sb.name})` : ""} y su disco?`) : t(`Destroy sandbox ${sb.sandboxId}?`, `\xBFDestruir el sandbox ${sb.sandboxId}?`),
1231
1356
  sb.persistent ? { typeName: sb.sandboxId } : {}
1232
1357
  );
1233
1358
  const r = await sb.destroy();
1234
- emit(ctx, { ...r, sandboxId: ctx.args[0] }, () => console.log(`Destroyed ${ctx.args[0]}`));
1359
+ emit(ctx, { ...r, sandboxId: ctx.args[0] }, () => console.log(t(`Destroyed ${ctx.args[0]}`, `Destruido ${ctx.args[0]}`)));
1235
1360
  }
1236
1361
  },
1237
1362
  snapshot: {
@@ -1270,7 +1395,7 @@ var machines = {
1270
1395
  () => table(
1271
1396
  rows,
1272
1397
  [["sandboxId", "ID"], ["name", "NAME"], ["tier", "TIER"], ["status", "STATUS"], ["monthlyMxn", "MXN/MONTH"]],
1273
- "No permanent machines. See: easybits docs hosting"
1398
+ t("No permanent machines. See: easybits docs hosting", "No hay m\xE1quinas permanentes. Mira: easybits docs hosting")
1274
1399
  )
1275
1400
  );
1276
1401
  }
@@ -1302,7 +1427,7 @@ var machines = {
1302
1427
  () => table(
1303
1428
  r.items.map((x) => ({ ...x, size: fmtBytes(x.sizeBytes), createdAt: fmtDate(x.createdAt) })),
1304
1429
  [["version", "VERSION"], ["releaseId", "ID"], ["status", "STATUS"], ["size", "SIZE"], ["createdAt", "CREATED"], ["message", "MESSAGE"]],
1305
- "No releases yet. Publish one: easybits machines deploy <machine>"
1430
+ t("No releases yet. Publish one: easybits machines deploy <machine>", "Todav\xEDa no hay releases. Publica uno: easybits deploy <m\xE1quina>")
1306
1431
  )
1307
1432
  );
1308
1433
  }
@@ -1331,7 +1456,7 @@ var machines = {
1331
1456
  const rel = need(ctx, 1, "release-id", this.usage);
1332
1457
  const eb = await getClient(ctx);
1333
1458
  const r = await eb.machines.rollback(id, rel);
1334
- emit(ctx, r, () => console.log(`Rolled back to v${r.version} (exit ${r.exitCode})`));
1459
+ emit(ctx, r, () => console.log(t(`Rolled back to v${r.version} (exit ${r.exitCode})`, `De vuelta en v${r.version} (salida ${r.exitCode})`)));
1335
1460
  if (r.exitCode !== 0) process.exitCode = 1;
1336
1461
  }
1337
1462
  },
@@ -1352,21 +1477,21 @@ var machines = {
1352
1477
  if (action === "ls" || action === "list") {
1353
1478
  const r = await eb.machines.secrets(id);
1354
1479
  emit(ctx, r, () => {
1355
- console.log(`Injected: ${r.secretNames.length ? r.secretNames.join(", ") : "(none)"}`);
1356
- console.log(`In vault: ${r.inVault.length ? r.inVault.join(", ") : "(none)"}`);
1480
+ console.log(`${t("Injected", "Inyectados")}: ${r.secretNames.length ? r.secretNames.join(", ") : t("(none)", "(ninguno)")}`);
1481
+ console.log(`${t("In vault", "En el vault")}: ${r.inVault.length ? r.inVault.join(", ") : t("(none)", "(ninguno)")}`);
1357
1482
  });
1358
1483
  } else if (action === "set") {
1359
1484
  const file = str(ctx, "dotenv");
1360
1485
  const values = { ...file ? await readEnvFile(file, SECRETS_USAGE) : {}, ...pairs(ctx.args.slice(2), SECRETS_USAGE) };
1361
- if (!Object.keys(values).length) throw usageError("Give --dotenv <path> (or - for stdin).", SECRETS_USAGE);
1486
+ if (!Object.keys(values).length) throw usageError(t("Give --dotenv <path> (or - for stdin).", "Pasa --dotenv <ruta> (o - para stdin)."), SECRETS_USAGE);
1362
1487
  const r = await eb.machines.setSecrets(id, values);
1363
- emit(ctx, r, () => console.log(`Set ${Object.keys(values).join(", ")}. Injected now: ${r.secretNames.join(", ")}`));
1488
+ emit(ctx, r, () => console.log(t(`Set ${Object.keys(values).join(", ")}. Injected now: ${r.secretNames.join(", ")}`, `Guardados ${Object.keys(values).join(", ")}. Inyectados ahora: ${r.secretNames.join(", ")}`)));
1364
1489
  } else if (action === "unset") {
1365
1490
  const name = need(ctx, 2, "NAME", SECRETS_USAGE);
1366
1491
  const r = await eb.machines.unsetSecret(id, name);
1367
- emit(ctx, r, () => console.log(`Unset ${name} (value kept in the vault)`));
1492
+ emit(ctx, r, () => console.log(t(`Unset ${name} (value kept in the vault)`, `Quitado ${name} (el valor sigue en el vault)`)));
1368
1493
  } else {
1369
- throw usageError(`Unknown secrets action "${action}".`, SECRETS_USAGE);
1494
+ throw usageError(t(`Unknown secrets action "${action}".`, `Acci\xF3n de secrets desconocida "${action}".`), SECRETS_USAGE);
1370
1495
  }
1371
1496
  }
1372
1497
  }
@@ -1392,7 +1517,7 @@ var domains = {
1392
1517
  examples: ["easybits domains ls sb_abc123"],
1393
1518
  async run(ctx) {
1394
1519
  const items = await (await sandbox2(ctx, this.usage)).listDomains();
1395
- emit(ctx, items, () => table(items, [["domain", "DOMAIN"], ["port", "PORT"]], "No custom domains."));
1520
+ emit(ctx, items, () => table(items, [["domain", "DOMAIN"], ["port", "PORT"]], t("No custom domains.", "Sin dominios propios.")));
1396
1521
  }
1397
1522
  },
1398
1523
  add: {
@@ -1403,12 +1528,12 @@ var domains = {
1403
1528
  async run(ctx) {
1404
1529
  const domain = need(ctx, 1, "domain", this.usage);
1405
1530
  const port = int(ctx, "port", this.usage);
1406
- if (port == null) throw usageError("Missing --port.", this.usage);
1531
+ if (port == null) throw usageError(t("Missing --port.", "Falta --port."), this.usage);
1407
1532
  const r = await (await sandbox2(ctx, this.usage)).addDomain(domain, port);
1408
1533
  emit(ctx, r, () => {
1409
- console.log(`Attached ${r.domain} \u2192 port ${r.port}`);
1410
- console.log(`Create this DNS record: ${JSON.stringify(r.dns)}`);
1411
- console.log(`Then run: easybits domains verify ${ctx.args[0]} ${r.domain}`);
1534
+ console.log(t(`Attached ${r.domain} \u2192 port ${r.port}`, `Ligado ${r.domain} \u2192 puerto ${r.port}`));
1535
+ console.log(t(`Create this DNS record: ${JSON.stringify(r.dns)}`, `Crea este registro DNS: ${JSON.stringify(r.dns)}`));
1536
+ console.log(t(`Then run: easybits domains verify ${ctx.args[0]} ${r.domain}`, `Luego corre: easybits domains verify ${ctx.args[0]} ${r.domain}`));
1412
1537
  });
1413
1538
  }
1414
1539
  },
@@ -1420,8 +1545,8 @@ var domains = {
1420
1545
  const domain = need(ctx, 1, "domain", this.usage);
1421
1546
  const r = await (await sandbox2(ctx, this.usage)).verifyDomain(domain);
1422
1547
  emit(ctx, r, () => {
1423
- console.log(`${r.domain}: ${r.ready ? "ready" : "not ready yet"}`);
1424
- console.log(`DNS resolved: ${r.dns.resolved} HTTPS ok: ${r.https.ok}`);
1548
+ console.log(`${r.domain}: ${r.ready ? t("ready", "listo") : t("not ready yet", "todav\xEDa no est\xE1 listo")}`);
1549
+ console.log(t(`DNS resolved: ${r.dns.resolved} HTTPS ok: ${r.https.ok}`, `DNS resuelve: ${r.dns.resolved} HTTPS ok: ${r.https.ok}`));
1425
1550
  if (r.hint) console.log(r.hint);
1426
1551
  });
1427
1552
  if (!r.ready) process.exitCode = 1;
@@ -1437,9 +1562,9 @@ var domains = {
1437
1562
  need(ctx, 0, "sandbox", this.usage);
1438
1563
  const domain = need(ctx, 1, "domain", this.usage);
1439
1564
  requireYesIfHeadless(ctx);
1440
- await confirm(ctx, `Detach ${domain} from ${ctx.args[0]}? It stops serving right away.`);
1565
+ await confirm(ctx, t(`Detach ${domain} from ${ctx.args[0]}? It stops serving right away.`, `\xBFDesligar ${domain} de ${ctx.args[0]}? Deja de servir de inmediato.`));
1441
1566
  const r = await (await sandbox2(ctx, this.usage)).removeDomain(domain);
1442
- emit(ctx, r, () => console.log(`Removed ${domain}`));
1567
+ emit(ctx, r, () => console.log(t(`Removed ${domain}`, `Quitado ${domain}`)));
1443
1568
  }
1444
1569
  }
1445
1570
  }
@@ -1503,7 +1628,8 @@ var LS_HINT = {
1503
1628
  sandbox: "easybits sandboxes ls",
1504
1629
  db: "easybits db ls"
1505
1630
  };
1506
- var NOUN = { agent: "agent", sandbox: "sandbox or machine", db: "database" };
1631
+ var NOUN = () => ({ agent: t("agent", "agente"), sandbox: t("sandbox or machine", "sandbox o m\xE1quina"), db: t("database", "base de datos") });
1632
+ var NOUNS = () => ({ agent: t("agents", "agentes"), sandbox: t("sandboxes", "sandboxes"), db: t("databases", "bases de datos") });
1507
1633
  var cache = /* @__PURE__ */ new Map();
1508
1634
  function listRefs(ctx, kind) {
1509
1635
  let p = cache.get(kind);
@@ -1524,15 +1650,15 @@ async function resolveRef(ctx, kind, ref) {
1524
1650
  const r = matchRef(kind, ref, await listRefs(ctx, kind));
1525
1651
  if (r.ok) return r.id;
1526
1652
  if (r.reason === "not_found") {
1527
- throw new CliError(`No ${NOUN[kind]} with id or name "${ref}".`, EXIT.API, `List them with: ${LS_HINT[kind]}`, "not_found", 404);
1653
+ 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);
1528
1654
  }
1529
1655
  const shown = r.matches.slice(0, 10).map((m) => ` ${m.id}${m.status ? ` (${m.status})` : ""}`);
1530
- if (r.matches.length > 10) shown.push(` \u2026and ${r.matches.length - 10} more`);
1656
+ if (r.matches.length > 10) shown.push(t(` \u2026and ${r.matches.length - 10} more`, ` \u2026y ${r.matches.length - 10} m\xE1s`));
1531
1657
  throw new CliError(
1532
- `"${ref}" matches ${r.matches.length} ${kind === "db" ? "databases" : `${kind}s`}. Use the id:`,
1658
+ t(`"${ref}" matches ${r.matches.length} ${NOUNS()[kind]}. Use the id:`, `"${ref}" coincide con ${r.matches.length} ${NOUNS()[kind]}. Usa el id:`),
1533
1659
  EXIT.USAGE,
1534
1660
  `${shown.join("\n")}
1535
- List them with: ${LS_HINT[kind]}`,
1661
+ ${t("List them with", "Enl\xEDstalos con")}: ${LS_HINT[kind]}`,
1536
1662
  "ambiguous"
1537
1663
  );
1538
1664
  }
@@ -1546,7 +1672,7 @@ async function resolveRefs(ctx, key) {
1546
1672
  // src/commands/db.ts
1547
1673
  async function findDb(ctx, id) {
1548
1674
  const hit = (await listRefs(ctx, "db")).find((d) => d.id === id);
1549
- if (!hit) throw new CliError(`No database "${id}".`, 1, "List them with: easybits db ls", "not_found", 404);
1675
+ if (!hit) throw new CliError(t(`No database "${id}".`, `No existe la base "${id}".`), 1, t("List them with: easybits db ls", "Enl\xEDstalas con: easybits db ls"), "not_found", 404);
1550
1676
  return { id, name: hit.name ?? id };
1551
1677
  }
1552
1678
  var db = {
@@ -1570,7 +1696,7 @@ var db = {
1570
1696
  () => table(
1571
1697
  items.map((d) => ({ ...d, createdAt: fmtDate(d.createdAt) })),
1572
1698
  [["id", "ID"], ["name", "NAME"], ["createdAt", "CREATED"], ["description", "DESCRIPTION"]],
1573
- "No databases. Create one: easybits db create <name>"
1699
+ t("No databases. Create one: easybits db create <name>", "No hay bases. Crea una: easybits db create <nombre>")
1574
1700
  )
1575
1701
  );
1576
1702
  }
@@ -1584,7 +1710,7 @@ var db = {
1584
1710
  const name = need(ctx, 0, "name", this.usage);
1585
1711
  const eb = await getClient(ctx);
1586
1712
  const d = await eb.createDatabase({ name, description: str(ctx, "description") });
1587
- emit(ctx, d, () => console.log(`Created ${d.name} (${d.id})`));
1713
+ emit(ctx, d, () => console.log(t(`Created ${d.name} (${d.id})`, `Creada ${d.name} (${d.id})`)));
1588
1714
  }
1589
1715
  },
1590
1716
  rm: {
@@ -1598,9 +1724,9 @@ var db = {
1598
1724
  requireYesIfHeadless(ctx);
1599
1725
  const eb = await getClient(ctx);
1600
1726
  const hit = await findDb(ctx, ref);
1601
- await confirm(ctx, `Delete database ${hit.name} (${hit.id}) and all its data?`, { typeName: hit.name });
1727
+ await confirm(ctx, t(`Delete database ${hit.name} (${hit.id}) and all its data?`, `\xBFBorrar la base ${hit.name} (${hit.id}) con todos sus datos?`), { typeName: hit.name });
1602
1728
  const r = await eb.deleteDatabase(hit.id);
1603
- emit(ctx, { ...r, id: hit.id, name: hit.name }, () => console.log(`Deleted ${hit.name} (${hit.id})`));
1729
+ emit(ctx, { ...r, id: hit.id, name: hit.name }, () => console.log(t(`Deleted ${hit.name} (${hit.id})`, `Borrada ${hit.name} (${hit.id})`)));
1604
1730
  }
1605
1731
  },
1606
1732
  tables: {
@@ -1628,10 +1754,10 @@ var db = {
1628
1754
  ctx,
1629
1755
  tables,
1630
1756
  () => table(
1631
- tables.map((t) => ({
1632
- name: t.name,
1633
- rows: t.rows,
1634
- columns: t.columns.map((c) => `${c.name}${c.type ? ` ${c.type}` : ""}${c.pk ? " PK" : ""}`).join(", ")
1757
+ tables.map((t2) => ({
1758
+ name: t2.name,
1759
+ rows: t2.rows,
1760
+ columns: t2.columns.map((c) => `${c.name}${c.type ? ` ${c.type}` : ""}${c.pk ? " PK" : ""}`).join(", ")
1635
1761
  })),
1636
1762
  [["name", "TABLE"], ["rows", "ROWS"], ["columns", "COLUMNS"]],
1637
1763
  `No tables in ${hit.name}. Create one: easybits db query ${hit.name} 'CREATE TABLE \u2026'`
@@ -1664,7 +1790,12 @@ var db = {
1664
1790
  "(0 rows)"
1665
1791
  );
1666
1792
  } else {
1667
- console.log(`OK, ${r.affected_row_count} row(s) affected${r.last_insert_rowid ? `, last id ${r.last_insert_rowid}` : ""}`);
1793
+ console.log(
1794
+ t(
1795
+ `OK, ${r.affected_row_count} row(s) affected${r.last_insert_rowid ? `, last id ${r.last_insert_rowid}` : ""}`,
1796
+ `OK, ${r.affected_row_count} fila(s) afectadas${r.last_insert_rowid ? `, \xFAltimo id ${r.last_insert_rowid}` : ""}`
1797
+ )
1798
+ );
1668
1799
  }
1669
1800
  });
1670
1801
  }
@@ -1719,15 +1850,15 @@ async function agentBox(eb, id) {
1719
1850
  }
1720
1851
  function skillFrontmatter(md) {
1721
1852
  const m = /^---\r?\n([\s\S]*?)\r?\n---/.exec(md);
1722
- if (!m) return { error: "SKILL.md has no frontmatter (--- name / description ---)" };
1853
+ if (!m) return { error: t("SKILL.md has no frontmatter (--- name / description ---)", "SKILL.md no tiene frontmatter (--- name / description ---)") };
1723
1854
  const out = {};
1724
1855
  for (const key of ["name", "description"]) {
1725
1856
  const line2 = m[1].split(/\r?\n/).find((l) => l.startsWith(`${key}:`));
1726
- if (!line2) return { ...out, error: `frontmatter has no \`${key}:\`` };
1857
+ if (!line2) return { ...out, error: t(`frontmatter has no \`${key}:\``, `el frontmatter no tiene \`${key}:\``) };
1727
1858
  const value = line2.slice(key.length + 1).trim();
1728
- if (!value) return { ...out, error: `\`${key}:\` is empty` };
1859
+ if (!value) return { ...out, error: t(`\`${key}:\` is empty`, `\`${key}:\` est\xE1 vac\xEDo`) };
1729
1860
  const quoted = /^(["']).*\1$/.test(value);
1730
- if (!quoted && value.includes(": ")) return { ...out, error: `\`${key}:\` has an unquoted ": " \u2014 quote the value or the skill is dropped` };
1861
+ if (!quoted && value.includes(": ")) return { ...out, error: t(`\`${key}:\` has an unquoted ": " \u2014 quote the value or the skill is dropped`, `\`${key}:\` lleva un ": " sin comillas \u2014 entrecomilla el valor o la skill se descarta`) };
1731
1862
  out[key] = quoted ? value.slice(1, -1) : value;
1732
1863
  }
1733
1864
  return out;
@@ -1780,10 +1911,10 @@ async function soft(fn) {
1780
1911
  }
1781
1912
  function readLocalText(path, usage2) {
1782
1913
  if (path === "-") {
1783
- if (process.stdin.isTTY) throw usageError("Expected the content on stdin.", usage2);
1914
+ if (process.stdin.isTTY) throw usageError(t("Expected the content on stdin.", "Se esperaba el contenido por stdin."), usage2);
1784
1915
  return readStdin().then((b) => b.toString("utf8"));
1785
1916
  }
1786
- if (!existsSync6(path)) throw usageError(`File not found: ${path}`, usage2);
1917
+ if (!existsSync6(path)) throw usageError(t(`File not found: ${path}`, `No existe el archivo: ${path}`), usage2);
1787
1918
  return readFileSync6(path, "utf8");
1788
1919
  }
1789
1920
  function parseMcpFile(text, usage2) {
@@ -1791,10 +1922,10 @@ function parseMcpFile(text, usage2) {
1791
1922
  try {
1792
1923
  j = JSON.parse(text);
1793
1924
  } catch (e) {
1794
- throw usageError(`servers file is not JSON: ${e.message}`, usage2);
1925
+ throw usageError(t(`servers file is not JSON: ${e.message}`, `el archivo de servers no es JSON: ${e.message}`), usage2);
1795
1926
  }
1796
1927
  const list2 = Array.isArray(j) ? j : j?.servers ?? j?.mcpServers;
1797
- if (!Array.isArray(list2)) throw usageError("Expected an array of MCP servers, or { servers: [...] }.", usage2);
1928
+ if (!Array.isArray(list2)) throw usageError(t("Expected an array of MCP servers, or { servers: [...] }.", "Se esperaba un arreglo de servidores MCP, o { servers: [...] }."), usage2);
1798
1929
  return list2;
1799
1930
  }
1800
1931
  var hasMasked = (servers) => servers.some((s) => [...s.env ?? [], ...s.headers ?? []].some((x) => /…\*\*\*$|^\*\*\*$/.test(x.value)));
@@ -1832,7 +1963,7 @@ var get = {
1832
1963
  const id = need(ctx, 0, "agent", this.usage);
1833
1964
  const fields = str(ctx, "fields")?.split(",").map((f) => f.trim()).filter(Boolean);
1834
1965
  const bad = fields?.filter((f) => !GET_FIELDS.includes(f));
1835
- if (bad?.length) throw usageError(`Unknown field(s): ${bad.join(", ")}. Valid: ${GET_FIELDS.join(", ")}`, this.usage);
1966
+ if (bad?.length) throw usageError(t(`Unknown field(s): ${bad.join(", ")}. Valid: ${GET_FIELDS.join(", ")}`, `Campo(s) desconocido(s): ${bad.join(", ")}. V\xE1lidos: ${GET_FIELDS.join(", ")}`), this.usage);
1836
1967
  const promptOut = str(ctx, "prompt-out");
1837
1968
  const want = (f) => !fields || fields.includes(f);
1838
1969
  const eb = await getClient(ctx);
@@ -1841,12 +1972,12 @@ var get = {
1841
1972
  const notes = [];
1842
1973
  const wantsConfig = ["systemPrompt", "systemPromptMode", "mcpServers", "files", "skills"].some(want) || !!promptOut;
1843
1974
  let machine = wantsConfig && a.status !== "lost";
1844
- if (wantsConfig && !machine) notes.push(`its sandbox is lost: prompt, MCP, skills and files are unavailable (easybits agents doctor ${id})`);
1975
+ if (wantsConfig && !machine) notes.push(t(`its sandbox is lost: prompt, MCP, skills and files are unavailable (easybits agents doctor ${id})`, `su sandbox se perdi\xF3: prompt, MCP, skills y archivos no est\xE1n disponibles (easybits agents doctor ${id})`));
1845
1976
  if (machine) {
1846
1977
  const p = await soft(() => getPrompt(ctx, id));
1847
1978
  if (!p.ok && /agente_sin_maquina|no expone/.test(p.error)) {
1848
1979
  machine = false;
1849
- notes.push(`template "${a.template}" has no machine: prompt, MCP, skills and files apply only to ghosty-lite and goose`);
1980
+ notes.push(t(`template "${a.template}" has no machine: prompt, MCP, skills and files apply only to ghosty-lite and goose`, `el template "${a.template}" no tiene m\xE1quina: prompt, MCP, skills y archivos s\xF3lo aplican a ghosty-lite y goose`));
1850
1981
  } else if (p.ok) {
1851
1982
  rec.systemPromptMode = p.value.systemPromptMode;
1852
1983
  if (promptOut) {
@@ -1880,12 +2011,12 @@ var get = {
1880
2011
  printAgent(a);
1881
2012
  if (rec.systemPromptMode) console.log(`Prompt: ${rec.systemPromptMode}${promptOut ? ` \u2192 ${resolve(promptOut)}` : ""}`);
1882
2013
  const mcp3 = rec.mcpServers;
1883
- if (mcp3) console.log(`MCP: ${mcp3.length ? mcp3.map((s) => s.name).join(", ") : "none"}`);
2014
+ if (mcp3) console.log(`MCP: ${mcp3.length ? mcp3.map((s) => s.name).join(", ") : t("none", "ninguno")}`);
1884
2015
  const skills2 = rec.skills;
1885
- if (skills2) console.log(`Skills: ${skills2.length ? skills2.map((s) => s.slug).join(", ") : "none"}`);
2016
+ if (skills2) console.log(`Skills: ${skills2.length ? skills2.map((s) => s.slug).join(", ") : t("none", "ninguna")}`);
1886
2017
  const files3 = rec.files;
1887
- if (files3) console.log(`Files: ${files3.length} in ${WORK_DIR}`);
1888
- for (const e of errors) console.error(`note: ${e}`);
2018
+ if (files3) console.log(`${t("Files: ", "Archivos: ")} ${files3.length} ${t("in", "en")} ${WORK_DIR}`);
2019
+ for (const e of errors) console.error(`${t("note", "nota")}: ${e}`);
1889
2020
  if (typeof rec.systemPrompt === "string" && rec.systemPrompt) console.log(`
1890
2021
  ${rec.systemPrompt.trimEnd()}`);
1891
2022
  });
@@ -1894,16 +2025,16 @@ ${rec.systemPrompt.trimEnd()}`);
1894
2025
  var startError = (a) => a.status === "error" ? a.lastError ?? null : null;
1895
2026
  function printAgent(a) {
1896
2027
  console.log(`ID: ${a.agentId}`);
1897
- if (a.name) console.log(`Name: ${a.name}`);
2028
+ if (a.name) console.log(`${t("Name: ", "Nombre: ")} ${a.name}`);
1898
2029
  console.log(`Template: ${a.template}`);
1899
- console.log(`Status: ${a.status}`);
2030
+ console.log(`${t("Status: ", "Estado: ")} ${a.status}`);
1900
2031
  const reason = startError(a);
1901
2032
  if (reason) console.log(`Error: ${reason}`);
1902
2033
  console.log(`Sandbox: ${a.sandboxId}`);
1903
2034
  console.log(`URL: ${a.agentUrl}`);
1904
2035
  console.log(`Token: ${maskSecret(a.embedToken ?? "")} (full value: --fields embedToken --json)`);
1905
- console.log(`Created: ${fmtDate(a.createdAt)}`);
1906
- console.log(`Expires: ${a.expiresAt ? fmtDate(a.expiresAt) : "never"}`);
2036
+ console.log(`${t("Created: ", "Creado: ")} ${fmtDate(a.createdAt)}`);
2037
+ console.log(`${t("Expires: ", "Vence: ")} ${a.expiresAt ? fmtDate(a.expiresAt) : t("never", "nunca")}`);
1907
2038
  }
1908
2039
  var set = {
1909
2040
  summary: "Change the system prompt (ghosty-lite, goose); takes effect without a reboot",
@@ -1923,11 +2054,11 @@ var set = {
1923
2054
  const id = need(ctx, 0, "agent", this.usage);
1924
2055
  const inline = str(ctx, "prompt");
1925
2056
  const file = str(ctx, "prompt-file");
1926
- if (inline != null && file) throw usageError("Use --prompt or --prompt-file, not both.", this.usage);
2057
+ if (inline != null && file) throw usageError(t("Use --prompt or --prompt-file, not both.", "Usa --prompt o --prompt-file, no los dos."), this.usage);
1927
2058
  const mode = str(ctx, "prompt-mode");
1928
- if (mode && mode !== "append" && mode !== "replace") throw usageError("--prompt-mode must be append or replace.", this.usage);
2059
+ 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);
1929
2060
  const prompt = inline ?? (file ? await readLocalText(file, this.usage) : void 0);
1930
- if (prompt == null && !mode) throw usageError("Nothing to change: pass --prompt, --prompt-file or --prompt-mode.", this.usage);
2061
+ if (prompt == null && !mode) throw usageError(t("Nothing to change: pass --prompt, --prompt-file or --prompt-mode.", "Nada que cambiar: pasa --prompt, --prompt-file o --prompt-mode."), this.usage);
1931
2062
  const cur = await getPrompt(ctx, id);
1932
2063
  const changes = {};
1933
2064
  if (prompt != null && prompt !== cur.systemPrompt) {
@@ -1937,9 +2068,9 @@ var set = {
1937
2068
  if (bool(ctx, "dry-run") || !Object.keys(changes).length) {
1938
2069
  const dry = { agentId: id, dryRun: bool(ctx, "dry-run"), changes };
1939
2070
  emit(ctx, dry, () => {
1940
- if (!Object.keys(changes).length) return console.log("No changes: the agent already has that prompt.");
2071
+ if (!Object.keys(changes).length) return console.log(t("No changes: the agent already has that prompt.", "Sin cambios: el agente ya tiene ese prompt."));
1941
2072
  for (const [k, v] of Object.entries(changes)) console.log(`${k}: ${v.from} \u2192 ${v.to}`);
1942
- console.log("(dry run: nothing changed)");
2073
+ console.log(t("(dry run: nothing changed)", "(simulaci\xF3n: no cambi\xF3 nada)"));
1943
2074
  });
1944
2075
  return;
1945
2076
  }
@@ -1950,7 +2081,7 @@ var set = {
1950
2081
  emit(ctx, { agentId: id, changes, ...r }, () => {
1951
2082
  for (const [k, v] of Object.entries(changes)) console.log(`${k}: ${v.from} \u2192 ${v.to}`);
1952
2083
  if (r.hooks) console.log(r.hooks);
1953
- console.log(`Check it: easybits agents try ${id} "who are you?"`);
2084
+ console.log(t(`Check it: easybits agents try ${id} "who are you?"`, `Rev\xEDsalo: easybits agents try ${id} "\xBFqui\xE9n eres?"`));
1954
2085
  });
1955
2086
  }
1956
2087
  };
@@ -1979,7 +2110,7 @@ var files2 = {
1979
2110
  emit(
1980
2111
  ctx,
1981
2112
  r,
1982
- () => table(r.files.map((f) => ({ ...f, size: fmtBytes(f.size) })), [["path", "PATH"], ["size", "SIZE"]], `No files in ${r.dir}. Upload: easybits agents files put ${id} <file>`)
2113
+ () => table(r.files.map((f) => ({ ...f, size: fmtBytes(f.size) })), [["path", "PATH"], ["size", "SIZE"]], t(`No files in ${r.dir}. Upload: easybits agents files put ${id} <file>`, `No hay archivos en ${r.dir}. Sube: easybits agents files put ${id} <archivo>`))
1983
2114
  );
1984
2115
  } else if (action === "get" || action === "read" || action === "cat") {
1985
2116
  const path = need(ctx, 2, "path", FILES_USAGE2);
@@ -1990,22 +2121,22 @@ var files2 = {
1990
2121
  if (out) {
1991
2122
  const r = await box.files.read(full, { encoding: "base64" });
1992
2123
  writeFileSync4(out, Buffer.from(r.content, "base64"));
1993
- emit(ctx, { path, out: resolve(out), size: r.size }, () => console.error(`Saved ${fmtBytes(r.size)} to ${resolve(out)}`));
2124
+ emit(ctx, { path, out: resolve(out), size: r.size }, () => console.error(t(`Saved ${fmtBytes(r.size)} to ${resolve(out)}`, `Guard\xE9 ${fmtBytes(r.size)} en ${resolve(out)}`)));
1994
2125
  } else {
1995
2126
  const r = await box.files.read(full);
1996
2127
  emit(ctx, r, () => process.stdout.write(r.content));
1997
2128
  }
1998
2129
  } else if (action === "put" || action === "upload") {
1999
- if (!rest.length) throw usageError("Give one or more local files.", FILES_USAGE2);
2130
+ if (!rest.length) throw usageError(t("Give one or more local files.", "Pasa uno o m\xE1s archivos locales."), FILES_USAGE2);
2000
2131
  const to = (str(ctx, "to") ?? "").replace(/^\/+|\/+$/g, "");
2001
2132
  const plan = rest.map((local) => {
2002
- if (!existsSync6(local) || !statSync2(local).isFile()) throw usageError(`File not found: ${local}`, FILES_USAGE2);
2133
+ if (!existsSync6(local) || !statSync2(local).isFile()) throw usageError(t(`File not found: ${local}`, `No existe el archivo: ${local}`), FILES_USAGE2);
2003
2134
  return { local, path: to ? `${to}/${basename2(local)}` : basename2(local), bytes: statSync2(local).size };
2004
2135
  });
2005
2136
  if (bool(ctx, "dry-run")) {
2006
2137
  emit(ctx, { agentId: id, dryRun: true, upload: plan }, () => {
2007
2138
  for (const p of plan) console.log(`${p.local} \u2192 ${WORK_DIR}/${p.path} (${fmtBytes(p.bytes)})`);
2008
- console.log("(dry run: nothing uploaded)");
2139
+ console.log(t("(dry run: nothing uploaded)", "(simulaci\xF3n: no se subi\xF3 nada)"));
2009
2140
  });
2010
2141
  return;
2011
2142
  }
@@ -2013,24 +2144,24 @@ var files2 = {
2013
2144
  for (const p of plan) {
2014
2145
  const r = await api(ctx, "PUT", `/agents/${id}/files/${encodePath(p.path)}`, readFileSync6(p.local), { raw: true });
2015
2146
  done.push({ local: p.local, ...r });
2016
- if (!ctx.json) console.log(`Uploaded ${p.local} \u2192 ${WORK_DIR}/${p.path} (${fmtBytes(p.bytes)})`);
2147
+ if (!ctx.json) console.log(`${t("Uploaded", "Subido")} ${p.local} \u2192 ${WORK_DIR}/${p.path} (${fmtBytes(p.bytes)})`);
2017
2148
  }
2018
2149
  if (ctx.json) emit(ctx, done, () => {
2019
2150
  });
2020
2151
  } else if (action === "rm" || action === "delete") {
2021
- if (!rest.length) throw usageError("Give one or more paths.", FILES_USAGE2);
2152
+ if (!rest.length) throw usageError(t("Give one or more paths.", "Pasa una o m\xE1s rutas."), FILES_USAGE2);
2022
2153
  if (bool(ctx, "dry-run")) {
2023
2154
  emit(ctx, { agentId: id, dryRun: true, delete: rest }, () => {
2024
- for (const p of rest) console.log(`would delete ${WORK_DIR}/${p}`);
2155
+ for (const p of rest) console.log(`${t("would delete", "borrar\xEDa")} ${WORK_DIR}/${p}`);
2025
2156
  });
2026
2157
  return;
2027
2158
  }
2028
2159
  requireYesIfHeadless(ctx);
2029
- await confirm(ctx, `Delete ${rest.length === 1 ? rest[0] : `${rest.length} files`} from agent ${id}?`);
2160
+ await confirm(ctx, t(`Delete ${rest.length === 1 ? rest[0] : `${rest.length} files`} from agent ${id}?`, `\xBFBorrar ${rest.length === 1 ? rest[0] : `${rest.length} archivos`} del agente ${id}?`));
2030
2161
  const done = [];
2031
2162
  for (const p of rest) done.push(await api(ctx, "DELETE", `/agents/${id}/files/${encodePath(p)}`));
2032
- emit(ctx, done, () => done.forEach((d) => console.log(`Deleted ${d.deleted}`)));
2033
- } else throw usageError(`Unknown files action "${action}".`, FILES_USAGE2);
2163
+ emit(ctx, done, () => done.forEach((d) => console.log(`${t("Deleted", "Borrado")} ${d.deleted}`)));
2164
+ } else throw usageError(t(`Unknown files action "${action}".`, `Acci\xF3n de files desconocida "${action}".`), FILES_USAGE2);
2034
2165
  }
2035
2166
  };
2036
2167
  var SKILLS_USAGE = "easybits agents skills <ls|get|add|rm> <agent> [slug...] [--file SKILL.md | --dir <folder>] [--restart]";
@@ -2078,14 +2209,14 @@ var skills = {
2078
2209
  emit(
2079
2210
  ctx,
2080
2211
  list2,
2081
- () => table(list2.map((s) => ({ ...s, files: s.files.length })), [["slug", "SLUG"], ["files", "FILES"], ["description", "DESCRIPTION"]], `No skills. Add one: easybits agents skills add ${id} --dir <folder>`)
2212
+ () => table(list2.map((s) => ({ ...s, files: s.files.length })), [["slug", "SLUG"], ["files", "FILES"], ["description", "DESCRIPTION"]], t(`No skills. Add one: easybits agents skills add ${id} --dir <folder>`, `No hay skills. Agrega una: easybits agents skills add ${id} --dir <carpeta>`))
2082
2213
  );
2083
2214
  } else if (action === "get" || action === "read") {
2084
2215
  const slug = need(ctx, 2, "slug", SKILLS_USAGE);
2085
2216
  const eb = await getClient(ctx);
2086
2217
  const { box } = await agentBox(eb, id);
2087
2218
  const entry = (await listSkills(box)).find((s2) => s2.slug === slug);
2088
- if (!entry) throw new CliError(`No skill "${slug}" on agent ${id}.`, EXIT.API, `List them: easybits agents skills ls ${id}`, "not_found", 404);
2219
+ if (!entry) throw new CliError(t(`No skill "${slug}" on agent ${id}.`, `El agente ${id} no tiene la skill "${slug}".`), EXIT.API, t(`List them: easybits agents skills ls ${id}`, `Enl\xEDstalas: easybits agents skills ls ${id}`), "not_found", 404);
2089
2220
  const out = str(ctx, "out");
2090
2221
  if (!out) {
2091
2222
  const md = (await box.files.read(`${SKILLS_DIR}/${slug}/SKILL.md`)).content;
@@ -2099,48 +2230,49 @@ var skills = {
2099
2230
  mkdirSync3(dirname(join3(out, a.name)), { recursive: true });
2100
2231
  writeFileSync4(join3(out, a.name), Buffer.from(a.contentBase64, "base64"));
2101
2232
  }
2102
- emit(ctx, { slug, out: resolve(out), files: entry.files }, () => console.log(`Saved ${entry.files.length} file(s) to ${resolve(out)}`));
2233
+ emit(ctx, { slug, out: resolve(out), files: entry.files }, () => console.log(t(`Saved ${entry.files.length} file(s) to ${resolve(out)}`, `Guard\xE9 ${entry.files.length} archivo(s) en ${resolve(out)}`)));
2103
2234
  } else if (action === "add" || action === "put") {
2104
2235
  const file = str(ctx, "file");
2105
2236
  const dir = str(ctx, "dir");
2106
- if (!!file === !!dir) throw usageError("Pass --file SKILL.md or --dir <folder>.", SKILLS_USAGE);
2237
+ if (!!file === !!dir) throw usageError(t("Pass --file SKILL.md or --dir <folder>.", "Pasa --file SKILL.md o --dir <carpeta>."), SKILLS_USAGE);
2107
2238
  const mdPath = file ?? join3(dir, "SKILL.md");
2108
- if (!existsSync6(mdPath)) throw usageError(`File not found: ${mdPath}`, SKILLS_USAGE);
2239
+ if (!existsSync6(mdPath)) throw usageError(t(`File not found: ${mdPath}`, `No existe el archivo: ${mdPath}`), SKILLS_USAGE);
2109
2240
  const markdown = readFileSync6(mdPath, "utf8");
2110
2241
  const fm = skillFrontmatter(markdown);
2111
2242
  if (fm.error) throw usageError(`${mdPath}: ${fm.error}`, SKILLS_USAGE);
2112
2243
  const slug = str(ctx, "slug") ?? (fm.name && SLUG_RE.test(fm.name) ? fm.name : basename2(resolve(dir ?? dirname(mdPath))).toLowerCase());
2113
- if (!SLUG_RE.test(slug)) throw usageError(`Invalid slug "${slug}" ([a-z0-9-], max 64). Pass --slug.`, SKILLS_USAGE);
2244
+ 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);
2114
2245
  const assets = dir ? readSkillDir(dir) : [];
2115
2246
  const plan = { agentId: id, slug, files: ["SKILL.md", ...assets.map((a) => a.name)], restart: bool(ctx, "restart") };
2116
2247
  if (bool(ctx, "dry-run")) {
2117
- emit(ctx, { ...plan, dryRun: true }, () => console.log(`would add skill ${slug} (${plan.files.length} file(s))${plan.restart ? " and restart" : ""}
2118
- (dry run: nothing changed)`));
2248
+ emit(ctx, { ...plan, dryRun: true }, () => console.log(t(`would add skill ${slug} (${plan.files.length} file(s))${plan.restart ? " and restart" : ""}
2249
+ (dry run: nothing changed)`, `agregar\xEDa la skill ${slug} (${plan.files.length} archivo(s))${plan.restart ? " y reiniciar\xEDa" : ""}
2250
+ (simulaci\xF3n: no cambi\xF3 nada)`)));
2119
2251
  return;
2120
2252
  }
2121
2253
  const r = await api(ctx, "PUT", `/agents/${id}/skills/${slug}`, { markdown, assets });
2122
2254
  const restarted = plan.restart ? await restartAgent(ctx, id) : null;
2123
2255
  emit(ctx, { ...r, restarted: !!restarted }, () => {
2124
- console.log(`Added skill ${slug} (${r.files.length} file(s), ${fmtBytes(r.bytes)})`);
2125
- console.log(restarted ? "Restarted: the agent sees it now." : `It takes effect after: easybits agents restart ${id}`);
2256
+ console.log(t(`Added skill ${slug} (${r.files.length} file(s), ${fmtBytes(r.bytes)})`, `Skill ${slug} agregada (${r.files.length} archivo(s), ${fmtBytes(r.bytes)})`));
2257
+ console.log(restarted ? t("Restarted: the agent sees it now.", "Reiniciado: el agente ya la ve.") : t(`It takes effect after: easybits agents restart ${id}`, `Entra despu\xE9s de: easybits agents restart ${id}`));
2126
2258
  });
2127
2259
  } else if (action === "rm" || action === "delete") {
2128
2260
  const slugs = ctx.args.slice(2);
2129
- if (!slugs.length) throw usageError("Give one or more slugs.", SKILLS_USAGE);
2261
+ if (!slugs.length) throw usageError(t("Give one or more slugs.", "Pasa uno o m\xE1s slugs."), SKILLS_USAGE);
2130
2262
  if (bool(ctx, "dry-run")) {
2131
- emit(ctx, { agentId: id, dryRun: true, delete: slugs }, () => slugs.forEach((s) => console.log(`would remove skill ${s}`)));
2263
+ emit(ctx, { agentId: id, dryRun: true, delete: slugs }, () => slugs.forEach((s) => console.log(`${t("would remove skill", "quitar\xEDa la skill")} ${s}`)));
2132
2264
  return;
2133
2265
  }
2134
2266
  requireYesIfHeadless(ctx);
2135
- await confirm(ctx, `Remove skill${slugs.length > 1 ? "s" : ""} ${slugs.join(", ")} from agent ${id}?`);
2267
+ await confirm(ctx, t(`Remove skill${slugs.length > 1 ? "s" : ""} ${slugs.join(", ")} from agent ${id}?`, `\xBFQuitar la(s) skill(s) ${slugs.join(", ")} del agente ${id}?`));
2136
2268
  const done = [];
2137
2269
  for (const s of slugs) done.push(await api(ctx, "DELETE", `/agents/${id}/skills/${s}`));
2138
2270
  const restarted = bool(ctx, "restart") ? await restartAgent(ctx, id) : null;
2139
2271
  emit(ctx, { deleted: done.map((d) => d.deleted), restarted: !!restarted }, () => {
2140
- done.forEach((d) => console.log(`Removed ${d.deleted}`));
2141
- console.log(restarted ? "Restarted." : `It takes effect after: easybits agents restart ${id}`);
2272
+ done.forEach((d) => console.log(`${t("Removed", "Quitada")} ${d.deleted}`));
2273
+ console.log(restarted ? t("Restarted.", "Reiniciado.") : t(`It takes effect after: easybits agents restart ${id}`, `Entra despu\xE9s de: easybits agents restart ${id}`));
2142
2274
  });
2143
- } else throw usageError(`Unknown skills action "${action}".`, SKILLS_USAGE);
2275
+ } else throw usageError(t(`Unknown skills action "${action}".`, `Acci\xF3n de skills desconocida "${action}".`), SKILLS_USAGE);
2144
2276
  }
2145
2277
  };
2146
2278
  var MCP_USAGE = "easybits agents mcp <get|set> <agent> [--file <servers.json|->] [--dry-run]";
@@ -2167,15 +2299,15 @@ var mcp2 = {
2167
2299
  emit(
2168
2300
  ctx,
2169
2301
  { servers: shown },
2170
- () => table(shown.map((s) => ({ name: s.name, type: s.type ?? "stdio", target: s.url ?? [s.command, ...s.args ?? []].join(" ") })), [["name", "NAME"], ["type", "TYPE"], ["target", "URL / COMMAND"]], "No MCP servers.")
2302
+ () => table(shown.map((s) => ({ name: s.name, type: s.type ?? "stdio", target: s.url ?? [s.command, ...s.args ?? []].join(" ") })), [["name", "NAME"], ["type", "TYPE"], ["target", "URL / COMMAND"]], t("No MCP servers.", "Sin servidores MCP."))
2171
2303
  );
2172
2304
  return;
2173
2305
  }
2174
- if (action !== "set") throw usageError(`Unknown mcp action "${action}".`, MCP_USAGE);
2306
+ if (action !== "set") throw usageError(t(`Unknown mcp action "${action}".`, `Acci\xF3n de mcp desconocida "${action}".`), MCP_USAGE);
2175
2307
  const file = str(ctx, "file");
2176
- if (!file) throw usageError("Missing --file.", MCP_USAGE);
2308
+ if (!file) throw usageError(t("Missing --file.", "Falta --file."), MCP_USAGE);
2177
2309
  const next = parseMcpFile(await readLocalText(file, MCP_USAGE), MCP_USAGE);
2178
- if (hasMasked(next)) throw usageError("The file has masked values (\u2026***). Export with --show-secrets or use $secret:NAME references.", MCP_USAGE);
2310
+ if (hasMasked(next)) throw usageError(t("The file has masked values (\u2026***). Export with --show-secrets or use $secret:NAME references.", "El archivo trae valores tapados (\u2026***). Exporta con --show-secrets o usa referencias $secret:NOMBRE."), MCP_USAGE);
2179
2311
  const cur = await getMcp(ctx, id);
2180
2312
  const names = (l) => new Set(l.map((s) => s.name));
2181
2313
  const diff = {
@@ -2191,7 +2323,7 @@ var mcp2 = {
2191
2323
  console.log(`added: ${diff.added.join(", ") || "-"}
2192
2324
  removed: ${diff.removed.join(", ") || "-"}
2193
2325
  changed: ${diff.changed.join(", ") || "-"}`);
2194
- console.log("(dry run: nothing changed; a real set restarts the agent)");
2326
+ console.log(t("(dry run: nothing changed; a real set restarts the agent)", "(simulaci\xF3n: no cambi\xF3 nada; un set de verdad reinicia el agente)"));
2195
2327
  });
2196
2328
  return;
2197
2329
  }
@@ -2199,7 +2331,7 @@ changed: ${diff.changed.join(", ") || "-"}`);
2199
2331
  emit(
2200
2332
  ctx,
2201
2333
  { agentId: id, ...diff, servers: maskMcp(r.servers), restarted: true },
2202
- () => console.log(`MCP servers: ${r.servers.map((s) => s.name).join(", ") || "none"} (agent restarted, new session)`)
2334
+ () => console.log(t(`MCP servers: ${r.servers.map((s) => s.name).join(", ") || "none"} (agent restarted, new session)`, `Servidores MCP: ${r.servers.map((s) => s.name).join(", ") || "ninguno"} (agente reiniciado, sesi\xF3n nueva)`))
2203
2335
  );
2204
2336
  }
2205
2337
  };
@@ -2210,7 +2342,7 @@ var restart = {
2210
2342
  async run(ctx) {
2211
2343
  const id = need(ctx, 0, "agent", this.usage);
2212
2344
  const r = await restartAgent(ctx, id);
2213
- emit(ctx, { agentId: id, restarted: true, sandboxId: r.sandboxId }, () => console.log(`Restarted ${id}`));
2345
+ emit(ctx, { agentId: id, restarted: true, sandboxId: r.sandboxId }, () => console.log(t(`Restarted ${id}`, `Reiniciado ${id}`)));
2214
2346
  }
2215
2347
  };
2216
2348
  var tryTurn = {
@@ -2224,19 +2356,19 @@ var tryTurn = {
2224
2356
  async run(ctx) {
2225
2357
  const id = need(ctx, 0, "agent", this.usage);
2226
2358
  const text = ctx.args.slice(1).join(" ");
2227
- if (!text) throw usageError("Missing <text>.", this.usage);
2359
+ if (!text) throw usageError(t("Missing <text>.", "Falta <text>."), this.usage);
2228
2360
  let r;
2229
2361
  try {
2230
2362
  r = await api(ctx, "POST", `/agents/${id}/try`, { text, session: str(ctx, "session"), reset: bool(ctx, "reset") });
2231
2363
  } catch (e) {
2232
2364
  const err = toCliError(e);
2233
- if (err.status === 409) err.hint = "A turn is already running in that session. Wait, or use another --session.";
2365
+ if (err.status === 409) err.hint = t("A turn is already running in that session. Wait, or use another --session.", "Ya hay un turno corriendo en esa sesi\xF3n. Espera, o usa otra --session.");
2234
2366
  throw err;
2235
2367
  }
2236
2368
  emit(ctx, r, () => {
2237
- console.log(r.text || "(no text)");
2369
+ console.log(r.text || t("(no text)", "(sin texto)"));
2238
2370
  if (r.error) console.error(`error: ${r.error}`);
2239
- console.error(r.session ? `session: ${r.session} \xB7 ${(r.ms / 1e3).toFixed(1)}s (continue: --session ${r.session})` : `${(r.ms / 1e3).toFixed(1)}s`);
2371
+ console.error(r.session ? `${t("session", "sesi\xF3n")}: ${r.session} \xB7 ${(r.ms / 1e3).toFixed(1)}s (${t("continue", "para seguir")}: --session ${r.session})` : `${(r.ms / 1e3).toFixed(1)}s`);
2240
2372
  });
2241
2373
  }
2242
2374
  };
@@ -2272,28 +2404,28 @@ var doctor = {
2272
2404
  check: "status",
2273
2405
  ok: a.status === "running" || a.status === "suspended",
2274
2406
  detail: startError(a) ? `error: ${startError(a)}` : a.status,
2275
- ...a.status === "error" ? { hint: `Its runtime did not start. Fix the cause, then: easybits agents restart ${id}` } : {},
2276
- ...a.status === "lost" ? { hint: `Its sandbox is gone. Destroy it (easybits agents destroy ${id}) or recreate it (easybits agents create --from <export.json>).` } : {}
2407
+ ...a.status === "error" ? { hint: t(`Its runtime did not start. Fix the cause, then: easybits agents restart ${id}`, `Su runtime no arranc\xF3. Corrige la causa y luego: easybits agents restart ${id}`) } : {},
2408
+ ...a.status === "lost" ? { hint: t(`Its sandbox is gone. Destroy it (easybits agents destroy ${id}) or recreate it (easybits agents create --from <export.json>).`, `Su sandbox ya no existe. Destr\xFAyelo (easybits agents destroy ${id}) o recr\xE9alo (easybits apply <archivo> --create).`) } : {}
2277
2409
  });
2278
2410
  if (a.expiresAt) {
2279
2411
  const left = new Date(a.expiresAt).getTime() - Date.now();
2280
- add({ check: "expires", ok: left > 0, detail: left > 0 ? `in ${Math.round(left / 6e4)} min` : `expired ${fmtDate(a.expiresAt)}` });
2412
+ add({ check: "expires", ok: left > 0, detail: left > 0 ? t(`in ${Math.round(left / 6e4)} min`, `en ${Math.round(left / 6e4)} min`) : t(`expired ${fmtDate(a.expiresAt)}`, `venci\xF3 ${fmtDate(a.expiresAt)}`) });
2281
2413
  }
2282
2414
  const sb = await soft(() => eb.sandboxes.get(a.sandboxId));
2283
2415
  add(sb.ok ? { check: "sandbox", ok: sb.value.status !== "error", detail: `${a.sandboxId} ${sb.value.status}` } : { check: "sandbox", ok: false, detail: sb.error });
2284
2416
  if (sb.ok && sb.value.status === "running") {
2285
2417
  const p = await soft(() => getPrompt(ctx, id));
2286
2418
  if (!p.ok && /agente_sin_maquina|no expone/.test(p.error)) {
2287
- add({ check: "machine", ok: true, detail: `template ${a.template} has no machine config (prompt/skills/MCP): nothing more to check` });
2419
+ add({ check: "machine", ok: true, detail: t(`template ${a.template} has no machine config (prompt/skills/MCP): nothing more to check`, `el template ${a.template} no tiene config de m\xE1quina (prompt/skills/MCP): nada m\xE1s que revisar`) });
2288
2420
  } else {
2289
- add(p.ok ? { check: "prompt", ok: p.value.systemPrompt.trim().length > 0, detail: `${Buffer.byteLength(p.value.systemPrompt)} bytes, ${p.value.systemPromptMode}`, ...p.value.systemPrompt.trim() ? {} : { hint: `Set one: easybits agents set ${id} --prompt-file PROMPT.md` } } : { check: "prompt", ok: false, detail: p.error });
2421
+ add(p.ok ? { check: "prompt", ok: p.value.systemPrompt.trim().length > 0, detail: `${Buffer.byteLength(p.value.systemPrompt)} bytes, ${p.value.systemPromptMode}`, ...p.value.systemPrompt.trim() ? {} : { hint: t(`Set one: easybits agents set ${id} --prompt-file PROMPT.md`, `Ponle uno: easybits agents set ${id} --prompt-file PROMPT.md`) } } : { check: "prompt", ok: false, detail: p.error });
2290
2422
  const s = await soft(() => listSkills(sb.value));
2291
2423
  if (s.ok) {
2292
2424
  add({ check: "skills", ok: true, detail: s.value.length ? s.value.map((x) => x.slug).join(", ") : "none" });
2293
2425
  for (const skill of s.value) {
2294
2426
  const md = await soft(() => sb.value.files.read(`${SKILLS_DIR}/${skill.slug}/SKILL.md`));
2295
2427
  const fm = md.ok ? skillFrontmatter(md.value.content) : { error: "no SKILL.md" };
2296
- if (fm.error) add({ check: `skill ${skill.slug}`, ok: false, detail: fm.error, hint: "The engine drops this skill silently. Fix SKILL.md and add it again." });
2428
+ if (fm.error) add({ check: `skill ${skill.slug}`, ok: false, detail: fm.error, hint: t("The engine drops this skill silently. Fix SKILL.md and add it again.", "El motor descarta esta skill en silencio. Corrige SKILL.md y vuelve a agregarla.") });
2297
2429
  }
2298
2430
  } else add({ check: "skills", ok: false, detail: s.error });
2299
2431
  const m = await soft(() => getMcp(ctx, id));
@@ -2303,10 +2435,10 @@ var doctor = {
2303
2435
  const ok = checks.every((c) => c.ok);
2304
2436
  emit(ctx, { agentId: id, ok, checks }, () => {
2305
2437
  for (const c of checks) {
2306
- console.log(`${c.ok ? "ok " : "FAIL"} ${c.check.padEnd(14)} ${c.detail}`);
2438
+ console.log(`${c.ok ? "ok " : t("FAIL", "MAL ")} ${c.check.padEnd(14)} ${c.detail}`);
2307
2439
  if (!c.ok && c.hint) console.log(` ${"".padEnd(14)} \u2192 ${c.hint}`);
2308
2440
  }
2309
- console.log(ok ? "\nNo problems found." : "\nProblems found.");
2441
+ console.log(ok ? t("\nNo problems found.", "\nSin problemas.") : t("\nProblems found.", "\nHay problemas."));
2310
2442
  });
2311
2443
  if (!ok) process.exitCode = EXIT.API;
2312
2444
  }
@@ -2324,7 +2456,7 @@ async function buildExport(ctx, id, opts) {
2324
2456
  const p = await soft(() => getPrompt(ctx, id));
2325
2457
  if (!p.ok) {
2326
2458
  if (/agente_sin_maquina|no expone/.test(p.error)) return out;
2327
- throw new CliError(`Could not read the agent's config: ${p.error}`, EXIT.API, `Is it running? easybits agents doctor ${id}`);
2459
+ throw new CliError(t(`Could not read the agent's config: ${p.error}`, `No se pudo leer la config del agente: ${p.error}`), EXIT.API, t(`Is it running? easybits agents doctor ${id}`, `\xBFEst\xE1 corriendo? easybits agents doctor ${id}`));
2328
2460
  }
2329
2461
  out.systemPrompt = p.value.systemPrompt;
2330
2462
  out.systemPromptMode = p.value.systemPromptMode;
@@ -2358,31 +2490,31 @@ var exportAgent = {
2358
2490
  if (out) {
2359
2491
  writeFileSync4(out, JSON.stringify(x, null, 2) + "\n", { mode: bool(ctx, "show-secrets") ? 384 : 420 });
2360
2492
  const summary = { out: resolve(out), template: x.template, skills: x.skills?.length ?? 0, mcpServers: x.mcpServers?.length ?? 0, files: x.files?.length ?? 0 };
2361
- emit(ctx, summary, () => console.log(`Exported ${id} to ${summary.out} (${summary.skills} skill(s), ${summary.mcpServers} MCP, ${summary.files} file(s))`));
2493
+ emit(ctx, summary, () => console.log(t(`Exported ${id} to ${summary.out} (${summary.skills} skill(s), ${summary.mcpServers} MCP, ${summary.files} file(s))`, `Exportado ${id} a ${summary.out} (${summary.skills} skill(s), ${summary.mcpServers} MCP, ${summary.files} archivo(s))`)));
2362
2494
  return;
2363
2495
  }
2364
2496
  process.stdout.write(JSON.stringify(x, null, 2) + "\n");
2365
2497
  }
2366
2498
  };
2367
2499
  function readExport(path, usage2) {
2368
- if (!existsSync6(path)) throw usageError(`File not found: ${path}`, usage2);
2500
+ if (!existsSync6(path)) throw usageError(t(`File not found: ${path}`, `No existe el archivo: ${path}`), usage2);
2369
2501
  let x;
2370
2502
  try {
2371
2503
  x = JSON.parse(readFileSync6(path, "utf8"));
2372
2504
  } catch (e) {
2373
- throw usageError(`${path} is not JSON: ${e.message}`, usage2);
2505
+ throw usageError(t(`${path} is not JSON: ${e.message}`, `${path} no es JSON: ${e.message}`), usage2);
2374
2506
  }
2375
- if (x?.kind !== "easybits.agent" || !x.template) throw usageError(`${path} is not an agent export (easybits agents export).`, usage2);
2507
+ if (x?.kind !== "easybits.agent" || !x.template) throw usageError(t(`${path} is not an agent export (easybits agents export).`, `${path} no es un export de agente (easybits agents export).`), usage2);
2376
2508
  return x;
2377
2509
  }
2378
2510
  async function waitMachine(ctx, id, ms = 12e4) {
2379
2511
  const until = Date.now() + ms;
2380
- if (!ctx.json) process.stderr.write("Waiting for the agent's machine");
2512
+ if (!ctx.json) process.stderr.write(t("Waiting for the agent's machine", "Esperando la m\xE1quina del agente"));
2381
2513
  let last = "";
2382
2514
  while (Date.now() < until) {
2383
2515
  const p = await soft(() => getPrompt(ctx, id));
2384
2516
  if (p.ok) {
2385
- if (!ctx.json) process.stderr.write(" ready\n");
2517
+ if (!ctx.json) process.stderr.write(t(" ready\n", " lista\n"));
2386
2518
  return;
2387
2519
  }
2388
2520
  last = p.error;
@@ -2390,7 +2522,7 @@ async function waitMachine(ctx, id, ms = 12e4) {
2390
2522
  await new Promise((r) => setTimeout(r, 3e3));
2391
2523
  }
2392
2524
  if (!ctx.json) process.stderr.write("\n");
2393
- throw new CliError(`Agent ${id} was created but its machine did not answer in ${ms / 1e3}s: ${last}`, EXIT.API, `Check it: easybits agents doctor ${id}`);
2525
+ throw new CliError(t(`Agent ${id} was created but its machine did not answer in ${ms / 1e3}s: ${last}`, `El agente ${id} se cre\xF3 pero su m\xE1quina no contest\xF3 en ${ms / 1e3}s: ${last}`), EXIT.API, t(`Check it: easybits agents doctor ${id}`, `Rev\xEDsalo: easybits agents doctor ${id}`));
2394
2526
  }
2395
2527
  async function applyExport(ctx, id, x, opts) {
2396
2528
  const copied = { skills: [], files: [], skippedFiles: [] };
@@ -2488,8 +2620,9 @@ var exportSpec = {
2488
2620
  }
2489
2621
  if (isFileTarget(out)) {
2490
2622
  writeFileSync5(out, /\.json$/i.test(out) ? JSON.stringify(await getJson(), null, 2) + "\n" : await getYaml());
2491
- emit(ctx, { saved: resolve2(out) }, () => console.log(`Saved ${resolve2(out)}
2492
- Edit it, then: easybits apply ${out} --dry-run`));
2623
+ emit(ctx, { saved: resolve2(out) }, () => console.log(t(`Saved ${resolve2(out)}
2624
+ Edit it, then: easybits apply ${out} --dry-run`, `Guardado ${resolve2(out)}
2625
+ Ed\xEDtalo y luego: easybits apply ${out} --dry-run`)));
2493
2626
  return;
2494
2627
  }
2495
2628
  const yaml = await getYaml();
@@ -2511,17 +2644,17 @@ Edit it, then: easybits apply ${out} --dry-run`));
2511
2644
  ctx,
2512
2645
  summary,
2513
2646
  () => console.log(
2514
- `Saved ${dir}/agent.yaml${summary.skills.length ? ` + skills/ (${summary.skills.join(", ")})` : ""}${summary.files.length ? ` + files/ (${summary.files.length})` : ""}
2515
- Edit it, then: easybits apply ${out} --dry-run`
2647
+ `${t("Saved", "Guardado")} ${dir}/agent.yaml${summary.skills.length ? ` + skills/ (${summary.skills.join(", ")})` : ""}${summary.files.length ? ` + files/ (${summary.files.length})` : ""}
2648
+ ${t("Edit it, then", "Ed\xEDtalo y luego")}: easybits apply ${out} --dry-run`
2516
2649
  )
2517
2650
  );
2518
2651
  }
2519
2652
  };
2520
2653
  function readTarget(target, usage2) {
2521
- if (!existsSync8(target)) throw usageError(`Not found: ${target}`, usage2);
2654
+ if (!existsSync8(target)) throw usageError(t(`Not found: ${target}`, `No existe: ${target}`), usage2);
2522
2655
  if (statSync3(target).isDirectory()) {
2523
2656
  const name = SPEC_NAMES.find((n) => existsSync8(join5(target, n)));
2524
- if (!name) throw usageError(`${target} has no agent.yaml.`, usage2);
2657
+ if (!name) throw usageError(t(`${target} has no agent.yaml.`, `${target} no tiene agent.yaml.`), usage2);
2525
2658
  return { text: readFileSync8(join5(target, name), "utf8"), dir: target };
2526
2659
  }
2527
2660
  return { text: readFileSync8(target, "utf8") };
@@ -2540,7 +2673,7 @@ async function applyTarget(ctx, target, o) {
2540
2673
  const { secrets, missing } = secretsFor(text);
2541
2674
  const uploads = dir ? readUploads(dir) : {};
2542
2675
  if (o.create) {
2543
- if (o.agent) throw usageError("Use --agent or --create, not both.", o.usage);
2676
+ if (o.agent) throw usageError(t("Use --agent or --create, not both.", "Usa --agent o --create, no los dos."), o.usage);
2544
2677
  const body2 = { spec: text, secrets, ...o.name ? { name: o.name } : {} };
2545
2678
  const plan2 = await api(ctx, "POST", "/agents/apply", { ...body2, dryRun: true });
2546
2679
  if (o.dryRun) {
@@ -2548,14 +2681,15 @@ async function applyTarget(ctx, target, o) {
2548
2681
  printPlan(plan2.plan);
2549
2682
  for (const s of Object.keys(uploads.skills ?? {})) console.log(`+ skill ${s}`);
2550
2683
  for (const f of Object.keys(uploads.files ?? {})) console.log(`+ file ${f}`);
2551
- console.log("(dry run: nothing created)");
2684
+ console.log(t("(dry run: nothing created)", "(simulaci\xF3n: no se cre\xF3 nada)"));
2552
2685
  });
2553
2686
  return;
2554
2687
  }
2555
2688
  const created = await api(ctx, "POST", "/agents/apply", body2);
2556
2689
  const id2 = created.agentId;
2557
- if (!ctx.json) process.stderr.write(`Created ${id2}; applying the rest\u2026
2558
- `);
2690
+ if (!ctx.json) process.stderr.write(t(`Created ${id2}; applying the rest\u2026
2691
+ `, `Creado ${id2}; aplicando lo dem\xE1s\u2026
2692
+ `));
2559
2693
  let rest;
2560
2694
  if (/\n?(skills|files):/m.test(text) || uploads.skills || uploads.files) {
2561
2695
  await waitMachine(ctx, id2).catch(() => {
@@ -2566,17 +2700,18 @@ async function applyTarget(ctx, target, o) {
2566
2700
  const res = { agentId: id2, sandboxId: created.sandboxId, plan: [...created.plan, ...extra], applied: rest?.applied, failed: rest?.failed };
2567
2701
  emit(ctx, res, () => {
2568
2702
  printPlan(res.plan);
2569
- console.log(`Created ${id2}.${res.failed ? "" : ` Try it: easybits agents try ${id2} "hello"`}`);
2570
- if (res.failed) console.error(`Stopped at \xAB${res.failed.what}\xBB: ${res.failed.error}`);
2703
+ console.log(t(`Created ${id2}.${res.failed ? "" : ` Try it: easybits agents try ${id2} "hello"`}`, `Creado ${id2}.${res.failed ? "" : ` Pru\xE9balo: easybits agents try ${id2} "hola"`}`));
2704
+ if (res.failed) console.error(t(`Stopped at \xAB${res.failed.what}\xBB: ${res.failed.error}`, `Se detuvo en \xAB${res.failed.what}\xBB: ${serverText(res.failed.error)}`));
2571
2705
  });
2572
2706
  if (res.failed) process.exitCode = EXIT.API;
2573
2707
  return;
2574
2708
  }
2575
2709
  const ref = o.agent ?? specId(text);
2576
- if (!ref) throw usageError("The file has no agent id.", "Point it at one with --agent <name|id>, or make a new one with --create.");
2710
+ if (!ref) throw usageError(t("The file has no agent id.", "El archivo no trae id de agente."), t("Point it at one with --agent <name|id>, or make a new one with --create.", "Ap\xFAntalo a uno con --agent <nombre|id>, o crea uno nuevo con --create."));
2577
2711
  const id = await resolveRef(ctx, "agent", ref);
2578
- if (missing.length && !ctx.json) process.stderr.write(`Not in your environment (keep their current value): ${missing.join(", ")}
2579
- `);
2712
+ if (missing.length && !ctx.json) process.stderr.write(t(`Not in your environment (keep their current value): ${missing.join(", ")}
2713
+ `, `No est\xE1n en tu entorno (conservan su valor de hoy): ${missing.join(", ")}
2714
+ `));
2580
2715
  const path = `/agents/${id}/apply`;
2581
2716
  const body = { spec: text, secrets, ...uploads, ...o.prune ? { prune: true } : {} };
2582
2717
  const plan = await api(ctx, "POST", path, { ...body, dryRun: true });
@@ -2584,20 +2719,22 @@ async function applyTarget(ctx, target, o) {
2584
2719
  if (o.dryRun || !actionable.length) {
2585
2720
  emit(ctx, plan, () => {
2586
2721
  if (plan.plan.length) printPlan(plan.plan);
2587
- console.log(actionable.length ? "(dry run: nothing changed)" : plan.plan.length ? "Nothing to apply (see the ! lines)." : "No changes: the agent already matches the file.");
2722
+ console.log(
2723
+ actionable.length ? t("(dry run: nothing changed)", "(simulaci\xF3n: no cambi\xF3 nada)") : plan.plan.length ? t("Nothing to apply (see the ! lines).", "Nada que aplicar (mira los renglones con !).") : t("No changes: the agent already matches the file.", "Sin cambios: el agente ya coincide con el archivo.")
2724
+ );
2588
2725
  });
2589
2726
  return;
2590
2727
  }
2591
2728
  requireYesIfHeadless(ctx);
2592
2729
  await confirm(ctx, `${plan.plan.map(line).join("\n")}
2593
- Apply ${actionable.length} change(s) to ${id}?`);
2730
+ ${t(`Apply ${actionable.length} change(s) to ${id}?`, `\xBFAplicar ${actionable.length} cambio(s) a ${id}?`)}`);
2594
2731
  const r = await api(ctx, "POST", path, body);
2595
2732
  const backup = r.before ? saveBackup(id, r.before) : void 0;
2596
2733
  const { before: _b, ...shown } = r;
2597
2734
  emit(ctx, { ...shown, ...backup ? { backup } : {} }, () => {
2598
- console.log(`Applied ${r.applied?.length ?? 0} change(s) to ${id}.`);
2599
- if (backup) console.log(`Before (to roll back: easybits apply ${backup} --agent ${id}): ${backup}`);
2600
- if (r.failed) console.error(`Stopped at \xAB${r.failed.what}\xBB: ${r.failed.error}`);
2735
+ console.log(t(`Applied ${r.applied?.length ?? 0} change(s) to ${id}.`, `${r.applied?.length ?? 0} cambio(s) aplicados a ${id}.`));
2736
+ if (backup) console.log(t(`Before (to roll back: easybits apply ${backup} --agent ${id}): ${backup}`, `Lo de antes (para volver: easybits apply ${backup} --agent ${id}): ${backup}`));
2737
+ if (r.failed) console.error(t(`Stopped at \xAB${r.failed.what}\xBB: ${r.failed.error}`, `Se detuvo en \xAB${r.failed.what}\xBB: ${serverText(r.failed.error)}`));
2601
2738
  });
2602
2739
  if (r.failed) process.exitCode = EXIT.API;
2603
2740
  }
@@ -2667,7 +2804,7 @@ var agents = {
2667
2804
  () => table(
2668
2805
  items.map((a) => ({ ...a, createdAt: fmtDate(a.createdAt) })),
2669
2806
  [["agentId", "ID"], ["name", "NAME"], ["template", "TEMPLATE"], ["status", "STATUS"], ["createdAt", "CREATED"]],
2670
- "No agents. Create one: easybits agents create --template <template>"
2807
+ t("No agents. Create one: easybits agents create --template <template>", "No hay agentes. Crea uno: easybits agents create --template <template>")
2671
2808
  )
2672
2809
  );
2673
2810
  }
@@ -2702,36 +2839,36 @@ var agents = {
2702
2839
  async run(ctx) {
2703
2840
  const like = str(ctx, "like");
2704
2841
  const from = str(ctx, "from");
2705
- if (like && from) throw usageError("Use --like or --from, not both.", this.usage);
2842
+ if (like && from) throw usageError(t("Use --like or --from, not both.", "Usa --like o --from, no los dos."), this.usage);
2706
2843
  const promptInline = str(ctx, "prompt");
2707
2844
  const promptFile = str(ctx, "prompt-file");
2708
- if (promptInline != null && promptFile) throw usageError("Use --prompt or --prompt-file, not both.", this.usage);
2845
+ if (promptInline != null && promptFile) throw usageError(t("Use --prompt or --prompt-file, not both.", "Usa --prompt o --prompt-file, no los dos."), this.usage);
2709
2846
  const mode = str(ctx, "prompt-mode");
2710
- if (mode && mode !== "append" && mode !== "replace") throw usageError("--prompt-mode must be append or replace.", this.usage);
2711
- if (promptFile && !existsSync9(promptFile)) throw usageError(`File not found: ${promptFile}`, this.usage);
2847
+ 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);
2848
+ if (promptFile && !existsSync9(promptFile)) throw usageError(t(`File not found: ${promptFile}`, `No existe el archivo: ${promptFile}`), this.usage);
2712
2849
  const mcpFile = str(ctx, "mcp-file");
2713
- if (mcpFile && !existsSync9(mcpFile)) throw usageError(`File not found: ${mcpFile}`, this.usage);
2714
- if (bool(ctx, "copy-files") && !like && !from) throw usageError("--copy-files needs --like or --from.", this.usage);
2850
+ if (mcpFile && !existsSync9(mcpFile)) throw usageError(t(`File not found: ${mcpFile}`, `No existe el archivo: ${mcpFile}`), this.usage);
2851
+ if (bool(ctx, "copy-files") && !like && !from) throw usageError(t("--copy-files needs --like or --from.", "--copy-files necesita --like o --from."), this.usage);
2715
2852
  if (from && !isLegacyExport(from)) {
2716
2853
  await applyTarget(ctx, from, { create: true, name: str(ctx, "name"), dryRun: bool(ctx, "dry-run"), usage: this.usage });
2717
2854
  return;
2718
2855
  }
2719
2856
  const source = from ? readExport(from, this.usage) : like ? await buildExport(ctx, await resolveRef(ctx, "agent", like), { secrets: true, fileContents: bool(ctx, "copy-files") }) : void 0;
2720
2857
  const template = str(ctx, "template") ?? source?.template;
2721
- if (!template) throw usageError("Missing --template (or --like / --from).", this.usage);
2858
+ if (!template) throw usageError(t("Missing --template (or --like / --from).", "Falta --template (o --like / --from)."), this.usage);
2722
2859
  const env = await envFrom(ctx, this.usage);
2723
2860
  const prompt = promptInline ?? (promptFile ? readFileSync9(promptFile, "utf8") : source?.systemPrompt);
2724
2861
  const promptMode = mode ?? source?.systemPromptMode;
2725
2862
  const multiline = !!prompt && /[\r\n]/.test(prompt.trimEnd());
2726
2863
  const promptAfter = multiline ? prompt : void 0;
2727
2864
  if (multiline && !MACHINE_TEMPLATES.has(template)) {
2728
- throw usageError(`A multi-line prompt needs a template with a machine (${[...MACHINE_TEMPLATES].join(", ")}).`, this.usage);
2865
+ throw usageError(t(`A multi-line prompt needs a template with a machine (${[...MACHINE_TEMPLATES].join(", ")}).`, `Un prompt de varios renglones necesita un template con m\xE1quina (${[...MACHINE_TEMPLATES].join(", ")}).`), this.usage);
2729
2866
  }
2730
2867
  if (prompt && !multiline) env.SYSTEM_PROMPT = prompt.trimEnd();
2731
2868
  if (promptMode && (prompt || mode) && !multiline) env.SYSTEM_PROMPT_MODE = promptMode;
2732
2869
  const mcpServers = mcpFile ? parseMcpFile(readFileSync9(mcpFile, "utf8"), this.usage) : source?.mcpServers;
2733
2870
  if (mcpServers && hasMasked(mcpServers)) {
2734
- throw usageError(`${from ?? "the source"} has masked MCP values (\u2026***).`, "Re-export with --show-secrets, or pass --mcp-file");
2871
+ 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"));
2735
2872
  }
2736
2873
  const name = str(ctx, "name") ?? (source?.name ? `${source.name}-copy` : void 0);
2737
2874
  const plan = {
@@ -2748,8 +2885,8 @@ var agents = {
2748
2885
  if (bool(ctx, "dry-run")) {
2749
2886
  emit(ctx, { dryRun: true, ...plan }, () => {
2750
2887
  for (const [k, v] of Object.entries(plan)) console.log(`${k.padEnd(12)} ${Array.isArray(v) ? v.join(", ") || "-" : v ?? "-"}`);
2751
- if (source) console.log("Env is never copied: pass the engine's keys with --dotenv.");
2752
- console.log("(dry run: nothing created)");
2888
+ if (source) console.log(t("Env is never copied: pass the engine's keys with --dotenv.", "El env nunca se copia: pasa las llaves del motor con --dotenv."));
2889
+ console.log(t("(dry run: nothing created)", "(simulaci\xF3n: no se cre\xF3 nada)"));
2753
2890
  });
2754
2891
  return;
2755
2892
  }
@@ -2764,11 +2901,11 @@ var agents = {
2764
2901
  const copied = source ? await applyExport(ctx, a.agentId, source, { files: bool(ctx, "copy-files") }) : void 0;
2765
2902
  if (promptAfter) await api(ctx, "PATCH", `/agents/${a.agentId}`, { systemPrompt: promptAfter, ...promptMode ? { systemPromptMode: promptMode } : {} });
2766
2903
  emit(ctx, { ...a, ...copied ? { copied } : {} }, () => {
2767
- console.log(`Agent: ${a.agentId}`);
2904
+ console.log(`${t("Agent: ", "Agente: ")} ${a.agentId}`);
2768
2905
  console.log(`Sandbox: ${a.sandboxId}`);
2769
2906
  console.log(`URL: ${a.agentUrl}`);
2770
- if (copied) console.log(`Copied: ${copied.skills.length} skill(s), ${copied.files.length} file(s)${copied.skippedFiles.length ? ` (${copied.skippedFiles.length} skipped: export without --with-files)` : ""}`);
2771
- console.log(`Talk to it: easybits agents try ${a.agentId} "hello"`);
2907
+ if (copied) console.log(t(`Copied: ${copied.skills.length} skill(s), ${copied.files.length} file(s)${copied.skippedFiles.length ? ` (${copied.skippedFiles.length} skipped: export without --with-files)` : ""}`, `Copiado: ${copied.skills.length} skill(s), ${copied.files.length} archivo(s)${copied.skippedFiles.length ? ` (${copied.skippedFiles.length} sin copiar: export sin --with-files)` : ""}`));
2908
+ console.log(t(`Talk to it: easybits agents try ${a.agentId} "hello"`, `H\xE1blale: easybits agents try ${a.agentId} "hola"`));
2772
2909
  });
2773
2910
  }
2774
2911
  },
@@ -2781,7 +2918,7 @@ var agents = {
2781
2918
  async run(ctx) {
2782
2919
  const id = need(ctx, 0, "agent", this.usage);
2783
2920
  const content = ctx.args.slice(1).join(" ");
2784
- if (!content) throw usageError("Missing <text>.", this.usage);
2921
+ if (!content) throw usageError(t("Missing <text>.", "Falta <text>."), this.usage);
2785
2922
  const eb = await getClient(ctx);
2786
2923
  const params = { content, sessionId: str(ctx, "session") };
2787
2924
  if (ctx.json) {
@@ -2802,10 +2939,10 @@ var agents = {
2802
2939
  async run(ctx) {
2803
2940
  const id = need(ctx, 0, "agent", this.usage);
2804
2941
  requireYesIfHeadless(ctx);
2805
- await confirm(ctx, `Destroy agent ${id} and its sandbox (including /data)?`, { typeName: id });
2942
+ await confirm(ctx, t(`Destroy agent ${id} and its sandbox (including /data)?`, `\xBFDestruir el agente ${id} y su sandbox (con /data)?`), { typeName: id });
2806
2943
  const eb = await getClient(ctx);
2807
2944
  const r = await eb.destroyAgent(id);
2808
- emit(ctx, { ...r, agentId: id }, () => console.log(`Destroyed ${id}`));
2945
+ emit(ctx, { ...r, agentId: id }, () => console.log(t(`Destroyed ${id}`, `Destruido ${id}`)));
2809
2946
  }
2810
2947
  },
2811
2948
  set,
@@ -2820,6 +2957,130 @@ var agents = {
2820
2957
  }
2821
2958
  };
2822
2959
 
2960
+ // src/commands/completion.ts
2961
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
2962
+ import { homedir as homedir4 } from "os";
2963
+ import { dirname as dirname3, join as join6 } from "path";
2964
+ var GLOBAL_FLAGS = ["--json", "--token", "--lang", "--help", "--version"];
2965
+ var AGENT_FLAGS = /* @__PURE__ */ new Set(["--agent", "--like"]);
2966
+ var findSub = (c, name) => Object.entries(c.subs ?? {}).find(([n, l]) => n === name || l.aliases?.includes(name));
2967
+ function completionFor(words, current2, commands) {
2968
+ const names = commands.filter((c) => !c.name.startsWith("__")).map((c) => c.name);
2969
+ const prev = words[words.length - 1];
2970
+ if (prev && AGENT_FLAGS.has(prev)) return { kind: "names", ref: "agent" };
2971
+ if (!words.length) return { kind: "words", words: [...names, "help"] };
2972
+ const find = (n2) => commands.find((c) => c.name === n2 || c.aliases?.includes(n2));
2973
+ const cmd = find(words[0]);
2974
+ if (words[0] === "help") return { kind: "words", words: words.length === 1 ? names : Object.keys(find(words[1])?.subs ?? {}) };
2975
+ if (!cmd) return { kind: "words", words: [] };
2976
+ const sub = cmd.subs && words[1] ? findSub(cmd, words[1]) : void 0;
2977
+ const leaf = sub?.[1] ?? cmd.leaf;
2978
+ if (current2.startsWith("-")) {
2979
+ const own = Object.keys(leaf?.options ?? {}).map((n2) => `--${n2}`);
2980
+ return { kind: "words", words: [...own, ...GLOBAL_FLAGS] };
2981
+ }
2982
+ if (cmd.subs && !sub) return { kind: "words", words: words.length === 1 ? Object.keys(cmd.subs) : [] };
2983
+ if (cmd.name === "completion") return { kind: "words", words: words.length === 1 ? ["zsh", "bash", "fish"] : [] };
2984
+ const opts = leaf?.options ?? {};
2985
+ let n = 0;
2986
+ for (let i = sub ? 2 : 1; i < words.length; i++) {
2987
+ const w = words[i];
2988
+ if (w.startsWith("--")) {
2989
+ const o = opts[w.slice(2)];
2990
+ if ((o?.type === "string" || w === "--token" || w === "--lang") && !w.includes("=")) i++;
2991
+ continue;
2992
+ }
2993
+ if (w.startsWith("-") && w !== "-") continue;
2994
+ n++;
2995
+ }
2996
+ const spec = REF_ARGS[sub ? `${cmd.name} ${sub[0]}` : cmd.name];
2997
+ if (spec && spec.index === n) return { kind: "names", ref: spec.kind };
2998
+ return { kind: "words", words: [] };
2999
+ }
3000
+ var cacheFile = () => join6(process.env.XDG_CACHE_HOME || join6(homedir4(), ".cache"), "easybits", "complete.json");
3001
+ async function namesOf(ctx, kind) {
3002
+ let cache2 = {};
3003
+ try {
3004
+ cache2 = JSON.parse(readFileSync10(cacheFile(), "utf8"));
3005
+ const c = cache2[kind];
3006
+ if (c && Date.now() - c.at < 5 * 6e4) return c.names;
3007
+ } catch {
3008
+ }
3009
+ const names = [...new Set((await listRefs(ctx, kind)).map((x) => x.name).filter((x) => !!x))];
3010
+ try {
3011
+ mkdirSync5(dirname3(cacheFile()), { recursive: true });
3012
+ writeFileSync6(cacheFile(), JSON.stringify({ ...cache2, [kind]: { at: Date.now(), names } }));
3013
+ } catch {
3014
+ }
3015
+ return names;
3016
+ }
3017
+ async function completeCmd(args, commands) {
3018
+ const rest = args[0] === "--" ? args.slice(1) : args;
3019
+ const current2 = rest.length ? rest[rest.length - 1] : "";
3020
+ const words = rest.slice(0, -1);
3021
+ let out = [];
3022
+ try {
3023
+ const want = completionFor(words, current2, commands);
3024
+ const ctx = { json: true, args: [], opts: {} };
3025
+ out = want.kind === "words" ? want.words : await namesOf(ctx, want.ref);
3026
+ } catch {
3027
+ out = [];
3028
+ }
3029
+ const cur = current2.toLowerCase();
3030
+ process.stdout.write(out.filter((w) => w.toLowerCase().startsWith(cur)).join("\n") + "\n");
3031
+ }
3032
+ function completionScript(shell) {
3033
+ if (shell === "bash") {
3034
+ return `# easybits \u2014 bash completion: eval "$(easybits completion bash)" en ~/.bashrc
3035
+ _easybits() {
3036
+ # Las palabras ANTES de cambiar IFS: el bash 3.2 de macOS junta \${arr[@]:1:n} con un IFS propio.
3037
+ local words=("\${COMP_WORDS[@]:1:COMP_CWORD-1}") cur="\${COMP_WORDS[COMP_CWORD]}"
3038
+ local IFS=$'\\n'
3039
+ COMPREPLY=( $(easybits __complete -- "\${words[@]}" "$cur" 2>/dev/null) )
3040
+ }
3041
+ complete -o default -F _easybits easybits
3042
+ `;
3043
+ }
3044
+ if (shell === "zsh") {
3045
+ return `#compdef easybits
3046
+ # easybits \u2014 zsh completion: source <(easybits completion zsh) en ~/.zshrc (despu\xE9s de compinit)
3047
+ _easybits() {
3048
+ local -a opts
3049
+ opts=("\${(@f)$(easybits __complete -- "\${(@)words[2,CURRENT-1]}" "\${words[CURRENT]}" 2>/dev/null)}")
3050
+ compadd -a opts
3051
+ }
3052
+ compdef _easybits easybits
3053
+ `;
3054
+ }
3055
+ if (shell === "fish") {
3056
+ return `# easybits \u2014 fish completion: easybits completion fish > ~/.config/fish/completions/easybits.fish
3057
+ complete -c easybits -f -a "(easybits __complete -- (commandline -opc)[2..-1] (commandline -ct) 2>/dev/null)"
3058
+ `;
3059
+ }
3060
+ return null;
3061
+ }
3062
+ var completion = {
3063
+ name: "completion",
3064
+ group: "Help",
3065
+ summary: "Shell completion script (zsh, bash, fish)",
3066
+ synopsis: "completion <shell>",
3067
+ leaf: {
3068
+ summary: "Print the completion script for your shell: commands, flags and your agents, sandboxes and databases",
3069
+ usage: "easybits completion <zsh|bash|fish>",
3070
+ examples: [
3071
+ "source <(easybits completion zsh) # add to ~/.zshrc, after compinit",
3072
+ 'eval "$(easybits completion bash)" # add to ~/.bashrc',
3073
+ "easybits completion fish > ~/.config/fish/completions/easybits.fish"
3074
+ ],
3075
+ async run(ctx) {
3076
+ const shell = need(ctx, 0, "zsh|bash|fish", this.usage);
3077
+ const script = completionScript(shell);
3078
+ if (!script) throw usageError(t(`Unknown shell "${shell}".`, `Shell desconocido "${shell}".`), this.usage);
3079
+ process.stdout.write(script);
3080
+ }
3081
+ }
3082
+ };
3083
+
2823
3084
  // src/commands/docs.ts
2824
3085
  import { spawn as spawn2 } from "child_process";
2825
3086
  import { resolveBaseUrl as resolveBaseUrl5 } from "@easybits.cloud/sdk";
@@ -2853,9 +3114,9 @@ var docs = {
2853
3114
  const res = await fetch(url);
2854
3115
  if (!res.ok) {
2855
3116
  throw new CliError(
2856
- `Docs section "${section}" not found (${res.status}).`,
3117
+ t(`Docs section "${section}" not found (${res.status}).`, `No existe la secci\xF3n "${section}" de los docs (${res.status}).`),
2857
3118
  2,
2858
- `See the section list: ${base}/llms.txt`,
3119
+ t(`See the section list: ${base}/llms.txt`, `Mira la lista de secciones: ${base}/llms.txt`),
2859
3120
  "usage",
2860
3121
  res.status
2861
3122
  );
@@ -2868,15 +3129,15 @@ var docs = {
2868
3129
 
2869
3130
  // src/commands/doctor.ts
2870
3131
  import { existsSync as existsSync10, statSync as statSync5 } from "fs";
2871
- import { homedir as homedir5 } from "os";
2872
- import { join as join7 } from "path";
3132
+ import { homedir as homedir6 } from "os";
3133
+ import { join as join8 } from "path";
2873
3134
  import { resolveBaseUrl as resolveBaseUrl6 } from "@easybits.cloud/sdk";
2874
3135
 
2875
3136
  // src/update.ts
2876
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
2877
- import { homedir as homedir4 } from "os";
2878
- import { dirname as dirname3, join as join6 } from "path";
2879
- var FILE = join6(process.env.XDG_CONFIG_HOME || join6(homedir4(), ".config"), "easybits", "update.json");
3137
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
3138
+ import { homedir as homedir5 } from "os";
3139
+ import { dirname as dirname4, join as join7 } from "path";
3140
+ var FILE = join7(process.env.XDG_CONFIG_HOME || join7(homedir5(), ".config"), "easybits", "update.json");
2880
3141
  var DAY = 24 * 60 * 60 * 1e3;
2881
3142
  var UPGRADE = "npm i -g @easybits.cloud/cli@latest";
2882
3143
  function newer(a, b) {
@@ -2888,7 +3149,7 @@ function newer(a, b) {
2888
3149
  var updateCheckDisabled = () => Boolean(process.env.EASYBITS_NO_UPDATE_CHECK || process.env.CI);
2889
3150
  function readCache() {
2890
3151
  try {
2891
- return JSON.parse(readFileSync10(FILE, "utf8"));
3152
+ return JSON.parse(readFileSync11(FILE, "utf8"));
2892
3153
  } catch {
2893
3154
  return {};
2894
3155
  }
@@ -2898,8 +3159,8 @@ async function fetchLatest(timeoutMs = 1500) {
2898
3159
  const r = await fetch("https://registry.npmjs.org/@easybits.cloud/cli/latest", { signal: AbortSignal.timeout(timeoutMs) });
2899
3160
  const latest = (await r.json()).version;
2900
3161
  if (!latest) return null;
2901
- mkdirSync5(dirname3(FILE), { recursive: true });
2902
- writeFileSync6(FILE, JSON.stringify({ latest, checkedAt: Date.now() }));
3162
+ mkdirSync6(dirname4(FILE), { recursive: true });
3163
+ writeFileSync7(FILE, JSON.stringify({ latest, checkedAt: Date.now() }));
2903
3164
  return latest;
2904
3165
  } catch {
2905
3166
  return null;
@@ -2911,19 +3172,19 @@ async function latestVersion() {
2911
3172
  if (c.latest && c.checkedAt && Date.now() - c.checkedAt < DAY) return c.latest;
2912
3173
  return await fetchLatest() ?? c.latest ?? null;
2913
3174
  }
2914
- function cachedNewer(current) {
2915
- if (updateCheckDisabled() || current === "dev") return null;
3175
+ function cachedNewer(current2) {
3176
+ if (updateCheckDisabled() || current2 === "dev") return null;
2916
3177
  const l = readCache().latest;
2917
- return l && newer(l, current) ? l : null;
3178
+ return l && newer(l, current2) ? l : null;
2918
3179
  }
2919
- async function updateNotice(current) {
2920
- if (current === "dev") return null;
3180
+ async function updateNotice(current2) {
3181
+ if (current2 === "dev") return null;
2921
3182
  const latest = await latestVersion();
2922
- return latest && newer(latest, current) ? `Update available: ${current} \u2192 ${latest}. Run: ${UPGRADE}` : null;
3183
+ return latest && newer(latest, current2) ? t(`Update available: ${current2} \u2192 ${latest}. Run: ${UPGRADE}`, `Hay versi\xF3n nueva: ${current2} \u2192 ${latest}. Corre: ${UPGRADE}`) : null;
2923
3184
  }
2924
3185
 
2925
3186
  // src/commands/doctor.ts
2926
- var VERSION = true ? "0.10.0" : "dev";
3187
+ var VERSION = true ? "0.12.0" : "dev";
2927
3188
  var doctor2 = {
2928
3189
  name: "doctor",
2929
3190
  group: "Account",
@@ -2936,49 +3197,49 @@ var doctor2 = {
2936
3197
  async run(ctx) {
2937
3198
  const checks = [];
2938
3199
  const major = Number(process.versions.node.split(".")[0]);
2939
- checks.push({ check: "node", ok: major >= 22, detail: process.versions.node, ...major >= 22 ? {} : { hint: "The CLI needs Node 22 or newer." } });
2940
- if (updateCheckDisabled()) checks.push({ check: "version", ok: true, detail: `${VERSION} (update check off)` });
3200
+ checks.push({ check: "node", ok: major >= 22, detail: process.versions.node, ...major >= 22 ? {} : { hint: t("The CLI needs Node 22 or newer.", "El CLI necesita Node 22 o m\xE1s nuevo.") } });
3201
+ if (updateCheckDisabled()) checks.push({ check: "version", ok: true, detail: t(`${VERSION} (update check off)`, `${VERSION} (sin revisar versiones)`) });
2941
3202
  else {
2942
3203
  const latest = await fetchLatest(3e3);
2943
3204
  const old = latest && VERSION !== "dev" && newer(latest, VERSION);
2944
3205
  checks.push({
2945
3206
  check: "version",
2946
3207
  ok: !old,
2947
- detail: latest ? `${VERSION}${old ? ` (latest ${latest})` : " (latest)"}` : `${VERSION} (npm unreachable)`,
2948
- ...old ? { hint: `Run: ${UPGRADE}` } : {}
3208
+ detail: latest ? `${VERSION}${old ? ` (${t("latest", "la m\xE1s nueva")} ${latest})` : t(" (latest)", " (la m\xE1s nueva)")}` : t(`${VERSION} (npm unreachable)`, `${VERSION} (npm no contesta)`),
3209
+ ...old ? { hint: `${t("Run", "Corre")}: ${UPGRADE}` } : {}
2949
3210
  });
2950
3211
  }
2951
3212
  const base = await resolveBaseUrl6();
2952
3213
  const health = await fetch(`${base.replace(/\/+$/, "")}/api/health`, { signal: AbortSignal.timeout(5e3) }).then((r) => ({ ok: r.ok, detail: `${base} (${r.status})` })).catch((e) => ({ ok: false, detail: `${base}: ${e.message}` }));
2953
- checks.push({ check: "api", ...health, ...health.ok ? {} : { hint: "Check your network or EASYBITS_URL." } });
3214
+ checks.push({ check: "api", ...health, ...health.ok ? {} : { hint: t("Check your network or EASYBITS_URL.", "Revisa tu red o EASYBITS_URL.") } });
2954
3215
  const rc = readRc();
2955
3216
  const source = process.env.EASYBITS_API_KEY ? "env EASYBITS_API_KEY" : ctx.token ? "--token" : rc.oauth?.accessToken ? "browser session (~/.easybitsrc)" : rc.apiKey ? "API key (~/.easybitsrc)" : null;
2956
- if (!source) checks.push({ check: "credential", ok: false, detail: "not logged in", hint: "Run: easybits login (or set EASYBITS_API_KEY)" });
3217
+ if (!source) checks.push({ check: "credential", ok: false, detail: t("not logged in", "sin sesi\xF3n"), hint: t("Run: easybits login (or set EASYBITS_API_KEY)", "Corre: easybits login (o define EASYBITS_API_KEY)") });
2957
3218
  else {
2958
3219
  try {
2959
3220
  const email = await fetchEmail(await resolveCredential(ctx));
2960
- checks.push({ check: "credential", ok: true, detail: `${email ?? "(unknown email)"} via ${source}` });
3221
+ checks.push({ check: "credential", ok: true, detail: `${email ?? t("(unknown email)", "(correo desconocido)")} ${t("via", "por")} ${source}` });
2961
3222
  } catch (e) {
2962
3223
  const err = toCliError(e);
2963
3224
  checks.push({ check: "credential", ok: false, detail: `${source}: ${err.message}`, hint: err.hint });
2964
3225
  }
2965
3226
  if (process.env.EASYBITS_API_KEY && (rc.apiKey || rc.oauth)) {
2966
- checks.push({ check: "credential-env", ok: true, detail: "EASYBITS_API_KEY wins over the saved login in ~/.easybitsrc" });
3227
+ checks.push({ check: "credential-env", ok: true, detail: t("EASYBITS_API_KEY wins over the saved login in ~/.easybitsrc", "EASYBITS_API_KEY le gana a la sesi\xF3n guardada en ~/.easybitsrc") });
2967
3228
  }
2968
3229
  }
2969
- const rcPath = join7(homedir5(), ".easybitsrc");
3230
+ const rcPath = join8(homedir6(), ".easybitsrc");
2970
3231
  if (existsSync10(rcPath)) {
2971
3232
  const mode = statSync5(rcPath).mode & 511;
2972
3233
  const ok2 = (mode & 63) === 0;
2973
- checks.push({ check: "rc-file", ok: ok2, detail: `${rcPath} ${mode.toString(8).padStart(3, "0")}`, ...ok2 ? {} : { hint: `It holds tokens. Run: chmod 600 ${rcPath}` } });
3234
+ 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}`) } });
2974
3235
  }
2975
3236
  const ok = checks.every((c) => c.ok);
2976
3237
  emit(ctx, { ok, checks }, () => {
2977
3238
  for (const c of checks) {
2978
- console.log(`${c.ok ? "ok " : "FAIL"} ${c.check.padEnd(14)} ${c.detail}`);
3239
+ console.log(`${c.ok ? "ok " : t("FAIL", "MAL ")} ${c.check.padEnd(14)} ${c.detail}`);
2979
3240
  if (!c.ok && c.hint) console.log(` ${"".padEnd(14)} \u2192 ${c.hint}`);
2980
3241
  }
2981
- console.log(ok ? "\nAll good." : "\nProblems found.");
3242
+ console.log(ok ? t("\nAll good.", "\nTodo bien.") : t("\nProblems found.", "\nHay problemas."));
2982
3243
  });
2983
3244
  if (!ok) process.exitCode = EXIT.API;
2984
3245
  }
@@ -3005,7 +3266,8 @@ var COMMANDS = [
3005
3266
  mcp,
3006
3267
  sshKey,
3007
3268
  sshProxy,
3008
- docs
3269
+ docs,
3270
+ completion
3009
3271
  ];
3010
3272
  function findCommand(name) {
3011
3273
  return COMMANDS.find((c) => c.name === name || c.aliases?.includes(name));
@@ -3033,13 +3295,191 @@ function didYouMean(input, candidates, prefix, canonical = (n) => n) {
3033
3295
  const hits = [...new Set(suggest(input, candidates).map(canonical))].slice(0, 3);
3034
3296
  if (!hits.length) return void 0;
3035
3297
  return `Did you mean this?
3036
- ${hits.map((h) => ` ${prefix}${h}`).join("\n")}`;
3298
+ ${hits.map((h2) => ` ${prefix}${h2}`).join("\n")}`;
3037
3299
  }
3038
3300
 
3301
+ // src/help.es.ts
3302
+ var HELP_ES = {
3303
+ "Account": "Cuenta",
3304
+ "Sign in with your browser (or save an API key)": "Entra con tu navegador (o guarda una API key)",
3305
+ "Sign in with your browser (OAuth2 + PKCE), or save an API key instead": "Entra con tu navegador (OAuth2 + PKCE), o guarda una API key en su lugar",
3306
+ "Only print the sign-in URL (e.g. for an agent to relay it)": "S\xF3lo imprime la URL para entrar (p. ej. para que un agente te la pase)",
3307
+ "Read an API key from stdin instead of the browser (same as -)": "Lee una API key por stdin en vez del navegador (igual que -)",
3308
+ "opens the browser; waits for you": "abre el navegador y te espera",
3309
+ 'agents: prints {"event":"login_url"} first': 'agentes: primero imprime {"event":"login_url"}',
3310
+ "API key from stdin (validated first)": "API key por stdin (se valida antes)",
3311
+ "CI/agents: the env var needs no login at all": "CI/agentes: con la variable no hace falta login",
3312
+ "Forget the saved session and API key": "Olvida la sesi\xF3n y la API key guardadas",
3313
+ "Remove the browser session and the API key from ~/.easybitsrc": "Quita la sesi\xF3n del navegador y la API key de ~/.easybitsrc",
3314
+ "Show which account and credential the CLI is using": "Muestra qu\xE9 cuenta y qu\xE9 credencial usa el CLI",
3315
+ "Show the account email and where the credential comes from": "Muestra el correo de la cuenta y de d\xF3nde sale la credencial",
3316
+ "Check the CLI setup: Node, version, credential, API; exits 1 on a problem": "Revisa el CLI: Node, versi\xF3n, credencial y API; sale con 1 si hay un problema",
3317
+ "Check Node, the CLI version, your credential and the API; exits 1 on a problem": "Revisa Node, la versi\xF3n del CLI, tu credencial y la API; sale con 1 si hay un problema",
3318
+ "one agent": "un agente",
3319
+ "Show your plan, storage and resource counts": "Muestra tu plan, almacenamiento y cu\xE1ntos recursos tienes",
3320
+ "Compute": "C\xF3mputo",
3321
+ "Firecracker microVMs (alias: sb)": "MicroVMs Firecracker (alias: sb)",
3322
+ "List your sandboxes": "Enlista tus sandboxes",
3323
+ "Create a sandbox and wait until it is running": "Crea un sandbox y espera a que est\xE9 corriendo",
3324
+ "Base template (default ubuntu): ubuntu, python, node, code-interpreter\u2026": "Template base (default ubuntu): ubuntu, python, node, code-interpreter\u2026",
3325
+ "Human label (lets you `ssh <name>.ghosty`)": "Nombre para personas (permite `ssh <nombre>.ghosty`)",
3326
+ "Time to live before auto-destroy (was --timeout)": "Tiempo de vida antes de autodestruirse (antes --timeout)",
3327
+ "VM size class (gated by plan)": "Tama\xF1o de la VM (seg\xFAn tu plan)",
3328
+ "Environment variable, non-secret (repeatable)": "Variable de entorno, no secreta (repetible)",
3329
+ "KEY=VALUE lines (dotenv); - reads stdin. Use it for secrets": "Renglones KEY=VALUE (dotenv); - lee stdin. \xDAsalo para secretos",
3330
+ "Return right away instead of waiting for running": "Regresa de inmediato en vez de esperar a que corra",
3331
+ "Show one sandbox": "Muestra un sandbox",
3332
+ "Run a shell command inside a sandbox": "Corre un comando de shell dentro de un sandbox",
3333
+ "Working directory": "Directorio de trabajo",
3334
+ "Kill the command after this long": "Mata el comando despu\xE9s de este tiempo",
3335
+ "Read the sandbox journal (optionally one systemd unit)": "Lee el journal del sandbox (opcionalmente de una unit de systemd)",
3336
+ "systemd unit to filter": "Unit de systemd a filtrar",
3337
+ "Last N lines (default 200)": "\xDAltimos N renglones (default 200)",
3338
+ 'journalctl time spec, e.g. "10 min ago"': 'Tiempo en sintaxis de journalctl, p. ej. "10 min ago"',
3339
+ "Only lines matching": "S\xF3lo los renglones que coincidan",
3340
+ "List, read or write files inside a sandbox": "Enlista, lee o escribe archivos dentro de un sandbox",
3341
+ "write: inline content instead of a local file": "write: contenido en l\xEDnea en vez de un archivo local",
3342
+ "read: save to a local file (binary-safe)": "read: guarda en un archivo local (seguro con binarios)",
3343
+ "Suspend to disk (frees CPU; TTL paused)": "Lo suspende a disco (libera CPU; el TTL se pausa)",
3344
+ "Wake a suspended sandbox": "Despierta un sandbox suspendido",
3345
+ "Rewrite env on wake, non-secret (repeatable)": "Reescribe el env al despertar, no secreto (repetible)",
3346
+ "Destroy a sandbox (irreversible)": "Destruye un sandbox (irreversible)",
3347
+ "Skip the confirmation (required without a terminal or with --json)": "Salta la confirmaci\xF3n (obligatorio sin terminal o con --json)",
3348
+ "Capture a copy-on-write snapshot without stopping the box": "Toma un snapshot copy-on-write sin detener la caja",
3349
+ "Snapshot label": "Nombre del snapshot",
3350
+ "Agents running in their own sandbox": "Agentes que corren en su propio sandbox",
3351
+ "List your agents": "Enlista tus agentes",
3352
+ "Show one agent: record, prompt, MCP servers, skills and files": "Muestra un agente: registro, prompt, servidores MCP, skills y archivos",
3353
+ "Only these fields: agentId, name, template, status, lastError, sandboxId, agentUrl, embedToken, createdAt, expiresAt, systemPrompt, systemPromptMode, mcpServers, skills, files": "S\xF3lo estos campos: agentId, name, template, status, lastError, sandboxId, agentUrl, embedToken, createdAt, expiresAt, systemPrompt, systemPromptMode, mcpServers, skills, files",
3354
+ "Write the system prompt to a file instead of printing it": "Escribe el system prompt en un archivo en vez de imprimirlo",
3355
+ "edit, then: agents set helper --prompt-file PROMPT.md": "ed\xEDtalo y luego: agents set helper --prompt-file PROMPT.md",
3356
+ "Create an agent from a template, or clone one (--like / --from)": "Crea un agente desde un template, o clona uno (--like / --from)",
3357
+ "Agent template (see: easybits docs agents)": "Template del agente (ver: easybits docs agents)",
3358
+ "Clone another agent's setup: template, prompt, MCP, skills (never its env)": "Clona la configuraci\xF3n de otro agente: template, prompt, MCP, skills (nunca su env)",
3359
+ "Clone from `easybits agents export` output": "Clona desde lo que dio `easybits agents export`",
3360
+ "Label": "Nombre",
3361
+ "System prompt (ghosty-lite, goose)": "System prompt (ghosty-lite, goose)",
3362
+ "System prompt from a file": "System prompt desde un archivo",
3363
+ "append (default) or replace the engine's prompt": "append (default) o reemplaza el prompt del motor",
3364
+ "MCP servers: JSON array or { servers } (ACP templates)": "Servidores MCP: arreglo JSON o { servers } (templates ACP)",
3365
+ "--like/--from: also copy the knowledge files in /data/work": "--like/--from: copia tambi\xE9n los archivos de conocimiento de /data/work",
3366
+ "Env for the agent, non-secret (repeatable)": "Env del agente, no secreto (repetible)",
3367
+ "Lifetime before auto-destroy (was --timeout)": "Vida antes de autodestruirse (antes --timeout)",
3368
+ "Print the plan; create nothing": "Imprime el plan; no crea nada",
3369
+ "Send a message (agent by id or name); streams the reply": "Manda un mensaje (agente por id o nombre); la respuesta llega en streaming",
3370
+ "Continue a conversation": "Sigue una conversaci\xF3n",
3371
+ "Destroy an agent and its sandbox": "Destruye un agente y su sandbox",
3372
+ "asks you to type the id": "te pide teclear el id",
3373
+ "Change the system prompt (ghosty-lite, goose); takes effect without a reboot": "Cambia el system prompt (ghosty-lite, goose); entra sin reiniciar",
3374
+ "New system prompt": "System prompt nuevo",
3375
+ "Read the prompt from a file (- = stdin)": "Lee el prompt de un archivo (- = stdin)",
3376
+ "append (to the engine's own) or replace it": "append (al del motor) o lo reemplaza",
3377
+ "Show what would change; change nothing": "Ense\xF1a qu\xE9 cambiar\xEDa; no cambia nada",
3378
+ "Knowledge files in /data/work: list, read, upload (several at once) or delete": "Archivos de conocimiento en /data/work: enlista, lee, sube (varios a la vez) o borra",
3379
+ "get: save to a local file (binary-safe)": "get: guarda en un archivo local (seguro con binarios)",
3380
+ "put: folder inside /data/work (default: its root)": "put: carpeta dentro de /data/work (default: la ra\xEDz)",
3381
+ "Skills in /data/agent/skills: list, read, add from a file or folder, remove": "Skills en /data/agent/skills: enlista, lee, agrega desde un archivo o carpeta, quita",
3382
+ "add: a single SKILL.md": "add: un solo SKILL.md",
3383
+ "add: a folder with SKILL.md and its assets": "add: una carpeta con SKILL.md y sus archivos",
3384
+ "add: slug (default: the frontmatter name, else the folder)": "add: slug (default: el name del frontmatter, si no, la carpeta)",
3385
+ "get: download the whole skill into this folder": "get: baja la skill entera a esta carpeta",
3386
+ "add/rm: restart the agent so it sees the change (new session)": "add/rm: reinicia el agente para que vea el cambio (sesi\xF3n nueva)",
3387
+ "The agent's own MCP servers: show them, or replace the whole list (restarts it)": "Los servidores MCP propios del agente: verlos o reemplazar la lista entera (lo reinicia)",
3388
+ "set: JSON array (or { servers }); - = stdin. [] removes all": "set: arreglo JSON (o { servers }); - = stdin. [] quita todos",
3389
+ "get: print env/header values unmasked": "get: imprime los valores de env/headers sin tapar",
3390
+ "Restart the agent (picks up skills, MCP and prompt mode; starts a new session)": "Reinicia el agente (toma skills, MCP y modo del prompt; abre sesi\xF3n nueva)",
3391
+ "One full turn as text (no stream): verify what you just configured": "Un turno completo como texto (sin streaming): verifica lo que acabas de configurar",
3392
+ "Continue this session (printed after each turn)": "Sigue esta sesi\xF3n (se imprime despu\xE9s de cada turno)",
3393
+ "Start a fresh session": "Empieza una sesi\xF3n nueva",
3394
+ "Read the journal of the agent's sandbox": "Lee el journal del sandbox del agente",
3395
+ "Check an agent: status, sandbox, prompt, skills frontmatter, MCP; exits 1 on a problem": "Revisa un agente: estado, sandbox, prompt, frontmatter de skills, MCP; sale con 1 si hay un problema",
3396
+ "Export an agent as a file you can edit and apply (secrets as ${NAME}, never their value)": "Exporta un agente como archivo que puedes editar y aplicar (secretos como ${NOMBRE}, nunca su valor)",
3397
+ "A folder (agent.yaml + skills/ + files/) or a .yaml/.json file": "Una carpeta (agent.yaml + skills/ + files/) o un archivo .yaml/.json",
3398
+ "Legacy clone JSON (0.9) with MCP values unmasked": "JSON de clonado de 0.9 con los valores del MCP sin tapar",
3399
+ "Legacy clone JSON (0.9) with the file contents": "JSON de clonado de 0.9 con el contenido de los archivos",
3400
+ "agent.yaml + skills/ + files/, versionable in git": "agent.yaml + skills/ + files/, versionable en git",
3401
+ "after editing: see the plan": "despu\xE9s de editarlo: mira el plan",
3402
+ "Make an agent match a file from `agents export` (plan, then apply)": "Hace que un agente coincida con un archivo de `agents export` (plan y luego aplica)",
3403
+ "Apply an agent file or folder: shows the plan (+ add, ~ change, - remove, ! skipped), then applies it": "Aplica un archivo o carpeta de agente: ense\xF1a el plan (+ agrega, ~ cambia, - quita, ! se salta) y luego lo aplica",
3404
+ "Apply to this agent (default: the id in the file)": "Aplicarlo a este agente (default: el id del archivo)",
3405
+ "Make a new agent from the file instead": "Crea un agente nuevo desde el archivo",
3406
+ "--create: name for the new agent": "--create: nombre del agente nuevo",
3407
+ "Also remove what the file doesn't list (env, MCP, skills, files)": "Quita tambi\xE9n lo que el archivo no enlista (env, MCP, skills, archivos)",
3408
+ "Show the plan; change nothing": "Ense\xF1a el plan; no cambia nada",
3409
+ "${ANTHROPIC_API_KEY} in the file gets this value": "${ANTHROPIC_API_KEY} del archivo toma este valor",
3410
+ "a copy": "una copia",
3411
+ "what would be removed": "lo que se quitar\xEDa",
3412
+ "Hosting": "Hosting",
3413
+ "Permanent machines and their releases (easybits deploy <machine> publishes)": "M\xE1quinas permanentes y sus releases (easybits deploy <m\xE1quina> publica)",
3414
+ "List your permanent machines": "Enlista tus m\xE1quinas permanentes",
3415
+ "Publish the machine's current app code as a new release": "Publica el c\xF3digo actual de la app como un release nuevo",
3416
+ "Release note": "Nota del release",
3417
+ "same thing, shorter": "lo mismo, m\xE1s corto",
3418
+ "List releases, newest first": "Enlista los releases, el m\xE1s nuevo primero",
3419
+ "How many": "Cu\xE1ntos",
3420
+ "Tail the app's own log": "Los \xFAltimos renglones del log de la app",
3421
+ "Last N lines": "\xDAltimos N renglones",
3422
+ "Put a previous release back on the same machine (data untouched)": "Vuelve a poner un release anterior en la misma m\xE1quina (datos intactos)",
3423
+ "App env vars stored encrypted in your vault": "Variables de entorno de la app, cifradas en tu vault",
3424
+ "Custom domains with auto TLS on a sandbox or machine": "Dominios propios con TLS autom\xE1tico en un sandbox o m\xE1quina",
3425
+ "List domains attached to a sandbox": "Enlista los dominios de un sandbox",
3426
+ "Attach a domain to a port; prints the DNS record to create": "Liga un dominio a un puerto; imprime el registro DNS que hay que crear",
3427
+ "Port inside the sandbox (required)": "Puerto dentro del sandbox (obligatorio)",
3428
+ "Check DNS + HTTPS for a domain": "Revisa DNS + HTTPS de un dominio",
3429
+ "Detach a domain": "Desliga un dominio",
3430
+ "Write the GitHub Actions deploy workflow for this repo": "Escribe el workflow de GitHub Actions que despliega este repo",
3431
+ "Write the GitHub Actions workflow that deploys this repo on every push to main": "Escribe el workflow de GitHub Actions que despliega este repo en cada push a main",
3432
+ "Where the app lives in the machine (default /srv/app)": "D\xF3nde vive la app en la m\xE1quina (default /srv/app)",
3433
+ "Port your app listens on (default 3000)": "Puerto en el que escucha tu app (default 3000)",
3434
+ "Data": "Datos",
3435
+ "SQL databases (libSQL)": "Bases de datos SQL (libSQL)",
3436
+ "List your databases": "Enlista tus bases de datos",
3437
+ "Create a database": "Crea una base de datos",
3438
+ "What it is for": "Para qu\xE9 es",
3439
+ "Delete a database (irreversible)": "Borra una base de datos (irreversible)",
3440
+ "asks you to type the name": "te pide teclear el nombre",
3441
+ "scripts and agents": "scripts y agentes",
3442
+ "List tables with row counts and columns": "Enlista las tablas con su n\xFAmero de filas y columnas",
3443
+ "Run one SQL statement (by database id or name)": "Corre una sentencia SQL (por id o nombre de la base)",
3444
+ "Positional ? parameter (repeatable)": "Par\xE1metro ? posicional (repetible)",
3445
+ "Storage": "Almacenamiento",
3446
+ "Your files in EasyBits storage": "Tus archivos en el almacenamiento de EasyBits",
3447
+ "List your files": "Enlista tus archivos",
3448
+ "Upload a local file": "Sube un archivo local",
3449
+ "Delete a file (soft delete, 7-day trash)": "Borra un archivo (a la papelera 7 d\xEDas)",
3450
+ "List your static websites": "Enlista tus sitios est\xE1ticos",
3451
+ "Show storage providers": "Muestra los proveedores de almacenamiento",
3452
+ "MCP": "MCP",
3453
+ "Print the MCP config JSON for your agent or editor": "Imprime el JSON de configuraci\xF3n MCP para tu agente o editor",
3454
+ "Print MCP config JSON: streamable HTTP with your key, or stdio (npx @easybits.cloud/mcp)": "Imprime el JSON de configuraci\xF3n MCP: streamable HTTP con tu key, o stdio (npx @easybits.cloud/mcp)",
3455
+ "stdio config (npx @easybits.cloud/mcp) instead of streamable HTTP": "Configuraci\xF3n stdio (npx @easybits.cloud/mcp) en vez de streamable HTTP",
3456
+ "SSH": "SSH",
3457
+ "Print your public key for sandbox SSH (creates it once)": "Imprime tu llave p\xFAblica para SSH a un sandbox (la crea una vez)",
3458
+ "Print your public key for sandbox_ssh_enable (creates ~/.ssh/easybits_ed25519 once)": "Imprime tu llave p\xFAblica para sandbox_ssh_enable (crea ~/.ssh/easybits_ed25519 una vez)",
3459
+ "SSH tunnel over 443 (use as ssh ProxyCommand)": "T\xFAnel SSH por el 443 (\xFAsalo como ProxyCommand de ssh)",
3460
+ "SSH tunnel over 443; meant to be the ProxyCommand in ~/.ssh/config": "T\xFAnel SSH por el 443; pensado como el ProxyCommand de ~/.ssh/config",
3461
+ "with the ~/.ssh/config snippet from `easybits --help`": "con el bloque de ~/.ssh/config de `easybits --help`",
3462
+ "Help": "Ayuda",
3463
+ "Print the docs URL, or a docs section as markdown": "Imprime la URL de los docs, o una secci\xF3n de los docs en markdown",
3464
+ "English docs": "Docs en ingl\xE9s",
3465
+ "Open the docs in your browser": "Abre los docs en tu navegador",
3466
+ "Shell completion script (zsh, bash, fish)": "Script de autocompletado para tu shell (zsh, bash, fish)",
3467
+ "Print the completion script for your shell: commands, flags and your agents, sandboxes and databases": "Imprime el script de autocompletado de tu shell: comandos, flags y tus agentes, sandboxes y bases",
3468
+ "add to ~/.zshrc, after compinit": "agr\xE9galo a ~/.zshrc, despu\xE9s de compinit",
3469
+ "add to ~/.bashrc": "agr\xE9galo a ~/.bashrc"
3470
+ };
3471
+
3039
3472
  // src/help.ts
3040
- var GLOBAL_FLAGS = `Global flags:
3473
+ var h = (en) => en && lang() === "es" ? HELP_ES[en] ?? en : en ?? "";
3474
+ function example(line2) {
3475
+ const i = line2.indexOf("# ");
3476
+ if (i === -1 || lang() !== "es") return line2;
3477
+ return line2.slice(0, i + 2) + h(line2.slice(i + 2).trim());
3478
+ }
3479
+ var GLOBAL_FLAGS_EN = `Global flags:
3041
3480
  --json Machine output: JSON on stdout, errors included
3042
3481
  --token <key> API key for this call (prefer EASYBITS_API_KEY: argv leaks to ps)
3482
+ --lang <es|en> Language (default: from LANG; also EASYBITS_LANG)
3043
3483
  -h, --help Help (also: easybits <command> --help)
3044
3484
  -v, --version Print the CLI version
3045
3485
 
@@ -3057,15 +3497,43 @@ Env: EASYBITS_API_KEY (API key), EASYBITS_URL (API server, default https://www.e
3057
3497
  EASYBITS_NO_UPDATE_CHECK=1 (no daily "update available" notice on stderr)
3058
3498
 
3059
3499
  Exit codes: 0 ok, 1 API error, 2 usage error, 3 not logged in or bad key`;
3500
+ var GLOBAL_FLAGS_ES = `Flags globales:
3501
+ --json Salida para m\xE1quinas: JSON en stdout, errores incluidos
3502
+ --token <key> API key para esta llamada (mejor EASYBITS_API_KEY: argv se ve en ps)
3503
+ --lang <es|en> Idioma (default: el de LANG; tambi\xE9n EASYBITS_LANG)
3504
+ -h, --help Ayuda (tambi\xE9n: easybits <comando> --help)
3505
+ -v, --version Imprime la versi\xF3n del CLI
3506
+
3507
+ Nombres (como gh): sustantivo y luego verbo \u2014 easybits <sustantivo> <verbo>. Las formas
3508
+ viejas siguen funcionando y avisan la nueva en stderr (nunca con --json).
3509
+
3510
+ Nombres o ids: agentes, sandboxes/m\xE1quinas y bases aceptan cualquiera de los dos
3511
+ (exacto, sin distinguir may\xFAsculas; dos con el mismo nombre \u2192 sale con 2 y los ids).
3512
+
3513
+ Los borrados (sandboxes destroy, agents destroy, db rm, domains rm, files rm, agents files|skills rm)
3514
+ preguntan antes; sin terminal o con --json pasa -y/--yes, o salen con 2.
3515
+ Lo que escribe en un agente (agents set|create|files|skills|mcp) acepta --dry-run: ense\xF1a el
3516
+ cambio y no toca nada.
3517
+
3518
+ Env: EASYBITS_API_KEY (API key), EASYBITS_URL (servidor de la API, default https://www.easybits.cloud),
3519
+ EASYBITS_NO_UPDATE_CHECK=1 (sin el aviso diario de versi\xF3n nueva en stderr)
3520
+
3521
+ C\xF3digos de salida: 0 ok, 1 error de la API, 2 error de uso, 3 sin sesi\xF3n o key inv\xE1lida`;
3522
+ var globalFlags = () => lang() === "es" ? GLOBAL_FLAGS_ES : GLOBAL_FLAGS_EN;
3060
3523
  var COL = 17;
3061
3524
  function globalHelp(version) {
3062
3525
  const groups = /* @__PURE__ */ new Map();
3063
3526
  for (const c of COMMANDS) groups.set(c.group, [...groups.get(c.group) ?? [], c]);
3064
- const lines = [`easybits ${version} \u2014 the cloud for AI agents, from your terminal`, "", "Usage: easybits <command> [subcommand] [args] [flags]", ""];
3527
+ const lines = [
3528
+ `easybits ${version} \u2014 ${t("the cloud for AI agents, from your terminal", "la nube para agentes de IA, desde tu terminal")}`,
3529
+ "",
3530
+ t("Usage: easybits <command> [subcommand] [args] [flags]", "Uso: easybits <comando> [subcomando] [args] [flags]"),
3531
+ ""
3532
+ ];
3065
3533
  for (const [group, cmds] of groups) {
3066
- lines.push(`${group}:`);
3534
+ lines.push(`${h(group)}:`);
3067
3535
  for (const c of cmds) {
3068
- lines.push(` ${c.synopsis.padEnd(COL)}${c.summary}`);
3536
+ lines.push(` ${c.synopsis.padEnd(COL)}${h(c.summary)}`);
3069
3537
  if (c.subs) {
3070
3538
  let row = "";
3071
3539
  for (const n of Object.keys(c.subs)) {
@@ -3079,36 +3547,36 @@ function globalHelp(version) {
3079
3547
  }
3080
3548
  lines.push("");
3081
3549
  }
3082
- lines.push(GLOBAL_FLAGS, "", "SSH to a sandbox \u2014 add to ~/.ssh/config:", indent(SSH_CONFIG_SNIPPET, 2), "");
3083
- lines.push("Docs: https://www.easybits.cloud/docs/cli.md (or: easybits docs cli)");
3550
+ lines.push(globalFlags(), "", t("SSH to a sandbox \u2014 add to ~/.ssh/config:", "SSH a un sandbox \u2014 agrega a ~/.ssh/config:"), indent(SSH_CONFIG_SNIPPET, 2), "");
3551
+ lines.push(t("Docs: https://www.easybits.cloud/en/docs/cli.md (or: easybits docs cli --en)", "Docs: https://www.easybits.cloud/docs/cli.md (o: easybits docs cli)"));
3084
3552
  return lines.join("\n");
3085
3553
  }
3086
3554
  function commandHelp(c) {
3087
3555
  if (c.leaf) return leafHelp(c.leaf);
3088
3556
  const subs = Object.entries(c.subs ?? {});
3089
3557
  const width = Math.max(...subs.map(([n]) => n.length)) + 2;
3090
- const lines = [`easybits ${c.name} \u2014 ${c.summary}`, "", `Usage: easybits ${c.name} <subcommand> [args] [flags]`, ""];
3091
- if (c.aliases?.length) lines.push(`Aliases: ${c.aliases.join(", ")}`, "");
3092
- lines.push("Subcommands:");
3093
- for (const [n, l] of subs) lines.push(` ${n.padEnd(width)}${l.summary}`);
3094
- lines.push("", `Run: easybits ${c.name} <subcommand> --help`);
3558
+ const lines = [`easybits ${c.name} \u2014 ${h(c.summary)}`, "", t(`Usage: easybits ${c.name} <subcommand> [args] [flags]`, `Uso: easybits ${c.name} <subcomando> [args] [flags]`), ""];
3559
+ if (c.aliases?.length) lines.push(`${t("Aliases", "Alias")}: ${c.aliases.join(", ")}`, "");
3560
+ lines.push(t("Subcommands:", "Subcomandos:"));
3561
+ for (const [n, l] of subs) lines.push(` ${n.padEnd(width)}${h(l.summary)}`);
3562
+ lines.push("", t(`Run: easybits ${c.name} <subcommand> --help`, `Corre: easybits ${c.name} <subcomando> --help`));
3095
3563
  return lines.join("\n");
3096
3564
  }
3097
3565
  function leafHelp(l) {
3098
- const lines = [l.summary, "", `Usage: ${l.usage}`];
3099
- if (l.aliases?.length) lines.push(`Aliases: ${l.aliases.join(", ")}`);
3566
+ const lines = [h(l.summary), "", `${t("Usage", "Uso")}: ${l.usage}`];
3567
+ if (l.aliases?.length) lines.push(`${t("Aliases", "Alias")}: ${l.aliases.join(", ")}`);
3100
3568
  const opts = Object.entries(l.options ?? {});
3101
3569
  if (opts.length) {
3102
3570
  const flag = ([n, o]) => `${o.short ? `-${o.short}, ` : ""}--${n}${o.type === "string" ? ` <${o.value ?? "value"}>` : ""}`;
3103
3571
  const width = Math.max(...opts.map((o) => flag(o).length)) + 2;
3104
3572
  lines.push("", "Flags:");
3105
- for (const o of opts) lines.push(` ${flag(o).padEnd(width)}${o[1].description ?? ""}`);
3573
+ for (const o of opts) lines.push(` ${flag(o).padEnd(width)}${h(o[1].description)}`);
3106
3574
  }
3107
3575
  if (l.examples?.length) {
3108
- lines.push("", "Examples:");
3109
- for (const e of l.examples) lines.push(` ${e}`);
3576
+ lines.push("", t("Examples:", "Ejemplos:"));
3577
+ for (const e of l.examples) lines.push(` ${example(e)}`);
3110
3578
  }
3111
- lines.push("", "Add --json for machine output.");
3579
+ lines.push("", t("Add --json for machine output.", "Agrega --json para salida de m\xE1quina."));
3112
3580
  return lines.join("\n");
3113
3581
  }
3114
3582
  function indent(s, n) {
@@ -3127,7 +3595,7 @@ function positionals(argv2) {
3127
3595
  for (let i = 0; i < argv2.length; i++) {
3128
3596
  const a = argv2[i];
3129
3597
  if (a === "--") break;
3130
- if (a === "--token") {
3598
+ if (a === "--token" || a === "--lang") {
3131
3599
  i++;
3132
3600
  continue;
3133
3601
  }
@@ -3149,20 +3617,20 @@ function normalizeArgs(input) {
3149
3617
  const at = (n) => pos[n] != null ? argv2[pos[n]] : void 0;
3150
3618
  if (at(0) === "config") {
3151
3619
  argv2.splice(pos[0], 1, "mcp", "config");
3152
- notes.push("`easybits config` is now `easybits mcp config`.");
3620
+ notes.push(["`easybits config` is now `easybits mcp config`.", "`easybits config` ahora es `easybits mcp config`."]);
3153
3621
  } else if (at(0) === "mcp" && at(1) == null && !help) {
3154
3622
  argv2.splice(pos[0] + 1, 0, "config");
3155
3623
  argv2.push("--stdio");
3156
- notes.push("`easybits mcp` is now `easybits mcp config --stdio`.");
3624
+ notes.push(["`easybits mcp` is now `easybits mcp config --stdio`.", "`easybits mcp` ahora es `easybits mcp config --stdio`."]);
3157
3625
  }
3158
3626
  if (at(0) === "deploy") {
3159
3627
  const sub = at(1);
3160
3628
  if (sub && MACHINE_SUBS.includes(sub) && sub !== "deploy") {
3161
3629
  argv2[pos[0]] = "machines";
3162
- notes.push(`\`easybits deploy ${sub}\` is now \`easybits machines ${sub}\`.`);
3630
+ notes.push([`\`easybits deploy ${sub}\` is now \`easybits machines ${sub}\`.`, `\`easybits deploy ${sub}\` ahora es \`easybits machines ${sub}\`.`]);
3163
3631
  } else if (sub === "deploy") {
3164
3632
  argv2[pos[0]] = "machines";
3165
- notes.push("`easybits deploy deploy` is now `easybits deploy <machine>`.");
3633
+ notes.push(["`easybits deploy deploy` is now `easybits deploy <machine>`.", "`easybits deploy deploy` ahora es `easybits deploy <m\xE1quina>`."]);
3166
3634
  } else {
3167
3635
  argv2.splice(pos[0], 1, "machines", "deploy");
3168
3636
  }
@@ -3170,7 +3638,7 @@ function normalizeArgs(input) {
3170
3638
  }
3171
3639
  if ((at(0) === "machines" || at(0) === "machine") && at(1) === "release") {
3172
3640
  argv2[pos[1]] = "deploy";
3173
- notes.push("`machines release` is now `machines deploy`.");
3641
+ notes.push(["`machines release` is now `machines deploy`.", "`machines release` ahora es `machines deploy`."]);
3174
3642
  }
3175
3643
  const noun = SANDBOX_NAMES.includes(at(0) ?? "") ? "sandboxes" : AGENT_NAMES.includes(at(0) ?? "") ? "agents" : null;
3176
3644
  if (noun && (at(1) === "create" || at(1) === "new")) {
@@ -3178,7 +3646,10 @@ function normalizeArgs(input) {
3178
3646
  for (let i = 0; i < (stop === -1 ? argv2.length : stop); i++) {
3179
3647
  if (argv2[i] === "--timeout" || argv2[i].startsWith("--timeout=")) {
3180
3648
  argv2[i] = argv2[i].replace("--timeout", "--ttl");
3181
- notes.push(`\`${noun} create --timeout\` is now \`--ttl\`${noun === "sandboxes" ? " (`--timeout` stays for `exec`)" : ""}.`);
3649
+ notes.push([
3650
+ `\`${noun} create --timeout\` is now \`--ttl\`${noun === "sandboxes" ? " (`--timeout` stays for `exec`)" : ""}.`,
3651
+ `\`${noun} create --timeout\` ahora es \`--ttl\`${noun === "sandboxes" ? " (`--timeout` sigue en `exec`)" : ""}.`
3652
+ ]);
3182
3653
  }
3183
3654
  }
3184
3655
  }
@@ -3186,10 +3657,11 @@ function normalizeArgs(input) {
3186
3657
  }
3187
3658
 
3188
3659
  // src/index.ts
3189
- var VERSION2 = true ? "0.10.0" : "dev";
3660
+ var VERSION2 = true ? "0.12.0" : "dev";
3190
3661
  var GLOBAL_OPTIONS = {
3191
3662
  json: { type: "boolean" },
3192
3663
  token: { type: "string" },
3664
+ lang: { type: "string" },
3193
3665
  help: { type: "boolean", short: "h" },
3194
3666
  version: { type: "boolean", short: "v" }
3195
3667
  };
@@ -3206,7 +3678,7 @@ function positionalIndexes(argv2) {
3206
3678
  for (let i = 0; i < argv2.length; i++) {
3207
3679
  const a = argv2[i];
3208
3680
  if (a === "--") break;
3209
- if (a === "--token") {
3681
+ if (a === "--token" || a === "--lang") {
3210
3682
  i++;
3211
3683
  continue;
3212
3684
  }
@@ -3229,32 +3701,35 @@ function parse(argv2, skip, leaf) {
3229
3701
  throw usageError(e.message.split("\n")[0].split(". To specify")[0].replace(/\.?$/, "."), leaf?.usage);
3230
3702
  }
3231
3703
  }
3232
- function findSub(cmd, name) {
3704
+ function findSub2(cmd, name) {
3233
3705
  return Object.entries(cmd.subs ?? {}).find(([n, l]) => n === name || l.aliases?.includes(name));
3234
3706
  }
3235
3707
  function unknownCommand(name) {
3236
- const e = usageError(`Unknown command "${name}".`);
3708
+ const e = usageError(t(`Unknown command "${name}".`, `Comando desconocido "${name}".`));
3237
3709
  const names = COMMANDS.flatMap((c) => [c.name, ...c.aliases ?? []]);
3238
3710
  const hint = didYouMean(name, names, "easybits ", (n) => findCommand(n)?.name ?? n);
3239
- if (hint) e.hint = `${hint}
3240
- Run: easybits --help`;
3711
+ if (hint) e.hint = `${localHint(hint)}
3712
+ ${t("Run", "Corre")}: easybits --help`;
3241
3713
  return e;
3242
3714
  }
3243
3715
  function unknownSub(cmd, name) {
3244
- const e = usageError(`Unknown subcommand "${cmd.name} ${name}".`, `easybits ${cmd.name} <${Object.keys(cmd.subs ?? {}).join("|")}>`);
3716
+ const e = usageError(t(`Unknown subcommand "${cmd.name} ${name}".`, `Subcomando desconocido "${cmd.name} ${name}".`), `easybits ${cmd.name} <${Object.keys(cmd.subs ?? {}).join("|")}>`);
3245
3717
  const names = Object.entries(cmd.subs ?? {}).flatMap(([n, l]) => [n, ...l.aliases ?? []]);
3246
- const hint = didYouMean(name, names, `easybits ${cmd.name} `, (n) => findSub(cmd, n)?.[0] ?? n);
3247
- if (hint) e.hint = `${hint}
3718
+ const hint = didYouMean(name, names, `easybits ${cmd.name} `, (n) => findSub2(cmd, n)?.[0] ?? n);
3719
+ if (hint) e.hint = `${localHint(hint)}
3248
3720
  ${e.hint}`;
3249
3721
  return e;
3250
3722
  }
3251
- function aliasNote(note) {
3723
+ function aliasNote([en, es]) {
3724
+ const note = t(en, es);
3252
3725
  const dim = process.stderr.isTTY && !process.env.NO_COLOR;
3253
3726
  process.stderr.write(dim ? `\x1B[2m${note}\x1B[0m
3254
3727
  ` : `${note}
3255
3728
  `);
3256
3729
  }
3730
+ var localHint = (hint) => hint.replace("Did you mean this?", t("Did you mean this?", "\xBFQuisiste decir esto?"));
3257
3731
  async function main(input) {
3732
+ if (input[0] === "__complete") return completeCmd(input.slice(1), COMMANDS);
3258
3733
  const { argv: argv2, notes } = normalizeArgs(input);
3259
3734
  if (!wantsJson(argv2)) notes.forEach(aliasNote);
3260
3735
  const pos = positionalIndexes(argv2);
@@ -3264,7 +3739,7 @@ async function main(input) {
3264
3739
  const topic = topicName ? findCommand(topicName) : void 0;
3265
3740
  if (topicName && !topic) throw unknownCommand(topicName);
3266
3741
  const subName = topic?.subs && pos[2] != null ? argv2[pos[2]] : void 0;
3267
- const sub = topic && subName ? findSub(topic, subName) : void 0;
3742
+ const sub = topic && subName ? findSub2(topic, subName) : void 0;
3268
3743
  if (topic && subName && !sub) throw unknownSub(topic, subName);
3269
3744
  const { values: values2 } = parse(argv2, pos.slice(0, sub ? 3 : 2), void 0);
3270
3745
  if (values2.version) {
@@ -3290,7 +3765,7 @@ async function main(input) {
3290
3765
  const skip = [pos[0]];
3291
3766
  if (cmd.subs) {
3292
3767
  const subName = pos[1] != null ? argv2[pos[1]] : void 0;
3293
- const entry = subName ? findSub(cmd, subName) : void 0;
3768
+ const entry = subName ? findSub2(cmd, subName) : void 0;
3294
3769
  if (entry) {
3295
3770
  leaf = entry[1];
3296
3771
  subKey = entry[0];
@@ -3301,7 +3776,7 @@ async function main(input) {
3301
3776
  leaf = cmd.subs[cmd.defaultSub];
3302
3777
  subKey = cmd.defaultSub;
3303
3778
  } else if (!argv2.includes("--help") && !argv2.includes("-h")) {
3304
- throw usageError(`Missing subcommand for "${cmd.name}".`, `easybits ${cmd.name} <${Object.keys(cmd.subs).join("|")}>`);
3779
+ throw usageError(t(`Missing subcommand for "${cmd.name}".`, `Falta el subcomando de "${cmd.name}".`), `easybits ${cmd.name} <${Object.keys(cmd.subs).join("|")}>`);
3305
3780
  }
3306
3781
  }
3307
3782
  const { values, positionals: positionals2 } = parse(argv2, skip, leaf);
@@ -3336,11 +3811,12 @@ async function main(input) {
3336
3811
  }
3337
3812
  }
3338
3813
  var argv = process.argv.slice(2);
3339
- var notice = process.stderr.isTTY && !wantsJson(argv) && !argv.includes("doctor") ? updateNotice(VERSION2) : null;
3814
+ setLang(detectLang(langFlag(argv)));
3815
+ var notice = process.stderr.isTTY && !wantsJson(argv) && !argv.includes("doctor") && argv[0] !== "__complete" ? updateNotice(VERSION2) : null;
3340
3816
  main(argv).catch((err) => {
3341
3817
  const e = toCliError(err);
3342
3818
  const latest = e.code === "usage" ? cachedNewer(VERSION2) : null;
3343
- if (latest) e.hint = `${e.hint ? e.hint + "\n" : ""}You have ${VERSION2}; ${latest} is out and may have it. Run: ${UPGRADE}`;
3819
+ if (latest) e.hint = `${e.hint ? e.hint + "\n" : ""}${t(`You have ${VERSION2}; ${latest} is out and may have it. Run: ${UPGRADE}`, `Tienes ${VERSION2}; ya sali\xF3 ${latest} y quiz\xE1 lo trae. Corre: ${UPGRADE}`)}`;
3344
3820
  if (wantsJson(argv)) {
3345
3821
  process.stdout.write(JSON.stringify({ error: e.message, code: e.exitCode, ...e.hint ? { hint: e.hint } : {} }) + "\n");
3346
3822
  } else {