@ghostystudio/cli 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/index.js +1697 -488
- 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((
|
|
771
|
-
const tagObj = match.find((
|
|
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((
|
|
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
|
|
1404
|
-
res = _stringify(
|
|
1403
|
+
const t2 = implicitKey && defaultKeyType || defaultStringType;
|
|
1404
|
+
res = _stringify(t2);
|
|
1405
1405
|
if (res === null)
|
|
1406
|
-
throw new Error(`Unsupported default string type ${
|
|
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((
|
|
1466
|
+
const match = tags.filter((t2) => t2.tag === item.tag);
|
|
1467
1467
|
if (match.length > 0)
|
|
1468
|
-
return match.find((
|
|
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((
|
|
1474
|
+
let match = tags.filter((t2) => t2.identify?.(obj));
|
|
1475
1475
|
if (match.length > 1) {
|
|
1476
|
-
const testMatch = match.filter((
|
|
1476
|
+
const testMatch = match.filter((t2) => t2.test);
|
|
1477
1477
|
if (testMatch.length > 0)
|
|
1478
1478
|
match = testMatch;
|
|
1479
1479
|
}
|
|
1480
|
-
tagObj = match.find((
|
|
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((
|
|
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((
|
|
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,609 @@ 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
|
+
// — Fotos sueltas de un catálogo (`dbs photos put`)
|
|
7573
|
+
["items: de 1 a 20 fotos por petici\xF3n", "items: 1 to 20 photos per request"],
|
|
7574
|
+
["no es JPEG/PNG/WebP", "not a JPEG/PNG/WebP"],
|
|
7575
|
+
["pasa de 10 MB", "larger than 10 MB"],
|
|
7576
|
+
["table, keyColumn y column: nombres v\xE1lidos", "table, keyColumn and column: valid names"],
|
|
7577
|
+
["no es JPEG/PNG/WebP ({})", "not JPEG/PNG/WebP ({})"],
|
|
7578
|
+
["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)"],
|
|
7579
|
+
["no se pudo bajar: {}", "couldn't download: {}"],
|
|
7580
|
+
// — Documentos
|
|
7581
|
+
["no hay un documento \xAB{}\xBB", `there's no document "{}"`],
|
|
7582
|
+
["manda url, o contentBase64 con name", "send url, or contentBase64 with name"],
|
|
7583
|
+
["faltan los bloques", "missing the blocks"],
|
|
7584
|
+
["este agente no tiene acceso a ese documento (mira doc_list)", "this agent has no access to that document (see doc_list)"],
|
|
7585
|
+
["ese documento es s\xF3lo para consultarlo, no para entregarlo", "that document is for reference only, not for sending"],
|
|
7586
|
+
// — Bases de datos
|
|
7587
|
+
["no hay una base \xAB{}\xBB", `there's no database "{}"`],
|
|
7588
|
+
["Ya tienes una base con ese nombre.", "You already have a database with that name."],
|
|
7589
|
+
["Tu plan incluye {} base{} de datos.", "Your plan includes {} database{}."],
|
|
7590
|
+
["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)."],
|
|
7591
|
+
["Nombre de tabla inv\xE1lido (letras, n\xFAmeros y _).", "Invalid table name (letters, numbers and _)."],
|
|
7592
|
+
["Nombre de tabla inv\xE1lido", "Invalid table name"],
|
|
7593
|
+
["nombres de tabla no v\xE1lidos: {}", "invalid table names: {}"],
|
|
7594
|
+
["nombres de tabla o columna inv\xE1lidos", "invalid table or column names"],
|
|
7595
|
+
["Tabla inv\xE1lida", "Invalid table"],
|
|
7596
|
+
["Tabla o columna inv\xE1lida", "Invalid table or column"],
|
|
7597
|
+
["Columna inv\xE1lida: {}", "Invalid column: {}"],
|
|
7598
|
+
["Columnas inv\xE1lidas", "Invalid columns"],
|
|
7599
|
+
["Una tabla necesita al menos una columna.", "A table needs at least one column."],
|
|
7600
|
+
["Sin valores", "No values"],
|
|
7601
|
+
["SQL vac\xEDo", "Empty SQL"],
|
|
7602
|
+
["falta sql", "missing sql"],
|
|
7603
|
+
["Aqu\xED s\xF3lo caben consultas SELECT", "Only SELECT queries are allowed here"],
|
|
7604
|
+
["S\xF3lo una sentencia por consulta", "Only one statement per query"],
|
|
7605
|
+
["S\xF3lo una sentencia por llamada", "Only one statement per call"],
|
|
7606
|
+
["La consulta intenta escribir; este acceso es de lectura", "The query tries to write; this access is read-only"],
|
|
7607
|
+
["Desde este canal s\xF3lo INSERT, UPDATE o DELETE", "From this channel only INSERT, UPDATE or DELETE"],
|
|
7608
|
+
["Desde este canal s\xF3lo puedes escribir en: {}", "From this channel you can only write to: {}"],
|
|
7609
|
+
["desde este canal no puedes leer \xAB{}\xBB", `from this channel you can't read "{}"`],
|
|
7610
|
+
["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"],
|
|
7611
|
+
["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'],
|
|
7612
|
+
["externalRowScope es { tabla: [columnas de tel\xE9fono] }", "externalRowScope is { table: [phone columns] }"],
|
|
7613
|
+
["externalWriteTables es una lista de tablas", "externalWriteTables is a list of tables"],
|
|
7614
|
+
["Cu\xE9ntame para qu\xE9 es la tabla.", "Tell me what the table is for."],
|
|
7615
|
+
["Dime qu\xE9 quieres consultar.", "Tell me what you want to query."],
|
|
7616
|
+
["No hay llave para pedir ayuda al modelo.", "There's no key to ask the model for help."],
|
|
7617
|
+
["El modelo no contest\xF3; int\xE9ntalo otra vez.", "The model didn't answer; try again."],
|
|
7618
|
+
["El modelo no contest\xF3 ({}).", "The model didn't answer ({})."],
|
|
7619
|
+
["El modelo no devolvi\xF3 SQL.", "The model returned no SQL."],
|
|
7620
|
+
["El modelo no propuso columnas; int\xE9ntalo con m\xE1s detalle.", "The model proposed no columns; try again with more detail."],
|
|
7621
|
+
["No parece un archivo .xlsx", "Doesn't look like an .xlsx file"],
|
|
7622
|
+
["No encontr\xE9 la hoja en el archivo", "I couldn't find the sheet in the file"],
|
|
7623
|
+
["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 {}"],
|
|
7624
|
+
["este agente no tiene acceso a esa base (mira db_list)", "this agent has no access to that database (see db_list)"],
|
|
7625
|
+
// importar de EasyBits y re-hospedar imágenes
|
|
7626
|
+
["from: s\xF3lo \xABeasybits\xBB por ahora", 'from: only "easybits" for now'],
|
|
7627
|
+
["falta sourceDb (id de la base en EasyBits)", "missing sourceDb (the database id in EasyBits)"],
|
|
7628
|
+
["falta easybitsKey", "missing easybitsKey"],
|
|
7629
|
+
["EasyBits rechaz\xF3 la llave (EASYBITS_API_KEY)", "EasyBits rejected the key (EASYBITS_API_KEY)"],
|
|
7630
|
+
["EasyBits no tiene la base {} (easybits db ls)", "EasyBits has no database {} (easybits db ls)"],
|
|
7631
|
+
["EasyBits no tiene: {}", "EasyBits doesn't have: {}"],
|
|
7632
|
+
["rehost: \xAB{}\xBB no es tabla.columna", `rehost: "{}" isn't table.column`],
|
|
7633
|
+
["{} tiene {} filas (m\xE1x. {})", "{} has {} rows (max {})"],
|
|
7634
|
+
["table y column: nombres v\xE1lidos", "table and column: valid names"],
|
|
7635
|
+
["{} no tiene la columna {}", "{} has no column {}"],
|
|
7636
|
+
["{} no tiene la tabla {}", "{} has no table {}"],
|
|
7637
|
+
["no parece un sqlite", "doesn't look like a sqlite file"],
|
|
7638
|
+
// — Tableros, conversaciones y CRM (moveCards, boards)
|
|
7639
|
+
["el tablero principal no se borra", "the main board can't be deleted"],
|
|
7640
|
+
["tiene leads: mu\xE9velos o arch\xEDvalos antes de borrarlo", "it has leads: move or archive them before deleting it"],
|
|
7641
|
+
["no hay un tablero \xAB{}\xBB en {} (ghosty boards ls {})", `there's no board "{}" in {} (ghosty boards ls {})`],
|
|
7642
|
+
["no hay un tablero \xAB{}\xBB en {}", `there's no board "{}" in {}`],
|
|
7643
|
+
["ya hay un tablero \xAB{}\xBB en {}", `there's already a board "{}" in {}`],
|
|
7644
|
+
["falta name", "missing name"],
|
|
7645
|
+
["no pude crear el tablero de {}", "I couldn't create the board for {}"],
|
|
7646
|
+
["ese tablero no es del espacio del agente", "that board isn't in the agent's workspace"],
|
|
7647
|
+
["ese tablero ya no existe", "that board no longer exists"],
|
|
7648
|
+
["ese tablero no existe o ya caduc\xF3", "that board doesn't exist or has expired"],
|
|
7649
|
+
["ese tablero no lo armaste t\xFA", "you didn't build that board"],
|
|
7650
|
+
["los tableros viven en un espacio de equipo, no en la cuenta", "boards live in a team workspace, not in the account"],
|
|
7651
|
+
["los tableros viven en un espacio de equipo; este agente es personal", "boards live in a team workspace; this agent is personal"],
|
|
7652
|
+
["espacio sin due\xF1o: {}", "workspace without an owner: {}"],
|
|
7653
|
+
["espacio sin due\xF1o", "workspace without an owner"],
|
|
7654
|
+
["from y to son el mismo tablero", "from and to are the same board"],
|
|
7655
|
+
["from y to: tableros de {} (id, nombre o main)", "from and to: boards in {} (id, name or main)"],
|
|
7656
|
+
["hace falta al menos un filtro: integration, column o before", "at least one filter is required: integration, column or before"],
|
|
7657
|
+
["hace falta un filtro: integration, phone o all", "a filter is required: integration, phone or all"],
|
|
7658
|
+
["phone: al menos 7 d\xEDgitos", "phone: at least 7 digits"],
|
|
7659
|
+
["no hay una conversaci\xF3n \xAB{}\xBB en el tablero {}", `there's no conversation "{}" on board {}`],
|
|
7660
|
+
["\xAB{}\xBB coincide con {} conversaciones: usa el folio o el id", '"{}" matches {} conversations: use the folio or the id'],
|
|
7661
|
+
["columna inv\xE1lida", "invalid column"],
|
|
7662
|
+
["columnas inv\xE1lidas", "invalid columns"],
|
|
7663
|
+
["direcci\xF3n inv\xE1lida", "invalid address"],
|
|
7664
|
+
["tags inv\xE1lidas", "invalid tags"],
|
|
7665
|
+
["pausa inv\xE1lida", "invalid pause"],
|
|
7666
|
+
["el nombre es obligatorio", "name is required"],
|
|
7667
|
+
["falta conversationId", "missing conversationId"],
|
|
7668
|
+
["intent desconocido: {}", "unknown intent: {}"],
|
|
7669
|
+
["un pedido necesita columna", "an order needs a column"],
|
|
7670
|
+
// — WhatsApp: ligar números, importar sesiones (importAgentWhatsApp) y grupos
|
|
7671
|
+
["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"],
|
|
7672
|
+
["integrationId: el id de la Integration de Formmy (24 hex)", "integrationId: the Formmy Integration id (24 hex)"],
|
|
7673
|
+
["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"],
|
|
7674
|
+
["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"],
|
|
7675
|
+
["este n\xFAmero no est\xE1 ligado en gs", "this number isn't linked in gs"],
|
|
7676
|
+
["el n\xFAmero no tiene espacio ni tablero", "the number has no workspace or board"],
|
|
7677
|
+
["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"],
|
|
7678
|
+
["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"],
|
|
7679
|
+
["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"],
|
|
7680
|
+
["WhatsApp rechaz\xF3 la sesi\xF3n (logout): hay que volver a parear", "WhatsApp rejected the session (logout): it has to be paired again"],
|
|
7681
|
+
["WhatsApp no est\xE1 conectado", "WhatsApp isn't connected"],
|
|
7682
|
+
["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)"],
|
|
7683
|
+
["este agente ya tiene una sesi\xF3n guardada{}: usa replace para reemplazarla", "this agent already has a saved session{}: use replace to replace it"],
|
|
7684
|
+
["su WhatsApp en gs est\xE1 \xAB{}\xBB: p\xE1usalo antes de importar", 'its WhatsApp in gs is "{}": pause it before importing'],
|
|
7685
|
+
["authCreds no parece una sesi\xF3n de Baileys (faltan noiseKey / signedIdentityKey)", "authCreds doesn't look like a Baileys session (missing noiseKey / signedIdentityKey)"],
|
|
7686
|
+
["la sesi\xF3n nunca termin\xF3 de parearse (authCreds.me.id vac\xEDo)", "the session never finished pairing (authCreds.me.id is empty)"],
|
|
7687
|
+
["el archivo dice +{} pero las credenciales son de +{}", "the file says +{} but the credentials are for +{}"],
|
|
7688
|
+
["faltan authKeys", "missing authKeys"],
|
|
7689
|
+
["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)?"],
|
|
7690
|
+
["grupo con jid inv\xE1lido: {}", "group with invalid jid: {}"],
|
|
7691
|
+
["falta jid", "missing jid"],
|
|
7692
|
+
["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"],
|
|
7693
|
+
["no pude obtener el enlace del grupo", "I couldn't get the group link"],
|
|
7694
|
+
["name + 2 opciones m\xEDnimo", "name + at least 2 options"],
|
|
7695
|
+
["emoji + un mensaje reciente", "emoji + a recent message"],
|
|
7696
|
+
["text o url requerido", "text or url required"],
|
|
7697
|
+
["sesi\xF3n desconocida", "unknown session"],
|
|
7698
|
+
["este turno no viene de tu WhatsApp personal", "this turn doesn't come from your personal WhatsApp"],
|
|
7699
|
+
["ya hay una respuesta en camino", "there's already a reply on its way"],
|
|
7700
|
+
// — Probar al agente (try) y turnos
|
|
7701
|
+
["falta text", "missing text"],
|
|
7702
|
+
["asFormmy va con as=waba y media", "asFormmy goes with as=waba and media"],
|
|
7703
|
+
["con as={}, session es el tel\xE9fono (10-15 d\xEDgitos)", "with as={}, session is the phone number (10-15 digits)"],
|
|
7704
|
+
["media: { url, mime?, filename?, type? } con url https", "media: { url, mime?, filename?, type? } with an https url"],
|
|
7705
|
+
['history: [{ role: "user"|"agent", text, at? }] (m\xE1x. 200)', 'history: [{ role: "user"|"agent", text, at? }] (max 200)'],
|
|
7706
|
+
["este hilo tiene un turno en curso pedido por otra persona", "this thread has a turn in progress requested by someone else"],
|
|
7707
|
+
["gs se reinici\xF3 a mitad del turno", "gs restarted mid-turn"],
|
|
7708
|
+
["el turno fall\xF3", "the turn failed"],
|
|
7709
|
+
["turno sin terminar tras {} ms", "turn unfinished after {} ms"],
|
|
7710
|
+
["steer sin respuesta", "steer got no response"],
|
|
7711
|
+
["el agente dej\xF3 de responder; turno cerrado", "the agent stopped responding; turn closed"],
|
|
7712
|
+
["el agente no devolvi\xF3 sessionId", "the agent returned no sessionId"],
|
|
7713
|
+
["el agente no pudo contestar; intenta de nuevo", "the agent couldn't answer; try again"],
|
|
7714
|
+
["la caja cerr\xF3 la conexi\xF3n a media respuesta", "the box closed the connection mid-response"],
|
|
7715
|
+
["la conversaci\xF3n se enfri\xF3; \xE1brela de nuevo para seguir", "the conversation went cold; open it again to continue"],
|
|
7716
|
+
["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"],
|
|
7717
|
+
["no se pudo firmar el ticket de la caja de este agente", "couldn't sign this agent's box ticket"],
|
|
7718
|
+
["{}: sin respuesta en {} ms", "{}: no response in {} ms"],
|
|
7719
|
+
["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."],
|
|
7720
|
+
["admisi\xF3n: {}", "admission: {}"],
|
|
7721
|
+
["sin noticias del encargo en 45 min", "no news from the task in 45 min"],
|
|
7722
|
+
["sin cierre: se dio por perdido", "never closed: given up as lost"],
|
|
7723
|
+
["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)"],
|
|
7724
|
+
// chat de la app (/me): conversaciones, archivos y docs
|
|
7725
|
+
["falta el contenido", "missing the content"],
|
|
7726
|
+
["falta el modelo", "missing the model"],
|
|
7727
|
+
["falta el t\xEDtulo", "missing the title"],
|
|
7728
|
+
["falta id u optionId", "missing id or optionId"],
|
|
7729
|
+
// — Turnos programados
|
|
7730
|
+
["dueAt no es una fecha", "dueAt isn't a date"],
|
|
7731
|
+
["falta qu\xE9 hacer", "missing what to do"],
|
|
7732
|
+
["cada {} min como m\xEDnimo", "at least every {} min"],
|
|
7733
|
+
["`until` como mucho a 30 d\xEDas", "`until` at most 30 days out"],
|
|
7734
|
+
["como mucho a 30 d\xEDas", "at most 30 days out"],
|
|
7735
|
+
["una repetici\xF3n necesita `until`", "a recurrence needs `until`"],
|
|
7736
|
+
["este agente ya tiene {} turnos pendientes", "this agent already has {} pending turns"],
|
|
7737
|
+
["ya hay {} pendientes en esta conversaci\xF3n", "there are already {} pending in this conversation"],
|
|
7738
|
+
["ya programaste {} turnos hoy; p\xEDdele al usuario que programe el resto", "you already scheduled {} turns today; ask the user to schedule the rest"],
|
|
7739
|
+
// — Saldo, planes y cuotas
|
|
7740
|
+
["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."],
|
|
7741
|
+
["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.'],
|
|
7742
|
+
["Se acab\xF3 tu sesi\xF3n. Vuelve a las {}.", "Your session limit is used up. Come back at {}."],
|
|
7743
|
+
["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."],
|
|
7744
|
+
["Se acab\xF3 tu uso de esta semana. Se renueva el {}.", "You've used up this week's usage. It renews on {}."],
|
|
7745
|
+
["Se acab\xF3 tu uso de este mes. Se renueva el {}.", "You've used up this month's usage. It renews on {}."],
|
|
7746
|
+
["Se acab\xF3 tu uso {}. Se renueva el {}.", "You've used up your usage {}. It renews on {}."],
|
|
7747
|
+
["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."],
|
|
7748
|
+
["Tu periodo de prueba termin\xF3 el {}. Escr\xEDbenos para seguir.", "Your trial period ended on {}. Write to us to continue."],
|
|
7749
|
+
["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."],
|
|
7750
|
+
["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."],
|
|
7751
|
+
["{} 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)."],
|
|
7752
|
+
["sin saldo", "no balance"],
|
|
7753
|
+
["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"],
|
|
7754
|
+
["suffixes requerido", "suffixes required"],
|
|
7755
|
+
["{}MB de {}MB de snapshots ({}%), tope de altas al {}%", "{}MB of {}MB of snapshots ({}%), sign-up cap at {}%"],
|
|
7756
|
+
["el host no reporta el disco de snapshots", "the host doesn't report the snapshots disk"],
|
|
7757
|
+
["el proveedor no reporta capacidad", "the provider doesn't report capacity"],
|
|
7758
|
+
["no se pudo medir: {}", "couldn't measure: {}"],
|
|
7759
|
+
// — Credenciales y vault
|
|
7760
|
+
["proveedor desconocido: {}", "unknown provider: {}"],
|
|
7761
|
+
["falta key", "missing key"],
|
|
7762
|
+
["el vault no est\xE1 configurado en este servidor", "the vault isn't configured on this server"],
|
|
7763
|
+
["El .env no puede ir vac\xEDo.", "The .env can't be empty."],
|
|
7764
|
+
['El repositorio va como "due\xF1o/repo".', 'The repository goes as "owner/repo".'],
|
|
7765
|
+
// — Partners (Deník y compañía) y Formmy
|
|
7766
|
+
["falta workspace (el slug de tu espacio)", "missing workspace (your workspace's slug)"],
|
|
7767
|
+
["espacio no encontrado", "workspace not found"],
|
|
7768
|
+
["el espacio {} no es partner (act\xEDvalo con: ghosty partner enable {})", "workspace {} isn't a partner (enable it with: ghosty partner enable {})"],
|
|
7769
|
+
["no tienes espacios de partner (act\xEDvalo con: ghosty partner enable <espacio>)", "you have no partner workspaces (enable one with: ghosty partner enable <workspace>)"],
|
|
7770
|
+
["tienes {} espacios de partner: di cu\xE1l con --workspace ({})", "you have {} partner workspaces: pick one with --workspace ({})"],
|
|
7771
|
+
["el partner est\xE1 apagado", "the partner is turned off"],
|
|
7772
|
+
["credencial revocada: emite una nueva", "credential revoked: issue a new one"],
|
|
7773
|
+
["m\xE1ximo {} credenciales activas: revoca una antes", "max {} active credentials: revoke one first"],
|
|
7774
|
+
["el partner a\xFAn no tiene agente (PUT /api/v2/partner/agent)", "the partner has no agent yet (PUT /api/v2/partner/agent)"],
|
|
7775
|
+
["el partner a\xFAn no tiene agente (PUT para crearlo)", "the partner has no agent yet (PUT to create it)"],
|
|
7776
|
+
["el partner a\xFAn no tiene agente", "the partner has no agent yet"],
|
|
7777
|
+
["el borrador es igual a la versi\xF3n vigente", "the draft is the same as the current version"],
|
|
7778
|
+
["no existe la versi\xF3n {}", "version {} doesn't exist"],
|
|
7779
|
+
["config debe ser un objeto", "config must be an object"],
|
|
7780
|
+
["config.timezone inv\xE1lida: {}", "invalid config.timezone: {}"],
|
|
7781
|
+
["config.{} no existe ({})", "config.{} doesn't exist ({})"],
|
|
7782
|
+
["mcp debe ser una lista", "mcp must be a list"],
|
|
7783
|
+
["m\xE1ximo 5 servidores MCP", "max 5 MCP servers"],
|
|
7784
|
+
["mcp[{}]: headers debe ser un objeto", "mcp[{}]: headers must be an object"],
|
|
7785
|
+
["mcp[{}]: header {} inv\xE1lido", "mcp[{}]: invalid header {}"],
|
|
7786
|
+
["mcp[{}]: name inv\xE1lido o repetido", "mcp[{}]: invalid or repeated name"],
|
|
7787
|
+
["mcp[{}]: placeholder desconocido {} (usa {})", "mcp[{}]: unknown placeholder {} (use {})"],
|
|
7788
|
+
["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"],
|
|
7789
|
+
["motor {} no corre en modo solo-MCP (hoy: {})", "engine {} doesn't run in MCP-only mode (today: {})"],
|
|
7790
|
+
["motor {} sin proveedor solo-MCP", "engine {} has no MCP-only provider"],
|
|
7791
|
+
["{} no entra en el plan del espacio (tope {}\xD7)", "{} isn't in the workspace's plan (cap {}\xD7)"],
|
|
7792
|
+
["externalId inv\xE1lido (1-128: letras, d\xEDgitos, . _ -)", "invalid externalId (1-128: letters, digits, . _ -)"],
|
|
7793
|
+
["tenant.externalId inv\xE1lido", "invalid tenant.externalId"],
|
|
7794
|
+
["tenant no existe", "tenant doesn't exist"],
|
|
7795
|
+
["groupId inv\xE1lido", "invalid groupId"],
|
|
7796
|
+
["groupId requerido", "groupId required"],
|
|
7797
|
+
["text requerido", "text required"],
|
|
7798
|
+
["este negocio est\xE1 suspendido", "this business is suspended"],
|
|
7799
|
+
["sin llave de {} para este espacio", "no {} key for this workspace"],
|
|
7800
|
+
["FORMMY_PARTNER_SECRET_GHOSTY ausente: no se puede hablar con Formmy", "FORMMY_PARTNER_SECRET_GHOSTY missing: can't talk to Formmy"],
|
|
7801
|
+
["Formmy respondi\xF3 {}", "Formmy responded {}"],
|
|
7802
|
+
// — Demos
|
|
7803
|
+
["falta la liga (slug) la primera vez", "missing the link (slug) the first time"],
|
|
7804
|
+
["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)"],
|
|
7805
|
+
["esa liga ya la usa otro agente", "that link is already used by another agent"],
|
|
7806
|
+
["este demo ya no est\xE1 disponible", "this demo is no longer available"],
|
|
7807
|
+
["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"],
|
|
7808
|
+
// — Cajas de trabajo, puertos y ACP
|
|
7809
|
+
["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."],
|
|
7810
|
+
["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."],
|
|
7811
|
+
["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: {}."],
|
|
7812
|
+
["Template desconocido. Los que puedes pedir: {}.", "Unknown template. The ones you can ask for: {}."],
|
|
7813
|
+
["Tama\xF1o desconocido. Elige: {}.", "Unknown size. Choose: {}."],
|
|
7814
|
+
["La caja no termin\xF3 de despertar. Reintenta en unos segundos.", "The box didn't finish waking up. Try again in a few seconds."],
|
|
7815
|
+
["command requerido", "command required"],
|
|
7816
|
+
["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)."],
|
|
7817
|
+
["Puerto inv\xE1lido: va entre 1024 y 65535.", "Invalid port: it must be between 1024 and 65535."],
|
|
7818
|
+
["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."],
|
|
7819
|
+
["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."],
|
|
7820
|
+
["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."],
|
|
7821
|
+
["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."],
|
|
7822
|
+
["L\xEDmite de subagentes concurrentes alcanzado, intenta en un momento.", "Concurrent subagent limit reached, try again in a moment."],
|
|
7823
|
+
["prompt requerido", "prompt required"],
|
|
7824
|
+
["el motor {} no es ACP: no tiene caja que gobernar aqu\xED", "engine {} isn't ACP: there's no box to manage here"],
|
|
7825
|
+
["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"],
|
|
7826
|
+
["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"],
|
|
7827
|
+
["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"],
|
|
7828
|
+
["la caja ACP {} arranc\xF3 en error: {}", "ACP box {} started in error: {}"],
|
|
7829
|
+
["la caja ACP {} no lleg\xF3 a running en 90s", "ACP box {} didn't reach running in 90s"],
|
|
7830
|
+
["la caja {} no lleg\xF3 a running (\xFAltimo estado: {})", "box {} didn't reach running (last state: {})"],
|
|
7831
|
+
["la caja del agente naci\xF3 muda (no arranc\xF3 {}-runtime): {}", "the agent's box came up mute ({}-runtime didn't start): {}"],
|
|
7832
|
+
["no se pudo re-rutear tras caja muerta", "couldn't re-route after a dead box"],
|
|
7833
|
+
["fleet route race sin ganador", "fleet route race with no winner"],
|
|
7834
|
+
["create: respuesta sin sandboxId", "create: response without sandboxId"],
|
|
7835
|
+
["warmpool.claim: respuesta sin sandboxId", "warmpool.claim: response without sandboxId"],
|
|
7836
|
+
["expose: respuesta sin url", "expose: response without url"],
|
|
7837
|
+
["expose-raw: respuesta sin hostPort", "expose-raw: response without hostPort"],
|
|
7838
|
+
// — Cajas compartidas: voz, render, video, ortografía, eventos
|
|
7839
|
+
["sin caja de eventos", "no events box"],
|
|
7840
|
+
["sin caja de llamadas", "no calls box"],
|
|
7841
|
+
["sin caja de ortograf\xEDa", "no spelling box"],
|
|
7842
|
+
["sin caja de render", "no render box"],
|
|
7843
|
+
["sin caja de transcripci\xF3n", "no transcription box"],
|
|
7844
|
+
["sin caja de video", "no video box"],
|
|
7845
|
+
["sin caja de voz", "no voice box"],
|
|
7846
|
+
["audio requerido", "audio required"],
|
|
7847
|
+
["audio vac\xEDo", "empty audio"],
|
|
7848
|
+
["base64 inv\xE1lido", "invalid base64"],
|
|
7849
|
+
["texto requerido", "text required"],
|
|
7850
|
+
["html requerido", "html required"],
|
|
7851
|
+
["file requerido", "file required"],
|
|
7852
|
+
["room requerido", "room required"],
|
|
7853
|
+
["jobId requerido", "jobId required"],
|
|
7854
|
+
["montaje desconocido", "unknown video job"],
|
|
7855
|
+
["clips requerido: [{name, url}]", "clips required: [{name, url}]"],
|
|
7856
|
+
["captions: falta segments [{start, end, text}] (de stt.transcribe)", "captions: missing segments [{start, end, text}] (from stt.transcribe)"],
|
|
7857
|
+
["captions: falta url https del video", "captions: missing the video's https url"],
|
|
7858
|
+
["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)"],
|
|
7859
|
+
["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)"],
|
|
7860
|
+
["youtube-audio: s\xF3lo links de youtube.com / youtu.be", "youtube-audio: only youtube.com / youtu.be links"],
|
|
7861
|
+
// — Meta Ads y Messenger
|
|
7862
|
+
["Falta configurar la app de Meta Ads en gs.", "The Meta Ads app isn't configured in gs yet."],
|
|
7863
|
+
["Fall\xF3 la llamada a Meta; intenta de nuevo.", "The call to Meta failed; try again."],
|
|
7864
|
+
["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."],
|
|
7865
|
+
["Elige la cuenta de anuncios en Ajustes \u2192 Apps \u2192 Ghosty Ads.", "Choose the ad account in Settings \u2192 Apps \u2192 Ghosty Ads."],
|
|
7866
|
+
["Elige la p\xE1gina en Ajustes \u2192 Apps \u2192 Ghosty Ads.", "Choose the page in Settings \u2192 Apps \u2192 Ghosty Ads."],
|
|
7867
|
+
["Esa campa\xF1a no es de la cuenta conectada.", "That campaign doesn't belong to the connected account."],
|
|
7868
|
+
["Esa cuenta de anuncios no est\xE1 entre las tuyas.", "That ad account isn't one of yours."],
|
|
7869
|
+
["Esa p\xE1gina no est\xE1 entre las tuyas.", "That page isn't one of yours."],
|
|
7870
|
+
["Esa p\xE1gina ya est\xE1 conectada en otra cuenta.", "That page is already connected in another account."],
|
|
7871
|
+
["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").`],
|
|
7872
|
+
["El link del anuncio tiene que ser https.", "The ad link must be https."],
|
|
7873
|
+
["El presupuesto diario m\xEDnimo es de $20.", "The minimum daily budget is $20."],
|
|
7874
|
+
["Deja al menos una ubicaci\xF3n.", "Keep at least one placement."],
|
|
7875
|
+
["Faltan el texto o el creativo del anuncio.", "The ad text or creative is missing."],
|
|
7876
|
+
["La campa\xF1a no tiene conjuntos.", "The campaign has no ad sets."],
|
|
7877
|
+
["La columna derecha s\xF3lo acepta imagen.", "The right column only accepts images."],
|
|
7878
|
+
["Un anuncio de columna derecha s\xF3lo sale en Facebook.", "A right-column ad only runs on Facebook."],
|
|
7879
|
+
["La fecha de fin tiene que ser a futuro.", "The end date must be in the future."],
|
|
7880
|
+
["La fecha de fin tiene que ser al menos ma\xF1ana.", "The end date must be at least tomorrow."],
|
|
7881
|
+
["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."],
|
|
7882
|
+
["Meta no acept\xF3 el token: {}", "Meta didn't accept the token: {}"],
|
|
7883
|
+
["Meta no devolvi\xF3 la imagen subida.", "Meta didn't return the uploaded image."],
|
|
7884
|
+
["Meta no devolvi\xF3 token", "Meta returned no token"],
|
|
7885
|
+
["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."],
|
|
7886
|
+
["Meta sigue procesando el video: vuelve a intentar en un minuto.", "Meta is still processing the video: try again in a minute."],
|
|
7887
|
+
["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."],
|
|
7888
|
+
["No pude suscribir la p\xE1gina al webhook: {}", "I couldn't subscribe the page to the webhook: {}"],
|
|
7889
|
+
// — Ajustes de plataforma (staff)
|
|
7890
|
+
["Campo desconocido \xAB{}\xBB en {}.", 'Unknown field "{}" in {}.'],
|
|
7891
|
+
["El ajuste de {} no es un objeto.", "The setting for {} is not an object."],
|
|
7892
|
+
["Plan inv\xE1lido en {}.", "Invalid plan in {}."],
|
|
7893
|
+
["imageQuality inv\xE1lida en {}.", "invalid imageQuality in {}."],
|
|
7894
|
+
["imagesPlan inv\xE1lido en {}.", "invalid imagesPlan in {}."],
|
|
7895
|
+
["Tiene que ser un objeto {correo: ajuste}.", "Must be an object {email: setting}."],
|
|
7896
|
+
["Tiene que ser un objeto {\u2026}", "Must be an object {\u2026}"],
|
|
7897
|
+
["flags tiene que ser un objeto {nombre: true|false}", "flags must be an object {name: true|false}"],
|
|
7898
|
+
["flags.{} tiene que ser true o false", "flags.{} must be true or false"],
|
|
7899
|
+
["minBuild tiene que ser un entero \u2265 0", "minBuild must be an integer \u2265 0"],
|
|
7900
|
+
["suggestBuild tiene que ser un entero \u2265 0", "suggestBuild must be an integer \u2265 0"],
|
|
7901
|
+
["\xAB{}\xBB no parece correo.", `"{}" doesn't look like an email.`],
|
|
7902
|
+
// — Tools del agente (tools/dispatch): los lee el modelo, pero también pueden salir por el CLI
|
|
7903
|
+
["Pon en tu respuesta: [Nota de voz]({})", "Put in your reply: [Nota de voz]({})"],
|
|
7904
|
+
["Pon en tu respuesta: ", "Put in your reply: "],
|
|
7905
|
+
["Pon en tu respuesta: [{}]({})", "Put in your reply: [{}]({})"],
|
|
7906
|
+
["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."],
|
|
7907
|
+
["Tarjeta creada ({}), pero {}.", "Card created ({}), but {}."],
|
|
7908
|
+
["{}. Lo dem\xE1s s\xED se guard\xF3.", "{}. Everything else was saved."],
|
|
7909
|
+
["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."],
|
|
7910
|
+
["Ya la escuch\xF3; no repitas lo que dijiste por texto.", "They already heard it; don't repeat what you said in text."],
|
|
7911
|
+
["Ya lo tiene; no repitas el enlace.", "They already have it; don't repeat the link."],
|
|
7912
|
+
["WhatsApp no acept\xF3 la nota de voz; contesta por texto", "WhatsApp didn't accept the voice note; reply in text"],
|
|
7913
|
+
["no se pudo generar la voz; contesta por texto", "couldn't generate the voice; reply in text"],
|
|
7914
|
+
["no se mand\xF3: {}", "not sent: {}"],
|
|
7915
|
+
["`emoji`: un emoji", "`emoji`: one emoji"],
|
|
7916
|
+
["`para` no es una direcci\xF3n v\xE1lida", "`para` isn't a valid address"],
|
|
7917
|
+
["`responder_a` no es una direcci\xF3n v\xE1lida", "`responder_a` isn't a valid address"],
|
|
7918
|
+
["`pausa` inv\xE1lida", "invalid `pausa`"],
|
|
7919
|
+
["falta `nombre`", "missing `nombre`"],
|
|
7920
|
+
["falta `que`", "missing `que`"],
|
|
7921
|
+
["falta `tarjeta` (o `tarjetas`)", "missing `tarjeta` (or `tarjetas`)"],
|
|
7922
|
+
["falta `texto`", "missing `texto`"],
|
|
7923
|
+
["falta `titulo`", "missing `titulo`"],
|
|
7924
|
+
["faltan `asunto` o `cuerpo`", "missing `asunto` or `cuerpo`"],
|
|
7925
|
+
["falta base64", "missing base64"],
|
|
7926
|
+
["falta el texto", "missing the text"],
|
|
7927
|
+
["manda al menos un rengl\xF3n en `items`", "send at least one row in `items`"],
|
|
7928
|
+
["manda al menos una columna", "send at least one column"],
|
|
7929
|
+
["monto inv\xE1lido", "invalid amount"],
|
|
7930
|
+
["el cuerpo pasa de 20,000 caracteres", "the body exceeds 20,000 characters"],
|
|
7931
|
+
["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)"],
|
|
7932
|
+
["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)"],
|
|
7933
|
+
["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"],
|
|
7934
|
+
["el due\xF1o no ha conectado Google Drive (Conectores \u2192 Google Drive)", "the owner hasn't connected Google Drive (Connectors \u2192 Google Drive)"],
|
|
7935
|
+
["el due\xF1o no ha conectado MercadoPago en /app/settings/credentials", "the owner hasn't connected MercadoPago in /app/settings/credentials"],
|
|
7936
|
+
["el due\xF1o no ha conectado Skydropx en /app/settings/credentials", "the owner hasn't connected Skydropx in /app/settings/credentials"],
|
|
7937
|
+
["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`],
|
|
7938
|
+
["el turno de esta llamada ya termin\xF3", "this call's turn already ended"],
|
|
7939
|
+
["no hay ning\xFAn turno en curso de este agente", "this agent has no turn in progress"],
|
|
7940
|
+
["hay varias conversaciones trabajando a la vez; int\xE9ntalo en un momento", "several conversations are running at once; try again in a moment"],
|
|
7941
|
+
["esta conversaci\xF3n no tiene tarjeta en el CRM", "this conversation has no card in the CRM"],
|
|
7942
|
+
["esta tool s\xF3lo la puede usar tu due\xF1o", "only your owner can use this tool"],
|
|
7943
|
+
["este agente no es de un espacio con Ghosty Ads", "this agent isn't in a workspace with Ghosty Ads"],
|
|
7944
|
+
["la tool {} no est\xE1 disponible en este canal ({})", "tool {} isn't available on this channel ({})"],
|
|
7945
|
+
["la tool {} pertenece al toolset \xAB{}\xBB, que este agente no tiene", `tool {} belongs to the "{}" toolset, which this agent doesn't have`],
|
|
7946
|
+
["no existe la tool \xAB{}\xBB", `tool "{}" doesn't exist`],
|
|
7947
|
+
["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"],
|
|
7948
|
+
["no encontr\xE9 esa tarea", "I couldn't find that task"],
|
|
7949
|
+
["no encontr\xE9 esa tarjeta", "I couldn't find that card"],
|
|
7950
|
+
["no encontr\xE9 ese seguimiento pendiente en esta conversaci\xF3n", "I couldn't find that pending follow-up in this conversation"],
|
|
7951
|
+
["no encuentro la tarjeta \xAB{}\xBB; l\xE9elas con crm_board_read", `I can't find the card "{}"; read them with crm_board_read`],
|
|
7952
|
+
["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)"],
|
|
7953
|
+
["no tienes tablero de ventas asignado", "you have no sales board assigned"],
|
|
7954
|
+
["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"],
|
|
7955
|
+
// — Infra interna (casi nunca llega al CLI, pero por si acaso)
|
|
7956
|
+
["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)."],
|
|
7957
|
+
["sqld: fetch sin namespace", "sqld: fetch without namespace"],
|
|
7958
|
+
["sqld: token sin namespace ser\xEDa un token maestro", "sqld: a token without namespace would be a master token"]
|
|
7959
|
+
];
|
|
7960
|
+
|
|
7961
|
+
// src/i18n.ts
|
|
7962
|
+
var current = null;
|
|
7963
|
+
function detectLang(flag, env = process.env) {
|
|
7964
|
+
const pick = (v) => v && /^es\b|^es[_-]/i.test(v) ? "es" : v && /^en\b|^en[_-]|^c$|^posix$/i.test(v) ? "en" : null;
|
|
7965
|
+
return pick(flag) ?? pick(env.GHOSTY_LANG) ?? pick(env.LC_ALL) ?? pick(env.LC_MESSAGES) ?? pick(env.LANG) ?? "en";
|
|
7966
|
+
}
|
|
7967
|
+
function setLang(l) {
|
|
7968
|
+
current = l;
|
|
7969
|
+
}
|
|
7970
|
+
var lang = () => current ?? (current = detectLang());
|
|
7971
|
+
var t = (en, es) => lang() === "es" ? es : en;
|
|
7972
|
+
var compiled = null;
|
|
7973
|
+
var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7974
|
+
function compile(es) {
|
|
7975
|
+
return new RegExp(`^${es.split("{}").map(escape).join("(.+?)")}$`, "s");
|
|
7976
|
+
}
|
|
7977
|
+
function serverText(msg, l = lang()) {
|
|
7978
|
+
if (l === "es" || !msg) return msg;
|
|
7979
|
+
compiled ??= SERVER_MESSAGES.map(([es, en]) => ({ re: compile(es), en }));
|
|
7980
|
+
for (const c of compiled) {
|
|
7981
|
+
const m = c.re.exec(msg);
|
|
7982
|
+
if (!m) continue;
|
|
7983
|
+
let i = 1;
|
|
7984
|
+
return c.en.replace(/\{\}/g, () => m[i++] ?? "");
|
|
7985
|
+
}
|
|
7986
|
+
return msg;
|
|
7987
|
+
}
|
|
7988
|
+
|
|
7386
7989
|
// src/util.ts
|
|
7387
7990
|
var EXIT = { OK: 0, API: 1, USAGE: 2, AUTH: 3 };
|
|
7388
7991
|
var CliError = class extends Error {
|
|
@@ -7408,7 +8011,7 @@ function out(data, human) {
|
|
|
7408
8011
|
else process.stdout.write(human() + "\n");
|
|
7409
8012
|
}
|
|
7410
8013
|
function table(rows, cols) {
|
|
7411
|
-
if (!rows.length) return dim("(empty)");
|
|
8014
|
+
if (!rows.length) return dim(t("(empty)", "(vac\xEDo)"));
|
|
7412
8015
|
const cell = (v) => v === null || v === void 0 ? "\u2014" : String(v);
|
|
7413
8016
|
const w = cols.map((c) => Math.max(c.length, ...rows.map((r) => cell(r[c]).length)));
|
|
7414
8017
|
const line2 = (vals) => vals.map((v, i) => v.padEnd(w[i])).join(" ").trimEnd();
|
|
@@ -7425,7 +8028,7 @@ function fail(e) {
|
|
|
7425
8028
|
if (globals.json) {
|
|
7426
8029
|
process.stdout.write(JSON.stringify({ error: err.message, code: err.code, ...err.hint ? { hint: err.hint } : {} }) + "\n");
|
|
7427
8030
|
} else {
|
|
7428
|
-
info(red("Error: ") + err.message);
|
|
8031
|
+
info(red(t("Error: ", "Error: ")) + err.message);
|
|
7429
8032
|
if (err.hint) info(dim(err.hint));
|
|
7430
8033
|
}
|
|
7431
8034
|
return exitAfterFlush(err.code);
|
|
@@ -7448,7 +8051,7 @@ var LOGIN_TIMEOUT_MS = 5 * 6e4;
|
|
|
7448
8051
|
function configPath() {
|
|
7449
8052
|
const profile = globals.profile || process.env.GHOSTY_PROFILE || "";
|
|
7450
8053
|
if (profile && !/^[A-Za-z0-9_-]{1,40}$/.test(profile)) {
|
|
7451
|
-
throw new CliError(`Invalid profile: ${profile}.`, EXIT.USAGE, "Use letters, digits, - or _");
|
|
8054
|
+
throw new CliError(t(`Invalid profile: ${profile}.`, `Perfil inv\xE1lido: ${profile}.`), EXIT.USAGE, t("Use letters, digits, - or _", "Usa letras, d\xEDgitos, - o _"));
|
|
7452
8055
|
}
|
|
7453
8056
|
return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "ghosty", profile ? `config.${profile}.json` : "config.json");
|
|
7454
8057
|
}
|
|
@@ -7480,17 +8083,17 @@ async function tokenRequest(base, form) {
|
|
|
7480
8083
|
});
|
|
7481
8084
|
const body = await res.json().catch(() => ({}));
|
|
7482
8085
|
if (!res.ok || !body.access_token) {
|
|
7483
|
-
throw new CliError(`Login failed (${body.error ?? res.status}).`, EXIT.AUTH, "Run: ghosty login");
|
|
8086
|
+
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
8087
|
}
|
|
7485
8088
|
return body;
|
|
7486
8089
|
}
|
|
7487
|
-
var CLOSE_PAGE = (ok, base) => `<!doctype html><html lang="
|
|
8090
|
+
var CLOSE_PAGE = (ok, base) => `<!doctype html><html lang="${lang()}"><meta charset="utf-8">
|
|
7488
8091
|
<meta name="viewport" content="width=device-width,initial-scale=1"><title>Ghosty CLI</title>
|
|
7489
8092
|
<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
8093
|
<div style="text-align:center;max-width:420px">
|
|
7491
8094
|
<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>
|
|
8095
|
+
<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>
|
|
8096
|
+
<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
8097
|
</div>`;
|
|
7495
8098
|
async function login() {
|
|
7496
8099
|
const base = await baseUrl();
|
|
@@ -7509,12 +8112,12 @@ async function login() {
|
|
|
7509
8112
|
clearTimeout(timer);
|
|
7510
8113
|
server.close();
|
|
7511
8114
|
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"));
|
|
8115
|
+
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
8116
|
});
|
|
7514
8117
|
let redirectUri2 = "";
|
|
7515
8118
|
const timer = setTimeout(() => {
|
|
7516
8119
|
server.close();
|
|
7517
|
-
reject(new CliError("Login timed out after 5 minutes.", EXIT.AUTH, "Run: ghosty login"));
|
|
8120
|
+
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
8121
|
}, LOGIN_TIMEOUT_MS);
|
|
7519
8122
|
server.listen(0, "127.0.0.1", () => {
|
|
7520
8123
|
const port = server.address().port;
|
|
@@ -7529,8 +8132,8 @@ async function login() {
|
|
|
7529
8132
|
code_challenge_method: "S256"
|
|
7530
8133
|
});
|
|
7531
8134
|
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}`));
|
|
8135
|
+
info(`${lavender("\u203A")} ${t("Opening your browser to sign in to Ghosty\u2026", "Abriendo tu navegador para entrar a Ghosty\u2026")}`);
|
|
8136
|
+
info(dim(t(` If it doesn't open, visit: ${url}`, ` Si no se abre, entra a: ${url}`)));
|
|
7534
8137
|
void openUrl(url);
|
|
7535
8138
|
});
|
|
7536
8139
|
});
|
|
@@ -7563,21 +8166,21 @@ async function loginDevice() {
|
|
|
7563
8166
|
body: new URLSearchParams({ client_id: CLIENT_ID, scope: SCOPES })
|
|
7564
8167
|
});
|
|
7565
8168
|
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);
|
|
8169
|
+
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
8170
|
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)}:`);
|
|
8171
|
+
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
8172
|
info(` ${d.verification_uri_complete}`);
|
|
7570
8173
|
let interval = (d.interval ?? 5) * 1e3;
|
|
7571
8174
|
const deadline = Date.now() + (d.expires_in ?? 600) * 1e3;
|
|
7572
8175
|
while (Date.now() < deadline) {
|
|
7573
8176
|
await new Promise((r) => setTimeout(r, interval));
|
|
7574
|
-
const
|
|
8177
|
+
const tokenRes = await fetch(`${base}/oauth2/token`, {
|
|
7575
8178
|
method: "POST",
|
|
7576
8179
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
7577
8180
|
body: new URLSearchParams({ client_id: CLIENT_ID, grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code: d.device_code })
|
|
7578
8181
|
});
|
|
7579
|
-
const body = await
|
|
7580
|
-
if (
|
|
8182
|
+
const body = await tokenRes.json().catch(() => ({}));
|
|
8183
|
+
if (tokenRes.ok && body.access_token) {
|
|
7581
8184
|
const prev = await readConfig();
|
|
7582
8185
|
const cfg = {
|
|
7583
8186
|
...prev.baseUrl ? { baseUrl: prev.baseUrl } : {},
|
|
@@ -7593,10 +8196,10 @@ async function loginDevice() {
|
|
|
7593
8196
|
interval += 5e3;
|
|
7594
8197
|
continue;
|
|
7595
8198
|
}
|
|
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 ??
|
|
8199
|
+
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"));
|
|
8200
|
+
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
8201
|
}
|
|
7599
|
-
throw new CliError("The code expired before it was confirmed.", EXIT.AUTH, "Run: ghosty login");
|
|
8202
|
+
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
8203
|
}
|
|
7601
8204
|
async function refresh(cfg) {
|
|
7602
8205
|
if (!cfg.refreshToken) return null;
|
|
@@ -7634,8 +8237,8 @@ async function currentToken() {
|
|
|
7634
8237
|
if (explicit) return explicit;
|
|
7635
8238
|
let cfg = await readConfig();
|
|
7636
8239
|
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."));
|
|
8240
|
+
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)"));
|
|
8241
|
+
info(dim(t("Not logged in yet \u2014 let's fix that.", "Todav\xEDa no inicias sesi\xF3n; vamos a arreglarlo.")));
|
|
7639
8242
|
cfg = await (hasLocalBrowser() ? login() : loginDevice());
|
|
7640
8243
|
}
|
|
7641
8244
|
if (cfg.expiresAt !== void 0 && cfg.expiresAt - Date.now() < 6e4) cfg = await refresh(cfg) ?? cfg;
|
|
@@ -7650,7 +8253,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
7650
8253
|
const req = httpsRequest(`${base}${path}`, {
|
|
7651
8254
|
method: "POST",
|
|
7652
8255
|
timeout: timeoutMs,
|
|
7653
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
8256
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.21.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
7654
8257
|
}, (res) => {
|
|
7655
8258
|
let buf = "";
|
|
7656
8259
|
res.on("data", (c) => buf += c);
|
|
@@ -7664,7 +8267,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
7664
8267
|
resolve3({ status: res.statusCode ?? 0, retryAfter: retryAfterSeconds(res.headers["retry-after"]), body: parsed });
|
|
7665
8268
|
});
|
|
7666
8269
|
});
|
|
7667
|
-
req.on("timeout", () => req.destroy(new Error(`no answer in ${Math.round(timeoutMs / 6e4)} min`)));
|
|
8270
|
+
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
8271
|
req.on("error", (e) => resolve3({ status: 0, retryAfter: null, body: { error: String(e) } }));
|
|
7669
8272
|
req.end(data);
|
|
7670
8273
|
});
|
|
@@ -7672,8 +8275,8 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
7672
8275
|
function retryAfterSeconds(h, now = Date.now()) {
|
|
7673
8276
|
if (!h) return null;
|
|
7674
8277
|
if (/^\d+$/.test(h.trim())) return Number(h.trim());
|
|
7675
|
-
const
|
|
7676
|
-
return Number.isNaN(
|
|
8278
|
+
const t2 = Date.parse(h);
|
|
8279
|
+
return Number.isNaN(t2) ? null : Math.max(0, Math.round((t2 - now) / 1e3));
|
|
7677
8280
|
}
|
|
7678
8281
|
async function api(path, opts = {}) {
|
|
7679
8282
|
const res = await request(path, opts);
|
|
@@ -7692,7 +8295,8 @@ async function request(path, opts = {}, retried = false) {
|
|
|
7692
8295
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
7693
8296
|
headers: {
|
|
7694
8297
|
authorization: `Bearer ${token}`,
|
|
7695
|
-
"user-agent": `ghosty-cli/${"0.
|
|
8298
|
+
"user-agent": `ghosty-cli/${"0.21.0"}`,
|
|
8299
|
+
"accept-language": lang(),
|
|
7696
8300
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
7697
8301
|
...opts.headers
|
|
7698
8302
|
},
|
|
@@ -7713,12 +8317,13 @@ async function toError(res) {
|
|
|
7713
8317
|
body = JSON.parse(text);
|
|
7714
8318
|
} catch {
|
|
7715
8319
|
}
|
|
7716
|
-
const
|
|
7717
|
-
|
|
7718
|
-
if (res.status ===
|
|
7719
|
-
if (res.status ===
|
|
8320
|
+
const raw = body.message || body.error_description || (body.detalle && body.error ? `${body.error}: ${body.detalle}` : body.error) || text.slice(0, 300) || res.statusText;
|
|
8321
|
+
const detail = serverText(raw);
|
|
8322
|
+
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"));
|
|
8323
|
+
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"));
|
|
8324
|
+
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
8325
|
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);
|
|
8326
|
+
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
8327
|
}
|
|
7723
8328
|
return new CliError(`${res.status} ${detail}`, EXIT.API);
|
|
7724
8329
|
}
|
|
@@ -7732,18 +8337,18 @@ import { dirname as dirname2, join as join3, relative, resolve } from "node:path
|
|
|
7732
8337
|
import { createInterface } from "node:readline/promises";
|
|
7733
8338
|
var canPrompt = () => Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
7734
8339
|
function requireYesIfHeadless(yes, retry) {
|
|
7735
|
-
if (!yes && !canPrompt()) throw usage("--yes required when not running interactively.", `Run: ${retry} --yes`);
|
|
8340
|
+
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
8341
|
}
|
|
7737
8342
|
async function confirm(question, opts) {
|
|
7738
8343
|
if (opts.yes) return;
|
|
7739
|
-
if (!canPrompt()) throw usage("--yes required when not running interactively.", `Run: ${opts.retry} --yes`);
|
|
8344
|
+
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
8345
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
7741
8346
|
try {
|
|
7742
8347
|
const ask = opts.expect ? `${question}
|
|
7743
|
-
Type ${bold(opts.expect)} to confirm: ` : `${question} [y/N] `;
|
|
8348
|
+
${t("Type", "Escribe")} ${bold(opts.expect)} ${t("to confirm", "para confirmar")}: ` : `${question} ${t("[y/N]", "[s/N]")} `;
|
|
7744
8349
|
const a = (await rl.question(ask)).trim();
|
|
7745
|
-
const ok = opts.expect ? a === opts.expect : /^y(es)
|
|
7746
|
-
if (!ok) throw usage("Cancelled.");
|
|
8350
|
+
const ok = opts.expect ? a === opts.expect : /^(y(es)?|s[ií]?)$/i.test(a);
|
|
8351
|
+
if (!ok) throw usage(t("Cancelled.", "Cancelado."));
|
|
7747
8352
|
} finally {
|
|
7748
8353
|
rl.close();
|
|
7749
8354
|
}
|
|
@@ -7772,7 +8377,7 @@ async function tryPost(id, body) {
|
|
|
7772
8377
|
const r = await postLong(`/api/v2/agents/${enc(id)}/try`, body);
|
|
7773
8378
|
if ((r.status === 503 || r.status === 409 || r.status === 0) && attempt < 40) {
|
|
7774
8379
|
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`));
|
|
8380
|
+
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
8381
|
await sleep(wait * 1e3);
|
|
7777
8382
|
continue;
|
|
7778
8383
|
}
|
|
@@ -7795,7 +8400,7 @@ function richLine(s) {
|
|
|
7795
8400
|
const str = (k) => typeof d[k] === "string" ? d[k] : "";
|
|
7796
8401
|
switch (s.rich) {
|
|
7797
8402
|
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")}`) : ""}`;
|
|
8403
|
+
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
8404
|
case "boton":
|
|
7800
8405
|
return `\u{1F518} ${str("boton")} ${dim(str("url"))} ${str("texto")}`;
|
|
7801
8406
|
case "contacto":
|
|
@@ -7803,9 +8408,9 @@ function richLine(s) {
|
|
|
7803
8408
|
case "encuesta":
|
|
7804
8409
|
return `\u{1F4CA} ${str("pregunta")}`;
|
|
7805
8410
|
case "reaccion":
|
|
7806
|
-
return `${str("emoji")} ${dim(`reaction to ${str("mensajeId") || "the last message"}`)}`;
|
|
8411
|
+
return `${str("emoji")} ${dim(t(`reaction to ${str("mensajeId") || "the last message"}`, `reacci\xF3n a ${str("mensajeId") || "el \xFAltimo mensaje"}`))}`;
|
|
7807
8412
|
case "cita":
|
|
7808
|
-
return `\u21A9\uFE0F ${str("texto")} ${dim(`quoting ${str("mensajeId") || "the last message"}`)}`;
|
|
8413
|
+
return `\u21A9\uFE0F ${str("texto")} ${dim(t(`quoting ${str("mensajeId") || "the last message"}`, `citando ${str("mensajeId") || "el \xFAltimo mensaje"}`))}`;
|
|
7809
8414
|
}
|
|
7810
8415
|
}
|
|
7811
8416
|
function sentText(sent) {
|
|
@@ -7815,33 +8420,33 @@ function sentText(sent) {
|
|
|
7815
8420
|
}
|
|
7816
8421
|
async function tryWaba(id, text, v) {
|
|
7817
8422
|
const phone = v.session ?? "";
|
|
7818
|
-
if (!/^\d{10,15}$/.test(phone)) throw usage("--waba needs --session <phone> (10-15 digits, a made-up number).", '
|
|
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.", '
|
|
7821
|
-
if (!text && !v.media?.length && !v.reset) throw usage("Missing message.", '
|
|
8423
|
+
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');
|
|
8424
|
+
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."));
|
|
8425
|
+
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');
|
|
8426
|
+
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
8427
|
let history;
|
|
7823
8428
|
if (v.history) {
|
|
7824
8429
|
const raw = JSON.parse(await readFile2(v.history, "utf8"));
|
|
7825
8430
|
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."));
|
|
8431
|
+
if (!Array.isArray(history)) throw usage(t(`${v.history}: expected [{role, text, at?}] or {messages: [...]}.`, `${v.history}: se esperaba [{role, text, at?}] o {messages: [...]}.`));
|
|
8432
|
+
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
8433
|
}
|
|
7829
8434
|
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)"));
|
|
8435
|
+
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
8436
|
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
|
|
8437
|
+
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
8438
|
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})`)}` : ""));
|
|
8439
|
+
${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)"))}` : "";
|
|
8440
|
+
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 ? `
|
|
8441
|
+
${dim(`(${t("warning", "aviso")}: ${serverText(r.body.error)})`)}` : ""));
|
|
7837
8442
|
}
|
|
7838
8443
|
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");
|
|
8444
|
+
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
8445
|
const max = Number(v.max ?? 15);
|
|
7841
8446
|
const conc = Math.max(1, Math.min(30, Number(v.concurrency ?? 8)));
|
|
7842
8447
|
const input = JSON.parse(await readFile2(v.replay, "utf8"));
|
|
7843
8448
|
const convs = input.sample ?? [];
|
|
7844
|
-
if (!convs.length) throw usage(`${v.replay}: no conversations in "sample".`, '
|
|
8449
|
+
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
8450
|
await mkdir2(v.out, { recursive: true });
|
|
7846
8451
|
const file = join2(v.out, "replay.json");
|
|
7847
8452
|
const prev = v.resume ? JSON.parse(await readFile2(file, "utf8").catch(() => "null")) : null;
|
|
@@ -7849,7 +8454,7 @@ async function tryReplay(id, v) {
|
|
|
7849
8454
|
report.done = false;
|
|
7850
8455
|
let saving = Promise.resolve();
|
|
7851
8456
|
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}`);
|
|
8457
|
+
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
8458
|
const queue = [...convs];
|
|
7854
8459
|
const worker = async () => {
|
|
7855
8460
|
for (let c = queue.shift(); c; c = queue.shift()) {
|
|
@@ -7869,7 +8474,7 @@ async function tryReplay(id, v) {
|
|
|
7869
8474
|
await save();
|
|
7870
8475
|
}
|
|
7871
8476
|
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` : ""}`);
|
|
8477
|
+
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
8478
|
}
|
|
7874
8479
|
};
|
|
7875
8480
|
await Promise.all(Array.from({ length: Math.min(conc, convs.length) }, worker));
|
|
@@ -7879,16 +8484,17 @@ async function tryReplay(id, v) {
|
|
|
7879
8484
|
const steps = Object.values(report.results).flatMap((r) => r.steps);
|
|
7880
8485
|
const failed = steps.filter((s) => s.status !== 200 || !s.reply).length;
|
|
7881
8486
|
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`)}`);
|
|
8487
|
+
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`)}
|
|
8488
|
+
${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
8489
|
if (failed) process.exitCode = 1;
|
|
7885
8490
|
}
|
|
7886
8491
|
|
|
7887
8492
|
// src/commands/agents.ts
|
|
7888
8493
|
var enc2 = encodeURIComponent;
|
|
7889
8494
|
var agentPath = (id, rest = "") => `/api/v2/agents/${enc2(id)}${rest}`;
|
|
8495
|
+
var WHAT_ES = { "agent id": "el id del agente", path: "la ruta", email: "el correo", "skill slug": "el slug de la skill" };
|
|
7890
8496
|
var need = (v, what, example) => {
|
|
7891
|
-
if (!v) throw usage(`Missing ${what}.`, `Example: ${example}`);
|
|
8497
|
+
if (!v) throw usage(t(`Missing ${what}.`, `Falta ${WHAT_ES[what] ?? what}.`), `${t("Example", "Ejemplo")}: ${example}`);
|
|
7892
8498
|
return v;
|
|
7893
8499
|
};
|
|
7894
8500
|
async function agentsLs(workspace) {
|
|
@@ -7905,14 +8511,14 @@ async function agentsLs(workspace) {
|
|
|
7905
8511
|
...a.compartidoPor ? { sharedBy: a.compartidoPor } : {}
|
|
7906
8512
|
}));
|
|
7907
8513
|
out(rows, () => `${table(rows, ["id", "name", "engine", "workspace", "lastActivity"])}${workspace ? `
|
|
7908
|
-
${dim(`only ${workspace} (ghosty use) \xB7 all: --all`)}` : ""}`);
|
|
8514
|
+
${dim(t(`only ${workspace} (ghosty use) \xB7 all: --all`, `s\xF3lo ${workspace} (ghosty use) \xB7 todos: --all`))}` : ""}`);
|
|
7909
8515
|
}
|
|
7910
8516
|
function parseEnv(raw) {
|
|
7911
8517
|
if (!raw) return void 0;
|
|
7912
8518
|
const env = {};
|
|
7913
8519
|
for (const pair of raw.split(",")) {
|
|
7914
8520
|
const i = pair.indexOf("=");
|
|
7915
|
-
if (i < 1) throw usage(`Bad --env entry: ${pair}.`, "Example: --env GHOSTY_THINKING_EFFORT=max");
|
|
8521
|
+
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
8522
|
env[pair.slice(0, i).trim()] = pair.slice(i + 1);
|
|
7917
8523
|
}
|
|
7918
8524
|
return env;
|
|
@@ -7922,7 +8528,7 @@ function parseChannels(raw) {
|
|
|
7922
8528
|
const ch = {};
|
|
7923
8529
|
for (const pair of raw.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
7924
8530
|
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)");
|
|
8531
|
+
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
8532
|
ch[k] = val === "on";
|
|
7927
8533
|
}
|
|
7928
8534
|
return ch;
|
|
@@ -7940,20 +8546,20 @@ async function agentsCreate(v) {
|
|
|
7940
8546
|
const channels = parseChannels(v.channels);
|
|
7941
8547
|
if (channels) body.channels = channels;
|
|
7942
8548
|
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.");
|
|
8549
|
+
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
8550
|
if (v.like && v["dry-run"]) {
|
|
7945
8551
|
const p = await api("/api/v2/me/agents", { method: "POST", body });
|
|
7946
|
-
return out(p, () => `${dim(
|
|
7947
|
-
${p.plan.join("\n")}
|
|
7948
|
-
${dim("Not copied: prompt, env, channels. Dry run: nothing created.")}`);
|
|
8552
|
+
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}` : ""}`)}
|
|
8553
|
+
${p.plan.map((x) => serverText(x)).join("\n")}
|
|
8554
|
+
${dim(t("Not copied: prompt, env, channels. Dry run: nothing created.", "No se copian: prompt, env, canales. Simulacro: no se cre\xF3 nada."))}`);
|
|
7949
8555
|
}
|
|
7950
8556
|
const r = await api("/api/v2/me/agents", { method: "POST", body });
|
|
7951
8557
|
const where = r.workspace ? `
|
|
7952
|
-
workspace: ${r.workspace}${r.handle ? ` \xB7 @${r.handle} in Teams` : dim(" \xB7 not active in Teams")}` : "";
|
|
8558
|
+
${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
8559
|
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"})`)}
|
|
8560
|
+
${r.copied.map((l) => ` ${serverText(l)}`).join("\n")}
|
|
8561
|
+
${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")}` : "";
|
|
8562
|
+
out(r, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${r.name} ${dim(`(${r.engine} \xB7 ${r.model ?? t("default", "por defecto")})`)}
|
|
7957
8563
|
id: ${r.id}${where}${copied}`);
|
|
7958
8564
|
}
|
|
7959
8565
|
async function agentsGet(id, fields, promptOut) {
|
|
@@ -7962,7 +8568,7 @@ async function agentsGet(id, fields, promptOut) {
|
|
|
7962
8568
|
const r = await api(agentPath(id, "?fields=prompt"));
|
|
7963
8569
|
const prompt = r.prompt ?? "";
|
|
7964
8570
|
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}`)}`);
|
|
8571
|
+
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
8572
|
}
|
|
7967
8573
|
const q = fields ? `?fields=${enc2(fields)}` : "?full=1";
|
|
7968
8574
|
out(await api(agentPath(id, q)));
|
|
@@ -7971,16 +8577,16 @@ async function agentsSet(id, v) {
|
|
|
7971
8577
|
id = need(id, "agent id", "ghosty agents set <agent-id> --prompt-file PROMPT.md");
|
|
7972
8578
|
if (v["prompt-base"]) {
|
|
7973
8579
|
const unlink = v["prompt-base"] === "none";
|
|
7974
|
-
if (!unlink && !v["prompt-overlay"]) throw usage("Missing --prompt-overlay FILE.",
|
|
8580
|
+
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
8581
|
const promptBase = unlink ? null : { agentId: v["prompt-base"], overlay: JSON.parse(await readFile3(v["prompt-overlay"], "utf8")) };
|
|
7976
8582
|
const r2 = await api(agentPath(id), { method: "PATCH", body: { promptBase, ...v["dry-run"] ? { dryRun: true } : {} } });
|
|
7977
8583
|
if (v["dry-run"] && r2.prompt) {
|
|
7978
8584
|
const out_ = v.out;
|
|
7979
8585
|
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
|
|
7981
|
-
${dim("Dry run: nothing changed.")}`);
|
|
8586
|
+
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."))}
|
|
8587
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
7982
8588
|
}
|
|
7983
|
-
return out(r2, () => r2.unlinked ? `${lavender("\u2713")} Unlinked: the prompt stays as it is and is edited by hand again
|
|
8589
|
+
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
8590
|
}
|
|
7985
8591
|
const body = {};
|
|
7986
8592
|
if (v.name) body.name = v.name;
|
|
@@ -7995,9 +8601,9 @@ ${dim("Dry run: nothing changed.")}`);
|
|
|
7995
8601
|
const cur = await api(agentPath(id, "?fields=tools"));
|
|
7996
8602
|
const known = Object.keys(cur.tools?.toolsets ?? {});
|
|
7997
8603
|
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(", ")}`);
|
|
8604
|
+
if (bad.length) throw usage(t(`Unknown toolset: ${bad.join(", ")}.`, `Toolset desconocido: ${bad.join(", ")}.`), `${t("Known", "Conocidos")}: ${known.join(", ")}`);
|
|
7999
8605
|
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).");
|
|
8606
|
+
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
8607
|
const next = new Set(delta ? known.filter((k) => cur.tools?.toolsets?.[k]?.granted) : []);
|
|
8002
8608
|
for (const x of items) {
|
|
8003
8609
|
if (x.startsWith("-")) next.delete(x.slice(1));
|
|
@@ -8015,67 +8621,67 @@ ${dim("Dry run: nothing changed.")}`);
|
|
|
8015
8621
|
const voice = {};
|
|
8016
8622
|
if (v.voice) {
|
|
8017
8623
|
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");
|
|
8624
|
+
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
8625
|
voice.engine = engine;
|
|
8020
8626
|
voice.voiceId = rest.join(":");
|
|
8021
8627
|
}
|
|
8022
8628
|
if (v["voice-replies"]) {
|
|
8023
|
-
if (v["voice-replies"] !== "auto" && v["voice-replies"] !== "off") throw usage("--voice-replies takes auto or off.");
|
|
8629
|
+
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
8630
|
voice.replies = v["voice-replies"];
|
|
8025
8631
|
}
|
|
8026
8632
|
if (Object.keys(voice).length) body.voice = voice;
|
|
8027
8633
|
if (v["auto-stage"]) {
|
|
8028
|
-
if (v["auto-stage"] !== "on" && v["auto-stage"] !== "off") throw usage("--auto-stage takes on or off.");
|
|
8634
|
+
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
8635
|
body.autoStage = v["auto-stage"] === "on";
|
|
8030
8636
|
}
|
|
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.");
|
|
8637
|
+
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
8638
|
const r = await api(agentPath(id), { method: "PATCH", body });
|
|
8033
|
-
out(r, () => `${lavender("\u2713")} Updated ${Object.keys(body).join(", ")}.`);
|
|
8639
|
+
out(r, () => `${lavender("\u2713")} ${t("Updated", "Actualizado")}: ${Object.keys(body).join(", ")}.`);
|
|
8034
8640
|
}
|
|
8035
8641
|
async function agentsRm(id, v = {}) {
|
|
8036
8642
|
id = need(id, "agent id", "ghosty agents rm <agent-id>");
|
|
8037
8643
|
requireYesIfHeadless(v.yes, `ghosty agents rm ${id}`);
|
|
8038
8644
|
if (!v.yes) {
|
|
8039
8645
|
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}` });
|
|
8646
|
+
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
8647
|
}
|
|
8042
|
-
out(await api(agentPath(id), { method: "DELETE" }), () => `${lavender("\u2713")} Deleted ${id}.`);
|
|
8648
|
+
out(await api(agentPath(id), { method: "DELETE" }), () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${id}.`);
|
|
8043
8649
|
}
|
|
8044
8650
|
async function agentsShare(sub, id, email, v) {
|
|
8045
8651
|
id = need(id, "agent id", "ghosty agents share <agent-id> <email> [--role admin|editor]");
|
|
8046
8652
|
if (sub === "shares") {
|
|
8047
8653
|
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(
|
|
8050
|
-
${rows.length ? table(rows, ["email", "role", "accepted"]) : dim("Not shared with anyone.")}`);
|
|
8654
|
+
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") }));
|
|
8655
|
+
return out(r2, () => `${dim(`${t("owner", "due\xF1o")}: ${r2.owner ?? "?"}`)}
|
|
8656
|
+
${rows.length ? table(rows, ["email", "role", "accepted"]) : dim(t("Not shared with anyone.", "No est\xE1 compartido con nadie."))}`);
|
|
8051
8657
|
}
|
|
8052
8658
|
email = need(email, "email", `ghosty agents ${sub} ${id} ana@empresa.com`);
|
|
8053
8659
|
if (sub === "unshare") {
|
|
8054
8660
|
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}` });
|
|
8661
|
+
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
8662
|
const r2 = await api(agentPath(id, "/shares"), { method: "DELETE", body: { email } });
|
|
8057
|
-
return out(r2, () => `${lavender("\u2713")} ${email} no longer has access
|
|
8663
|
+
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
8664
|
}
|
|
8059
|
-
if (v.role && v.role !== "admin" && v.role !== "editor") throw usage("--role takes admin or editor.");
|
|
8665
|
+
if (v.role && v.role !== "admin" && v.role !== "editor") throw usage(t("--role takes admin or editor.", "--role acepta admin o editor."));
|
|
8060
8666
|
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})`)}.`);
|
|
8667
|
+
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
8668
|
}
|
|
8063
8669
|
async function agentsRestart(id) {
|
|
8064
8670
|
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
|
|
8671
|
+
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
8672
|
}
|
|
8067
8673
|
async function tryTurn(id, text, v) {
|
|
8068
8674
|
id = need(id, "agent id", 'ghosty try <agent-id> "hello"');
|
|
8069
8675
|
if (v.replay) return tryReplay(id, v);
|
|
8070
|
-
if (v.whatsapp && !v.waba) info(dim("Note: --whatsapp is now --waba (the real WhatsApp path)."));
|
|
8676
|
+
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
8677
|
if (v.waba || v.whatsapp || v.media?.length || v.history) return tryWaba(id, text, v);
|
|
8072
|
-
if (!text && !v.reset) throw usage("Missing message.", '
|
|
8073
|
-
if (process.stderr.isTTY && text && !globals.json) info(dim("Thinking\u2026 (a test turn can take up to 3 minutes)"));
|
|
8678
|
+
if (!text && !v.reset) throw usage(t("Missing message.", "Falta el mensaje."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "hello"');
|
|
8679
|
+
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
8680
|
const r = await api(agentPath(id, "/try"), {
|
|
8075
8681
|
body: { text, session: v.session, reset: v.reset }
|
|
8076
8682
|
});
|
|
8077
8683
|
out(r, () => (r.text ?? "") + (r.error ? `
|
|
8078
|
-
${dim(`(warning: ${r.error})`)}` : ""));
|
|
8684
|
+
${dim(`(${t("warning", "aviso")}: ${serverText(r.error)})`)}` : ""));
|
|
8079
8685
|
}
|
|
8080
8686
|
async function filesCmd(sub, id, path, v) {
|
|
8081
8687
|
const ex = "ghosty files ls <agent-id> | get <agent-id> <path> | put <agent-id> <path> --file <local> | rm <agent-id> <path>";
|
|
@@ -8095,7 +8701,7 @@ ${table(r.files ?? [], cols)}`);
|
|
|
8095
8701
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
8096
8702
|
if (v.out) {
|
|
8097
8703
|
await writeFile3(v.out, bytes);
|
|
8098
|
-
out({ path: p, saved: v.out, bytes: bytes.length }, () => `${lavender("\u2713")} Saved ${bytes.length} bytes to ${v.out}`);
|
|
8704
|
+
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
8705
|
} else process.stdout.write(bytes);
|
|
8100
8706
|
return;
|
|
8101
8707
|
}
|
|
@@ -8103,21 +8709,21 @@ ${table(r.files ?? [], cols)}`);
|
|
|
8103
8709
|
need(path, "path", ex);
|
|
8104
8710
|
const data = v.file ? await readFile3(v.file) : await readStdin();
|
|
8105
8711
|
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).`);
|
|
8712
|
+
out(r ?? { path: p, bytes: data.length }, () => `${lavender("\u2713")} ${t("Wrote", "Escrito")} ${p} (${data.length} bytes).`);
|
|
8107
8713
|
return;
|
|
8108
8714
|
}
|
|
8109
8715
|
case "rm": {
|
|
8110
8716
|
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}`);
|
|
8717
|
+
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}` });
|
|
8718
|
+
out(await api(agentPath(id, `/files/${p}`), { method: "DELETE" }) ?? { deleted: p }, () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${p}`);
|
|
8113
8719
|
return;
|
|
8114
8720
|
}
|
|
8115
8721
|
default:
|
|
8116
|
-
throw usage(`Unknown files subcommand: ${sub ?? "(none)"}.`, ex);
|
|
8722
|
+
throw usage(t(`Unknown files subcommand: ${sub ?? "(none)"}.`, `Subcomando de files desconocido: ${sub ?? "(ninguno)"}.`), ex);
|
|
8117
8723
|
}
|
|
8118
8724
|
}
|
|
8119
8725
|
async function readStdin() {
|
|
8120
|
-
if (process.stdin.isTTY) throw usage("Nothing to upload.", "Use --file <local> or pipe the content through stdin.");
|
|
8726
|
+
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
8727
|
const parts = [];
|
|
8122
8728
|
for await (const c of process.stdin) parts.push(c);
|
|
8123
8729
|
return Buffer.concat(parts);
|
|
@@ -8144,7 +8750,7 @@ async function readSkillDir(dir) {
|
|
|
8144
8750
|
if (rel === "SKILL.md") markdown = await readFile3(full, "utf8");
|
|
8145
8751
|
else if (!rel.split("/").some((part) => part.startsWith("."))) assets.push({ name: rel, contentBase64: (await readFile3(full)).toString("base64") });
|
|
8146
8752
|
}
|
|
8147
|
-
if (!markdown) throw usage(`No SKILL.md in ${dir}.`, "A skill folder has SKILL.md at its root (agentskills.io).");
|
|
8753
|
+
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
8754
|
return { markdown, assets };
|
|
8149
8755
|
}
|
|
8150
8756
|
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 +8768,7 @@ async function skillsCmd(sub, id, slug, v) {
|
|
|
8162
8768
|
];
|
|
8163
8769
|
const more = r.community.filter((c) => !c.installed).length;
|
|
8164
8770
|
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}`)}`);
|
|
8771
|
+
${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
8772
|
return;
|
|
8167
8773
|
}
|
|
8168
8774
|
case "house": {
|
|
@@ -8171,20 +8777,20 @@ ${dim(`${more} more in the community catalog: ghosty skills add <agent-id> <slug
|
|
|
8171
8777
|
if (!only && !on && !off) {
|
|
8172
8778
|
const r2 = await api(agentPath(id, "/skills/house"));
|
|
8173
8779
|
out(r2, () => `${table(r2.house.map((h) => ({ slug: h.slug, on: h.enabled ? "on" : "off" })), ["slug", "on"])}
|
|
8174
|
-
${dim(
|
|
8780
|
+
${dim(`${t("Change", "Cambiar")}: ghosty skills house ${id} --only a,b | --enable a | --disable b [--dry-run]`)}`);
|
|
8175
8781
|
return;
|
|
8176
8782
|
}
|
|
8177
8783
|
const body = { ...only ? { only } : {}, ...on ? { on } : {}, ...off ? { off } : {}, ...v["dry-run"] ? { dryRun: true } : {} };
|
|
8178
8784
|
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(
|
|
8785
|
+
out(r, () => `${r.changes.map((c) => `${c.enabled ? "+" : "-"} ${c.slug}`).join("\n") || dim(t("Nothing to change.", "Nada que cambiar."))}
|
|
8786
|
+
${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
8787
|
return;
|
|
8182
8788
|
}
|
|
8183
8789
|
case "on":
|
|
8184
8790
|
case "off": {
|
|
8185
8791
|
slug = need(slug, "skill slug", `ghosty skills ${sub} <agent-id> <slug>`);
|
|
8186
8792
|
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}` : ""})`)}`);
|
|
8793
|
+
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
8794
|
return;
|
|
8189
8795
|
}
|
|
8190
8796
|
case "get": {
|
|
@@ -8193,27 +8799,27 @@ ${dim(`On now: ${r.on.join(", ") || "none"}${r.dryRun ? " \xB7 dry run: nothing
|
|
|
8193
8799
|
if (!v.out) {
|
|
8194
8800
|
const files = sk.files.map((f) => ({ path: f.path, bytes: Buffer.from(f.contentBase64, "base64").length }));
|
|
8195
8801
|
out({ slug, files, saved: null }, () => `${table(files, ["path", "bytes"])}
|
|
8196
|
-
${dim(
|
|
8802
|
+
${dim(`${t("Download", "Descargar")}: ghosty skills get ${id} ${slug} --out DIR`)}`);
|
|
8197
8803
|
return;
|
|
8198
8804
|
}
|
|
8199
8805
|
const dir = resolve(v.out);
|
|
8200
8806
|
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)`)}`);
|
|
8807
|
+
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
8808
|
return;
|
|
8203
8809
|
}
|
|
8204
8810
|
case "add": {
|
|
8205
8811
|
slug = need(slug, "skill slug", ex);
|
|
8206
|
-
if (v.file && v.dir) throw usage("Use --file or --dir, not both.");
|
|
8812
|
+
if (v.file && v.dir) throw usage(t("Use --file or --dir, not both.", "Usa --file o --dir, no los dos."));
|
|
8207
8813
|
const body = v.dir ? await readSkillDir(v.dir) : v.file ? { markdown: await readFile3(v.file, "utf8") } : { fromCatalog: true };
|
|
8208
8814
|
const r = await api(agentPath(id, `/skills/${enc2(slug)}`), { method: "PUT", body });
|
|
8209
|
-
out(r, () => `${lavender("\u2713")} Skill ${slug} installed
|
|
8815
|
+
out(r, () => `${lavender("\u2713")} ${t(`Skill ${slug} installed.`, `Skill ${slug} instalada.`)}${r?.nota ? dim(` (${serverText(r.nota)})`) : ""}`);
|
|
8210
8816
|
return;
|
|
8211
8817
|
}
|
|
8212
8818
|
case "templates": {
|
|
8213
8819
|
const r = await api(agentPath(id, "/templates"));
|
|
8214
|
-
const rows = r.templates.map((
|
|
8820
|
+
const rows = r.templates.map((tp) => ({ name: tp.name, source: tp.source, description: (tp.description ?? "").replace(/\s+/g, " ").slice(0, 60) }));
|
|
8215
8821
|
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).")}`);
|
|
8822
|
+
${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
8823
|
return;
|
|
8218
8824
|
}
|
|
8219
8825
|
case "rm":
|
|
@@ -8221,18 +8827,19 @@ ${dim("Its own: put <name>.html in <skill>/pdf-templates/ of any of its skills (
|
|
|
8221
8827
|
slug = need(slug, "skill slug", ex);
|
|
8222
8828
|
requireYesIfHeadless(v.yes, `ghosty skills rm ${id} ${slug}`);
|
|
8223
8829
|
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}` });
|
|
8830
|
+
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
8831
|
const backup = sk ? trashDir(id, slug) : null;
|
|
8226
8832
|
if (sk && backup) await writeSkill(backup, sk);
|
|
8227
8833
|
const r = await api(agentPath(id, `/skills/${enc2(slug)}`), { method: "DELETE" }) ?? { deleted: slug };
|
|
8228
8834
|
const restore = backup ? `ghosty skills add ${id} ${slug} --dir "${backup}"` : null;
|
|
8229
|
-
out({ ...r, backup, restore }, () => `${lavender("\u2713")} Skill ${slug} removed
|
|
8230
|
-
${dim(`Backup: ${backup}
|
|
8231
|
-
Undo: ${restore}`
|
|
8835
|
+
out({ ...r, backup, restore }, () => `${lavender("\u2713")} ${t(`Skill ${slug} removed.`, `Skill ${slug} quitada.`)}${restore ? `
|
|
8836
|
+
${dim(t(`Backup: ${backup}
|
|
8837
|
+
Undo: ${restore}`, `Respaldo: ${backup}
|
|
8838
|
+
Deshacer: ${restore}`))}` : ""}`);
|
|
8232
8839
|
return;
|
|
8233
8840
|
}
|
|
8234
8841
|
default:
|
|
8235
|
-
throw usage(`Unknown skills subcommand: ${sub}.`, ex);
|
|
8842
|
+
throw usage(t(`Unknown skills subcommand: ${sub}.`, `Subcomando de skills desconocido: ${sub}.`), ex);
|
|
8236
8843
|
}
|
|
8237
8844
|
}
|
|
8238
8845
|
async function mcpCmd(sub, id, v) {
|
|
@@ -8244,13 +8851,13 @@ async function mcpCmd(sub, id, v) {
|
|
|
8244
8851
|
const parsed = JSON.parse(await readFile3(file, "utf8"));
|
|
8245
8852
|
const body = { servers: Array.isArray(parsed) ? parsed : parsed.servers ?? [] };
|
|
8246
8853
|
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."}`);
|
|
8854
|
+
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
8855
|
}
|
|
8249
|
-
throw usage(`Unknown mcp subcommand: ${sub ?? "(none)"}.`, ex);
|
|
8856
|
+
throw usage(t(`Unknown mcp subcommand: ${sub ?? "(none)"}.`, `Subcomando de mcp desconocido: ${sub ?? "(ninguno)"}.`), ex);
|
|
8250
8857
|
}
|
|
8251
8858
|
async function agentsDemo(id, v) {
|
|
8252
8859
|
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.");
|
|
8860
|
+
if (v.on && v.off) throw usage(t("Use --on or --off, not both.", "Usa --on o --off, no los dos."));
|
|
8254
8861
|
const body = {};
|
|
8255
8862
|
if (v.slug) body.slug = v.slug;
|
|
8256
8863
|
if (v.on) body.enabled = true;
|
|
@@ -8263,20 +8870,28 @@ async function agentsDemo(id, v) {
|
|
|
8263
8870
|
if (v.starter !== void 0) body.starter = v.starter || null;
|
|
8264
8871
|
if (v.rotate) body.rotate = true;
|
|
8265
8872
|
if (v.sponsor !== void 0) {
|
|
8266
|
-
if (v.sponsor !== "on" && v.sponsor !== "off") throw usage("--sponsor takes on or off.");
|
|
8873
|
+
if (v.sponsor !== "on" && v.sponsor !== "off") throw usage(t("--sponsor takes on or off.", "--sponsor acepta on u off."));
|
|
8267
8874
|
body.sponsor = v.sponsor === "on";
|
|
8268
8875
|
}
|
|
8269
8876
|
const d = Object.keys(body).length ? await api(agentPath(id, "/demo"), { method: "PUT", body }) : await api(agentPath(id, "/demo"));
|
|
8270
8877
|
out(
|
|
8271
8878
|
d,
|
|
8272
8879
|
() => [
|
|
8273
|
-
`${d.enabled ? lavender("\u25CF on") : dim("\u25CB off")} ${d.url ?? dim("(no link yet: --slug NAME)")}`,
|
|
8274
|
-
dim(
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8880
|
+
`${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)"))}`,
|
|
8881
|
+
dim(
|
|
8882
|
+
t(
|
|
8883
|
+
`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`,
|
|
8884
|
+
`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`
|
|
8885
|
+
)
|
|
8886
|
+
),
|
|
8887
|
+
d.welcome ? `${t("welcome", "bienvenida")}: ${d.welcome}` : dim(t("welcome: (default greeting)", "bienvenida: (saludo por defecto)")),
|
|
8888
|
+
d.starter ? `${t("starter", "inicial")}: \u2728 ${d.starter}` : dim(t("starter: (none)", "inicial: (ninguna)")),
|
|
8889
|
+
d.chips.length ? `chips: ${d.chips.join(" | ")}` : dim(t("chips: (default)", "chips: (por defecto)")),
|
|
8890
|
+
t(
|
|
8891
|
+
`tried ${d.stats.pruebas} \xB7 messages ${d.stats.mensajes} \xB7 kept ${d.stats.clones} (${d.stats.patrocinados} on your key)`,
|
|
8892
|
+
`pruebas ${d.stats.pruebas} \xB7 mensajes ${d.stats.mensajes} \xB7 se lo quedaron ${d.stats.clones} (${d.stats.patrocinados} con tu llave)`
|
|
8893
|
+
),
|
|
8894
|
+
...d.lastError ? [red(`${t("last failure", "\xFAltima falla")} ${d.lastError.at}: ${serverText(d.lastError.message ?? d.lastError.error ?? "")}`)] : []
|
|
8280
8895
|
].join("\n")
|
|
8281
8896
|
);
|
|
8282
8897
|
}
|
|
@@ -8284,8 +8899,8 @@ async function agentsBox(id, v) {
|
|
|
8284
8899
|
id = need(id, "agent id", "ghosty agents box <agent-id> [--check PATH] [--contains PATH=TEXT] [--recycle]");
|
|
8285
8900
|
if (v.recycle) {
|
|
8286
8901
|
const r2 = await api(agentPath(id, "/box"), { method: "POST", body: { recycle: true } });
|
|
8287
|
-
out(r2, () => `${lavender("\u2713")} Recycled. ${dim(r2?.note ?? "")}
|
|
8288
|
-
${dim(
|
|
8902
|
+
out(r2, () => `${lavender("\u2713")} ${t("Recycled.", "Reciclada.")} ${dim(serverText(r2?.note ?? ""))}
|
|
8903
|
+
${dim(`${t("Verify after one turn", "Verifica despu\xE9s de un turno")}: ghosty agents box ${id} --check <path>`)}`);
|
|
8289
8904
|
return;
|
|
8290
8905
|
}
|
|
8291
8906
|
const q = new URLSearchParams();
|
|
@@ -8293,12 +8908,12 @@ ${dim(`Verify after one turn: ghosty agents box ${id} --check <path>`)}`);
|
|
|
8293
8908
|
for (const c of v.contains ?? []) q.append("contains", c);
|
|
8294
8909
|
const r = await api(agentPath(id, `/box?${q}`));
|
|
8295
8910
|
out(r, () => {
|
|
8296
|
-
if (!r.boxes.length) return dim(r.note
|
|
8911
|
+
if (!r.boxes.length) return dim(r.note ? serverText(r.note) : t("No live box.", "No hay caja viva."));
|
|
8297
8912
|
const lines = r.boxes.flatMap((b) => [
|
|
8298
|
-
`${bold(b.sandboxId)} ${dim(b.lastMessageAt ?
|
|
8913
|
+
`${bold(b.sandboxId)} ${dim(b.lastMessageAt ? `${t("last message", "\xFAltimo mensaje")} ${b.lastMessageAt}` : r.kind)}${b.error ? ` ${red(serverText(b.error))}` : ""}`,
|
|
8299
8914
|
...b.checks.map((c) => ` ${c.ok ? lavender("\u2713") : red("\u2717")} ${c.path}${c.contains !== void 0 ? ` \u220B ${c.contains}` : ""}`)
|
|
8300
8915
|
]);
|
|
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
|
|
8916
|
+
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
8917
|
return [...lines, verdict].join("\n");
|
|
8303
8918
|
});
|
|
8304
8919
|
}
|
|
@@ -8345,20 +8960,20 @@ function parseData(e) {
|
|
|
8345
8960
|
var enc3 = encodeURIComponent;
|
|
8346
8961
|
var convPath = (agent, rest = "") => `/api/v2/me/agents/${enc3(agent)}/conversations${rest}`;
|
|
8347
8962
|
async function conversationsLs(agent) {
|
|
8348
|
-
if (!agent) throw usage("Missing agent id.", "Example: ghosty conversations ls <agent-id>");
|
|
8963
|
+
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
8964
|
const r = await api(convPath(agent));
|
|
8350
8965
|
const rows = r.conversaciones.map((c) => ({ id: c.id, title: c.titulo, updated: c.actualizada }));
|
|
8351
8966
|
out(rows, () => table(rows, ["id", "title", "updated"]));
|
|
8352
8967
|
}
|
|
8353
8968
|
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]");
|
|
8969
|
+
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
8970
|
const tail = v.limit ?? "20";
|
|
8356
8971
|
const r = await api(convPath(agent, `/${enc3(sid)}?tail=${enc3(tail)}`));
|
|
8357
8972
|
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)");
|
|
8973
|
+
const lines = r.messages.map((m) => `${bold(m.role === "user" ? t("User", "Usuario") : m.role === "assistant" ? t("Agent", "Agente") : m.role)} ${m.text.trim()}`);
|
|
8974
|
+
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)`))] : [];
|
|
8975
|
+
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)"))] : [];
|
|
8976
|
+
return [...head, ...lines, ...tailNote].join("\n\n") || dim(t("(empty)", "(vac\xEDa)"));
|
|
8362
8977
|
});
|
|
8363
8978
|
}
|
|
8364
8979
|
async function turn(agent, sid, text, first) {
|
|
@@ -8369,7 +8984,7 @@ async function turn(agent, sid, text, first) {
|
|
|
8369
8984
|
const onSigint = () => {
|
|
8370
8985
|
if (cancelled) process.exit(130);
|
|
8371
8986
|
cancelled = true;
|
|
8372
|
-
info(dim("\n(cancelling\u2026 press Ctrl-C again to quit)"));
|
|
8987
|
+
info(dim(t("\n(cancelling\u2026 press Ctrl-C again to quit)", "\n(cancelando\u2026 presiona Ctrl-C otra vez para salir)")));
|
|
8373
8988
|
void api(convPath(agent, `/${enc3(sid)}/cancel`), { method: "POST", body: {} }).catch(() => {
|
|
8374
8989
|
});
|
|
8375
8990
|
};
|
|
@@ -8390,11 +9005,11 @@ async function turn(agent, sid, text, first) {
|
|
|
8390
9005
|
else process.stdout.write(d.text);
|
|
8391
9006
|
} else if (ev.event === "tool" && d.title) {
|
|
8392
9007
|
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}`));
|
|
9008
|
+
else if (!d.status || d.status === "pending" || d.status === "in_progress") info(dim(` \u2699 ${serverText(d.title)}`));
|
|
8394
9009
|
} else if (ev.event === "usage") {
|
|
8395
9010
|
usageData = d;
|
|
8396
9011
|
} else if (ev.event === "error") {
|
|
8397
|
-
throw new CliError(d.message
|
|
9012
|
+
throw new CliError(d.message ? serverText(d.message) : t("The turn failed.", "El turno fall\xF3."), EXIT.API);
|
|
8398
9013
|
} else if (ev.event === "done") {
|
|
8399
9014
|
break;
|
|
8400
9015
|
}
|
|
@@ -8408,13 +9023,13 @@ async function turn(agent, sid, text, first) {
|
|
|
8408
9023
|
return { text: answer, usage: usageData };
|
|
8409
9024
|
}
|
|
8410
9025
|
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)');
|
|
9026
|
+
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
9027
|
let sid = v.conversation;
|
|
8413
9028
|
let first = false;
|
|
8414
9029
|
if (!sid) {
|
|
8415
9030
|
sid = (await api(convPath(agent), { method: "POST", body: {} })).id;
|
|
8416
9031
|
first = true;
|
|
8417
|
-
if (!globals.json) info(dim(`conversation ${sid} (continue it with --conversation ${sid})`));
|
|
9032
|
+
if (!globals.json) info(dim(t(`conversation ${sid} (continue it with --conversation ${sid})`, `conversaci\xF3n ${sid} (s\xEDguela con --conversation ${sid})`)));
|
|
8418
9033
|
}
|
|
8419
9034
|
if (message) {
|
|
8420
9035
|
await turn(agent, sid, message, first);
|
|
@@ -8422,12 +9037,12 @@ async function chat(agent, message, v) {
|
|
|
8422
9037
|
}
|
|
8423
9038
|
if (!process.stdin.isTTY) {
|
|
8424
9039
|
const piped = (await readAll()).trim();
|
|
8425
|
-
if (!piped) throw usage("Nothing to send.", 'Example: ghosty chat <agent-id> "hello"');
|
|
9040
|
+
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
9041
|
await turn(agent, sid, piped, first);
|
|
8427
9042
|
return;
|
|
8428
9043
|
}
|
|
8429
9044
|
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
8430
|
-
info(dim("Type your message. Ctrl-C cancels a turn, Ctrl-D exits."));
|
|
9045
|
+
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
9046
|
for (; ; ) {
|
|
8432
9047
|
let line2;
|
|
8433
9048
|
try {
|
|
@@ -8458,28 +9073,28 @@ async function meFiles(sub, arg, v) {
|
|
|
8458
9073
|
out(r, () => {
|
|
8459
9074
|
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
9075
|
return `${table(rows, ["id", "name", "kind", "size", "created"])}
|
|
8461
|
-
${dim(`${(r.storage.usedBytes / 1e9).toFixed(2)} GB of ${r.storage.limitGb} GB used`)}`;
|
|
9076
|
+
${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
9077
|
});
|
|
8463
9078
|
return;
|
|
8464
9079
|
}
|
|
8465
9080
|
case "upload": {
|
|
8466
|
-
if (!arg) throw usage("Missing file.", ex);
|
|
9081
|
+
if (!arg) throw usage(t("Missing file.", "Falta el archivo."), ex);
|
|
8467
9082
|
const bytes = await readFile4(arg);
|
|
8468
9083
|
const r = await api(`/api/v2/me/files?nombre=${enc3(basename2(arg))}`, {
|
|
8469
9084
|
method: "POST",
|
|
8470
9085
|
raw: new Uint8Array(bytes),
|
|
8471
9086
|
headers: { "content-type": "application/octet-stream" }
|
|
8472
9087
|
});
|
|
8473
|
-
out(r, () => `${lavender("\u2713")} Uploaded ${r.name} (${r.id})`);
|
|
9088
|
+
out(r, () => `${lavender("\u2713")} ${t("Uploaded", "Subido")} ${r.name} (${r.id})`);
|
|
8474
9089
|
return;
|
|
8475
9090
|
}
|
|
8476
9091
|
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}`);
|
|
9092
|
+
if (!arg) throw usage(t("Missing file id.", "Falta el id del archivo."), ex);
|
|
9093
|
+
out(await api(`/api/v2/me/files/${enc3(arg)}`, { method: "DELETE" }) ?? { deleted: arg }, () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${arg}`);
|
|
8479
9094
|
return;
|
|
8480
9095
|
}
|
|
8481
9096
|
default:
|
|
8482
|
-
throw usage(`Unknown files subcommand: ${sub ?? "(none)"}.`, ex);
|
|
9097
|
+
throw usage(t(`Unknown files subcommand: ${sub ?? "(none)"}.`, `Subcomando de files desconocido: ${sub ?? "(ninguno)"}.`), ex);
|
|
8483
9098
|
}
|
|
8484
9099
|
}
|
|
8485
9100
|
async function meUsage() {
|
|
@@ -8488,11 +9103,11 @@ async function meUsage() {
|
|
|
8488
9103
|
out(
|
|
8489
9104
|
r,
|
|
8490
9105
|
() => [
|
|
8491
|
-
`Plan ${r.plan.name}${r.exhausted ? " \u2014 out of usage for now" : ""}`,
|
|
8492
|
-
` This week
|
|
8493
|
-
...r.month.pct !== null ? [` This month
|
|
8494
|
-
` Top-ups
|
|
8495
|
-
dim("Change plan or top up: https://www.ghosty.studio/c/uso")
|
|
9106
|
+
`Plan ${r.plan.name}${r.exhausted ? t(" \u2014 out of usage for now", " \u2014 sin uso disponible por ahora") : ""}`,
|
|
9107
|
+
` ${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)}`)}`,
|
|
9108
|
+
...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)}`)}`] : [],
|
|
9109
|
+
` ${t("Top-ups ", "Recargas ")} ${r.creditMxn > 0 ? t("available", "disponibles") : t("none", "ninguna")}`,
|
|
9110
|
+
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
9111
|
].join("\n")
|
|
8497
9112
|
);
|
|
8498
9113
|
}
|
|
@@ -8501,14 +9116,14 @@ async function meUsage() {
|
|
|
8501
9116
|
var enc4 = encodeURIComponent;
|
|
8502
9117
|
function minutesOf(s, flag) {
|
|
8503
9118
|
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");
|
|
9119
|
+
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
9120
|
const n = Number(m[1]);
|
|
8506
9121
|
const unit = (m[2] ?? "m").toLowerCase();
|
|
8507
9122
|
return Math.round(unit === "d" ? n * 1440 : unit === "h" ? n * 60 : n);
|
|
8508
9123
|
}
|
|
8509
9124
|
function isoOf(s, flag) {
|
|
8510
9125
|
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");
|
|
9126
|
+
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
9127
|
return d.toISOString();
|
|
8513
9128
|
}
|
|
8514
9129
|
var every = (min) => min == null ? "once" : min % 1440 === 0 ? `${min / 1440}d` : min % 60 === 0 ? `${min / 60}h` : `${min}m`;
|
|
@@ -8518,7 +9133,7 @@ async function agentSchedule(agent) {
|
|
|
8518
9133
|
}
|
|
8519
9134
|
async function scheduleCmd(sub, agent, arg, v) {
|
|
8520
9135
|
if (sub === "ls" || sub === "list") {
|
|
8521
|
-
if (!agent) throw usage("Missing agent id.", "Example: ghosty schedule ls <agent-id>");
|
|
9136
|
+
if (!agent) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty schedule ls <agent-id>");
|
|
8522
9137
|
const r = await agentSchedule(agent);
|
|
8523
9138
|
if (globals.json) return out({ pending: r.pendientes, runs: r.corridas });
|
|
8524
9139
|
const pending = r.pendientes.map((p) => ({
|
|
@@ -8533,27 +9148,27 @@ async function scheduleCmd(sub, agent, arg, v) {
|
|
|
8533
9148
|
return out(
|
|
8534
9149
|
null,
|
|
8535
9150
|
() => [
|
|
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"])] : []
|
|
9151
|
+
pending.length ? table(pending, ["id", "due", "every", "until", "conversation", "prompt"]) : dim(t("No scheduled turns.", "No hay turnos programados.")),
|
|
9152
|
+
...runs.length ? ["", lavender(t("Recent runs", "Corridas recientes")), table(runs, ["fired", "conversation", "answer"])] : []
|
|
8538
9153
|
].join("\n")
|
|
8539
9154
|
);
|
|
8540
9155
|
}
|
|
8541
9156
|
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.");
|
|
9157
|
+
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'));
|
|
9158
|
+
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"));
|
|
9159
|
+
if (v.at && v.in) throw usage(t("Use --at or --in, not both.", "Usa --at o --in, no ambos."));
|
|
8545
9160
|
const body = {
|
|
8546
9161
|
prompt: arg,
|
|
8547
9162
|
...v.in ? { inMinutes: minutesOf(v.in, "--in") } : { dueAt: isoOf(v.at, "--at") },
|
|
8548
9163
|
repeatMin: v.every ? minutesOf(v.every, "--every") : null,
|
|
8549
9164
|
until: v.until ? isoOf(v.until, "--until") : null
|
|
8550
9165
|
};
|
|
8551
|
-
if (body.repeatMin && !body.until) throw usage("A repeating turn needs --until.", "Example: --every 12h --until 2026-10-25");
|
|
9166
|
+
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
9167
|
let sid = v.conversation;
|
|
8553
9168
|
const created = !sid;
|
|
8554
9169
|
if (!sid) {
|
|
8555
9170
|
sid = (await api(convPath(agent), { method: "POST", body: {} })).id;
|
|
8556
|
-
if (!globals.json) info(dim(`new conversation ${sid}`));
|
|
9171
|
+
if (!globals.json) info(dim(t(`new conversation ${sid}`, `conversaci\xF3n nueva ${sid}`)));
|
|
8557
9172
|
}
|
|
8558
9173
|
let r;
|
|
8559
9174
|
try {
|
|
@@ -8567,24 +9182,25 @@ async function scheduleCmd(sub, agent, arg, v) {
|
|
|
8567
9182
|
const p = r.programado;
|
|
8568
9183
|
return out(
|
|
8569
9184
|
{ scheduled: p },
|
|
8570
|
-
() => `${lavender("\u2713")} Scheduled ${p.id} \xB7 ${p.dueAt}${p.repeatMin ? ` \xB7 every ${every(p.repeatMin)} until ${p.until}` : ""}
|
|
8571
|
-
${dim(
|
|
9185
|
+
() => `${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}`) : ""}
|
|
9186
|
+
${dim(`${t("conversation", "conversaci\xF3n")} ${p.sessionId}`)}`
|
|
8572
9187
|
);
|
|
8573
9188
|
}
|
|
8574
9189
|
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>)");
|
|
9190
|
+
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
9191
|
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>");
|
|
9192
|
+
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
9193
|
await api(convPath(agent, `/${enc4(row.sessionId)}/schedule/${enc4(arg)}`), { method: "DELETE" });
|
|
8579
|
-
return out({ canceled: arg }, () => `${lavender("\u2713")} Canceled ${arg}`);
|
|
9194
|
+
return out({ canceled: arg }, () => `${lavender("\u2713")} ${t("Canceled", "Cancelado")} ${arg}`);
|
|
8580
9195
|
}
|
|
8581
|
-
throw usage(`Unknown schedule subcommand: ${sub ?? "(none)"}.`, "Try: ghosty schedule ls | add | rm");
|
|
9196
|
+
throw usage(t(`Unknown schedule subcommand: ${sub ?? "(none)"}.`, `Subcomando de schedule desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty schedule ls | add | rm");
|
|
8582
9197
|
}
|
|
8583
9198
|
|
|
8584
9199
|
// src/commands/partner.ts
|
|
8585
9200
|
import { createHmac } from "node:crypto";
|
|
8586
9201
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
8587
|
-
var
|
|
9202
|
+
var partnerHelp = () => t(
|
|
9203
|
+
`${bold("Usage:")} ghosty partner <command> [options]
|
|
8588
9204
|
|
|
8589
9205
|
${bold("Partner space")} ${dim("(you, signed in as the space owner)")}
|
|
8590
9206
|
partner ls Your partner spaces
|
|
@@ -8614,7 +9230,39 @@ ${bold("Options")}
|
|
|
8614
9230
|
--json Machine-readable output
|
|
8615
9231
|
|
|
8616
9232
|
${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")}
|
|
9233
|
+
${dim("Docs: https://www.ghosty.studio/docs/cli/partners")}`,
|
|
9234
|
+
`${bold("Uso:")} ghosty partner <comando> [opciones]
|
|
9235
|
+
|
|
9236
|
+
${bold("Espacio de partner")} ${dim("(t\xFA, con sesi\xF3n como due\xF1a del espacio)")}
|
|
9237
|
+
partner ls Tus espacios de partner
|
|
9238
|
+
partner enable <workspace> Convierte uno de tus espacios en espacio de partner
|
|
9239
|
+
partner credentials ls Credenciales de servidor (sin secretos)
|
|
9240
|
+
partner credentials create [--label L] Emite una \u2014 el secreto se muestra UNA vez
|
|
9241
|
+
partner credentials rotate <keyId> [--grace MIN] Secreto nuevo, mismo keyId (el viejo vale MIN, default 60)
|
|
9242
|
+
partner credentials revoke <keyId> La revoca (idempotente)
|
|
9243
|
+
partner audit Qui\xE9n hizo qu\xE9 y cu\xE1ndo
|
|
9244
|
+
|
|
9245
|
+
${bold("Agente compartido (solo-MCP)")}
|
|
9246
|
+
partner agent get La plantilla del agente y su versi\xF3n
|
|
9247
|
+
partner agent apply --file agent.json Lo crea o lo actualiza (idempotente)
|
|
9248
|
+
|
|
9249
|
+
${bold("Tenants")} ${dim("(tus clientes, por externalId \u2014 s\xF3lo config, sin caja)")}
|
|
9250
|
+
partner tenants ls
|
|
9251
|
+
partner tenants get <externalId>
|
|
9252
|
+
partner tenants set <externalId> --config '{"name":"\u2026","timezone":"America/Mexico_City"}'
|
|
9253
|
+
partner tenants rm <externalId> Borra su config Y su memoria
|
|
9254
|
+
|
|
9255
|
+
${bold("Pru\xE9balo")}
|
|
9256
|
+
partner try <externalId> "mensaje" Un turno [--group END_USER_ID] [--turn-token TOKEN_DE_VIDA_CORTA]
|
|
9257
|
+
(el token del turno llega a TU MCP como \${turn.token}; --token tambi\xE9n sirve aqu\xED)
|
|
9258
|
+
|
|
9259
|
+
${bold("Opciones")}
|
|
9260
|
+
--workspace SLUG Qu\xE9 espacio de partner (s\xF3lo si tienes m\xE1s de uno)
|
|
9261
|
+
--json Salida para m\xE1quinas
|
|
9262
|
+
|
|
9263
|
+
${bold("Modo credencial de servidor")} GHOSTY_PARTNER_KEY=gpk_\u2026 GHOSTY_PARTNER_SECRET=gps_\u2026 ghosty partner agent get
|
|
9264
|
+
${dim("Docs: https://www.ghosty.studio/docs/cli/partners")}`
|
|
9265
|
+
);
|
|
8618
9266
|
var enc5 = encodeURIComponent;
|
|
8619
9267
|
function partnerCreds() {
|
|
8620
9268
|
const key = process.env.GHOSTY_PARTNER_KEY;
|
|
@@ -8635,7 +9283,7 @@ async function call(path, v, init = {}) {
|
|
|
8635
9283
|
"x-ghosty-ts": ts,
|
|
8636
9284
|
"x-ghosty-sig": sig,
|
|
8637
9285
|
"content-type": "application/json",
|
|
8638
|
-
"user-agent": `ghosty-cli/${"0.
|
|
9286
|
+
"user-agent": `ghosty-cli/${"0.21.0"}`
|
|
8639
9287
|
},
|
|
8640
9288
|
body: method === "GET" ? void 0 : raw
|
|
8641
9289
|
});
|
|
@@ -8650,15 +9298,15 @@ async function call(path, v, init = {}) {
|
|
|
8650
9298
|
}
|
|
8651
9299
|
async function json(path, v, init = {}) {
|
|
8652
9300
|
const res = await call(path, v, init);
|
|
8653
|
-
const
|
|
8654
|
-
return
|
|
9301
|
+
const text = await res.text();
|
|
9302
|
+
return text ? JSON.parse(text) : void 0;
|
|
8655
9303
|
}
|
|
8656
9304
|
var fmt = (d) => d ? d.replace("T", " ").slice(0, 16) : "\u2014";
|
|
8657
9305
|
function showSecret(r) {
|
|
8658
9306
|
out(
|
|
8659
9307
|
r,
|
|
8660
9308
|
() => [
|
|
8661
|
-
`${lavender("\u2713")} Save these now \u2014 the secret is not shown again
|
|
9309
|
+
`${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
9310
|
` GHOSTY_PARTNER_KEY=${r.keyId}`,
|
|
8663
9311
|
` GHOSTY_PARTNER_SECRET=${r.secret}`
|
|
8664
9312
|
].join("\n")
|
|
@@ -8679,17 +9327,17 @@ async function credentialsCmd(sub, id, v) {
|
|
|
8679
9327
|
if (sub === "create" || sub === "new") {
|
|
8680
9328
|
return showSecret(await json("/api/v2/partner/credentials", v, { ...owner, method: "POST", body: { label: v.label } }));
|
|
8681
9329
|
}
|
|
8682
|
-
if (!id) throw usage("Missing keyId.",
|
|
9330
|
+
if (!id) throw usage(t("Missing keyId.", "Falta el keyId."), `${t("Example", "Ejemplo")}: ghosty partner credentials ${sub} gpk_\u2026`);
|
|
8683
9331
|
if (sub === "rotate") {
|
|
8684
9332
|
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.");
|
|
9333
|
+
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
9334
|
return showSecret(await json(`/api/v2/partner/credentials/${enc5(id)}/rotate`, v, { ...owner, method: "POST", body: { graceMinutes: grace } }));
|
|
8687
9335
|
}
|
|
8688
9336
|
if (sub === "revoke" || sub === "rm") {
|
|
8689
9337
|
const r = await json(`/api/v2/partner/credentials/${enc5(id)}`, v, { ...owner, method: "DELETE" });
|
|
8690
|
-
return out(r, () => `${lavender("\u2713")} Revoked ${r.keyId}.`);
|
|
9338
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Revoked ${r.keyId}.`, `${r.keyId} revocada.`)}`);
|
|
8691
9339
|
}
|
|
8692
|
-
throw usage(`Unknown credentials subcommand: ${sub}.`, "Try: ghosty partner credentials ls | create | rotate | revoke");
|
|
9340
|
+
throw usage(t(`Unknown credentials subcommand: ${sub}.`, `Subcomando de credentials desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner credentials ls | create | rotate | revoke");
|
|
8693
9341
|
}
|
|
8694
9342
|
async function agentCmd(sub, v) {
|
|
8695
9343
|
if (!sub || sub === "get") {
|
|
@@ -8698,46 +9346,46 @@ async function agentCmd(sub, v) {
|
|
|
8698
9346
|
const a = r.agent;
|
|
8699
9347
|
return [
|
|
8700
9348
|
`${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"}`,
|
|
9349
|
+
`MCP: ${a.mcp.map((m) => `${m.name} (${m.url})`).join(", ") || t("none", "ninguno")}`,
|
|
8702
9350
|
dim(a.prompt.slice(0, 300))
|
|
8703
9351
|
].join("\n");
|
|
8704
9352
|
});
|
|
8705
9353
|
}
|
|
8706
9354
|
if (sub === "apply" || sub === "set") {
|
|
8707
|
-
if (!v.file) throw usage("Missing --file.", "Example: ghosty partner agent apply --file agent.json");
|
|
9355
|
+
if (!v.file) throw usage(t("Missing --file.", "Falta --file."), t("Example", "Ejemplo") + ": ghosty partner agent apply --file agent.json");
|
|
8708
9356
|
const body = JSON.parse(await readFile5(v.file, "utf8"));
|
|
8709
9357
|
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}`);
|
|
9358
|
+
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
9359
|
}
|
|
8712
|
-
throw usage(`Unknown agent subcommand: ${sub}.`, "Try: ghosty partner agent get | apply --file agent.json");
|
|
9360
|
+
throw usage(t(`Unknown agent subcommand: ${sub}.`, `Subcomando de agent desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner agent get | apply --file agent.json");
|
|
8713
9361
|
}
|
|
8714
9362
|
async function tenantsCmd(sub, id, v) {
|
|
8715
9363
|
if (!sub || sub === "ls" || sub === "list") {
|
|
8716
9364
|
const r = await json("/api/v2/partner/tenants", v);
|
|
8717
|
-
return out(r, () => table(r.tenants.map((
|
|
9365
|
+
return out(r, () => table(r.tenants.map((tn) => ({ externalId: tn.externalId, config: JSON.stringify(tn.config ?? {}), updated: fmt(tn.updatedAt) })), ["externalId", "config", "updated"]));
|
|
8718
9366
|
}
|
|
8719
|
-
if (!id) throw usage("Missing externalId.",
|
|
9367
|
+
if (!id) throw usage(t("Missing externalId.", "Falta el externalId."), `${t("Example", "Ejemplo")}: ghosty partner tenants ${sub} org_123`);
|
|
8720
9368
|
const path = `/api/v2/partner/tenants/${enc5(id)}`;
|
|
8721
9369
|
if (sub === "get") return out(await json(path, v));
|
|
8722
9370
|
if (sub === "set" || sub === "apply") {
|
|
8723
|
-
if (!v.config) throw usage("Missing --config.",
|
|
9371
|
+
if (!v.config) throw usage(t("Missing --config.", "Falta --config."), `${t("Example", "Ejemplo")}: --config '{"name":"Spa","timezone":"America/Mexico_City"}'`);
|
|
8724
9372
|
let config;
|
|
8725
9373
|
try {
|
|
8726
9374
|
config = JSON.parse(v.config);
|
|
8727
9375
|
} catch {
|
|
8728
|
-
throw usage("--config must be JSON.");
|
|
9376
|
+
throw usage(t("--config must be JSON.", "--config debe ser JSON."));
|
|
8729
9377
|
}
|
|
8730
9378
|
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}`);
|
|
9379
|
+
return out(r, () => `${lavender("\u2713")} ${r.created ? t("Created", "Creado") : r.changed ? t("Updated", "Actualizado") : t("No changes", "Sin cambios")} ${id}`);
|
|
8732
9380
|
}
|
|
8733
9381
|
if (sub === "rm" || sub === "delete") {
|
|
8734
9382
|
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.`));
|
|
9383
|
+
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
9384
|
}
|
|
8737
|
-
throw usage(`Unknown tenants subcommand: ${sub}.`, "Try: ghosty partner tenants ls | get | set | rm");
|
|
9385
|
+
throw usage(t(`Unknown tenants subcommand: ${sub}.`, `Subcomando de tenants desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner tenants ls | get | set | rm");
|
|
8738
9386
|
}
|
|
8739
9387
|
async function tryCmd(externalId, text, v) {
|
|
8740
|
-
if (!externalId || !text) throw usage("Missing externalId or message.", '
|
|
9388
|
+
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
9389
|
const res = await call("/api/v2/partner/turns", v, {
|
|
8742
9390
|
method: "POST",
|
|
8743
9391
|
body: { tenant: { externalId }, groupId: v.group ?? "cli", text, ...v.token ? { token: v.token } : {} }
|
|
@@ -8762,7 +9410,7 @@ async function tryCmd(externalId, text, v) {
|
|
|
8762
9410
|
full += ev.value;
|
|
8763
9411
|
process.stdout.write(ev.value);
|
|
8764
9412
|
} else if (ev.type === "tool" && ev.phase === "start") info(dim(` \u21B3 ${ev.name}`));
|
|
8765
|
-
else if (ev.type === "error") info(`Error: ${ev.message}`);
|
|
9413
|
+
else if (ev.type === "error") info(`Error: ${serverText(ev.message ?? "")}`);
|
|
8766
9414
|
}
|
|
8767
9415
|
}
|
|
8768
9416
|
if (!globals.json && full) process.stdout.write("\n");
|
|
@@ -8771,16 +9419,16 @@ async function partnerCmd(sub, a1, a2, v) {
|
|
|
8771
9419
|
switch (sub) {
|
|
8772
9420
|
case void 0:
|
|
8773
9421
|
case "help":
|
|
8774
|
-
return out({ help:
|
|
9422
|
+
return out({ help: partnerHelp() }, () => partnerHelp());
|
|
8775
9423
|
case "ls":
|
|
8776
9424
|
case "list": {
|
|
8777
9425
|
const r = await json("/api/v2/partner", v, { ownerOnly: true });
|
|
8778
9426
|
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
9427
|
}
|
|
8780
9428
|
case "enable": {
|
|
8781
|
-
if (!a1) throw usage("Missing workspace.", "Example: ghosty partner enable denik");
|
|
9429
|
+
if (!a1) throw usage(t("Missing workspace.", "Falta el workspace."), t("Example", "Ejemplo") + ": ghosty partner enable denik");
|
|
8782
9430
|
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.`);
|
|
9431
|
+
return out(r, () => `${lavender("\u2713")} ${t(`${r.partner.workspace} is a partner space.`, `${r.partner.workspace} ya es espacio de partner.`)}`);
|
|
8784
9432
|
}
|
|
8785
9433
|
case "credentials":
|
|
8786
9434
|
case "creds":
|
|
@@ -8796,13 +9444,13 @@ async function partnerCmd(sub, a1, a2, v) {
|
|
|
8796
9444
|
case "try":
|
|
8797
9445
|
return tryCmd(a1, a2, v);
|
|
8798
9446
|
default:
|
|
8799
|
-
throw usage(`Unknown partner subcommand: ${sub}.`, "Run: ghosty partner --help");
|
|
9447
|
+
throw usage(t(`Unknown partner subcommand: ${sub}.`, `Subcomando de partner desconocido: ${sub}.`), t("Run", "Corre") + ": ghosty partner --help");
|
|
8800
9448
|
}
|
|
8801
9449
|
}
|
|
8802
9450
|
|
|
8803
9451
|
// src/commands/data.ts
|
|
8804
|
-
import { readFile as readFile6 } from "node:fs/promises";
|
|
8805
|
-
import { basename as basename3 } from "node:path";
|
|
9452
|
+
import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
|
|
9453
|
+
import { basename as basename3, join as join4 } from "node:path";
|
|
8806
9454
|
|
|
8807
9455
|
// src/rows.ts
|
|
8808
9456
|
var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -8820,40 +9468,55 @@ function parseRowScope(raw) {
|
|
|
8820
9468
|
}
|
|
8821
9469
|
return out2;
|
|
8822
9470
|
}
|
|
8823
|
-
var describeRowScope = (scope) => Object.entries(scope ?? {}).map(([
|
|
9471
|
+
var describeRowScope = (scope) => Object.entries(scope ?? {}).map(([t2, c]) => `${t2} (${c.join(", ")})`).join("; ");
|
|
8824
9472
|
|
|
8825
9473
|
// src/commands/data.ts
|
|
8826
9474
|
var enc6 = encodeURIComponent;
|
|
8827
9475
|
var agentPath2 = (id, rest = "") => `/api/v2/agents/${enc6(id)}${rest}`;
|
|
8828
9476
|
var need2 = (v, what, example) => {
|
|
8829
|
-
if (!v) throw usage(`Missing ${what}.`, `Example: ${example}`);
|
|
9477
|
+
if (!v) throw usage(t(`Missing ${what}.`, `Falta ${what}.`), `${t("Example", "Ejemplo")}: ${example}`);
|
|
8830
9478
|
return v;
|
|
8831
9479
|
};
|
|
8832
9480
|
var csv = (s) => (s ?? "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
9481
|
+
function importWarnings(ws) {
|
|
9482
|
+
if (!ws?.length) return "";
|
|
9483
|
+
const flag = ws.map((w) => `${w.table}.${w.column}`).join(",");
|
|
9484
|
+
const lines = ws.map(
|
|
9485
|
+
(w) => red(
|
|
9486
|
+
t(
|
|
9487
|
+
` ! ${w.table}.${w.column}: ${w.permanent} permanent photo link(s) would go back to ${w.incomingExternal} external link(s)`,
|
|
9488
|
+
` ! ${w.table}.${w.column}: ${w.permanent} liga(s) de foto permanentes volver\xEDan a ${w.incomingExternal} liga(s) de fuera`
|
|
9489
|
+
)
|
|
9490
|
+
)
|
|
9491
|
+
);
|
|
9492
|
+
return `
|
|
9493
|
+
${lines.join("\n")}
|
|
9494
|
+
${dim(t(`Keep them permanent: add --rehost-images ${flag}`, `Para que sigan permanentes: agrega --rehost-images ${flag}`))}`;
|
|
9495
|
+
}
|
|
8833
9496
|
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");
|
|
9497
|
+
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
9498
|
}
|
|
8836
9499
|
async function dbsImport(id, db, v) {
|
|
8837
9500
|
const ex = "ghosty dbs import <agent-id> <db> --from easybits:<db-id> [--tables a,b] [--dry-run]";
|
|
8838
9501
|
const m = /^easybits:(.+)$/.exec(v.from ?? "");
|
|
8839
|
-
if (!m) throw usage("Missing or bad --from.",
|
|
9502
|
+
if (!m) throw usage(t("Missing or bad --from.", "Falta --from o no es v\xE1lido."), `${t("Example", "Ejemplo")}: ${ex}`);
|
|
8840
9503
|
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.");
|
|
9504
|
+
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
9505
|
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
9506
|
const path = agentPath2(id, `/dbs/${enc6(db)}/import`);
|
|
8844
9507
|
const dry = await api(path, { body: { ...body, dryRun: true } });
|
|
8845
9508
|
if (v["dry-run"]) return out(dry, () => `${dry.db} \u2190 easybits:${m[1]}
|
|
8846
|
-
${planText(dry.plan)}
|
|
8847
|
-
${dim("Dry run: nothing changed.")}`);
|
|
9509
|
+
${planText(dry.plan)}${importWarnings(dry.warnings)}
|
|
9510
|
+
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
8848
9511
|
const destructive = dry.plan.some((p) => p.action === "replace" && p.targetRows > 0);
|
|
8849
|
-
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}` : ""}` });
|
|
9512
|
+
if (destructive) await confirm(`${planText(dry.plan)}${importWarnings(dry.warnings)}
|
|
9513
|
+
${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
9514
|
const r = await api(path, { body });
|
|
8852
9515
|
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:
|
|
9516
|
+
${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
9517
|
${r.rehosted.failed.map((f) => ` ${f.url} ${dim(f.error)}`).join("\n")}` : ""}` : "";
|
|
8855
9518
|
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.")}`);
|
|
9519
|
+
${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
9520
|
}
|
|
8858
9521
|
async function dbsRehost(id, db, v) {
|
|
8859
9522
|
const ex = "ghosty dbs rehost <agent-id> <db> --table catalogo --column imagen_url [--dry-run]";
|
|
@@ -8861,9 +9524,9 @@ async function dbsRehost(id, db, v) {
|
|
|
8861
9524
|
const column = need2(v.column, "--column", ex);
|
|
8862
9525
|
const path = agentPath2(id, `/dbs/${enc6(db)}/rehost`);
|
|
8863
9526
|
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`;
|
|
9527
|
+
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
9528
|
if (v["dry-run"] || !dry.toCopy) return out(dry, () => `${head}
|
|
8866
|
-
${dim(v["dry-run"] ? "Dry run: nothing changed." : "Nothing to do.")}`);
|
|
9529
|
+
${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
9530
|
info(head);
|
|
8868
9531
|
const total = { copied: 0, reused: 0, failed: [], warnings: [] };
|
|
8869
9532
|
let after;
|
|
@@ -8880,50 +9543,58 @@ ${dim(v["dry-run"] ? "Dry run: nothing changed." : "Nothing to do.")}`);
|
|
|
8880
9543
|
return out(
|
|
8881
9544
|
total,
|
|
8882
9545
|
() => [
|
|
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):
|
|
9546
|
+
`${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).`)}`,
|
|
9547
|
+
total.warnings.length ? `${total.warnings.length} ${t("warning(s)", "aviso(s)")}:
|
|
8885
9548
|
${total.warnings.map((w) => ` ${w.url} ${dim(w.warning)}`).join("\n")}` : "",
|
|
8886
|
-
total.failed.length ? `${red(String(total.failed.length))} kept as they were:
|
|
9549
|
+
total.failed.length ? `${red(String(total.failed.length))} ${t("kept as they were", "se quedaron como estaban")}:
|
|
8887
9550
|
${total.failed.map((f) => ` ${f.url} ${dim(f.error)}`).join("\n")}` : ""
|
|
8888
9551
|
].filter(Boolean).join("\n")
|
|
8889
9552
|
);
|
|
8890
9553
|
}
|
|
8891
9554
|
async function dbsCmd(sub, id, db, rest, v) {
|
|
8892
9555
|
const ex = "ghosty dbs ls <agent-id>";
|
|
8893
|
-
id = need2(id, "agent id", ex);
|
|
9556
|
+
id = need2(id, t("agent id", "el id del agente"), ex);
|
|
8894
9557
|
switch (sub ?? "ls") {
|
|
8895
9558
|
case "ls":
|
|
8896
9559
|
case "list": {
|
|
8897
9560
|
const r = await api(agentPath2(id, "/dbs"));
|
|
8898
9561
|
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."));
|
|
9562
|
+
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
9563
|
}
|
|
8901
9564
|
case "tables": {
|
|
8902
|
-
db = need2(db, "database (id or name)", "ghosty dbs tables <agent-id> <db>");
|
|
9565
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs tables <agent-id> <db>");
|
|
8903
9566
|
const r = await api(agentPath2(id, `/dbs/${enc6(db)}`));
|
|
8904
9567
|
const ext = new Set(r.externalWriteTables);
|
|
8905
9568
|
const own = r.externalRowScope ?? {};
|
|
8906
|
-
const rows = r.tables.map((
|
|
8907
|
-
|
|
8908
|
-
|
|
9569
|
+
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(", ") }));
|
|
9570
|
+
const linkLines = r.tables.flatMap(
|
|
9571
|
+
(tb) => (tb.links ?? []).map(
|
|
9572
|
+
(l) => ` ${tb.name}.${l.column}: ${t(`${l.permanent} permanent \xB7 ${l.external} external \xB7 ${l.empty} empty`, `${l.permanent} permanentes \xB7 ${l.external} de fuera \xB7 ${l.empty} vac\xEDas`)}` + (l.external ? dim(` \u2192 ghosty dbs rehost ${id} ${db} --table ${tb.name} --column ${l.column}`) : "")
|
|
9573
|
+
)
|
|
9574
|
+
);
|
|
9575
|
+
const linksText = linkLines.length ? `
|
|
9576
|
+
${t("Photo/link columns", "Columnas de fotos/ligas")}:
|
|
9577
|
+
${linkLines.join("\n")}` : "";
|
|
9578
|
+
return out(r, () => `${bold(r.name)} ${r.granted ? lavender(t("granted", "concedida")) : dim(t("not granted", "no concedida"))}
|
|
9579
|
+
${table(rows, ["table", "rows", "customer writes", "customer sees", "columns"])}${linksText}`);
|
|
8909
9580
|
}
|
|
8910
9581
|
case "query":
|
|
8911
9582
|
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"');
|
|
9583
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), 'ghosty dbs query <agent-id> <db> "SELECT \u2026"');
|
|
9584
|
+
const sql = need2(rest.join(" ").trim() || void 0, t("SQL", "el SQL"), 'ghosty dbs query <agent-id> <db> "SELECT * FROM t LIMIT 10"');
|
|
8914
9585
|
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.");
|
|
9586
|
+
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
9587
|
throw e;
|
|
8917
9588
|
});
|
|
8918
9589
|
return out(r, () => {
|
|
8919
|
-
if (!r.cols.length) return `${lavender("\u2713")} ${r.affected} row(s) affected ${dim(`(${Math.round(r.ms)} ms)`)}`;
|
|
9590
|
+
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
9591
|
const rows = r.rows.map((row) => Object.fromEntries(r.cols.map((c, i) => [c, row[i]])));
|
|
8921
9592
|
return `${table(rows, r.cols)}
|
|
8922
|
-
${dim(`${rows.length} row(s) \xB7 ${Math.round(r.ms)} ms`)}`;
|
|
9593
|
+
${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round(r.ms)} ms`)}`;
|
|
8923
9594
|
});
|
|
8924
9595
|
}
|
|
8925
9596
|
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]");
|
|
9597
|
+
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
9598
|
let rowScope;
|
|
8928
9599
|
try {
|
|
8929
9600
|
rowScope = v["external-rows"] !== void 0 ? parseRowScope(v["external-rows"]) : void 0;
|
|
@@ -8936,44 +9607,44 @@ ${dim(`${rows.length} row(s) \xB7 ${Math.round(r.ms)} ms`)}`;
|
|
|
8936
9607
|
};
|
|
8937
9608
|
const r = await api(agentPath2(id, `/dbs/${enc6(db)}`), { method: "PUT", body });
|
|
8938
9609
|
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}.` : ""}`)}`);
|
|
9610
|
+
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
9611
|
}
|
|
8941
9612
|
case "import": {
|
|
8942
|
-
db = need2(db, "database (id or name)", "ghosty dbs import <agent-id> <db> --from easybits:<db-id>");
|
|
9613
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs import <agent-id> <db> --from easybits:<db-id>");
|
|
8943
9614
|
return dbsImport(id, db, v);
|
|
8944
9615
|
}
|
|
8945
9616
|
case "rehost": {
|
|
8946
|
-
db = need2(db, "database (id or name)", "ghosty dbs rehost <agent-id> <db> --table T --column C");
|
|
9617
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs rehost <agent-id> <db> --table T --column C");
|
|
8947
9618
|
return dbsRehost(id, db, v);
|
|
8948
9619
|
}
|
|
8949
9620
|
case "revoke": {
|
|
8950
|
-
db = need2(db, "database (id or name)", "ghosty dbs revoke <agent-id> <db>");
|
|
9621
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs revoke <agent-id> <db>");
|
|
8951
9622
|
const r = await api(agentPath2(id, `/dbs/${enc6(db)}`), { method: "DELETE" });
|
|
8952
|
-
return out(r, () => `${lavender("\u2713")} ${r.name} revoked. ${dim(
|
|
9623
|
+
return out(r, () => `${lavender("\u2713")} ${r.name} ${t("revoked.", "revocada.")} ${dim(`${t("Undo", "Deshacer")}: ghosty dbs grant ${id} ${r.name}`)}`);
|
|
8953
9624
|
}
|
|
8954
9625
|
default:
|
|
8955
|
-
throw usage(`Unknown dbs subcommand: ${sub}.`, "Try: ghosty dbs ls | tables | query | import | rehost | grant | revoke (ghosty dbs --help)");
|
|
9626
|
+
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
9627
|
}
|
|
8957
9628
|
}
|
|
8958
9629
|
async function agentDocsCmd(sub, id, docs, v) {
|
|
8959
9630
|
let doc = docs[0];
|
|
8960
|
-
id = need2(id, "agent id", "ghosty agents docs ls <agent-id>");
|
|
9631
|
+
id = need2(id, t("agent id", "el id del agente"), "ghosty agents docs ls <agent-id>");
|
|
8961
9632
|
switch (sub ?? "ls") {
|
|
8962
9633
|
case "ls":
|
|
8963
9634
|
case "list": {
|
|
8964
9635
|
const r = await api(agentPath2(id, "/documents"));
|
|
8965
9636
|
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."));
|
|
9637
|
+
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
9638
|
}
|
|
8968
9639
|
case "grant": {
|
|
8969
9640
|
let wanted;
|
|
8970
9641
|
if (v.like) {
|
|
8971
|
-
if (docs.length) throw usage("Use document names or --like, not both.");
|
|
9642
|
+
if (docs.length) throw usage(t("Use document names or --like, not both.", "Usa nombres de documentos o --like, no ambos."));
|
|
8972
9643
|
const other = await api(agentPath2(v.like, "/documents"));
|
|
8973
9644
|
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.`));
|
|
9645
|
+
if (!wanted.length) return out({ granted: [] }, () => dim(t(`${v.like} has no documents granted.`, `${v.like} no tiene documentos concedidos.`)));
|
|
8975
9646
|
} 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>)');
|
|
9647
|
+
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
9648
|
const body = {};
|
|
8978
9649
|
if (v.write) body.mode = "write";
|
|
8979
9650
|
if (v["no-deliver"]) body.deliverable = false;
|
|
@@ -8982,11 +9653,11 @@ async function agentDocsCmd(sub, id, docs, v) {
|
|
|
8982
9653
|
}
|
|
8983
9654
|
const done = [];
|
|
8984
9655
|
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"));
|
|
9656
|
+
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
9657
|
}
|
|
8987
9658
|
case "add": {
|
|
8988
|
-
if (!v.url === !v.file) throw usage("Use --url URL or --file PATH (one of them).", '
|
|
8989
|
-
if (v.grant && !["read", "write", "no"].includes(v.grant)) throw usage("--grant takes read, write or no.");
|
|
9659
|
+
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"');
|
|
9660
|
+
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
9661
|
const body = {
|
|
8991
9662
|
...v.url ? { url: v.url } : { contentBase64: (await readFile6(v.file)).toString("base64"), name: v.name ?? basename3(v.file) },
|
|
8992
9663
|
...v.url && v.name ? { name: v.name } : {},
|
|
@@ -8995,21 +9666,21 @@ async function agentDocsCmd(sub, id, docs, v) {
|
|
|
8995
9666
|
...v["no-deliver"] ? { deliverable: false } : {}
|
|
8996
9667
|
};
|
|
8997
9668
|
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}"`}`);
|
|
9669
|
+
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
9670
|
}
|
|
9000
9671
|
case "revoke": {
|
|
9001
|
-
doc = need2(doc, "document (id or exact name)", 'ghosty agents docs revoke <agent-id> "Cat\xE1logo.pdf"');
|
|
9672
|
+
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
9673
|
const r = await api(agentPath2(id, `/documents/${enc6(doc)}`), { method: "DELETE" });
|
|
9003
|
-
return out(r, () => `${lavender("\u2713")} ${r.name} revoked. ${dim(
|
|
9674
|
+
return out(r, () => `${lavender("\u2713")} ${r.name} ${t("revoked.", "revocado.")} ${dim(`${t("Undo", "Deshacer")}: ghosty agents docs grant ${id} "${r.name}"`)}`);
|
|
9004
9675
|
}
|
|
9005
9676
|
default:
|
|
9006
|
-
throw usage(`Unknown agents docs subcommand: ${sub}.`, "Try: ghosty agents docs ls | add | grant | revoke");
|
|
9677
|
+
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
9678
|
}
|
|
9008
9679
|
}
|
|
9009
9680
|
async function turnsCmd(sub, id, turnId, v) {
|
|
9010
9681
|
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");
|
|
9682
|
+
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]");
|
|
9683
|
+
id = need2(id, t("agent id", "el id del agente"), "ghosty turns ls <agent-id> --errors --since 1h");
|
|
9013
9684
|
const q = new URLSearchParams();
|
|
9014
9685
|
const status = v.errors ? "error" : v.status;
|
|
9015
9686
|
if (status) q.set("state", status);
|
|
@@ -9017,44 +9688,44 @@ async function turnsCmd(sub, id, turnId, v) {
|
|
|
9017
9688
|
q.set("limit", v.limit ?? "20");
|
|
9018
9689
|
const r = await api(agentPath2(id, `/turns?${q}`));
|
|
9019
9690
|
const when = (s) => s.slice(0, 16).replace("T", " ");
|
|
9020
|
-
const rows = r.turns.map((
|
|
9021
|
-
id:
|
|
9022
|
-
started: when(
|
|
9023
|
-
state:
|
|
9024
|
-
secs:
|
|
9025
|
-
from:
|
|
9026
|
-
session:
|
|
9027
|
-
error: (
|
|
9691
|
+
const rows = r.turns.map((tu) => ({
|
|
9692
|
+
id: tu.id,
|
|
9693
|
+
started: when(tu.startedAt),
|
|
9694
|
+
state: tu.state === "error" ? red(tu.state) : tu.state,
|
|
9695
|
+
secs: tu.ms == null ? "" : Math.round(tu.ms / 1e3),
|
|
9696
|
+
from: tu.surface ?? "",
|
|
9697
|
+
session: tu.sessionId.slice(0, 24),
|
|
9698
|
+
error: serverText(tu.error ?? "").replace(/\s+/g, " ").slice(0, 90)
|
|
9028
9699
|
}));
|
|
9029
9700
|
const tally = Object.entries(r.counts).map(([k, n]) => `${k} ${n}`).join(" \xB7 ");
|
|
9030
9701
|
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}` : ""}.`));
|
|
9702
|
+
${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
9703
|
}
|
|
9033
9704
|
async function turnShow(id, turnId, full = false) {
|
|
9034
|
-
id = need2(id, "agent id", "ghosty turns show <agent-id> [turn-id|last] [--full]");
|
|
9705
|
+
id = need2(id, t("agent id", "el id del agente"), "ghosty turns show <agent-id> [turn-id|last] [--full]");
|
|
9035
9706
|
const r = await api(agentPath2(id, `/turns/${enc6(turnId ?? "last")}`));
|
|
9036
9707
|
out(r, () => {
|
|
9037
|
-
const
|
|
9038
|
-
const clip = (s) => full || s.length <= 600 ? s : `${s.slice(0, 600)}\u2026 ${dim(`(+${s.length - 600} chars, --full)`)}`;
|
|
9708
|
+
const tu = r.turn;
|
|
9709
|
+
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
9710
|
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)")}`
|
|
9711
|
+
`${lavender("in ")} ${r.input ? clip(r.input) : dim(t("(no copy of the message)", "(sin copia del mensaje)"))}`,
|
|
9712
|
+
`${lavender("out")} ${r.output ? clip(r.output) : dim(t("(no answer saved)", "(sin respuesta guardada)"))}`
|
|
9042
9713
|
];
|
|
9043
|
-
const state = `${bold(
|
|
9044
|
-
if (!r.totals) return [state, ...io, dim(
|
|
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`)}`;
|
|
9714
|
+
const state = `${bold(tu.id)} ${tu.state === "error" ? red(tu.state) : tu.state} ${tu.secs ?? "?"} s${tu.session ? dim(` ${tu.session}`) : ""}`;
|
|
9715
|
+
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");
|
|
9716
|
+
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
9717
|
const line2 = (s) => {
|
|
9047
9718
|
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") : ""}`;
|
|
9719
|
+
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
9720
|
const tok = s.outTokens ? dim(` [${s.outTokens} out]`) : "";
|
|
9050
|
-
return s.kind === "thinking" ? `${at} ${dim("thinking")}${tok}` : `${at} ${s.text}${tok}`;
|
|
9721
|
+
return s.kind === "thinking" ? `${at} ${dim(t("thinking", "pensando"))}${tok}` : `${at} ${s.text}${tok}`;
|
|
9051
9722
|
};
|
|
9052
9723
|
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(
|
|
9724
|
+
return [head, ...io, ...r.steps.map(line2), dim(`${t("slowest", "lo m\xE1s lento")}: ${top || "\u2014"}`)].join("\n");
|
|
9054
9725
|
});
|
|
9055
9726
|
}
|
|
9056
9727
|
async function agentsDoctor(id, v) {
|
|
9057
|
-
id = need2(id, "agent id", "ghosty agents doctor <agent-id>");
|
|
9728
|
+
id = need2(id, t("agent id", "el id del agente"), "ghosty agents doctor <agent-id>");
|
|
9058
9729
|
const r = await api(agentPath2(id, "/doctor"));
|
|
9059
9730
|
const problems = r.summary.warn + r.summary.error;
|
|
9060
9731
|
if (globals.json) out(r);
|
|
@@ -9063,18 +9734,62 @@ async function agentsDoctor(id, v) {
|
|
|
9063
9734
|
const lines = [];
|
|
9064
9735
|
for (const c of r.checks) {
|
|
9065
9736
|
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}`);
|
|
9737
|
+
lines.push(`${process.stdout.isTTY ? mark[c.level] : { ok: "\u2713", warn: "!", error: "\u2717" }[c.level]} ${serverText(c.title)}`);
|
|
9738
|
+
if (c.detail && c.level !== "ok") lines.push(...serverText(c.detail).split("\n").map((l) => ` ${dim(l)}`));
|
|
9739
|
+
if (c.fix && c.level !== "ok") lines.push(` ${dim(t("fix:", "arreglo:"))} ${serverText(c.fix)}`);
|
|
9069
9740
|
}
|
|
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).`);
|
|
9741
|
+
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
9742
|
process.stdout.write(lines.join("\n") + "\n");
|
|
9072
9743
|
}
|
|
9073
9744
|
if (problems) {
|
|
9074
|
-
if (!globals.json) info(dim("Exit 1: problems found."));
|
|
9745
|
+
if (!globals.json) info(dim(t("Exit 1: problems found.", "Salida 1: hay problemas.")));
|
|
9075
9746
|
return exitAfterFlush(EXIT.API);
|
|
9076
9747
|
}
|
|
9077
9748
|
}
|
|
9749
|
+
var PHOTO_EXT = /\.(jpe?g|png|webp)$/i;
|
|
9750
|
+
function photoFiles(names2) {
|
|
9751
|
+
return names2.filter((n) => PHOTO_EXT.test(n) && !n.startsWith(".")).sort().map((file) => ({ key: file.replace(PHOTO_EXT, ""), file }));
|
|
9752
|
+
}
|
|
9753
|
+
async function dbsPhotosCmd(verb, id, db, v) {
|
|
9754
|
+
const ex = "ghosty dbs photos put <agent> <db> --table catalogo --key-column sku --dir fotos/ [--column imagen_url] [--dry-run]";
|
|
9755
|
+
if (verb !== "put") throw usage(t(`Unknown dbs photos subcommand: ${verb ?? "(none)"}.`, `Subcomando desconocido de dbs photos: ${verb ?? "(ninguno)"}.`), `${t("Example", "Ejemplo")}: ${ex}`);
|
|
9756
|
+
id = need2(id, t("agent", "el agente"), ex);
|
|
9757
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), ex);
|
|
9758
|
+
const tableName = need2(v.table, "--table", ex);
|
|
9759
|
+
const keyColumn = need2(v["key-column"], "--key-column", ex);
|
|
9760
|
+
const dir = need2(v.dir, "--dir", ex);
|
|
9761
|
+
let column = v.column;
|
|
9762
|
+
if (!column) {
|
|
9763
|
+
const r = await api(agentPath2(id, `/dbs/${enc6(db)}`));
|
|
9764
|
+
const tb = r.tables.find((x) => x.name === tableName);
|
|
9765
|
+
if (!tb) throw usage(t(`${db} has no table ${tableName}.`, `${db} no tiene la tabla ${tableName}.`));
|
|
9766
|
+
column = tb.columns.find((c) => c.name === "imagen_url")?.name;
|
|
9767
|
+
if (!column) throw usage(t("Which column gets the photo link? Pass --column.", "\xBFEn qu\xE9 columna va la liga de la foto? Usa --column."), `${t("Columns", "Columnas")}: ${tb.columns.map((c) => c.name).join(", ")}`);
|
|
9768
|
+
}
|
|
9769
|
+
const files = photoFiles(await readdir2(dir));
|
|
9770
|
+
if (!files.length) throw usage(t(`No .jpg/.png/.webp files in ${dir}.`, `No hay archivos .jpg/.png/.webp en ${dir}.`), t("Name each photo after its key: SKU-123.jpg", "Nombra cada foto con su clave: SKU-123.jpg"));
|
|
9771
|
+
const path = agentPath2(id, `/dbs/${enc6(db)}/photos`);
|
|
9772
|
+
const results = [];
|
|
9773
|
+
for (let i = 0; i < files.length; i += 8) {
|
|
9774
|
+
const batch = files.slice(i, i + 8);
|
|
9775
|
+
const items = await Promise.all(batch.map(async (f) => ({ key: f.key, filename: f.file, contentBase64: (await readFile6(join4(dir, f.file))).toString("base64") })));
|
|
9776
|
+
const r = await api(path, { body: { table: tableName, keyColumn, column, items, ...v["dry-run"] ? { dryRun: true } : {} } });
|
|
9777
|
+
results.push(...r.items);
|
|
9778
|
+
if (!globals.json && files.length > 8) info(dim(` ${Math.min(i + 8, files.length)}/${files.length}`));
|
|
9779
|
+
}
|
|
9780
|
+
const by = (s) => results.filter((r) => r.status === s);
|
|
9781
|
+
const stored = by(v["dry-run"] ? "would_store" : "stored");
|
|
9782
|
+
const missing = by("missing_row");
|
|
9783
|
+
const bad = [...by("bad_type"), ...by("too_big"), ...by("failed")];
|
|
9784
|
+
const summary = [
|
|
9785
|
+
`${lavender("\u2713")} ${v["dry-run"] ? t(`${stored.length} photo(s) would go to ${tableName}.${column}`, `${stored.length} foto(s) ir\xEDan a ${tableName}.${column}`) : t(`${stored.length} photo(s) stored in ${tableName}.${column} (permanent links)`, `${stored.length} foto(s) guardadas en ${tableName}.${column} (ligas permanentes)`)}`,
|
|
9786
|
+
missing.length ? red(t(`${missing.length} with no row where ${keyColumn} = file name: ${missing.map((x) => x.key).join(", ")}`, `${missing.length} sin fila con ${keyColumn} = nombre del archivo: ${missing.map((x) => x.key).join(", ")}`)) : "",
|
|
9787
|
+
bad.length ? red(t(`${bad.length} not stored: ${bad.map((b) => `${b.key} (${b.error ? serverText(b.error) : b.status})`).join(", ")}`, `${bad.length} sin guardar: ${bad.map((b) => `${b.key} (${b.error ?? b.status})`).join(", ")}`)) : "",
|
|
9788
|
+
...results.filter((x) => x.warning).map((x) => dim(` ${x.key}: ${serverText(x.warning)}`)),
|
|
9789
|
+
v["dry-run"] ? dim(t("Dry run: nothing uploaded.", "Simulaci\xF3n: no se subi\xF3 nada.")) : ""
|
|
9790
|
+
].filter(Boolean);
|
|
9791
|
+
out({ table: tableName, keyColumn, column, dryRun: !!v["dry-run"], items: results }, () => summary.join("\n"));
|
|
9792
|
+
}
|
|
9078
9793
|
|
|
9079
9794
|
// src/commands/credentials.ts
|
|
9080
9795
|
async function hiddenPrompt(label) {
|
|
@@ -9097,7 +9812,7 @@ async function hiddenPrompt(label) {
|
|
|
9097
9812
|
if (ch === "") {
|
|
9098
9813
|
stdin.setRawMode(false);
|
|
9099
9814
|
process.stderr.write("\n");
|
|
9100
|
-
return reject(usage("Cancelled."));
|
|
9815
|
+
return reject(usage(t("Cancelled.", "Cancelado.")));
|
|
9101
9816
|
}
|
|
9102
9817
|
if (ch === "\x7F") value = value.slice(0, -1);
|
|
9103
9818
|
else value += ch;
|
|
@@ -9109,7 +9824,7 @@ async function hiddenPrompt(label) {
|
|
|
9109
9824
|
async function readSecret(provider, fromEnv) {
|
|
9110
9825
|
if (fromEnv) {
|
|
9111
9826
|
const v = process.env[fromEnv];
|
|
9112
|
-
if (!v) throw usage(`$${fromEnv} is empty or not set.`,
|
|
9827
|
+
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
9828
|
return v;
|
|
9114
9829
|
}
|
|
9115
9830
|
if (!process.stdin.isTTY) {
|
|
@@ -9117,8 +9832,8 @@ async function readSecret(provider, fromEnv) {
|
|
|
9117
9832
|
for await (const c of process.stdin) parts.push(c);
|
|
9118
9833
|
return Buffer.concat(parts).toString("utf8").replace(/\r?\n$/, "");
|
|
9119
9834
|
}
|
|
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): `);
|
|
9835
|
+
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.`));
|
|
9836
|
+
return hiddenPrompt(t(`${provider} key (hidden): `, `Llave de ${provider} (oculta): `));
|
|
9122
9837
|
}
|
|
9123
9838
|
async function credentialsCmd2(sub, provider, v) {
|
|
9124
9839
|
switch (sub ?? "ls") {
|
|
@@ -9129,21 +9844,21 @@ async function credentialsCmd2(sub, provider, v) {
|
|
|
9129
9844
|
return out(r.credentials, () => table(rows, ["provider", "set", "updated"]));
|
|
9130
9845
|
}
|
|
9131
9846
|
case "set": {
|
|
9132
|
-
if (!provider) throw usage("Missing provider.", "Example: ghosty credentials set elevenlabs --from-env ELEVENLABS_API_KEY (ghosty credentials ls lists them)");
|
|
9847
|
+
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
9848
|
const key = (await readSecret(provider, v["from-env"])).trim();
|
|
9134
|
-
if (!key) throw usage("Empty key: nothing saved.");
|
|
9849
|
+
if (!key) throw usage(t("Empty key: nothing saved.", "Llave vac\xEDa: no se guard\xF3 nada."));
|
|
9135
9850
|
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.`)}`);
|
|
9851
|
+
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
9852
|
}
|
|
9138
9853
|
case "rm":
|
|
9139
9854
|
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}` });
|
|
9855
|
+
if (!provider) throw usage(t("Missing provider.", "Falta el proveedor."), t("Example", "Ejemplo") + ": ghosty credentials rm elevenlabs");
|
|
9856
|
+
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
9857
|
const r = await api(`/api/v2/me/credentials/${encodeURIComponent(provider)}`, { method: "DELETE" });
|
|
9143
|
-
return out(r, () => `${lavender("\u2713")} ${provider} removed
|
|
9858
|
+
return out(r, () => `${lavender("\u2713")} ${provider} ${t("removed.", "quitada.")}`);
|
|
9144
9859
|
}
|
|
9145
9860
|
default:
|
|
9146
|
-
throw usage(`Unknown credentials subcommand: ${sub}.`, "Try: ghosty credentials ls | set | rm (ghosty credentials --help)");
|
|
9861
|
+
throw usage(t(`Unknown credentials subcommand: ${sub}.`, `Subcomando de credentials desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty credentials ls | set | rm (ghosty credentials --help)");
|
|
9147
9862
|
}
|
|
9148
9863
|
}
|
|
9149
9864
|
|
|
@@ -9151,8 +9866,8 @@ async function credentialsCmd2(sub, provider, v) {
|
|
|
9151
9866
|
import { readFile as readFile8 } from "node:fs/promises";
|
|
9152
9867
|
|
|
9153
9868
|
// src/wa-auth.ts
|
|
9154
|
-
import { readdir as
|
|
9155
|
-
import { join as
|
|
9869
|
+
import { readdir as readdir3, readFile as readFile7 } from "node:fs/promises";
|
|
9870
|
+
import { join as join5 } from "node:path";
|
|
9156
9871
|
var TYPES = ["app-state-sync-version", "app-state-sync-key", "sender-key-memory", "sender-key", "lid-mapping", "device-list", "identity-key", "pre-key", "session", "tctoken"];
|
|
9157
9872
|
function keyOfFileName(name) {
|
|
9158
9873
|
for (const type of TYPES) {
|
|
@@ -9164,9 +9879,9 @@ function keyOfFileName(name) {
|
|
|
9164
9879
|
return null;
|
|
9165
9880
|
}
|
|
9166
9881
|
async function readMultiFileAuth(dir) {
|
|
9167
|
-
const files = (await
|
|
9882
|
+
const files = (await readdir3(dir)).filter((f) => f.endsWith(".json"));
|
|
9168
9883
|
if (!files.includes("creds.json")) throw new Error(`${dir} has no creds.json: not a Baileys multi-file auth folder`);
|
|
9169
|
-
const authCreds = JSON.parse(await readFile7(
|
|
9884
|
+
const authCreds = JSON.parse(await readFile7(join5(dir, "creds.json"), "utf8"));
|
|
9170
9885
|
const authKeys = {};
|
|
9171
9886
|
const counts = {};
|
|
9172
9887
|
const skipped = [];
|
|
@@ -9177,7 +9892,7 @@ async function readMultiFileAuth(dir) {
|
|
|
9177
9892
|
skipped.push(f);
|
|
9178
9893
|
continue;
|
|
9179
9894
|
}
|
|
9180
|
-
(authKeys[k.type] ??= {})[k.id] = JSON.parse(await readFile7(
|
|
9895
|
+
(authKeys[k.type] ??= {})[k.id] = JSON.parse(await readFile7(join5(dir, f), "utf8"));
|
|
9181
9896
|
counts[k.type] = (counts[k.type] ?? 0) + 1;
|
|
9182
9897
|
}
|
|
9183
9898
|
return { authCreds, authKeys, counts, skipped };
|
|
@@ -9189,12 +9904,12 @@ var digits = (s) => s.replace(/\D/g, "");
|
|
|
9189
9904
|
function find(nums, ref) {
|
|
9190
9905
|
const d = digits(ref);
|
|
9191
9906
|
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 ?
|
|
9907
|
+
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
9908
|
return hit;
|
|
9194
9909
|
}
|
|
9195
9910
|
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>");
|
|
9911
|
+
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>");
|
|
9912
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels whatsapp ls <agent-id>");
|
|
9198
9913
|
if (sub === "link" || sub === "unlink") return linkCmd(sub, id, v);
|
|
9199
9914
|
if (sub === "history") return historyCmd(id, v);
|
|
9200
9915
|
if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd(sub, id, v);
|
|
@@ -9203,25 +9918,25 @@ async function channelsCmd(kind, sub, id, ref, v) {
|
|
|
9203
9918
|
const listing = !sub || sub === "ls" || sub === "list";
|
|
9204
9919
|
const cur = await api(listing ? `${path}?formmy=1` : path);
|
|
9205
9920
|
if (listing) {
|
|
9206
|
-
const fwd = (n2) => !n2.formmy ? dim("\u2014") : "error" in n2.formmy ? dim("?") : n2.formmy.forwards ? "yes" : lavender("NO");
|
|
9921
|
+
const fwd = (n2) => !n2.formmy ? dim("\u2014") : "error" in n2.formmy ? dim("?") : n2.formmy.forwards ? t("yes", "s\xED") : lavender("NO");
|
|
9207
9922
|
const rows = cur.numbers.map((n2) => ({
|
|
9208
9923
|
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)"),
|
|
9924
|
+
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
9925
|
"formmy forwards": fwd(n2),
|
|
9211
9926
|
id: n2.id
|
|
9212
9927
|
}));
|
|
9213
|
-
return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "id"]) : dim(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`));
|
|
9928
|
+
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
9929
|
}
|
|
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.",
|
|
9930
|
+
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");
|
|
9931
|
+
if (!ref) throw usage(t("Missing number.", "Falta el n\xFAmero."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} ${id} +52 55 1234 5678`);
|
|
9217
9932
|
const n = find(cur.numbers, ref);
|
|
9218
9933
|
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)`;
|
|
9934
|
+
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"}.`)));
|
|
9935
|
+
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
9936
|
if (v["dry-run"]) return out({ dryRun: true, plan: [plan] }, () => `${plan}
|
|
9222
|
-
${dim("Dry run: nothing changed.")}`);
|
|
9937
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
9223
9938
|
if (on) await confirm(`${bold(plan)}
|
|
9224
|
-
Real customers will get answers from this agent
|
|
9939
|
+
${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
9940
|
const wanted = cur.numbers.filter((x) => x.id === n.id ? on : x.mine).map((x) => x.id);
|
|
9226
9941
|
const r = await api(path, { method: "PUT", body: { numbers: wanted } });
|
|
9227
9942
|
return out(r, () => `${lavender("\u2713")} ${plan.slice(2)}`);
|
|
@@ -9231,118 +9946,121 @@ function findGroup(groups, ref) {
|
|
|
9231
9946
|
if (byJid) return byJid;
|
|
9232
9947
|
const byName = groups.filter((g) => g.name.toLowerCase() === ref.toLowerCase());
|
|
9233
9948
|
if (byName.length === 1) return byName[0];
|
|
9234
|
-
if (byName.length > 1) throw usage(`${byName.length} groups are called "${ref}".`,
|
|
9235
|
-
throw usage(`No group "${ref}" for this agent.`, groups.length ?
|
|
9949
|
+
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(", ")}`);
|
|
9950
|
+
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
9951
|
}
|
|
9237
9952
|
async function groupsCmd(args, v) {
|
|
9238
9953
|
const [sub, id, ...rest] = args;
|
|
9239
9954
|
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.",
|
|
9955
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), `${t("Example", "Ejemplo")}: ${ex}`);
|
|
9241
9956
|
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.");
|
|
9957
|
+
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
9958
|
if (!sub || sub === "ls" || sub === "list") {
|
|
9244
9959
|
const r2 = await api(`${path}${v.cached ? "?live=0" : ""}`);
|
|
9245
9960
|
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"'));
|
|
9961
|
+
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
9962
|
}
|
|
9248
9963
|
if (sub === "create") {
|
|
9249
9964
|
const name = rest.join(" ").trim();
|
|
9250
|
-
if (!name) throw usage("Missing group name.",
|
|
9965
|
+
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
9966
|
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}`);
|
|
9967
|
+
return out(r2, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${bold(name)} (${v.role ?? "equipo"}, on)
|
|
9968
|
+
${t("invite", "invitaci\xF3n")}: ${r2.inviteUrl}`);
|
|
9254
9969
|
}
|
|
9255
|
-
if (sub !== "set") throw usage(`Unknown groups subcommand: ${sub}.`, ex);
|
|
9970
|
+
if (sub !== "set") throw usage(t(`Unknown groups subcommand: ${sub}.`, `Subcomando de groups desconocido: ${sub}.`), ex);
|
|
9256
9971
|
const ref = rest.join(" ").trim();
|
|
9257
|
-
if (!ref) throw usage("Missing group (name or jid).",
|
|
9258
|
-
if (v.on && v.off) throw usage("Use --on or --off, not both.");
|
|
9972
|
+
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`);
|
|
9973
|
+
if (v.on && v.off) throw usage(t("Use --on or --off, not both.", "Usa --on o --off, no los dos."));
|
|
9259
9974
|
const cur = await api(`${path}?live=0`);
|
|
9260
9975
|
const g = findGroup(cur.groups, ref);
|
|
9261
9976
|
const enabled = v.on ? true : v.off ? false : g.enabled;
|
|
9262
9977
|
const role = v.role ?? g.role;
|
|
9263
9978
|
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}.`));
|
|
9979
|
+
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
9980
|
const plan = `~ ${g.name}: ${changes.join(", ")}`;
|
|
9266
9981
|
if (v["dry-run"]) return out({ dryRun: true, plan: [plan] }, () => `${plan}
|
|
9267
|
-
${dim("Dry run: nothing changed.")}`);
|
|
9982
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
9268
9983
|
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` });
|
|
9984
|
+
${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
9985
|
const r = await api(path, { method: "POST", body: { jid: g.jid, enabled, role } });
|
|
9271
9986
|
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}`);
|
|
9987
|
+
return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${g.name}: ${now?.enabled ? "on" : "off"} ${t("as", "como")} ${now?.role ?? role}`);
|
|
9273
9988
|
}
|
|
9274
9989
|
async function waCmd(sub, id, v) {
|
|
9275
|
-
if (!id) throw usage("Missing agent id.",
|
|
9990
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + `: ghosty channels wa ${sub} <agent-id>`);
|
|
9276
9991
|
const base = `/api/v2/agents/${enc7(id)}/wa`;
|
|
9277
9992
|
if (sub === "status") {
|
|
9278
9993
|
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)}` : ""}`);
|
|
9994
|
+
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 ? `
|
|
9995
|
+
${dim(serverText(s.reason))}` : ""}`);
|
|
9281
9996
|
}
|
|
9282
9997
|
if (sub === "pair") {
|
|
9283
9998
|
const phone = (v.phone ?? "").replace(/\D/g, "");
|
|
9284
|
-
if (phone.length < 10) throw usage("Missing --phone (10-15 digits, with country code).",
|
|
9999
|
+
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
10000
|
await api(base, { method: "POST", body: { phone } });
|
|
9286
10001
|
let s = { status: "connecting", phone: null, live: false, hasCreds: false };
|
|
9287
10002
|
for (let i = 0; i < 15 && !s.pairingCode && s.status !== "connected"; i++) {
|
|
9288
10003
|
await new Promise((r2) => setTimeout(r2, 1e3));
|
|
9289
10004
|
s = await api(base);
|
|
9290
10005
|
}
|
|
9291
|
-
return out(s, () => s.status === "connected" ? `${lavender("\u2713")} Already connected (+${s.phone}).` : 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.`));
|
|
10006
|
+
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)}
|
|
10007
|
+
${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
10008
|
}
|
|
9294
10009
|
if (sub === "pause") {
|
|
9295
10010
|
await api(base, { method: "DELETE" });
|
|
9296
|
-
return out({ paused: true }, () => `${lavender("\u2713")} Paused. The session is kept: ${dim(
|
|
10011
|
+
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
10012
|
}
|
|
9298
10013
|
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).",
|
|
10014
|
+
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
10015
|
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)");
|
|
10016
|
+
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
10017
|
let data;
|
|
9303
10018
|
if (v["from-dir"]) {
|
|
9304
10019
|
const m = await readMultiFileAuth(v["from-dir"]);
|
|
9305
|
-
info(dim(`${v["from-dir"]}: ${Object.entries(m.counts).map(([
|
|
10020
|
+
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
10021
|
data = { authCreds: m.authCreds, authKeys: m.authKeys };
|
|
9307
10022
|
} else data = JSON.parse(await readFile8(file, "utf8"));
|
|
9308
10023
|
const body = { authCreds: data.authCreds, authKeys: data.authKeys, phone: data.phone, groups: data.groups, ownNumber: own === "yes", replace: !!v.replace };
|
|
9309
10024
|
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)
|
|
10025
|
+
if (v["dry-run"]) return out(plan, () => `${plan.plan.map((x) => serverText(x)).join("\n")}
|
|
10026
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
10027
|
+
await confirm(`${bold(plan.plan.map((x) => serverText(x)).join("\n"))}
|
|
10028
|
+
${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
10029
|
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`)}`);
|
|
10030
|
+
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)})`) : ""}
|
|
10031
|
+
${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
10032
|
}
|
|
9318
10033
|
async function linkCmd(sub, id, v) {
|
|
9319
|
-
if (!id || !v.integration) throw usage("Missing agent id or --integration.",
|
|
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)");
|
|
10034
|
+
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`);
|
|
10035
|
+
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
10036
|
const path = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
|
|
9322
10037
|
const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {} } };
|
|
9323
10038
|
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})`)}`;
|
|
10039
|
+
const text = `${plan.plan.map((x) => serverText(x)).join("\n")}
|
|
10040
|
+
${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (${t("as", "como")} ${plan.formmy.by})`)}`;
|
|
9326
10041
|
if (v["dry-run"]) return out(plan, () => `${text}
|
|
9327
|
-
${dim("Dry run: nothing changed.")}`);
|
|
10042
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
9328
10043
|
const digits4 = (plan.number.phoneNumberId ?? v.integration).slice(-4);
|
|
9329
10044
|
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" : ""}` : ""}` });
|
|
10045
|
+
${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
10046
|
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."}`);
|
|
10047
|
+
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
10048
|
}
|
|
9334
10049
|
async function historyCmd(id, v) {
|
|
9335
|
-
if (!v.integration) throw usage("Missing --integration.",
|
|
10050
|
+
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
10051
|
const days = Number(v.days ?? 7);
|
|
9337
|
-
if (!Number.isInteger(days) || days < 1 || days > 30) throw usage("--days takes 1 to 30.");
|
|
10052
|
+
if (!Number.isInteger(days) || days < 1 || days > 30) throw usage(t("--days takes 1 to 30.", "--days acepta de 1 a 30."));
|
|
9338
10053
|
const path = `/api/v2/agents/${enc7(id)}/whatsapp/history`;
|
|
9339
10054
|
const c = await api(path, { method: "POST", body: { integrationId: v.integration, days, dryRun: true } });
|
|
9340
|
-
const plan =
|
|
10055
|
+
const plan = t(
|
|
10056
|
+
`${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}`,
|
|
10057
|
+
`${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}`
|
|
10058
|
+
);
|
|
9341
10059
|
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}.`));
|
|
10060
|
+
${dim(t("Dry run: nothing imported. Re-running later never duplicates.", "Simulacro: no se import\xF3 nada. Correrlo otra vez nunca duplica."))}`);
|
|
10061
|
+
if (!c.conversations) return out({ ...c, imported: 0 }, () => dim(t(`Nothing to import: ${plan}.`, `Nada que importar: ${plan}.`)));
|
|
9344
10062
|
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}` });
|
|
10063
|
+
${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
10064
|
const sum = { newCards: 0, updatedCards: 0, messages: 0, orders: 0, suppressed: 0 };
|
|
9347
10065
|
let cursor = 0;
|
|
9348
10066
|
let done = 0;
|
|
@@ -9350,47 +10068,47 @@ Cards appear on the team's board (no "new lead" alerts).`, { yes: v.yes, retry:
|
|
|
9350
10068
|
const p = await api(path, { method: "POST", body: { integrationId: v.integration, days, cursor } });
|
|
9351
10069
|
for (const k of Object.keys(sum)) sum[k] += p[k];
|
|
9352
10070
|
done += p.page;
|
|
9353
|
-
if (!globals.json) info(dim(` ${done}/${p.total} conversations`));
|
|
10071
|
+
if (!globals.json) info(dim(` ${done}/${p.total} ${t("conversations", "conversaciones")}`));
|
|
9354
10072
|
cursor = p.nextCursor;
|
|
9355
10073
|
}
|
|
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`) : ""}`);
|
|
10074
|
+
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
10075
|
}
|
|
9358
10076
|
|
|
9359
10077
|
// src/commands/board.ts
|
|
9360
|
-
var
|
|
10078
|
+
var who = (role) => ({ user: t("customer", "cliente"), assistant: t("agent", "agente"), operator: t("operator", "operador") })[role] ?? role;
|
|
9361
10079
|
async function boardLs(id, v) {
|
|
9362
10080
|
const qs = new URLSearchParams({ ...v.q ? { q: v.q } : {}, ...v.limit ? { limit: v.limit } : {} });
|
|
9363
10081
|
const r = await api(`/api/v2/agents/${encodeURIComponent(id)}/board/conversations?${qs}`);
|
|
9364
10082
|
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
10083
|
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}"` : ""}.`));
|
|
10084
|
+
${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
10085
|
}
|
|
9368
10086
|
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})`);
|
|
10087
|
+
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
10088
|
const r = await api(`/api/v2/agents/${encodeURIComponent(id)}/board/conversations/${encodeURIComponent(ref)}${v.limit ? `?limit=${encodeURIComponent(v.limit)}` : ""}`);
|
|
9371
10089
|
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}`)}`;
|
|
10090
|
+
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
10091
|
const line2 = (m) => {
|
|
9374
10092
|
const at = m.createdAt.slice(5, 16).replace("T", " ");
|
|
9375
10093
|
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((
|
|
10094
|
+
${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")}`)}` : "";
|
|
10095
|
+
return `${dim(at)} ${lavender(who(m.role).padEnd(8))} ${m.content.replace(/\n/g, "\n ")}${media}`;
|
|
9378
10096
|
};
|
|
9379
10097
|
out(r, () => [head, ...r.messages.map(line2)].join("\n"));
|
|
9380
10098
|
}
|
|
9381
10099
|
async function boardCmd(sub, id, v, ref) {
|
|
9382
10100
|
if (sub === "ls" || sub === "list") {
|
|
9383
|
-
if (!id) throw usage("Missing agent id.", "Example: ghosty board ls <agent-id> [--q text] [--limit 20]");
|
|
10101
|
+
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
10102
|
return boardLs(id, v);
|
|
9385
10103
|
}
|
|
9386
10104
|
if (sub === "show") {
|
|
9387
|
-
if (!id) throw usage("Missing agent id.", '
|
|
10105
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ': ghosty board show <agent-id> "#12"');
|
|
9388
10106
|
return boardShow(id, ref, v);
|
|
9389
10107
|
}
|
|
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");
|
|
10108
|
+
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");
|
|
10109
|
+
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
10110
|
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.");
|
|
10111
|
+
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
10112
|
const retry = `ghosty board archive ${id} ${Object.entries(filter).map(([k, x]) => `--${k} ${JSON.stringify(x)}`).join(" ")}`;
|
|
9395
10113
|
if (!v["dry-run"]) requireYesIfHeadless(v.yes, retry);
|
|
9396
10114
|
const path = `/api/v2/agents/${encodeURIComponent(id)}/board/archive`;
|
|
@@ -9398,12 +10116,12 @@ async function boardCmd(sub, id, v, ref) {
|
|
|
9398
10116
|
const sample = () => table(plan.sample.map((c) => ({ name: c.name ?? "", phone: c.phone, column: c.estado_label ?? "" })), ["name", "phone", "column"]);
|
|
9399
10117
|
if (v["dry-run"] || plan.matched === 0) {
|
|
9400
10118
|
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.`));
|
|
10119
|
+
${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
10120
|
}
|
|
9403
10121
|
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 });
|
|
10122
|
+
${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
10123
|
const r = await api(path, { body: filter });
|
|
9406
|
-
out(r, () => `${lavender("\u2713")} Archived ${r.archived} card(s) on \xAB${r.board}\xBB.`);
|
|
10124
|
+
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
10125
|
}
|
|
9408
10126
|
|
|
9409
10127
|
// src/commands/boards.ts
|
|
@@ -9416,31 +10134,31 @@ async function boardsCmd(sub, a1, a2, v, context) {
|
|
|
9416
10134
|
switch (sub ?? "ls") {
|
|
9417
10135
|
case "ls":
|
|
9418
10136
|
case "list": {
|
|
9419
|
-
if (!a1) throw usage("Missing workspace.", "Example: ghosty boards ls <workspace-slug>");
|
|
10137
|
+
if (!a1) throw usage(t("Missing workspace.", "Falta el espacio."), t("Example", "Ejemplo") + ": ghosty boards ls <workspace-slug>");
|
|
9420
10138
|
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 }));
|
|
10139
|
+
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
10140
|
return out(r, () => table(rows, ["name", "cards", "agents", "columns", "id"]));
|
|
9423
10141
|
}
|
|
9424
10142
|
case "create": {
|
|
9425
|
-
if (!a1 || !a2) throw usage("Missing workspace or name.", '
|
|
10143
|
+
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
10144
|
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(
|
|
10145
|
+
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
10146
|
}
|
|
9429
10147
|
case "rm": {
|
|
9430
|
-
if (!a1 || !a2) throw usage("Missing workspace or board.", '
|
|
10148
|
+
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
10149
|
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)}` });
|
|
10150
|
+
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
10151
|
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.`);
|
|
10152
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.deleted}\xBB deleted.`, `Tablero \xAB${r.deleted}\xBB borrado.`)}`);
|
|
9435
10153
|
}
|
|
9436
10154
|
case "assign": {
|
|
9437
|
-
if (!a1 || !a2) throw usage("Missing agent or board.", '
|
|
10155
|
+
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
10156
|
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(
|
|
10157
|
+
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
10158
|
}
|
|
9441
10159
|
case "move-cards": {
|
|
9442
|
-
if (!a1 || !v.from || !v.to) throw usage("Missing workspace, --from or --to.", '
|
|
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.");
|
|
10160
|
+
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');
|
|
10161
|
+
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
10162
|
const path = `/api/v2/workspaces/${enc8(a1)}/boards/move-cards`;
|
|
9445
10163
|
const body = { from: v.from, to: v.to, ...v.integration ? { integration: v.integration } : {}, ...v.phone ? { phone: v.phone } : {}, ...v.all ? { all: true } : {} };
|
|
9446
10164
|
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 +10166,272 @@ async function boardsCmd(sub, a1, a2, v, context) {
|
|
|
9448
10166
|
const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
|
|
9449
10167
|
const sample = table(plan.sample.map((c) => ({ folio: c.folio ? `#${c.folio}` : "", name: c.name ?? "", phone: c.phone })), ["folio", "name", "phone"]);
|
|
9450
10168
|
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`) : ""}`;
|
|
10169
|
+
${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
10170
|
if (v["dry-run"] || !plan.moved) return out(plan, () => `${text}
|
|
9453
|
-
${dim("Dry run: nothing changed.")}`);
|
|
10171
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
9454
10172
|
await confirm(`${text}
|
|
9455
|
-
Move them with their threads and orders? (they get a new folio there)`, { yes: v.yes, retry });
|
|
10173
|
+
${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
10174
|
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
|
|
10175
|
+
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
10176
|
}
|
|
9459
10177
|
default:
|
|
9460
|
-
throw usage(`Unknown boards subcommand: ${sub}.`, "Try: ghosty boards ls | create | assign | move-cards | rm");
|
|
10178
|
+
throw usage(t(`Unknown boards subcommand: ${sub}.`, `Subcomando de boards desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty boards ls | create | assign | move-cards | rm");
|
|
9461
10179
|
}
|
|
9462
10180
|
}
|
|
9463
10181
|
|
|
10182
|
+
// src/help.es.ts
|
|
10183
|
+
var HELPS_ES = {
|
|
10184
|
+
agents: {
|
|
10185
|
+
summary: "Crea, revisa, cambia, diagnostica y borra agentes.",
|
|
10186
|
+
replace: [
|
|
10187
|
+
["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
|
|
10188
|
+
["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"]
|
|
10189
|
+
],
|
|
10190
|
+
flags: [
|
|
10191
|
+
["--fields a,b", "get: regresa s\xF3lo estos campos"],
|
|
10192
|
+
["--prompt-out FILE", "get: escribe el prompt en FILE (ed\xEDtalo y vuelve con set --prompt-file)"],
|
|
10193
|
+
["--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"],
|
|
10194
|
+
["--copy-dbs", "create --like: duplica sus bases de datos con sus filas en vez de conceder las mismas"],
|
|
10195
|
+
["--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"],
|
|
10196
|
+
["--no-notify", "create --like: lo comparte con las mismas personas SIN mandarles correo"],
|
|
10197
|
+
["--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"],
|
|
10198
|
+
["--channels k=on|off,\u2026", "create/set: prende/apaga canales: teams, web, whatsapp, messenger, wa (WhatsApp personal)"],
|
|
10199
|
+
["--toolsets LIST", "set: grupos de tools de gs que recibe (crm, sales\u2026): una lista reemplaza; +x / -x agrega o quita"],
|
|
10200
|
+
["--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)"],
|
|
10201
|
+
["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, aplicados en orden; cada uno debe encontrar su lugar o no cambia nada'],
|
|
10202
|
+
["--voice E:ID", "set: voz de las notas de voz: elevenlabs:<voiceId> o kokoro:em_santa|em_alex|ef_dora"],
|
|
10203
|
+
["--voice-replies M", "set: auto = contesta con nota de voz cuando el cliente manda o pide audio; off = siempre texto"],
|
|
10204
|
+
["--auto-stage on|off", "set: mueve solo las tarjetas del tablero tras cada turno del cliente (una llamada extra al modelo por turno)"],
|
|
10205
|
+
["--verbose", "doctor: lista tambi\xE9n las revisiones que pasaron"],
|
|
10206
|
+
["--write", "docs grant: el agente puede reemplazar el documento (default: s\xF3lo lectura)"],
|
|
10207
|
+
["--no-deliver", "docs grant/add: puede leerlo pero nunca mand\xE1rselo a quien escribe"],
|
|
10208
|
+
["--url URL | --file PATH", "docs add: de d\xF3nde sale el documento (URL p\xFAblica, hasta 25 MB, o un archivo local)"],
|
|
10209
|
+
["--grant read|write|no", "docs add: acceso de este agente (default read; no = s\xF3lo subirlo a la cuenta)"],
|
|
10210
|
+
["--role R", "share: editor (lo configura y lo usa) o admin (adem\xE1s lo comparte); repetirlo sobre un correo ya compartido cambia el rol"],
|
|
10211
|
+
["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
|
|
10212
|
+
["--slug NAME", "demo: nombre de la liga; se le agrega un c\xF3digo de 6 caracteres (/p/NAME-CODE)"],
|
|
10213
|
+
["--expires DATE|no", 'demo: caducidad fija en la hora de tu m\xE1quina ("2026-10-15T20:00"); no = nunca'],
|
|
10214
|
+
["--turns N", "demo: mensajes por visitante (default 150)"],
|
|
10215
|
+
["--per-ip N", "demo: mensajes por IP al d\xEDa (default 30)"],
|
|
10216
|
+
["--welcome TEXT", "demo: saludo del chat (vac\xEDo = el de siempre)"],
|
|
10217
|
+
["--starter TEXT", 'demo: primera sugerencia destacada ("" la quita)'],
|
|
10218
|
+
['--chips "a|b|c"', "demo: sugerencias, separadas por |"],
|
|
10219
|
+
["--sponsor on|off", "demo: las copias que la gente se queda corren con TU llave del motor"],
|
|
10220
|
+
["--rotate", "demo: c\xF3digo nuevo; la liga vieja deja de servir"],
|
|
10221
|
+
["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
|
|
10222
|
+
["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
|
|
10223
|
+
["--recycle", "box: reemplaza sus m\xE1quinas (no a media respuesta); la nueva arranca en el siguiente turno. ACP: usa restart"]
|
|
10224
|
+
],
|
|
10225
|
+
notes: [
|
|
10226
|
+
"doctor sale con 1 cuando encuentra un problema, as\xED puede frenar un script o un CI.",
|
|
10227
|
+
"Un prompt derivado no se edita directo (409): cambia la base o el overlay, o desenl\xE1zalo con --prompt-base none.",
|
|
10228
|
+
"demo sin flags s\xF3lo imprime su estado: liga, caducidad, topes y n\xFAmeros."
|
|
10229
|
+
]
|
|
10230
|
+
},
|
|
10231
|
+
skills: {
|
|
10232
|
+
summary: "Skills de un agente (formato agentskills.io: una carpeta con SKILL.md).",
|
|
10233
|
+
replace: [
|
|
10234
|
+
["# from the community catalog", "# del cat\xE1logo de la comunidad"],
|
|
10235
|
+
["# keeps a local copy first", "# antes guarda una copia local"]
|
|
10236
|
+
],
|
|
10237
|
+
flags: [
|
|
10238
|
+
["--out DIR", "get: carpeta donde escribir; sin ella, get s\xF3lo lista los archivos"],
|
|
10239
|
+
["--only a,b", "house: deja prendidas S\xD3LO estas skills de Ghosty (las dem\xE1s apagadas)"],
|
|
10240
|
+
["--enable / --disable a,b", "house: prende / apaga estas skills de Ghosty"],
|
|
10241
|
+
["--file FILE", "add: sube un SKILL.md"],
|
|
10242
|
+
["--dir FOLDER", "add: sube la carpeta entera de una skill (SKILL.md + archivos); restaura un respaldo"],
|
|
10243
|
+
["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
10244
|
+
],
|
|
10245
|
+
notes: [
|
|
10246
|
+
"rm guarda una copia en ~/.config/ghosty/trash/ e imprime el comando que la restaura.",
|
|
10247
|
+
"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."
|
|
10248
|
+
]
|
|
10249
|
+
},
|
|
10250
|
+
dbs: {
|
|
10251
|
+
summary: "Bases de datos que un agente puede usar, y qu\xE9 puede escribir desde canales de clientes.",
|
|
10252
|
+
replace: [["# reads $EASYBITS_API_KEY", "# lee $EASYBITS_API_KEY"]],
|
|
10253
|
+
flags: [
|
|
10254
|
+
["--arg V", "query: valor para un marcador ? (repetible)"],
|
|
10255
|
+
["--write", "query: permite INSERT/UPDATE/DELETE/DDL (default: s\xF3lo lectura)"],
|
|
10256
|
+
["--external-write t1,t2", 'grant: tablas que puede escribir desde WhatsApp/Messenger ("" = ninguna)'],
|
|
10257
|
+
["--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)'],
|
|
10258
|
+
["--from easybits:ID", "import: base de origen en EasyBits (llave de $EASYBITS_API_KEY)"],
|
|
10259
|
+
["--tables a,b", "import: s\xF3lo estas tablas (default: todas)"],
|
|
10260
|
+
["--append", "import: agrega filas en vez de reemplazarlas (reemplazar es el default idempotente)"],
|
|
10261
|
+
["--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)"],
|
|
10262
|
+
["--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"],
|
|
10263
|
+
["--key-column K --dir DIR", "photos put: cada archivo de DIR se llama como la clave de su fila (SKU-123.jpg \u2192 la fila donde K = SKU-123); va al almacenamiento permanente y su liga a --column (default imagen_url)"],
|
|
10264
|
+
["-n, --dry-run", "import: imprime el plan (+ crear, ~ reemplazar N \u2192 M filas), no cambia nada \xB7 photos put: lista las claves sin fila y los archivos que no son imagen"]
|
|
10265
|
+
],
|
|
10266
|
+
notes: [
|
|
10267
|
+
"<db> es el id de la base o su nombre. Desde canales del due\xF1o el agente lee y escribe todo lo concedido.",
|
|
10268
|
+
"tables dice, por columna de fotos/ligas, cu\xE1ntas son permanentes (img.ghosty.studio), de fuera o vac\xEDas; import avisa antes de reemplazar ligas permanentes por ligas de fuera."
|
|
10269
|
+
]
|
|
10270
|
+
},
|
|
10271
|
+
turns: {
|
|
10272
|
+
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.",
|
|
10273
|
+
replace: [["# last turn: each tool, its seconds, output tokens", "# \xFAltimo turno: cada tool, sus segundos, tokens de salida"]],
|
|
10274
|
+
flags: [
|
|
10275
|
+
["--errors", "s\xF3lo turnos fallidos (igual que --status error)"],
|
|
10276
|
+
["--status S", "running | done | stopped | error"],
|
|
10277
|
+
["--since T", "30m, 1h, 2d o una fecha ISO"],
|
|
10278
|
+
["--limit N", "m\xE1ximo de filas (default 20, m\xE1x. 500)"],
|
|
10279
|
+
["--full", "show: el mensaje que entr\xF3 y la respuesta completos (default: primeros 600 caracteres)"]
|
|
10280
|
+
],
|
|
10281
|
+
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)."]
|
|
10282
|
+
},
|
|
10283
|
+
files: { summary: "Archivos en la m\xE1quina del agente." },
|
|
10284
|
+
mcp: { summary: "Servidores MCP de un agente. set REEMPLAZA la lista entera." },
|
|
10285
|
+
try: {
|
|
10286
|
+
summary: "Turnos de prueba: sencillos, por el camino real de WhatsApp (no se manda nada), o repetici\xF3n de conversaciones reales.",
|
|
10287
|
+
replace: [['"message"', '"mensaje"']],
|
|
10288
|
+
flags: [
|
|
10289
|
+
["--waba", "el camino real de WhatsApp Business: contexto heredado, regla de precios, tablero, voz. Muestra lo que MANDAR\xCDA"],
|
|
10290
|
+
["--session PHONE", "con --waba: un tel\xE9fono de cliente inventado (10-15 d\xEDgitos); cada tel\xE9fono es su propia conversaci\xF3n"],
|
|
10291
|
+
["--media FILE", "con --waba: el cliente manda esta foto/audio/PDF (uno por turno)"],
|
|
10292
|
+
["--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"],
|
|
10293
|
+
["--history FILE", "con --waba: lo que se dijo antes, como [{role: user|agent, text, at?}]; cuenta en el primer turno (--reset)"],
|
|
10294
|
+
["--reset", "olvida esa sesi\xF3n de prueba (con --waba tambi\xE9n su hilo de prueba)"],
|
|
10295
|
+
["--replay FILE", "corre las conversaciones de FILE ({sample: [{id, fakePhone, messages}]}) contra el agente"],
|
|
10296
|
+
["--out DIR", "replay: d\xF3nde se escribe replay.json, tras cada paso"],
|
|
10297
|
+
["--max N", "replay: turnos del cliente por conversaci\xF3n (default 15)"],
|
|
10298
|
+
["--concurrency N", "replay: conversaciones al mismo tiempo (default 8, m\xE1x. 30)"],
|
|
10299
|
+
["--resume", "replay: contin\xFAa desde DIR/replay.json, salt\xE1ndose los pasos terminados"]
|
|
10300
|
+
],
|
|
10301
|
+
notes: [
|
|
10302
|
+
"Las repeticiones sobreviven a los deploys de Ghosty: un 503 se reintenta tras su Retry-After; el reporte vive en tu disco.",
|
|
10303
|
+
"--whatsapp ahora es alias de --waba.",
|
|
10304
|
+
"--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."
|
|
10305
|
+
]
|
|
10306
|
+
},
|
|
10307
|
+
chat: {
|
|
10308
|
+
summary: "Conversa en streaming; sin mensaje es interactivo.",
|
|
10309
|
+
replace: [['["message"]', '["mensaje"]']]
|
|
10310
|
+
},
|
|
10311
|
+
schedule: {
|
|
10312
|
+
summary: "Turnos que corren despu\xE9s, con el reloj propio del agente.",
|
|
10313
|
+
replace: [['"what to do"', '"qu\xE9 hacer"']]
|
|
10314
|
+
},
|
|
10315
|
+
me: { summary: "Tu cuenta: archivos y uso." },
|
|
10316
|
+
credentials: {
|
|
10317
|
+
summary: "Llaves de proveedores en la b\xF3veda de tu cuenta (cifradas; s\xF3lo regresan los \xFAltimos 4 caracteres).",
|
|
10318
|
+
replace: [["# hidden prompt", "# pregunta oculta"]],
|
|
10319
|
+
flags: [
|
|
10320
|
+
["--from-env VAR", "set: lee la llave de esta variable de entorno (pasa su NOMBRE)"],
|
|
10321
|
+
["--yes", "rm: se salta la confirmaci\xF3n"]
|
|
10322
|
+
],
|
|
10323
|
+
notes: ["La llave nunca se acepta como argumento: se quedar\xEDa en el historial de tu shell y en `ps`."]
|
|
10324
|
+
},
|
|
10325
|
+
channels: {
|
|
10326
|
+
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.",
|
|
10327
|
+
replace: [['"Name"', '"Nombre"']],
|
|
10328
|
+
flags: [
|
|
10329
|
+
["-n, --dry-run", "imprime el plan (+ empieza a contestar, - deja de contestar) y no cambia nada"],
|
|
10330
|
+
["--yes", "enable / groups enable: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
|
|
10331
|
+
["--role R", "groups: equipo (el equipo: escribe en bases de datos, tools de admin) o cliente (como cualquier cliente)"],
|
|
10332
|
+
["--cached", "groups ls: no le pide a WhatsApp la lista en vivo (m\xE1s r\xE1pido; puede faltar alg\xFAn grupo nuevo)"],
|
|
10333
|
+
["--integration ID", "link/unlink/history: el id de la Integration del n\xFAmero de WhatsApp Business en Formmy"],
|
|
10334
|
+
["--days N", "history: conversaciones activas en los \xFAltimos N d\xEDas (1-30, default 7)"],
|
|
10335
|
+
["--answer on|off", "link (obligatorio, sin default): on = este agente contesta a clientes reales; off = s\xF3lo registra"],
|
|
10336
|
+
["--forward", "link --answer off: igual hace que Formmy reenv\xEDe, para que gs vea las conversaciones"],
|
|
10337
|
+
["--from-file F", "import: {authCreds, authKeys, phone, groups} exportado de EasyBits"],
|
|
10338
|
+
["--from-dir DIR", "import: una carpeta de auth multi-archivo de Baileys (creds.json + un archivo por llave; p. ej. store/auth de nanoclaw)"],
|
|
10339
|
+
["--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)"],
|
|
10340
|
+
["--replace", "import: reemplaza una sesi\xF3n ya guardada en gs"],
|
|
10341
|
+
["--no-connect", "import: la guarda pero todav\xEDa no conecta"]
|
|
10342
|
+
],
|
|
10343
|
+
notes: [
|
|
10344
|
+
"<number> es su id, su etiqueta o sus \xFAltimos d\xEDgitos. Un n\xFAmero, un agente: prenderlo se lo quita al otro agente.",
|
|
10345
|
+
"Apagarlo sigue registrando los mensajes; nadie los contesta.",
|
|
10346
|
+
"Los grupos son del WhatsApp PERSONAL emparejado al agente (no de WhatsApp Business). <group> es su nombre o su jid.",
|
|
10347
|
+
"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.",
|
|
10348
|
+
"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.",
|
|
10349
|
+
"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."
|
|
10350
|
+
]
|
|
10351
|
+
},
|
|
10352
|
+
login: {
|
|
10353
|
+
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.",
|
|
10354
|
+
replace: [["# no login: an agent token", "# sin login: un token de agente"]],
|
|
10355
|
+
flags: [
|
|
10356
|
+
["--device", "inicia sesi\xF3n desde otro dispositivo con liga + c\xF3digo (autom\xE1tico por SSH o en CI)"],
|
|
10357
|
+
["--profile NAME", "una sesi\xF3n guardada aparte, p. ej. una por cuenta"]
|
|
10358
|
+
]
|
|
10359
|
+
},
|
|
10360
|
+
logout: { summary: "Cierra sesi\xF3n y revoca en el servidor la sesi\xF3n de este dispositivo." },
|
|
10361
|
+
whoami: { summary: "Con qui\xE9n iniciaste sesi\xF3n (con --json, tambi\xE9n el plan de tu cuenta)." },
|
|
10362
|
+
docs: { summary: "Abre la documentaci\xF3n en tu navegador (con --json, s\xF3lo imprime la URL)." },
|
|
10363
|
+
apply: {
|
|
10364
|
+
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.",
|
|
10365
|
+
replace: [
|
|
10366
|
+
["# or --out DIR: agent.yaml + skills/<slug>/\u2026", "# o --out DIR: agent.yaml + skills/<slug>/\u2026"],
|
|
10367
|
+
["# a new agent from the file", "# un agente nuevo a partir del archivo"]
|
|
10368
|
+
],
|
|
10369
|
+
flags: [
|
|
10370
|
+
["--agent X", "aplica a este agente en vez del id del archivo (p. ej. el mismo archivo sobre una copia)"],
|
|
10371
|
+
["--create", "crea un agente nuevo con el archivo (su espacio, motor, canales) y aplica lo dem\xE1s"],
|
|
10372
|
+
["--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 !"],
|
|
10373
|
+
["-n, --dry-run", "imprime el plan, no cambia nada"],
|
|
10374
|
+
["--no-notify", "compartidos nuevos sin mandar correo a la gente"]
|
|
10375
|
+
],
|
|
10376
|
+
notes: [
|
|
10377
|
+
"Lo que el archivo no menciona no se toca; los campos que trae quedan exactamente como dice.",
|
|
10378
|
+
"Los secretos nunca salen: se exportan como ${NAME}; define NAME en tu entorno para cambiarlo, si no, se queda el valor actual.",
|
|
10379
|
+
"Antes de aplicar se guarda la versi\xF3n actual del agente (Versiones en la web)."
|
|
10380
|
+
]
|
|
10381
|
+
},
|
|
10382
|
+
completion: {
|
|
10383
|
+
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).",
|
|
10384
|
+
replace: [
|
|
10385
|
+
["# add to ~/.zshrc, after compinit", "# agr\xE9galo a ~/.zshrc, despu\xE9s de compinit"],
|
|
10386
|
+
["# add to ~/.bashrc", "# agr\xE9galo a ~/.bashrc"]
|
|
10387
|
+
],
|
|
10388
|
+
notes: ["Los nombres salen de ghosty agents ls; un agente nuevo aparece en m\xE1ximo 5 minutos (o borra ~/.cache/ghosty/complete.json)."]
|
|
10389
|
+
},
|
|
10390
|
+
use: {
|
|
10391
|
+
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.",
|
|
10392
|
+
replace: [["# which one", "# cu\xE1l es"]],
|
|
10393
|
+
flags: [["--clear", "sin espacio por default"]],
|
|
10394
|
+
notes: [
|
|
10395
|
+
"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.",
|
|
10396
|
+
"agents create --workspace none crea un agente personal aunque haya espacio por default."
|
|
10397
|
+
]
|
|
10398
|
+
},
|
|
10399
|
+
boards: {
|
|
10400
|
+
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.",
|
|
10401
|
+
replace: [['"Name"', '"Nombre"']],
|
|
10402
|
+
flags: [
|
|
10403
|
+
["--columns-from B", "create: copia las columnas de ese tablero (main, un nombre o un id) en vez de las de siempre"],
|
|
10404
|
+
["--from / --to B", "move-cards: tableros por nombre, id o main"],
|
|
10405
|
+
["--integration ID | --phone N | --all", "move-cards: qu\xE9 tarjetas (el filtro es obligatorio)"],
|
|
10406
|
+
["-n, --dry-run", "move-cards: cuenta y muestra una muestra, no cambia nada"],
|
|
10407
|
+
["--yes", "move-cards: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
10408
|
+
],
|
|
10409
|
+
notes: [
|
|
10410
|
+
"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.",
|
|
10411
|
+
"Leer requiere ser miembro; cambiar requiere ser due\xF1o o staff del espacio.",
|
|
10412
|
+
"rm s\xF3lo borra un tablero vac\xEDo que no sea el principal."
|
|
10413
|
+
]
|
|
10414
|
+
},
|
|
10415
|
+
cards: {
|
|
10416
|
+
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.",
|
|
10417
|
+
flags: [
|
|
10418
|
+
["--q TEXT", "ls: tarjetas cuyo nombre, tel\xE9fono o \xFAltimo mensaje contiene TEXT"],
|
|
10419
|
+
["--limit N", "ls: tarjetas (default 20) \xB7 show: \xFAltimos N mensajes (default 20, m\xE1x. 500)"],
|
|
10420
|
+
["--integration ID", "tarjetas de este canal/integraci\xF3n (p. ej. prueba)"],
|
|
10421
|
+
["--column NAME", "tarjetas en esta columna"],
|
|
10422
|
+
["--before T", "tarjetas sin actividad desde T (30d, 12h o una fecha ISO)"],
|
|
10423
|
+
["-n, --dry-run", "cuenta y muestra una muestra, no cambia nada"],
|
|
10424
|
+
["--yes", "se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
10425
|
+
],
|
|
10426
|
+
notes: [
|
|
10427
|
+
"show imprime cada mensaje con qui\xE9n lo escribi\xF3 (cliente, agente, operador), su adjunto y si gs guarda una copia.",
|
|
10428
|
+
"Archivar es reversible: cuando el cliente vuelve a escribir, la tarjeta regresa. Se necesita al menos un filtro."
|
|
10429
|
+
]
|
|
10430
|
+
},
|
|
10431
|
+
usage: { summary: "Tu plan y cu\xE1nto llevas usado esta semana y este mes." }
|
|
10432
|
+
};
|
|
10433
|
+
var HELP_ALIASES = { creds: "credentials", board: "cards", conversations: "chat" };
|
|
10434
|
+
|
|
9464
10435
|
// src/help.ts
|
|
9465
10436
|
var DOCS = "https://www.ghosty.studio/docs/cli";
|
|
9466
10437
|
var REF = `${DOCS}/comandos`;
|
|
@@ -9582,6 +10553,7 @@ var HELPS = {
|
|
|
9582
10553
|
'ghosty dbs query <agent-id> <db> "SQL" [--arg V]\u2026 [--write]',
|
|
9583
10554
|
"ghosty dbs import <agent-id> <db> --from easybits:<db-id> [--tables a,b] [--append] [--rehost-images t.col,\u2026] [--dry-run] [--yes]",
|
|
9584
10555
|
"ghosty dbs rehost <agent-id> <db> --table T --column C [--dry-run]",
|
|
10556
|
+
"ghosty dbs photos put <agent-id> <db> --table T --key-column K --dir DIR [--column C] [--dry-run]",
|
|
9585
10557
|
"ghosty dbs grant <agent-id> <db> [--external-write t1,t2] [--external-rows t:col+col,\u2026]",
|
|
9586
10558
|
"ghosty dbs revoke <agent-id> <db>"
|
|
9587
10559
|
],
|
|
@@ -9595,7 +10567,8 @@ var HELPS = {
|
|
|
9595
10567
|
["--append", "import: add rows instead of replacing them (replace is the idempotent default)"],
|
|
9596
10568
|
["--rehost-images t.col,\u2026", "import: copy the image links in those columns to Ghosty's public storage (permanent links at img.ghosty.studio)"],
|
|
9597
10569
|
["--table T --column C", "rehost: the column of image links (e.g. catalogo imagen_url) to move to permanent links at img.ghosty.studio; idempotent, a link that fails stays as it was"],
|
|
9598
|
-
["-
|
|
10570
|
+
["--key-column K --dir DIR", "photos put: each file in DIR is named after its row's key (SKU-123.jpg \u2192 the row where K = SKU-123); it goes to permanent storage and its link to --column (default imagen_url)"],
|
|
10571
|
+
["-n, --dry-run", "import: print the plan (+ create, ~ replace N \u2192 M rows), change nothing \xB7 photos put: list keys with no row and files that aren't images"]
|
|
9599
10572
|
],
|
|
9600
10573
|
examples: [
|
|
9601
10574
|
"ghosty dbs ls <id>",
|
|
@@ -9603,9 +10576,13 @@ var HELPS = {
|
|
|
9603
10576
|
'ghosty dbs query <id> totequim "SELECT count(*) AS n FROM productos"',
|
|
9604
10577
|
"ghosty dbs grant <id> totequim --external-write cotizaciones,folio",
|
|
9605
10578
|
"ghosty dbs grant <id> totequim --external-rows clientes:telefono+tel_secundario,cotizaciones:cliente_tel",
|
|
9606
|
-
"ghosty dbs import <id> totequim --from easybits:6a10c84c\u2026 --dry-run # reads $EASYBITS_API_KEY"
|
|
10579
|
+
"ghosty dbs import <id> totequim --from easybits:6a10c84c\u2026 --dry-run # reads $EASYBITS_API_KEY",
|
|
10580
|
+
"ghosty dbs photos put <id> siiqtec --table catalogo --key-column sku --dir fotos/ --dry-run"
|
|
10581
|
+
],
|
|
10582
|
+
notes: [
|
|
10583
|
+
"<db> is the database id or its name. From owner channels the agent reads and writes everything granted.",
|
|
10584
|
+
"tables shows, per photo/link column, how many links are permanent (img.ghosty.studio), external or empty; import warns before replacing permanent links with external ones."
|
|
9607
10585
|
],
|
|
9608
|
-
notes: ["<db> is the database id or its name. From owner channels the agent reads and writes everything granted."],
|
|
9609
10586
|
learn: `${REF}#bases-de-datos`
|
|
9610
10587
|
},
|
|
9611
10588
|
turns: {
|
|
@@ -9786,6 +10763,17 @@ HELPS.apply = {
|
|
|
9786
10763
|
],
|
|
9787
10764
|
learn: `${REF}#el-agente-como-archivo`
|
|
9788
10765
|
};
|
|
10766
|
+
HELPS.completion = {
|
|
10767
|
+
summary: "Tab completion for your shell: commands, subcommands and flags (from this help), agent and workspace names (from your account, cached 5 min).",
|
|
10768
|
+
usage: ["ghosty completion zsh|bash|fish"],
|
|
10769
|
+
examples: [
|
|
10770
|
+
"source <(ghosty completion zsh) # add to ~/.zshrc, after compinit",
|
|
10771
|
+
'eval "$(ghosty completion bash)" # add to ~/.bashrc',
|
|
10772
|
+
"ghosty completion fish > ~/.config/fish/completions/ghosty.fish"
|
|
10773
|
+
],
|
|
10774
|
+
notes: ["Names come from ghosty agents ls; a new agent shows up within 5 minutes (or delete ~/.cache/ghosty/complete.json)."],
|
|
10775
|
+
learn: `${REF}#autocompletado`
|
|
10776
|
+
};
|
|
9789
10777
|
HELPS.use = {
|
|
9790
10778
|
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
10779
|
usage: ["ghosty use # which one", "ghosty use <workspace>", "ghosty use --clear"],
|
|
@@ -9838,19 +10826,23 @@ HELPS.board = HELPS.cards;
|
|
|
9838
10826
|
HELPS.conversations = HELPS.chat;
|
|
9839
10827
|
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
10828
|
function helpText(cmd) {
|
|
9841
|
-
const
|
|
9842
|
-
if (!
|
|
9843
|
-
const
|
|
10829
|
+
const base = HELPS[cmd];
|
|
10830
|
+
if (!base) return void 0;
|
|
10831
|
+
const es = lang() === "es";
|
|
10832
|
+
const tr = es ? HELPS_ES[HELP_ALIASES[cmd] ?? cmd] : void 0;
|
|
10833
|
+
const fix = (l) => (tr?.replace ?? []).reduce((acc, [a, b]) => acc.split(a).join(b), l);
|
|
10834
|
+
const h = { ...base, ...tr ? { summary: tr.summary ?? base.summary, flags: tr.flags ?? base.flags, notes: tr.notes ?? base.notes } : {} };
|
|
10835
|
+
const section = (t2, lines) => [bold(t2), ...lines.map((l) => ` ${l}`), ""];
|
|
9844
10836
|
const w = Math.max(0, ...(h.flags ?? []).map(([f]) => f.length)) + 2;
|
|
9845
10837
|
return [
|
|
9846
10838
|
h.summary,
|
|
9847
10839
|
"",
|
|
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.`,
|
|
10840
|
+
...section(es ? "USO" : "USAGE", h.usage.map(fix)),
|
|
10841
|
+
...h.flags?.length ? section(es ? "OPCIONES" : "FLAGS", h.flags.map(([f, d]) => `${f.padEnd(w)}${d}`)) : [],
|
|
10842
|
+
...section(es ? "EJEMPLOS" : "EXAMPLES", h.examples.map((e) => `$ ${fix(e)}`)),
|
|
10843
|
+
...h.notes?.length ? section(es ? "NOTAS" : "NOTES", h.notes) : [],
|
|
10844
|
+
bold(es ? "M\xC1S INFORMACI\xD3N" : "LEARN MORE"),
|
|
10845
|
+
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
10846
|
` ${dim(h.learn ?? DOCS)}`
|
|
9855
10847
|
].join("\n");
|
|
9856
10848
|
}
|
|
@@ -9858,8 +10850,8 @@ function helpText(cmd) {
|
|
|
9858
10850
|
// src/update.ts
|
|
9859
10851
|
import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile4 } from "node:fs/promises";
|
|
9860
10852
|
import { homedir as homedir3 } from "node:os";
|
|
9861
|
-
import { dirname as dirname3, join as
|
|
9862
|
-
var FILE =
|
|
10853
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
10854
|
+
var FILE = join6(process.env.XDG_CONFIG_HOME || join6(homedir3(), ".config"), "ghosty", "update.json");
|
|
9863
10855
|
var DAY = 24 * 60 * 60 * 1e3;
|
|
9864
10856
|
var UPGRADE = "npm i -g @ghostystudio/cli@latest";
|
|
9865
10857
|
function newer(a, b) {
|
|
@@ -9887,9 +10879,9 @@ async function latestVersion() {
|
|
|
9887
10879
|
}
|
|
9888
10880
|
return cache2.latest ?? null;
|
|
9889
10881
|
}
|
|
9890
|
-
async function updateNotice(
|
|
10882
|
+
async function updateNotice(current2) {
|
|
9891
10883
|
const latest = await latestVersion();
|
|
9892
|
-
return latest && newer(latest,
|
|
10884
|
+
return latest && newer(latest, current2) ? t(`Update available: ${current2} \u2192 ${latest}. Run: ${UPGRADE}`, `Hay versi\xF3n nueva: ${current2} \u2192 ${latest}. Corre: ${UPGRADE}`) : null;
|
|
9893
10885
|
}
|
|
9894
10886
|
|
|
9895
10887
|
// src/resolve.ts
|
|
@@ -9904,11 +10896,14 @@ function pickAgent(list, ref, workspace) {
|
|
|
9904
10896
|
if (hits.length === 1) return hits[0].id;
|
|
9905
10897
|
if (!hits.length) {
|
|
9906
10898
|
const near = list.filter((a) => a.nombre.toLowerCase().includes(want)).slice(0, 5);
|
|
9907
|
-
|
|
10899
|
+
const names2 = near.map((a) => `${a.nombre} (${a.id})`).join(", ");
|
|
10900
|
+
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
10901
|
}
|
|
9909
10902
|
const where = (a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "?";
|
|
9910
|
-
|
|
9911
|
-
|
|
10903
|
+
const ids = hits.map((a) => ` ${a.id} ${a.motor ?? ""} ${where(a)}`).join("\n");
|
|
10904
|
+
throw new CliError(t(`${hits.length} agents are named "${ref}".`, `${hits.length} agentes se llaman "${ref}".`), EXIT.USAGE, t(`Use the id:
|
|
10905
|
+
${ids}`, `Usa el id:
|
|
10906
|
+
${ids}`));
|
|
9912
10907
|
}
|
|
9913
10908
|
var cache = null;
|
|
9914
10909
|
function listAgents() {
|
|
@@ -9919,57 +10914,186 @@ async function resolveAgent(ref, workspace) {
|
|
|
9919
10914
|
if (!ref || looksLikeId(ref)) return ref;
|
|
9920
10915
|
return pickAgent(await listAgents(), ref, workspace);
|
|
9921
10916
|
}
|
|
10917
|
+
function agentArgIndex(pos) {
|
|
10918
|
+
const [cmd, sub] = pos;
|
|
10919
|
+
switch (cmd) {
|
|
10920
|
+
case "agents":
|
|
10921
|
+
if (sub === "docs" || sub === "documents") return 3;
|
|
10922
|
+
return sub && !["ls", "list", "create", "new"].includes(sub) ? 2 : null;
|
|
10923
|
+
case "files":
|
|
10924
|
+
case "skills":
|
|
10925
|
+
case "mcp":
|
|
10926
|
+
case "dbs":
|
|
10927
|
+
case "db":
|
|
10928
|
+
return sub === "photos" ? 3 : 2;
|
|
10929
|
+
case "turns":
|
|
10930
|
+
case "board":
|
|
10931
|
+
case "conversations":
|
|
10932
|
+
case "schedule":
|
|
10933
|
+
return 2;
|
|
10934
|
+
case "try":
|
|
10935
|
+
case "chat":
|
|
10936
|
+
return 1;
|
|
10937
|
+
case "channels":
|
|
10938
|
+
return pos[2] === "groups" ? 4 : 3;
|
|
10939
|
+
case "boards":
|
|
10940
|
+
return sub === "assign" ? 2 : null;
|
|
10941
|
+
default:
|
|
10942
|
+
return null;
|
|
10943
|
+
}
|
|
10944
|
+
}
|
|
10945
|
+
|
|
10946
|
+
// src/completion.ts
|
|
10947
|
+
import { mkdir as mkdir5, readFile as readFile10, writeFile as writeFile5 } from "node:fs/promises";
|
|
10948
|
+
import { homedir as homedir4 } from "node:os";
|
|
10949
|
+
import { join as join7 } from "node:path";
|
|
10950
|
+
var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
|
|
10951
|
+
var AGENT_FLAGS = /* @__PURE__ */ new Set(["--like", "--agent", "--prompt-base"]);
|
|
10952
|
+
var WORKSPACE_FLAGS = /* @__PURE__ */ new Set(["--workspace"]);
|
|
10953
|
+
function subcommandsOf(cmd) {
|
|
10954
|
+
const subs = /* @__PURE__ */ new Set();
|
|
10955
|
+
for (const u of HELPS[cmd]?.usage ?? []) {
|
|
10956
|
+
const m = new RegExp(`^ghosty ${cmd} ([a-z][a-z|-]*)`).exec(u);
|
|
10957
|
+
if (m) {
|
|
10958
|
+
for (const s of m[1].split("|")) if (!s.includes("-") || /^[a-z]+-[a-z]+$/.test(s)) subs.add(s);
|
|
10959
|
+
}
|
|
10960
|
+
}
|
|
10961
|
+
return [...subs];
|
|
10962
|
+
}
|
|
10963
|
+
function flagsOf(cmd) {
|
|
10964
|
+
const own = /* @__PURE__ */ new Set();
|
|
10965
|
+
for (const [f] of HELPS[cmd]?.flags ?? []) for (const m of f.matchAll(/--[a-z][a-z-]*/g)) own.add(m[0]);
|
|
10966
|
+
for (const u of HELPS[cmd]?.usage ?? []) for (const m of u.matchAll(/--[a-z][a-z-]*/g)) own.add(m[0]);
|
|
10967
|
+
return [...own, ...GLOBAL_FLAGS];
|
|
10968
|
+
}
|
|
10969
|
+
function completionFor(words, current2, commands) {
|
|
10970
|
+
const prev = words[words.length - 1];
|
|
10971
|
+
if (prev && AGENT_FLAGS.has(prev)) return { kind: "agents" };
|
|
10972
|
+
if (prev && WORKSPACE_FLAGS.has(prev)) return { kind: "workspaces" };
|
|
10973
|
+
if (!words.length) return { kind: "words", words: commands };
|
|
10974
|
+
const [cmd, sub] = words;
|
|
10975
|
+
if (current2.startsWith("-")) return { kind: "words", words: flagsOf(cmd) };
|
|
10976
|
+
const pos = words.filter((w) => !w.startsWith("-"));
|
|
10977
|
+
if (cmd === "use" && pos.length === 1) return { kind: "workspaces" };
|
|
10978
|
+
if (cmd === "boards" && pos.length === 2 && sub !== "assign") return { kind: "workspaces" };
|
|
10979
|
+
if (cmd === "help" && pos.length === 1) return { kind: "words", words: commands };
|
|
10980
|
+
if (pos.length === 1 && cmd !== "try" && cmd !== "chat") {
|
|
10981
|
+
const subs = subcommandsOf(cmd);
|
|
10982
|
+
if (subs.length) return { kind: "words", words: subs };
|
|
10983
|
+
}
|
|
10984
|
+
if (agentArgIndex([...pos, current2]) === pos.length) return { kind: "agents" };
|
|
10985
|
+
return { kind: "words", words: [] };
|
|
10986
|
+
}
|
|
10987
|
+
var cacheFile = () => join7(process.env.XDG_CACHE_HOME || join7(homedir4(), ".cache"), "ghosty", `complete${process.env.GHOSTY_PROFILE ? `.${process.env.GHOSTY_PROFILE}` : ""}.json`);
|
|
10988
|
+
async function names() {
|
|
10989
|
+
try {
|
|
10990
|
+
const c = JSON.parse(await readFile10(cacheFile(), "utf8"));
|
|
10991
|
+
if (Date.now() - c.at < 5 * 6e4) return c;
|
|
10992
|
+
} catch {
|
|
10993
|
+
}
|
|
10994
|
+
const list = await listAgents();
|
|
10995
|
+
const agents = [...new Set(list.map((a) => a.nombre))];
|
|
10996
|
+
const workspaces = [...new Set(list.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
|
|
10997
|
+
await mkdir5(join7(cacheFile(), ".."), { recursive: true }).catch(() => {
|
|
10998
|
+
});
|
|
10999
|
+
await writeFile5(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
|
|
11000
|
+
});
|
|
11001
|
+
return { agents, workspaces };
|
|
11002
|
+
}
|
|
11003
|
+
async function completeCmd(args, commands) {
|
|
11004
|
+
const current2 = args.length ? args[args.length - 1] : "";
|
|
11005
|
+
const words = args.slice(0, -1);
|
|
11006
|
+
const want = completionFor(words, current2, commands);
|
|
11007
|
+
let out2 = [];
|
|
11008
|
+
try {
|
|
11009
|
+
if (want.kind === "words") out2 = want.words;
|
|
11010
|
+
else out2 = (await names())[want.kind];
|
|
11011
|
+
} catch {
|
|
11012
|
+
out2 = [];
|
|
11013
|
+
}
|
|
11014
|
+
process.stdout.write(out2.filter((w) => w.toLowerCase().startsWith(current2.toLowerCase())).join("\n") + "\n");
|
|
11015
|
+
}
|
|
11016
|
+
function completionScript(shell) {
|
|
11017
|
+
if (shell === "bash") {
|
|
11018
|
+
return `# ghosty \u2014 bash completion: eval "$(ghosty completion bash)" en ~/.bashrc
|
|
11019
|
+
_ghosty() {
|
|
11020
|
+
# Las palabras ANTES de cambiar IFS: el bash 3.2 de macOS junta \${arr[@]:1:n} con un IFS propio.
|
|
11021
|
+
local words=("\${COMP_WORDS[@]:1:COMP_CWORD-1}") cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
11022
|
+
local IFS=$'\\n'
|
|
11023
|
+
COMPREPLY=( $(ghosty __complete -- "\${words[@]}" "$cur" 2>/dev/null) )
|
|
11024
|
+
}
|
|
11025
|
+
complete -o default -F _ghosty ghosty
|
|
11026
|
+
`;
|
|
11027
|
+
}
|
|
11028
|
+
if (shell === "zsh") {
|
|
11029
|
+
return `#compdef ghosty
|
|
11030
|
+
# ghosty \u2014 zsh completion: source <(ghosty completion zsh) en ~/.zshrc (despu\xE9s de compinit)
|
|
11031
|
+
_ghosty() {
|
|
11032
|
+
local -a opts
|
|
11033
|
+
opts=("\${(@f)$(ghosty __complete -- "\${(@)words[2,CURRENT-1]}" "\${words[CURRENT]}" 2>/dev/null)}")
|
|
11034
|
+
compadd -a opts
|
|
11035
|
+
}
|
|
11036
|
+
compdef _ghosty ghosty
|
|
11037
|
+
`;
|
|
11038
|
+
}
|
|
11039
|
+
if (shell === "fish") {
|
|
11040
|
+
return `# ghosty \u2014 fish completion: ghosty completion fish > ~/.config/fish/completions/ghosty.fish
|
|
11041
|
+
complete -c ghosty -f -a "(ghosty __complete -- (commandline -opc)[2..-1] (commandline -ct) 2>/dev/null)"
|
|
11042
|
+
`;
|
|
11043
|
+
}
|
|
11044
|
+
return null;
|
|
11045
|
+
}
|
|
9922
11046
|
|
|
9923
11047
|
// src/aliases.ts
|
|
9924
11048
|
function normalizeArgs(pos, v) {
|
|
9925
11049
|
const notes = [];
|
|
9926
11050
|
const [cmd, sub] = pos;
|
|
9927
11051
|
if (cmd === "cards") pos[0] = "board";
|
|
9928
|
-
else if (cmd === "board") notes.push("`ghosty board` is now `ghosty cards` (same subcommands).");
|
|
11052
|
+
else if (cmd === "board") notes.push(t("`ghosty board` is now `ghosty cards` (same subcommands).", "`ghosty board` ahora es `ghosty cards` (mismos subcomandos)."));
|
|
9929
11053
|
if (cmd === "skills") {
|
|
9930
11054
|
if (sub === "enable") pos[1] = "on";
|
|
9931
11055
|
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"}\`.`);
|
|
11056
|
+
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
11057
|
}
|
|
9934
11058
|
if (cmd === "agents" && sub === "demo") {
|
|
9935
11059
|
if (pos[2] === "enable" || pos[2] === "disable") {
|
|
9936
11060
|
v[pos[2] === "enable" ? "on" : "off"] = true;
|
|
9937
11061
|
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>\`.`);
|
|
11062
|
+
} 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
11063
|
if (v.expires !== void 0) v.vence = v.expires;
|
|
9940
|
-
else if (v.vence !== void 0) notes.push("`--vence` is now `--expires`.");
|
|
11064
|
+
else if (v.vence !== void 0) notes.push(t("`--vence` is now `--expires`.", "`--vence` ahora es `--expires`."));
|
|
9941
11065
|
}
|
|
9942
11066
|
if (cmd === "channels" && pos[2] === "groups") {
|
|
9943
11067
|
if (pos[3] === "enable" || pos[3] === "disable") {
|
|
9944
11068
|
v[pos[3] === "enable" ? "on" : "off"] = true;
|
|
9945
11069
|
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).`);
|
|
11070
|
+
} 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
11071
|
}
|
|
9948
11072
|
return notes;
|
|
9949
11073
|
}
|
|
9950
11074
|
|
|
9951
11075
|
// src/commands/spec.ts
|
|
9952
11076
|
var import_yaml = __toESM(require_dist(), 1);
|
|
9953
|
-
import { mkdir as
|
|
9954
|
-
import { join as
|
|
11077
|
+
import { mkdir as mkdir6, readdir as readdir4, readFile as readFile11, stat, writeFile as writeFile6 } from "node:fs/promises";
|
|
11078
|
+
import { join as join8, resolve as resolve2 } from "node:path";
|
|
9955
11079
|
var enc9 = encodeURIComponent;
|
|
9956
11080
|
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");
|
|
11081
|
+
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
11082
|
const yaml = await (await request(`/api/v2/agents/${enc9(id)}/export`)).text();
|
|
9959
11083
|
if (!v.out) {
|
|
9960
11084
|
if (globals.json) return out((0, import_yaml.parse)(yaml));
|
|
9961
11085
|
return void process.stdout.write(yaml);
|
|
9962
11086
|
}
|
|
9963
11087
|
const dir = resolve2(v.out);
|
|
9964
|
-
await
|
|
9965
|
-
await
|
|
11088
|
+
await mkdir6(dir, { recursive: true });
|
|
11089
|
+
await writeFile6(join8(dir, "agent.yaml"), yaml);
|
|
9966
11090
|
const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
|
|
9967
11091
|
for (const slug of own) {
|
|
9968
11092
|
const sk = await api(`/api/v2/agents/${enc9(id)}/skills/${enc9(slug)}`);
|
|
9969
|
-
await writeSkill(
|
|
11093
|
+
await writeSkill(join8(dir, "skills", slug), sk);
|
|
9970
11094
|
}
|
|
9971
11095
|
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`)}`);
|
|
11096
|
+
${dim(t(`Edit it, then: ghosty apply ${v.out} --dry-run`, `Ed\xEDtalo y luego: ghosty apply ${v.out} --dry-run`))}`);
|
|
9973
11097
|
}
|
|
9974
11098
|
function fillSecrets(file) {
|
|
9975
11099
|
const missing = [];
|
|
@@ -9987,61 +11111,61 @@ function fillSecrets(file) {
|
|
|
9987
11111
|
return missing;
|
|
9988
11112
|
}
|
|
9989
11113
|
async function readSkills(dir) {
|
|
9990
|
-
const root =
|
|
11114
|
+
const root = join8(dir, "skills");
|
|
9991
11115
|
const out2 = {};
|
|
9992
|
-
const slugs = await
|
|
11116
|
+
const slugs = await readdir4(root, { withFileTypes: true }).catch(() => []);
|
|
9993
11117
|
for (const e of slugs) {
|
|
9994
11118
|
if (!e.isDirectory() || e.name.startsWith(".")) continue;
|
|
9995
|
-
const { markdown, assets } = await readSkillDir(
|
|
11119
|
+
const { markdown, assets } = await readSkillDir(join8(root, e.name));
|
|
9996
11120
|
out2[e.name] = [{ path: "SKILL.md", contentBase64: Buffer.from(markdown).toString("base64") }, ...assets.map((a) => ({ path: a.name, contentBase64: a.contentBase64 }))];
|
|
9997
11121
|
}
|
|
9998
11122
|
return out2;
|
|
9999
11123
|
}
|
|
10000
11124
|
var line = (c) => c.op === "!" ? dim(`! ${c.what}`) : `${c.op === "+" ? lavender("+") : c.op === "-" ? red("-") : "~"} ${c.what}`;
|
|
10001
11125
|
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)");
|
|
11126
|
+
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
11127
|
const isDir = (await stat(target).catch(() => null))?.isDirectory() ?? false;
|
|
10004
|
-
const text = await
|
|
11128
|
+
const text = await readFile11(isDir ? join8(target, "agent.yaml") : target, "utf8");
|
|
10005
11129
|
const file = (0, import_yaml.parse)(text);
|
|
10006
|
-
if (!file || typeof file !== "object") throw usage(`${target} is not a YAML/JSON agent file.`);
|
|
11130
|
+
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
11131
|
const missing = fillSecrets(file);
|
|
10008
|
-
if (missing.length && !globals.json) info(dim(`Secrets not in your environment keep their current value: ${missing.join(", ")}`));
|
|
11132
|
+
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
11133
|
const files = isDir ? await readSkills(target) : void 0;
|
|
10010
11134
|
let id = v.agent ?? file["x-ghosty"]?.id;
|
|
10011
11135
|
if (v.create) {
|
|
10012
11136
|
const x = file["x-ghosty"] ?? {};
|
|
10013
11137
|
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.")}`);
|
|
11138
|
+
if (!name) throw usage(t("The file has no title: pass --name.", "El archivo no tiene t\xEDtulo: pasa --name."));
|
|
11139
|
+
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}`) : ""})
|
|
11140
|
+
${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
11141
|
requireYesIfHeadless(v.yes, `ghosty apply ${target} --create`);
|
|
10018
11142
|
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
11143
|
id = created.id;
|
|
10020
|
-
info(dim(`Created ${name} (${id}); applying the rest\u2026`));
|
|
11144
|
+
info(dim(t(`Created ${name} (${id}); applying the rest\u2026`, `${name} creado (${id}); aplicando lo dem\xE1s\u2026`)));
|
|
10021
11145
|
} else {
|
|
10022
11146
|
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.");
|
|
11147
|
+
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
11148
|
}
|
|
10025
11149
|
const spec = JSON.stringify(v.create ? { ...file, title: void 0 } : file);
|
|
10026
11150
|
const body = { spec, ...files ? { files } : {}, ...v.prune ? { prune: true } : {}, ...v["no-notify"] ? { notify: false } : {} };
|
|
10027
11151
|
const path = `/api/v2/agents/${enc9(id)}/apply`;
|
|
10028
11152
|
const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
|
|
10029
11153
|
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.");
|
|
11154
|
+
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
11155
|
if (v["dry-run"] || !actionable.length) return out(plan, () => `${bold(id)}
|
|
10032
11156
|
${text2}${actionable.length ? `
|
|
10033
|
-
${dim("Dry run: nothing changed.")}` : ""}`);
|
|
11157
|
+
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}` : ""}`);
|
|
10034
11158
|
const retry = `ghosty apply ${target}${v.agent ? ` --agent ${v.agent}` : ""}${v.prune ? " --prune" : ""}`;
|
|
10035
11159
|
if (!v.create) requireYesIfHeadless(v.yes, retry);
|
|
10036
11160
|
if (!v.create) await confirm(`${text2}
|
|
10037
|
-
Apply ${actionable.length} change(s) to ${id}?`, { yes: v.yes, retry });
|
|
11161
|
+
${t(`Apply ${actionable.length} change(s) to ${id}?`, `\xBFAplicar ${actionable.length} cambio(s) a ${id}?`)}`, { yes: v.yes, retry });
|
|
10038
11162
|
const r = await api(path, { method: "POST", body });
|
|
10039
|
-
out(r, () => `${lavender("\u2713")} ${r.applied?.length ?? 0} change(s) applied to ${id}
|
|
10040
|
-
${red(`Stopped at \xAB${r.failed.what}\xBB: ${r.failed.error}`)}` : ""}`);
|
|
11163
|
+
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 ? `
|
|
11164
|
+
${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
11165
|
}
|
|
10042
11166
|
|
|
10043
11167
|
// src/index.ts
|
|
10044
|
-
var
|
|
11168
|
+
var HELP_EN = `${bold("Usage:")} ghosty <command> [options]
|
|
10045
11169
|
|
|
10046
11170
|
${bold("Session")}
|
|
10047
11171
|
login Sign in with your browser (OAuth2). An agent can run it for you.
|
|
@@ -10049,6 +11173,7 @@ ${bold("Session")}
|
|
|
10049
11173
|
logout Sign out and revoke this device's session
|
|
10050
11174
|
whoami Show who you are signed in as
|
|
10051
11175
|
use [workspace] Default workspace for boards, agents create and agents ls [--clear]
|
|
11176
|
+
completion zsh|bash|fish Tab completion for commands, flags, agents and workspaces
|
|
10052
11177
|
usage Your plan and how much you've used this week and month
|
|
10053
11178
|
|
|
10054
11179
|
${bold("Agents")}
|
|
@@ -10084,6 +11209,7 @@ ${bold("Configure an agent")}
|
|
|
10084
11209
|
skills ls|get|add|rm <id> [slug] Skills (add from catalog, --file SKILL.md or --dir FOLDER; get lists, --out DIR saves; rm keeps a copy)
|
|
10085
11210
|
skills templates <id> pdf-doc templates it sees: its own and Ghosty's, and which folder wins
|
|
10086
11211
|
dbs ls|tables|query|import|rehost|grant|revoke <id> [db] Databases it uses [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run] [--table T --column C]
|
|
11212
|
+
dbs photos put <id> <db> --table T --key-column K --dir DIR Loose photos to a catalog (SKU-123.jpg \u2192 its row), permanent links [--column C] [--dry-run]
|
|
10087
11213
|
mcp get|set <id> MCP servers [--file servers.json]
|
|
10088
11214
|
channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
|
|
10089
11215
|
channels whatsapp link|unlink <id> --integration ID --answer on|off Bring a WhatsApp Business number from Formmy/EasyBits to this agent, or send it back [--forward] [--dry-run]
|
|
@@ -10125,6 +11251,7 @@ ${bold("Global options")}
|
|
|
10125
11251
|
--json Machine-readable output (stdout is JSON only)
|
|
10126
11252
|
--token <token> Use this token (or env GHOSTY_TOKEN). A gat_\u2026 agent token reaches only that agent.
|
|
10127
11253
|
--profile <name> Use a separate saved session (or env GHOSTY_PROFILE), e.g. one per account
|
|
11254
|
+
--lang es|en CLI language (or env GHOSTY_LANG; otherwise from LANG)
|
|
10128
11255
|
--yes Skip confirmations (required for rm without a terminal)
|
|
10129
11256
|
-h, --help Show help (also: ghosty <command> --help, ghosty help <command>)
|
|
10130
11257
|
-v, --version Show version
|
|
@@ -10132,56 +11259,129 @@ ${bold("Global options")}
|
|
|
10132
11259
|
${bold("Exit codes")} 0 ok \xB7 1 API error \xB7 2 usage error \xB7 3 not signed in
|
|
10133
11260
|
${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
11261
|
${dim("Docs: https://www.ghosty.studio/docs/cli")}`;
|
|
10135
|
-
var
|
|
11262
|
+
var HELP_ES = `${bold("Uso:")} ghosty <comando> [opciones]
|
|
11263
|
+
|
|
11264
|
+
${bold("Sesi\xF3n")}
|
|
11265
|
+
login Inicia sesi\xF3n con tu navegador (OAuth2). Un agente puede correrlo por ti.
|
|
11266
|
+
--device Inicia sesi\xF3n desde otro dispositivo con liga + c\xF3digo (autom\xE1tico por SSH/CI)
|
|
11267
|
+
logout Cierra sesi\xF3n y revoca la sesi\xF3n de este dispositivo
|
|
11268
|
+
whoami Muestra con qu\xE9 cuenta entraste
|
|
11269
|
+
use [espacio] Espacio por default para boards, agents create y agents ls [--clear]
|
|
11270
|
+
completion zsh|bash|fish Autocompletado con TAB: comandos, opciones, agentes y espacios
|
|
11271
|
+
usage Tu plan y cu\xE1nto llevas usado esta semana y este mes
|
|
11272
|
+
|
|
11273
|
+
${bold("Agentes")}
|
|
11274
|
+
agents ls Lista tus agentes
|
|
11275
|
+
agents create Crea un agente --name [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
|
|
11276
|
+
[--workspace SLUG] [--channels teams=off,\u2026] nace en un espacio, activo en su Teams
|
|
11277
|
+
[--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)
|
|
11278
|
+
agents export <id> El agente como archivo (YAML, sin secretos) [--out DIR: agent.yaml + sus skills]
|
|
11279
|
+
apply <archivo|dir> Deja un agente igual que un archivo: ense\xF1a el plan (+ ~ -), pregunta y aplica [--agent X] [--create] [--prune] [--dry-run]
|
|
11280
|
+
agents get <id> Muestra la configuraci\xF3n de un agente [--fields name,model,prompt] [--prompt-out ARCHIVO]
|
|
11281
|
+
agents set <id> Cambia identidad o motor [--name] [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
|
|
11282
|
+
[--connect google-drive,\u2026] [--disconnect \u2026] dale o qu\xEDtale acceso a tus conectores
|
|
11283
|
+
[--channels wa=on,teams=off] prende/apaga canales (wa = WhatsApp personal)
|
|
11284
|
+
[--voice elevenlabs:ID | kokoro:em_santa] [--voice-replies auto|off] [--auto-stage on|off]
|
|
11285
|
+
[--toolsets crm,sales | +crm,-sales] grupos de tools de gs que recibe
|
|
11286
|
+
[--prompt-base AGENTE|none --prompt-overlay ARCHIVO [--dry-run]] prompt = el de otro agente + ediciones, sincronizado
|
|
11287
|
+
agents doctor <id> Revisa la configuraci\xF3n: skills encimadas, tools que nombra el prompt, permisos rotos, turnos que fallan
|
|
11288
|
+
agents docs ls|grant|revoke <id> [doc] Documentos que lee y puede enviar [--write] [--no-deliver]
|
|
11289
|
+
agents docs grant <id> A B\u2026 Da varios documentos a la vez [--like AGENTE] copia los de otro agente
|
|
11290
|
+
agents docs add <id> Sube un documento y d\xE1selo --url URL | --file RUTA [--description] [--grant read|write|no]
|
|
11291
|
+
agents share|unshare <id> <correo> Comp\xE1rtelo sin cederlo [--role editor|admin] \xB7 agents shares <id> los lista
|
|
11292
|
+
agents rm <id> Borra un agente (s\xF3lo el due\xF1o; te pide escribir su nombre, o --yes)
|
|
11293
|
+
agents restart <id> Reinicia un agente ACP (needsRestart) para que cargue skills nuevas
|
|
11294
|
+
agents box <id> La m\xE1quina que lo atiende: \xBFya trae la imagen nueva? [--check RUTA]\u2026 [--contains RUTA=TEXTO]\u2026 [--recycle]
|
|
11295
|
+
agents demo enable|disable <id> Prende o apaga su liga p\xFAblica de demo
|
|
11296
|
+
agents demo <id> Liga p\xFAblica de demo /p/<slug>: cualquiera lo prueba sin cuenta y \xABQued\xE1rmelo\xBB lo clona a la suya
|
|
11297
|
+
[--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]
|
|
11298
|
+
|
|
11299
|
+
${bold("Configurar un agente")}
|
|
11300
|
+
files ls|get|put|rm <id> [ruta] Archivos en la m\xE1quina del agente [--file LOCAL] [--out LOCAL]
|
|
11301
|
+
skills enable|disable <id> <slug> Prende o apaga una de sus skills (o una de Ghosty)
|
|
11302
|
+
skills house <id> Las skills de Ghosty en bloque [--only a,b | --enable a,b | --disable a,b] [--dry-run]
|
|
11303
|
+
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)
|
|
11304
|
+
skills templates <id> Plantillas de pdf-doc que ve: las suyas y las de Ghosty, y qu\xE9 carpeta gana
|
|
11305
|
+
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]
|
|
11306
|
+
dbs photos put <id> <db> --table T --key-column K --dir DIR Fotos sueltas a un cat\xE1logo (SKU-123.jpg \u2192 su fila), ligas permanentes [--column C] [--dry-run]
|
|
11307
|
+
mcp get|set <id> Servidores MCP [--file servers.json]
|
|
11308
|
+
channels whatsapp ls|enable|disable <id> [n\xFAmero] Qu\xE9 n\xFAmeros de WhatsApp Business contesta [--dry-run]
|
|
11309
|
+
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]
|
|
11310
|
+
channels whatsapp history <id> --integration ID [--days 7] Trae de Formmy sus tarjetas, mensajes y pedidos recientes (sin duplicar) [--dry-run]
|
|
11311
|
+
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]
|
|
11312
|
+
channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
|
|
11313
|
+
channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
|
|
11314
|
+
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]
|
|
11315
|
+
boards ls|create|assign|move-cards|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas
|
|
11316
|
+
cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--limit 20]
|
|
11317
|
+
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)
|
|
11318
|
+
cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
11319
|
+
|
|
11320
|
+
${bold("Hablar con un agente")}
|
|
11321
|
+
try <id> "mensaje" Un turno de prueba; imprime la respuesta completa [--session] [--reset]
|
|
11322
|
+
--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]
|
|
11323
|
+
--replay ARCHIVO --out DIR Repite conversaciones reales; el reporte se escribe local [--max 15] [--concurrency 8] [--resume]
|
|
11324
|
+
chat <id> ["mensaje"] Conversaci\xF3n en streaming (sin mensaje = interactivo) [--conversation ID]
|
|
11325
|
+
conversations ls <id> Lista conversaciones
|
|
11326
|
+
conversations show <id> <conversaci\xF3n> Qu\xE9 se dijo [--limit 20] (0 = todo)
|
|
11327
|
+
turns ls <id> Turnos recientes y por qu\xE9 fallaron [--errors] [--since 1h] [--limit N]
|
|
11328
|
+
turns show <id> [turno] Qu\xE9 lleg\xF3, qu\xE9 contest\xF3 y en qu\xE9 se fue el tiempo (default: el \xFAltimo) [--full]
|
|
11329
|
+
|
|
11330
|
+
${bold("Turnos programados")} ${dim("(cada corrida cae en una conversaci\xF3n y avisa a tu tel\xE9fono; una respuesta de exactamente OK no avisa)")}
|
|
11331
|
+
schedule ls <id> Turnos pendientes y corridas recientes
|
|
11332
|
+
schedule add <id> "qu\xE9 hacer" Programa un turno --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]
|
|
11333
|
+
schedule rm <id> <schedule-id> Cancela un turno pendiente
|
|
11334
|
+
|
|
11335
|
+
${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ver: ghosty partner --help)")}
|
|
11336
|
+
partner ls | enable | credentials | agent | tenants | audit | try
|
|
11337
|
+
|
|
11338
|
+
${bold("Tu cuenta")}
|
|
11339
|
+
me files ls|upload|rm Archivos de tu cuenta [--kind image|video|audio|document|other]
|
|
11340
|
+
credentials ls|set|rm [proveedor] Llaves de proveedor en tu b\xF3veda (prompt oculto, stdin o --from-env VAR; nunca como argumento)
|
|
11341
|
+
docs [tema] Abre la documentaci\xF3n
|
|
11342
|
+
|
|
11343
|
+
${bold("Opciones globales")}
|
|
11344
|
+
<agente> En todos lados: el NOMBRE de un agente (sin importar may\xFAsculas) o su id
|
|
11345
|
+
--json Salida para m\xE1quinas (stdout es s\xF3lo JSON)
|
|
11346
|
+
--token <token> Usa este token (o env GHOSTY_TOKEN). Un token de agente gat_\u2026 s\xF3lo llega a ese agente.
|
|
11347
|
+
--profile <nombre> Usa una sesi\xF3n guardada aparte (o env GHOSTY_PROFILE), p. ej. una por cuenta
|
|
11348
|
+
--lang es|en Idioma del CLI (o env GHOSTY_LANG; si no, el de LANG)
|
|
11349
|
+
--yes S\xE1ltate las confirmaciones (obligatorio para rm sin terminal)
|
|
11350
|
+
-h, --help Muestra la ayuda (tambi\xE9n: ghosty <comando> --help, ghosty help <comando>)
|
|
11351
|
+
-v, --version Muestra la versi\xF3n
|
|
11352
|
+
|
|
11353
|
+
${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
|
|
11354
|
+
${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.")}
|
|
11355
|
+
${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
|
|
11356
|
+
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
11357
|
var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
|
|
10137
11358
|
function unknown(what, input, known, helpCmd) {
|
|
10138
11359
|
const s = suggest(input, known);
|
|
10139
|
-
return usage(`Unknown command: ${what}.`, s ? `Did you mean "${s}"? Run: ${helpCmd}` : `Run: ${helpCmd}`);
|
|
11360
|
+
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
11361
|
}
|
|
10141
11362
|
function showHelp() {
|
|
10142
11363
|
if (process.stdout.isTTY && !process.env.NO_COLOR && !globals.json) process.stdout.write(BANNER + "\n\n");
|
|
10143
|
-
process.stdout.write(
|
|
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
|
-
}
|
|
11364
|
+
process.stdout.write(t(HELP_EN, HELP_ES) + "\n");
|
|
10171
11365
|
}
|
|
10172
11366
|
async function useCmd(slug, v) {
|
|
10173
11367
|
const cfg = await readConfig();
|
|
10174
11368
|
if (v.clear) {
|
|
10175
11369
|
delete cfg.workspace;
|
|
10176
11370
|
await writeConfig(cfg);
|
|
10177
|
-
return out({ workspace: null }, () => `${lavender("\u2713")} No default workspace
|
|
11371
|
+
return out({ workspace: null }, () => `${lavender("\u2713")} ${t("No default workspace.", "Sin espacio por default.")}`);
|
|
10178
11372
|
}
|
|
10179
|
-
if (!slug) return out({ workspace: cfg.workspace ?? null }, () => cfg.workspace ?
|
|
11373
|
+
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
11374
|
await api(`/api/v2/workspaces/${encodeURIComponent(slug)}/boards`);
|
|
10181
11375
|
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)")}`);
|
|
11376
|
+
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
11377
|
}
|
|
10184
11378
|
async function main() {
|
|
11379
|
+
if (process.argv[2] === "__complete") {
|
|
11380
|
+
const rest = process.argv.slice(3);
|
|
11381
|
+
return completeCmd(rest[0] === "--" ? rest.slice(1) : rest, COMMANDS);
|
|
11382
|
+
}
|
|
11383
|
+
const langAt = process.argv.findIndex((a) => a === "--lang" || a.startsWith("--lang="));
|
|
11384
|
+
if (langAt >= 0) setLang(detectLang(process.argv[langAt].includes("=") ? process.argv[langAt].split("=")[1] : process.argv[langAt + 1]));
|
|
10185
11385
|
const { values: v, positionals: pos } = parseArgs({
|
|
10186
11386
|
allowPositionals: true,
|
|
10187
11387
|
strict: true,
|
|
@@ -10252,6 +11452,7 @@ async function main() {
|
|
|
10252
11452
|
verbose: { type: "boolean" },
|
|
10253
11453
|
full: { type: "boolean" },
|
|
10254
11454
|
"as-formmy": { type: "boolean" },
|
|
11455
|
+
"key-column": { type: "string" },
|
|
10255
11456
|
like: { type: "string" },
|
|
10256
11457
|
"copy-dbs": { type: "boolean" },
|
|
10257
11458
|
"no-notify": { type: "boolean" },
|
|
@@ -10264,6 +11465,7 @@ async function main() {
|
|
|
10264
11465
|
all: { type: "boolean" },
|
|
10265
11466
|
clear: { type: "boolean" },
|
|
10266
11467
|
expires: { type: "string" },
|
|
11468
|
+
lang: { type: "string" },
|
|
10267
11469
|
agent: { type: "string" },
|
|
10268
11470
|
create: { type: "boolean" },
|
|
10269
11471
|
prune: { type: "boolean" },
|
|
@@ -10306,10 +11508,11 @@ async function main() {
|
|
|
10306
11508
|
}
|
|
10307
11509
|
});
|
|
10308
11510
|
globals.json = Boolean(v.json);
|
|
11511
|
+
setLang(detectLang(v.lang));
|
|
10309
11512
|
const tokens = splitTokens(pos, v);
|
|
10310
11513
|
globals.token = tokens.session;
|
|
10311
11514
|
globals.profile = v.profile;
|
|
10312
|
-
if (v.version) return out({ version: "0.
|
|
11515
|
+
if (v.version) return out({ version: "0.21.0" }, () => "0.21.0");
|
|
10313
11516
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
10314
11517
|
const context = (await readConfig()).workspace;
|
|
10315
11518
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -10321,30 +11524,35 @@ async function main() {
|
|
|
10321
11524
|
}
|
|
10322
11525
|
}
|
|
10323
11526
|
const [cmd, sub, a1, a2] = pos;
|
|
10324
|
-
if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help:
|
|
11527
|
+
if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: partnerHelp() }, () => partnerHelp());
|
|
10325
11528
|
const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
|
|
10326
|
-
if (topic === "partner") return out({ help:
|
|
11529
|
+
if (topic === "partner") return out({ help: partnerHelp() }, () => partnerHelp());
|
|
10327
11530
|
if (topic && helpText(topic)) return out({ help: helpText(topic) }, () => helpText(topic));
|
|
10328
11531
|
if (!cmd || v.help || cmd === "help") return showHelp();
|
|
10329
11532
|
switch (cmd) {
|
|
10330
11533
|
case "login": {
|
|
10331
11534
|
if (v.token) {
|
|
10332
|
-
throw usage("--token is used per command, not saved.", "Set GHOSTY_TOKEN=<token> in your environment instead.");
|
|
11535
|
+
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
11536
|
}
|
|
10334
11537
|
await (v.device || !hasLocalBrowser() ? loginDevice() : login());
|
|
10335
11538
|
const me = await api("/api/v2/me");
|
|
10336
11539
|
const cfg = await readConfig();
|
|
10337
11540
|
await writeConfig({ ...cfg, email: me.email });
|
|
10338
|
-
return out({ event: "logged_in", email: me.email }, () => `${lavender("\u2713")} Signed in as ${bold(me.email)}`);
|
|
11541
|
+
return out({ event: "logged_in", email: me.email }, () => `${lavender("\u2713")} ${t("Signed in as", "Sesi\xF3n iniciada como")} ${bold(me.email)}`);
|
|
10339
11542
|
}
|
|
10340
11543
|
case "logout":
|
|
10341
11544
|
await logout();
|
|
10342
|
-
return out({ loggedOut: true }, () => `${lavender("\u2713")} Signed out
|
|
11545
|
+
return out({ loggedOut: true }, () => `${lavender("\u2713")} ${t("Signed out.", "Sesi\xF3n cerrada.")}`);
|
|
10343
11546
|
case "whoami": {
|
|
10344
|
-
if (explicitToken()?.startsWith("gat_")) return out({ tokenType: "agent" }, () => "Using an agent token (gat_\u2026): it reaches only its own agent.");
|
|
11547
|
+
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
11548
|
const me = await api("/api/v2/me");
|
|
10346
11549
|
return out(me, () => `${bold(me.email)}${dim(` \xB7 ${new URL(process.env.GHOSTY_URL || "https://www.ghosty.studio").host}`)}`);
|
|
10347
11550
|
}
|
|
11551
|
+
case "completion": {
|
|
11552
|
+
const script = completionScript(sub ?? "");
|
|
11553
|
+
if (!script) throw usage(t("Which shell?", "\xBFQu\xE9 shell?"), "ghosty completion zsh | bash | fish");
|
|
11554
|
+
return void process.stdout.write(script);
|
|
11555
|
+
}
|
|
10348
11556
|
case "use":
|
|
10349
11557
|
return useCmd(sub, v);
|
|
10350
11558
|
case "apply":
|
|
@@ -10355,7 +11563,7 @@ async function main() {
|
|
|
10355
11563
|
if (v.workspace === "none") delete v.workspace;
|
|
10356
11564
|
else if (!v.workspace && context) {
|
|
10357
11565
|
v.workspace = context;
|
|
10358
|
-
if (!globals.json) info(dim(`workspace ${context} (from ghosty use; --workspace none for a personal agent)`));
|
|
11566
|
+
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
11567
|
}
|
|
10360
11568
|
return agentsCreate(v);
|
|
10361
11569
|
}
|
|
@@ -10378,6 +11586,7 @@ async function main() {
|
|
|
10378
11586
|
return mcpCmd(sub, a1, v);
|
|
10379
11587
|
case "dbs":
|
|
10380
11588
|
case "db":
|
|
11589
|
+
if (sub === "photos") return dbsPhotosCmd(a1, a2, pos[4], v);
|
|
10381
11590
|
return dbsCmd(sub, a1, a2, pos.slice(4).filter((x) => x !== void 0), v);
|
|
10382
11591
|
case "turns":
|
|
10383
11592
|
return turnsCmd(sub, a1, a2, v);
|
|
@@ -10398,7 +11607,7 @@ async function main() {
|
|
|
10398
11607
|
case "conversations":
|
|
10399
11608
|
if (sub === "ls" || sub === "list") return conversationsLs(a1);
|
|
10400
11609
|
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>");
|
|
11610
|
+
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
11611
|
case "schedule":
|
|
10403
11612
|
return scheduleCmd(sub, a1, a2, v);
|
|
10404
11613
|
case "partner":
|
|
@@ -10408,7 +11617,7 @@ async function main() {
|
|
|
10408
11617
|
case "me":
|
|
10409
11618
|
if (sub === "files") return meFiles(a1, a2, v);
|
|
10410
11619
|
if (sub === "usage") return meUsage();
|
|
10411
|
-
throw usage(`Unknown me subcommand: ${sub ?? "(none)"}.`, "Try: ghosty me files ls");
|
|
11620
|
+
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
11621
|
case "docs": {
|
|
10413
11622
|
const url = `${await baseUrl()}/docs${sub ? `/${encodeURIComponent(sub)}` : "/cli"}`;
|
|
10414
11623
|
if (globals.json) return out({ url });
|
|
@@ -10419,7 +11628,7 @@ async function main() {
|
|
|
10419
11628
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
10420
11629
|
}
|
|
10421
11630
|
}
|
|
10422
|
-
var notice = updateNotice("0.
|
|
11631
|
+
var notice = updateNotice("0.21.0").catch(() => null);
|
|
10423
11632
|
main().then(
|
|
10424
11633
|
async () => {
|
|
10425
11634
|
const n = await notice;
|
|
@@ -10430,7 +11639,7 @@ main().then(
|
|
|
10430
11639
|
if (e && typeof e === "object" && "code" in e && String(e.code).startsWith("ERR_PARSE_ARGS")) {
|
|
10431
11640
|
const n = await notice;
|
|
10432
11641
|
return fail(usage(e.message, n ? `${n}
|
|
10433
|
-
This option may exist in the new version
|
|
11642
|
+
${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
11643
|
}
|
|
10435
11644
|
return fail(e);
|
|
10436
11645
|
}
|