@ghostystudio/cli 0.19.0 → 0.20.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 +5 -0
  2. package/dist/index.js +1594 -472
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -767,13 +767,13 @@ var require_createNode = __commonJS({
767
767
  var defaultTagPrefix = "tag:yaml.org,2002:";
768
768
  function findTagObject(value, tagName, tags) {
769
769
  if (tagName) {
770
- const match = tags.filter((t) => t.tag === tagName);
771
- const tagObj = match.find((t) => !t.format) ?? match[0];
770
+ const match = tags.filter((t2) => t2.tag === tagName);
771
+ const tagObj = match.find((t2) => !t2.format) ?? match[0];
772
772
  if (!tagObj)
773
773
  throw new Error(`Tag ${tagName} not found`);
774
774
  return tagObj;
775
775
  }
776
- return tags.find((t) => t.identify?.(value) && !t.format);
776
+ return tags.find((t2) => t2.identify?.(value) && !t2.format);
777
777
  }
778
778
  function createNode(value, tagName, ctx) {
779
779
  if (identity.isDocument(value))
@@ -1400,10 +1400,10 @@ ${indent}`);
1400
1400
  let res = _stringify(type);
1401
1401
  if (res === null) {
1402
1402
  const { defaultKeyType, defaultStringType } = ctx.options;
1403
- const t = implicitKey && defaultKeyType || defaultStringType;
1404
- res = _stringify(t);
1403
+ const t2 = implicitKey && defaultKeyType || defaultStringType;
1404
+ res = _stringify(t2);
1405
1405
  if (res === null)
1406
- throw new Error(`Unsupported default string type ${t}`);
1406
+ throw new Error(`Unsupported default string type ${t2}`);
1407
1407
  }
1408
1408
  return res;
1409
1409
  }
@@ -1463,24 +1463,24 @@ var require_stringify = __commonJS({
1463
1463
  }
1464
1464
  function getTagObject(tags, item) {
1465
1465
  if (item.tag) {
1466
- const match = tags.filter((t) => t.tag === item.tag);
1466
+ const match = tags.filter((t2) => t2.tag === item.tag);
1467
1467
  if (match.length > 0)
1468
- return match.find((t) => t.format === item.format) ?? match[0];
1468
+ return match.find((t2) => t2.format === item.format) ?? match[0];
1469
1469
  }
1470
1470
  let tagObj = void 0;
1471
1471
  let obj;
1472
1472
  if (identity.isScalar(item)) {
1473
1473
  obj = item.value;
1474
- let match = tags.filter((t) => t.identify?.(obj));
1474
+ let match = tags.filter((t2) => t2.identify?.(obj));
1475
1475
  if (match.length > 1) {
1476
- const testMatch = match.filter((t) => t.test);
1476
+ const testMatch = match.filter((t2) => t2.test);
1477
1477
  if (testMatch.length > 0)
1478
1478
  match = testMatch;
1479
1479
  }
1480
- tagObj = match.find((t) => t.format === item.format) ?? match.find((t) => !t.format);
1480
+ tagObj = match.find((t2) => t2.format === item.format) ?? match.find((t2) => !t2.format);
1481
1481
  } else {
1482
1482
  obj = item;
1483
- tagObj = tags.find((t) => t.nodeClass && obj instanceof t.nodeClass);
1483
+ tagObj = tags.find((t2) => t2.nodeClass && obj instanceof t2.nodeClass);
1484
1484
  }
1485
1485
  if (!tagObj) {
1486
1486
  const name = obj?.constructor?.name ?? (obj === null ? "null" : typeof obj);
@@ -4409,7 +4409,7 @@ var require_compose_collection = __commonJS({
4409
4409
  if (!tagToken || !tagName || tagName === "!" || tagName === YAMLMap.YAMLMap.tagName && expType === "map" || tagName === YAMLSeq.YAMLSeq.tagName && expType === "seq") {
4410
4410
  return resolveCollection(CN, ctx, token, onError, tagName);
4411
4411
  }
4412
- let tag = ctx.schema.tags.find((t) => t.tag === tagName && t.collection === expType);
4412
+ let tag = ctx.schema.tags.find((t2) => t2.tag === tagName && t2.collection === expType);
4413
4413
  if (!tag) {
4414
4414
  const kt = ctx.schema.knownTags[tagName];
4415
4415
  if (kt?.collection === expType) {
@@ -7383,6 +7383,604 @@ import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
7383
7383
  import { homedir } from "node:os";
7384
7384
  import { dirname, join } from "node:path";
7385
7385
 
7386
+ // src/i18n/server-messages.ts
7387
+ var SERVER_MESSAGES = [
7388
+ // — Genéricos de la API: método, cuerpo, firma y tokens
7389
+ ["m\xE9todo no permitido", "method not allowed"],
7390
+ ["json inv\xE1lido", "invalid json"],
7391
+ ["body JSON requerido", "JSON body required"],
7392
+ ["body debe ser un objeto JSON", "body must be a JSON object"],
7393
+ ["cuerpo inv\xE1lido", "invalid body"],
7394
+ ["cuerpo vac\xEDo", "empty body"],
7395
+ ["respuesta vac\xEDa", "empty response"],
7396
+ ["vac\xEDo", "empty"],
7397
+ ["nada que cambiar", "nothing to change"],
7398
+ ["nada que guardar", "nothing to save"],
7399
+ ["No existe o no es tuyo.", "Doesn't exist or isn't yours."],
7400
+ ["no existe", "doesn't exist"],
7401
+ ["No existe", "Doesn't exist"],
7402
+ ["No es JSON v\xE1lido.", "Not valid JSON."],
7403
+ ["No es JSON v\xE1lido", "Not valid JSON"],
7404
+ ["token inv\xE1lido", "invalid token"],
7405
+ ["falta firma", "missing signature"],
7406
+ ["firma inv\xE1lida", "invalid signature"],
7407
+ ["firma sin workspace identificado", "signature without an identified workspace"],
7408
+ ["falta el workspace en la firma", "missing the workspace in the signature"],
7409
+ ["ownerUserId no coincide con el workspace", "ownerUserId doesn't match the workspace"],
7410
+ ["runtime sin GHOSTY_PARTNER_SECRET", "runtime without GHOSTY_PARTNER_SECRET"],
7411
+ ["ruta inv\xE1lida", "invalid path"],
7412
+ ["path requerido", "path required"],
7413
+ ["url requerida", "url required"],
7414
+ ["url inv\xE1lida: tiene que ser https", "invalid url: must be https"],
7415
+ ["url inv\xE1lida", "invalid url"],
7416
+ ["s\xF3lo https", "https only"],
7417
+ ["la URL no puede llevar credenciales", "the URL can't carry credentials"],
7418
+ ["{} apunta a la red interna", "{} points to the internal network"],
7419
+ ["returnTo inv\xE1lido", "invalid returnTo"],
7420
+ ["status inv\xE1lido", "invalid status"],
7421
+ ["estado inv\xE1lido", "invalid status"],
7422
+ ["visibility inv\xE1lida", "invalid visibility"],
7423
+ ["vista no v\xE1lida", "invalid view"],
7424
+ ["la vista ya no est\xE1 disponible (enlace caducado)", "the view is no longer available (link expired)"],
7425
+ ["versi\xF3n no encontrada", "version not found"],
7426
+ ["acci\xF3n desconocida {}", "unknown action {}"],
7427
+ ["acci\xF3n desconocida", "unknown action"],
7428
+ ["acci\xF3n inv\xE1lida", "invalid action"],
7429
+ ["acci\xF3n no soportada: {}", "unsupported action: {}"],
7430
+ ["op desconocida: usa create | list | exec | write | expose | destroy", "unknown op: use create | list | exec | write | expose | destroy"],
7431
+ ["op desconocida: {}", "unknown op: {}"],
7432
+ ["since: 30m, 1h, 2d o una fecha ISO", "since: 30m, 1h, 2d or an ISO date"],
7433
+ ["before: 30m, 1h, 2d o una fecha ISO", "before: 30m, 1h, 2d or an ISO date"],
7434
+ // — Tokens de agente: lo que sólo se hace con la sesión de la persona
7435
+ ["el token del agente no cambia datos: usa tu sesi\xF3n (ghosty login)", "an agent token can't change data: use your session (ghosty login)"],
7436
+ ["el token del agente no cambia grupos: usa tu sesi\xF3n (ghosty login)", "an agent token can't change groups: use your session (ghosty login)"],
7437
+ ["el token del agente no cambia su tablero: usa tu sesi\xF3n", "an agent token can't change its board: use your session"],
7438
+ ["el token del agente no concede bases: usa tu sesi\xF3n (ghosty login)", "an agent token can't grant databases: use your session (ghosty login)"],
7439
+ ["el token del agente no concede documentos: usa tu sesi\xF3n (ghosty login)", "an agent token can't grant documents: use your session (ghosty login)"],
7440
+ ["el token del agente no exporta su configuraci\xF3n: usa tu sesi\xF3n", "an agent token can't export its configuration: use your session"],
7441
+ ["el token del agente no importa datos: usa tu sesi\xF3n (ghosty login)", "an agent token can't import data: use your session (ghosty login)"],
7442
+ ["el token del agente no liga su prompt a otro: usa tu sesi\xF3n", "an agent token can't link its prompt to another: use your session"],
7443
+ ["el token del agente no sube documentos: usa tu sesi\xF3n (ghosty login)", "an agent token can't upload documents: use your session (ghosty login)"],
7444
+ ["el token del agente s\xF3lo lee: usa tu sesi\xF3n (ghosty login)", "an agent token is read-only: use your session (ghosty login)"],
7445
+ // — Agentes: existencia, dueño y permisos
7446
+ ["fleet agent no existe", "fleet agent doesn't exist"],
7447
+ ["agente no existe", "agent doesn't exist"],
7448
+ ["agente no encontrado", "agent not found"],
7449
+ ["agente desconocido", "unknown agent"],
7450
+ ["agente borrado", "agent deleted"],
7451
+ ["el agente no existe", "the agent doesn't exist"],
7452
+ ["el agente ya no existe", "the agent no longer exists"],
7453
+ ["el agente no tiene due\xF1o", "the agent has no owner"],
7454
+ ["ese agente no es ACP", "that agent isn't ACP"],
7455
+ ["no encuentro el agente base (o no tienes acceso a \xE9l)", "I can't find the base agent (or you don't have access to it)"],
7456
+ ["no encuentro el agente {} (o no tienes acceso)", "I can't find agent {} (or you don't have access)"],
7457
+ ["no administras el espacio {} (o no existe)", "you don't manage workspace {} (or it doesn't exist)"],
7458
+ ["s\xF3lo el due\xF1o da acceso a sus conectores", "only the owner grants access to their connectors"],
7459
+ ["s\xF3lo el due\xF1o del agente maneja su demo", "only the agent's owner manages its demo"],
7460
+ ["s\xF3lo el due\xF1o puede borrar el agente", "only the owner can delete the agent"],
7461
+ ["Ese agente est\xE1 activo en {}. Qu\xEDtalo de ah\xED (Ajustes \u2192 Agentes) antes de borrarlo.", "That agent is active in {}. Remove it from there (Settings \u2192 Agents) before deleting it."],
7462
+ ["Ese espacio no es tuyo.", "That workspace isn't yours."],
7463
+ ["@{} ya est\xE1 ocupado por otro agente en este espacio: dale un handle propio", "@{} is already taken by another agent in this workspace: give it its own handle"],
7464
+ ["sin cupo para otro agente ahora", "no room for another agent right now"],
7465
+ ["Tu plan incluye 1 agente.", "Your plan includes 1 agent."],
7466
+ ["Tu plan incluye {} agentes.", "Your plan includes {} agents."],
7467
+ ["name requerido", "name required"],
7468
+ ["canal inv\xE1lido: {}", "invalid channel: {}"],
7469
+ ["channel inv\xE1lido", "invalid channel"],
7470
+ ["conector desconocido: {}", "unknown connector: {}"],
7471
+ ["voz de kokoro desconocida: {}", "unknown kokoro voice: {}"],
7472
+ ["box s\xF3lo lo cambia el staff de Ghosty (sale del plan)", "box can only be changed by Ghosty staff (it comes from the plan)"],
7473
+ // — Compartir un agente
7474
+ ["Esa persona no tiene cuenta en ghosty.studio todav\xEDa.", "That person doesn't have a ghosty.studio account yet."],
7475
+ ["Ya es tuyo.", "It's already yours."],
7476
+ ["falta email", "missing email"],
7477
+ ["no est\xE1 compartido con {}", "it isn't shared with {}"],
7478
+ // — Motor, modelo y máquina del agente (updateFleetAgentConfig, createFleetAgentRecord)
7479
+ ["motor desconocido: {}", "unknown engine: {}"],
7480
+ ["el modelo {} no es del motor {}", "model {} doesn't belong to engine {}"],
7481
+ ["modelo {} inv\xE1lido para {}", "invalid model {} for {}"],
7482
+ ["model requerido", "model required"],
7483
+ ["motor {} no existe o no tiene modelo listo", "engine {} doesn't exist or has no model ready"],
7484
+ ["motor {} sin modelo listo", "engine {} has no model ready"],
7485
+ ['el motor "{}" no usa llave del vault', `engine "{}" doesn't use a vault key`],
7486
+ ["mutaci\xF3n desconocida", "unknown mutation"],
7487
+ ["tama\xF1o inv\xE1lido: {} (usa {} MB)", "invalid size: {} (use {} MB)"],
7488
+ ["conversaciones por caja: 1 a 40", "conversations per box: 1 to 40"],
7489
+ ["el agente est\xE1 trabajando ahora; cambia el motor cuando termine", "the agent is working right now; change the engine when it finishes"],
7490
+ ["no pude saber si el agente est\xE1 trabajando; no cambio el motor a ciegas", "I couldn't tell whether the agent is working; I won't change the engine blindly"],
7491
+ ["la identidad qued\xF3 guardada pero no lleg\xF3 a la m\xE1quina del agente: {}", "the identity was saved but didn't reach the agent's machine: {}"],
7492
+ ["las tools quedaron guardadas pero no llegaron a la m\xE1quina del agente: {}", "the tools were saved but didn't reach the agent's machine: {}"],
7493
+ ["el motor qued\xF3 guardado pero su m\xE1quina no arranc\xF3: {}", "the engine was saved but its machine didn't start: {}"],
7494
+ ["el modelo qued\xF3 guardado pero no lleg\xF3 a la caja del agente: {}", "the model was saved but didn't reach the agent's box: {}"],
7495
+ ["no se pudo aplicar el cambio en la m\xE1quina del agente, as\xED que no se cambi\xF3 nada: {}", "the change couldn't be applied on the agent's machine, so nothing was changed: {}"],
7496
+ ['el plan de este espacio topa en {}\xD7 y ning\xFAn modelo de "{}" cabe ah\xED. Usa un motor m\xE1s barato, o s\xFAbele el tope con "Cap de modelo" en /admin/tenants.', `this workspace's plan caps at {}\xD7 and no "{}" model fits under it. Use a cheaper engine, or raise the cap with "Cap de modelo" in /admin/tenants.`],
7497
+ ["{} no entra en tu plan: gasta {}\xD7 de saldo por turno y tu plan admite modelos de hasta {}\xD7. Elige otro modelo o sube de plan en /planes.", "{} isn't in your plan: it spends {}\xD7 balance per turn and your plan allows models up to {}\xD7. Choose another model or upgrade at /planes."],
7498
+ ["este motor carga archivos, skills y MCP en el siguiente turno: no necesita restart", "this engine loads files, skills and MCP on the next turn: no restart needed"],
7499
+ ["no tienes llave de {} que prestar", "you have no {} key to lend"],
7500
+ // — Prompt derivado (setPromptBase y la capa de ediciones)
7501
+ ["este prompt se genera del de {} + una capa: cambia la base o la capa, o su\xE9ltalo (ghosty agents set {} --prompt-base none)", "this prompt is generated from {}'s plus a layer: change the base or the layer, or detach it (ghosty agents set {} --prompt-base none)"],
7502
+ ["el agente base no existe", "the base agent doesn't exist"],
7503
+ ["el agente base no tiene prompt", "the base agent has no prompt"],
7504
+ ["el prompt de este agente no deriva de otro", "this agent's prompt doesn't derive from another"],
7505
+ ["la base deriva de este agente: har\xEDa un ciclo", "the base derives from this agent: it would make a cycle"],
7506
+ ["la base ya no tiene \xAB{}\xBB", 'the base no longer has "{}"'],
7507
+ ["la capa necesita `edits`: [{what, find, replace}]", "the layer needs `edits`: [{what, find, replace}]"],
7508
+ ["capa inv\xE1lida: {}", "invalid layer: {}"],
7509
+ ["demasiadas ediciones (m\xE1x. 200)", "too many edits (max 200)"],
7510
+ ["edits[{}]: falta find", "edits[{}]: missing find"],
7511
+ ['edits[{}]: falta replace (puede ser "")', 'edits[{}]: missing replace (can be "")'],
7512
+ ["edits[{}]: regex inv\xE1lida: {}", "edits[{}]: invalid regex: {}"],
7513
+ // — Skills del agente y de casa
7514
+ ["el agente no tiene una skill propia \xAB{}\xBB", 'the agent has no own skill "{}"'],
7515
+ ["ni el agente ni Ghosty tienen una skill \xAB{}\xBB (ghosty skills ls)", 'neither the agent nor Ghosty has a skill "{}" (ghosty skills ls)'],
7516
+ ["no son skills de Ghosty: {} (ghosty skills house <id> las lista)", "not Ghosty skills: {} (ghosty skills house <id> lists them)"],
7517
+ ["falta markdown (el contenido de SKILL.md)", "missing markdown (the contents of SKILL.md)"],
7518
+ ["falta enabled (true|false)", "missing enabled (true|false)"],
7519
+ ["slug inv\xE1lido: a-z, 0-9 y guiones, m\xE1x 64", "invalid slug: a-z, 0-9 and hyphens, max 64"],
7520
+ ["Nombre inv\xE1lido: a-z, 0-9 y guiones, m\xE1x 64.", "Invalid name: a-z, 0-9 and hyphens, max 64."],
7521
+ ["Nombre de archivo inv\xE1lido: {}", "Invalid file name: {}"],
7522
+ ["SKILL.md empieza con un bloque --- name / description ---.", "SKILL.md starts with a --- name / description --- block."],
7523
+ ["Falta `name:` en el frontmatter.", "Missing `name:` in the frontmatter."],
7524
+ ["Falta `description:` en el frontmatter (es lo que el agente lee para decidir cu\xE1ndo usarla).", "Missing `description:` in the frontmatter (it's what the agent reads to decide when to use it)."],
7525
+ ["`{}` empieza con un car\xE1cter que YAML no admite sin comillas.", "`{}` starts with a character YAML doesn't accept without quotes."],
7526
+ ["`{}` lleva un \xAB: \xBB sin comillas; ponlo entre comillas dobles o el agente no ver\xE1 la habilidad.", '`{}` has an unquoted ": "; wrap it in double quotes or the agent won\'t see the skill.'],
7527
+ ["La habilidad pesa m\xE1s de 25 MB.", "The skill is larger than 25 MB."],
7528
+ ["Esa habilidad no est\xE1 en el cat\xE1logo de la comunidad.", "That skill isn't in the community catalog."],
7529
+ ["aplica en el siguiente reinicio (POST \u2026/restart)", "applies on the next restart (POST \u2026/restart)"],
7530
+ ["aplica en el siguiente turno", "applies on the next turn"],
7531
+ ["aplica desde tu siguiente conversaci\xF3n (o tras un reinicio)", "applies from your next conversation (or after a restart)"],
7532
+ ["desaparece de la lista del cerebro en el siguiente reinicio (POST \u2026/restart)", "disappears from the brain's list on the next restart (POST \u2026/restart)"],
7533
+ ["desaparece en el siguiente turno", "disappears on the next turn"],
7534
+ ["el cerebro la ve en el siguiente turno", "the brain sees it on the next turn"],
7535
+ ["el cerebro la ve tras el siguiente reinicio: POST \u2026/restart", "the brain sees it after the next restart: POST \u2026/restart"],
7536
+ ["manda only, on u off (listas de slugs)", "send only, on or off (lists of slugs)"],
7537
+ ["only va solo (o usa on/off)", "only goes alone (or use on/off)"],
7538
+ ["en on y off a la vez: {}", "in both on and off: {}"],
7539
+ // — Archivo del agente (apply/export/bundle)
7540
+ ["falta spec (el texto del archivo)", "missing spec (the file's text)"],
7541
+ ["archivo inv\xE1lido: {}", "invalid file: {}"],
7542
+ ["tools/, channels/ y schedules/ de Eve no se importan: las tools y automatizaciones se configuran en Studio", "Eve's tools/, channels/ and schedules/ aren't imported: tools and automations are configured in Studio"],
7543
+ ["{}: demasiado grande (m\xE1x 10 MB)", "{}: too large (max 10 MB)"],
7544
+ // — MCP y cajas del agente (servers, config.yaml)
7545
+ ["servers debe ser una lista", "servers must be a list"],
7546
+ ["servers[{}]: no es un objeto", "servers[{}]: not an object"],
7547
+ ["servers[{}]: name inv\xE1lido (a-z, 0-9, -, _)", "servers[{}]: invalid name (a-z, 0-9, -, _)"],
7548
+ ["servers[{}]: hace falta url (http) o command (stdio)", "servers[{}]: url (http) or command (stdio) is required"],
7549
+ ["m\xE1ximo 20 servidores", "max 20 servers"],
7550
+ ["no se pudieron escribir las extensiones: {}", "couldn't write the extensions: {}"],
7551
+ ["no se pudo escribir config.yaml: {}", "couldn't write config.yaml: {}"],
7552
+ // — Archivos del agente y de la cuenta
7553
+ ["archivo demasiado grande (m\xE1x 10 MB)", "file too large (max 10 MB)"],
7554
+ ["archivo vac\xEDo o >25MB", "empty file or >25MB"],
7555
+ ["archivo vac\xEDo o de m\xE1s de 8 MB", "empty file or larger than 8 MB"],
7556
+ ["el archivo pasa de 8 MB", "the file exceeds 8 MB"],
7557
+ ["el archivo pesa {} y el m\xE1ximo son {}", "the file is {} and the max is {}"],
7558
+ ["el archivo viene vac\xEDo", "the file is empty"],
7559
+ ["el nombre del archivo no es utilizable", "the file name isn't usable"],
7560
+ ["el objeto est\xE1 vac\xEDo", "the object is empty"],
7561
+ ["el objeto {} ya no est\xE1 en el bucket", "object {} is no longer in the bucket"],
7562
+ ["esta conversaci\xF3n ya tiene {} archivos y el m\xE1ximo son {}", "this conversation already has {} files and the max is {}"],
7563
+ ["pesa {}; el m\xE1ximo son {}", "it's {}; the max is {}"],
7564
+ ["pesa m\xE1s de {} MB", "larger than {} MB"],
7565
+ ["no cabe: llevas {} de {} GB. Se ampl\xEDa en paquetes de 50 GB desde /planes.", "doesn't fit: you're using {} of {} GB. You can add 50 GB packs at /planes."],
7566
+ ["no cabe: llevas {} de {} GB. Borra algo que ya no uses para hacer sitio.", "doesn't fit: you're using {} of {} GB. Delete something you no longer use to make room."],
7567
+ ["no cabe: llevas {} de {} GB. {}", "doesn't fit: you're using {} of {} GB. {}"],
7568
+ ["falta el par\xE1metro nombre", "missing the nombre parameter"],
7569
+ ["el almacenamiento no est\xE1 configurado", "storage isn't configured"],
7570
+ ["storage sin configurar (TIGRIS_*/STORAGE_*)", "storage not configured (TIGRIS_*/STORAGE_*)"],
7571
+ ["storage sin configurar", "storage not configured"],
7572
+ ["no es JPEG/PNG/WebP ({})", "not JPEG/PNG/WebP ({})"],
7573
+ ["no se pudo bajar (respuesta vac\xEDa, no 2xx o m\xE1s de 10 MB)", "couldn't download (empty response, not 2xx or over 10 MB)"],
7574
+ ["no se pudo bajar: {}", "couldn't download: {}"],
7575
+ // — Documentos
7576
+ ["no hay un documento \xAB{}\xBB", `there's no document "{}"`],
7577
+ ["manda url, o contentBase64 con name", "send url, or contentBase64 with name"],
7578
+ ["faltan los bloques", "missing the blocks"],
7579
+ ["este agente no tiene acceso a ese documento (mira doc_list)", "this agent has no access to that document (see doc_list)"],
7580
+ ["ese documento es s\xF3lo para consultarlo, no para entregarlo", "that document is for reference only, not for sending"],
7581
+ // — Bases de datos
7582
+ ["no hay una base \xAB{}\xBB", `there's no database "{}"`],
7583
+ ["Ya tienes una base con ese nombre.", "You already have a database with that name."],
7584
+ ["Tu plan incluye {} base{} de datos.", "Your plan includes {} database{}."],
7585
+ ["El nombre lleva letras min\xFAsculas, n\xFAmeros, guiones o guiones bajos (m\xE1x. 64).", "The name takes lowercase letters, numbers, hyphens or underscores (max 64)."],
7586
+ ["Nombre de tabla inv\xE1lido (letras, n\xFAmeros y _).", "Invalid table name (letters, numbers and _)."],
7587
+ ["Nombre de tabla inv\xE1lido", "Invalid table name"],
7588
+ ["nombres de tabla no v\xE1lidos: {}", "invalid table names: {}"],
7589
+ ["nombres de tabla o columna inv\xE1lidos", "invalid table or column names"],
7590
+ ["Tabla inv\xE1lida", "Invalid table"],
7591
+ ["Tabla o columna inv\xE1lida", "Invalid table or column"],
7592
+ ["Columna inv\xE1lida: {}", "Invalid column: {}"],
7593
+ ["Columnas inv\xE1lidas", "Invalid columns"],
7594
+ ["Una tabla necesita al menos una columna.", "A table needs at least one column."],
7595
+ ["Sin valores", "No values"],
7596
+ ["SQL vac\xEDo", "Empty SQL"],
7597
+ ["falta sql", "missing sql"],
7598
+ ["Aqu\xED s\xF3lo caben consultas SELECT", "Only SELECT queries are allowed here"],
7599
+ ["S\xF3lo una sentencia por consulta", "Only one statement per query"],
7600
+ ["S\xF3lo una sentencia por llamada", "Only one statement per call"],
7601
+ ["La consulta intenta escribir; este acceso es de lectura", "The query tries to write; this access is read-only"],
7602
+ ["Desde este canal s\xF3lo INSERT, UPDATE o DELETE", "From this channel only INSERT, UPDATE or DELETE"],
7603
+ ["Desde este canal s\xF3lo puedes escribir en: {}", "From this channel you can only write to: {}"],
7604
+ ["desde este canal no puedes leer \xAB{}\xBB", `from this channel you can't read "{}"`],
7605
+ ["desde este canal no se puede usar el prefijo main.; consulta las tablas por su nombre", "from this channel you can't use the main. prefix; query tables by name"],
7606
+ ["desde este canal s\xF3lo puedes agregar en \xAB{}\xBB; cambiar o borrar se hace desde el equipo", 'from this channel you can only add to "{}"; changing or deleting is done from the team'],
7607
+ ["externalRowScope es { tabla: [columnas de tel\xE9fono] }", "externalRowScope is { table: [phone columns] }"],
7608
+ ["externalWriteTables es una lista de tablas", "externalWriteTables is a list of tables"],
7609
+ ["Cu\xE9ntame para qu\xE9 es la tabla.", "Tell me what the table is for."],
7610
+ ["Dime qu\xE9 quieres consultar.", "Tell me what you want to query."],
7611
+ ["No hay llave para pedir ayuda al modelo.", "There's no key to ask the model for help."],
7612
+ ["El modelo no contest\xF3; int\xE9ntalo otra vez.", "The model didn't answer; try again."],
7613
+ ["El modelo no contest\xF3 ({}).", "The model didn't answer ({})."],
7614
+ ["El modelo no devolvi\xF3 SQL.", "The model returned no SQL."],
7615
+ ["El modelo no propuso columnas; int\xE9ntalo con m\xE1s detalle.", "The model proposed no columns; try again with more detail."],
7616
+ ["No parece un archivo .xlsx", "Doesn't look like an .xlsx file"],
7617
+ ["No encontr\xE9 la hoja en el archivo", "I couldn't find the sheet in the file"],
7618
+ ["el destino es la cuenta del original: usa un sufijo para no escribir en su base real {}", "the target is the original's account: use a suffix so you don't write to its real database {}"],
7619
+ ["este agente no tiene acceso a esa base (mira db_list)", "this agent has no access to that database (see db_list)"],
7620
+ // importar de EasyBits y re-hospedar imágenes
7621
+ ["from: s\xF3lo \xABeasybits\xBB por ahora", 'from: only "easybits" for now'],
7622
+ ["falta sourceDb (id de la base en EasyBits)", "missing sourceDb (the database id in EasyBits)"],
7623
+ ["falta easybitsKey", "missing easybitsKey"],
7624
+ ["EasyBits rechaz\xF3 la llave (EASYBITS_API_KEY)", "EasyBits rejected the key (EASYBITS_API_KEY)"],
7625
+ ["EasyBits no tiene la base {} (easybits db ls)", "EasyBits has no database {} (easybits db ls)"],
7626
+ ["EasyBits no tiene: {}", "EasyBits doesn't have: {}"],
7627
+ ["rehost: \xAB{}\xBB no es tabla.columna", `rehost: "{}" isn't table.column`],
7628
+ ["{} tiene {} filas (m\xE1x. {})", "{} has {} rows (max {})"],
7629
+ ["table y column: nombres v\xE1lidos", "table and column: valid names"],
7630
+ ["{} no tiene la columna {}", "{} has no column {}"],
7631
+ ["{} no tiene la tabla {}", "{} has no table {}"],
7632
+ ["no parece un sqlite", "doesn't look like a sqlite file"],
7633
+ // — Tableros, conversaciones y CRM (moveCards, boards)
7634
+ ["el tablero principal no se borra", "the main board can't be deleted"],
7635
+ ["tiene leads: mu\xE9velos o arch\xEDvalos antes de borrarlo", "it has leads: move or archive them before deleting it"],
7636
+ ["no hay un tablero \xAB{}\xBB en {} (ghosty boards ls {})", `there's no board "{}" in {} (ghosty boards ls {})`],
7637
+ ["no hay un tablero \xAB{}\xBB en {}", `there's no board "{}" in {}`],
7638
+ ["ya hay un tablero \xAB{}\xBB en {}", `there's already a board "{}" in {}`],
7639
+ ["falta name", "missing name"],
7640
+ ["no pude crear el tablero de {}", "I couldn't create the board for {}"],
7641
+ ["ese tablero no es del espacio del agente", "that board isn't in the agent's workspace"],
7642
+ ["ese tablero ya no existe", "that board no longer exists"],
7643
+ ["ese tablero no existe o ya caduc\xF3", "that board doesn't exist or has expired"],
7644
+ ["ese tablero no lo armaste t\xFA", "you didn't build that board"],
7645
+ ["los tableros viven en un espacio de equipo, no en la cuenta", "boards live in a team workspace, not in the account"],
7646
+ ["los tableros viven en un espacio de equipo; este agente es personal", "boards live in a team workspace; this agent is personal"],
7647
+ ["espacio sin due\xF1o: {}", "workspace without an owner: {}"],
7648
+ ["espacio sin due\xF1o", "workspace without an owner"],
7649
+ ["from y to son el mismo tablero", "from and to are the same board"],
7650
+ ["from y to: tableros de {} (id, nombre o main)", "from and to: boards in {} (id, name or main)"],
7651
+ ["hace falta al menos un filtro: integration, column o before", "at least one filter is required: integration, column or before"],
7652
+ ["hace falta un filtro: integration, phone o all", "a filter is required: integration, phone or all"],
7653
+ ["phone: al menos 7 d\xEDgitos", "phone: at least 7 digits"],
7654
+ ["no hay una conversaci\xF3n \xAB{}\xBB en el tablero {}", `there's no conversation "{}" on board {}`],
7655
+ ["\xAB{}\xBB coincide con {} conversaciones: usa el folio o el id", '"{}" matches {} conversations: use the folio or the id'],
7656
+ ["columna inv\xE1lida", "invalid column"],
7657
+ ["columnas inv\xE1lidas", "invalid columns"],
7658
+ ["direcci\xF3n inv\xE1lida", "invalid address"],
7659
+ ["tags inv\xE1lidas", "invalid tags"],
7660
+ ["pausa inv\xE1lida", "invalid pause"],
7661
+ ["el nombre es obligatorio", "name is required"],
7662
+ ["falta conversationId", "missing conversationId"],
7663
+ ["intent desconocido: {}", "unknown intent: {}"],
7664
+ ["un pedido necesita columna", "an order needs a column"],
7665
+ // — WhatsApp: ligar números, importar sesiones (importAgentWhatsApp) y grupos
7666
+ ["answer: true (contesta a clientes reales) o false (registra sin contestar); no hay default", "answer: true (replies to real customers) or false (records without replying); there's no default"],
7667
+ ["integrationId: el id de la Integration de Formmy (24 hex)", "integrationId: the Formmy Integration id (24 hex)"],
7668
+ ["ese n\xFAmero no est\xE1 ligado a este agente: ghosty channels whatsapp link {} --integration {} --answer on|off", "that number isn't linked to this agent: ghosty channels whatsapp link {} --integration {} --answer on|off"],
7669
+ ["ese n\xFAmero ya est\xE1 ligado en gs a otra cuenta o espacio", "that number is already linked in gs to another account or workspace"],
7670
+ ["este n\xFAmero no est\xE1 ligado en gs", "this number isn't linked in gs"],
7671
+ ["el n\xFAmero no tiene espacio ni tablero", "the number has no workspace or board"],
7672
+ ["s\xF3lo el due\xF1o, un admin del agente o staff mueven un n\xFAmero", "only the owner, an agent admin or staff can move a number"],
7673
+ ["s\xF3lo el due\xF1o, un admin del agente o staff importan una sesi\xF3n de WhatsApp", "only the owner, an agent admin or staff can import a WhatsApp session"],
7674
+ ["esta caja de gs no es la activa (deploy en curso): reintenta en un minuto", "this gs box isn't the active one (deploy in progress): try again in a minute"],
7675
+ ["WhatsApp rechaz\xF3 la sesi\xF3n (logout): hay que volver a parear", "WhatsApp rejected the session (logout): it has to be paired again"],
7676
+ ["WhatsApp no est\xE1 conectado", "WhatsApp isn't connected"],
7677
+ ["este agente tiene WhatsApp conectado en gs: p\xE1usalo primero (DELETE \u2026/wa, sin olvidar)", "this agent has WhatsApp connected in gs: pause it first (DELETE \u2026/wa, without forgetting)"],
7678
+ ["este agente ya tiene una sesi\xF3n guardada{}: usa replace para reemplazarla", "this agent already has a saved session{}: use replace to replace it"],
7679
+ ["su WhatsApp en gs est\xE1 \xAB{}\xBB: p\xE1usalo antes de importar", 'its WhatsApp in gs is "{}": pause it before importing'],
7680
+ ["authCreds no parece una sesi\xF3n de Baileys (faltan noiseKey / signedIdentityKey)", "authCreds doesn't look like a Baileys session (missing noiseKey / signedIdentityKey)"],
7681
+ ["la sesi\xF3n nunca termin\xF3 de parearse (authCreds.me.id vac\xEDo)", "the session never finished pairing (authCreds.me.id is empty)"],
7682
+ ["el archivo dice +{} pero las credenciales son de +{}", "the file says +{} but the credentials are for +{}"],
7683
+ ["faltan authKeys", "missing authKeys"],
7684
+ ["falta ownNumber: \xBFel n\xFAmero es del agente (true) o de una persona (false)?", "missing ownNumber: is the number the agent's (true) or a person's (false)?"],
7685
+ ["grupo con jid inv\xE1lido: {}", "group with invalid jid: {}"],
7686
+ ["falta jid", "missing jid"],
7687
+ ["olvidar la sesi\xF3n de WhatsApp lo hace una persona desde el panel", "forgetting the WhatsApp session is done by a person from the dashboard"],
7688
+ ["no pude obtener el enlace del grupo", "I couldn't get the group link"],
7689
+ ["name + 2 opciones m\xEDnimo", "name + at least 2 options"],
7690
+ ["emoji + un mensaje reciente", "emoji + a recent message"],
7691
+ ["text o url requerido", "text or url required"],
7692
+ ["sesi\xF3n desconocida", "unknown session"],
7693
+ ["este turno no viene de tu WhatsApp personal", "this turn doesn't come from your personal WhatsApp"],
7694
+ ["ya hay una respuesta en camino", "there's already a reply on its way"],
7695
+ // — Probar al agente (try) y turnos
7696
+ ["falta text", "missing text"],
7697
+ ["asFormmy va con as=waba y media", "asFormmy goes with as=waba and media"],
7698
+ ["con as={}, session es el tel\xE9fono (10-15 d\xEDgitos)", "with as={}, session is the phone number (10-15 digits)"],
7699
+ ["media: { url, mime?, filename?, type? } con url https", "media: { url, mime?, filename?, type? } with an https url"],
7700
+ ['history: [{ role: "user"|"agent", text, at? }] (m\xE1x. 200)', 'history: [{ role: "user"|"agent", text, at? }] (max 200)'],
7701
+ ["este hilo tiene un turno en curso pedido por otra persona", "this thread has a turn in progress requested by someone else"],
7702
+ ["gs se reinici\xF3 a mitad del turno", "gs restarted mid-turn"],
7703
+ ["el turno fall\xF3", "the turn failed"],
7704
+ ["turno sin terminar tras {} ms", "turn unfinished after {} ms"],
7705
+ ["steer sin respuesta", "steer got no response"],
7706
+ ["el agente dej\xF3 de responder; turno cerrado", "the agent stopped responding; turn closed"],
7707
+ ["el agente no devolvi\xF3 sessionId", "the agent returned no sessionId"],
7708
+ ["el agente no pudo contestar; intenta de nuevo", "the agent couldn't answer; try again"],
7709
+ ["la caja cerr\xF3 la conexi\xF3n a media respuesta", "the box closed the connection mid-response"],
7710
+ ["la conversaci\xF3n se enfri\xF3; \xE1brela de nuevo para seguir", "the conversation went cold; open it again to continue"],
7711
+ ["tu agente ya atiende todas las conversaciones que puede a la vez; cierra alguna o espera a que termine", "your agent is already handling as many conversations as it can at once; close one or wait for one to finish"],
7712
+ ["no se pudo firmar el ticket de la caja de este agente", "couldn't sign this agent's box ticket"],
7713
+ ["{}: sin respuesta en {} ms", "{}: no response in {} ms"],
7714
+ ["Seguimos llenos tras {}s de espera. Tu mensaje NO se perdi\xF3: vuelve a enviarlo en un momento.", "Still full after {}s of waiting. Your message was NOT lost: send it again in a moment."],
7715
+ ["admisi\xF3n: {}", "admission: {}"],
7716
+ ["sin noticias del encargo en 45 min", "no news from the task in 45 min"],
7717
+ ["sin cierre: se dio por perdido", "never closed: given up as lost"],
7718
+ ["sin cierre: se perdi\xF3 el rastro del montaje (la caja pudo reciclarse)", "never closed: the video job's trace was lost (the box may have been recycled)"],
7719
+ // chat de la app (/me): conversaciones, archivos y docs
7720
+ ["falta el contenido", "missing the content"],
7721
+ ["falta el modelo", "missing the model"],
7722
+ ["falta el t\xEDtulo", "missing the title"],
7723
+ ["falta id u optionId", "missing id or optionId"],
7724
+ // — Turnos programados
7725
+ ["dueAt no es una fecha", "dueAt isn't a date"],
7726
+ ["falta qu\xE9 hacer", "missing what to do"],
7727
+ ["cada {} min como m\xEDnimo", "at least every {} min"],
7728
+ ["`until` como mucho a 30 d\xEDas", "`until` at most 30 days out"],
7729
+ ["como mucho a 30 d\xEDas", "at most 30 days out"],
7730
+ ["una repetici\xF3n necesita `until`", "a recurrence needs `until`"],
7731
+ ["este agente ya tiene {} turnos pendientes", "this agent already has {} pending turns"],
7732
+ ["ya hay {} pendientes en esta conversaci\xF3n", "there are already {} pending in this conversation"],
7733
+ ["ya programaste {} turnos hoy; p\xEDdele al usuario que programe el resto", "you already scheduled {} turns today; ask the user to schedule the rest"],
7734
+ // — Saldo, planes y cuotas
7735
+ ["Se acab\xF3 el saldo de este agente ({}). Se reinicia el {}. Los dem\xE1s agentes del espacio siguen con el suyo.", "This agent's balance ran out ({}). It resets on {}. The other agents in the workspace keep their own."],
7736
+ ["Se acab\xF3 la bolsa \xAB{}\xBB ({}), que este agente comparte con otros {}. Se reinicia el {}. Los agentes con otra bolsa siguen trabajando.", 'The "{}" pool ran out ({}), which this agent shares with {} others. It resets on {}. Agents on another pool keep working.'],
7737
+ ["Se acab\xF3 tu sesi\xF3n. Vuelve a las {}.", "Your session limit is used up. Come back at {}."],
7738
+ ["Se acab\xF3 tu uso de este modelo esta semana. Cambia a otro modelo para seguir.", "You've used up this model for the week. Switch to another model to continue."],
7739
+ ["Se acab\xF3 tu uso de esta semana. Se renueva el {}.", "You've used up this week's usage. It renews on {}."],
7740
+ ["Se acab\xF3 tu uso de este mes. Se renueva el {}.", "You've used up this month's usage. It renews on {}."],
7741
+ ["Se acab\xF3 tu uso {}. Se renueva el {}.", "You've used up your usage {}. It renews on {}."],
7742
+ ["Ese modelo no est\xE1 disponible en tu plan. Cambia de modelo para seguir.", "That model isn't available on your plan. Switch models to continue."],
7743
+ ["Tu periodo de prueba termin\xF3 el {}. Escr\xEDbenos para seguir.", "Your trial period ended on {}. Write to us to continue."],
7744
+ ["Este agente corre con Claude y no tiene llave. Conecta tu cuenta de Claude en ghosty.studio \u2192 Credenciales, o c\xE1mbialo a otro motor en Ajustes \u2192 Agentes.", "This agent runs on Claude and has no key. Connect your Claude account in ghosty.studio \u2192 Credentials, or switch it to another engine in Settings \u2192 Agents."],
7745
+ ["Este agente de regalo se qued\xF3 sin llave. Av\xEDsale a quien te lo comparti\xF3.", "This shared agent has no key anymore. Let whoever shared it with you know."],
7746
+ ["{} corre s\xF3lo con la llave de OpenAI del due\xF1o del espacio, y todav\xEDa no hay ninguna. P\xE9gala en ghosty.studio \u2192 Credenciales \u2192 OpenAI (o cambia el agente a un modelo de Claude en Ajustes \u2192 Agentes).", "{} only runs with the workspace owner's OpenAI key, and there isn't one yet. Paste it in ghosty.studio \u2192 Credentials \u2192 OpenAI (or switch the agent to a Claude model in Settings \u2192 Agents)."],
7747
+ ["sin saldo", "no balance"],
7748
+ ["cache_read_input_tokens y cache_creation_input_tokens deben ser n\xFAmeros", "cache_read_input_tokens and cache_creation_input_tokens must be numbers"],
7749
+ ["suffixes requerido", "suffixes required"],
7750
+ ["{}MB de {}MB de snapshots ({}%), tope de altas al {}%", "{}MB of {}MB of snapshots ({}%), sign-up cap at {}%"],
7751
+ ["el host no reporta el disco de snapshots", "the host doesn't report the snapshots disk"],
7752
+ ["el proveedor no reporta capacidad", "the provider doesn't report capacity"],
7753
+ ["no se pudo medir: {}", "couldn't measure: {}"],
7754
+ // — Credenciales y vault
7755
+ ["proveedor desconocido: {}", "unknown provider: {}"],
7756
+ ["falta key", "missing key"],
7757
+ ["el vault no est\xE1 configurado en este servidor", "the vault isn't configured on this server"],
7758
+ ["El .env no puede ir vac\xEDo.", "The .env can't be empty."],
7759
+ ['El repositorio va como "due\xF1o/repo".', 'The repository goes as "owner/repo".'],
7760
+ // — Partners (Deník y compañía) y Formmy
7761
+ ["falta workspace (el slug de tu espacio)", "missing workspace (your workspace's slug)"],
7762
+ ["espacio no encontrado", "workspace not found"],
7763
+ ["el espacio {} no es partner (act\xEDvalo con: ghosty partner enable {})", "workspace {} isn't a partner (enable it with: ghosty partner enable {})"],
7764
+ ["no tienes espacios de partner (act\xEDvalo con: ghosty partner enable <espacio>)", "you have no partner workspaces (enable one with: ghosty partner enable <workspace>)"],
7765
+ ["tienes {} espacios de partner: di cu\xE1l con --workspace ({})", "you have {} partner workspaces: pick one with --workspace ({})"],
7766
+ ["el partner est\xE1 apagado", "the partner is turned off"],
7767
+ ["credencial revocada: emite una nueva", "credential revoked: issue a new one"],
7768
+ ["m\xE1ximo {} credenciales activas: revoca una antes", "max {} active credentials: revoke one first"],
7769
+ ["el partner a\xFAn no tiene agente (PUT /api/v2/partner/agent)", "the partner has no agent yet (PUT /api/v2/partner/agent)"],
7770
+ ["el partner a\xFAn no tiene agente (PUT para crearlo)", "the partner has no agent yet (PUT to create it)"],
7771
+ ["el partner a\xFAn no tiene agente", "the partner has no agent yet"],
7772
+ ["el borrador es igual a la versi\xF3n vigente", "the draft is the same as the current version"],
7773
+ ["no existe la versi\xF3n {}", "version {} doesn't exist"],
7774
+ ["config debe ser un objeto", "config must be an object"],
7775
+ ["config.timezone inv\xE1lida: {}", "invalid config.timezone: {}"],
7776
+ ["config.{} no existe ({})", "config.{} doesn't exist ({})"],
7777
+ ["mcp debe ser una lista", "mcp must be a list"],
7778
+ ["m\xE1ximo 5 servidores MCP", "max 5 MCP servers"],
7779
+ ["mcp[{}]: headers debe ser un objeto", "mcp[{}]: headers must be an object"],
7780
+ ["mcp[{}]: header {} inv\xE1lido", "mcp[{}]: invalid header {}"],
7781
+ ["mcp[{}]: name inv\xE1lido o repetido", "mcp[{}]: invalid or repeated name"],
7782
+ ["mcp[{}]: placeholder desconocido {} (usa {})", "mcp[{}]: unknown placeholder {} (use {})"],
7783
+ ["mcp[{}]: s\xF3lo servidores HTTP (url); stdio no existe en modo solo-MCP", "mcp[{}]: only HTTP servers (url); stdio doesn't exist in MCP-only mode"],
7784
+ ["motor {} no corre en modo solo-MCP (hoy: {})", "engine {} doesn't run in MCP-only mode (today: {})"],
7785
+ ["motor {} sin proveedor solo-MCP", "engine {} has no MCP-only provider"],
7786
+ ["{} no entra en el plan del espacio (tope {}\xD7)", "{} isn't in the workspace's plan (cap {}\xD7)"],
7787
+ ["externalId inv\xE1lido (1-128: letras, d\xEDgitos, . _ -)", "invalid externalId (1-128: letters, digits, . _ -)"],
7788
+ ["tenant.externalId inv\xE1lido", "invalid tenant.externalId"],
7789
+ ["tenant no existe", "tenant doesn't exist"],
7790
+ ["groupId inv\xE1lido", "invalid groupId"],
7791
+ ["groupId requerido", "groupId required"],
7792
+ ["text requerido", "text required"],
7793
+ ["este negocio est\xE1 suspendido", "this business is suspended"],
7794
+ ["sin llave de {} para este espacio", "no {} key for this workspace"],
7795
+ ["FORMMY_PARTNER_SECRET_GHOSTY ausente: no se puede hablar con Formmy", "FORMMY_PARTNER_SECRET_GHOSTY missing: can't talk to Formmy"],
7796
+ ["Formmy respondi\xF3 {}", "Formmy responded {}"],
7797
+ // — Demos
7798
+ ["falta la liga (slug) la primera vez", "missing the link (slug) the first time"],
7799
+ ["el nombre de la liga va en min\xFAsculas, n\xFAmeros y guiones (3 a 40)", "the link name takes lowercase letters, numbers and hyphens (3 to 40)"],
7800
+ ["esa liga ya la usa otro agente", "that link is already used by another agent"],
7801
+ ["este demo ya no est\xE1 disponible", "this demo is no longer available"],
7802
+ ["por ahora el demo s\xF3lo corre con motores de pool (Claude, Codex\u2026), no con ghosty-lite/goose", "for now demos only run on pool engines (Claude, Codex\u2026), not on ghosty-lite/goose"],
7803
+ // — Cajas de trabajo, puertos y ACP
7804
+ ["Ahora mismo no hay capacidad para otra caja. Reintenta en unos minutos o suelta alguna con destroy.", "There's no capacity for another box right now. Try again in a few minutes or release one with destroy."],
7805
+ ["Esa caja no existe o no es tuya. Lista las tuyas con list y usa un id de ah\xED.", "That box doesn't exist or isn't yours. List yours with list and use an id from there."],
7806
+ ["Ya tienes {} cajas de trabajo (tope {}). Suelta una con destroy \u2014 las de tu lista: {}.", "You already have {} work boxes (limit {}). Release one with destroy \u2014 the ones on your list: {}."],
7807
+ ["Template desconocido. Los que puedes pedir: {}.", "Unknown template. The ones you can ask for: {}."],
7808
+ ["Tama\xF1o desconocido. Elige: {}.", "Unknown size. Choose: {}."],
7809
+ ["La caja no termin\xF3 de despertar. Reintenta en unos segundos.", "The box didn't finish waking up. Try again in a few seconds."],
7810
+ ["command requerido", "command required"],
7811
+ ["Puerto inv\xE1lido: va entre 1024 y 65535, y los puertos {} (runtime de la caja) y {} (sistema) no se publican \u2014 levanta el server en otro (5173, 4321, 8080\u2026).", "Invalid port: it must be between 1024 and 65535, and ports {} (box runtime) and {} (system) aren't published \u2014 run the server on another one (5173, 4321, 8080\u2026)."],
7812
+ ["Puerto inv\xE1lido: va entre 1024 y 65535.", "Invalid port: it must be between 1024 and 65535."],
7813
+ ["El puerto {} es del sistema de la caja y no se publica. Usa el de tu app.", "Port {} belongs to the box's system and isn't published. Use your app's port."],
7814
+ ["Esta conversaci\xF3n no tiene caja viva todav\xEDa; corre algo primero y reintenta.", "This conversation has no live box yet; run something first and try again."],
7815
+ ["La caja de esta conversaci\xF3n ya se recicl\xF3; corre cualquier comando primero (eso la revive) y reintenta.", "This conversation's box was already recycled; run any command first (that revives it) and try again."],
7816
+ ["Los subagentes corren con el Claude de Ghosty y este espacio no lo tiene habilitado. Haz el trabajo en este mismo turno.", "Subagents run on Ghosty's Claude and this workspace doesn't have it enabled. Do the work in this same turn."],
7817
+ ["L\xEDmite de subagentes concurrentes alcanzado, intenta en un momento.", "Concurrent subagent limit reached, try again in a moment."],
7818
+ ["prompt requerido", "prompt required"],
7819
+ ["el motor {} no es ACP: no tiene caja que gobernar aqu\xED", "engine {} isn't ACP: there's no box to manage here"],
7820
+ ["el proveedor de cajas no soporta agent/start: no puede levantar un rel\xE9 ACP", "the box provider doesn't support agent/start: it can't bring up an ACP relay"],
7821
+ ["falta ACP_TICKET_SECRET en el env de gs: sin \xE9l la caja ACP no autentica a nadie", "ACP_TICKET_SECRET is missing from gs's env: without it the ACP box authenticates no one"],
7822
+ ["no puedo resolver el workspace de {}: sin ACP_TENANT la caja aceptar\xEDa tickets de otros clientes", "I can't resolve the workspace for {}: without ACP_TENANT the box would accept tickets from other customers"],
7823
+ ["la caja ACP {} arranc\xF3 en error: {}", "ACP box {} started in error: {}"],
7824
+ ["la caja ACP {} no lleg\xF3 a running en 90s", "ACP box {} didn't reach running in 90s"],
7825
+ ["la caja {} no lleg\xF3 a running (\xFAltimo estado: {})", "box {} didn't reach running (last state: {})"],
7826
+ ["la caja del agente naci\xF3 muda (no arranc\xF3 {}-runtime): {}", "the agent's box came up mute ({}-runtime didn't start): {}"],
7827
+ ["no se pudo re-rutear tras caja muerta", "couldn't re-route after a dead box"],
7828
+ ["fleet route race sin ganador", "fleet route race with no winner"],
7829
+ ["create: respuesta sin sandboxId", "create: response without sandboxId"],
7830
+ ["warmpool.claim: respuesta sin sandboxId", "warmpool.claim: response without sandboxId"],
7831
+ ["expose: respuesta sin url", "expose: response without url"],
7832
+ ["expose-raw: respuesta sin hostPort", "expose-raw: response without hostPort"],
7833
+ // — Cajas compartidas: voz, render, video, ortografía, eventos
7834
+ ["sin caja de eventos", "no events box"],
7835
+ ["sin caja de llamadas", "no calls box"],
7836
+ ["sin caja de ortograf\xEDa", "no spelling box"],
7837
+ ["sin caja de render", "no render box"],
7838
+ ["sin caja de transcripci\xF3n", "no transcription box"],
7839
+ ["sin caja de video", "no video box"],
7840
+ ["sin caja de voz", "no voice box"],
7841
+ ["audio requerido", "audio required"],
7842
+ ["audio vac\xEDo", "empty audio"],
7843
+ ["base64 inv\xE1lido", "invalid base64"],
7844
+ ["texto requerido", "text required"],
7845
+ ["html requerido", "html required"],
7846
+ ["file requerido", "file required"],
7847
+ ["room requerido", "room required"],
7848
+ ["jobId requerido", "jobId required"],
7849
+ ["montaje desconocido", "unknown video job"],
7850
+ ["clips requerido: [{name, url}]", "clips required: [{name, url}]"],
7851
+ ["captions: falta segments [{start, end, text}] (de stt.transcribe)", "captions: missing segments [{start, end, text}] (from stt.transcribe)"],
7852
+ ["captions: falta url https del video", "captions: missing the video's https url"],
7853
+ ["video-download: falta url https del video (LinkedIn, Facebook, Instagram, YouTube, Mux, Vimeo, Wistia, Loom o un .mp4 directo)", "video-download: missing the video's https url (LinkedIn, Facebook, Instagram, YouTube, Mux, Vimeo, Wistia, Loom or a direct .mp4)"],
7854
+ ["youtube-audio: falta url (video o playlist), queries (canciones) o query (b\xFAsqueda)", "youtube-audio: missing url (video or playlist), queries (songs) or query (search)"],
7855
+ ["youtube-audio: s\xF3lo links de youtube.com / youtu.be", "youtube-audio: only youtube.com / youtu.be links"],
7856
+ // — Meta Ads y Messenger
7857
+ ["Falta configurar la app de Meta Ads en gs.", "The Meta Ads app isn't configured in gs yet."],
7858
+ ["Fall\xF3 la llamada a Meta; intenta de nuevo.", "The call to Meta failed; try again."],
7859
+ ["Este espacio no tiene Meta conectado: con\xE9ctalo en Ajustes \u2192 Apps \u2192 Ghosty Ads.", "This workspace has no Meta connected: connect it in Settings \u2192 Apps \u2192 Ghosty Ads."],
7860
+ ["Elige la cuenta de anuncios en Ajustes \u2192 Apps \u2192 Ghosty Ads.", "Choose the ad account in Settings \u2192 Apps \u2192 Ghosty Ads."],
7861
+ ["Elige la p\xE1gina en Ajustes \u2192 Apps \u2192 Ghosty Ads.", "Choose the page in Settings \u2192 Apps \u2192 Ghosty Ads."],
7862
+ ["Esa campa\xF1a no es de la cuenta conectada.", "That campaign doesn't belong to the connected account."],
7863
+ ["Esa cuenta de anuncios no est\xE1 entre las tuyas.", "That ad account isn't one of yours."],
7864
+ ["Esa p\xE1gina no est\xE1 entre las tuyas.", "That page isn't one of yours."],
7865
+ ["Esa p\xE1gina ya est\xE1 conectada en otra cuenta.", "That page is already connected in another account."],
7866
+ ["El id de la p\xE1gina son s\xF3lo d\xEDgitos (est\xE1 en la URL de Meta Business o en \xABAcerca de\xBB).", `The page id is digits only (it's in the Meta Business URL or under "About").`],
7867
+ ["El link del anuncio tiene que ser https.", "The ad link must be https."],
7868
+ ["El presupuesto diario m\xEDnimo es de $20.", "The minimum daily budget is $20."],
7869
+ ["Deja al menos una ubicaci\xF3n.", "Keep at least one placement."],
7870
+ ["Faltan el texto o el creativo del anuncio.", "The ad text or creative is missing."],
7871
+ ["La campa\xF1a no tiene conjuntos.", "The campaign has no ad sets."],
7872
+ ["La columna derecha s\xF3lo acepta imagen.", "The right column only accepts images."],
7873
+ ["Un anuncio de columna derecha s\xF3lo sale en Facebook.", "A right-column ad only runs on Facebook."],
7874
+ ["La fecha de fin tiene que ser a futuro.", "The end date must be in the future."],
7875
+ ["La fecha de fin tiene que ser al menos ma\xF1ana.", "The end date must be at least tomorrow."],
7876
+ ["Llegamos al tope de cambios por hora en esta cuenta (protege la cuenta ante Meta). Intenta en un rato.", "We hit the hourly change limit on this account (it protects the account with Meta). Try again in a while."],
7877
+ ["Meta no acept\xF3 el token: {}", "Meta didn't accept the token: {}"],
7878
+ ["Meta no devolvi\xF3 la imagen subida.", "Meta didn't return the uploaded image."],
7879
+ ["Meta no devolvi\xF3 token", "Meta returned no token"],
7880
+ ["Meta no pudo procesar el video: revisa que sea un mp4 p\xFAblico.", "Meta couldn't process the video: check that it's a public mp4."],
7881
+ ["Meta sigue procesando el video: vuelve a intentar en un minuto.", "Meta is still processing the video: try again in a minute."],
7882
+ ["No pude descargar la imagen del creativo: revisa que la URL sea p\xFAblica.", "I couldn't download the creative's image: check that the URL is public."],
7883
+ ["No pude suscribir la p\xE1gina al webhook: {}", "I couldn't subscribe the page to the webhook: {}"],
7884
+ // — Ajustes de plataforma (staff)
7885
+ ["Campo desconocido \xAB{}\xBB en {}.", 'Unknown field "{}" in {}.'],
7886
+ ["El ajuste de {} no es un objeto.", "The setting for {} is not an object."],
7887
+ ["Plan inv\xE1lido en {}.", "Invalid plan in {}."],
7888
+ ["imageQuality inv\xE1lida en {}.", "invalid imageQuality in {}."],
7889
+ ["imagesPlan inv\xE1lido en {}.", "invalid imagesPlan in {}."],
7890
+ ["Tiene que ser un objeto {correo: ajuste}.", "Must be an object {email: setting}."],
7891
+ ["Tiene que ser un objeto {\u2026}", "Must be an object {\u2026}"],
7892
+ ["flags tiene que ser un objeto {nombre: true|false}", "flags must be an object {name: true|false}"],
7893
+ ["flags.{} tiene que ser true o false", "flags.{} must be true or false"],
7894
+ ["minBuild tiene que ser un entero \u2265 0", "minBuild must be an integer \u2265 0"],
7895
+ ["suggestBuild tiene que ser un entero \u2265 0", "suggestBuild must be an integer \u2265 0"],
7896
+ ["\xAB{}\xBB no parece correo.", `"{}" doesn't look like an email.`],
7897
+ // — Tools del agente (tools/dispatch): los lee el modelo, pero también pueden salir por el CLI
7898
+ ["Pon en tu respuesta: [Nota de voz]({})", "Put in your reply: [Nota de voz]({})"],
7899
+ ["Pon en tu respuesta: ![{}]({})", "Put in your reply: ![{}]({})"],
7900
+ ["Pon en tu respuesta: [{}]({})", "Put in your reply: [{}]({})"],
7901
+ ["La URL caduca en 6 h. B\xE1jalo y l\xE9elo con tus skills de lectura.", "The URL expires in 6 h. Download it and read it with your reading skills."],
7902
+ ["Tarjeta creada ({}), pero {}.", "Card created ({}), but {}."],
7903
+ ["{}. Lo dem\xE1s s\xED se guard\xF3.", "{}. Everything else was saved."],
7904
+ ["Ya avis\xE9 al equipo y dej\xE9 de contestar aqu\xED; desp\xEDdete diciendo que una persona lo atiende en breve.", "I've alerted the team and stopped replying here; sign off saying a person will help them shortly."],
7905
+ ["Ya la escuch\xF3; no repitas lo que dijiste por texto.", "They already heard it; don't repeat what you said in text."],
7906
+ ["Ya lo tiene; no repitas el enlace.", "They already have it; don't repeat the link."],
7907
+ ["WhatsApp no acept\xF3 la nota de voz; contesta por texto", "WhatsApp didn't accept the voice note; reply in text"],
7908
+ ["no se pudo generar la voz; contesta por texto", "couldn't generate the voice; reply in text"],
7909
+ ["no se mand\xF3: {}", "not sent: {}"],
7910
+ ["`emoji`: un emoji", "`emoji`: one emoji"],
7911
+ ["`para` no es una direcci\xF3n v\xE1lida", "`para` isn't a valid address"],
7912
+ ["`responder_a` no es una direcci\xF3n v\xE1lida", "`responder_a` isn't a valid address"],
7913
+ ["`pausa` inv\xE1lida", "invalid `pausa`"],
7914
+ ["falta `nombre`", "missing `nombre`"],
7915
+ ["falta `que`", "missing `que`"],
7916
+ ["falta `tarjeta` (o `tarjetas`)", "missing `tarjeta` (or `tarjetas`)"],
7917
+ ["falta `texto`", "missing `texto`"],
7918
+ ["falta `titulo`", "missing `titulo`"],
7919
+ ["faltan `asunto` o `cuerpo`", "missing `asunto` or `cuerpo`"],
7920
+ ["falta base64", "missing base64"],
7921
+ ["falta el texto", "missing the text"],
7922
+ ["manda al menos un rengl\xF3n en `items`", "send at least one row in `items`"],
7923
+ ["manda al menos una columna", "send at least one column"],
7924
+ ["monto inv\xE1lido", "invalid amount"],
7925
+ ["el cuerpo pasa de 20,000 caracteres", "the body exceeds 20,000 characters"],
7926
+ ["desde esta conversaci\xF3n s\xF3lo puedo mandar correo a la direcci\xF3n que te dio esta persona (la de su tarjeta o la que escribi\xF3 en el chat)", "from this conversation I can only email the address this person gave you (the one on their card or the one they wrote in the chat)"],
7927
+ ["desde este canal s\xF3lo puedes leer los archivos; escribir lo tiene que permitir el due\xF1o (ficha \u2192 Conectores)", "from this channel you can only read files; writing must be allowed by the owner (agent page \u2192 Connectors)"],
7928
+ ["desde este canal s\xF3lo puedes tocar la tarjeta de la persona con la que hablas; el resto del tablero se trabaja desde el equipo", "from this channel you can only touch the card of the person you're talking to; the rest of the board is handled by the team"],
7929
+ ["el due\xF1o no ha conectado Google Drive (Conectores \u2192 Google Drive)", "the owner hasn't connected Google Drive (Connectors \u2192 Google Drive)"],
7930
+ ["el due\xF1o no ha conectado MercadoPago en /app/settings/credentials", "the owner hasn't connected MercadoPago in /app/settings/credentials"],
7931
+ ["el due\xF1o no ha conectado Skydropx en /app/settings/credentials", "the owner hasn't connected Skydropx in /app/settings/credentials"],
7932
+ ["no tienes acceso a \xAB{}\xBB; usa drive_archivos para ver los que el due\xF1o eligi\xF3", `you don't have access to "{}"; use drive_archivos to see the ones the owner chose`],
7933
+ ["el turno de esta llamada ya termin\xF3", "this call's turn already ended"],
7934
+ ["no hay ning\xFAn turno en curso de este agente", "this agent has no turn in progress"],
7935
+ ["hay varias conversaciones trabajando a la vez; int\xE9ntalo en un momento", "several conversations are running at once; try again in a moment"],
7936
+ ["esta conversaci\xF3n no tiene tarjeta en el CRM", "this conversation has no card in the CRM"],
7937
+ ["esta tool s\xF3lo la puede usar tu due\xF1o", "only your owner can use this tool"],
7938
+ ["este agente no es de un espacio con Ghosty Ads", "this agent isn't in a workspace with Ghosty Ads"],
7939
+ ["la tool {} no est\xE1 disponible en este canal ({})", "tool {} isn't available on this channel ({})"],
7940
+ ["la tool {} pertenece al toolset \xAB{}\xBB, que este agente no tiene", `tool {} belongs to the "{}" toolset, which this agent doesn't have`],
7941
+ ["no existe la tool \xAB{}\xBB", `tool "{}" doesn't exist`],
7942
+ ["los n\xFAmeros de los anuncios son del equipo; no se comparten en conversaciones con clientes", "ad numbers belong to the team; they aren't shared in conversations with customers"],
7943
+ ["no encontr\xE9 esa tarea", "I couldn't find that task"],
7944
+ ["no encontr\xE9 esa tarjeta", "I couldn't find that card"],
7945
+ ["no encontr\xE9 ese seguimiento pendiente en esta conversaci\xF3n", "I couldn't find that pending follow-up in this conversation"],
7946
+ ["no encuentro la tarjeta \xAB{}\xBB; l\xE9elas con crm_board_read", `I can't find the card "{}"; read them with crm_board_read`],
7947
+ ["no tienes tablero de ventas asignado: p\xEDdele al due\xF1o que te asigne uno (ghosty.studio \u2192 Ventas)", "you have no sales board assigned: ask the owner to assign you one (ghosty.studio \u2192 Sales)"],
7948
+ ["no tienes tablero de ventas asignado", "you have no sales board assigned"],
7949
+ ["tu identidad se cambia desde el equipo, no en una conversaci\xF3n con clientes", "your identity is changed from the team, not in a conversation with customers"],
7950
+ // — Infra interna (casi nunca llega al CLI, pero por si acaso)
7951
+ ["SESSION_SECRET ausente en producci\xF3n \u2014 las cookies quedar\xEDan firmadas con un valor p\xFAblico. Inyectarlo con ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32).", "SESSION_SECRET missing in production \u2014 cookies would be signed with a public value. Inject it with ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32)."],
7952
+ ["sqld: fetch sin namespace", "sqld: fetch without namespace"],
7953
+ ["sqld: token sin namespace ser\xEDa un token maestro", "sqld: a token without namespace would be a master token"]
7954
+ ];
7955
+
7956
+ // src/i18n.ts
7957
+ var current = null;
7958
+ function detectLang(flag, env = process.env) {
7959
+ const pick = (v) => v && /^es\b|^es[_-]/i.test(v) ? "es" : v && /^en\b|^en[_-]|^c$|^posix$/i.test(v) ? "en" : null;
7960
+ return pick(flag) ?? pick(env.GHOSTY_LANG) ?? pick(env.LC_ALL) ?? pick(env.LC_MESSAGES) ?? pick(env.LANG) ?? "en";
7961
+ }
7962
+ function setLang(l) {
7963
+ current = l;
7964
+ }
7965
+ var lang = () => current ?? (current = detectLang());
7966
+ var t = (en, es) => lang() === "es" ? es : en;
7967
+ var compiled = null;
7968
+ var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7969
+ function compile(es) {
7970
+ return new RegExp(`^${es.split("{}").map(escape).join("(.+?)")}$`, "s");
7971
+ }
7972
+ function serverText(msg, l = lang()) {
7973
+ if (l === "es" || !msg) return msg;
7974
+ compiled ??= SERVER_MESSAGES.map(([es, en]) => ({ re: compile(es), en }));
7975
+ for (const c of compiled) {
7976
+ const m = c.re.exec(msg);
7977
+ if (!m) continue;
7978
+ let i = 1;
7979
+ return c.en.replace(/\{\}/g, () => m[i++] ?? "");
7980
+ }
7981
+ return msg;
7982
+ }
7983
+
7386
7984
  // src/util.ts
7387
7985
  var EXIT = { OK: 0, API: 1, USAGE: 2, AUTH: 3 };
7388
7986
  var CliError = class extends Error {
@@ -7408,7 +8006,7 @@ function out(data, human) {
7408
8006
  else process.stdout.write(human() + "\n");
7409
8007
  }
7410
8008
  function table(rows, cols) {
7411
- if (!rows.length) return dim("(empty)");
8009
+ if (!rows.length) return dim(t("(empty)", "(vac\xEDo)"));
7412
8010
  const cell = (v) => v === null || v === void 0 ? "\u2014" : String(v);
7413
8011
  const w = cols.map((c) => Math.max(c.length, ...rows.map((r) => cell(r[c]).length)));
7414
8012
  const line2 = (vals) => vals.map((v, i) => v.padEnd(w[i])).join(" ").trimEnd();
@@ -7425,7 +8023,7 @@ function fail(e) {
7425
8023
  if (globals.json) {
7426
8024
  process.stdout.write(JSON.stringify({ error: err.message, code: err.code, ...err.hint ? { hint: err.hint } : {} }) + "\n");
7427
8025
  } else {
7428
- info(red("Error: ") + err.message);
8026
+ info(red(t("Error: ", "Error: ")) + err.message);
7429
8027
  if (err.hint) info(dim(err.hint));
7430
8028
  }
7431
8029
  return exitAfterFlush(err.code);
@@ -7448,7 +8046,7 @@ var LOGIN_TIMEOUT_MS = 5 * 6e4;
7448
8046
  function configPath() {
7449
8047
  const profile = globals.profile || process.env.GHOSTY_PROFILE || "";
7450
8048
  if (profile && !/^[A-Za-z0-9_-]{1,40}$/.test(profile)) {
7451
- throw new CliError(`Invalid profile: ${profile}.`, EXIT.USAGE, "Use letters, digits, - or _");
8049
+ throw new CliError(t(`Invalid profile: ${profile}.`, `Perfil inv\xE1lido: ${profile}.`), EXIT.USAGE, t("Use letters, digits, - or _", "Usa letras, d\xEDgitos, - o _"));
7452
8050
  }
7453
8051
  return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "ghosty", profile ? `config.${profile}.json` : "config.json");
7454
8052
  }
@@ -7480,17 +8078,17 @@ async function tokenRequest(base, form) {
7480
8078
  });
7481
8079
  const body = await res.json().catch(() => ({}));
7482
8080
  if (!res.ok || !body.access_token) {
7483
- throw new CliError(`Login failed (${body.error ?? res.status}).`, EXIT.AUTH, "Run: ghosty login");
8081
+ throw new CliError(t(`Login failed (${body.error ?? res.status}).`, `Fall\xF3 el inicio de sesi\xF3n (${body.error ?? res.status}).`), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
7484
8082
  }
7485
8083
  return body;
7486
8084
  }
7487
- var CLOSE_PAGE = (ok, base) => `<!doctype html><html lang="es"><meta charset="utf-8">
8085
+ var CLOSE_PAGE = (ok, base) => `<!doctype html><html lang="${lang()}"><meta charset="utf-8">
7488
8086
  <meta name="viewport" content="width=device-width,initial-scale=1"><title>Ghosty CLI</title>
7489
8087
  <body style="font-family:system-ui,sans-serif;background:#F4F5FB;color:#191A20;display:grid;place-items:center;min-height:100vh;margin:0;padding:16px;box-sizing:border-box">
7490
8088
  <div style="text-align:center;max-width:420px">
7491
8089
  <img src="${base}/mascot.png" alt="Ghosty" width="112" height="133" style="display:block;margin:0 auto 20px">
7492
- <h1 style="font-weight:700;font-size:28px;margin:0 0 8px">${ok ? "\xA1Listo, ya entraste!" : "No se pudo entrar"}</h1>
7493
- <p style="margin:0;color:#4b4d57;line-height:1.5">${ok ? "Ya puedes cerrar esta pesta\xF1a y volver a la terminal." : "Vuelve a la terminal y corre <code>ghosty login</code> otra vez."}</p>
8090
+ <h1 style="font-weight:700;font-size:28px;margin:0 0 8px">${ok ? t("You're in!", "\xA1Listo, ya entraste!") : t("Sign-in failed", "No se pudo entrar")}</h1>
8091
+ <p style="margin:0;color:#4b4d57;line-height:1.5">${ok ? t("You can close this tab and go back to the terminal.", "Ya puedes cerrar esta pesta\xF1a y volver a la terminal.") : t("Go back to the terminal and run <code>ghosty login</code> again.", "Vuelve a la terminal y corre <code>ghosty login</code> otra vez.")}</p>
7494
8092
  </div>`;
7495
8093
  async function login() {
7496
8094
  const base = await baseUrl();
@@ -7509,12 +8107,12 @@ async function login() {
7509
8107
  clearTimeout(timer);
7510
8108
  server.close();
7511
8109
  if (ok) resolve3({ code: u.searchParams.get("code"), redirectUri: redirectUri2 });
7512
- else reject(new CliError(`Login was not completed (${u.searchParams.get("error") ?? "state mismatch"}).`, EXIT.AUTH, "Run: ghosty login"));
8110
+ else reject(new CliError(t(`Login was not completed (${u.searchParams.get("error") ?? "state mismatch"}).`, `No se complet\xF3 el inicio de sesi\xF3n (${u.searchParams.get("error") ?? "state mismatch"}).`), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login")));
7513
8111
  });
7514
8112
  let redirectUri2 = "";
7515
8113
  const timer = setTimeout(() => {
7516
8114
  server.close();
7517
- reject(new CliError("Login timed out after 5 minutes.", EXIT.AUTH, "Run: ghosty login"));
8115
+ reject(new CliError(t("Login timed out after 5 minutes.", "El inicio de sesi\xF3n caduc\xF3 a los 5 minutos."), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login")));
7518
8116
  }, LOGIN_TIMEOUT_MS);
7519
8117
  server.listen(0, "127.0.0.1", () => {
7520
8118
  const port = server.address().port;
@@ -7529,8 +8127,8 @@ async function login() {
7529
8127
  code_challenge_method: "S256"
7530
8128
  });
7531
8129
  if (globals.json) process.stdout.write(JSON.stringify({ event: "login_url", url }) + "\n");
7532
- info(`${lavender("\u203A")} Opening your browser to sign in to Ghosty\u2026`);
7533
- info(dim(` If it doesn't open, visit: ${url}`));
8130
+ info(`${lavender("\u203A")} ${t("Opening your browser to sign in to Ghosty\u2026", "Abriendo tu navegador para entrar a Ghosty\u2026")}`);
8131
+ info(dim(t(` If it doesn't open, visit: ${url}`, ` Si no se abre, entra a: ${url}`)));
7534
8132
  void openUrl(url);
7535
8133
  });
7536
8134
  });
@@ -7563,21 +8161,21 @@ async function loginDevice() {
7563
8161
  body: new URLSearchParams({ client_id: CLIENT_ID, scope: SCOPES })
7564
8162
  });
7565
8163
  const d = await res.json().catch(() => ({}));
7566
- if (!res.ok || !d.device_code) throw new CliError(`Could not start the login (${d.error ?? res.status}).`, EXIT.AUTH);
8164
+ if (!res.ok || !d.device_code) throw new CliError(t(`Could not start the login (${d.error ?? res.status}).`, `No se pudo iniciar el inicio de sesi\xF3n (${d.error ?? res.status}).`), EXIT.AUTH);
7567
8165
  if (globals.json) process.stdout.write(JSON.stringify({ event: "login_url", url: d.verification_uri_complete, code: d.user_code, method: "device" }) + "\n");
7568
- info(`${lavender("\u203A")} Open this link on any device and confirm the code ${lavender(d.user_code)}:`);
8166
+ info(`${lavender("\u203A")} ${t("Open this link on any device and confirm the code", "Abre esta liga en cualquier dispositivo y confirma el c\xF3digo")} ${lavender(d.user_code)}:`);
7569
8167
  info(` ${d.verification_uri_complete}`);
7570
8168
  let interval = (d.interval ?? 5) * 1e3;
7571
8169
  const deadline = Date.now() + (d.expires_in ?? 600) * 1e3;
7572
8170
  while (Date.now() < deadline) {
7573
8171
  await new Promise((r) => setTimeout(r, interval));
7574
- const t = await fetch(`${base}/oauth2/token`, {
8172
+ const tokenRes = await fetch(`${base}/oauth2/token`, {
7575
8173
  method: "POST",
7576
8174
  headers: { "content-type": "application/x-www-form-urlencoded" },
7577
8175
  body: new URLSearchParams({ client_id: CLIENT_ID, grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code: d.device_code })
7578
8176
  });
7579
- const body = await t.json().catch(() => ({}));
7580
- if (t.ok && body.access_token) {
8177
+ const body = await tokenRes.json().catch(() => ({}));
8178
+ if (tokenRes.ok && body.access_token) {
7581
8179
  const prev = await readConfig();
7582
8180
  const cfg = {
7583
8181
  ...prev.baseUrl ? { baseUrl: prev.baseUrl } : {},
@@ -7593,10 +8191,10 @@ async function loginDevice() {
7593
8191
  interval += 5e3;
7594
8192
  continue;
7595
8193
  }
7596
- if (body.error === "access_denied") throw new CliError("The login was rejected.", EXIT.AUTH, "Run: ghosty login");
7597
- throw new CliError(`Login failed (${body.error ?? t.status}).`, EXIT.AUTH, "Run: ghosty login");
8194
+ if (body.error === "access_denied") throw new CliError(t("The login was rejected.", "Se rechaz\xF3 el inicio de sesi\xF3n."), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
8195
+ throw new CliError(t(`Login failed (${body.error ?? tokenRes.status}).`, `Fall\xF3 el inicio de sesi\xF3n (${body.error ?? tokenRes.status}).`), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
7598
8196
  }
7599
- throw new CliError("The code expired before it was confirmed.", EXIT.AUTH, "Run: ghosty login");
8197
+ throw new CliError(t("The code expired before it was confirmed.", "El c\xF3digo caduc\xF3 antes de confirmarse."), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
7600
8198
  }
7601
8199
  async function refresh(cfg) {
7602
8200
  if (!cfg.refreshToken) return null;
@@ -7634,8 +8232,8 @@ async function currentToken() {
7634
8232
  if (explicit) return explicit;
7635
8233
  let cfg = await readConfig();
7636
8234
  if (!cfg.accessToken) {
7637
- if (!interactive()) throw new CliError("Not logged in.", EXIT.AUTH, "Run: ghosty login (or set GHOSTY_TOKEN)");
7638
- info(dim("Not logged in yet \u2014 let's fix that."));
8235
+ if (!interactive()) throw new CliError(t("Not logged in.", "No has iniciado sesi\xF3n."), EXIT.AUTH, t("Run: ghosty login (or set GHOSTY_TOKEN)", "Corre: ghosty login (o pon GHOSTY_TOKEN)"));
8236
+ info(dim(t("Not logged in yet \u2014 let's fix that.", "Todav\xEDa no inicias sesi\xF3n; vamos a arreglarlo.")));
7639
8237
  cfg = await (hasLocalBrowser() ? login() : loginDevice());
7640
8238
  }
7641
8239
  if (cfg.expiresAt !== void 0 && cfg.expiresAt - Date.now() < 6e4) cfg = await refresh(cfg) ?? cfg;
@@ -7650,7 +8248,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
7650
8248
  const req = httpsRequest(`${base}${path}`, {
7651
8249
  method: "POST",
7652
8250
  timeout: timeoutMs,
7653
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.19.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8251
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.20.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
7654
8252
  }, (res) => {
7655
8253
  let buf = "";
7656
8254
  res.on("data", (c) => buf += c);
@@ -7664,7 +8262,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
7664
8262
  resolve3({ status: res.statusCode ?? 0, retryAfter: retryAfterSeconds(res.headers["retry-after"]), body: parsed });
7665
8263
  });
7666
8264
  });
7667
- req.on("timeout", () => req.destroy(new Error(`no answer in ${Math.round(timeoutMs / 6e4)} min`)));
8265
+ req.on("timeout", () => req.destroy(new Error(t(`no answer in ${Math.round(timeoutMs / 6e4)} min`, `sin respuesta en ${Math.round(timeoutMs / 6e4)} min`))));
7668
8266
  req.on("error", (e) => resolve3({ status: 0, retryAfter: null, body: { error: String(e) } }));
7669
8267
  req.end(data);
7670
8268
  });
@@ -7672,8 +8270,8 @@ async function postLong(path, body, timeoutMs = 9e5) {
7672
8270
  function retryAfterSeconds(h, now = Date.now()) {
7673
8271
  if (!h) return null;
7674
8272
  if (/^\d+$/.test(h.trim())) return Number(h.trim());
7675
- const t = Date.parse(h);
7676
- return Number.isNaN(t) ? null : Math.max(0, Math.round((t - now) / 1e3));
8273
+ const t2 = Date.parse(h);
8274
+ return Number.isNaN(t2) ? null : Math.max(0, Math.round((t2 - now) / 1e3));
7677
8275
  }
7678
8276
  async function api(path, opts = {}) {
7679
8277
  const res = await request(path, opts);
@@ -7692,7 +8290,8 @@ async function request(path, opts = {}, retried = false) {
7692
8290
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
7693
8291
  headers: {
7694
8292
  authorization: `Bearer ${token}`,
7695
- "user-agent": `ghosty-cli/${"0.19.0"}`,
8293
+ "user-agent": `ghosty-cli/${"0.20.0"}`,
8294
+ "accept-language": lang(),
7696
8295
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
7697
8296
  ...opts.headers
7698
8297
  },
@@ -7713,12 +8312,13 @@ async function toError(res) {
7713
8312
  body = JSON.parse(text);
7714
8313
  } catch {
7715
8314
  }
7716
- const detail = body.message || body.error_description || (body.detalle && body.error ? `${body.error}: ${body.detalle}` : body.error) || text.slice(0, 300) || res.statusText;
7717
- if (res.status === 401) return new CliError("Your session is not valid anymore.", EXIT.AUTH, "Run: ghosty login");
7718
- if (res.status === 403) return new CliError(`Not allowed: ${detail}`, EXIT.API, "If the agent was shared with you, its workspace (board, channels, numbers) stays with the owner: ask them to add you to that workspace. Otherwise run: ghosty login");
7719
- if (res.status === 404) return new CliError("Not found.", EXIT.API, "Check the agent id (ghosty agents ls) or that your token belongs to that agent.");
8315
+ const raw = body.message || body.error_description || (body.detalle && body.error ? `${body.error}: ${body.detalle}` : body.error) || text.slice(0, 300) || res.statusText;
8316
+ const detail = serverText(raw);
8317
+ if (res.status === 401) return new CliError(t("Your session is not valid anymore.", "Tu sesi\xF3n ya no es v\xE1lida."), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
8318
+ if (res.status === 403) return new CliError(t(`Not allowed: ${detail}`, `No permitido: ${detail}`), EXIT.API, t("If the agent was shared with you, its workspace (board, channels, numbers) stays with the owner: ask them to add you to that workspace. Otherwise run: ghosty login", "Si el agente se comparti\xF3 contigo, su espacio (tablero, canales, n\xFAmeros) sigue siendo del due\xF1o: p\xEDdele que te agregue a ese espacio. Si no, corre: ghosty login"));
8319
+ if (res.status === 404) return new CliError(t("Not found.", "No se encontr\xF3."), EXIT.API, t("Check the agent id (ghosty agents ls) or that your token belongs to that agent.", "Revisa el id del agente (ghosty agents ls) o que tu token sea de ese agente."));
7720
8320
  if (body.error === "restart_no_aplica" || body.error === "agente_sin_maquina") {
7721
- return new CliError("This agent doesn't need a restart: new files, skills and MCP load on its next turn.", EXIT.API);
8321
+ return new CliError(t("This agent doesn't need a restart: new files, skills and MCP load on its next turn.", "Este agente no necesita reinicio: los archivos, skills y MCP nuevos se cargan en su siguiente turno."), EXIT.API);
7722
8322
  }
7723
8323
  return new CliError(`${res.status} ${detail}`, EXIT.API);
7724
8324
  }
@@ -7732,18 +8332,18 @@ import { dirname as dirname2, join as join3, relative, resolve } from "node:path
7732
8332
  import { createInterface } from "node:readline/promises";
7733
8333
  var canPrompt = () => Boolean(process.stdin.isTTY && process.stderr.isTTY);
7734
8334
  function requireYesIfHeadless(yes, retry) {
7735
- if (!yes && !canPrompt()) throw usage("--yes required when not running interactively.", `Run: ${retry} --yes`);
8335
+ if (!yes && !canPrompt()) throw usage(t("--yes required when not running interactively.", "Hace falta --yes cuando no corre en una terminal."), t(`Run: ${retry} --yes`, `Corre: ${retry} --yes`));
7736
8336
  }
7737
8337
  async function confirm(question, opts) {
7738
8338
  if (opts.yes) return;
7739
- if (!canPrompt()) throw usage("--yes required when not running interactively.", `Run: ${opts.retry} --yes`);
8339
+ if (!canPrompt()) throw usage(t("--yes required when not running interactively.", "Hace falta --yes cuando no corre en una terminal."), t(`Run: ${opts.retry} --yes`, `Corre: ${opts.retry} --yes`));
7740
8340
  const rl = createInterface({ input: process.stdin, output: process.stderr });
7741
8341
  try {
7742
8342
  const ask = opts.expect ? `${question}
7743
- Type ${bold(opts.expect)} to confirm: ` : `${question} [y/N] `;
8343
+ ${t("Type", "Escribe")} ${bold(opts.expect)} ${t("to confirm", "para confirmar")}: ` : `${question} ${t("[y/N]", "[s/N]")} `;
7744
8344
  const a = (await rl.question(ask)).trim();
7745
- const ok = opts.expect ? a === opts.expect : /^y(es)?$/i.test(a);
7746
- if (!ok) throw usage("Cancelled.");
8345
+ const ok = opts.expect ? a === opts.expect : /^(y(es)?|s[ií]?)$/i.test(a);
8346
+ if (!ok) throw usage(t("Cancelled.", "Cancelado."));
7747
8347
  } finally {
7748
8348
  rl.close();
7749
8349
  }
@@ -7772,7 +8372,7 @@ async function tryPost(id, body) {
7772
8372
  const r = await postLong(`/api/v2/agents/${enc(id)}/try`, body);
7773
8373
  if ((r.status === 503 || r.status === 409 || r.status === 0) && attempt < 40) {
7774
8374
  const wait = r.status === 503 ? r.retryAfter ?? 15 : Math.min(30, 2 ** Math.min(attempt, 4) + 1);
7775
- if (!globals.json) info(dim(` ${r.status === 503 ? "Ghosty is restarting" : r.status === 409 ? "previous turn still running" : "network error"} \u2014 retrying in ${wait}s`));
8375
+ if (!globals.json) info(dim(` ${r.status === 503 ? t("Ghosty is restarting", "Ghosty se est\xE1 reiniciando") : r.status === 409 ? t("previous turn still running", "el turno anterior sigue en curso") : t("network error", "error de red")} \u2014 ${t(`retrying in ${wait}s`, `reintento en ${wait}s`)}`));
7776
8376
  await sleep(wait * 1e3);
7777
8377
  continue;
7778
8378
  }
@@ -7795,7 +8395,7 @@ function richLine(s) {
7795
8395
  const str = (k) => typeof d[k] === "string" ? d[k] : "";
7796
8396
  switch (s.rich) {
7797
8397
  case "ubicacion":
7798
- return `\u{1F4CD} ${str("nombre") || "location"} \xB7 ${Number(d.latitud).toFixed(4)},${Number(d.longitud).toFixed(4)}${str("direccion") ? dim(` ${str("direccion")}`) : ""}`;
8398
+ return `\u{1F4CD} ${str("nombre") || t("location", "ubicaci\xF3n")} \xB7 ${Number(d.latitud).toFixed(4)},${Number(d.longitud).toFixed(4)}${str("direccion") ? dim(` ${str("direccion")}`) : ""}`;
7799
8399
  case "boton":
7800
8400
  return `\u{1F518} ${str("boton")} ${dim(str("url"))} ${str("texto")}`;
7801
8401
  case "contacto":
@@ -7803,9 +8403,9 @@ function richLine(s) {
7803
8403
  case "encuesta":
7804
8404
  return `\u{1F4CA} ${str("pregunta")}`;
7805
8405
  case "reaccion":
7806
- return `${str("emoji")} ${dim(`reaction to ${str("mensajeId") || "the last message"}`)}`;
8406
+ return `${str("emoji")} ${dim(t(`reaction to ${str("mensajeId") || "the last message"}`, `reacci\xF3n a ${str("mensajeId") || "el \xFAltimo mensaje"}`))}`;
7807
8407
  case "cita":
7808
- return `\u21A9\uFE0F ${str("texto")} ${dim(`quoting ${str("mensajeId") || "the last message"}`)}`;
8408
+ return `\u21A9\uFE0F ${str("texto")} ${dim(t(`quoting ${str("mensajeId") || "the last message"}`, `citando ${str("mensajeId") || "el \xFAltimo mensaje"}`))}`;
7809
8409
  }
7810
8410
  }
7811
8411
  function sentText(sent) {
@@ -7815,33 +8415,33 @@ function sentText(sent) {
7815
8415
  }
7816
8416
  async function tryWaba(id, text, v) {
7817
8417
  const phone = v.session ?? "";
7818
- if (!/^\d{10,15}$/.test(phone)) throw usage("--waba needs --session <phone> (10-15 digits, a made-up number).", 'Example: ghosty try <agent-id> "hola" --waba --session 5210000000009');
7819
- if ((v.media?.length ?? 0) > 1) throw usage("One --media per turn (WhatsApp sends one file per message).", "Send several turns, one file each.");
7820
- if (v["as-formmy"] && !v.media?.length) throw usage("--as-formmy goes with --media FILE.", 'Example: ghosty try <agent-id> "" --waba --session 5210000000009 --media nota.ogg --as-formmy');
7821
- if (!text && !v.media?.length && !v.reset) throw usage("Missing message.", 'Example: ghosty try <agent-id> "hola" --waba --session 5210000000009');
8418
+ if (!/^\d{10,15}$/.test(phone)) throw usage(t("--waba needs --session <phone> (10-15 digits, a made-up number).", "--waba necesita --session <tel\xE9fono> (10-15 d\xEDgitos, un n\xFAmero inventado)."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "hola" --waba --session 5210000000009');
8419
+ if ((v.media?.length ?? 0) > 1) throw usage(t("One --media per turn (WhatsApp sends one file per message).", "Un --media por turno (WhatsApp manda un archivo por mensaje)."), t("Send several turns, one file each.", "Manda varios turnos, un archivo en cada uno."));
8420
+ if (v["as-formmy"] && !v.media?.length) throw usage(t("--as-formmy goes with --media FILE.", "--as-formmy va con --media ARCHIVO."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "" --waba --session 5210000000009 --media nota.ogg --as-formmy');
8421
+ if (!text && !v.media?.length && !v.reset) throw usage(t("Missing message.", "Falta el mensaje."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "hola" --waba --session 5210000000009');
7822
8422
  let history;
7823
8423
  if (v.history) {
7824
8424
  const raw = JSON.parse(await readFile2(v.history, "utf8"));
7825
8425
  history = Array.isArray(raw) ? raw : raw.messages;
7826
- if (!Array.isArray(history)) throw usage(`${v.history}: expected [{role, text, at?}] or {messages: [...]}.`);
7827
- if (!v.reset) info(dim("Note: history only counts on the first turn of a session; add --reset to start over."));
8426
+ if (!Array.isArray(history)) throw usage(t(`${v.history}: expected [{role, text, at?}] or {messages: [...]}.`, `${v.history}: se esperaba [{role, text, at?}] o {messages: [...]}.`));
8427
+ if (!v.reset) info(dim(t("Note: history only counts on the first turn of a session; add --reset to start over.", "Nota: el historial s\xF3lo cuenta en el primer turno de una sesi\xF3n; agrega --reset para empezar de nuevo.")));
7828
8428
  }
7829
8429
  const media = v.media?.[0] ? await uploadMedia(v.media[0]) : void 0;
7830
- if (process.stderr.isTTY && !globals.json && (text || media)) info(dim("Thinking\u2026 (the real WhatsApp path; nothing is sent)"));
8430
+ if (process.stderr.isTTY && !globals.json && (text || media)) info(dim(t("Thinking\u2026 (the real WhatsApp path; nothing is sent)", "Pensando\u2026 (el camino real de WhatsApp; no se manda nada)")));
7831
8431
  const r = await tryPost(id, { as: "waba", session: phone, reset: v.reset, text: text ?? "", ...media ? { media } : {}, ...history ? { history } : {}, ...v["as-formmy"] ? { asFormmy: true } : {} });
7832
- if (r.status !== 200) throw Object.assign(new Error(`${r.status} ${r.body.error ?? "turn failed"}`), { code: 1 });
8432
+ if (r.status !== 200) throw Object.assign(new Error(`${r.status} ${r.body.error ? serverText(r.body.error) : t("turn failed", "el turno fall\xF3")}`), { code: 1 });
7833
8433
  const via = r.body.media ? `
7834
- ${r.body.media.copied ? dim("attachment: downloaded through the Formmy-like proxy and copied to gs") : red("attachment: NOT copied to gs (the proxy download failed)")}` : "";
7835
- out(r.body, () => (r.body.sent ? sentText(r.body.sent) || dim("(the agent sent nothing)") : dim("Session reset.")) + via + (r.body.error ? `
7836
- ${dim(`(warning: ${r.body.error})`)}` : ""));
8434
+ ${r.body.media.copied ? dim(t("attachment: downloaded through the Formmy-like proxy and copied to gs", "adjunto: descargado por el proxy tipo Formmy y copiado a gs")) : red(t("attachment: NOT copied to gs (the proxy download failed)", "adjunto: NO se copi\xF3 a gs (fall\xF3 la descarga por el proxy)"))}` : "";
8435
+ out(r.body, () => (r.body.sent ? sentText(r.body.sent) || dim(t("(the agent sent nothing)", "(el agente no mand\xF3 nada)")) : dim(t("Session reset.", "Sesi\xF3n reiniciada."))) + via + (r.body.error ? `
8436
+ ${dim(`(${t("warning", "aviso")}: ${serverText(r.body.error)})`)}` : ""));
7837
8437
  }
7838
8438
  async function tryReplay(id, v) {
7839
- if (!v.out) throw usage("--replay needs --out DIR (the report is written there after every step).", "Example: ghosty try <agent-id> --replay muestra.json --out ./replay");
8439
+ if (!v.out) throw usage(t("--replay needs --out DIR (the report is written there after every step).", "--replay necesita --out DIR (ah\xED se escribe el reporte despu\xE9s de cada paso)."), t("Example", "Ejemplo") + ": ghosty try <agent-id> --replay muestra.json --out ./replay");
7840
8440
  const max = Number(v.max ?? 15);
7841
8441
  const conc = Math.max(1, Math.min(30, Number(v.concurrency ?? 8)));
7842
8442
  const input = JSON.parse(await readFile2(v.replay, "utf8"));
7843
8443
  const convs = input.sample ?? [];
7844
- if (!convs.length) throw usage(`${v.replay}: no conversations in "sample".`, 'Format: {"sample": [{"id", "fakePhone", "messages": [{"role": "user"|"agent", "text"}]}]}');
8444
+ if (!convs.length) throw usage(t(`${v.replay}: no conversations in "sample".`, `${v.replay}: no hay conversaciones en "sample".`), t("Format", "Formato") + ': {"sample": [{"id", "fakePhone", "messages": [{"role": "user"|"agent", "text"}]}]}');
7845
8445
  await mkdir2(v.out, { recursive: true });
7846
8446
  const file = join2(v.out, "replay.json");
7847
8447
  const prev = v.resume ? JSON.parse(await readFile2(file, "utf8").catch(() => "null")) : null;
@@ -7849,7 +8449,7 @@ async function tryReplay(id, v) {
7849
8449
  report.done = false;
7850
8450
  let saving = Promise.resolve();
7851
8451
  const save = () => saving = saving.then(() => writeFile2(file, JSON.stringify(report, null, 1)));
7852
- info(`${bold("Replay")} ${convs.length} conversation(s), up to ${max} customer turns each, ${conc} at a time \u2192 ${file}`);
8452
+ info(`${bold("Replay")} ${t(`${convs.length} conversation(s), up to ${max} customer turns each, ${conc} at a time`, `${convs.length} conversaci\xF3n(es), hasta ${max} turnos del cliente cada una, ${conc} a la vez`)} \u2192 ${file}`);
7853
8453
  const queue = [...convs];
7854
8454
  const worker = async () => {
7855
8455
  for (let c = queue.shift(); c; c = queue.shift()) {
@@ -7869,7 +8469,7 @@ async function tryReplay(id, v) {
7869
8469
  await save();
7870
8470
  }
7871
8471
  const bad = entry.steps.filter((s) => s.status !== 200 || !s.reply).length;
7872
- info(` ${bad ? red("!") : lavender("\u2713")} ${c.id}: ${entry.steps.length} turn(s)${bad ? `, ${bad} without an answer` : ""}`);
8472
+ info(` ${bad ? red("!") : lavender("\u2713")} ${c.id}: ${t(`${entry.steps.length} turn(s)`, `${entry.steps.length} turno(s)`)}${bad ? t(`, ${bad} without an answer`, `, ${bad} sin respuesta`) : ""}`);
7873
8473
  }
7874
8474
  };
7875
8475
  await Promise.all(Array.from({ length: Math.min(conc, convs.length) }, worker));
@@ -7879,16 +8479,17 @@ async function tryReplay(id, v) {
7879
8479
  const steps = Object.values(report.results).flatMap((r) => r.steps);
7880
8480
  const failed = steps.filter((s) => s.status !== 200 || !s.reply).length;
7881
8481
  const summary = { file, conversations: Object.keys(report.results).length, turns: steps.length, failed, medianMs: steps.map((s) => s.ms).sort((a, b) => a - b)[Math.floor(steps.length / 2)] ?? 0 };
7882
- out(summary, () => `${failed ? red("!") : lavender("\u2713")} ${summary.turns} turn(s) in ${summary.conversations} conversation(s), ${failed} without an answer, median ${Math.round(summary.medianMs / 1e3)}s
7883
- ${dim(`Report: ${file} Failed turns: ghosty turns ls ${id} --errors --since 2h`)}`);
8482
+ out(summary, () => `${failed ? red("!") : lavender("\u2713")} ${t(`${summary.turns} turn(s) in ${summary.conversations} conversation(s), ${failed} without an answer, median ${Math.round(summary.medianMs / 1e3)}s`, `${summary.turns} turno(s) en ${summary.conversations} conversaci\xF3n(es), ${failed} sin respuesta, mediana ${Math.round(summary.medianMs / 1e3)}s`)}
8483
+ ${dim(t(`Report: ${file} Failed turns: ghosty turns ls ${id} --errors --since 2h`, `Reporte: ${file} Turnos fallidos: ghosty turns ls ${id} --errors --since 2h`))}`);
7884
8484
  if (failed) process.exitCode = 1;
7885
8485
  }
7886
8486
 
7887
8487
  // src/commands/agents.ts
7888
8488
  var enc2 = encodeURIComponent;
7889
8489
  var agentPath = (id, rest = "") => `/api/v2/agents/${enc2(id)}${rest}`;
8490
+ var WHAT_ES = { "agent id": "el id del agente", path: "la ruta", email: "el correo", "skill slug": "el slug de la skill" };
7890
8491
  var need = (v, what, example) => {
7891
- if (!v) throw usage(`Missing ${what}.`, `Example: ${example}`);
8492
+ if (!v) throw usage(t(`Missing ${what}.`, `Falta ${WHAT_ES[what] ?? what}.`), `${t("Example", "Ejemplo")}: ${example}`);
7892
8493
  return v;
7893
8494
  };
7894
8495
  async function agentsLs(workspace) {
@@ -7905,14 +8506,14 @@ async function agentsLs(workspace) {
7905
8506
  ...a.compartidoPor ? { sharedBy: a.compartidoPor } : {}
7906
8507
  }));
7907
8508
  out(rows, () => `${table(rows, ["id", "name", "engine", "workspace", "lastActivity"])}${workspace ? `
7908
- ${dim(`only ${workspace} (ghosty use) \xB7 all: --all`)}` : ""}`);
8509
+ ${dim(t(`only ${workspace} (ghosty use) \xB7 all: --all`, `s\xF3lo ${workspace} (ghosty use) \xB7 todos: --all`))}` : ""}`);
7909
8510
  }
7910
8511
  function parseEnv(raw) {
7911
8512
  if (!raw) return void 0;
7912
8513
  const env = {};
7913
8514
  for (const pair of raw.split(",")) {
7914
8515
  const i = pair.indexOf("=");
7915
- if (i < 1) throw usage(`Bad --env entry: ${pair}.`, "Example: --env GHOSTY_THINKING_EFFORT=max");
8516
+ if (i < 1) throw usage(t(`Bad --env entry: ${pair}.`, `Entrada de --env inv\xE1lida: ${pair}.`), t("Example", "Ejemplo") + ": --env GHOSTY_THINKING_EFFORT=max");
7916
8517
  env[pair.slice(0, i).trim()] = pair.slice(i + 1);
7917
8518
  }
7918
8519
  return env;
@@ -7922,7 +8523,7 @@ function parseChannels(raw) {
7922
8523
  const ch = {};
7923
8524
  for (const pair of raw.split(",").map((x) => x.trim()).filter(Boolean)) {
7924
8525
  const [k, val] = pair.split("=");
7925
- if (!["teams", "web", "whatsapp", "messenger", "wa"].includes(k) || val !== "on" && val !== "off") throw usage(`Bad --channels entry: ${pair}.`, "Use teams|web|whatsapp|messenger|wa=on|off, comma-separated (wa = personal WhatsApp)");
8526
+ if (!["teams", "web", "whatsapp", "messenger", "wa"].includes(k) || val !== "on" && val !== "off") throw usage(t(`Bad --channels entry: ${pair}.`, `Entrada de --channels inv\xE1lida: ${pair}.`), t("Use teams|web|whatsapp|messenger|wa=on|off, comma-separated (wa = personal WhatsApp)", "Usa teams|web|whatsapp|messenger|wa=on|off, separados por comas (wa = WhatsApp personal)"));
7926
8527
  ch[k] = val === "on";
7927
8528
  }
7928
8529
  return ch;
@@ -7940,20 +8541,20 @@ async function agentsCreate(v) {
7940
8541
  const channels = parseChannels(v.channels);
7941
8542
  if (channels) body.channels = channels;
7942
8543
  if (v.like) Object.assign(body, { like: v.like, ...v["copy-dbs"] ? { copyDbs: true } : {}, ...v["dry-run"] ? { dryRun: true } : {}, ...v.board ? { board: v.board } : {}, ...v["no-notify"] ? { notify: false } : {} });
7943
- else if (v["dry-run"] || v.board || v["no-notify"]) throw usage("--dry-run, --board and --no-notify go with --like.");
8544
+ else if (v["dry-run"] || v.board || v["no-notify"]) throw usage(t("--dry-run, --board and --no-notify go with --like.", "--dry-run, --board y --no-notify van con --like."));
7944
8545
  if (v.like && v["dry-run"]) {
7945
8546
  const p = await api("/api/v2/me/agents", { method: "POST", body });
7946
- return out(p, () => `${dim(`like ${v.like} \xB7 ${p.engine ?? "?"} \xB7 ${p.model ?? "default"}${p.workspace ? ` \xB7 workspace ${p.workspace}` : ""}`)}
7947
- ${p.plan.join("\n")}
7948
- ${dim("Not copied: prompt, env, channels. Dry run: nothing created.")}`);
8547
+ return out(p, () => `${dim(`${t("like", "como")} ${v.like} \xB7 ${p.engine ?? "?"} \xB7 ${p.model ?? t("default", "por defecto")}${p.workspace ? ` \xB7 ${t("workspace", "espacio")} ${p.workspace}` : ""}`)}
8548
+ ${p.plan.map((x) => serverText(x)).join("\n")}
8549
+ ${dim(t("Not copied: prompt, env, channels. Dry run: nothing created.", "No se copian: prompt, env, canales. Simulacro: no se cre\xF3 nada."))}`);
7949
8550
  }
7950
8551
  const r = await api("/api/v2/me/agents", { method: "POST", body });
7951
8552
  const where = r.workspace ? `
7952
- workspace: ${r.workspace}${r.handle ? ` \xB7 @${r.handle} in Teams` : dim(" \xB7 not active in Teams")}` : "";
8553
+ ${t("workspace", "espacio")}: ${r.workspace}${r.handle ? ` \xB7 @${r.handle} ${t("in Teams", "en Teams")}` : dim(t(" \xB7 not active in Teams", " \xB7 no est\xE1 activo en Teams"))}` : "";
7953
8554
  const copied = r.copied?.length ? `
7954
- ${r.copied.map((l) => ` ${l}`).join("\n")}
7955
- ${dim(" Now give it its own prompt: ghosty agents set " + r.id + " --prompt-file PROMPT.md")}` : "";
7956
- out(r, () => `${lavender("\u2713")} Created ${r.name} ${dim(`(${r.engine} \xB7 ${r.model ?? "default"})`)}
8555
+ ${r.copied.map((l) => ` ${serverText(l)}`).join("\n")}
8556
+ ${dim(t(" Now give it its own prompt: ghosty agents set ", " Ahora dale su propio prompt: ghosty agents set ") + r.id + " --prompt-file PROMPT.md")}` : "";
8557
+ out(r, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${r.name} ${dim(`(${r.engine} \xB7 ${r.model ?? t("default", "por defecto")})`)}
7957
8558
  id: ${r.id}${where}${copied}`);
7958
8559
  }
7959
8560
  async function agentsGet(id, fields, promptOut) {
@@ -7962,7 +8563,7 @@ async function agentsGet(id, fields, promptOut) {
7962
8563
  const r = await api(agentPath(id, "?fields=prompt"));
7963
8564
  const prompt = r.prompt ?? "";
7964
8565
  await writeFile3(promptOut, prompt);
7965
- return out({ saved: resolve(promptOut), bytes: Buffer.byteLength(prompt) }, () => `${lavender("\u2713")} Prompt \u2192 ${promptOut} ${dim(`(${Buffer.byteLength(prompt)} bytes) \xB7 back: ghosty agents set ${id} --prompt-file ${promptOut}`)}`);
8566
+ return out({ saved: resolve(promptOut), bytes: Buffer.byteLength(prompt) }, () => `${lavender("\u2713")} Prompt \u2192 ${promptOut} ${dim(`(${Buffer.byteLength(prompt)} bytes) \xB7 ${t("back", "de vuelta")}: ghosty agents set ${id} --prompt-file ${promptOut}`)}`);
7966
8567
  }
7967
8568
  const q = fields ? `?fields=${enc2(fields)}` : "?full=1";
7968
8569
  out(await api(agentPath(id, q)));
@@ -7971,16 +8572,16 @@ async function agentsSet(id, v) {
7971
8572
  id = need(id, "agent id", "ghosty agents set <agent-id> --prompt-file PROMPT.md");
7972
8573
  if (v["prompt-base"]) {
7973
8574
  const unlink = v["prompt-base"] === "none";
7974
- if (!unlink && !v["prompt-overlay"]) throw usage("Missing --prompt-overlay FILE.", 'Its format: {"edits":[{"what":"name","find":"Tania","replace":"Pia","all":true}]} (regex: true for patterns)');
8575
+ if (!unlink && !v["prompt-overlay"]) throw usage(t("Missing --prompt-overlay FILE.", "Falta --prompt-overlay FILE."), t("Its format", "Su formato") + ': {"edits":[{"what":"name","find":"Tania","replace":"Pia","all":true}]} (regex: true ' + t("for patterns", "para patrones") + ")");
7975
8576
  const promptBase = unlink ? null : { agentId: v["prompt-base"], overlay: JSON.parse(await readFile3(v["prompt-overlay"], "utf8")) };
7976
8577
  const r2 = await api(agentPath(id), { method: "PATCH", body: { promptBase, ...v["dry-run"] ? { dryRun: true } : {} } });
7977
8578
  if (v["dry-run"] && r2.prompt) {
7978
8579
  const out_ = v.out;
7979
8580
  if (out_) await writeFile3(out_, r2.prompt);
7980
- return out({ ...r2, ...out_ ? { prompt: void 0, saved: resolve(out_) } : {} }, () => `${r2.chars} chars (base ${r2.baseChars}): every edit applies.${out_ ? ` Written to ${out_}.` : dim(" --out FILE to read it.")}
7981
- ${dim("Dry run: nothing changed.")}`);
8581
+ return out({ ...r2, ...out_ ? { prompt: void 0, saved: resolve(out_) } : {} }, () => `${t(`${r2.chars} chars (base ${r2.baseChars}): every edit applies.`, `${r2.chars} caracteres (base ${r2.baseChars}): todas las ediciones aplican.`)}${out_ ? t(` Written to ${out_}.`, ` Escrito en ${out_}.`) : dim(t(" --out FILE to read it.", " --out FILE para leerlo."))}
8582
+ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
7982
8583
  }
7983
- return out(r2, () => r2.unlinked ? `${lavender("\u2713")} Unlinked: the prompt stays as it is and is edited by hand again.` : `${lavender("\u2713")} Prompt = ${v["prompt-base"]} + ${v["prompt-overlay"]} (${r2.chars} chars). It regenerates when the base changes; ghosty agents doctor ${id} tells if the overlay stops applying.`);
8584
+ return out(r2, () => r2.unlinked ? `${lavender("\u2713")} ${t("Unlinked: the prompt stays as it is and is edited by hand again.", "Desenlazado: el prompt se queda como est\xE1 y se vuelve a editar a mano.")}` : `${lavender("\u2713")} Prompt = ${v["prompt-base"]} + ${v["prompt-overlay"]} ${t(`(${r2.chars} chars). It regenerates when the base changes; ghosty agents doctor ${id} tells if the overlay stops applying.`, `(${r2.chars} caracteres). Se regenera cuando cambia la base; ghosty agents doctor ${id} avisa si la capa deja de aplicar.`)}`);
7984
8585
  }
7985
8586
  const body = {};
7986
8587
  if (v.name) body.name = v.name;
@@ -7995,9 +8596,9 @@ ${dim("Dry run: nothing changed.")}`);
7995
8596
  const cur = await api(agentPath(id, "?fields=tools"));
7996
8597
  const known = Object.keys(cur.tools?.toolsets ?? {});
7997
8598
  const bad = items.map((x) => x.replace(/^[+-]/, "")).filter((x) => !known.includes(x));
7998
- if (bad.length) throw usage(`Unknown toolset: ${bad.join(", ")}.`, `Known: ${known.join(", ")}`);
8599
+ if (bad.length) throw usage(t(`Unknown toolset: ${bad.join(", ")}.`, `Toolset desconocido: ${bad.join(", ")}.`), `${t("Known", "Conocidos")}: ${known.join(", ")}`);
7999
8600
  const delta = items.every((x) => /^[+-]/.test(x));
8000
- if (!delta && items.some((x) => /^[+-]/.test(x))) throw usage("Mix of plain and +/- toolsets.", "Either a full list (crm,sales) or changes (+crm,-sales).");
8601
+ if (!delta && items.some((x) => /^[+-]/.test(x))) throw usage(t("Mix of plain and +/- toolsets.", "Mezcla de toolsets simples y con +/-."), t("Either a full list (crm,sales) or changes (+crm,-sales).", "O una lista completa (crm,sales) o cambios (+crm,-sales)."));
8001
8602
  const next = new Set(delta ? known.filter((k) => cur.tools?.toolsets?.[k]?.granted) : []);
8002
8603
  for (const x of items) {
8003
8604
  if (x.startsWith("-")) next.delete(x.slice(1));
@@ -8015,67 +8616,67 @@ ${dim("Dry run: nothing changed.")}`);
8015
8616
  const voice = {};
8016
8617
  if (v.voice) {
8017
8618
  const [engine, ...rest] = v.voice.split(":");
8018
- if (engine !== "elevenlabs" && engine !== "kokoro" || !rest.join(":")) throw usage(`Bad --voice: ${v.voice}.`, "Use elevenlabs:<voiceId> or kokoro:em_santa|em_alex|ef_dora");
8619
+ if (engine !== "elevenlabs" && engine !== "kokoro" || !rest.join(":")) throw usage(t(`Bad --voice: ${v.voice}.`, `--voice inv\xE1lido: ${v.voice}.`), t("Use", "Usa") + " elevenlabs:<voiceId> " + t("or", "o") + " kokoro:em_santa|em_alex|ef_dora");
8019
8620
  voice.engine = engine;
8020
8621
  voice.voiceId = rest.join(":");
8021
8622
  }
8022
8623
  if (v["voice-replies"]) {
8023
- if (v["voice-replies"] !== "auto" && v["voice-replies"] !== "off") throw usage("--voice-replies takes auto or off.");
8624
+ if (v["voice-replies"] !== "auto" && v["voice-replies"] !== "off") throw usage(t("--voice-replies takes auto or off.", "--voice-replies acepta auto u off."));
8024
8625
  voice.replies = v["voice-replies"];
8025
8626
  }
8026
8627
  if (Object.keys(voice).length) body.voice = voice;
8027
8628
  if (v["auto-stage"]) {
8028
- if (v["auto-stage"] !== "on" && v["auto-stage"] !== "off") throw usage("--auto-stage takes on or off.");
8629
+ if (v["auto-stage"] !== "on" && v["auto-stage"] !== "off") throw usage(t("--auto-stage takes on or off.", "--auto-stage acepta on u off."));
8029
8630
  body.autoStage = v["auto-stage"] === "on";
8030
8631
  }
8031
- if (!Object.keys(body).length) throw usage("Nothing to change.", "Use --name, --engine, --model, --prompt, --prompt-file, --env, --channels, --toolsets, --connect, --disconnect, --voice, --voice-replies or --auto-stage.");
8632
+ if (!Object.keys(body).length) throw usage(t("Nothing to change.", "Nada que cambiar."), t("Use", "Usa") + " --name, --engine, --model, --prompt, --prompt-file, --env, --channels, --toolsets, --connect, --disconnect, --voice, --voice-replies " + t("or", "o") + " --auto-stage.");
8032
8633
  const r = await api(agentPath(id), { method: "PATCH", body });
8033
- out(r, () => `${lavender("\u2713")} Updated ${Object.keys(body).join(", ")}.`);
8634
+ out(r, () => `${lavender("\u2713")} ${t("Updated", "Actualizado")}: ${Object.keys(body).join(", ")}.`);
8034
8635
  }
8035
8636
  async function agentsRm(id, v = {}) {
8036
8637
  id = need(id, "agent id", "ghosty agents rm <agent-id>");
8037
8638
  requireYesIfHeadless(v.yes, `ghosty agents rm ${id}`);
8038
8639
  if (!v.yes) {
8039
8640
  const a = await api(agentPath(id, "?fields=name"));
8040
- await confirm(`Delete agent ${a.name ?? id}? Its machine, memory and files go too. This can't be undone.`, { expect: a.name || id, retry: `ghosty agents rm ${id}` });
8641
+ await confirm(t(`Delete agent ${a.name ?? id}? Its machine, memory and files go too. This can't be undone.`, `\xBFBorrar el agente ${a.name ?? id}? Se van tambi\xE9n su m\xE1quina, memoria y archivos. No se puede deshacer.`), { expect: a.name || id, retry: `ghosty agents rm ${id}` });
8041
8642
  }
8042
- out(await api(agentPath(id), { method: "DELETE" }), () => `${lavender("\u2713")} Deleted ${id}.`);
8643
+ out(await api(agentPath(id), { method: "DELETE" }), () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${id}.`);
8043
8644
  }
8044
8645
  async function agentsShare(sub, id, email, v) {
8045
8646
  id = need(id, "agent id", "ghosty agents share <agent-id> <email> [--role admin|editor]");
8046
8647
  if (sub === "shares") {
8047
8648
  const r2 = await api(agentPath(id, "/shares"));
8048
- const rows = r2.shares.map((x) => ({ email: x.email, role: x.role, accepted: x.acceptedAt ? "yes" : "not yet" }));
8049
- return out(r2, () => `${dim(`owner: ${r2.owner ?? "?"}`)}
8050
- ${rows.length ? table(rows, ["email", "role", "accepted"]) : dim("Not shared with anyone.")}`);
8649
+ const rows = r2.shares.map((x) => ({ email: x.email, role: x.role, accepted: x.acceptedAt ? t("yes", "s\xED") : t("not yet", "todav\xEDa no") }));
8650
+ return out(r2, () => `${dim(`${t("owner", "due\xF1o")}: ${r2.owner ?? "?"}`)}
8651
+ ${rows.length ? table(rows, ["email", "role", "accepted"]) : dim(t("Not shared with anyone.", "No est\xE1 compartido con nadie."))}`);
8051
8652
  }
8052
8653
  email = need(email, "email", `ghosty agents ${sub} ${id} ana@empresa.com`);
8053
8654
  if (sub === "unshare") {
8054
8655
  requireYesIfHeadless(v.yes, `ghosty agents unshare ${id} ${email}`);
8055
- if (!v.yes) await confirm(`Remove ${email}'s access? On ACP agents this rotates the connection token (apps reconnect).`, { retry: `ghosty agents unshare ${id} ${email}` });
8656
+ if (!v.yes) await confirm(t(`Remove ${email}'s access? On ACP agents this rotates the connection token (apps reconnect).`, `\xBFQuitarle el acceso a ${email}? En agentes ACP esto rota el token de conexi\xF3n (las apps se reconectan).`), { retry: `ghosty agents unshare ${id} ${email}` });
8056
8657
  const r2 = await api(agentPath(id, "/shares"), { method: "DELETE", body: { email } });
8057
- return out(r2, () => `${lavender("\u2713")} ${email} no longer has access.${r2.tokenRotated ? dim(" Token rotated.") : ""}`);
8658
+ return out(r2, () => `${lavender("\u2713")} ${t(`${email} no longer has access.`, `${email} ya no tiene acceso.`)}${r2.tokenRotated ? dim(t(" Token rotated.", " Token rotado.")) : ""}`);
8058
8659
  }
8059
- if (v.role && v.role !== "admin" && v.role !== "editor") throw usage("--role takes admin or editor.");
8660
+ if (v.role && v.role !== "admin" && v.role !== "editor") throw usage(t("--role takes admin or editor.", "--role acepta admin o editor."));
8060
8661
  const r = await api(agentPath(id, "/shares"), { method: "POST", body: { email, ...v.role ? { role: v.role } : {} } });
8061
- out(r, () => `${lavender("\u2713")} ${r.already ? `Already shared with ${r.email}` : `Shared with ${r.email}; they got an email`} ${dim(`(${r.role})`)}.`);
8662
+ out(r, () => `${lavender("\u2713")} ${r.already ? t(`Already shared with ${r.email}`, `Ya estaba compartido con ${r.email}`) : t(`Shared with ${r.email}; they got an email`, `Compartido con ${r.email}; le lleg\xF3 un correo`)} ${dim(`(${r.role})`)}.`);
8062
8663
  }
8063
8664
  async function agentsRestart(id) {
8064
8665
  id = need(id, "agent id", "ghosty agents restart <agent-id>");
8065
- out(await api(agentPath(id, "/restart"), { method: "POST", body: {} }), () => `${lavender("\u2713")} Restarting \u2014 the next turn uses the new config.`);
8666
+ out(await api(agentPath(id, "/restart"), { method: "POST", body: {} }), () => `${lavender("\u2713")} ${t("Restarting \u2014 the next turn uses the new config.", "Reiniciando: el siguiente turno usa la config nueva.")}`);
8066
8667
  }
8067
8668
  async function tryTurn(id, text, v) {
8068
8669
  id = need(id, "agent id", 'ghosty try <agent-id> "hello"');
8069
8670
  if (v.replay) return tryReplay(id, v);
8070
- if (v.whatsapp && !v.waba) info(dim("Note: --whatsapp is now --waba (the real WhatsApp path)."));
8671
+ if (v.whatsapp && !v.waba) info(dim(t("Note: --whatsapp is now --waba (the real WhatsApp path).", "Nota: --whatsapp ahora es --waba (el camino real de WhatsApp).")));
8071
8672
  if (v.waba || v.whatsapp || v.media?.length || v.history) return tryWaba(id, text, v);
8072
- if (!text && !v.reset) throw usage("Missing message.", 'Example: ghosty try <agent-id> "hello"');
8073
- if (process.stderr.isTTY && text && !globals.json) info(dim("Thinking\u2026 (a test turn can take up to 3 minutes)"));
8673
+ if (!text && !v.reset) throw usage(t("Missing message.", "Falta el mensaje."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "hello"');
8674
+ if (process.stderr.isTTY && text && !globals.json) info(dim(t("Thinking\u2026 (a test turn can take up to 3 minutes)", "Pensando\u2026 (un turno de prueba puede tardar hasta 3 minutos)")));
8074
8675
  const r = await api(agentPath(id, "/try"), {
8075
8676
  body: { text, session: v.session, reset: v.reset }
8076
8677
  });
8077
8678
  out(r, () => (r.text ?? "") + (r.error ? `
8078
- ${dim(`(warning: ${r.error})`)}` : ""));
8679
+ ${dim(`(${t("warning", "aviso")}: ${serverText(r.error)})`)}` : ""));
8079
8680
  }
8080
8681
  async function filesCmd(sub, id, path, v) {
8081
8682
  const ex = "ghosty files ls <agent-id> | get <agent-id> <path> | put <agent-id> <path> --file <local> | rm <agent-id> <path>";
@@ -8095,7 +8696,7 @@ ${table(r.files ?? [], cols)}`);
8095
8696
  const bytes = Buffer.from(await res.arrayBuffer());
8096
8697
  if (v.out) {
8097
8698
  await writeFile3(v.out, bytes);
8098
- out({ path: p, saved: v.out, bytes: bytes.length }, () => `${lavender("\u2713")} Saved ${bytes.length} bytes to ${v.out}`);
8699
+ out({ path: p, saved: v.out, bytes: bytes.length }, () => `${lavender("\u2713")} ${t(`Saved ${bytes.length} bytes to ${v.out}`, `Se guardaron ${bytes.length} bytes en ${v.out}`)}`);
8099
8700
  } else process.stdout.write(bytes);
8100
8701
  return;
8101
8702
  }
@@ -8103,21 +8704,21 @@ ${table(r.files ?? [], cols)}`);
8103
8704
  need(path, "path", ex);
8104
8705
  const data = v.file ? await readFile3(v.file) : await readStdin();
8105
8706
  const r = await api(agentPath(id, `/files/${p}`), { method: "PUT", raw: new Uint8Array(data), headers: { "content-type": "application/octet-stream" } });
8106
- out(r ?? { path: p, bytes: data.length }, () => `${lavender("\u2713")} Wrote ${p} (${data.length} bytes).`);
8707
+ out(r ?? { path: p, bytes: data.length }, () => `${lavender("\u2713")} ${t("Wrote", "Escrito")} ${p} (${data.length} bytes).`);
8107
8708
  return;
8108
8709
  }
8109
8710
  case "rm": {
8110
8711
  need(path, "path", ex);
8111
- await confirm(`Delete ${p} from the agent's machine?`, { yes: v.yes, retry: `ghosty files rm ${id} ${p}` });
8112
- out(await api(agentPath(id, `/files/${p}`), { method: "DELETE" }) ?? { deleted: p }, () => `${lavender("\u2713")} Deleted ${p}`);
8712
+ await confirm(t(`Delete ${p} from the agent's machine?`, `\xBFBorrar ${p} de la m\xE1quina del agente?`), { yes: v.yes, retry: `ghosty files rm ${id} ${p}` });
8713
+ out(await api(agentPath(id, `/files/${p}`), { method: "DELETE" }) ?? { deleted: p }, () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${p}`);
8113
8714
  return;
8114
8715
  }
8115
8716
  default:
8116
- throw usage(`Unknown files subcommand: ${sub ?? "(none)"}.`, ex);
8717
+ throw usage(t(`Unknown files subcommand: ${sub ?? "(none)"}.`, `Subcomando de files desconocido: ${sub ?? "(ninguno)"}.`), ex);
8117
8718
  }
8118
8719
  }
8119
8720
  async function readStdin() {
8120
- if (process.stdin.isTTY) throw usage("Nothing to upload.", "Use --file <local> or pipe the content through stdin.");
8721
+ if (process.stdin.isTTY) throw usage(t("Nothing to upload.", "Nada que subir."), t("Use --file <local> or pipe the content through stdin.", "Usa --file <local> o pasa el contenido por stdin."));
8121
8722
  const parts = [];
8122
8723
  for await (const c of process.stdin) parts.push(c);
8123
8724
  return Buffer.concat(parts);
@@ -8144,7 +8745,7 @@ async function readSkillDir(dir) {
8144
8745
  if (rel === "SKILL.md") markdown = await readFile3(full, "utf8");
8145
8746
  else if (!rel.split("/").some((part) => part.startsWith("."))) assets.push({ name: rel, contentBase64: (await readFile3(full)).toString("base64") });
8146
8747
  }
8147
- if (!markdown) throw usage(`No SKILL.md in ${dir}.`, "A skill folder has SKILL.md at its root (agentskills.io).");
8748
+ if (!markdown) throw usage(t(`No SKILL.md in ${dir}.`, `No hay SKILL.md en ${dir}.`), t("A skill folder has SKILL.md at its root (agentskills.io).", "Una carpeta de skill lleva SKILL.md en su ra\xEDz (agentskills.io)."));
8148
8749
  return { markdown, assets };
8149
8750
  }
8150
8751
  var trashDir = (id, slug) => join3(process.env.XDG_CONFIG_HOME || join3(homedir2(), ".config"), "ghosty", "trash", id, `${slug}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`);
@@ -8162,7 +8763,7 @@ async function skillsCmd(sub, id, slug, v) {
8162
8763
  ];
8163
8764
  const more = r.community.filter((c) => !c.installed).length;
8164
8765
  out(r, () => `${table(rows, ["slug", "source", "on", "description"])}
8165
- ${dim(`${more} more in the community catalog: ghosty skills add <agent-id> <slug> (--json lists them) \xB7 overlaps: ghosty agents doctor ${id}`)}`);
8766
+ ${dim(t(`${more} more in the community catalog: ghosty skills add <agent-id> <slug> (--json lists them) \xB7 overlaps: ghosty agents doctor ${id}`, `${more} m\xE1s en el cat\xE1logo de la comunidad: ghosty skills add <agent-id> <slug> (--json las lista) \xB7 traslapes: ghosty agents doctor ${id}`))}`);
8166
8767
  return;
8167
8768
  }
8168
8769
  case "house": {
@@ -8171,20 +8772,20 @@ ${dim(`${more} more in the community catalog: ghosty skills add <agent-id> <slug
8171
8772
  if (!only && !on && !off) {
8172
8773
  const r2 = await api(agentPath(id, "/skills/house"));
8173
8774
  out(r2, () => `${table(r2.house.map((h) => ({ slug: h.slug, on: h.enabled ? "on" : "off" })), ["slug", "on"])}
8174
- ${dim(`Change: ghosty skills house ${id} --only a,b | --enable a | --disable b [--dry-run]`)}`);
8775
+ ${dim(`${t("Change", "Cambiar")}: ghosty skills house ${id} --only a,b | --enable a | --disable b [--dry-run]`)}`);
8175
8776
  return;
8176
8777
  }
8177
8778
  const body = { ...only ? { only } : {}, ...on ? { on } : {}, ...off ? { off } : {}, ...v["dry-run"] ? { dryRun: true } : {} };
8178
8779
  const r = await api(agentPath(id, "/skills/house"), { method: "PUT", body });
8179
- out(r, () => `${r.changes.map((c) => `${c.enabled ? "+" : "-"} ${c.slug}`).join("\n") || dim("Nothing to change.")}
8180
- ${dim(`On now: ${r.on.join(", ") || "none"}${r.dryRun ? " \xB7 dry run: nothing changed" : ""}`)}`);
8780
+ out(r, () => `${r.changes.map((c) => `${c.enabled ? "+" : "-"} ${c.slug}`).join("\n") || dim(t("Nothing to change.", "Nada que cambiar."))}
8781
+ ${dim(`${t("On now", "Encendidas ahora")}: ${r.on.join(", ") || t("none", "ninguna")}${r.dryRun ? t(" \xB7 dry run: nothing changed", " \xB7 simulacro: no se cambi\xF3 nada") : ""}`)}`);
8181
8782
  return;
8182
8783
  }
8183
8784
  case "on":
8184
8785
  case "off": {
8185
8786
  slug = need(slug, "skill slug", `ghosty skills ${sub} <agent-id> <slug>`);
8186
8787
  const r = await api(agentPath(id, `/skills/${enc2(slug)}`), { method: "PATCH", body: { enabled: sub === "on" } });
8187
- out(r, () => `${lavender("\u2713")} ${slug} ${sub === "on" ? "on" : "off"} ${dim(`(${r.source === "house" ? "Ghosty's" : "own"}${r.nota ? `; ${r.nota}` : ""})`)}`);
8788
+ out(r, () => `${lavender("\u2713")} ${slug} ${sub === "on" ? "on" : "off"} ${dim(`(${r.source === "house" ? t("Ghosty's", "de Ghosty") : t("own", "propia")}${r.nota ? `; ${serverText(r.nota)}` : ""})`)}`);
8188
8789
  return;
8189
8790
  }
8190
8791
  case "get": {
@@ -8193,27 +8794,27 @@ ${dim(`On now: ${r.on.join(", ") || "none"}${r.dryRun ? " \xB7 dry run: nothing
8193
8794
  if (!v.out) {
8194
8795
  const files = sk.files.map((f) => ({ path: f.path, bytes: Buffer.from(f.contentBase64, "base64").length }));
8195
8796
  out({ slug, files, saved: null }, () => `${table(files, ["path", "bytes"])}
8196
- ${dim(`Download: ghosty skills get ${id} ${slug} --out DIR`)}`);
8797
+ ${dim(`${t("Download", "Descargar")}: ghosty skills get ${id} ${slug} --out DIR`)}`);
8197
8798
  return;
8198
8799
  }
8199
8800
  const dir = resolve(v.out);
8200
8801
  const bytes = await writeSkill(dir, sk);
8201
- out({ slug, saved: dir, files: sk.files.map((f) => f.path), bytes }, () => `${lavender("\u2713")} ${slug} \u2192 ${dir}/ ${dim(`(${sk.files.length} file(s), ${bytes} bytes)`)}`);
8802
+ out({ slug, saved: dir, files: sk.files.map((f) => f.path), bytes }, () => `${lavender("\u2713")} ${slug} \u2192 ${dir}/ ${dim(t(`(${sk.files.length} file(s), ${bytes} bytes)`, `(${sk.files.length} archivo(s), ${bytes} bytes)`))}`);
8202
8803
  return;
8203
8804
  }
8204
8805
  case "add": {
8205
8806
  slug = need(slug, "skill slug", ex);
8206
- if (v.file && v.dir) throw usage("Use --file or --dir, not both.");
8807
+ if (v.file && v.dir) throw usage(t("Use --file or --dir, not both.", "Usa --file o --dir, no los dos."));
8207
8808
  const body = v.dir ? await readSkillDir(v.dir) : v.file ? { markdown: await readFile3(v.file, "utf8") } : { fromCatalog: true };
8208
8809
  const r = await api(agentPath(id, `/skills/${enc2(slug)}`), { method: "PUT", body });
8209
- out(r, () => `${lavender("\u2713")} Skill ${slug} installed.${r?.nota ? dim(` (${r.nota})`) : ""}`);
8810
+ out(r, () => `${lavender("\u2713")} ${t(`Skill ${slug} installed.`, `Skill ${slug} instalada.`)}${r?.nota ? dim(` (${serverText(r.nota)})`) : ""}`);
8210
8811
  return;
8211
8812
  }
8212
8813
  case "templates": {
8213
8814
  const r = await api(agentPath(id, "/templates"));
8214
- const rows = r.templates.map((t) => ({ name: t.name, source: t.source, description: (t.description ?? "").replace(/\s+/g, " ").slice(0, 60) }));
8815
+ const rows = r.templates.map((tp) => ({ name: tp.name, source: tp.source, description: (tp.description ?? "").replace(/\s+/g, " ").slice(0, 60) }));
8215
8816
  out(r, () => `${table(rows, ["name", "source", "description"])}
8216
- ${dim("Its own: put <name>.html in <skill>/pdf-templates/ of any of its skills (it wins over a house one with the same name).")}`);
8817
+ ${dim(t("Its own: put <name>.html in <skill>/pdf-templates/ of any of its skills (it wins over a house one with the same name).", "Las suyas: pon <name>.html en <skill>/pdf-templates/ de cualquiera de sus skills (le gana a la de casa con el mismo nombre)."))}`);
8217
8818
  return;
8218
8819
  }
8219
8820
  case "rm":
@@ -8221,18 +8822,19 @@ ${dim("Its own: put <name>.html in <skill>/pdf-templates/ of any of its skills (
8221
8822
  slug = need(slug, "skill slug", ex);
8222
8823
  requireYesIfHeadless(v.yes, `ghosty skills rm ${id} ${slug}`);
8223
8824
  const sk = await api(agentPath(id, `/skills/${enc2(slug)}`)).catch(() => null);
8224
- await confirm(`Remove skill ${slug} from agent ${id}?${sk ? " A local copy is kept." : ""}`, { yes: v.yes, retry: `ghosty skills rm ${id} ${slug}` });
8825
+ await confirm(t(`Remove skill ${slug} from agent ${id}?${sk ? " A local copy is kept." : ""}`, `\xBFQuitarle la skill ${slug} al agente ${id}?${sk ? " Se guarda una copia local." : ""}`), { yes: v.yes, retry: `ghosty skills rm ${id} ${slug}` });
8225
8826
  const backup = sk ? trashDir(id, slug) : null;
8226
8827
  if (sk && backup) await writeSkill(backup, sk);
8227
8828
  const r = await api(agentPath(id, `/skills/${enc2(slug)}`), { method: "DELETE" }) ?? { deleted: slug };
8228
8829
  const restore = backup ? `ghosty skills add ${id} ${slug} --dir "${backup}"` : null;
8229
- out({ ...r, backup, restore }, () => `${lavender("\u2713")} Skill ${slug} removed.${restore ? `
8230
- ${dim(`Backup: ${backup}
8231
- Undo: ${restore}`)}` : ""}`);
8830
+ out({ ...r, backup, restore }, () => `${lavender("\u2713")} ${t(`Skill ${slug} removed.`, `Skill ${slug} quitada.`)}${restore ? `
8831
+ ${dim(t(`Backup: ${backup}
8832
+ Undo: ${restore}`, `Respaldo: ${backup}
8833
+ Deshacer: ${restore}`))}` : ""}`);
8232
8834
  return;
8233
8835
  }
8234
8836
  default:
8235
- throw usage(`Unknown skills subcommand: ${sub}.`, ex);
8837
+ throw usage(t(`Unknown skills subcommand: ${sub}.`, `Subcomando de skills desconocido: ${sub}.`), ex);
8236
8838
  }
8237
8839
  }
8238
8840
  async function mcpCmd(sub, id, v) {
@@ -8244,13 +8846,13 @@ async function mcpCmd(sub, id, v) {
8244
8846
  const parsed = JSON.parse(await readFile3(file, "utf8"));
8245
8847
  const body = { servers: Array.isArray(parsed) ? parsed : parsed.servers ?? [] };
8246
8848
  const r = await api(agentPath(id, "/mcp"), { method: "PUT", body });
8247
- return out(r, () => `${lavender("\u2713")} MCP servers replaced \u2014 ${r?.reiniciado ? "the agent restarted with them." : "they load on the next turn."}`);
8849
+ return out(r, () => `${lavender("\u2713")} ${t("MCP servers replaced \u2014", "Servidores MCP reemplazados:")} ${r?.reiniciado ? t("the agent restarted with them.", "el agente se reinici\xF3 con ellos.") : t("they load on the next turn.", "entran en el siguiente turno.")}`);
8248
8850
  }
8249
- throw usage(`Unknown mcp subcommand: ${sub ?? "(none)"}.`, ex);
8851
+ throw usage(t(`Unknown mcp subcommand: ${sub ?? "(none)"}.`, `Subcomando de mcp desconocido: ${sub ?? "(ninguno)"}.`), ex);
8250
8852
  }
8251
8853
  async function agentsDemo(id, v) {
8252
8854
  id = need(id, "agent id", "ghosty agents demo <agent-id> --slug guardias --on");
8253
- if (v.on && v.off) throw usage("Use --on or --off, not both.");
8855
+ if (v.on && v.off) throw usage(t("Use --on or --off, not both.", "Usa --on o --off, no los dos."));
8254
8856
  const body = {};
8255
8857
  if (v.slug) body.slug = v.slug;
8256
8858
  if (v.on) body.enabled = true;
@@ -8263,20 +8865,28 @@ async function agentsDemo(id, v) {
8263
8865
  if (v.starter !== void 0) body.starter = v.starter || null;
8264
8866
  if (v.rotate) body.rotate = true;
8265
8867
  if (v.sponsor !== void 0) {
8266
- if (v.sponsor !== "on" && v.sponsor !== "off") throw usage("--sponsor takes on or off.");
8868
+ if (v.sponsor !== "on" && v.sponsor !== "off") throw usage(t("--sponsor takes on or off.", "--sponsor acepta on u off."));
8267
8869
  body.sponsor = v.sponsor === "on";
8268
8870
  }
8269
8871
  const d = Object.keys(body).length ? await api(agentPath(id, "/demo"), { method: "PUT", body }) : await api(agentPath(id, "/demo"));
8270
8872
  out(
8271
8873
  d,
8272
8874
  () => [
8273
- `${d.enabled ? lavender("\u25CF on") : dim("\u25CB off")} ${d.url ?? dim("(no link yet: --slug NAME)")}`,
8274
- dim(`expires: ${d.venceEl ? new Date(d.venceEl).toLocaleString("es-MX") : "never"} \xB7 cap ${d.maxTurns} messages per visitor \xB7 ${d.perIpDay} per IP per day`),
8275
- d.welcome ? `welcome: ${d.welcome}` : dim("welcome: (default greeting)"),
8276
- d.starter ? `starter: \u2728 ${d.starter}` : dim("starter: (none)"),
8277
- d.chips.length ? `chips: ${d.chips.join(" | ")}` : dim("chips: (default)"),
8278
- `tried ${d.stats.pruebas} \xB7 messages ${d.stats.mensajes} \xB7 kept ${d.stats.clones} (${d.stats.patrocinados} on your key)`,
8279
- ...d.lastError ? [red(`last failure ${d.lastError.at}: ${d.lastError.message ?? d.lastError.error}`)] : []
8875
+ `${d.enabled ? lavender(t("\u25CF on", "\u25CF encendido")) : dim(t("\u25CB off", "\u25CB apagado"))} ${d.url ?? dim(t("(no link yet: --slug NAME)", "(todav\xEDa sin liga: --slug NAME)"))}`,
8876
+ dim(
8877
+ t(
8878
+ `expires: ${d.venceEl ? new Date(d.venceEl).toLocaleString("es-MX") : "never"} \xB7 cap ${d.maxTurns} messages per visitor \xB7 ${d.perIpDay} per IP per day`,
8879
+ `vence: ${d.venceEl ? new Date(d.venceEl).toLocaleString("es-MX") : "nunca"} \xB7 tope ${d.maxTurns} mensajes por visitante \xB7 ${d.perIpDay} por IP al d\xEDa`
8880
+ )
8881
+ ),
8882
+ d.welcome ? `${t("welcome", "bienvenida")}: ${d.welcome}` : dim(t("welcome: (default greeting)", "bienvenida: (saludo por defecto)")),
8883
+ d.starter ? `${t("starter", "inicial")}: \u2728 ${d.starter}` : dim(t("starter: (none)", "inicial: (ninguna)")),
8884
+ d.chips.length ? `chips: ${d.chips.join(" | ")}` : dim(t("chips: (default)", "chips: (por defecto)")),
8885
+ t(
8886
+ `tried ${d.stats.pruebas} \xB7 messages ${d.stats.mensajes} \xB7 kept ${d.stats.clones} (${d.stats.patrocinados} on your key)`,
8887
+ `pruebas ${d.stats.pruebas} \xB7 mensajes ${d.stats.mensajes} \xB7 se lo quedaron ${d.stats.clones} (${d.stats.patrocinados} con tu llave)`
8888
+ ),
8889
+ ...d.lastError ? [red(`${t("last failure", "\xFAltima falla")} ${d.lastError.at}: ${serverText(d.lastError.message ?? d.lastError.error ?? "")}`)] : []
8280
8890
  ].join("\n")
8281
8891
  );
8282
8892
  }
@@ -8284,8 +8894,8 @@ async function agentsBox(id, v) {
8284
8894
  id = need(id, "agent id", "ghosty agents box <agent-id> [--check PATH] [--contains PATH=TEXT] [--recycle]");
8285
8895
  if (v.recycle) {
8286
8896
  const r2 = await api(agentPath(id, "/box"), { method: "POST", body: { recycle: true } });
8287
- out(r2, () => `${lavender("\u2713")} Recycled. ${dim(r2?.note ?? "")}
8288
- ${dim(`Verify after one turn: ghosty agents box ${id} --check <path>`)}`);
8897
+ out(r2, () => `${lavender("\u2713")} ${t("Recycled.", "Reciclada.")} ${dim(serverText(r2?.note ?? ""))}
8898
+ ${dim(`${t("Verify after one turn", "Verifica despu\xE9s de un turno")}: ghosty agents box ${id} --check <path>`)}`);
8289
8899
  return;
8290
8900
  }
8291
8901
  const q = new URLSearchParams();
@@ -8293,12 +8903,12 @@ ${dim(`Verify after one turn: ghosty agents box ${id} --check <path>`)}`);
8293
8903
  for (const c of v.contains ?? []) q.append("contains", c);
8294
8904
  const r = await api(agentPath(id, `/box?${q}`));
8295
8905
  out(r, () => {
8296
- if (!r.boxes.length) return dim(r.note ?? "No live box.");
8906
+ if (!r.boxes.length) return dim(r.note ? serverText(r.note) : t("No live box.", "No hay caja viva."));
8297
8907
  const lines = r.boxes.flatMap((b) => [
8298
- `${bold(b.sandboxId)} ${dim(b.lastMessageAt ? `last message ${b.lastMessageAt}` : r.kind)}${b.error ? ` ${red(b.error)}` : ""}`,
8908
+ `${bold(b.sandboxId)} ${dim(b.lastMessageAt ? `${t("last message", "\xFAltimo mensaje")} ${b.lastMessageAt}` : r.kind)}${b.error ? ` ${red(serverText(b.error))}` : ""}`,
8299
8909
  ...b.checks.map((c) => ` ${c.ok ? lavender("\u2713") : red("\u2717")} ${c.path}${c.contains !== void 0 ? ` \u220B ${c.contains}` : ""}`)
8300
8910
  ]);
8301
- const verdict = !(v.check?.length || v.contains?.length) ? dim("Alive. Add --check PATH to confirm the new image.") : r.stale ? red(`${r.stale} box(es) on an OLD image: ghosty agents box ${id} --recycle`) : `${lavender("\u2713")} Serving with the expected image.`;
8911
+ const verdict = !(v.check?.length || v.contains?.length) ? dim(t("Alive. Add --check PATH to confirm the new image.", "Viva. Agrega --check PATH para confirmar la imagen nueva.")) : r.stale ? red(t(`${r.stale} box(es) on an OLD image: ghosty agents box ${id} --recycle`, `${r.stale} caja(s) con imagen VIEJA: ghosty agents box ${id} --recycle`)) : `${lavender("\u2713")} ${t("Serving with the expected image.", "Atiende con la imagen esperada.")}`;
8302
8912
  return [...lines, verdict].join("\n");
8303
8913
  });
8304
8914
  }
@@ -8345,20 +8955,20 @@ function parseData(e) {
8345
8955
  var enc3 = encodeURIComponent;
8346
8956
  var convPath = (agent, rest = "") => `/api/v2/me/agents/${enc3(agent)}/conversations${rest}`;
8347
8957
  async function conversationsLs(agent) {
8348
- if (!agent) throw usage("Missing agent id.", "Example: ghosty conversations ls <agent-id>");
8958
+ if (!agent) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example: ghosty conversations ls <agent-id>", "Ejemplo: ghosty conversations ls <agent-id>"));
8349
8959
  const r = await api(convPath(agent));
8350
8960
  const rows = r.conversaciones.map((c) => ({ id: c.id, title: c.titulo, updated: c.actualizada }));
8351
8961
  out(rows, () => table(rows, ["id", "title", "updated"]));
8352
8962
  }
8353
8963
  async function conversationsShow(agent, sid, v) {
8354
- if (!agent || !sid) throw usage("Missing agent id or conversation id.", "Example: ghosty conversations show <agent-id> <conversation-id> [--limit 20]");
8964
+ if (!agent || !sid) throw usage(t("Missing agent id or conversation id.", "Falta el id del agente o de la conversaci\xF3n."), t("Example: ", "Ejemplo: ") + "ghosty conversations show <agent-id> <conversation-id> [--limit 20]");
8355
8965
  const tail = v.limit ?? "20";
8356
8966
  const r = await api(convPath(agent, `/${enc3(sid)}?tail=${enc3(tail)}`));
8357
8967
  out(r, () => {
8358
- const lines = r.messages.map((m) => `${bold(m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role)} ${m.text.trim()}`);
8359
- const head = r.saltados ? [dim(`(${r.saltados} earlier messages not shown \xB7 --limit 0 for all)`)] : [];
8360
- const tailNote = r.enCurso ? [dim("(a turn is running: its answer is not here yet)")] : [];
8361
- return [...head, ...lines, ...tailNote].join("\n\n") || dim("(empty)");
8968
+ const lines = r.messages.map((m) => `${bold(m.role === "user" ? t("User", "Usuario") : m.role === "assistant" ? t("Agent", "Agente") : m.role)} ${m.text.trim()}`);
8969
+ const head = r.saltados ? [dim(t(`(${r.saltados} earlier messages not shown \xB7 --limit 0 for all)`, `(${r.saltados} mensajes anteriores sin mostrar \xB7 --limit 0 para verlos todos)`))] : [];
8970
+ const tailNote = r.enCurso ? [dim(t("(a turn is running: its answer is not here yet)", "(hay un turno en curso: su respuesta todav\xEDa no est\xE1 aqu\xED)"))] : [];
8971
+ return [...head, ...lines, ...tailNote].join("\n\n") || dim(t("(empty)", "(vac\xEDa)"));
8362
8972
  });
8363
8973
  }
8364
8974
  async function turn(agent, sid, text, first) {
@@ -8369,7 +8979,7 @@ async function turn(agent, sid, text, first) {
8369
8979
  const onSigint = () => {
8370
8980
  if (cancelled) process.exit(130);
8371
8981
  cancelled = true;
8372
- info(dim("\n(cancelling\u2026 press Ctrl-C again to quit)"));
8982
+ info(dim(t("\n(cancelling\u2026 press Ctrl-C again to quit)", "\n(cancelando\u2026 presiona Ctrl-C otra vez para salir)")));
8373
8983
  void api(convPath(agent, `/${enc3(sid)}/cancel`), { method: "POST", body: {} }).catch(() => {
8374
8984
  });
8375
8985
  };
@@ -8390,11 +9000,11 @@ async function turn(agent, sid, text, first) {
8390
9000
  else process.stdout.write(d.text);
8391
9001
  } else if (ev.event === "tool" && d.title) {
8392
9002
  if (globals.json) process.stdout.write(JSON.stringify({ event: "tool", ...d }) + "\n");
8393
- else if (!d.status || d.status === "pending" || d.status === "in_progress") info(dim(` \u2699 ${d.title}`));
9003
+ else if (!d.status || d.status === "pending" || d.status === "in_progress") info(dim(` \u2699 ${serverText(d.title)}`));
8394
9004
  } else if (ev.event === "usage") {
8395
9005
  usageData = d;
8396
9006
  } else if (ev.event === "error") {
8397
- throw new CliError(d.message ?? "The turn failed.", EXIT.API);
9007
+ throw new CliError(d.message ? serverText(d.message) : t("The turn failed.", "El turno fall\xF3."), EXIT.API);
8398
9008
  } else if (ev.event === "done") {
8399
9009
  break;
8400
9010
  }
@@ -8408,13 +9018,13 @@ async function turn(agent, sid, text, first) {
8408
9018
  return { text: answer, usage: usageData };
8409
9019
  }
8410
9020
  async function chat(agent, message, v) {
8411
- if (!agent) throw usage("Missing agent id.", 'Example: ghosty chat <agent-id> "hello" (ghosty agents ls to find it)');
9021
+ if (!agent) throw usage(t("Missing agent id.", "Falta el id del agente."), t('Example: ghosty chat <agent-id> "hello" (ghosty agents ls to find it)', 'Ejemplo: ghosty chat <agent-id> "hola" (ghosty agents ls para encontrarlo)'));
8412
9022
  let sid = v.conversation;
8413
9023
  let first = false;
8414
9024
  if (!sid) {
8415
9025
  sid = (await api(convPath(agent), { method: "POST", body: {} })).id;
8416
9026
  first = true;
8417
- if (!globals.json) info(dim(`conversation ${sid} (continue it with --conversation ${sid})`));
9027
+ if (!globals.json) info(dim(t(`conversation ${sid} (continue it with --conversation ${sid})`, `conversaci\xF3n ${sid} (s\xEDguela con --conversation ${sid})`)));
8418
9028
  }
8419
9029
  if (message) {
8420
9030
  await turn(agent, sid, message, first);
@@ -8422,12 +9032,12 @@ async function chat(agent, message, v) {
8422
9032
  }
8423
9033
  if (!process.stdin.isTTY) {
8424
9034
  const piped = (await readAll()).trim();
8425
- if (!piped) throw usage("Nothing to send.", 'Example: ghosty chat <agent-id> "hello"');
9035
+ if (!piped) throw usage(t("Nothing to send.", "No hay nada que mandar."), t('Example: ghosty chat <agent-id> "hello"', 'Ejemplo: ghosty chat <agent-id> "hola"'));
8426
9036
  await turn(agent, sid, piped, first);
8427
9037
  return;
8428
9038
  }
8429
9039
  const rl = createInterface2({ input: process.stdin, output: process.stderr });
8430
- info(dim("Type your message. Ctrl-C cancels a turn, Ctrl-D exits."));
9040
+ info(dim(t("Type your message. Ctrl-C cancels a turn, Ctrl-D exits.", "Escribe tu mensaje. Ctrl-C cancela un turno, Ctrl-D sale.")));
8431
9041
  for (; ; ) {
8432
9042
  let line2;
8433
9043
  try {
@@ -8458,28 +9068,28 @@ async function meFiles(sub, arg, v) {
8458
9068
  out(r, () => {
8459
9069
  const rows = r.files.map((f) => ({ id: f.id, name: f.name, kind: f.kind, size: f.size, created: f.createdAt.slice(0, 10) }));
8460
9070
  return `${table(rows, ["id", "name", "kind", "size", "created"])}
8461
- ${dim(`${(r.storage.usedBytes / 1e9).toFixed(2)} GB of ${r.storage.limitGb} GB used`)}`;
9071
+ ${dim(t(`${(r.storage.usedBytes / 1e9).toFixed(2)} GB of ${r.storage.limitGb} GB used`, `${(r.storage.usedBytes / 1e9).toFixed(2)} GB de ${r.storage.limitGb} GB usados`))}`;
8462
9072
  });
8463
9073
  return;
8464
9074
  }
8465
9075
  case "upload": {
8466
- if (!arg) throw usage("Missing file.", ex);
9076
+ if (!arg) throw usage(t("Missing file.", "Falta el archivo."), ex);
8467
9077
  const bytes = await readFile4(arg);
8468
9078
  const r = await api(`/api/v2/me/files?nombre=${enc3(basename2(arg))}`, {
8469
9079
  method: "POST",
8470
9080
  raw: new Uint8Array(bytes),
8471
9081
  headers: { "content-type": "application/octet-stream" }
8472
9082
  });
8473
- out(r, () => `${lavender("\u2713")} Uploaded ${r.name} (${r.id})`);
9083
+ out(r, () => `${lavender("\u2713")} ${t("Uploaded", "Subido")} ${r.name} (${r.id})`);
8474
9084
  return;
8475
9085
  }
8476
9086
  case "rm": {
8477
- if (!arg) throw usage("Missing file id.", ex);
8478
- out(await api(`/api/v2/me/files/${enc3(arg)}`, { method: "DELETE" }) ?? { deleted: arg }, () => `${lavender("\u2713")} Deleted ${arg}`);
9087
+ if (!arg) throw usage(t("Missing file id.", "Falta el id del archivo."), ex);
9088
+ out(await api(`/api/v2/me/files/${enc3(arg)}`, { method: "DELETE" }) ?? { deleted: arg }, () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${arg}`);
8479
9089
  return;
8480
9090
  }
8481
9091
  default:
8482
- throw usage(`Unknown files subcommand: ${sub ?? "(none)"}.`, ex);
9092
+ throw usage(t(`Unknown files subcommand: ${sub ?? "(none)"}.`, `Subcomando de files desconocido: ${sub ?? "(ninguno)"}.`), ex);
8483
9093
  }
8484
9094
  }
8485
9095
  async function meUsage() {
@@ -8488,11 +9098,11 @@ async function meUsage() {
8488
9098
  out(
8489
9099
  r,
8490
9100
  () => [
8491
- `Plan ${r.plan.name}${r.exhausted ? " \u2014 out of usage for now" : ""}`,
8492
- ` This week ${pct(r.week.pct).padStart(4)} used ${dim(`resets ${r.week.resetsAt.slice(0, 10)}`)}`,
8493
- ...r.month.pct !== null ? [` This month ${pct(r.month.pct).padStart(4)} used ${dim(`resets ${r.month.resetsAt.slice(0, 10)}`)}`] : [],
8494
- ` Top-ups ${r.creditMxn > 0 ? "available" : "none"}`,
8495
- dim("Change plan or top up: https://www.ghosty.studio/c/uso")
9101
+ `Plan ${r.plan.name}${r.exhausted ? t(" \u2014 out of usage for now", " \u2014 sin uso disponible por ahora") : ""}`,
9102
+ ` ${t("This week ", "Esta semana")} ${pct(r.week.pct).padStart(4)} ${t("used", "usado")} ${dim(`${t("resets", "se reinicia")} ${r.week.resetsAt.slice(0, 10)}`)}`,
9103
+ ...r.month.pct !== null ? [` ${t("This month ", "Este mes ")} ${pct(r.month.pct).padStart(4)} ${t("used", "usado")} ${dim(`${t("resets", "se reinicia")} ${r.month.resetsAt.slice(0, 10)}`)}`] : [],
9104
+ ` ${t("Top-ups ", "Recargas ")} ${r.creditMxn > 0 ? t("available", "disponibles") : t("none", "ninguna")}`,
9105
+ dim(t("Change plan or top up: https://www.ghosty.studio/c/uso", "Cambia de plan o recarga: https://www.ghosty.studio/c/uso"))
8496
9106
  ].join("\n")
8497
9107
  );
8498
9108
  }
@@ -8501,14 +9111,14 @@ async function meUsage() {
8501
9111
  var enc4 = encodeURIComponent;
8502
9112
  function minutesOf(s, flag) {
8503
9113
  const m = /^(\d+(?:\.\d+)?)\s*(m|min|h|d)?$/i.exec(s.trim());
8504
- if (!m) throw usage(`Invalid ${flag}: ${s}.`, "Use minutes, hours or days: 30m, 12h, 1d");
9114
+ if (!m) throw usage(t(`Invalid ${flag}: ${s}.`, `${flag} inv\xE1lido: ${s}.`), t("Use minutes, hours or days: 30m, 12h, 1d", "Usa minutos, horas o d\xEDas: 30m, 12h, 1d"));
8505
9115
  const n = Number(m[1]);
8506
9116
  const unit = (m[2] ?? "m").toLowerCase();
8507
9117
  return Math.round(unit === "d" ? n * 1440 : unit === "h" ? n * 60 : n);
8508
9118
  }
8509
9119
  function isoOf(s, flag) {
8510
9120
  const d = new Date(s);
8511
- if (Number.isNaN(d.getTime())) throw usage(`Invalid ${flag}: ${s}.`, "Use an ISO date, e.g. 2026-10-01T09:00-06:00");
9121
+ if (Number.isNaN(d.getTime())) throw usage(t(`Invalid ${flag}: ${s}.`, `${flag} inv\xE1lido: ${s}.`), t("Use an ISO date, e.g. 2026-10-01T09:00-06:00", "Usa una fecha ISO, p. ej. 2026-10-01T09:00-06:00"));
8512
9122
  return d.toISOString();
8513
9123
  }
8514
9124
  var every = (min) => min == null ? "once" : min % 1440 === 0 ? `${min / 1440}d` : min % 60 === 0 ? `${min / 60}h` : `${min}m`;
@@ -8518,7 +9128,7 @@ async function agentSchedule(agent) {
8518
9128
  }
8519
9129
  async function scheduleCmd(sub, agent, arg, v) {
8520
9130
  if (sub === "ls" || sub === "list") {
8521
- if (!agent) throw usage("Missing agent id.", "Example: ghosty schedule ls <agent-id>");
9131
+ if (!agent) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty schedule ls <agent-id>");
8522
9132
  const r = await agentSchedule(agent);
8523
9133
  if (globals.json) return out({ pending: r.pendientes, runs: r.corridas });
8524
9134
  const pending = r.pendientes.map((p) => ({
@@ -8533,27 +9143,27 @@ async function scheduleCmd(sub, agent, arg, v) {
8533
9143
  return out(
8534
9144
  null,
8535
9145
  () => [
8536
- pending.length ? table(pending, ["id", "due", "every", "until", "conversation", "prompt"]) : dim("No scheduled turns."),
8537
- ...runs.length ? ["", lavender("Recent runs"), table(runs, ["fired", "conversation", "answer"])] : []
9146
+ pending.length ? table(pending, ["id", "due", "every", "until", "conversation", "prompt"]) : dim(t("No scheduled turns.", "No hay turnos programados.")),
9147
+ ...runs.length ? ["", lavender(t("Recent runs", "Corridas recientes")), table(runs, ["fired", "conversation", "answer"])] : []
8538
9148
  ].join("\n")
8539
9149
  );
8540
9150
  }
8541
9151
  if (sub === "add") {
8542
- if (!agent || !arg) throw usage("Missing agent id or prompt.", 'Example: ghosty schedule add <agent-id> "Check the campaign" --in 30m');
8543
- if (!v.at && !v.in) throw usage("Say when.", "Use --in 30m or --at 2026-10-01T09:00-06:00");
8544
- if (v.at && v.in) throw usage("Use --at or --in, not both.");
9152
+ if (!agent || !arg) throw usage(t("Missing agent id or prompt.", "Falta el id del agente o el prompt."), t('Example: ghosty schedule add <agent-id> "Check the campaign" --in 30m', 'Ejemplo: ghosty schedule add <agent-id> "Revisa la campa\xF1a" --in 30m'));
9153
+ if (!v.at && !v.in) throw usage(t("Say when.", "Di cu\xE1ndo."), t("Use --in 30m or --at 2026-10-01T09:00-06:00", "Usa --in 30m o --at 2026-10-01T09:00-06:00"));
9154
+ if (v.at && v.in) throw usage(t("Use --at or --in, not both.", "Usa --at o --in, no ambos."));
8545
9155
  const body = {
8546
9156
  prompt: arg,
8547
9157
  ...v.in ? { inMinutes: minutesOf(v.in, "--in") } : { dueAt: isoOf(v.at, "--at") },
8548
9158
  repeatMin: v.every ? minutesOf(v.every, "--every") : null,
8549
9159
  until: v.until ? isoOf(v.until, "--until") : null
8550
9160
  };
8551
- if (body.repeatMin && !body.until) throw usage("A repeating turn needs --until.", "Example: --every 12h --until 2026-10-25");
9161
+ if (body.repeatMin && !body.until) throw usage(t("A repeating turn needs --until.", "Un turno que se repite necesita --until."), t("Example", "Ejemplo") + ": --every 12h --until 2026-10-25");
8552
9162
  let sid = v.conversation;
8553
9163
  const created = !sid;
8554
9164
  if (!sid) {
8555
9165
  sid = (await api(convPath(agent), { method: "POST", body: {} })).id;
8556
- if (!globals.json) info(dim(`new conversation ${sid}`));
9166
+ if (!globals.json) info(dim(t(`new conversation ${sid}`, `conversaci\xF3n nueva ${sid}`)));
8557
9167
  }
8558
9168
  let r;
8559
9169
  try {
@@ -8567,24 +9177,25 @@ async function scheduleCmd(sub, agent, arg, v) {
8567
9177
  const p = r.programado;
8568
9178
  return out(
8569
9179
  { scheduled: p },
8570
- () => `${lavender("\u2713")} Scheduled ${p.id} \xB7 ${p.dueAt}${p.repeatMin ? ` \xB7 every ${every(p.repeatMin)} until ${p.until}` : ""}
8571
- ${dim(`conversation ${p.sessionId}`)}`
9180
+ () => `${lavender("\u2713")} ${t("Scheduled", "Programado")} ${p.id} \xB7 ${p.dueAt}${p.repeatMin ? t(` \xB7 every ${every(p.repeatMin)} until ${p.until}`, ` \xB7 cada ${every(p.repeatMin)} hasta ${p.until}`) : ""}
9181
+ ${dim(`${t("conversation", "conversaci\xF3n")} ${p.sessionId}`)}`
8572
9182
  );
8573
9183
  }
8574
9184
  if (sub === "rm" || sub === "cancel") {
8575
- if (!agent || !arg) throw usage("Missing agent id or schedule id.", "Example: ghosty schedule rm <agent-id> <schedule-id> (ids: ghosty schedule ls <agent-id>)");
9185
+ if (!agent || !arg) throw usage(t("Missing agent id or schedule id.", "Falta el id del agente o de lo programado."), t("Example", "Ejemplo") + ": ghosty schedule rm <agent-id> <schedule-id> (ids: ghosty schedule ls <agent-id>)");
8576
9186
  const row = (await agentSchedule(agent)).pendientes.find((p) => p.id === arg);
8577
- if (!row) throw usage(`No pending scheduled turn ${arg}.`, "See them with: ghosty schedule ls <agent-id>");
9187
+ if (!row) throw usage(t(`No pending scheduled turn ${arg}.`, `No hay un turno programado pendiente ${arg}.`), t("See them with", "M\xEDralos con") + ": ghosty schedule ls <agent-id>");
8578
9188
  await api(convPath(agent, `/${enc4(row.sessionId)}/schedule/${enc4(arg)}`), { method: "DELETE" });
8579
- return out({ canceled: arg }, () => `${lavender("\u2713")} Canceled ${arg}`);
9189
+ return out({ canceled: arg }, () => `${lavender("\u2713")} ${t("Canceled", "Cancelado")} ${arg}`);
8580
9190
  }
8581
- throw usage(`Unknown schedule subcommand: ${sub ?? "(none)"}.`, "Try: ghosty schedule ls | add | rm");
9191
+ throw usage(t(`Unknown schedule subcommand: ${sub ?? "(none)"}.`, `Subcomando de schedule desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty schedule ls | add | rm");
8582
9192
  }
8583
9193
 
8584
9194
  // src/commands/partner.ts
8585
9195
  import { createHmac } from "node:crypto";
8586
9196
  import { readFile as readFile5 } from "node:fs/promises";
8587
- var PARTNER_HELP = `${bold("Usage:")} ghosty partner <command> [options]
9197
+ var partnerHelp = () => t(
9198
+ `${bold("Usage:")} ghosty partner <command> [options]
8588
9199
 
8589
9200
  ${bold("Partner space")} ${dim("(you, signed in as the space owner)")}
8590
9201
  partner ls Your partner spaces
@@ -8614,7 +9225,39 @@ ${bold("Options")}
8614
9225
  --json Machine-readable output
8615
9226
 
8616
9227
  ${bold("Server credential mode")} GHOSTY_PARTNER_KEY=gpk_\u2026 GHOSTY_PARTNER_SECRET=gps_\u2026 ghosty partner agent get
8617
- ${dim("Docs: https://www.ghosty.studio/docs/cli/partners")}`;
9228
+ ${dim("Docs: https://www.ghosty.studio/docs/cli/partners")}`,
9229
+ `${bold("Uso:")} ghosty partner <comando> [opciones]
9230
+
9231
+ ${bold("Espacio de partner")} ${dim("(t\xFA, con sesi\xF3n como due\xF1a del espacio)")}
9232
+ partner ls Tus espacios de partner
9233
+ partner enable <workspace> Convierte uno de tus espacios en espacio de partner
9234
+ partner credentials ls Credenciales de servidor (sin secretos)
9235
+ partner credentials create [--label L] Emite una \u2014 el secreto se muestra UNA vez
9236
+ partner credentials rotate <keyId> [--grace MIN] Secreto nuevo, mismo keyId (el viejo vale MIN, default 60)
9237
+ partner credentials revoke <keyId> La revoca (idempotente)
9238
+ partner audit Qui\xE9n hizo qu\xE9 y cu\xE1ndo
9239
+
9240
+ ${bold("Agente compartido (solo-MCP)")}
9241
+ partner agent get La plantilla del agente y su versi\xF3n
9242
+ partner agent apply --file agent.json Lo crea o lo actualiza (idempotente)
9243
+
9244
+ ${bold("Tenants")} ${dim("(tus clientes, por externalId \u2014 s\xF3lo config, sin caja)")}
9245
+ partner tenants ls
9246
+ partner tenants get <externalId>
9247
+ partner tenants set <externalId> --config '{"name":"\u2026","timezone":"America/Mexico_City"}'
9248
+ partner tenants rm <externalId> Borra su config Y su memoria
9249
+
9250
+ ${bold("Pru\xE9balo")}
9251
+ partner try <externalId> "mensaje" Un turno [--group END_USER_ID] [--turn-token TOKEN_DE_VIDA_CORTA]
9252
+ (el token del turno llega a TU MCP como \${turn.token}; --token tambi\xE9n sirve aqu\xED)
9253
+
9254
+ ${bold("Opciones")}
9255
+ --workspace SLUG Qu\xE9 espacio de partner (s\xF3lo si tienes m\xE1s de uno)
9256
+ --json Salida para m\xE1quinas
9257
+
9258
+ ${bold("Modo credencial de servidor")} GHOSTY_PARTNER_KEY=gpk_\u2026 GHOSTY_PARTNER_SECRET=gps_\u2026 ghosty partner agent get
9259
+ ${dim("Docs: https://www.ghosty.studio/docs/cli/partners")}`
9260
+ );
8618
9261
  var enc5 = encodeURIComponent;
8619
9262
  function partnerCreds() {
8620
9263
  const key = process.env.GHOSTY_PARTNER_KEY;
@@ -8635,7 +9278,7 @@ async function call(path, v, init = {}) {
8635
9278
  "x-ghosty-ts": ts,
8636
9279
  "x-ghosty-sig": sig,
8637
9280
  "content-type": "application/json",
8638
- "user-agent": `ghosty-cli/${"0.19.0"}`
9281
+ "user-agent": `ghosty-cli/${"0.20.0"}`
8639
9282
  },
8640
9283
  body: method === "GET" ? void 0 : raw
8641
9284
  });
@@ -8650,15 +9293,15 @@ async function call(path, v, init = {}) {
8650
9293
  }
8651
9294
  async function json(path, v, init = {}) {
8652
9295
  const res = await call(path, v, init);
8653
- const t = await res.text();
8654
- return t ? JSON.parse(t) : void 0;
9296
+ const text = await res.text();
9297
+ return text ? JSON.parse(text) : void 0;
8655
9298
  }
8656
9299
  var fmt = (d) => d ? d.replace("T", " ").slice(0, 16) : "\u2014";
8657
9300
  function showSecret(r) {
8658
9301
  out(
8659
9302
  r,
8660
9303
  () => [
8661
- `${lavender("\u2713")} Save these now \u2014 the secret is not shown again:`,
9304
+ `${lavender("\u2713")} ${t("Save these now \u2014 the secret is not shown again:", "Gu\xE1rdalas ya \u2014 el secreto no se vuelve a mostrar:")}`,
8662
9305
  ` GHOSTY_PARTNER_KEY=${r.keyId}`,
8663
9306
  ` GHOSTY_PARTNER_SECRET=${r.secret}`
8664
9307
  ].join("\n")
@@ -8679,17 +9322,17 @@ async function credentialsCmd(sub, id, v) {
8679
9322
  if (sub === "create" || sub === "new") {
8680
9323
  return showSecret(await json("/api/v2/partner/credentials", v, { ...owner, method: "POST", body: { label: v.label } }));
8681
9324
  }
8682
- if (!id) throw usage("Missing keyId.", `Example: ghosty partner credentials ${sub} gpk_\u2026`);
9325
+ if (!id) throw usage(t("Missing keyId.", "Falta el keyId."), `${t("Example", "Ejemplo")}: ghosty partner credentials ${sub} gpk_\u2026`);
8683
9326
  if (sub === "rotate") {
8684
9327
  const grace = v.grace !== void 0 ? Number(v.grace) : void 0;
8685
- if (grace !== void 0 && (!Number.isFinite(grace) || grace < 0)) throw usage("--grace must be minutes \u2265 0.");
9328
+ if (grace !== void 0 && (!Number.isFinite(grace) || grace < 0)) throw usage(t("--grace must be minutes \u2265 0.", "--grace deben ser minutos \u2265 0."));
8686
9329
  return showSecret(await json(`/api/v2/partner/credentials/${enc5(id)}/rotate`, v, { ...owner, method: "POST", body: { graceMinutes: grace } }));
8687
9330
  }
8688
9331
  if (sub === "revoke" || sub === "rm") {
8689
9332
  const r = await json(`/api/v2/partner/credentials/${enc5(id)}`, v, { ...owner, method: "DELETE" });
8690
- return out(r, () => `${lavender("\u2713")} Revoked ${r.keyId}.`);
9333
+ return out(r, () => `${lavender("\u2713")} ${t(`Revoked ${r.keyId}.`, `${r.keyId} revocada.`)}`);
8691
9334
  }
8692
- throw usage(`Unknown credentials subcommand: ${sub}.`, "Try: ghosty partner credentials ls | create | rotate | revoke");
9335
+ throw usage(t(`Unknown credentials subcommand: ${sub}.`, `Subcomando de credentials desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner credentials ls | create | rotate | revoke");
8693
9336
  }
8694
9337
  async function agentCmd(sub, v) {
8695
9338
  if (!sub || sub === "get") {
@@ -8698,46 +9341,46 @@ async function agentCmd(sub, v) {
8698
9341
  const a = r.agent;
8699
9342
  return [
8700
9343
  `${bold(a.name)} ${dim(`v${a.version} \xB7 solo-MCP \xB7 ${a.engine}/${a.model}`)}`,
8701
- `MCP: ${a.mcp.map((m) => `${m.name} (${m.url})`).join(", ") || "none"}`,
9344
+ `MCP: ${a.mcp.map((m) => `${m.name} (${m.url})`).join(", ") || t("none", "ninguno")}`,
8702
9345
  dim(a.prompt.slice(0, 300))
8703
9346
  ].join("\n");
8704
9347
  });
8705
9348
  }
8706
9349
  if (sub === "apply" || sub === "set") {
8707
- if (!v.file) throw usage("Missing --file.", "Example: ghosty partner agent apply --file agent.json");
9350
+ if (!v.file) throw usage(t("Missing --file.", "Falta --file."), t("Example", "Ejemplo") + ": ghosty partner agent apply --file agent.json");
8708
9351
  const body = JSON.parse(await readFile5(v.file, "utf8"));
8709
9352
  const r = await json("/api/v2/partner/agent", v, { method: "PUT", body });
8710
- return out(r, () => `${lavender("\u2713")} ${r.created ? "Created" : r.changed ? "Updated" : "No changes"} \xB7 v${r.agent.version}`);
9353
+ return out(r, () => `${lavender("\u2713")} ${r.created ? t("Created", "Creado") : r.changed ? t("Updated", "Actualizado") : t("No changes", "Sin cambios")} \xB7 v${r.agent.version}`);
8711
9354
  }
8712
- throw usage(`Unknown agent subcommand: ${sub}.`, "Try: ghosty partner agent get | apply --file agent.json");
9355
+ throw usage(t(`Unknown agent subcommand: ${sub}.`, `Subcomando de agent desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner agent get | apply --file agent.json");
8713
9356
  }
8714
9357
  async function tenantsCmd(sub, id, v) {
8715
9358
  if (!sub || sub === "ls" || sub === "list") {
8716
9359
  const r = await json("/api/v2/partner/tenants", v);
8717
- return out(r, () => table(r.tenants.map((t) => ({ externalId: t.externalId, config: JSON.stringify(t.config ?? {}), updated: fmt(t.updatedAt) })), ["externalId", "config", "updated"]));
9360
+ return out(r, () => table(r.tenants.map((tn) => ({ externalId: tn.externalId, config: JSON.stringify(tn.config ?? {}), updated: fmt(tn.updatedAt) })), ["externalId", "config", "updated"]));
8718
9361
  }
8719
- if (!id) throw usage("Missing externalId.", `Example: ghosty partner tenants ${sub} org_123`);
9362
+ if (!id) throw usage(t("Missing externalId.", "Falta el externalId."), `${t("Example", "Ejemplo")}: ghosty partner tenants ${sub} org_123`);
8720
9363
  const path = `/api/v2/partner/tenants/${enc5(id)}`;
8721
9364
  if (sub === "get") return out(await json(path, v));
8722
9365
  if (sub === "set" || sub === "apply") {
8723
- if (!v.config) throw usage("Missing --config.", `Example: --config '{"name":"Spa","timezone":"America/Mexico_City"}'`);
9366
+ if (!v.config) throw usage(t("Missing --config.", "Falta --config."), `${t("Example", "Ejemplo")}: --config '{"name":"Spa","timezone":"America/Mexico_City"}'`);
8724
9367
  let config;
8725
9368
  try {
8726
9369
  config = JSON.parse(v.config);
8727
9370
  } catch {
8728
- throw usage("--config must be JSON.");
9371
+ throw usage(t("--config must be JSON.", "--config debe ser JSON."));
8729
9372
  }
8730
9373
  const r = await json(path, v, { method: "PUT", body: { config } });
8731
- return out(r, () => `${lavender("\u2713")} ${r.created ? "Created" : r.changed ? "Updated" : "No changes"} ${id}`);
9374
+ return out(r, () => `${lavender("\u2713")} ${r.created ? t("Created", "Creado") : r.changed ? t("Updated", "Actualizado") : t("No changes", "Sin cambios")} ${id}`);
8732
9375
  }
8733
9376
  if (sub === "rm" || sub === "delete") {
8734
9377
  const r = await json(path, v, { method: "DELETE" });
8735
- return out(r, () => r.deleted ? `${lavender("\u2713")} Deleted ${id} (${r.conversations} conversations)` : dim(`${id} did not exist.`));
9378
+ return out(r, () => r.deleted ? `${lavender("\u2713")} ${t(`Deleted ${id} (${r.conversations} conversations)`, `${id} borrado (${r.conversations} conversaciones)`)}` : dim(t(`${id} did not exist.`, `${id} no exist\xEDa.`)));
8736
9379
  }
8737
- throw usage(`Unknown tenants subcommand: ${sub}.`, "Try: ghosty partner tenants ls | get | set | rm");
9380
+ throw usage(t(`Unknown tenants subcommand: ${sub}.`, `Subcomando de tenants desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner tenants ls | get | set | rm");
8738
9381
  }
8739
9382
  async function tryCmd(externalId, text, v) {
8740
- if (!externalId || !text) throw usage("Missing externalId or message.", 'Example: ghosty partner try org_123 "\xBFqu\xE9 citas tengo hoy?"');
9383
+ if (!externalId || !text) throw usage(t("Missing externalId or message.", "Falta el externalId o el mensaje."), t("Example", "Ejemplo") + ': ghosty partner try org_123 "\xBFqu\xE9 citas tengo hoy?"');
8741
9384
  const res = await call("/api/v2/partner/turns", v, {
8742
9385
  method: "POST",
8743
9386
  body: { tenant: { externalId }, groupId: v.group ?? "cli", text, ...v.token ? { token: v.token } : {} }
@@ -8762,7 +9405,7 @@ async function tryCmd(externalId, text, v) {
8762
9405
  full += ev.value;
8763
9406
  process.stdout.write(ev.value);
8764
9407
  } else if (ev.type === "tool" && ev.phase === "start") info(dim(` \u21B3 ${ev.name}`));
8765
- else if (ev.type === "error") info(`Error: ${ev.message}`);
9408
+ else if (ev.type === "error") info(`Error: ${serverText(ev.message ?? "")}`);
8766
9409
  }
8767
9410
  }
8768
9411
  if (!globals.json && full) process.stdout.write("\n");
@@ -8771,16 +9414,16 @@ async function partnerCmd(sub, a1, a2, v) {
8771
9414
  switch (sub) {
8772
9415
  case void 0:
8773
9416
  case "help":
8774
- return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
9417
+ return out({ help: partnerHelp() }, () => partnerHelp());
8775
9418
  case "ls":
8776
9419
  case "list": {
8777
9420
  const r = await json("/api/v2/partner", v, { ownerOnly: true });
8778
9421
  return out(r, () => table(r.partners.map((p) => ({ workspace: p.workspace, credentials: p.credentials, agent: p.agents ? "yes" : "no", status: p.disabledAt ? "off" : "on" })), ["workspace", "credentials", "agent", "status"]));
8779
9422
  }
8780
9423
  case "enable": {
8781
- if (!a1) throw usage("Missing workspace.", "Example: ghosty partner enable denik");
9424
+ if (!a1) throw usage(t("Missing workspace.", "Falta el workspace."), t("Example", "Ejemplo") + ": ghosty partner enable denik");
8782
9425
  const r = await json("/api/v2/partner", v, { ownerOnly: true, method: "POST", body: { workspace: a1 } });
8783
- return out(r, () => `${lavender("\u2713")} ${r.partner.workspace} is a partner space.`);
9426
+ return out(r, () => `${lavender("\u2713")} ${t(`${r.partner.workspace} is a partner space.`, `${r.partner.workspace} ya es espacio de partner.`)}`);
8784
9427
  }
8785
9428
  case "credentials":
8786
9429
  case "creds":
@@ -8796,7 +9439,7 @@ async function partnerCmd(sub, a1, a2, v) {
8796
9439
  case "try":
8797
9440
  return tryCmd(a1, a2, v);
8798
9441
  default:
8799
- throw usage(`Unknown partner subcommand: ${sub}.`, "Run: ghosty partner --help");
9442
+ throw usage(t(`Unknown partner subcommand: ${sub}.`, `Subcomando de partner desconocido: ${sub}.`), t("Run", "Corre") + ": ghosty partner --help");
8800
9443
  }
8801
9444
  }
8802
9445
 
@@ -8820,40 +9463,40 @@ function parseRowScope(raw) {
8820
9463
  }
8821
9464
  return out2;
8822
9465
  }
8823
- var describeRowScope = (scope) => Object.entries(scope ?? {}).map(([t, c]) => `${t} (${c.join(", ")})`).join("; ");
9466
+ var describeRowScope = (scope) => Object.entries(scope ?? {}).map(([t2, c]) => `${t2} (${c.join(", ")})`).join("; ");
8824
9467
 
8825
9468
  // src/commands/data.ts
8826
9469
  var enc6 = encodeURIComponent;
8827
9470
  var agentPath2 = (id, rest = "") => `/api/v2/agents/${enc6(id)}${rest}`;
8828
9471
  var need2 = (v, what, example) => {
8829
- if (!v) throw usage(`Missing ${what}.`, `Example: ${example}`);
9472
+ if (!v) throw usage(t(`Missing ${what}.`, `Falta ${what}.`), `${t("Example", "Ejemplo")}: ${example}`);
8830
9473
  return v;
8831
9474
  };
8832
9475
  var csv = (s) => (s ?? "").split(",").map((x) => x.trim()).filter(Boolean);
8833
9476
  function planText(plan) {
8834
- return plan.map((p) => p.action === "create" ? ` + ${p.table} create, ${p.sourceRows} row(s)` : ` ~ ${p.table} ${p.action} ${p.targetRows} \u2192 ${p.resultRows} row(s)`).join("\n");
9477
+ return plan.map((p) => p.action === "create" ? t(` + ${p.table} create, ${p.sourceRows} row(s)`, ` + ${p.table} crear, ${p.sourceRows} fila(s)`) : t(` ~ ${p.table} ${p.action} ${p.targetRows} \u2192 ${p.resultRows} row(s)`, ` ~ ${p.table} ${p.action === "replace" ? "reemplazar" : "agregar"} ${p.targetRows} \u2192 ${p.resultRows} fila(s)`)).join("\n");
8835
9478
  }
8836
9479
  async function dbsImport(id, db, v) {
8837
9480
  const ex = "ghosty dbs import <agent-id> <db> --from easybits:<db-id> [--tables a,b] [--dry-run]";
8838
9481
  const m = /^easybits:(.+)$/.exec(v.from ?? "");
8839
- if (!m) throw usage("Missing or bad --from.", `Example: ${ex}`);
9482
+ if (!m) throw usage(t("Missing or bad --from.", "Falta --from o no es v\xE1lido."), `${t("Example", "Ejemplo")}: ${ex}`);
8840
9483
  const easybitsKey = process.env.EASYBITS_API_KEY;
8841
- if (!easybitsKey) throw usage("EASYBITS_API_KEY is not set.", "Export your EasyBits API key (https://www.easybits.cloud/dash/developer) and run again.");
9484
+ if (!easybitsKey) throw usage(t("EASYBITS_API_KEY is not set.", "EASYBITS_API_KEY no est\xE1 definida."), t("Export your EasyBits API key (https://www.easybits.cloud/dash/developer) and run again.", "Exporta tu API key de EasyBits (https://www.easybits.cloud/dash/developer) y vuelve a correrlo."));
8842
9485
  const body = { from: "easybits", sourceDb: m[1], easybitsKey, mode: v.append ? "append" : "replace", ...v.tables ? { tables: csv(v.tables) } : {}, ...v["rehost-images"] ? { rehost: csv(v["rehost-images"]) } : {} };
8843
9486
  const path = agentPath2(id, `/dbs/${enc6(db)}/import`);
8844
9487
  const dry = await api(path, { body: { ...body, dryRun: true } });
8845
9488
  if (v["dry-run"]) return out(dry, () => `${dry.db} \u2190 easybits:${m[1]}
8846
9489
  ${planText(dry.plan)}
8847
- ${dim("Dry run: nothing changed.")}`);
9490
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
8848
9491
  const destructive = dry.plan.some((p) => p.action === "replace" && p.targetRows > 0);
8849
9492
  if (destructive) await confirm(`${planText(dry.plan)}
8850
- Replace the rows above in ${dry.db}?`, { yes: v.yes, retry: `ghosty dbs import ${id} ${db} --from ${v.from}${v.tables ? ` --tables ${v.tables}` : ""}` });
9493
+ ${t(`Replace the rows above in ${dry.db}?`, `\xBFReemplazar las filas de arriba en ${dry.db}?`)}`, { yes: v.yes, retry: `ghosty dbs import ${id} ${db} --from ${v.from}${v.tables ? ` --tables ${v.tables}` : ""}` });
8851
9494
  const r = await api(path, { body });
8852
9495
  const img = r.rehosted ? `
8853
- ${r.rehosted.copied} image link(s) now permanent${r.rehosted.failed.length ? `, ${red(String(r.rehosted.failed.length))} kept as they were:
9496
+ ${t(`${r.rehosted.copied} image link(s) now permanent`, `${r.rehosted.copied} liga(s) de imagen ya permanentes`)}${r.rehosted.failed.length ? `, ${red(String(r.rehosted.failed.length))} ${t("kept as they were", "se quedaron como estaban")}:
8854
9497
  ${r.rehosted.failed.map((f) => ` ${f.url} ${dim(f.error)}`).join("\n")}` : ""}` : "";
8855
9498
  return out(r, () => `${planText(r.plan)}${img}
8856
- ${lavender("\u2713")} Imported into ${r.db}. ${dim("Run it again any time: with replace it ends in the same state.")}`);
9499
+ ${lavender("\u2713")} ${t(`Imported into ${r.db}.`, `Importado en ${r.db}.`)} ${dim(t("Run it again any time: with replace it ends in the same state.", "C\xF3rrelo otra vez cuando quieras: con replace termina en el mismo estado."))}`);
8857
9500
  }
8858
9501
  async function dbsRehost(id, db, v) {
8859
9502
  const ex = "ghosty dbs rehost <agent-id> <db> --table catalogo --column imagen_url [--dry-run]";
@@ -8861,9 +9504,9 @@ async function dbsRehost(id, db, v) {
8861
9504
  const column = need2(v.column, "--column", ex);
8862
9505
  const path = agentPath2(id, `/dbs/${enc6(db)}/rehost`);
8863
9506
  const dry = await api(path, { body: { table: table2, column, dryRun: true } });
8864
- const head = `${dry.db} \xB7 ${table2}.${column}: ${dry.distinct} distinct link(s), ${dry.alreadyOurs} already permanent, ${dry.toCopy} to copy`;
9507
+ const head = `${dry.db} \xB7 ${table2}.${column}: ${t(`${dry.distinct} distinct link(s), ${dry.alreadyOurs} already permanent, ${dry.toCopy} to copy`, `${dry.distinct} liga(s) distintas, ${dry.alreadyOurs} ya permanentes, ${dry.toCopy} por copiar`)}`;
8865
9508
  if (v["dry-run"] || !dry.toCopy) return out(dry, () => `${head}
8866
- ${dim(v["dry-run"] ? "Dry run: nothing changed." : "Nothing to do.")}`);
9509
+ ${dim(v["dry-run"] ? t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada.") : t("Nothing to do.", "No hay nada que hacer."))}`);
8867
9510
  info(head);
8868
9511
  const total = { copied: 0, reused: 0, failed: [], warnings: [] };
8869
9512
  let after;
@@ -8880,50 +9523,50 @@ ${dim(v["dry-run"] ? "Dry run: nothing changed." : "Nothing to do.")}`);
8880
9523
  return out(
8881
9524
  total,
8882
9525
  () => [
8883
- `${lavender("\u2713")} ${total.copied + total.reused} link(s) now permanent (${total.copied} copied, ${total.reused} already stored).`,
8884
- total.warnings.length ? `${total.warnings.length} warning(s):
9526
+ `${lavender("\u2713")} ${t(`${total.copied + total.reused} link(s) now permanent (${total.copied} copied, ${total.reused} already stored).`, `${total.copied + total.reused} liga(s) ya permanentes (${total.copied} copiadas, ${total.reused} ya guardadas).`)}`,
9527
+ total.warnings.length ? `${total.warnings.length} ${t("warning(s)", "aviso(s)")}:
8885
9528
  ${total.warnings.map((w) => ` ${w.url} ${dim(w.warning)}`).join("\n")}` : "",
8886
- total.failed.length ? `${red(String(total.failed.length))} kept as they were:
9529
+ total.failed.length ? `${red(String(total.failed.length))} ${t("kept as they were", "se quedaron como estaban")}:
8887
9530
  ${total.failed.map((f) => ` ${f.url} ${dim(f.error)}`).join("\n")}` : ""
8888
9531
  ].filter(Boolean).join("\n")
8889
9532
  );
8890
9533
  }
8891
9534
  async function dbsCmd(sub, id, db, rest, v) {
8892
9535
  const ex = "ghosty dbs ls <agent-id>";
8893
- id = need2(id, "agent id", ex);
9536
+ id = need2(id, t("agent id", "el id del agente"), ex);
8894
9537
  switch (sub ?? "ls") {
8895
9538
  case "ls":
8896
9539
  case "list": {
8897
9540
  const r = await api(agentPath2(id, "/dbs"));
8898
9541
  const rows = r.dbs.map((d) => ({ name: d.name, access: d.granted ? "granted" : "\u2014", "writes from customers": d.granted ? d.externalWriteTables.join(", ") || "(none)" : "", "own rows": d.granted ? Object.keys(d.externalRowScope ?? {}).join(", ") : "", id: d.id }));
8899
- return out(r.dbs, () => rows.length ? table(rows, ["name", "access", "writes from customers", "own rows", "id"]) : dim("This account has no databases yet: create one at /app/dbs."));
9542
+ return out(r.dbs, () => rows.length ? table(rows, ["name", "access", "writes from customers", "own rows", "id"]) : dim(t("This account has no databases yet: create one at /app/dbs.", "Esta cuenta todav\xEDa no tiene bases: crea una en /app/dbs.")));
8900
9543
  }
8901
9544
  case "tables": {
8902
- db = need2(db, "database (id or name)", "ghosty dbs tables <agent-id> <db>");
9545
+ db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs tables <agent-id> <db>");
8903
9546
  const r = await api(agentPath2(id, `/dbs/${enc6(db)}`));
8904
9547
  const ext = new Set(r.externalWriteTables);
8905
9548
  const own = r.externalRowScope ?? {};
8906
- const rows = r.tables.map((t) => ({ table: t.name, rows: t.rows, "customer writes": ext.has(t.name) ? "yes" : "", "customer sees": own[t.name] ? `own rows (${own[t.name].join(", ")})` : "", columns: t.columns.map((c) => `${c.name}${c.pk ? "*" : ""}`).join(", ") }));
8907
- return out(r, () => `${bold(r.name)} ${r.granted ? lavender("granted") : dim("not granted")}
9549
+ const rows = r.tables.map((tb) => ({ table: tb.name, rows: tb.rows, "customer writes": ext.has(tb.name) ? "yes" : "", "customer sees": own[tb.name] ? `own rows (${own[tb.name].join(", ")})` : "", columns: tb.columns.map((c) => `${c.name}${c.pk ? "*" : ""}`).join(", ") }));
9550
+ return out(r, () => `${bold(r.name)} ${r.granted ? lavender(t("granted", "concedida")) : dim(t("not granted", "no concedida"))}
8908
9551
  ${table(rows, ["table", "rows", "customer writes", "customer sees", "columns"])}`);
8909
9552
  }
8910
9553
  case "query":
8911
9554
  case "sql": {
8912
- db = need2(db, "database (id or name)", 'ghosty dbs query <agent-id> <db> "SELECT \u2026"');
8913
- const sql = need2(rest.join(" ").trim() || void 0, "SQL", 'ghosty dbs query <agent-id> <db> "SELECT * FROM t LIMIT 10"');
9555
+ db = need2(db, t("database (id or name)", "la base (id o nombre)"), 'ghosty dbs query <agent-id> <db> "SELECT \u2026"');
9556
+ const sql = need2(rest.join(" ").trim() || void 0, t("SQL", "el SQL"), 'ghosty dbs query <agent-id> <db> "SELECT * FROM t LIMIT 10"');
8914
9557
  const r = await api(agentPath2(id, `/dbs/${enc6(db)}/query`), { body: { sql, args: v.arg ?? [], write: v.write === true } }).catch((e) => {
8915
- if (e instanceof CliError && !v.write && /lectura|SELECT|escribir/i.test(e.message)) throw new CliError(e.message, EXIT.API, "To write on purpose, add --write.");
9558
+ if (e instanceof CliError && !v.write && /lectura|SELECT|escribir/i.test(e.message)) throw new CliError(e.message, EXIT.API, t("To write on purpose, add --write.", "Para escribir a prop\xF3sito, agrega --write."));
8916
9559
  throw e;
8917
9560
  });
8918
9561
  return out(r, () => {
8919
- if (!r.cols.length) return `${lavender("\u2713")} ${r.affected} row(s) affected ${dim(`(${Math.round(r.ms)} ms)`)}`;
9562
+ if (!r.cols.length) return `${lavender("\u2713")} ${t(`${r.affected} row(s) affected`, `${r.affected} fila(s) afectadas`)} ${dim(`(${Math.round(r.ms)} ms)`)}`;
8920
9563
  const rows = r.rows.map((row) => Object.fromEntries(r.cols.map((c, i) => [c, row[i]])));
8921
9564
  return `${table(rows, r.cols)}
8922
- ${dim(`${rows.length} row(s) \xB7 ${Math.round(r.ms)} ms`)}`;
9565
+ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round(r.ms)} ms`)}`;
8923
9566
  });
8924
9567
  }
8925
9568
  case "grant": {
8926
- db = need2(db, "database (id or name)", "ghosty dbs grant <agent-id> <db> [--external-write t1,t2] [--external-rows t:col+col]");
9569
+ db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs grant <agent-id> <db> [--external-write t1,t2] [--external-rows t:col+col]");
8927
9570
  let rowScope;
8928
9571
  try {
8929
9572
  rowScope = v["external-rows"] !== void 0 ? parseRowScope(v["external-rows"]) : void 0;
@@ -8936,44 +9579,44 @@ ${dim(`${rows.length} row(s) \xB7 ${Math.round(r.ms)} ms`)}`;
8936
9579
  };
8937
9580
  const r = await api(agentPath2(id, `/dbs/${enc6(db)}`), { method: "PUT", body });
8938
9581
  const own = describeRowScope(r.externalRowScope);
8939
- return out(r, () => `${lavender("\u2713")} ${r.name} granted. ${dim(`From customer channels it writes to: ${r.externalWriteTables.join(", ") || "nothing (read only)"}.${own ? ` Each customer sees only their own rows of: ${own}.` : ""}`)}`);
9582
+ return out(r, () => `${lavender("\u2713")} ${r.name} ${t("granted.", "concedida.")} ${dim(t(`From customer channels it writes to: ${r.externalWriteTables.join(", ") || "nothing (read only)"}.${own ? ` Each customer sees only their own rows of: ${own}.` : ""}`, `Desde canales de clientes escribe en: ${r.externalWriteTables.join(", ") || "nada (s\xF3lo lectura)"}.${own ? ` Cada cliente ve s\xF3lo sus propias filas de: ${own}.` : ""}`))}`);
8940
9583
  }
8941
9584
  case "import": {
8942
- db = need2(db, "database (id or name)", "ghosty dbs import <agent-id> <db> --from easybits:<db-id>");
9585
+ db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs import <agent-id> <db> --from easybits:<db-id>");
8943
9586
  return dbsImport(id, db, v);
8944
9587
  }
8945
9588
  case "rehost": {
8946
- db = need2(db, "database (id or name)", "ghosty dbs rehost <agent-id> <db> --table T --column C");
9589
+ db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs rehost <agent-id> <db> --table T --column C");
8947
9590
  return dbsRehost(id, db, v);
8948
9591
  }
8949
9592
  case "revoke": {
8950
- db = need2(db, "database (id or name)", "ghosty dbs revoke <agent-id> <db>");
9593
+ db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs revoke <agent-id> <db>");
8951
9594
  const r = await api(agentPath2(id, `/dbs/${enc6(db)}`), { method: "DELETE" });
8952
- return out(r, () => `${lavender("\u2713")} ${r.name} revoked. ${dim(`Undo: ghosty dbs grant ${id} ${r.name}`)}`);
9595
+ return out(r, () => `${lavender("\u2713")} ${r.name} ${t("revoked.", "revocada.")} ${dim(`${t("Undo", "Deshacer")}: ghosty dbs grant ${id} ${r.name}`)}`);
8953
9596
  }
8954
9597
  default:
8955
- throw usage(`Unknown dbs subcommand: ${sub}.`, "Try: ghosty dbs ls | tables | query | import | rehost | grant | revoke (ghosty dbs --help)");
9598
+ throw usage(t(`Unknown dbs subcommand: ${sub}.`, `Subcomando de dbs desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty dbs ls | tables | query | import | rehost | grant | revoke (ghosty dbs --help)");
8956
9599
  }
8957
9600
  }
8958
9601
  async function agentDocsCmd(sub, id, docs, v) {
8959
9602
  let doc = docs[0];
8960
- id = need2(id, "agent id", "ghosty agents docs ls <agent-id>");
9603
+ id = need2(id, t("agent id", "el id del agente"), "ghosty agents docs ls <agent-id>");
8961
9604
  switch (sub ?? "ls") {
8962
9605
  case "ls":
8963
9606
  case "list": {
8964
9607
  const r = await api(agentPath2(id, "/documents"));
8965
9608
  const rows = r.documents.map((d) => ({ name: d.name, access: d.granted ? d.mode === "write" ? "read+replace" : "read" : "\u2014", delivers: d.granted ? d.deliverable ? "yes" : "no" : "", id: d.id }));
8966
- return out(r.documents, () => rows.length ? table(rows, ["name", "access", "delivers", "id"]) : dim("This account has no documents yet: upload them at /app/datos."));
9609
+ return out(r.documents, () => rows.length ? table(rows, ["name", "access", "delivers", "id"]) : dim(t("This account has no documents yet: upload them at /app/datos.", "Esta cuenta todav\xEDa no tiene documentos: s\xFAbelos en /app/datos.")));
8967
9610
  }
8968
9611
  case "grant": {
8969
9612
  let wanted;
8970
9613
  if (v.like) {
8971
- if (docs.length) throw usage("Use document names or --like, not both.");
9614
+ if (docs.length) throw usage(t("Use document names or --like, not both.", "Usa nombres de documentos o --like, no ambos."));
8972
9615
  const other = await api(agentPath2(v.like, "/documents"));
8973
9616
  wanted = other.documents.filter((d) => d.granted).map((d) => ({ ref: d.id, body: { mode: d.mode ?? "read", deliverable: d.deliverable ?? true } }));
8974
- if (!wanted.length) return out({ granted: [] }, () => dim(`${v.like} has no documents granted.`));
9617
+ if (!wanted.length) return out({ granted: [] }, () => dim(t(`${v.like} has no documents granted.`, `${v.like} no tiene documentos concedidos.`)));
8975
9618
  } else {
8976
- if (!docs.length) throw usage("Missing document (id or exact name).", 'Example: ghosty agents docs grant <agent-id> "Cat\xE1logo.pdf" "Precios.xlsx" (or --like <other-agent>)');
9619
+ if (!docs.length) throw usage(t("Missing document (id or exact name).", "Falta el documento (id o nombre exacto)."), t('Example: ghosty agents docs grant <agent-id> "Cat\xE1logo.pdf" "Precios.xlsx" (or --like <other-agent>)', 'Ejemplo: ghosty agents docs grant <agent-id> "Cat\xE1logo.pdf" "Precios.xlsx" (o --like <otro-agente>)'));
8977
9620
  const body = {};
8978
9621
  if (v.write) body.mode = "write";
8979
9622
  if (v["no-deliver"]) body.deliverable = false;
@@ -8982,11 +9625,11 @@ async function agentDocsCmd(sub, id, docs, v) {
8982
9625
  }
8983
9626
  const done = [];
8984
9627
  for (const w of wanted) done.push(await api(agentPath2(id, `/documents/${enc6(w.ref)}`), { method: "PUT", body: w.body }));
8985
- return out(done.length === 1 ? done[0] : { granted: done }, () => done.map((r) => `${lavender("\u2713")} ${r.name}: ${r.mode === "write" ? "reads and replaces it" : "reads it"}${r.deliverable ? ", and can send it" : ", never sends it"}.`).join("\n"));
9628
+ return out(done.length === 1 ? done[0] : { granted: done }, () => done.map((r) => `${lavender("\u2713")} ${r.name}: ${r.mode === "write" ? t("reads and replaces it", "lo lee y lo reemplaza") : t("reads it", "lo lee")}${r.deliverable ? t(", and can send it", ", y puede mandarlo") : t(", never sends it", ", nunca lo manda")}.`).join("\n"));
8986
9629
  }
8987
9630
  case "add": {
8988
- if (!v.url === !v.file) throw usage("Use --url URL or --file PATH (one of them).", 'Example: ghosty agents docs add <agent-id> --url https://\u2026/lista.pdf --description "Precios 2026"');
8989
- if (v.grant && !["read", "write", "no"].includes(v.grant)) throw usage("--grant takes read, write or no.");
9631
+ if (!v.url === !v.file) throw usage(t("Use --url URL or --file PATH (one of them).", "Usa --url URL o --file RUTA (uno de los dos)."), t("Example", "Ejemplo") + ': ghosty agents docs add <agent-id> --url https://\u2026/lista.pdf --description "Precios 2026"');
9632
+ if (v.grant && !["read", "write", "no"].includes(v.grant)) throw usage(t("--grant takes read, write or no.", "--grant acepta read, write o no."));
8990
9633
  const body = {
8991
9634
  ...v.url ? { url: v.url } : { contentBase64: (await readFile6(v.file)).toString("base64"), name: v.name ?? basename3(v.file) },
8992
9635
  ...v.url && v.name ? { name: v.name } : {},
@@ -8995,21 +9638,21 @@ async function agentDocsCmd(sub, id, docs, v) {
8995
9638
  ...v["no-deliver"] ? { deliverable: false } : {}
8996
9639
  };
8997
9640
  const r = await api(agentPath2(id, "/documents"), { method: "POST", body });
8998
- return out(r, () => `${lavender("\u2713")} ${r.name} uploaded ${dim(`(${r.size} bytes)`)}${r.granted ? `, ${r.mode === "write" ? "it reads and replaces it" : "it reads it"}.` : ". Not granted: ghosty agents docs grant " + id + ` "${r.name}"`}`);
9641
+ return out(r, () => `${lavender("\u2713")} ${r.name} ${t("uploaded", "subido")} ${dim(`(${r.size} bytes)`)}${r.granted ? `, ${r.mode === "write" ? t("it reads and replaces it", "lo lee y lo reemplaza") : t("it reads it", "lo lee")}.` : t(". Not granted: ", ". Sin conceder: ") + "ghosty agents docs grant " + id + ` "${r.name}"`}`);
8999
9642
  }
9000
9643
  case "revoke": {
9001
- doc = need2(doc, "document (id or exact name)", 'ghosty agents docs revoke <agent-id> "Cat\xE1logo.pdf"');
9644
+ doc = need2(doc, t("document (id or exact name)", "el documento (id o nombre exacto)"), 'ghosty agents docs revoke <agent-id> "Cat\xE1logo.pdf"');
9002
9645
  const r = await api(agentPath2(id, `/documents/${enc6(doc)}`), { method: "DELETE" });
9003
- return out(r, () => `${lavender("\u2713")} ${r.name} revoked. ${dim(`Undo: ghosty agents docs grant ${id} "${r.name}"`)}`);
9646
+ return out(r, () => `${lavender("\u2713")} ${r.name} ${t("revoked.", "revocado.")} ${dim(`${t("Undo", "Deshacer")}: ghosty agents docs grant ${id} "${r.name}"`)}`);
9004
9647
  }
9005
9648
  default:
9006
- throw usage(`Unknown agents docs subcommand: ${sub}.`, "Try: ghosty agents docs ls | add | grant | revoke");
9649
+ throw usage(t(`Unknown agents docs subcommand: ${sub}.`, `Subcomando de agents docs desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty agents docs ls | add | grant | revoke");
9007
9650
  }
9008
9651
  }
9009
9652
  async function turnsCmd(sub, id, turnId, v) {
9010
9653
  if (sub === "show") return turnShow(id, turnId, !!v.full);
9011
- if (sub && sub !== "ls" && sub !== "list") throw usage(`Unknown turns subcommand: ${sub}.`, "Try: ghosty turns ls <agent-id> --errors --since 1h \xB7 ghosty turns show <agent-id> [turn-id|last]");
9012
- id = need2(id, "agent id", "ghosty turns ls <agent-id> --errors --since 1h");
9654
+ if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown turns subcommand: ${sub}.`, `Subcomando de turns desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty turns ls <agent-id> --errors --since 1h \xB7 ghosty turns show <agent-id> [turn-id|last]");
9655
+ id = need2(id, t("agent id", "el id del agente"), "ghosty turns ls <agent-id> --errors --since 1h");
9013
9656
  const q = new URLSearchParams();
9014
9657
  const status = v.errors ? "error" : v.status;
9015
9658
  if (status) q.set("state", status);
@@ -9017,44 +9660,44 @@ async function turnsCmd(sub, id, turnId, v) {
9017
9660
  q.set("limit", v.limit ?? "20");
9018
9661
  const r = await api(agentPath2(id, `/turns?${q}`));
9019
9662
  const when = (s) => s.slice(0, 16).replace("T", " ");
9020
- const rows = r.turns.map((t) => ({
9021
- id: t.id,
9022
- started: when(t.startedAt),
9023
- state: t.state === "error" ? red(t.state) : t.state,
9024
- secs: t.ms == null ? "" : Math.round(t.ms / 1e3),
9025
- from: t.surface ?? "",
9026
- session: t.sessionId.slice(0, 24),
9027
- error: (t.error ?? "").replace(/\s+/g, " ").slice(0, 90)
9663
+ const rows = r.turns.map((tu) => ({
9664
+ id: tu.id,
9665
+ started: when(tu.startedAt),
9666
+ state: tu.state === "error" ? red(tu.state) : tu.state,
9667
+ secs: tu.ms == null ? "" : Math.round(tu.ms / 1e3),
9668
+ from: tu.surface ?? "",
9669
+ session: tu.sessionId.slice(0, 24),
9670
+ error: serverText(tu.error ?? "").replace(/\s+/g, " ").slice(0, 90)
9028
9671
  }));
9029
9672
  const tally = Object.entries(r.counts).map(([k, n]) => `${k} ${n}`).join(" \xB7 ");
9030
9673
  out(r, () => rows.length ? `${table(rows, ["id", "started", "state", "secs", "from", "session", "error"])}
9031
- ${dim(`${v.since ? `since ${v.since}: ` : ""}${tally} \xB7 where the time went: ghosty turns show ${id} <turn-id>`)}` : dim(`No ${status ?? ""} turns${v.since ? ` since ${v.since}` : ""}.`));
9674
+ ${dim(`${v.since ? t(`since ${v.since}: `, `desde ${v.since}: `) : ""}${tally} \xB7 ${t("where the time went", "en qu\xE9 se fue el tiempo")}: ghosty turns show ${id} <turn-id>`)}` : dim(t(`No ${status ?? ""} turns${v.since ? ` since ${v.since}` : ""}.`, `Sin turnos${status ? ` ${status}` : ""}${v.since ? ` desde ${v.since}` : ""}.`)));
9032
9675
  }
9033
9676
  async function turnShow(id, turnId, full = false) {
9034
- id = need2(id, "agent id", "ghosty turns show <agent-id> [turn-id|last] [--full]");
9677
+ id = need2(id, t("agent id", "el id del agente"), "ghosty turns show <agent-id> [turn-id|last] [--full]");
9035
9678
  const r = await api(agentPath2(id, `/turns/${enc6(turnId ?? "last")}`));
9036
9679
  out(r, () => {
9037
- const t = r.turn;
9038
- const clip = (s) => full || s.length <= 600 ? s : `${s.slice(0, 600)}\u2026 ${dim(`(+${s.length - 600} chars, --full)`)}`;
9680
+ const tu = r.turn;
9681
+ const clip = (s) => full || s.length <= 600 ? s : `${s.slice(0, 600)}\u2026 ${dim(t(`(+${s.length - 600} chars, --full)`, `(+${s.length - 600} caracteres, --full)`))}`;
9039
9682
  const io = [
9040
- `${lavender("in ")} ${r.input ? clip(r.input) : dim("(no copy of the message)")}`,
9041
- `${lavender("out")} ${r.output ? clip(r.output) : dim("(no answer saved)")}`
9683
+ `${lavender("in ")} ${r.input ? clip(r.input) : dim(t("(no copy of the message)", "(sin copia del mensaje)"))}`,
9684
+ `${lavender("out")} ${r.output ? clip(r.output) : dim(t("(no answer saved)", "(sin respuesta guardada)"))}`
9042
9685
  ];
9043
- const state = `${bold(t.id)} ${t.state === "error" ? red(t.state) : t.state} ${t.secs ?? "?"} s${t.session ? dim(` ${t.session}`) : ""}`;
9044
- if (!r.totals) return [state, ...io, dim(`no timeline: ${r.why ?? "no transcript"}`)].join("\n");
9045
- const head = `${state} ${dim(`${r.totals.model ?? ""} \xB7 ${r.totals.modelCalls} model calls \xB7 ${r.totals.outputTokens} output tokens \xB7 ${r.totals.toolSecs} s in tools`)}`;
9686
+ const state = `${bold(tu.id)} ${tu.state === "error" ? red(tu.state) : tu.state} ${tu.secs ?? "?"} s${tu.session ? dim(` ${tu.session}`) : ""}`;
9687
+ if (!r.totals) return [state, ...io, dim(`${t("no timeline", "sin l\xEDnea de tiempo")}: ${r.why ? serverText(r.why) : t("no transcript", "sin transcript")}`)].join("\n");
9688
+ const head = `${state} ${dim(t(`${r.totals.model ?? ""} \xB7 ${r.totals.modelCalls} model calls \xB7 ${r.totals.outputTokens} output tokens \xB7 ${r.totals.toolSecs} s in tools`, `${r.totals.model ?? ""} \xB7 ${r.totals.modelCalls} llamadas al modelo \xB7 ${r.totals.outputTokens} tokens de salida \xB7 ${r.totals.toolSecs} s en tools`))}`;
9046
9689
  const line2 = (s) => {
9047
9690
  const at = `+${s.at.toFixed(1)}s`.padStart(8);
9048
- if (s.kind === "tool") return `${at} ${bold(s.tool ?? "?")} ${dim(s.input ?? "")} ${s.secs === void 0 ? red("(no result)") : `${s.secs} s`}${s.error ? red(" error") : ""}`;
9691
+ if (s.kind === "tool") return `${at} ${bold(s.tool ?? "?")} ${dim(s.input ?? "")} ${s.secs === void 0 ? red(t("(no result)", "(sin resultado)")) : `${s.secs} s`}${s.error ? red(" error") : ""}`;
9049
9692
  const tok = s.outTokens ? dim(` [${s.outTokens} out]`) : "";
9050
- return s.kind === "thinking" ? `${at} ${dim("thinking")}${tok}` : `${at} ${s.text}${tok}`;
9693
+ return s.kind === "thinking" ? `${at} ${dim(t("thinking", "pensando"))}${tok}` : `${at} ${s.text}${tok}`;
9051
9694
  };
9052
9695
  const top = r.byTool.slice(0, 5).map((b) => `${b.tool} ${b.secs} s (${b.calls})`).join(" \xB7 ");
9053
- return [head, ...io, ...r.steps.map(line2), dim(`slowest: ${top || "\u2014"}`)].join("\n");
9696
+ return [head, ...io, ...r.steps.map(line2), dim(`${t("slowest", "lo m\xE1s lento")}: ${top || "\u2014"}`)].join("\n");
9054
9697
  });
9055
9698
  }
9056
9699
  async function agentsDoctor(id, v) {
9057
- id = need2(id, "agent id", "ghosty agents doctor <agent-id>");
9700
+ id = need2(id, t("agent id", "el id del agente"), "ghosty agents doctor <agent-id>");
9058
9701
  const r = await api(agentPath2(id, "/doctor"));
9059
9702
  const problems = r.summary.warn + r.summary.error;
9060
9703
  if (globals.json) out(r);
@@ -9063,15 +9706,15 @@ async function agentsDoctor(id, v) {
9063
9706
  const lines = [];
9064
9707
  for (const c of r.checks) {
9065
9708
  if (c.level === "ok" && !v.verbose) continue;
9066
- lines.push(`${process.stdout.isTTY ? mark[c.level] : { ok: "\u2713", warn: "!", error: "\u2717" }[c.level]} ${c.title}`);
9067
- if (c.detail && c.level !== "ok") lines.push(...c.detail.split("\n").map((l) => ` ${dim(l)}`));
9068
- if (c.fix && c.level !== "ok") lines.push(` ${dim("fix:")} ${c.fix}`);
9709
+ lines.push(`${process.stdout.isTTY ? mark[c.level] : { ok: "\u2713", warn: "!", error: "\u2717" }[c.level]} ${serverText(c.title)}`);
9710
+ if (c.detail && c.level !== "ok") lines.push(...serverText(c.detail).split("\n").map((l) => ` ${dim(l)}`));
9711
+ if (c.fix && c.level !== "ok") lines.push(` ${dim(t("fix:", "arreglo:"))} ${serverText(c.fix)}`);
9069
9712
  }
9070
- lines.push(problems ? `Doctor found ${r.summary.error} error(s) and ${r.summary.warn} warning(s) in ${r.checks.length} checks.` : `${lavender("\u2713")} No issues found (${r.checks.length} checks).`);
9713
+ lines.push(problems ? t(`Doctor found ${r.summary.error} error(s) and ${r.summary.warn} warning(s) in ${r.checks.length} checks.`, `Doctor encontr\xF3 ${r.summary.error} error(es) y ${r.summary.warn} aviso(s) en ${r.checks.length} revisiones.`) : `${lavender("\u2713")} ${t(`No issues found (${r.checks.length} checks).`, `Sin problemas (${r.checks.length} revisiones).`)}`);
9071
9714
  process.stdout.write(lines.join("\n") + "\n");
9072
9715
  }
9073
9716
  if (problems) {
9074
- if (!globals.json) info(dim("Exit 1: problems found."));
9717
+ if (!globals.json) info(dim(t("Exit 1: problems found.", "Salida 1: hay problemas.")));
9075
9718
  return exitAfterFlush(EXIT.API);
9076
9719
  }
9077
9720
  }
@@ -9097,7 +9740,7 @@ async function hiddenPrompt(label) {
9097
9740
  if (ch === "") {
9098
9741
  stdin.setRawMode(false);
9099
9742
  process.stderr.write("\n");
9100
- return reject(usage("Cancelled."));
9743
+ return reject(usage(t("Cancelled.", "Cancelado.")));
9101
9744
  }
9102
9745
  if (ch === "\x7F") value = value.slice(0, -1);
9103
9746
  else value += ch;
@@ -9109,7 +9752,7 @@ async function hiddenPrompt(label) {
9109
9752
  async function readSecret(provider, fromEnv) {
9110
9753
  if (fromEnv) {
9111
9754
  const v = process.env[fromEnv];
9112
- if (!v) throw usage(`$${fromEnv} is empty or not set.`, `Example: export ${fromEnv}=\u2026 && ghosty credentials set ${provider} --from-env ${fromEnv}`);
9755
+ if (!v) throw usage(t(`$${fromEnv} is empty or not set.`, `$${fromEnv} est\xE1 vac\xEDa o no existe.`), `${t("Example", "Ejemplo")}: export ${fromEnv}=\u2026 && ghosty credentials set ${provider} --from-env ${fromEnv}`);
9113
9756
  return v;
9114
9757
  }
9115
9758
  if (!process.stdin.isTTY) {
@@ -9117,8 +9760,8 @@ async function readSecret(provider, fromEnv) {
9117
9760
  for await (const c of process.stdin) parts.push(c);
9118
9761
  return Buffer.concat(parts).toString("utf8").replace(/\r?\n$/, "");
9119
9762
  }
9120
- if (!canPrompt()) throw usage("No value to save.", `Pipe it (\u2026 | ghosty credentials set ${provider}) or use --from-env VAR.`);
9121
- return hiddenPrompt(`${provider} key (hidden): `);
9763
+ if (!canPrompt()) throw usage(t("No value to save.", "No hay valor que guardar."), t(`Pipe it (\u2026 | ghosty credentials set ${provider}) or use --from-env VAR.`, `P\xE1salo por tuber\xEDa (\u2026 | ghosty credentials set ${provider}) o usa --from-env VAR.`));
9764
+ return hiddenPrompt(t(`${provider} key (hidden): `, `Llave de ${provider} (oculta): `));
9122
9765
  }
9123
9766
  async function credentialsCmd2(sub, provider, v) {
9124
9767
  switch (sub ?? "ls") {
@@ -9129,21 +9772,21 @@ async function credentialsCmd2(sub, provider, v) {
9129
9772
  return out(r.credentials, () => table(rows, ["provider", "set", "updated"]));
9130
9773
  }
9131
9774
  case "set": {
9132
- if (!provider) throw usage("Missing provider.", "Example: ghosty credentials set elevenlabs --from-env ELEVENLABS_API_KEY (ghosty credentials ls lists them)");
9775
+ if (!provider) throw usage(t("Missing provider.", "Falta el proveedor."), t("Example: ghosty credentials set elevenlabs --from-env ELEVENLABS_API_KEY (ghosty credentials ls lists them)", "Ejemplo: ghosty credentials set elevenlabs --from-env ELEVENLABS_API_KEY (ghosty credentials ls los lista)"));
9133
9776
  const key = (await readSecret(provider, v["from-env"])).trim();
9134
- if (!key) throw usage("Empty key: nothing saved.");
9777
+ if (!key) throw usage(t("Empty key: nothing saved.", "Llave vac\xEDa: no se guard\xF3 nada."));
9135
9778
  const r = await api(`/api/v2/me/credentials/${encodeURIComponent(provider)}`, { method: "PUT", body: { key } });
9136
- return out(r, () => `${lavender("\u2713")} ${provider} saved ${dim(`(\u2026${r.lastFour ?? ""}). Agents with a machine pick it up on their next turn.`)}`);
9779
+ return out(r, () => `${lavender("\u2713")} ${provider} ${t("saved", "guardada")} ${dim(t(`(\u2026${r.lastFour ?? ""}). Agents with a machine pick it up on their next turn.`, `(\u2026${r.lastFour ?? ""}). Los agentes con m\xE1quina la toman en su siguiente turno.`))}`);
9137
9780
  }
9138
9781
  case "rm":
9139
9782
  case "remove": {
9140
- if (!provider) throw usage("Missing provider.", "Example: ghosty credentials rm elevenlabs");
9141
- await confirm(`Remove your ${provider} key? Agents using it stop working until you set another.`, { yes: v.yes, retry: `ghosty credentials rm ${provider}` });
9783
+ if (!provider) throw usage(t("Missing provider.", "Falta el proveedor."), t("Example", "Ejemplo") + ": ghosty credentials rm elevenlabs");
9784
+ await confirm(t(`Remove your ${provider} key? Agents using it stop working until you set another.`, `\xBFQuitar tu llave de ${provider}? Los agentes que la usan dejan de funcionar hasta que pongas otra.`), { yes: v.yes, retry: `ghosty credentials rm ${provider}` });
9142
9785
  const r = await api(`/api/v2/me/credentials/${encodeURIComponent(provider)}`, { method: "DELETE" });
9143
- return out(r, () => `${lavender("\u2713")} ${provider} removed.`);
9786
+ return out(r, () => `${lavender("\u2713")} ${provider} ${t("removed.", "quitada.")}`);
9144
9787
  }
9145
9788
  default:
9146
- throw usage(`Unknown credentials subcommand: ${sub}.`, "Try: ghosty credentials ls | set | rm (ghosty credentials --help)");
9789
+ throw usage(t(`Unknown credentials subcommand: ${sub}.`, `Subcomando de credentials desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty credentials ls | set | rm (ghosty credentials --help)");
9147
9790
  }
9148
9791
  }
9149
9792
 
@@ -9189,12 +9832,12 @@ var digits = (s) => s.replace(/\D/g, "");
9189
9832
  function find(nums, ref) {
9190
9833
  const d = digits(ref);
9191
9834
  const hit = nums.find((n) => n.id === ref) ?? nums.find((n) => n.label.toLowerCase() === ref.toLowerCase()) ?? (d.length >= 6 ? nums.find((n) => digits(n.label).endsWith(d)) : void 0);
9192
- if (!hit) throw usage(`No WhatsApp number "${ref}" for this agent.`, nums.length ? `Available: ${nums.map((n) => n.label).join(", ")}` : "Pair a number first in the web (the agent's Channels page).");
9835
+ if (!hit) throw usage(t(`No WhatsApp number "${ref}" for this agent.`, `Este agente no tiene el n\xFAmero de WhatsApp "${ref}".`), nums.length ? `${t("Available", "Disponibles")}: ${nums.map((n) => n.label).join(", ")}` : t("Pair a number first in the web (the agent's Channels page).", "Primero parea un n\xFAmero en la web (p\xE1gina Canales del agente)."));
9193
9836
  return hit;
9194
9837
  }
9195
9838
  async function channelsCmd(kind, sub, id, ref, v) {
9196
- if (kind !== "whatsapp" && kind !== "wa") throw usage(`Unknown channel: ${kind ?? "(none)"}.`, "Try: ghosty channels whatsapp ls <agent-id>");
9197
- if (!id) throw usage("Missing agent id.", "Example: ghosty channels whatsapp ls <agent-id>");
9839
+ if (kind !== "whatsapp" && kind !== "wa") throw usage(t(`Unknown channel: ${kind ?? "(none)"}.`, `Canal desconocido: ${kind ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty channels whatsapp ls <agent-id>");
9840
+ if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels whatsapp ls <agent-id>");
9198
9841
  if (sub === "link" || sub === "unlink") return linkCmd(sub, id, v);
9199
9842
  if (sub === "history") return historyCmd(id, v);
9200
9843
  if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd(sub, id, v);
@@ -9203,25 +9846,25 @@ async function channelsCmd(kind, sub, id, ref, v) {
9203
9846
  const listing = !sub || sub === "ls" || sub === "list";
9204
9847
  const cur = await api(listing ? `${path}?formmy=1` : path);
9205
9848
  if (listing) {
9206
- const fwd = (n2) => !n2.formmy ? dim("\u2014") : "error" in n2.formmy ? dim("?") : n2.formmy.forwards ? "yes" : lavender("NO");
9849
+ const fwd = (n2) => !n2.formmy ? dim("\u2014") : "error" in n2.formmy ? dim("?") : n2.formmy.forwards ? t("yes", "s\xED") : lavender("NO");
9207
9850
  const rows = cur.numbers.map((n2) => ({
9208
9851
  number: n2.phoneNumber ? `+${n2.phoneNumber}${n2.label !== n2.phoneNumber && n2.label !== n2.id ? ` ${n2.label}` : ""}` : n2.label,
9209
- answers: n2.mine ? lavender("this agent") : n2.recordsOnly ? "this agent (records only)" : n2.takenByOther ? "another agent" : dim("nobody (silent)"),
9852
+ answers: n2.mine ? lavender(t("this agent", "este agente")) : n2.recordsOnly ? t("this agent (records only)", "este agente (s\xF3lo registra)") : n2.takenByOther ? t("another agent", "otro agente") : dim(t("nobody (silent)", "nadie (en silencio)")),
9210
9853
  "formmy forwards": fwd(n2),
9211
9854
  id: n2.id
9212
9855
  }));
9213
- return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "id"]) : dim(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`));
9856
+ return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "id"]) : dim(t(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`, `Todav\xEDa no hay n\xFAmeros de WhatsApp Business. Parea uno en ${cur.salesUrl}`)));
9214
9857
  }
9215
- if (sub !== "enable" && sub !== "disable") throw usage(`Unknown subcommand: ${sub}.`, "Try: ghosty channels whatsapp ls | enable | disable | link | unlink | history \xB7 ghosty channels wa import | status | pause | groups");
9216
- if (!ref) throw usage("Missing number.", `Example: ghosty channels whatsapp ${sub} ${id} +52 55 1234 5678`);
9858
+ if (sub !== "enable" && sub !== "disable") throw usage(t(`Unknown subcommand: ${sub}.`, `Subcomando desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty channels whatsapp ls | enable | disable | link | unlink | history \xB7 ghosty channels wa import | status | pause | groups");
9859
+ if (!ref) throw usage(t("Missing number.", "Falta el n\xFAmero."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} ${id} +52 55 1234 5678`);
9217
9860
  const n = find(cur.numbers, ref);
9218
9861
  const on = sub === "enable";
9219
- if (on === n.mine) return out({ changed: 0, number: n.label, answers: on }, () => dim(`Nothing to do: ${n.label} is already ${on ? "answered by this agent" : "not answered by it"}.`));
9220
- const plan = on ? `+ ${n.label}: this agent starts answering its customers${n.takenByOther ? " (takes it from the agent answering today)" : ""}` : `- ${n.label}: this agent stops answering (messages are still logged)`;
9862
+ if (on === n.mine) return out({ changed: 0, number: n.label, answers: on }, () => dim(t(`Nothing to do: ${n.label} is already ${on ? "answered by this agent" : "not answered by it"}.`, `Nada que hacer: ${n.label} ya ${on ? "lo contesta este agente" : "no lo contesta este agente"}.`)));
9863
+ const plan = on ? `+ ${n.label}: ${t(`this agent starts answering its customers${n.takenByOther ? " (takes it from the agent answering today)" : ""}`, `este agente empieza a contestarles a sus clientes${n.takenByOther ? " (se lo quita al agente que contesta hoy)" : ""}`)}` : `- ${n.label}: ${t("this agent stops answering (messages are still logged)", "este agente deja de contestar (los mensajes se siguen registrando)")}`;
9221
9864
  if (v["dry-run"]) return out({ dryRun: true, plan: [plan] }, () => `${plan}
9222
- ${dim("Dry run: nothing changed.")}`);
9865
+ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
9223
9866
  if (on) await confirm(`${bold(plan)}
9224
- Real customers will get answers from this agent.`, { yes: v.yes, expect: digits(n.label).slice(-4) || n.id, retry: `ghosty channels whatsapp enable ${id} ${ref}` });
9867
+ ${t("Real customers will get answers from this agent.", "Clientes reales recibir\xE1n respuestas de este agente.")}`, { yes: v.yes, expect: digits(n.label).slice(-4) || n.id, retry: `ghosty channels whatsapp enable ${id} ${ref}` });
9225
9868
  const wanted = cur.numbers.filter((x) => x.id === n.id ? on : x.mine).map((x) => x.id);
9226
9869
  const r = await api(path, { method: "PUT", body: { numbers: wanted } });
9227
9870
  return out(r, () => `${lavender("\u2713")} ${plan.slice(2)}`);
@@ -9231,118 +9874,121 @@ function findGroup(groups, ref) {
9231
9874
  if (byJid) return byJid;
9232
9875
  const byName = groups.filter((g) => g.name.toLowerCase() === ref.toLowerCase());
9233
9876
  if (byName.length === 1) return byName[0];
9234
- if (byName.length > 1) throw usage(`${byName.length} groups are called "${ref}".`, `Use its jid: ${byName.map((g) => g.jid).join(", ")}`);
9235
- throw usage(`No group "${ref}" for this agent.`, groups.length ? `Groups: ${groups.map((g) => g.name).join(", ")}` : "Is its personal WhatsApp paired and in some group? See the agent's Channels page.");
9877
+ if (byName.length > 1) throw usage(t(`${byName.length} groups are called "${ref}".`, `${byName.length} grupos se llaman "${ref}".`), `${t("Use its jid", "Usa su jid")}: ${byName.map((g) => g.jid).join(", ")}`);
9878
+ throw usage(t(`No group "${ref}" for this agent.`, `Este agente no tiene el grupo "${ref}".`), groups.length ? `${t("Groups", "Grupos")}: ${groups.map((g) => g.name).join(", ")}` : t("Is its personal WhatsApp paired and in some group? See the agent's Channels page.", "\xBFSu WhatsApp personal est\xE1 pareado y en alg\xFAn grupo? Revisa la p\xE1gina Canales del agente."));
9236
9879
  }
9237
9880
  async function groupsCmd(args, v) {
9238
9881
  const [sub, id, ...rest] = args;
9239
9882
  const ex = 'ghosty channels wa groups ls <agent-id> | set <agent-id> <group> [--role equipo|cliente] [--on|--off] | create <agent-id> "Name"';
9240
- if (!id) throw usage("Missing agent id.", `Example: ${ex}`);
9883
+ if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), `${t("Example", "Ejemplo")}: ${ex}`);
9241
9884
  const path = `/api/v2/agents/${enc7(id)}/wa/groups`;
9242
- if (v.role !== void 0 && v.role !== "equipo" && v.role !== "cliente") throw usage("--role takes equipo or cliente.");
9885
+ if (v.role !== void 0 && v.role !== "equipo" && v.role !== "cliente") throw usage(t("--role takes equipo or cliente.", "--role acepta equipo o cliente."));
9243
9886
  if (!sub || sub === "ls" || sub === "list") {
9244
9887
  const r2 = await api(`${path}${v.cached ? "?live=0" : ""}`);
9245
9888
  const rows = r2.groups.map((g2) => ({ name: g2.name, on: g2.enabled ? lavender("on") : dim("off"), role: g2.role, jid: g2.jid }));
9246
- return out(r2, () => rows.length ? table(rows, ["name", "on", "role", "jid"]) : dim('No groups. Pair its personal WhatsApp in the web, or: ghosty channels wa groups create <agent-id> "Name"'));
9889
+ return out(r2, () => rows.length ? table(rows, ["name", "on", "role", "jid"]) : dim(t('No groups. Pair its personal WhatsApp in the web, or: ghosty channels wa groups create <agent-id> "Name"', 'Sin grupos. Parea su WhatsApp personal en la web, o: ghosty channels wa groups create <agent-id> "Nombre"')));
9247
9890
  }
9248
9891
  if (sub === "create") {
9249
9892
  const name = rest.join(" ").trim();
9250
- if (!name) throw usage("Missing group name.", `Example: ghosty channels wa groups create ${id} "Equipo ventas" --role equipo`);
9893
+ if (!name) throw usage(t("Missing group name.", "Falta el nombre del grupo."), t("Example", "Ejemplo") + `: ghosty channels wa groups create ${id} "Equipo ventas" --role equipo`);
9251
9894
  const r2 = await api(path, { method: "POST", body: { create: name, role: v.role ?? "equipo" } });
9252
- return out(r2, () => `${lavender("\u2713")} Created ${bold(name)} (${v.role ?? "equipo"}, on)
9253
- invite: ${r2.inviteUrl}`);
9895
+ return out(r2, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${bold(name)} (${v.role ?? "equipo"}, on)
9896
+ ${t("invite", "invitaci\xF3n")}: ${r2.inviteUrl}`);
9254
9897
  }
9255
- if (sub !== "set") throw usage(`Unknown groups subcommand: ${sub}.`, ex);
9898
+ if (sub !== "set") throw usage(t(`Unknown groups subcommand: ${sub}.`, `Subcomando de groups desconocido: ${sub}.`), ex);
9256
9899
  const ref = rest.join(" ").trim();
9257
- if (!ref) throw usage("Missing group (name or jid).", `Example: ghosty channels wa groups set ${id} "Pia-cotizadora" --role equipo --on`);
9258
- if (v.on && v.off) throw usage("Use --on or --off, not both.");
9900
+ if (!ref) throw usage(t("Missing group (name or jid).", "Falta el grupo (nombre o jid)."), t("Example", "Ejemplo") + `: ghosty channels wa groups set ${id} "Pia-cotizadora" --role equipo --on`);
9901
+ if (v.on && v.off) throw usage(t("Use --on or --off, not both.", "Usa --on o --off, no los dos."));
9259
9902
  const cur = await api(`${path}?live=0`);
9260
9903
  const g = findGroup(cur.groups, ref);
9261
9904
  const enabled = v.on ? true : v.off ? false : g.enabled;
9262
9905
  const role = v.role ?? g.role;
9263
9906
  const changes = [...enabled !== g.enabled ? [`${g.enabled ? "on" : "off"}\u2192${enabled ? "on" : "off"}`] : [], ...role !== g.role ? [`${g.role}\u2192${role}`] : []];
9264
- if (!changes.length) return out({ changed: 0, group: g }, () => dim(`Nothing to do: ${g.name} is already ${g.enabled ? "on" : "off"} as ${g.role}.`));
9907
+ if (!changes.length) return out({ changed: 0, group: g }, () => dim(t(`Nothing to do: ${g.name} is already ${g.enabled ? "on" : "off"} as ${g.role}.`, `Nada que hacer: ${g.name} ya est\xE1 ${g.enabled ? "encendido" : "apagado"} como ${g.role}.`)));
9265
9908
  const plan = `~ ${g.name}: ${changes.join(", ")}`;
9266
9909
  if (v["dry-run"]) return out({ dryRun: true, plan: [plan] }, () => `${plan}
9267
- ${dim("Dry run: nothing changed.")}`);
9910
+ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
9268
9911
  if (enabled && !g.enabled) await confirm(`${bold(plan)}
9269
- The agent will start answering in this group as ${role}.`, { yes: v.yes, retry: `ghosty channels wa groups set ${id} "${ref}"${v.role ? ` --role ${v.role}` : ""} --on` });
9912
+ ${t(`The agent will start answering in this group as ${role}.`, `El agente empezar\xE1 a contestar en este grupo como ${role}.`)}`, { yes: v.yes, retry: `ghosty channels wa groups set ${id} "${ref}"${v.role ? ` --role ${v.role}` : ""} --on` });
9270
9913
  const r = await api(path, { method: "POST", body: { jid: g.jid, enabled, role } });
9271
9914
  const now = r.groups.find((x) => x.jid === g.jid);
9272
- return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${g.name}: ${now?.enabled ? "on" : "off"} as ${now?.role ?? role}`);
9915
+ return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${g.name}: ${now?.enabled ? "on" : "off"} ${t("as", "como")} ${now?.role ?? role}`);
9273
9916
  }
9274
9917
  async function waCmd(sub, id, v) {
9275
- if (!id) throw usage("Missing agent id.", `Example: ghosty channels wa ${sub} <agent-id>`);
9918
+ if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + `: ghosty channels wa ${sub} <agent-id>`);
9276
9919
  const base = `/api/v2/agents/${enc7(id)}/wa`;
9277
9920
  if (sub === "status") {
9278
9921
  const s = await api(base);
9279
- return out(s, () => `${s.status === "connected" ? lavender("\u25CF connected") : dim(`\u25CB ${s.status}`)} ${s.phone ? `+${s.phone}` : dim("(no number)")}${s.live ? "" : dim(" \xB7 no socket in gs")}${s.hasCreds ? "" : dim(" \xB7 no saved session")}${s.reason ? `
9280
- ${dim(s.reason)}` : ""}`);
9922
+ return out(s, () => `${s.status === "connected" ? lavender(t("\u25CF connected", "\u25CF conectado")) : dim(`\u25CB ${s.status}`)} ${s.phone ? `+${s.phone}` : dim(t("(no number)", "(sin n\xFAmero)"))}${s.live ? "" : dim(t(" \xB7 no socket in gs", " \xB7 sin socket en gs"))}${s.hasCreds ? "" : dim(t(" \xB7 no saved session", " \xB7 sin sesi\xF3n guardada"))}${s.reason ? `
9923
+ ${dim(serverText(s.reason))}` : ""}`);
9281
9924
  }
9282
9925
  if (sub === "pair") {
9283
9926
  const phone = (v.phone ?? "").replace(/\D/g, "");
9284
- if (phone.length < 10) throw usage("Missing --phone (10-15 digits, with country code).", `Example: ghosty channels wa pair ${id} --phone 5215512345678`);
9927
+ if (phone.length < 10) throw usage(t("Missing --phone (10-15 digits, with country code).", "Falta --phone (10-15 d\xEDgitos, con lada de pa\xEDs)."), t("Example", "Ejemplo") + `: ghosty channels wa pair ${id} --phone 5215512345678`);
9285
9928
  await api(base, { method: "POST", body: { phone } });
9286
9929
  let s = { status: "connecting", phone: null, live: false, hasCreds: false };
9287
9930
  for (let i = 0; i < 15 && !s.pairingCode && s.status !== "connected"; i++) {
9288
9931
  await new Promise((r2) => setTimeout(r2, 1e3));
9289
9932
  s = await api(base);
9290
9933
  }
9291
- return out(s, () => s.status === "connected" ? `${lavender("\u2713")} Already connected (+${s.phone}).` : s.pairingCode ? `Code: ${bold(s.pairingCode)}
9292
- ${dim("On the phone: WhatsApp \u2192 Linked devices \u2192 Link with phone number \u2192 type it. Then: ghosty channels wa status " + id)}` : dim(`No code yet (status: ${s.status}${s.reason ? `, ${s.reason}` : ""}). Try again in a minute.`));
9934
+ return out(s, () => s.status === "connected" ? `${lavender("\u2713")} ${t("Already connected", "Ya est\xE1 conectado")} (+${s.phone}).` : s.pairingCode ? `${t("Code", "C\xF3digo")}: ${bold(s.pairingCode)}
9935
+ ${dim(t("On the phone: WhatsApp \u2192 Linked devices \u2192 Link with phone number \u2192 type it. Then: ghosty channels wa status ", "En el tel\xE9fono: WhatsApp \u2192 Dispositivos vinculados \u2192 Vincular con n\xFAmero de tel\xE9fono \u2192 escr\xEDbelo. Luego: ghosty channels wa status ") + id)}` : dim(t(`No code yet (status: ${s.status}${s.reason ? `, ${serverText(s.reason)}` : ""}). Try again in a minute.`, `Todav\xEDa no hay c\xF3digo (estado: ${s.status}${s.reason ? `, ${s.reason}` : ""}). Intenta de nuevo en un minuto.`)));
9293
9936
  }
9294
9937
  if (sub === "pause") {
9295
9938
  await api(base, { method: "DELETE" });
9296
- return out({ paused: true }, () => `${lavender("\u2713")} Paused. The session is kept: ${dim(`ghosty channels wa import \u2026 or the Channels page reconnects it`)}`);
9939
+ return out({ paused: true }, () => `${lavender("\u2713")} ${t("Paused. The session is kept:", "En pausa. La sesi\xF3n se conserva:")} ${dim(t("ghosty channels wa import \u2026 or the Channels page reconnects it", "ghosty channels wa import \u2026 o la p\xE1gina Canales la reconecta"))}`);
9297
9940
  }
9298
9941
  const file = v["from-file"] ?? v["from-dir"];
9299
- if (!file || v["from-file"] && v["from-dir"]) throw usage("Use --from-file F (EasyBits JSON) or --from-dir DIR (Baileys multi-file folder).", `Example: ghosty channels wa import ${id} --from-file creds.json --own-number no --dry-run`);
9942
+ if (!file || v["from-file"] && v["from-dir"]) throw usage(t("Use --from-file F (EasyBits JSON) or --from-dir DIR (Baileys multi-file folder).", "Usa --from-file F (JSON de EasyBits) o --from-dir DIR (carpeta multi-archivo de Baileys)."), t("Example", "Ejemplo") + `: ghosty channels wa import ${id} --from-file creds.json --own-number no --dry-run`);
9300
9943
  const own = v["own-number"];
9301
- if (own !== "yes" && own !== "no") throw usage("Missing --own-number yes|no.", "yes = the number belongs to the agent \xB7 no = it is a person's number (only their chat with themselves is the owner's)");
9944
+ if (own !== "yes" && own !== "no") throw usage(t("Missing --own-number yes|no.", "Falta --own-number yes|no."), t("yes = the number belongs to the agent \xB7 no = it is a person's number (only their chat with themselves is the owner's)", "yes = el n\xFAmero es del agente \xB7 no = es el n\xFAmero de una persona (s\xF3lo su chat consigo misma es \xABdel due\xF1o\xBB)"));
9302
9945
  let data;
9303
9946
  if (v["from-dir"]) {
9304
9947
  const m = await readMultiFileAuth(v["from-dir"]);
9305
- info(dim(`${v["from-dir"]}: ${Object.entries(m.counts).map(([t, n]) => `${t} ${n}`).join(" \xB7 ") || "no keys"}${m.skipped.length ? ` \xB7 skipped ${m.skipped.length} unknown file(s)` : ""}`));
9948
+ info(dim(`${v["from-dir"]}: ${Object.entries(m.counts).map(([k, n]) => `${k} ${n}`).join(" \xB7 ") || t("no keys", "sin llaves")}${m.skipped.length ? t(` \xB7 skipped ${m.skipped.length} unknown file(s)`, ` \xB7 se omitieron ${m.skipped.length} archivo(s) desconocido(s)`) : ""}`));
9306
9949
  data = { authCreds: m.authCreds, authKeys: m.authKeys };
9307
9950
  } else data = JSON.parse(await readFile8(file, "utf8"));
9308
9951
  const body = { authCreds: data.authCreds, authKeys: data.authKeys, phone: data.phone, groups: data.groups, ownNumber: own === "yes", replace: !!v.replace };
9309
9952
  const plan = await api(`${base}/import`, { method: "POST", body: { ...body, dryRun: true } });
9310
- if (v["dry-run"]) return out(plan, () => `${plan.plan.join("\n")}
9311
- ${dim("Dry run: nothing changed.")}`);
9312
- await confirm(`${bold(plan.plan.join("\n"))}
9313
- This number starts living in gs. Turn off its EasyBits socket first, WITHOUT logout (two sockets knock each other off).`, { yes: v.yes, expect: plan.phone.slice(-4), retry: `ghosty channels wa import ${id} ${v["from-dir"] ? "--from-dir" : "--from-file"} ${file} --own-number ${own}${v.replace ? " --replace" : ""}` });
9953
+ if (v["dry-run"]) return out(plan, () => `${plan.plan.map((x) => serverText(x)).join("\n")}
9954
+ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
9955
+ await confirm(`${bold(plan.plan.map((x) => serverText(x)).join("\n"))}
9956
+ ${t("This number starts living in gs. Turn off its EasyBits socket first, WITHOUT logout (two sockets knock each other off).", "Este n\xFAmero empieza a vivir en gs. Apaga antes su socket de EasyBits, SIN logout (dos sockets se tumban entre s\xED).")}`, { yes: v.yes, expect: plan.phone.slice(-4), retry: `ghosty channels wa import ${id} ${v["from-dir"] ? "--from-dir" : "--from-file"} ${file} --own-number ${own}${v.replace ? " --replace" : ""}` });
9314
9957
  const r = await api(`${base}/import`, { method: "POST", body: { ...body, connect: !v["no-connect"] } });
9315
- return out(r, () => `${lavender("\u2713")} +${r.phone} imported \xB7 status: ${r.status === "connected" ? lavender("connected") : r.status}${r.reason ? dim(` (${r.reason})`) : ""}
9316
- ${dim(`Next: ghosty channels wa groups ls ${id} \xB7 delete ${file}: it holds the account's keys`)}`);
9958
+ return out(r, () => `${lavender("\u2713")} +${r.phone} ${t("imported \xB7 status", "importado \xB7 estado")}: ${r.status === "connected" ? lavender(t("connected", "conectado")) : r.status}${r.reason ? dim(` (${serverText(r.reason)})`) : ""}
9959
+ ${dim(t(`Next: ghosty channels wa groups ls ${id} \xB7 delete ${file}: it holds the account's keys`, `Sigue: ghosty channels wa groups ls ${id} \xB7 borra ${file}: guarda las llaves de la cuenta`))}`);
9317
9960
  }
9318
9961
  async function linkCmd(sub, id, v) {
9319
- if (!id || !v.integration) throw usage("Missing agent id or --integration.", `Example: ghosty channels whatsapp ${sub} <agent-id> --integration <formmy-integration-id> --answer off --dry-run`);
9320
- if (sub === "link" && v.answer !== "on" && v.answer !== "off") throw usage("Missing --answer on|off.", "on = this agent answers real customers \xB7 off = it only records (add --forward so gs still sees the conversations)");
9962
+ if (!id || !v.integration) throw usage(t("Missing agent id or --integration.", "Falta el id del agente o --integration."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} <agent-id> --integration <formmy-integration-id> --answer off --dry-run`);
9963
+ if (sub === "link" && v.answer !== "on" && v.answer !== "off") throw usage(t("Missing --answer on|off.", "Falta --answer on|off."), t("on = this agent answers real customers \xB7 off = it only records (add --forward so gs still sees the conversations)", "on = este agente contesta a clientes reales \xB7 off = s\xF3lo registra (agrega --forward para que gs siga viendo las conversaciones)"));
9321
9964
  const path = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
9322
9965
  const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {} } };
9323
9966
  const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
9324
- const text = `${plan.plan.join("\n")}
9325
- ${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (as ${plan.formmy.by})`)}`;
9967
+ const text = `${plan.plan.map((x) => serverText(x)).join("\n")}
9968
+ ${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (${t("as", "como")} ${plan.formmy.by})`)}`;
9326
9969
  if (v["dry-run"]) return out(plan, () => `${text}
9327
- ${dim("Dry run: nothing changed.")}`);
9970
+ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
9328
9971
  const digits4 = (plan.number.phoneNumberId ?? v.integration).slice(-4);
9329
9972
  await confirm(`${bold(text)}
9330
- ${sub === "link" ? "From now on this number's messages reach this agent in gs." : "This number goes back to where it was before."}`, { yes: v.yes, expect: digits4, retry: `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}` : ""}` });
9973
+ ${sub === "link" ? t("From now on this number's messages reach this agent in gs.", "Desde ahora los mensajes de este n\xFAmero le llegan a este agente en gs.") : t("This number goes back to where it was before.", "Este n\xFAmero regresa a donde estaba antes.")}`, { yes: v.yes, expect: digits4, retry: `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}` : ""}` });
9331
9974
  const r = await api(path, { method: "POST", body });
9332
- return out(r, () => `${lavender("\u2713")} ${sub === "link" ? `Linked (link ${r.linkId}). Check: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m` : "Unlinked: Formmy points where it did before."}`);
9975
+ return out(r, () => `${lavender("\u2713")} ${sub === "link" ? t(`Linked (link ${r.linkId}). Check: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m`, `Enlazado (enlace ${r.linkId}). Revisa: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m`) : t("Unlinked: Formmy points where it did before.", "Desenlazado: Formmy apunta a donde apuntaba antes.")}`);
9333
9976
  }
9334
9977
  async function historyCmd(id, v) {
9335
- if (!v.integration) throw usage("Missing --integration.", `Example: ghosty channels whatsapp history ${id} --integration <formmy-integration-id> --days 7 --dry-run`);
9978
+ if (!v.integration) throw usage(t("Missing --integration.", "Falta --integration."), t("Example", "Ejemplo") + `: ghosty channels whatsapp history ${id} --integration <formmy-integration-id> --days 7 --dry-run`);
9336
9979
  const days = Number(v.days ?? 7);
9337
- if (!Number.isInteger(days) || days < 1 || days > 30) throw usage("--days takes 1 to 30.");
9980
+ if (!Number.isInteger(days) || days < 1 || days > 30) throw usage(t("--days takes 1 to 30.", "--days acepta de 1 a 30."));
9338
9981
  const path = `/api/v2/agents/${enc7(id)}/whatsapp/history`;
9339
9982
  const c = await api(path, { method: "POST", body: { integrationId: v.integration, days, dryRun: true } });
9340
- const plan = `${c.conversations} conversation(s), ${c.messages} message(s), ${c.orders} order(s) from the last ${days} day(s)${c.phoneNumber ? ` of +${c.phoneNumber}` : ""} \u2192 board ${c.board}`;
9983
+ const plan = t(
9984
+ `${c.conversations} conversation(s), ${c.messages} message(s), ${c.orders} order(s) from the last ${days} day(s)${c.phoneNumber ? ` of +${c.phoneNumber}` : ""} \u2192 board ${c.board}`,
9985
+ `${c.conversations} conversaci\xF3n(es), ${c.messages} mensaje(s), ${c.orders} pedido(s) de los \xFAltimos ${days} d\xEDa(s)${c.phoneNumber ? ` de +${c.phoneNumber}` : ""} \u2192 tablero ${c.board}`
9986
+ );
9341
9987
  if (v["dry-run"]) return out(c, () => `${plan}
9342
- ${dim("Dry run: nothing imported. Re-running later never duplicates.")}`);
9343
- if (!c.conversations) return out({ ...c, imported: 0 }, () => dim(`Nothing to import: ${plan}.`));
9988
+ ${dim(t("Dry run: nothing imported. Re-running later never duplicates.", "Simulacro: no se import\xF3 nada. Correrlo otra vez nunca duplica."))}`);
9989
+ if (!c.conversations) return out({ ...c, imported: 0 }, () => dim(t(`Nothing to import: ${plan}.`, `Nada que importar: ${plan}.`)));
9344
9990
  await confirm(`${bold(plan)}
9345
- Cards appear on the team's board (no "new lead" alerts).`, { yes: v.yes, retry: `ghosty channels whatsapp history ${id} --integration ${v.integration} --days ${days}` });
9991
+ ${t(`Cards appear on the team's board (no "new lead" alerts).`, "Las tarjetas aparecen en el tablero del equipo (sin avisos de \xABlead nuevo\xBB).")}`, { yes: v.yes, retry: `ghosty channels whatsapp history ${id} --integration ${v.integration} --days ${days}` });
9346
9992
  const sum = { newCards: 0, updatedCards: 0, messages: 0, orders: 0, suppressed: 0 };
9347
9993
  let cursor = 0;
9348
9994
  let done = 0;
@@ -9350,47 +9996,47 @@ Cards appear on the team's board (no "new lead" alerts).`, { yes: v.yes, retry:
9350
9996
  const p = await api(path, { method: "POST", body: { integrationId: v.integration, days, cursor } });
9351
9997
  for (const k of Object.keys(sum)) sum[k] += p[k];
9352
9998
  done += p.page;
9353
- if (!globals.json) info(dim(` ${done}/${p.total} conversations`));
9999
+ if (!globals.json) info(dim(` ${done}/${p.total} ${t("conversations", "conversaciones")}`));
9354
10000
  cursor = p.nextCursor;
9355
10001
  }
9356
- out({ ...sum, conversations: done }, () => `${lavender("\u2713")} ${sum.newCards} new card(s), ${sum.updatedCards} updated, ${sum.messages} message(s), ${sum.orders} order(s)${sum.suppressed ? dim(` \xB7 ${sum.suppressed} bot draft(s) the customer never saw, skipped`) : ""}`);
10002
+ out({ ...sum, conversations: done }, () => `${lavender("\u2713")} ${t(`${sum.newCards} new card(s), ${sum.updatedCards} updated, ${sum.messages} message(s), ${sum.orders} order(s)`, `${sum.newCards} tarjeta(s) nueva(s), ${sum.updatedCards} actualizada(s), ${sum.messages} mensaje(s), ${sum.orders} pedido(s)`)}${sum.suppressed ? dim(t(` \xB7 ${sum.suppressed} bot draft(s) the customer never saw, skipped`, ` \xB7 se omitieron ${sum.suppressed} borrador(es) del bot que el cliente nunca vio`)) : ""}`);
9357
10003
  }
9358
10004
 
9359
10005
  // src/commands/board.ts
9360
- var WHO = { user: "customer", assistant: "agent", operator: "operator" };
10006
+ var who = (role) => ({ user: t("customer", "cliente"), assistant: t("agent", "agente"), operator: t("operator", "operador") })[role] ?? role;
9361
10007
  async function boardLs(id, v) {
9362
10008
  const qs = new URLSearchParams({ ...v.q ? { q: v.q } : {}, ...v.limit ? { limit: v.limit } : {} });
9363
10009
  const r = await api(`/api/v2/agents/${encodeURIComponent(id)}/board/conversations?${qs}`);
9364
10010
  const rows = r.conversations.map((c) => ({ folio: c.folio ? `#${c.folio}` : "", name: c.name ?? "", phone: c.phone, column: c.column ?? "", msgs: c.messages, last: (c.lastActivity ?? "").slice(0, 16).replace("T", " ") }));
9365
10011
  out(r, () => rows.length ? `${table(rows, ["folio", "name", "phone", "column", "msgs", "last"])}
9366
- ${dim(`\xAB${r.board}\xBB \xB7 thread: ghosty board show ${id} <folio|phone>`)}` : dim(`No cards on \xAB${r.board}\xBB${v.q ? ` matching "${v.q}"` : ""}.`));
10012
+ ${dim(`\xAB${r.board}\xBB \xB7 ${t("thread", "hilo")}: ghosty board show ${id} <folio|phone>`)}` : dim(t(`No cards on \xAB${r.board}\xBB${v.q ? ` matching "${v.q}"` : ""}.`, `No hay tarjetas en \xAB${r.board}\xBB${v.q ? ` que coincidan con "${v.q}"` : ""}.`)));
9367
10013
  }
9368
10014
  async function boardShow(id, ref, v) {
9369
- if (!ref) throw usage("Missing conversation.", `Example: ghosty board show ${id} "#12" (folio, phone, session or id; list them with ghosty board ls ${id})`);
10015
+ if (!ref) throw usage(t("Missing conversation.", "Falta la conversaci\xF3n."), t(`Example: ghosty board show ${id} "#12" (folio, phone, session or id; list them with ghosty board ls ${id})`, `Ejemplo: ghosty board show ${id} "#12" (folio, tel\xE9fono, sesi\xF3n o id; l\xEDstalas con ghosty board ls ${id})`));
9370
10016
  const r = await api(`/api/v2/agents/${encodeURIComponent(id)}/board/conversations/${encodeURIComponent(ref)}${v.limit ? `?limit=${encodeURIComponent(v.limit)}` : ""}`);
9371
10017
  const c = r.conversation;
9372
- const head = `${bold(c.name ?? c.phone)} ${dim(`${c.folio ? `#${c.folio} \xB7 ` : ""}+${c.phone} \xB7 ${c.column ?? "no column"} \xB7 ${c.session}`)}`;
10018
+ const head = `${bold(c.name ?? c.phone)} ${dim(`${c.folio ? `#${c.folio} \xB7 ` : ""}+${c.phone} \xB7 ${c.column ?? t("no column", "sin columna")} \xB7 ${c.session}`)}`;
9373
10019
  const line2 = (m) => {
9374
10020
  const at = m.createdAt.slice(5, 16).replace("T", " ");
9375
10021
  const media = m.mediaType || m.mediaFilename ? `
9376
- ${dim(`\u{1F4CE} ${m.mediaType ?? "file"} ${m.mediaFilename ?? ""} \xB7 ${m.mediaCopied ? "copy in gs" : "no copy"}`)}` : "";
9377
- return `${dim(at)} ${lavender((WHO[m.role] ?? m.role).padEnd(8))} ${m.content.replace(/\n/g, "\n ")}${media}`;
10022
+ ${dim(`\u{1F4CE} ${m.mediaType ?? t("file", "archivo")} ${m.mediaFilename ?? ""} \xB7 ${m.mediaCopied ? t("copy in gs", "copia en gs") : t("no copy", "sin copia")}`)}` : "";
10023
+ return `${dim(at)} ${lavender(who(m.role).padEnd(8))} ${m.content.replace(/\n/g, "\n ")}${media}`;
9378
10024
  };
9379
10025
  out(r, () => [head, ...r.messages.map(line2)].join("\n"));
9380
10026
  }
9381
10027
  async function boardCmd(sub, id, v, ref) {
9382
10028
  if (sub === "ls" || sub === "list") {
9383
- if (!id) throw usage("Missing agent id.", "Example: ghosty board ls <agent-id> [--q text] [--limit 20]");
10029
+ if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty board ls <agent-id> [--q text] [--limit 20]");
9384
10030
  return boardLs(id, v);
9385
10031
  }
9386
10032
  if (sub === "show") {
9387
- if (!id) throw usage("Missing agent id.", 'Example: ghosty board show <agent-id> "#12"');
10033
+ if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ': ghosty board show <agent-id> "#12"');
9388
10034
  return boardShow(id, ref, v);
9389
10035
  }
9390
- if (sub !== "archive") throw usage(`Unknown board subcommand: ${sub ?? "(none)"}.`, "Try: ghosty board ls <agent-id> \xB7 ghosty board show <agent-id> <folio|phone> \xB7 ghosty board archive <agent-id> --integration prueba --dry-run");
9391
- if (!id) throw usage("Missing agent id.", "Example: ghosty board archive <agent-id> --integration prueba --dry-run");
10036
+ if (sub !== "archive") throw usage(t(`Unknown board subcommand: ${sub ?? "(none)"}.`, `Subcomando de board desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty board ls <agent-id> \xB7 ghosty board show <agent-id> <folio|phone> \xB7 ghosty board archive <agent-id> --integration prueba --dry-run");
10037
+ if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty board archive <agent-id> --integration prueba --dry-run");
9392
10038
  const filter = { ...v.integration ? { integration: v.integration } : {}, ...v.column ? { column: v.column } : {}, ...v.before ? { before: v.before } : {} };
9393
- if (!Object.keys(filter).length) throw usage("Add at least one filter: --integration, --column or --before.", "A filter-less archive would empty the board.");
10039
+ if (!Object.keys(filter).length) throw usage(t("Add at least one filter: --integration, --column or --before.", "Agrega al menos un filtro: --integration, --column o --before."), t("A filter-less archive would empty the board.", "Archivar sin filtro vaciar\xEDa el tablero."));
9394
10040
  const retry = `ghosty board archive ${id} ${Object.entries(filter).map(([k, x]) => `--${k} ${JSON.stringify(x)}`).join(" ")}`;
9395
10041
  if (!v["dry-run"]) requireYesIfHeadless(v.yes, retry);
9396
10042
  const path = `/api/v2/agents/${encodeURIComponent(id)}/board/archive`;
@@ -9398,12 +10044,12 @@ async function boardCmd(sub, id, v, ref) {
9398
10044
  const sample = () => table(plan.sample.map((c) => ({ name: c.name ?? "", phone: c.phone, column: c.estado_label ?? "" })), ["name", "phone", "column"]);
9399
10045
  if (v["dry-run"] || plan.matched === 0) {
9400
10046
  return out(plan, () => plan.matched ? `${sample()}
9401
- ${dim(`Would archive ${plan.matched} card(s) on \xAB${plan.board}\xBB. Dry run: nothing changed.`)}` : dim(`No cards match on \xAB${plan.board}\xBB.`));
10047
+ ${dim(t(`Would archive ${plan.matched} card(s) on \xAB${plan.board}\xBB. Dry run: nothing changed.`, `Se archivar\xEDan ${plan.matched} tarjeta(s) en \xAB${plan.board}\xBB. Simulacro: no se cambi\xF3 nada.`))}` : dim(t(`No cards match on \xAB${plan.board}\xBB.`, `Ninguna tarjeta coincide en \xAB${plan.board}\xBB.`)));
9402
10048
  }
9403
10049
  await confirm(`${sample()}
9404
- Archive ${plan.matched} card(s) on \xAB${plan.board}\xBB? (reversible: a new message brings a card back)`, { yes: v.yes, retry });
10050
+ ${t(`Archive ${plan.matched} card(s) on \xAB${plan.board}\xBB? (reversible: a new message brings a card back)`, `\xBFArchivar ${plan.matched} tarjeta(s) en \xAB${plan.board}\xBB? (reversible: un mensaje nuevo regresa la tarjeta)`)}`, { yes: v.yes, retry });
9405
10051
  const r = await api(path, { body: filter });
9406
- out(r, () => `${lavender("\u2713")} Archived ${r.archived} card(s) on \xAB${r.board}\xBB.`);
10052
+ out(r, () => `${lavender("\u2713")} ${t(`Archived ${r.archived} card(s) on \xAB${r.board}\xBB.`, `Se archivaron ${r.archived} tarjeta(s) en \xAB${r.board}\xBB.`)}`);
9407
10053
  }
9408
10054
 
9409
10055
  // src/commands/boards.ts
@@ -9416,31 +10062,31 @@ async function boardsCmd(sub, a1, a2, v, context) {
9416
10062
  switch (sub ?? "ls") {
9417
10063
  case "ls":
9418
10064
  case "list": {
9419
- if (!a1) throw usage("Missing workspace.", "Example: ghosty boards ls <workspace-slug>");
10065
+ if (!a1) throw usage(t("Missing workspace.", "Falta el espacio."), t("Example", "Ejemplo") + ": ghosty boards ls <workspace-slug>");
9420
10066
  const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`);
9421
- const rows = r.boards.map((b) => ({ name: b.main ? `${b.name} (main)` : b.name, cards: b.cards, agents: b.agents.map((x) => x.name ?? x.id).join(", "), columns: b.columns.length, id: b.id }));
10067
+ const rows = r.boards.map((b) => ({ name: b.main ? `${b.name} (${t("main", "principal")})` : b.name, cards: b.cards, agents: b.agents.map((x) => x.name ?? x.id).join(", "), columns: b.columns.length, id: b.id }));
9422
10068
  return out(r, () => table(rows, ["name", "cards", "agents", "columns", "id"]));
9423
10069
  }
9424
10070
  case "create": {
9425
- if (!a1 || !a2) throw usage("Missing workspace or name.", 'Example: ghosty boards create <workspace> "Siiqtec - Ixmiquilpan" --columns-from main');
10071
+ if (!a1 || !a2) throw usage(t("Missing workspace or name.", "Falta el espacio o el nombre."), t("Example", "Ejemplo") + ': ghosty boards create <workspace> "Siiqtec - Ixmiquilpan" --columns-from main');
9426
10072
  const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`, { method: "POST", body: { name: a2, ...v["columns-from"] ? { columnsFrom: v["columns-from"] } : {} } });
9427
- return out(r, () => `${lavender("\u2713")} Board \xAB${r.name}\xBB created${r.columnsFrom ? ` with the ${r.columns} column(s) of \xAB${r.columnsFrom}\xBB` : ""}. ${dim(`Send an agent's cards there: ghosty boards assign <agent-id> "${r.name}"`)}`);
10073
+ return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.name}\xBB created${r.columnsFrom ? ` with the ${r.columns} column(s) of \xAB${r.columnsFrom}\xBB` : ""}.`, `Tablero \xAB${r.name}\xBB creado${r.columnsFrom ? ` con las ${r.columns} columna(s) de \xAB${r.columnsFrom}\xBB` : ""}.`)} ${dim(`${t("Send an agent's cards there", "Manda ah\xED las tarjetas de un agente")}: ghosty boards assign <agent-id> "${r.name}"`)}`);
9428
10074
  }
9429
10075
  case "rm": {
9430
- if (!a1 || !a2) throw usage("Missing workspace or board.", 'Example: ghosty boards rm <workspace> "Prueba"');
10076
+ if (!a1 || !a2) throw usage(t("Missing workspace or board.", "Falta el espacio o el tablero."), t("Example", "Ejemplo") + ': ghosty boards rm <workspace> "Prueba"');
9431
10077
  requireYesIfHeadless(v.yes, `ghosty boards rm ${a1} ${JSON.stringify(a2)}`);
9432
- await confirm(`Delete the board \xAB${a2}\xBB of ${a1}? (only an empty one; its agents go back to the main board)`, { yes: v.yes, retry: `ghosty boards rm ${a1} ${JSON.stringify(a2)}` });
10078
+ await confirm(t(`Delete the board \xAB${a2}\xBB of ${a1}? (only an empty one; its agents go back to the main board)`, `\xBFBorrar el tablero \xAB${a2}\xBB de ${a1}? (s\xF3lo si est\xE1 vac\xEDo; sus agentes regresan al tablero principal)`), { yes: v.yes, retry: `ghosty boards rm ${a1} ${JSON.stringify(a2)}` });
9433
10079
  const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`, { method: "DELETE", body: { board: a2 } });
9434
- return out(r, () => `${lavender("\u2713")} Board \xAB${r.deleted}\xBB deleted.`);
10080
+ return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.deleted}\xBB deleted.`, `Tablero \xAB${r.deleted}\xBB borrado.`)}`);
9435
10081
  }
9436
10082
  case "assign": {
9437
- if (!a1 || !a2) throw usage("Missing agent or board.", 'Example: ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan" (or main)');
10083
+ if (!a1 || !a2) throw usage(t("Missing agent or board.", "Falta el agente o el tablero."), t("Example", "Ejemplo") + ': ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan" (' + t("or main", "o main") + ")");
9438
10084
  const r = await api(`/api/v2/agents/${enc8(a1)}`, { method: "PATCH", body: { board: a2 } });
9439
- return out(r, () => `${lavender("\u2713")} New cards of ${a1} go to \xAB${r.board}\xBB. ${dim(`Existing ones stay where they are: ghosty boards move-cards ${r.workspace} --from \u2026 --to "${r.board}" \u2026`)}`);
10085
+ return out(r, () => `${lavender("\u2713")} ${t(`New cards of ${a1} go to \xAB${r.board}\xBB.`, `Las tarjetas nuevas de ${a1} van a \xAB${r.board}\xBB.`)} ${dim(`${t("Existing ones stay where they are", "Las que ya existen se quedan donde est\xE1n")}: ghosty boards move-cards ${r.workspace} --from \u2026 --to "${r.board}" \u2026`)}`);
9440
10086
  }
9441
10087
  case "move-cards": {
9442
- if (!a1 || !v.from || !v.to) throw usage("Missing workspace, --from or --to.", 'Example: ghosty boards move-cards <workspace> --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run');
9443
- if (!v.integration && !v.phone && !v.all) throw usage("Add a filter: --integration ID, --phone N or --all.", "A filter-less move would empty the board.");
10088
+ if (!a1 || !v.from || !v.to) throw usage(t("Missing workspace, --from or --to.", "Falta el espacio, --from o --to."), t("Example", "Ejemplo") + ': ghosty boards move-cards <workspace> --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run');
10089
+ if (!v.integration && !v.phone && !v.all) throw usage(t("Add a filter: --integration ID, --phone N or --all.", "Agrega un filtro: --integration ID, --phone N o --all."), t("A filter-less move would empty the board.", "Mover sin filtro vaciar\xEDa el tablero."));
9444
10090
  const path = `/api/v2/workspaces/${enc8(a1)}/boards/move-cards`;
9445
10091
  const body = { from: v.from, to: v.to, ...v.integration ? { integration: v.integration } : {}, ...v.phone ? { phone: v.phone } : {}, ...v.all ? { all: true } : {} };
9446
10092
  const retry = `ghosty boards move-cards ${a1} --from ${JSON.stringify(v.from)} --to ${JSON.stringify(v.to)}${v.integration ? ` --integration ${v.integration}` : ""}${v.phone ? ` --phone ${v.phone}` : ""}${v.all ? " --all" : ""}`;
@@ -9448,19 +10094,268 @@ async function boardsCmd(sub, a1, a2, v, context) {
9448
10094
  const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
9449
10095
  const sample = table(plan.sample.map((c) => ({ folio: c.folio ? `#${c.folio}` : "", name: c.name ?? "", phone: c.phone })), ["folio", "name", "phone"]);
9450
10096
  const text = `${sample}
9451
- ${plan.moved} of ${plan.matched} card(s) \xAB${plan.from}\xBB \u2192 \xAB${plan.to}\xBB${plan.skipped.length ? dim(` \xB7 ${plan.skipped.length} already in \xAB${plan.to}\xBB, skipped`) : ""}`;
10097
+ ${t(`${plan.moved} of ${plan.matched} card(s)`, `${plan.moved} de ${plan.matched} tarjeta(s)`)} \xAB${plan.from}\xBB \u2192 \xAB${plan.to}\xBB${plan.skipped.length ? dim(t(` \xB7 ${plan.skipped.length} already in \xAB${plan.to}\xBB, skipped`, ` \xB7 ${plan.skipped.length} ya en \xAB${plan.to}\xBB, se omiten`)) : ""}`;
9452
10098
  if (v["dry-run"] || !plan.moved) return out(plan, () => `${text}
9453
- ${dim("Dry run: nothing changed.")}`);
10099
+ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
9454
10100
  await confirm(`${text}
9455
- Move them with their threads and orders? (they get a new folio there)`, { yes: v.yes, retry });
10101
+ ${t("Move them with their threads and orders? (they get a new folio there)", "\xBFMoverlas con sus hilos y pedidos? (all\xE1 reciben folio nuevo)")}`, { yes: v.yes, retry });
9456
10102
  const r = await api(path, { method: "POST", body });
9457
- return out(r, () => `${lavender("\u2713")} Moved ${r.moved} card(s) (${r.messages} message(s), ${r.orders} order(s)) to \xAB${r.to}\xBB.${r.skipped.length ? dim(` Skipped ${r.skipped.length}.`) : ""}`);
10103
+ return out(r, () => `${lavender("\u2713")} ${t(`Moved ${r.moved} card(s) (${r.messages} message(s), ${r.orders} order(s)) to \xAB${r.to}\xBB.`, `Se movieron ${r.moved} tarjeta(s) (${r.messages} mensaje(s), ${r.orders} pedido(s)) a \xAB${r.to}\xBB.`)}${r.skipped.length ? dim(t(` Skipped ${r.skipped.length}.`, ` Se omitieron ${r.skipped.length}.`)) : ""}`);
9458
10104
  }
9459
10105
  default:
9460
- throw usage(`Unknown boards subcommand: ${sub}.`, "Try: ghosty boards ls | create | assign | move-cards | rm");
10106
+ throw usage(t(`Unknown boards subcommand: ${sub}.`, `Subcomando de boards desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty boards ls | create | assign | move-cards | rm");
9461
10107
  }
9462
10108
  }
9463
10109
 
10110
+ // src/help.es.ts
10111
+ var HELPS_ES = {
10112
+ agents: {
10113
+ summary: "Crea, revisa, cambia, diagnostica y borra agentes.",
10114
+ replace: [
10115
+ ["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
10116
+ ["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"]
10117
+ ],
10118
+ flags: [
10119
+ ["--fields a,b", "get: regresa s\xF3lo estos campos"],
10120
+ ["--prompt-out FILE", "get: escribe el prompt en FILE (ed\xEDtalo y vuelve con set --prompt-file)"],
10121
+ ["--like AGENT", "create: copia la configuraci\xF3n de ese agente (skills y sus switches, toolsets, conectores, voz, bases de datos, documentos, tablero, compartidos) \u2014 no su prompt, env ni canales. docs grant: los mismos documentos"],
10122
+ ["--copy-dbs", "create --like: duplica sus bases de datos con sus filas en vez de conceder las mismas"],
10123
+ ["--board B", "create --like: same (el tablero del origen, default) \xB7 main \xB7 new (un tablero con el nombre del agente y las columnas del origen: un tablero por n\xFAmero) \xB7 o un tablero existente"],
10124
+ ["--no-notify", "create --like: lo comparte con las mismas personas SIN mandarles correo"],
10125
+ ["--workspace SLUG", "create: nace en ese espacio (del que eres due\xF1o o admin), a nombre de su due\xF1o y activo en su Teams"],
10126
+ ["--channels k=on|off,\u2026", "create/set: prende/apaga canales: teams, web, whatsapp, messenger, wa (WhatsApp personal)"],
10127
+ ["--toolsets LIST", "set: grupos de tools de gs que recibe (crm, sales\u2026): una lista reemplaza; +x / -x agrega o quita"],
10128
+ ["--prompt-base ID", "set: el prompt de este agente = el prompt de ese agente + --prompt-overlay; se regenera cuando cambia la base. none = desliga (conserva el prompt de hoy)"],
10129
+ ["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, aplicados en orden; cada uno debe encontrar su lugar o no cambia nada'],
10130
+ ["--voice E:ID", "set: voz de las notas de voz: elevenlabs:<voiceId> o kokoro:em_santa|em_alex|ef_dora"],
10131
+ ["--voice-replies M", "set: auto = contesta con nota de voz cuando el cliente manda o pide audio; off = siempre texto"],
10132
+ ["--auto-stage on|off", "set: mueve solo las tarjetas del tablero tras cada turno del cliente (una llamada extra al modelo por turno)"],
10133
+ ["--verbose", "doctor: lista tambi\xE9n las revisiones que pasaron"],
10134
+ ["--write", "docs grant: el agente puede reemplazar el documento (default: s\xF3lo lectura)"],
10135
+ ["--no-deliver", "docs grant/add: puede leerlo pero nunca mand\xE1rselo a quien escribe"],
10136
+ ["--url URL | --file PATH", "docs add: de d\xF3nde sale el documento (URL p\xFAblica, hasta 25 MB, o un archivo local)"],
10137
+ ["--grant read|write|no", "docs add: acceso de este agente (default read; no = s\xF3lo subirlo a la cuenta)"],
10138
+ ["--role R", "share: editor (lo configura y lo usa) o admin (adem\xE1s lo comparte); repetirlo sobre un correo ya compartido cambia el rol"],
10139
+ ["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
10140
+ ["--slug NAME", "demo: nombre de la liga; se le agrega un c\xF3digo de 6 caracteres (/p/NAME-CODE)"],
10141
+ ["--expires DATE|no", 'demo: caducidad fija en la hora de tu m\xE1quina ("2026-10-15T20:00"); no = nunca'],
10142
+ ["--turns N", "demo: mensajes por visitante (default 150)"],
10143
+ ["--per-ip N", "demo: mensajes por IP al d\xEDa (default 30)"],
10144
+ ["--welcome TEXT", "demo: saludo del chat (vac\xEDo = el de siempre)"],
10145
+ ["--starter TEXT", 'demo: primera sugerencia destacada ("" la quita)'],
10146
+ ['--chips "a|b|c"', "demo: sugerencias, separadas por |"],
10147
+ ["--sponsor on|off", "demo: las copias que la gente se queda corren con TU llave del motor"],
10148
+ ["--rotate", "demo: c\xF3digo nuevo; la liga vieja deja de servir"],
10149
+ ["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
10150
+ ["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
10151
+ ["--recycle", "box: reemplaza sus m\xE1quinas (no a media respuesta); la nueva arranca en el siguiente turno. ACP: usa restart"]
10152
+ ],
10153
+ notes: [
10154
+ "doctor sale con 1 cuando encuentra un problema, as\xED puede frenar un script o un CI.",
10155
+ "Un prompt derivado no se edita directo (409): cambia la base o el overlay, o desenl\xE1zalo con --prompt-base none.",
10156
+ "demo sin flags s\xF3lo imprime su estado: liga, caducidad, topes y n\xFAmeros."
10157
+ ]
10158
+ },
10159
+ skills: {
10160
+ summary: "Skills de un agente (formato agentskills.io: una carpeta con SKILL.md).",
10161
+ replace: [
10162
+ ["# from the community catalog", "# del cat\xE1logo de la comunidad"],
10163
+ ["# keeps a local copy first", "# antes guarda una copia local"]
10164
+ ],
10165
+ flags: [
10166
+ ["--out DIR", "get: carpeta donde escribir; sin ella, get s\xF3lo lista los archivos"],
10167
+ ["--only a,b", "house: deja prendidas S\xD3LO estas skills de Ghosty (las dem\xE1s apagadas)"],
10168
+ ["--enable / --disable a,b", "house: prende / apaga estas skills de Ghosty"],
10169
+ ["--file FILE", "add: sube un SKILL.md"],
10170
+ ["--dir FOLDER", "add: sube la carpeta entera de una skill (SKILL.md + archivos); restaura un respaldo"],
10171
+ ["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
10172
+ ],
10173
+ notes: [
10174
+ "rm guarda una copia en ~/.config/ghosty/trash/ e imprime el comando que la restaura.",
10175
+ "templates lee una m\xE1quina viva (corre un turno antes). La plantilla propia de un agente es <skill>/pdf-templates/<name>.html en cualquiera de sus skills; le gana a una de casa con el mismo nombre."
10176
+ ]
10177
+ },
10178
+ dbs: {
10179
+ summary: "Bases de datos que un agente puede usar, y qu\xE9 puede escribir desde canales de clientes.",
10180
+ replace: [["# reads $EASYBITS_API_KEY", "# lee $EASYBITS_API_KEY"]],
10181
+ flags: [
10182
+ ["--arg V", "query: valor para un marcador ? (repetible)"],
10183
+ ["--write", "query: permite INSERT/UPDATE/DELETE/DDL (default: s\xF3lo lectura)"],
10184
+ ["--external-write t1,t2", 'grant: tablas que puede escribir desde WhatsApp/Messenger ("" = ninguna)'],
10185
+ ["--external-rows t:col+col", 'grant: desde WhatsApp/Messenger cada cliente ve s\xF3lo SUS filas de t, por columna(s) de tel\xE9fono ("" = sin filtro)'],
10186
+ ["--from easybits:ID", "import: base de origen en EasyBits (llave de $EASYBITS_API_KEY)"],
10187
+ ["--tables a,b", "import: s\xF3lo estas tablas (default: todas)"],
10188
+ ["--append", "import: agrega filas en vez de reemplazarlas (reemplazar es el default idempotente)"],
10189
+ ["--rehost-images t.col,\u2026", "import: copia las ligas de imagen de esas columnas al almacenamiento p\xFAblico de Ghosty (ligas permanentes en img.ghosty.studio)"],
10190
+ ["--table T --column C", "rehost: la columna de ligas de imagen (p. ej. catalogo imagen_url) que se pasa a ligas permanentes en img.ghosty.studio; idempotente, una liga que falla se queda como estaba"],
10191
+ ["-n, --dry-run", "import: imprime el plan (+ crear, ~ reemplazar N \u2192 M filas), no cambia nada"]
10192
+ ],
10193
+ notes: ["<db> es el id de la base o su nombre. Desde canales del due\xF1o el agente lee y escribe todo lo concedido."]
10194
+ },
10195
+ turns: {
10196
+ summary: "Los turnos recientes de un agente: qu\xE9 contest\xF3, qu\xE9 fall\xF3 y por qu\xE9, y en qu\xE9 se fue el tiempo de un turno.",
10197
+ replace: [["# last turn: each tool, its seconds, output tokens", "# \xFAltimo turno: cada tool, sus segundos, tokens de salida"]],
10198
+ flags: [
10199
+ ["--errors", "s\xF3lo turnos fallidos (igual que --status error)"],
10200
+ ["--status S", "running | done | stopped | error"],
10201
+ ["--since T", "30m, 1h, 2d o una fecha ISO"],
10202
+ ["--limit N", "m\xE1ximo de filas (default 20, m\xE1x. 500)"],
10203
+ ["--full", "show: el mensaje que entr\xF3 y la respuesta completos (default: primeros 600 caracteres)"]
10204
+ ],
10205
+ notes: ["show imprime lo que entr\xF3 y lo que sali\xF3 en cualquier motor o canal (grupos de WhatsApp tambi\xE9n); la l\xEDnea de tiempo lee el transcript del worker, as\xED que s\xF3lo motores del pool (no ACP/goose)."]
10206
+ },
10207
+ files: { summary: "Archivos en la m\xE1quina del agente." },
10208
+ mcp: { summary: "Servidores MCP de un agente. set REEMPLAZA la lista entera." },
10209
+ try: {
10210
+ summary: "Turnos de prueba: sencillos, por el camino real de WhatsApp (no se manda nada), o repetici\xF3n de conversaciones reales.",
10211
+ replace: [['"message"', '"mensaje"']],
10212
+ flags: [
10213
+ ["--waba", "el camino real de WhatsApp Business: contexto heredado, regla de precios, tablero, voz. Muestra lo que MANDAR\xCDA"],
10214
+ ["--session PHONE", "con --waba: un tel\xE9fono de cliente inventado (10-15 d\xEDgitos); cada tel\xE9fono es su propia conversaci\xF3n"],
10215
+ ["--media FILE", "con --waba: el cliente manda esta foto/audio/PDF (uno por turno)"],
10216
+ ["--as-formmy", "con --media: llega como un reenv\xEDo real de Formmy (proxy con credencial, copia a gs, transcripci\xF3n) y dice si la copia funcion\xF3"],
10217
+ ["--history FILE", "con --waba: lo que se dijo antes, como [{role: user|agent, text, at?}]; cuenta en el primer turno (--reset)"],
10218
+ ["--reset", "olvida esa sesi\xF3n de prueba (con --waba tambi\xE9n su hilo de prueba)"],
10219
+ ["--replay FILE", "corre las conversaciones de FILE ({sample: [{id, fakePhone, messages}]}) contra el agente"],
10220
+ ["--out DIR", "replay: d\xF3nde se escribe replay.json, tras cada paso"],
10221
+ ["--max N", "replay: turnos del cliente por conversaci\xF3n (default 15)"],
10222
+ ["--concurrency N", "replay: conversaciones al mismo tiempo (default 8, m\xE1x. 30)"],
10223
+ ["--resume", "replay: contin\xFAa desde DIR/replay.json, salt\xE1ndose los pasos terminados"]
10224
+ ],
10225
+ notes: [
10226
+ "Las repeticiones sobreviven a los deploys de Ghosty: un 503 se reintenta tras su Retry-After; el reporte vive en tu disco.",
10227
+ "--whatsapp ahora es alias de --waba.",
10228
+ "--waba lista todo lo que mandar\xEDa, tools incluidas: \u{1F4CD} ubicaci\xF3n, \u{1F4C4} archivos, \u{1F3A4} voz, \u21A9\uFE0F citas, \u{1F518} botones, \u{1F464} contactos, reacciones."
10229
+ ]
10230
+ },
10231
+ chat: {
10232
+ summary: "Conversa en streaming; sin mensaje es interactivo.",
10233
+ replace: [['["message"]', '["mensaje"]']]
10234
+ },
10235
+ schedule: {
10236
+ summary: "Turnos que corren despu\xE9s, con el reloj propio del agente.",
10237
+ replace: [['"what to do"', '"qu\xE9 hacer"']]
10238
+ },
10239
+ me: { summary: "Tu cuenta: archivos y uso." },
10240
+ credentials: {
10241
+ summary: "Llaves de proveedores en la b\xF3veda de tu cuenta (cifradas; s\xF3lo regresan los \xFAltimos 4 caracteres).",
10242
+ replace: [["# hidden prompt", "# pregunta oculta"]],
10243
+ flags: [
10244
+ ["--from-env VAR", "set: lee la llave de esta variable de entorno (pasa su NOMBRE)"],
10245
+ ["--yes", "rm: se salta la confirmaci\xF3n"]
10246
+ ],
10247
+ notes: ["La llave nunca se acepta como argumento: se quedar\xEDa en el historial de tu shell y en `ps`."]
10248
+ },
10249
+ channels: {
10250
+ summary: "Qu\xE9 n\xFAmeros de WhatsApp Business contesta un agente, y qu\xE9 grupos de su WhatsApp personal (y como qui\xE9n). Los n\xFAmeros se emparejan en la web.",
10251
+ replace: [['"Name"', '"Nombre"']],
10252
+ flags: [
10253
+ ["-n, --dry-run", "imprime el plan (+ empieza a contestar, - deja de contestar) y no cambia nada"],
10254
+ ["--yes", "enable / groups enable: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
10255
+ ["--role R", "groups: equipo (el equipo: escribe en bases de datos, tools de admin) o cliente (como cualquier cliente)"],
10256
+ ["--cached", "groups ls: no le pide a WhatsApp la lista en vivo (m\xE1s r\xE1pido; puede faltar alg\xFAn grupo nuevo)"],
10257
+ ["--integration ID", "link/unlink/history: el id de la Integration del n\xFAmero de WhatsApp Business en Formmy"],
10258
+ ["--days N", "history: conversaciones activas en los \xFAltimos N d\xEDas (1-30, default 7)"],
10259
+ ["--answer on|off", "link (obligatorio, sin default): on = este agente contesta a clientes reales; off = s\xF3lo registra"],
10260
+ ["--forward", "link --answer off: igual hace que Formmy reenv\xEDe, para que gs vea las conversaciones"],
10261
+ ["--from-file F", "import: {authCreds, authKeys, phone, groups} exportado de EasyBits"],
10262
+ ["--from-dir DIR", "import: una carpeta de auth multi-archivo de Baileys (creds.json + un archivo por llave; p. ej. store/auth de nanoclaw)"],
10263
+ ["--own-number yes|no", "import (obligatorio): el n\xFAmero es del agente, o de una persona (s\xF3lo su chat consigo misma es del due\xF1o)"],
10264
+ ["--replace", "import: reemplaza una sesi\xF3n ya guardada en gs"],
10265
+ ["--no-connect", "import: la guarda pero todav\xEDa no conecta"]
10266
+ ],
10267
+ notes: [
10268
+ "<number> es su id, su etiqueta o sus \xFAltimos d\xEDgitos. Un n\xFAmero, un agente: prenderlo se lo quita al otro agente.",
10269
+ "Apagarlo sigue registrando los mensajes; nadie los contesta.",
10270
+ "Los grupos son del WhatsApp PERSONAL emparejado al agente (no de WhatsApp Business). <group> es su nombre o su jid.",
10271
+ "link mueve un n\xFAmero que ya vive en Formmy (EasyBits, un droplet) sin rehacer el alta de Meta; unlink lo regresa. S\xF3lo el due\xF1o, un admin del agente o el staff de Ghosty.",
10272
+ "history trae al tablero las tarjetas, mensajes y pedidos recientes del n\xFAmero desde Formmy (enl\xE1zalo antes). Repetirlo nunca duplica: c\xF3rrelo antes del corte y otra vez justo en el corte.",
10273
+ "import: apaga primero el socket de EasyBits SIN logout (dos sockets se tumban entre s\xED; un logout invalida las llaves). Borra el archivo despu\xE9s."
10274
+ ]
10275
+ },
10276
+ login: {
10277
+ summary: "Inicia sesi\xF3n con tu navegador (OAuth2, aqu\xED no hay contrase\xF1a). Un agente puede correrlo por ti: imprime la URL y espera.",
10278
+ replace: [["# no login: an agent token", "# sin login: un token de agente"]],
10279
+ flags: [
10280
+ ["--device", "inicia sesi\xF3n desde otro dispositivo con liga + c\xF3digo (autom\xE1tico por SSH o en CI)"],
10281
+ ["--profile NAME", "una sesi\xF3n guardada aparte, p. ej. una por cuenta"]
10282
+ ]
10283
+ },
10284
+ logout: { summary: "Cierra sesi\xF3n y revoca en el servidor la sesi\xF3n de este dispositivo." },
10285
+ whoami: { summary: "Con qui\xE9n iniciaste sesi\xF3n (con --json, tambi\xE9n el plan de tu cuenta)." },
10286
+ docs: { summary: "Abre la documentaci\xF3n en tu navegador (con --json, s\xF3lo imprime la URL)." },
10287
+ apply: {
10288
+ summary: "Hace que un agente coincida con un archivo (de ghosty agents export): prompt, modelo, canales, tools, conectores, voz, tablero, skills, MCP, bases de datos, documentos, compartidos. Primero muestra el plan (+ agregar, ~ cambiar, - quitar, ! nota) y pregunta.",
10289
+ replace: [
10290
+ ["# or --out DIR: agent.yaml + skills/<slug>/\u2026", "# o --out DIR: agent.yaml + skills/<slug>/\u2026"],
10291
+ ["# a new agent from the file", "# un agente nuevo a partir del archivo"]
10292
+ ],
10293
+ flags: [
10294
+ ["--agent X", "aplica a este agente en vez del id del archivo (p. ej. el mismo archivo sobre una copia)"],
10295
+ ["--create", "crea un agente nuevo con el archivo (su espacio, motor, canales) y aplica lo dem\xE1s"],
10296
+ ["--prune", "tambi\xE9n QUITA lo que el archivo ya no lista (bases de datos, documentos, compartidos, skills propias, variables de entorno); sin esto s\xF3lo se se\xF1alan con !"],
10297
+ ["-n, --dry-run", "imprime el plan, no cambia nada"],
10298
+ ["--no-notify", "compartidos nuevos sin mandar correo a la gente"]
10299
+ ],
10300
+ notes: [
10301
+ "Lo que el archivo no menciona no se toca; los campos que trae quedan exactamente como dice.",
10302
+ "Los secretos nunca salen: se exportan como ${NAME}; define NAME en tu entorno para cambiarlo, si no, se queda el valor actual.",
10303
+ "Antes de aplicar se guarda la versi\xF3n actual del agente (Versiones en la web)."
10304
+ ]
10305
+ },
10306
+ completion: {
10307
+ summary: "Autocompletado con TAB en tu shell: comandos, subcomandos y opciones (de esta ayuda), nombres de agentes y espacios (de tu cuenta, con cach\xE9 de 5 min).",
10308
+ replace: [
10309
+ ["# add to ~/.zshrc, after compinit", "# agr\xE9galo a ~/.zshrc, despu\xE9s de compinit"],
10310
+ ["# add to ~/.bashrc", "# agr\xE9galo a ~/.bashrc"]
10311
+ ],
10312
+ notes: ["Los nombres salen de ghosty agents ls; un agente nuevo aparece en m\xE1ximo 5 minutos (o borra ~/.cache/ghosty/complete.json)."]
10313
+ },
10314
+ use: {
10315
+ summary: "Un espacio de trabajo por default para esta sesi\xF3n (perfil): boards lo toma sin escribirlo, agents create nace ah\xED y agents ls lista s\xF3lo sus agentes.",
10316
+ replace: [["# which one", "# cu\xE1l es"]],
10317
+ flags: [["--clear", "sin espacio por default"]],
10318
+ notes: [
10319
+ "En cualquier lugar puedes nombrar a un agente en vez de usar su id; si hay varios con el mismo nombre, gana el de este espacio.",
10320
+ "agents create --workspace none crea un agente personal aunque haya espacio por default."
10321
+ ]
10322
+ },
10323
+ boards: {
10324
+ summary: "Los tableros de ventas de un ESPACIO: listarlos, crear uno (copiando las columnas de otro), elegir a d\xF3nde manda sus tarjetas un agente y mover tarjetas entre tableros.",
10325
+ replace: [['"Name"', '"Nombre"']],
10326
+ flags: [
10327
+ ["--columns-from B", "create: copia las columnas de ese tablero (main, un nombre o un id) en vez de las de siempre"],
10328
+ ["--from / --to B", "move-cards: tableros por nombre, id o main"],
10329
+ ["--integration ID | --phone N | --all", "move-cards: qu\xE9 tarjetas (el filtro es obligatorio)"],
10330
+ ["-n, --dry-run", "move-cards: cuenta y muestra una muestra, no cambia nada"],
10331
+ ["--yes", "move-cards: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
10332
+ ],
10333
+ notes: [
10334
+ "assign cambia a d\xF3nde van las tarjetas NUEVAS; move-cards trae las que ya existen (hilo, pedidos, actividad) con folio nuevo. Una tarjeta cuya conversaci\xF3n ya est\xE1 en el tablero destino se salta.",
10335
+ "Leer requiere ser miembro; cambiar requiere ser due\xF1o o staff del espacio.",
10336
+ "rm s\xF3lo borra un tablero vac\xEDo que no sea el principal."
10337
+ ]
10338
+ },
10339
+ cards: {
10340
+ summary: "Las tarjetas del tablero de ventas de un agente (su tablero es del ESPACIO: debes ser miembro, un agente compartido no basta). Los tableros del espacio en s\xED: ghosty boards.",
10341
+ flags: [
10342
+ ["--q TEXT", "ls: tarjetas cuyo nombre, tel\xE9fono o \xFAltimo mensaje contiene TEXT"],
10343
+ ["--limit N", "ls: tarjetas (default 20) \xB7 show: \xFAltimos N mensajes (default 20, m\xE1x. 500)"],
10344
+ ["--integration ID", "tarjetas de este canal/integraci\xF3n (p. ej. prueba)"],
10345
+ ["--column NAME", "tarjetas en esta columna"],
10346
+ ["--before T", "tarjetas sin actividad desde T (30d, 12h o una fecha ISO)"],
10347
+ ["-n, --dry-run", "cuenta y muestra una muestra, no cambia nada"],
10348
+ ["--yes", "se salta la confirmaci\xF3n (obligatorio sin terminal)"]
10349
+ ],
10350
+ notes: [
10351
+ "show imprime cada mensaje con qui\xE9n lo escribi\xF3 (cliente, agente, operador), su adjunto y si gs guarda una copia.",
10352
+ "Archivar es reversible: cuando el cliente vuelve a escribir, la tarjeta regresa. Se necesita al menos un filtro."
10353
+ ]
10354
+ },
10355
+ usage: { summary: "Tu plan y cu\xE1nto llevas usado esta semana y este mes." }
10356
+ };
10357
+ var HELP_ALIASES = { creds: "credentials", board: "cards", conversations: "chat" };
10358
+
9464
10359
  // src/help.ts
9465
10360
  var DOCS = "https://www.ghosty.studio/docs/cli";
9466
10361
  var REF = `${DOCS}/comandos`;
@@ -9786,6 +10681,17 @@ HELPS.apply = {
9786
10681
  ],
9787
10682
  learn: `${REF}#el-agente-como-archivo`
9788
10683
  };
10684
+ HELPS.completion = {
10685
+ summary: "Tab completion for your shell: commands, subcommands and flags (from this help), agent and workspace names (from your account, cached 5 min).",
10686
+ usage: ["ghosty completion zsh|bash|fish"],
10687
+ examples: [
10688
+ "source <(ghosty completion zsh) # add to ~/.zshrc, after compinit",
10689
+ 'eval "$(ghosty completion bash)" # add to ~/.bashrc',
10690
+ "ghosty completion fish > ~/.config/fish/completions/ghosty.fish"
10691
+ ],
10692
+ notes: ["Names come from ghosty agents ls; a new agent shows up within 5 minutes (or delete ~/.cache/ghosty/complete.json)."],
10693
+ learn: `${REF}#autocompletado`
10694
+ };
9789
10695
  HELPS.use = {
9790
10696
  summary: "A default workspace for this session (profile): boards take it without typing it, agents create is born there and agents ls lists only its agents.",
9791
10697
  usage: ["ghosty use # which one", "ghosty use <workspace>", "ghosty use --clear"],
@@ -9838,19 +10744,23 @@ HELPS.board = HELPS.cards;
9838
10744
  HELPS.conversations = HELPS.chat;
9839
10745
  HELPS.usage = { ...HELPS.me, summary: "Your plan and how much you've used this week and month.", usage: ["ghosty usage", "ghosty me usage"], examples: ["ghosty usage", "ghosty usage --json"], learn: `${REF}#uso` };
9840
10746
  function helpText(cmd) {
9841
- const h = HELPS[cmd];
9842
- if (!h) return void 0;
9843
- const section = (t, lines) => [bold(t), ...lines.map((l) => ` ${l}`), ""];
10747
+ const base = HELPS[cmd];
10748
+ if (!base) return void 0;
10749
+ const es = lang() === "es";
10750
+ const tr = es ? HELPS_ES[HELP_ALIASES[cmd] ?? cmd] : void 0;
10751
+ const fix = (l) => (tr?.replace ?? []).reduce((acc, [a, b]) => acc.split(a).join(b), l);
10752
+ const h = { ...base, ...tr ? { summary: tr.summary ?? base.summary, flags: tr.flags ?? base.flags, notes: tr.notes ?? base.notes } : {} };
10753
+ const section = (t2, lines) => [bold(t2), ...lines.map((l) => ` ${l}`), ""];
9844
10754
  const w = Math.max(0, ...(h.flags ?? []).map(([f]) => f.length)) + 2;
9845
10755
  return [
9846
10756
  h.summary,
9847
10757
  "",
9848
- ...section("USAGE", h.usage),
9849
- ...h.flags?.length ? section("FLAGS", h.flags.map(([f, d]) => `${f.padEnd(w)}${d}`)) : [],
9850
- ...section("EXAMPLES", h.examples.map((e) => `$ ${e}`)),
9851
- ...h.notes?.length ? section("NOTES", h.notes) : [],
9852
- bold("LEARN MORE"),
9853
- ` Add --json for machine output. Exit codes: 0 ok \xB7 1 API error \xB7 2 usage \xB7 3 not signed in.`,
10758
+ ...section(es ? "USO" : "USAGE", h.usage.map(fix)),
10759
+ ...h.flags?.length ? section(es ? "OPCIONES" : "FLAGS", h.flags.map(([f, d]) => `${f.padEnd(w)}${d}`)) : [],
10760
+ ...section(es ? "EJEMPLOS" : "EXAMPLES", h.examples.map((e) => `$ ${fix(e)}`)),
10761
+ ...h.notes?.length ? section(es ? "NOTAS" : "NOTES", h.notes) : [],
10762
+ bold(es ? "M\xC1S INFORMACI\xD3N" : "LEARN MORE"),
10763
+ es ? ` Agrega --json para salida para m\xE1quinas. C\xF3digos de salida: 0 bien \xB7 1 error de la API \xB7 2 uso \xB7 3 sin sesi\xF3n.` : ` Add --json for machine output. Exit codes: 0 ok \xB7 1 API error \xB7 2 usage \xB7 3 not signed in.`,
9854
10764
  ` ${dim(h.learn ?? DOCS)}`
9855
10765
  ].join("\n");
9856
10766
  }
@@ -9887,9 +10797,9 @@ async function latestVersion() {
9887
10797
  }
9888
10798
  return cache2.latest ?? null;
9889
10799
  }
9890
- async function updateNotice(current) {
10800
+ async function updateNotice(current2) {
9891
10801
  const latest = await latestVersion();
9892
- return latest && newer(latest, current) ? `Update available: ${current} \u2192 ${latest}. Run: ${UPGRADE}` : null;
10802
+ return latest && newer(latest, current2) ? t(`Update available: ${current2} \u2192 ${latest}. Run: ${UPGRADE}`, `Hay versi\xF3n nueva: ${current2} \u2192 ${latest}. Corre: ${UPGRADE}`) : null;
9893
10803
  }
9894
10804
 
9895
10805
  // src/resolve.ts
@@ -9904,11 +10814,14 @@ function pickAgent(list, ref, workspace) {
9904
10814
  if (hits.length === 1) return hits[0].id;
9905
10815
  if (!hits.length) {
9906
10816
  const near = list.filter((a) => a.nombre.toLowerCase().includes(want)).slice(0, 5);
9907
- throw new CliError(`No agent named "${ref}".`, EXIT.USAGE, near.length ? `Did you mean: ${near.map((a) => `${a.nombre} (${a.id})`).join(", ")}?` : "List them: ghosty agents ls");
10817
+ const names2 = near.map((a) => `${a.nombre} (${a.id})`).join(", ");
10818
+ throw new CliError(t(`No agent named "${ref}".`, `No hay ning\xFAn agente llamado "${ref}".`), EXIT.USAGE, near.length ? t(`Did you mean: ${names2}?`, `\xBFQuisiste decir: ${names2}?`) : t("List them: ghosty agents ls", "L\xEDstalos: ghosty agents ls"));
9908
10819
  }
9909
10820
  const where = (a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "?";
9910
- throw new CliError(`${hits.length} agents are named "${ref}".`, EXIT.USAGE, `Use the id:
9911
- ${hits.map((a) => ` ${a.id} ${a.motor ?? ""} ${where(a)}`).join("\n")}`);
10821
+ const ids = hits.map((a) => ` ${a.id} ${a.motor ?? ""} ${where(a)}`).join("\n");
10822
+ throw new CliError(t(`${hits.length} agents are named "${ref}".`, `${hits.length} agentes se llaman "${ref}".`), EXIT.USAGE, t(`Use the id:
10823
+ ${ids}`, `Usa el id:
10824
+ ${ids}`));
9912
10825
  }
9913
10826
  var cache = null;
9914
10827
  function listAgents() {
@@ -9919,57 +10832,185 @@ async function resolveAgent(ref, workspace) {
9919
10832
  if (!ref || looksLikeId(ref)) return ref;
9920
10833
  return pickAgent(await listAgents(), ref, workspace);
9921
10834
  }
10835
+ function agentArgIndex(pos) {
10836
+ const [cmd, sub] = pos;
10837
+ switch (cmd) {
10838
+ case "agents":
10839
+ if (sub === "docs" || sub === "documents") return 3;
10840
+ return sub && !["ls", "list", "create", "new"].includes(sub) ? 2 : null;
10841
+ case "files":
10842
+ case "skills":
10843
+ case "mcp":
10844
+ case "dbs":
10845
+ case "db":
10846
+ case "turns":
10847
+ case "board":
10848
+ case "conversations":
10849
+ case "schedule":
10850
+ return 2;
10851
+ case "try":
10852
+ case "chat":
10853
+ return 1;
10854
+ case "channels":
10855
+ return pos[2] === "groups" ? 4 : 3;
10856
+ case "boards":
10857
+ return sub === "assign" ? 2 : null;
10858
+ default:
10859
+ return null;
10860
+ }
10861
+ }
10862
+
10863
+ // src/completion.ts
10864
+ import { mkdir as mkdir5, readFile as readFile10, writeFile as writeFile5 } from "node:fs/promises";
10865
+ import { homedir as homedir4 } from "node:os";
10866
+ import { join as join6 } from "node:path";
10867
+ var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
10868
+ var AGENT_FLAGS = /* @__PURE__ */ new Set(["--like", "--agent", "--prompt-base"]);
10869
+ var WORKSPACE_FLAGS = /* @__PURE__ */ new Set(["--workspace"]);
10870
+ function subcommandsOf(cmd) {
10871
+ const subs = /* @__PURE__ */ new Set();
10872
+ for (const u of HELPS[cmd]?.usage ?? []) {
10873
+ const m = new RegExp(`^ghosty ${cmd} ([a-z][a-z|-]*)`).exec(u);
10874
+ if (m) {
10875
+ for (const s of m[1].split("|")) if (!s.includes("-") || /^[a-z]+-[a-z]+$/.test(s)) subs.add(s);
10876
+ }
10877
+ }
10878
+ return [...subs];
10879
+ }
10880
+ function flagsOf(cmd) {
10881
+ const own = /* @__PURE__ */ new Set();
10882
+ for (const [f] of HELPS[cmd]?.flags ?? []) for (const m of f.matchAll(/--[a-z][a-z-]*/g)) own.add(m[0]);
10883
+ for (const u of HELPS[cmd]?.usage ?? []) for (const m of u.matchAll(/--[a-z][a-z-]*/g)) own.add(m[0]);
10884
+ return [...own, ...GLOBAL_FLAGS];
10885
+ }
10886
+ function completionFor(words, current2, commands) {
10887
+ const prev = words[words.length - 1];
10888
+ if (prev && AGENT_FLAGS.has(prev)) return { kind: "agents" };
10889
+ if (prev && WORKSPACE_FLAGS.has(prev)) return { kind: "workspaces" };
10890
+ if (!words.length) return { kind: "words", words: commands };
10891
+ const [cmd, sub] = words;
10892
+ if (current2.startsWith("-")) return { kind: "words", words: flagsOf(cmd) };
10893
+ const pos = words.filter((w) => !w.startsWith("-"));
10894
+ if (cmd === "use" && pos.length === 1) return { kind: "workspaces" };
10895
+ if (cmd === "boards" && pos.length === 2 && sub !== "assign") return { kind: "workspaces" };
10896
+ if (cmd === "help" && pos.length === 1) return { kind: "words", words: commands };
10897
+ if (pos.length === 1 && cmd !== "try" && cmd !== "chat") {
10898
+ const subs = subcommandsOf(cmd);
10899
+ if (subs.length) return { kind: "words", words: subs };
10900
+ }
10901
+ if (agentArgIndex([...pos, current2]) === pos.length) return { kind: "agents" };
10902
+ return { kind: "words", words: [] };
10903
+ }
10904
+ var cacheFile = () => join6(process.env.XDG_CACHE_HOME || join6(homedir4(), ".cache"), "ghosty", `complete${process.env.GHOSTY_PROFILE ? `.${process.env.GHOSTY_PROFILE}` : ""}.json`);
10905
+ async function names() {
10906
+ try {
10907
+ const c = JSON.parse(await readFile10(cacheFile(), "utf8"));
10908
+ if (Date.now() - c.at < 5 * 6e4) return c;
10909
+ } catch {
10910
+ }
10911
+ const list = await listAgents();
10912
+ const agents = [...new Set(list.map((a) => a.nombre))];
10913
+ const workspaces = [...new Set(list.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
10914
+ await mkdir5(join6(cacheFile(), ".."), { recursive: true }).catch(() => {
10915
+ });
10916
+ await writeFile5(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
10917
+ });
10918
+ return { agents, workspaces };
10919
+ }
10920
+ async function completeCmd(args, commands) {
10921
+ const current2 = args.length ? args[args.length - 1] : "";
10922
+ const words = args.slice(0, -1);
10923
+ const want = completionFor(words, current2, commands);
10924
+ let out2 = [];
10925
+ try {
10926
+ if (want.kind === "words") out2 = want.words;
10927
+ else out2 = (await names())[want.kind];
10928
+ } catch {
10929
+ out2 = [];
10930
+ }
10931
+ process.stdout.write(out2.filter((w) => w.toLowerCase().startsWith(current2.toLowerCase())).join("\n") + "\n");
10932
+ }
10933
+ function completionScript(shell) {
10934
+ if (shell === "bash") {
10935
+ return `# ghosty \u2014 bash completion: eval "$(ghosty completion bash)" en ~/.bashrc
10936
+ _ghosty() {
10937
+ # Las palabras ANTES de cambiar IFS: el bash 3.2 de macOS junta \${arr[@]:1:n} con un IFS propio.
10938
+ local words=("\${COMP_WORDS[@]:1:COMP_CWORD-1}") cur="\${COMP_WORDS[COMP_CWORD]}"
10939
+ local IFS=$'\\n'
10940
+ COMPREPLY=( $(ghosty __complete -- "\${words[@]}" "$cur" 2>/dev/null) )
10941
+ }
10942
+ complete -o default -F _ghosty ghosty
10943
+ `;
10944
+ }
10945
+ if (shell === "zsh") {
10946
+ return `#compdef ghosty
10947
+ # ghosty \u2014 zsh completion: source <(ghosty completion zsh) en ~/.zshrc (despu\xE9s de compinit)
10948
+ _ghosty() {
10949
+ local -a opts
10950
+ opts=("\${(@f)$(ghosty __complete -- "\${(@)words[2,CURRENT-1]}" "\${words[CURRENT]}" 2>/dev/null)}")
10951
+ compadd -a opts
10952
+ }
10953
+ compdef _ghosty ghosty
10954
+ `;
10955
+ }
10956
+ if (shell === "fish") {
10957
+ return `# ghosty \u2014 fish completion: ghosty completion fish > ~/.config/fish/completions/ghosty.fish
10958
+ complete -c ghosty -f -a "(ghosty __complete -- (commandline -opc)[2..-1] (commandline -ct) 2>/dev/null)"
10959
+ `;
10960
+ }
10961
+ return null;
10962
+ }
9922
10963
 
9923
10964
  // src/aliases.ts
9924
10965
  function normalizeArgs(pos, v) {
9925
10966
  const notes = [];
9926
10967
  const [cmd, sub] = pos;
9927
10968
  if (cmd === "cards") pos[0] = "board";
9928
- else if (cmd === "board") notes.push("`ghosty board` is now `ghosty cards` (same subcommands).");
10969
+ else if (cmd === "board") notes.push(t("`ghosty board` is now `ghosty cards` (same subcommands).", "`ghosty board` ahora es `ghosty cards` (mismos subcomandos)."));
9929
10970
  if (cmd === "skills") {
9930
10971
  if (sub === "enable") pos[1] = "on";
9931
10972
  else if (sub === "disable") pos[1] = "off";
9932
- else if (sub === "on" || sub === "off") notes.push(`\`skills ${sub}\` is now \`skills ${sub === "on" ? "enable" : "disable"}\`.`);
10973
+ else if (sub === "on" || sub === "off") notes.push(t(`\`skills ${sub}\` is now \`skills ${sub === "on" ? "enable" : "disable"}\`.`, `\`skills ${sub}\` ahora es \`skills ${sub === "on" ? "enable" : "disable"}\`.`));
9933
10974
  }
9934
10975
  if (cmd === "agents" && sub === "demo") {
9935
10976
  if (pos[2] === "enable" || pos[2] === "disable") {
9936
10977
  v[pos[2] === "enable" ? "on" : "off"] = true;
9937
10978
  pos.splice(2, 1);
9938
- } else if (v.on || v.off) notes.push(`\`agents demo <agent> --${v.on ? "on" : "off"}\` is now \`agents demo ${v.on ? "enable" : "disable"} <agent>\`.`);
10979
+ } else if (v.on || v.off) notes.push(t(`\`agents demo <agent> --${v.on ? "on" : "off"}\` is now \`agents demo ${v.on ? "enable" : "disable"} <agent>\`.`, `\`agents demo <agente> --${v.on ? "on" : "off"}\` ahora es \`agents demo ${v.on ? "enable" : "disable"} <agente>\`.`));
9939
10980
  if (v.expires !== void 0) v.vence = v.expires;
9940
- else if (v.vence !== void 0) notes.push("`--vence` is now `--expires`.");
10981
+ else if (v.vence !== void 0) notes.push(t("`--vence` is now `--expires`.", "`--vence` ahora es `--expires`."));
9941
10982
  }
9942
10983
  if (cmd === "channels" && pos[2] === "groups") {
9943
10984
  if (pos[3] === "enable" || pos[3] === "disable") {
9944
10985
  v[pos[3] === "enable" ? "on" : "off"] = true;
9945
10986
  pos[3] = "set";
9946
- } else if (pos[3] === "set" && (v.on || v.off)) notes.push(`\`groups set \u2026 --${v.on ? "on" : "off"}\` is now \`groups ${v.on ? "enable" : "disable"} <agent> <group>\` (set keeps --role).`);
10987
+ } else if (pos[3] === "set" && (v.on || v.off)) notes.push(t(`\`groups set \u2026 --${v.on ? "on" : "off"}\` is now \`groups ${v.on ? "enable" : "disable"} <agent> <group>\` (set keeps --role).`, `\`groups set \u2026 --${v.on ? "on" : "off"}\` ahora es \`groups ${v.on ? "enable" : "disable"} <agente> <grupo>\` (set se queda para --role).`));
9947
10988
  }
9948
10989
  return notes;
9949
10990
  }
9950
10991
 
9951
10992
  // src/commands/spec.ts
9952
10993
  var import_yaml = __toESM(require_dist(), 1);
9953
- import { mkdir as mkdir5, readdir as readdir3, readFile as readFile10, stat, writeFile as writeFile5 } from "node:fs/promises";
9954
- import { join as join6, resolve as resolve2 } from "node:path";
10994
+ import { mkdir as mkdir6, readdir as readdir3, readFile as readFile11, stat, writeFile as writeFile6 } from "node:fs/promises";
10995
+ import { join as join7, resolve as resolve2 } from "node:path";
9955
10996
  var enc9 = encodeURIComponent;
9956
10997
  async function agentsExport(id, v) {
9957
- if (!id) throw usage("Missing agent.", "Example: ghosty agents export pia-0 > pia.yaml \xB7 ghosty agents export pia-0 --out ./pia");
10998
+ if (!id) throw usage(t("Missing agent.", "Falta el agente."), t("Example", "Ejemplo") + ": ghosty agents export pia-0 > pia.yaml \xB7 ghosty agents export pia-0 --out ./pia");
9958
10999
  const yaml = await (await request(`/api/v2/agents/${enc9(id)}/export`)).text();
9959
11000
  if (!v.out) {
9960
11001
  if (globals.json) return out((0, import_yaml.parse)(yaml));
9961
11002
  return void process.stdout.write(yaml);
9962
11003
  }
9963
11004
  const dir = resolve2(v.out);
9964
- await mkdir5(dir, { recursive: true });
9965
- await writeFile5(join6(dir, "agent.yaml"), yaml);
11005
+ await mkdir6(dir, { recursive: true });
11006
+ await writeFile6(join7(dir, "agent.yaml"), yaml);
9966
11007
  const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
9967
11008
  for (const slug of own) {
9968
11009
  const sk = await api(`/api/v2/agents/${enc9(id)}/skills/${enc9(slug)}`);
9969
- await writeSkill(join6(dir, "skills", slug), sk);
11010
+ await writeSkill(join7(dir, "skills", slug), sk);
9970
11011
  }
9971
11012
  out({ saved: dir, skills: own }, () => `${lavender("\u2713")} ${dir}/agent.yaml${own.length ? ` + skills/ (${own.join(", ")})` : ""}
9972
- ${dim(`Edit it, then: ghosty apply ${v.out} --dry-run`)}`);
11013
+ ${dim(t(`Edit it, then: ghosty apply ${v.out} --dry-run`, `Ed\xEDtalo y luego: ghosty apply ${v.out} --dry-run`))}`);
9973
11014
  }
9974
11015
  function fillSecrets(file) {
9975
11016
  const missing = [];
@@ -9987,61 +11028,61 @@ function fillSecrets(file) {
9987
11028
  return missing;
9988
11029
  }
9989
11030
  async function readSkills(dir) {
9990
- const root = join6(dir, "skills");
11031
+ const root = join7(dir, "skills");
9991
11032
  const out2 = {};
9992
11033
  const slugs = await readdir3(root, { withFileTypes: true }).catch(() => []);
9993
11034
  for (const e of slugs) {
9994
11035
  if (!e.isDirectory() || e.name.startsWith(".")) continue;
9995
- const { markdown, assets } = await readSkillDir(join6(root, e.name));
11036
+ const { markdown, assets } = await readSkillDir(join7(root, e.name));
9996
11037
  out2[e.name] = [{ path: "SKILL.md", contentBase64: Buffer.from(markdown).toString("base64") }, ...assets.map((a) => ({ path: a.name, contentBase64: a.contentBase64 }))];
9997
11038
  }
9998
11039
  return out2;
9999
11040
  }
10000
11041
  var line = (c) => c.op === "!" ? dim(`! ${c.what}`) : `${c.op === "+" ? lavender("+") : c.op === "-" ? red("-") : "~"} ${c.what}`;
10001
11042
  async function applyCmd(target, v) {
10002
- if (!target) throw usage("Missing file or directory.", "Example: ghosty apply pia.yaml --dry-run \xB7 ghosty apply ./pia (from agents export --out)");
11043
+ if (!target) throw usage(t("Missing file or directory.", "Falta el archivo o el directorio."), t("Example: ghosty apply pia.yaml --dry-run \xB7 ghosty apply ./pia (from agents export --out)", "Ejemplo: ghosty apply pia.yaml --dry-run \xB7 ghosty apply ./pia (de agents export --out)"));
10003
11044
  const isDir = (await stat(target).catch(() => null))?.isDirectory() ?? false;
10004
- const text = await readFile10(isDir ? join6(target, "agent.yaml") : target, "utf8");
11045
+ const text = await readFile11(isDir ? join7(target, "agent.yaml") : target, "utf8");
10005
11046
  const file = (0, import_yaml.parse)(text);
10006
- if (!file || typeof file !== "object") throw usage(`${target} is not a YAML/JSON agent file.`);
11047
+ if (!file || typeof file !== "object") throw usage(t(`${target} is not a YAML/JSON agent file.`, `${target} no es un archivo de agente YAML/JSON.`));
10007
11048
  const missing = fillSecrets(file);
10008
- if (missing.length && !globals.json) info(dim(`Secrets not in your environment keep their current value: ${missing.join(", ")}`));
11049
+ if (missing.length && !globals.json) info(dim(t(`Secrets not in your environment keep their current value: ${missing.join(", ")}`, `Los secretos que no est\xE1n en tu entorno conservan su valor actual: ${missing.join(", ")}`)));
10009
11050
  const files = isDir ? await readSkills(target) : void 0;
10010
11051
  let id = v.agent ?? file["x-ghosty"]?.id;
10011
11052
  if (v.create) {
10012
11053
  const x = file["x-ghosty"] ?? {};
10013
11054
  const name = v.name ?? file.title;
10014
- if (!name) throw usage("The file has no title: pass --name.");
10015
- if (v["dry-run"]) return out({ create: true, name, engine: x.engine, workspace: x.workspace ?? null }, () => `${lavender("+")} agent ${name} (${x.engine ?? "default engine"}${x.workspace ? ` in ${x.workspace}` : ""})
10016
- ${dim("Then every setting of the file is applied. Dry run: nothing created.")}`);
11055
+ if (!name) throw usage(t("The file has no title: pass --name.", "El archivo no tiene t\xEDtulo: pasa --name."));
11056
+ if (v["dry-run"]) return out({ create: true, name, engine: x.engine, workspace: x.workspace ?? null }, () => `${lavender("+")} ${t("agent", "agente")} ${name} (${x.engine ?? t("default engine", "motor por default")}${x.workspace ? t(` in ${x.workspace}`, ` en ${x.workspace}`) : ""})
11057
+ ${dim(t("Then every setting of the file is applied. Dry run: nothing created.", "Despu\xE9s se aplica cada ajuste del archivo. Simulaci\xF3n: no se cre\xF3 nada."))}`);
10017
11058
  requireYesIfHeadless(v.yes, `ghosty apply ${target} --create`);
10018
11059
  const created = await api("/api/v2/me/agents", { method: "POST", body: { name, ...x.engine ? { engine: x.engine } : {}, ...x.model ? { model: x.model } : {}, ...x.workspace ? { workspace: x.workspace } : {}, ...x.channels ? { channels: x.channels } : {} } });
10019
11060
  id = created.id;
10020
- info(dim(`Created ${name} (${id}); applying the rest\u2026`));
11061
+ info(dim(t(`Created ${name} (${id}); applying the rest\u2026`, `${name} creado (${id}); aplicando lo dem\xE1s\u2026`)));
10021
11062
  } else {
10022
11063
  id = await resolveAgent(id);
10023
- if (!id) throw usage("The file has no agent id.", "Point it at one with --agent <name|id>, or make a new one with --create.");
11064
+ if (!id) throw usage(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."));
10024
11065
  }
10025
11066
  const spec = JSON.stringify(v.create ? { ...file, title: void 0 } : file);
10026
11067
  const body = { spec, ...files ? { files } : {}, ...v.prune ? { prune: true } : {}, ...v["no-notify"] ? { notify: false } : {} };
10027
11068
  const path = `/api/v2/agents/${enc9(id)}/apply`;
10028
11069
  const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
10029
11070
  const actionable = plan.plan.filter((c) => c.op !== "!");
10030
- const text2 = plan.plan.map(line).join("\n") || dim("No changes: the agent already matches the file.");
11071
+ const text2 = plan.plan.map(line).join("\n") || dim(t("No changes: the agent already matches the file.", "Sin cambios: el agente ya coincide con el archivo."));
10031
11072
  if (v["dry-run"] || !actionable.length) return out(plan, () => `${bold(id)}
10032
11073
  ${text2}${actionable.length ? `
10033
- ${dim("Dry run: nothing changed.")}` : ""}`);
11074
+ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}` : ""}`);
10034
11075
  const retry = `ghosty apply ${target}${v.agent ? ` --agent ${v.agent}` : ""}${v.prune ? " --prune" : ""}`;
10035
11076
  if (!v.create) requireYesIfHeadless(v.yes, retry);
10036
11077
  if (!v.create) await confirm(`${text2}
10037
- Apply ${actionable.length} change(s) to ${id}?`, { yes: v.yes, retry });
11078
+ ${t(`Apply ${actionable.length} change(s) to ${id}?`, `\xBFAplicar ${actionable.length} cambio(s) a ${id}?`)}`, { yes: v.yes, retry });
10038
11079
  const r = await api(path, { method: "POST", body });
10039
- out(r, () => `${lavender("\u2713")} ${r.applied?.length ?? 0} change(s) applied to ${id}.${r.failed ? `
10040
- ${red(`Stopped at \xAB${r.failed.what}\xBB: ${r.failed.error}`)}` : ""}`);
11080
+ out(r, () => `${lavender("\u2713")} ${t(`${r.applied?.length ?? 0} change(s) applied to ${id}.`, `${r.applied?.length ?? 0} cambio(s) aplicados a ${id}.`)}${r.failed ? `
11081
+ ${red(t(`Stopped at \xAB${r.failed.what}\xBB: ${serverText(r.failed.error)}`, `Se detuvo en \xAB${r.failed.what}\xBB: ${r.failed.error}`))}` : ""}`);
10041
11082
  }
10042
11083
 
10043
11084
  // src/index.ts
10044
- var HELP = `${bold("Usage:")} ghosty <command> [options]
11085
+ var HELP_EN = `${bold("Usage:")} ghosty <command> [options]
10045
11086
 
10046
11087
  ${bold("Session")}
10047
11088
  login Sign in with your browser (OAuth2). An agent can run it for you.
@@ -10049,6 +11090,7 @@ ${bold("Session")}
10049
11090
  logout Sign out and revoke this device's session
10050
11091
  whoami Show who you are signed in as
10051
11092
  use [workspace] Default workspace for boards, agents create and agents ls [--clear]
11093
+ completion zsh|bash|fish Tab completion for commands, flags, agents and workspaces
10052
11094
  usage Your plan and how much you've used this week and month
10053
11095
 
10054
11096
  ${bold("Agents")}
@@ -10125,6 +11167,7 @@ ${bold("Global options")}
10125
11167
  --json Machine-readable output (stdout is JSON only)
10126
11168
  --token <token> Use this token (or env GHOSTY_TOKEN). A gat_\u2026 agent token reaches only that agent.
10127
11169
  --profile <name> Use a separate saved session (or env GHOSTY_PROFILE), e.g. one per account
11170
+ --lang es|en CLI language (or env GHOSTY_LANG; otherwise from LANG)
10128
11171
  --yes Skip confirmations (required for rm without a terminal)
10129
11172
  -h, --help Show help (also: ghosty <command> --help, ghosty help <command>)
10130
11173
  -v, --version Show version
@@ -10132,56 +11175,128 @@ ${bold("Global options")}
10132
11175
  ${bold("Exit codes")} 0 ok \xB7 1 API error \xB7 2 usage error \xB7 3 not signed in
10133
11176
  ${dim("Note: `ghosty` may collide with another binary on your PATH (e.g. the Ghosty TUI). Check with `which -a ghosty`; run `npx @ghostystudio/cli` to be sure.")}
10134
11177
  ${dim("Docs: https://www.ghosty.studio/docs/cli")}`;
10135
- var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
11178
+ var HELP_ES = `${bold("Uso:")} ghosty <comando> [opciones]
11179
+
11180
+ ${bold("Sesi\xF3n")}
11181
+ login Inicia sesi\xF3n con tu navegador (OAuth2). Un agente puede correrlo por ti.
11182
+ --device Inicia sesi\xF3n desde otro dispositivo con liga + c\xF3digo (autom\xE1tico por SSH/CI)
11183
+ logout Cierra sesi\xF3n y revoca la sesi\xF3n de este dispositivo
11184
+ whoami Muestra con qu\xE9 cuenta entraste
11185
+ use [espacio] Espacio por default para boards, agents create y agents ls [--clear]
11186
+ completion zsh|bash|fish Autocompletado con TAB: comandos, opciones, agentes y espacios
11187
+ usage Tu plan y cu\xE1nto llevas usado esta semana y este mes
11188
+
11189
+ ${bold("Agentes")}
11190
+ agents ls Lista tus agentes
11191
+ agents create Crea un agente --name [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
11192
+ [--workspace SLUG] [--channels teams=off,\u2026] nace en un espacio, activo en su Teams
11193
+ [--like AGENTE [--copy-dbs] [--board same|main|new|NOMBRE] [--no-notify] [--dry-run]] con la configuraci\xF3n de otro agente (no su prompt, env ni canales)
11194
+ agents export <id> El agente como archivo (YAML, sin secretos) [--out DIR: agent.yaml + sus skills]
11195
+ apply <archivo|dir> Deja un agente igual que un archivo: ense\xF1a el plan (+ ~ -), pregunta y aplica [--agent X] [--create] [--prune] [--dry-run]
11196
+ agents get <id> Muestra la configuraci\xF3n de un agente [--fields name,model,prompt] [--prompt-out ARCHIVO]
11197
+ agents set <id> Cambia identidad o motor [--name] [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
11198
+ [--connect google-drive,\u2026] [--disconnect \u2026] dale o qu\xEDtale acceso a tus conectores
11199
+ [--channels wa=on,teams=off] prende/apaga canales (wa = WhatsApp personal)
11200
+ [--voice elevenlabs:ID | kokoro:em_santa] [--voice-replies auto|off] [--auto-stage on|off]
11201
+ [--toolsets crm,sales | +crm,-sales] grupos de tools de gs que recibe
11202
+ [--prompt-base AGENTE|none --prompt-overlay ARCHIVO [--dry-run]] prompt = el de otro agente + ediciones, sincronizado
11203
+ agents doctor <id> Revisa la configuraci\xF3n: skills encimadas, tools que nombra el prompt, permisos rotos, turnos que fallan
11204
+ agents docs ls|grant|revoke <id> [doc] Documentos que lee y puede enviar [--write] [--no-deliver]
11205
+ agents docs grant <id> A B\u2026 Da varios documentos a la vez [--like AGENTE] copia los de otro agente
11206
+ agents docs add <id> Sube un documento y d\xE1selo --url URL | --file RUTA [--description] [--grant read|write|no]
11207
+ agents share|unshare <id> <correo> Comp\xE1rtelo sin cederlo [--role editor|admin] \xB7 agents shares <id> los lista
11208
+ agents rm <id> Borra un agente (s\xF3lo el due\xF1o; te pide escribir su nombre, o --yes)
11209
+ agents restart <id> Reinicia un agente ACP (needsRestart) para que cargue skills nuevas
11210
+ agents box <id> La m\xE1quina que lo atiende: \xBFya trae la imagen nueva? [--check RUTA]\u2026 [--contains RUTA=TEXTO]\u2026 [--recycle]
11211
+ agents demo enable|disable <id> Prende o apaga su liga p\xFAblica de demo
11212
+ agents demo <id> Liga p\xFAblica de demo /p/<slug>: cualquiera lo prueba sin cuenta y \xABQued\xE1rmelo\xBB lo clona a la suya
11213
+ [--slug NOMBRE] [--expires "AAAA-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXTO] [--chips "a|b|c"] [--starter TEXTO] [--sponsor on|off] [--rotate]
11214
+
11215
+ ${bold("Configurar un agente")}
11216
+ files ls|get|put|rm <id> [ruta] Archivos en la m\xE1quina del agente [--file LOCAL] [--out LOCAL]
11217
+ skills enable|disable <id> <slug> Prende o apaga una de sus skills (o una de Ghosty)
11218
+ skills house <id> Las skills de Ghosty en bloque [--only a,b | --enable a,b | --disable a,b] [--dry-run]
11219
+ skills ls|get|add|rm <id> [slug] Skills (add del cat\xE1logo, --file SKILL.md o --dir CARPETA; get lista, --out DIR guarda; rm deja copia)
11220
+ skills templates <id> Plantillas de pdf-doc que ve: las suyas y las de Ghosty, y qu\xE9 carpeta gana
11221
+ dbs ls|tables|query|import|rehost|grant|revoke <id> [db] Bases de datos que usa [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run] [--table T --column C]
11222
+ mcp get|set <id> Servidores MCP [--file servers.json]
11223
+ channels whatsapp ls|enable|disable <id> [n\xFAmero] Qu\xE9 n\xFAmeros de WhatsApp Business contesta [--dry-run]
11224
+ channels whatsapp link|unlink <id> --integration ID --answer on|off Trae un n\xFAmero de WhatsApp Business de Formmy/EasyBits a este agente, o devu\xE9lvelo [--forward] [--dry-run]
11225
+ channels whatsapp history <id> --integration ID [--days 7] Trae de Formmy sus tarjetas, mensajes y pedidos recientes (sin duplicar) [--dry-run]
11226
+ channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no Mueve aqu\xED una sesi\xF3n de WhatsApp personal, sin QR [--dry-run] [--replace] [--no-connect]
11227
+ channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
11228
+ channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
11229
+ channels wa groups ls|enable|disable|set|create <id> [grupo] Grupos de WhatsApp personal que contesta, y como qui\xE9n [--role equipo|cliente] [--dry-run]
11230
+ boards ls|create|assign|move-cards|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas
11231
+ cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--limit 20]
11232
+ cards show <id> <tarjeta> El hilo de una tarjeta: qui\xE9n escribi\xF3, texto, adjuntos y si gs guard\xF3 copia (folio, tel\xE9fono o id)
11233
+ cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
11234
+
11235
+ ${bold("Hablar con un agente")}
11236
+ try <id> "mensaje" Un turno de prueba; imprime la respuesta completa [--session] [--reset]
11237
+ --waba --session TEL \u2026por el camino real de WhatsApp, sin enviar nada; lista lo que mandar\xEDa (tools tambi\xE9n) [--media ARCHIVO [--as-formmy]] [--history ARCHIVO]
11238
+ --replay ARCHIVO --out DIR Repite conversaciones reales; el reporte se escribe local [--max 15] [--concurrency 8] [--resume]
11239
+ chat <id> ["mensaje"] Conversaci\xF3n en streaming (sin mensaje = interactivo) [--conversation ID]
11240
+ conversations ls <id> Lista conversaciones
11241
+ conversations show <id> <conversaci\xF3n> Qu\xE9 se dijo [--limit 20] (0 = todo)
11242
+ turns ls <id> Turnos recientes y por qu\xE9 fallaron [--errors] [--since 1h] [--limit N]
11243
+ turns show <id> [turno] Qu\xE9 lleg\xF3, qu\xE9 contest\xF3 y en qu\xE9 se fue el tiempo (default: el \xFAltimo) [--full]
11244
+
11245
+ ${bold("Turnos programados")} ${dim("(cada corrida cae en una conversaci\xF3n y avisa a tu tel\xE9fono; una respuesta de exactamente OK no avisa)")}
11246
+ schedule ls <id> Turnos pendientes y corridas recientes
11247
+ schedule add <id> "qu\xE9 hacer" Programa un turno --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]
11248
+ schedule rm <id> <schedule-id> Cancela un turno pendiente
11249
+
11250
+ ${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ver: ghosty partner --help)")}
11251
+ partner ls | enable | credentials | agent | tenants | audit | try
11252
+
11253
+ ${bold("Tu cuenta")}
11254
+ me files ls|upload|rm Archivos de tu cuenta [--kind image|video|audio|document|other]
11255
+ credentials ls|set|rm [proveedor] Llaves de proveedor en tu b\xF3veda (prompt oculto, stdin o --from-env VAR; nunca como argumento)
11256
+ docs [tema] Abre la documentaci\xF3n
11257
+
11258
+ ${bold("Opciones globales")}
11259
+ <agente> En todos lados: el NOMBRE de un agente (sin importar may\xFAsculas) o su id
11260
+ --json Salida para m\xE1quinas (stdout es s\xF3lo JSON)
11261
+ --token <token> Usa este token (o env GHOSTY_TOKEN). Un token de agente gat_\u2026 s\xF3lo llega a ese agente.
11262
+ --profile <nombre> Usa una sesi\xF3n guardada aparte (o env GHOSTY_PROFILE), p. ej. una por cuenta
11263
+ --lang es|en Idioma del CLI (o env GHOSTY_LANG; si no, el de LANG)
11264
+ --yes S\xE1ltate las confirmaciones (obligatorio para rm sin terminal)
11265
+ -h, --help Muestra la ayuda (tambi\xE9n: ghosty <comando> --help, ghosty help <comando>)
11266
+ -v, --version Muestra la versi\xF3n
11267
+
11268
+ ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
11269
+ ${dim("Nota: `ghosty` puede chocar con otro binario en tu PATH (p. ej. la TUI de Ghosty). Rev\xEDsalo con `which -a ghosty`; corre `npx @ghostystudio/cli` para ir a la segura.")}
11270
+ ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
11271
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
10136
11272
  var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
10137
11273
  function unknown(what, input, known, helpCmd) {
10138
11274
  const s = suggest(input, known);
10139
- return usage(`Unknown command: ${what}.`, s ? `Did you mean "${s}"? Run: ${helpCmd}` : `Run: ${helpCmd}`);
11275
+ return usage(t(`Unknown command: ${what}.`, `Comando desconocido: ${what}.`), s ? t(`Did you mean "${s}"? Run: ${helpCmd}`, `\xBFQuisiste decir "${s}"? Corre: ${helpCmd}`) : t(`Run: ${helpCmd}`, `Corre: ${helpCmd}`));
10140
11276
  }
10141
11277
  function showHelp() {
10142
11278
  if (process.stdout.isTTY && !process.env.NO_COLOR && !globals.json) process.stdout.write(BANNER + "\n\n");
10143
- process.stdout.write(HELP + "\n");
10144
- }
10145
- function agentArgIndex(pos) {
10146
- const [cmd, sub] = pos;
10147
- switch (cmd) {
10148
- case "agents":
10149
- if (sub === "docs" || sub === "documents") return 3;
10150
- return sub && !["ls", "list", "create", "new"].includes(sub) ? 2 : null;
10151
- case "files":
10152
- case "skills":
10153
- case "mcp":
10154
- case "dbs":
10155
- case "db":
10156
- case "turns":
10157
- case "board":
10158
- case "conversations":
10159
- case "schedule":
10160
- return 2;
10161
- case "try":
10162
- case "chat":
10163
- return 1;
10164
- case "channels":
10165
- return pos[2] === "groups" ? 4 : 3;
10166
- case "boards":
10167
- return sub === "assign" ? 2 : null;
10168
- default:
10169
- return null;
10170
- }
11279
+ process.stdout.write(t(HELP_EN, HELP_ES) + "\n");
10171
11280
  }
10172
11281
  async function useCmd(slug, v) {
10173
11282
  const cfg = await readConfig();
10174
11283
  if (v.clear) {
10175
11284
  delete cfg.workspace;
10176
11285
  await writeConfig(cfg);
10177
- return out({ workspace: null }, () => `${lavender("\u2713")} No default workspace.`);
11286
+ return out({ workspace: null }, () => `${lavender("\u2713")} ${t("No default workspace.", "Sin espacio por default.")}`);
10178
11287
  }
10179
- if (!slug) return out({ workspace: cfg.workspace ?? null }, () => cfg.workspace ? `Workspace: ${bold(cfg.workspace)}` : dim("No default workspace. Set one: ghosty use <workspace>"));
11288
+ if (!slug) return out({ workspace: cfg.workspace ?? null }, () => cfg.workspace ? `${t("Workspace", "Espacio")}: ${bold(cfg.workspace)}` : dim(t("No default workspace. Set one: ghosty use <workspace>", "Sin espacio por default. Elige uno: ghosty use <espacio>")));
10180
11289
  await api(`/api/v2/workspaces/${encodeURIComponent(slug)}/boards`);
10181
11290
  await writeConfig({ ...cfg, workspace: slug });
10182
- return out({ workspace: slug }, () => `${lavender("\u2713")} Using ${bold(slug)}: boards, agents create and agents ls default to it. ${dim("(ghosty use --clear to stop)")}`);
11291
+ return out({ workspace: slug }, () => t(`${lavender("\u2713")} Using ${bold(slug)}: boards, agents create and agents ls default to it. ${dim("(ghosty use --clear to stop)")}`, `${lavender("\u2713")} Usando ${bold(slug)}: boards, agents create y agents ls lo toman por default. ${dim("(ghosty use --clear para dejar de usarlo)")}`));
10183
11292
  }
10184
11293
  async function main() {
11294
+ if (process.argv[2] === "__complete") {
11295
+ const rest = process.argv.slice(3);
11296
+ return completeCmd(rest[0] === "--" ? rest.slice(1) : rest, COMMANDS);
11297
+ }
11298
+ const langAt = process.argv.findIndex((a) => a === "--lang" || a.startsWith("--lang="));
11299
+ if (langAt >= 0) setLang(detectLang(process.argv[langAt].includes("=") ? process.argv[langAt].split("=")[1] : process.argv[langAt + 1]));
10185
11300
  const { values: v, positionals: pos } = parseArgs({
10186
11301
  allowPositionals: true,
10187
11302
  strict: true,
@@ -10264,6 +11379,7 @@ async function main() {
10264
11379
  all: { type: "boolean" },
10265
11380
  clear: { type: "boolean" },
10266
11381
  expires: { type: "string" },
11382
+ lang: { type: "string" },
10267
11383
  agent: { type: "string" },
10268
11384
  create: { type: "boolean" },
10269
11385
  prune: { type: "boolean" },
@@ -10306,10 +11422,11 @@ async function main() {
10306
11422
  }
10307
11423
  });
10308
11424
  globals.json = Boolean(v.json);
11425
+ setLang(detectLang(v.lang));
10309
11426
  const tokens = splitTokens(pos, v);
10310
11427
  globals.token = tokens.session;
10311
11428
  globals.profile = v.profile;
10312
- if (v.version) return out({ version: "0.19.0" }, () => "0.19.0");
11429
+ if (v.version) return out({ version: "0.20.0" }, () => "0.20.0");
10313
11430
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
10314
11431
  const context = (await readConfig()).workspace;
10315
11432
  if (!explicitToken()?.startsWith("gat_")) {
@@ -10321,30 +11438,35 @@ async function main() {
10321
11438
  }
10322
11439
  }
10323
11440
  const [cmd, sub, a1, a2] = pos;
10324
- if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
11441
+ if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: partnerHelp() }, () => partnerHelp());
10325
11442
  const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
10326
- if (topic === "partner") return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
11443
+ if (topic === "partner") return out({ help: partnerHelp() }, () => partnerHelp());
10327
11444
  if (topic && helpText(topic)) return out({ help: helpText(topic) }, () => helpText(topic));
10328
11445
  if (!cmd || v.help || cmd === "help") return showHelp();
10329
11446
  switch (cmd) {
10330
11447
  case "login": {
10331
11448
  if (v.token) {
10332
- throw usage("--token is used per command, not saved.", "Set GHOSTY_TOKEN=<token> in your environment instead.");
11449
+ throw usage(t("--token is used per command, not saved.", "--token se usa en cada comando, no se guarda."), t("Set GHOSTY_TOKEN=<token> in your environment instead.", "Mejor pon GHOSTY_TOKEN=<token> en tu entorno."));
10333
11450
  }
10334
11451
  await (v.device || !hasLocalBrowser() ? loginDevice() : login());
10335
11452
  const me = await api("/api/v2/me");
10336
11453
  const cfg = await readConfig();
10337
11454
  await writeConfig({ ...cfg, email: me.email });
10338
- return out({ event: "logged_in", email: me.email }, () => `${lavender("\u2713")} Signed in as ${bold(me.email)}`);
11455
+ return out({ event: "logged_in", email: me.email }, () => `${lavender("\u2713")} ${t("Signed in as", "Sesi\xF3n iniciada como")} ${bold(me.email)}`);
10339
11456
  }
10340
11457
  case "logout":
10341
11458
  await logout();
10342
- return out({ loggedOut: true }, () => `${lavender("\u2713")} Signed out.`);
11459
+ return out({ loggedOut: true }, () => `${lavender("\u2713")} ${t("Signed out.", "Sesi\xF3n cerrada.")}`);
10343
11460
  case "whoami": {
10344
- if (explicitToken()?.startsWith("gat_")) return out({ tokenType: "agent" }, () => "Using an agent token (gat_\u2026): it reaches only its own agent.");
11461
+ if (explicitToken()?.startsWith("gat_")) return out({ tokenType: "agent" }, () => t("Using an agent token (gat_\u2026): it reaches only its own agent.", "Usando un token de agente (gat_\u2026): s\xF3lo llega a su propio agente."));
10345
11462
  const me = await api("/api/v2/me");
10346
11463
  return out(me, () => `${bold(me.email)}${dim(` \xB7 ${new URL(process.env.GHOSTY_URL || "https://www.ghosty.studio").host}`)}`);
10347
11464
  }
11465
+ case "completion": {
11466
+ const script = completionScript(sub ?? "");
11467
+ if (!script) throw usage(t("Which shell?", "\xBFQu\xE9 shell?"), "ghosty completion zsh | bash | fish");
11468
+ return void process.stdout.write(script);
11469
+ }
10348
11470
  case "use":
10349
11471
  return useCmd(sub, v);
10350
11472
  case "apply":
@@ -10355,7 +11477,7 @@ async function main() {
10355
11477
  if (v.workspace === "none") delete v.workspace;
10356
11478
  else if (!v.workspace && context) {
10357
11479
  v.workspace = context;
10358
- if (!globals.json) info(dim(`workspace ${context} (from ghosty use; --workspace none for a personal agent)`));
11480
+ if (!globals.json) info(dim(t(`workspace ${context} (from ghosty use; --workspace none for a personal agent)`, `espacio ${context} (de ghosty use; --workspace none para un agente personal)`)));
10359
11481
  }
10360
11482
  return agentsCreate(v);
10361
11483
  }
@@ -10398,7 +11520,7 @@ async function main() {
10398
11520
  case "conversations":
10399
11521
  if (sub === "ls" || sub === "list") return conversationsLs(a1);
10400
11522
  if (sub === "show") return conversationsShow(a1, a2, v);
10401
- throw usage(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, "Try: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>");
11523
+ throw usage(t(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, `Subcomando de conversations desconocido: ${sub ?? "(ninguno)"}.`), t("Try: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>", "Prueba: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>"));
10402
11524
  case "schedule":
10403
11525
  return scheduleCmd(sub, a1, a2, v);
10404
11526
  case "partner":
@@ -10408,7 +11530,7 @@ async function main() {
10408
11530
  case "me":
10409
11531
  if (sub === "files") return meFiles(a1, a2, v);
10410
11532
  if (sub === "usage") return meUsage();
10411
- throw usage(`Unknown me subcommand: ${sub ?? "(none)"}.`, "Try: ghosty me files ls");
11533
+ throw usage(t(`Unknown me subcommand: ${sub ?? "(none)"}.`, `Subcomando de me desconocido: ${sub ?? "(ninguno)"}.`), t("Try: ghosty me files ls", "Prueba: ghosty me files ls"));
10412
11534
  case "docs": {
10413
11535
  const url = `${await baseUrl()}/docs${sub ? `/${encodeURIComponent(sub)}` : "/cli"}`;
10414
11536
  if (globals.json) return out({ url });
@@ -10419,7 +11541,7 @@ async function main() {
10419
11541
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
10420
11542
  }
10421
11543
  }
10422
- var notice = updateNotice("0.19.0").catch(() => null);
11544
+ var notice = updateNotice("0.20.0").catch(() => null);
10423
11545
  main().then(
10424
11546
  async () => {
10425
11547
  const n = await notice;
@@ -10430,7 +11552,7 @@ main().then(
10430
11552
  if (e && typeof e === "object" && "code" in e && String(e.code).startsWith("ERR_PARSE_ARGS")) {
10431
11553
  const n = await notice;
10432
11554
  return fail(usage(e.message, n ? `${n}
10433
- This option may exist in the new version.` : `Run: ghosty --help (or update: ${UPGRADE})`));
11555
+ ${t("This option may exist in the new version.", "Esta opci\xF3n puede existir en la versi\xF3n nueva.")}` : t(`Run: ghosty --help (or update: ${UPGRADE})`, `Corre: ghosty --help (o actualiza: ${UPGRADE})`)));
10434
11556
  }
10435
11557
  return fail(e);
10436
11558
  }