@ghostystudio/cli 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/index.js +1594 -472
- 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,604 @@ import { chmod, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
7383
7383
|
import { homedir } from "node:os";
|
|
7384
7384
|
import { dirname, join } from "node:path";
|
|
7385
7385
|
|
|
7386
|
+
// src/i18n/server-messages.ts
|
|
7387
|
+
var SERVER_MESSAGES = [
|
|
7388
|
+
// — Genéricos de la API: método, cuerpo, firma y tokens
|
|
7389
|
+
["m\xE9todo no permitido", "method not allowed"],
|
|
7390
|
+
["json inv\xE1lido", "invalid json"],
|
|
7391
|
+
["body JSON requerido", "JSON body required"],
|
|
7392
|
+
["body debe ser un objeto JSON", "body must be a JSON object"],
|
|
7393
|
+
["cuerpo inv\xE1lido", "invalid body"],
|
|
7394
|
+
["cuerpo vac\xEDo", "empty body"],
|
|
7395
|
+
["respuesta vac\xEDa", "empty response"],
|
|
7396
|
+
["vac\xEDo", "empty"],
|
|
7397
|
+
["nada que cambiar", "nothing to change"],
|
|
7398
|
+
["nada que guardar", "nothing to save"],
|
|
7399
|
+
["No existe o no es tuyo.", "Doesn't exist or isn't yours."],
|
|
7400
|
+
["no existe", "doesn't exist"],
|
|
7401
|
+
["No existe", "Doesn't exist"],
|
|
7402
|
+
["No es JSON v\xE1lido.", "Not valid JSON."],
|
|
7403
|
+
["No es JSON v\xE1lido", "Not valid JSON"],
|
|
7404
|
+
["token inv\xE1lido", "invalid token"],
|
|
7405
|
+
["falta firma", "missing signature"],
|
|
7406
|
+
["firma inv\xE1lida", "invalid signature"],
|
|
7407
|
+
["firma sin workspace identificado", "signature without an identified workspace"],
|
|
7408
|
+
["falta el workspace en la firma", "missing the workspace in the signature"],
|
|
7409
|
+
["ownerUserId no coincide con el workspace", "ownerUserId doesn't match the workspace"],
|
|
7410
|
+
["runtime sin GHOSTY_PARTNER_SECRET", "runtime without GHOSTY_PARTNER_SECRET"],
|
|
7411
|
+
["ruta inv\xE1lida", "invalid path"],
|
|
7412
|
+
["path requerido", "path required"],
|
|
7413
|
+
["url requerida", "url required"],
|
|
7414
|
+
["url inv\xE1lida: tiene que ser https", "invalid url: must be https"],
|
|
7415
|
+
["url inv\xE1lida", "invalid url"],
|
|
7416
|
+
["s\xF3lo https", "https only"],
|
|
7417
|
+
["la URL no puede llevar credenciales", "the URL can't carry credentials"],
|
|
7418
|
+
["{} apunta a la red interna", "{} points to the internal network"],
|
|
7419
|
+
["returnTo inv\xE1lido", "invalid returnTo"],
|
|
7420
|
+
["status inv\xE1lido", "invalid status"],
|
|
7421
|
+
["estado inv\xE1lido", "invalid status"],
|
|
7422
|
+
["visibility inv\xE1lida", "invalid visibility"],
|
|
7423
|
+
["vista no v\xE1lida", "invalid view"],
|
|
7424
|
+
["la vista ya no est\xE1 disponible (enlace caducado)", "the view is no longer available (link expired)"],
|
|
7425
|
+
["versi\xF3n no encontrada", "version not found"],
|
|
7426
|
+
["acci\xF3n desconocida {}", "unknown action {}"],
|
|
7427
|
+
["acci\xF3n desconocida", "unknown action"],
|
|
7428
|
+
["acci\xF3n inv\xE1lida", "invalid action"],
|
|
7429
|
+
["acci\xF3n no soportada: {}", "unsupported action: {}"],
|
|
7430
|
+
["op desconocida: usa create | list | exec | write | expose | destroy", "unknown op: use create | list | exec | write | expose | destroy"],
|
|
7431
|
+
["op desconocida: {}", "unknown op: {}"],
|
|
7432
|
+
["since: 30m, 1h, 2d o una fecha ISO", "since: 30m, 1h, 2d or an ISO date"],
|
|
7433
|
+
["before: 30m, 1h, 2d o una fecha ISO", "before: 30m, 1h, 2d or an ISO date"],
|
|
7434
|
+
// — Tokens de agente: lo que sólo se hace con la sesión de la persona
|
|
7435
|
+
["el token del agente no cambia datos: usa tu sesi\xF3n (ghosty login)", "an agent token can't change data: use your session (ghosty login)"],
|
|
7436
|
+
["el token del agente no cambia grupos: usa tu sesi\xF3n (ghosty login)", "an agent token can't change groups: use your session (ghosty login)"],
|
|
7437
|
+
["el token del agente no cambia su tablero: usa tu sesi\xF3n", "an agent token can't change its board: use your session"],
|
|
7438
|
+
["el token del agente no concede bases: usa tu sesi\xF3n (ghosty login)", "an agent token can't grant databases: use your session (ghosty login)"],
|
|
7439
|
+
["el token del agente no concede documentos: usa tu sesi\xF3n (ghosty login)", "an agent token can't grant documents: use your session (ghosty login)"],
|
|
7440
|
+
["el token del agente no exporta su configuraci\xF3n: usa tu sesi\xF3n", "an agent token can't export its configuration: use your session"],
|
|
7441
|
+
["el token del agente no importa datos: usa tu sesi\xF3n (ghosty login)", "an agent token can't import data: use your session (ghosty login)"],
|
|
7442
|
+
["el token del agente no liga su prompt a otro: usa tu sesi\xF3n", "an agent token can't link its prompt to another: use your session"],
|
|
7443
|
+
["el token del agente no sube documentos: usa tu sesi\xF3n (ghosty login)", "an agent token can't upload documents: use your session (ghosty login)"],
|
|
7444
|
+
["el token del agente s\xF3lo lee: usa tu sesi\xF3n (ghosty login)", "an agent token is read-only: use your session (ghosty login)"],
|
|
7445
|
+
// — Agentes: existencia, dueño y permisos
|
|
7446
|
+
["fleet agent no existe", "fleet agent doesn't exist"],
|
|
7447
|
+
["agente no existe", "agent doesn't exist"],
|
|
7448
|
+
["agente no encontrado", "agent not found"],
|
|
7449
|
+
["agente desconocido", "unknown agent"],
|
|
7450
|
+
["agente borrado", "agent deleted"],
|
|
7451
|
+
["el agente no existe", "the agent doesn't exist"],
|
|
7452
|
+
["el agente ya no existe", "the agent no longer exists"],
|
|
7453
|
+
["el agente no tiene due\xF1o", "the agent has no owner"],
|
|
7454
|
+
["ese agente no es ACP", "that agent isn't ACP"],
|
|
7455
|
+
["no encuentro el agente base (o no tienes acceso a \xE9l)", "I can't find the base agent (or you don't have access to it)"],
|
|
7456
|
+
["no encuentro el agente {} (o no tienes acceso)", "I can't find agent {} (or you don't have access)"],
|
|
7457
|
+
["no administras el espacio {} (o no existe)", "you don't manage workspace {} (or it doesn't exist)"],
|
|
7458
|
+
["s\xF3lo el due\xF1o da acceso a sus conectores", "only the owner grants access to their connectors"],
|
|
7459
|
+
["s\xF3lo el due\xF1o del agente maneja su demo", "only the agent's owner manages its demo"],
|
|
7460
|
+
["s\xF3lo el due\xF1o puede borrar el agente", "only the owner can delete the agent"],
|
|
7461
|
+
["Ese agente est\xE1 activo en {}. Qu\xEDtalo de ah\xED (Ajustes \u2192 Agentes) antes de borrarlo.", "That agent is active in {}. Remove it from there (Settings \u2192 Agents) before deleting it."],
|
|
7462
|
+
["Ese espacio no es tuyo.", "That workspace isn't yours."],
|
|
7463
|
+
["@{} ya est\xE1 ocupado por otro agente en este espacio: dale un handle propio", "@{} is already taken by another agent in this workspace: give it its own handle"],
|
|
7464
|
+
["sin cupo para otro agente ahora", "no room for another agent right now"],
|
|
7465
|
+
["Tu plan incluye 1 agente.", "Your plan includes 1 agent."],
|
|
7466
|
+
["Tu plan incluye {} agentes.", "Your plan includes {} agents."],
|
|
7467
|
+
["name requerido", "name required"],
|
|
7468
|
+
["canal inv\xE1lido: {}", "invalid channel: {}"],
|
|
7469
|
+
["channel inv\xE1lido", "invalid channel"],
|
|
7470
|
+
["conector desconocido: {}", "unknown connector: {}"],
|
|
7471
|
+
["voz de kokoro desconocida: {}", "unknown kokoro voice: {}"],
|
|
7472
|
+
["box s\xF3lo lo cambia el staff de Ghosty (sale del plan)", "box can only be changed by Ghosty staff (it comes from the plan)"],
|
|
7473
|
+
// — Compartir un agente
|
|
7474
|
+
["Esa persona no tiene cuenta en ghosty.studio todav\xEDa.", "That person doesn't have a ghosty.studio account yet."],
|
|
7475
|
+
["Ya es tuyo.", "It's already yours."],
|
|
7476
|
+
["falta email", "missing email"],
|
|
7477
|
+
["no est\xE1 compartido con {}", "it isn't shared with {}"],
|
|
7478
|
+
// — Motor, modelo y máquina del agente (updateFleetAgentConfig, createFleetAgentRecord)
|
|
7479
|
+
["motor desconocido: {}", "unknown engine: {}"],
|
|
7480
|
+
["el modelo {} no es del motor {}", "model {} doesn't belong to engine {}"],
|
|
7481
|
+
["modelo {} inv\xE1lido para {}", "invalid model {} for {}"],
|
|
7482
|
+
["model requerido", "model required"],
|
|
7483
|
+
["motor {} no existe o no tiene modelo listo", "engine {} doesn't exist or has no model ready"],
|
|
7484
|
+
["motor {} sin modelo listo", "engine {} has no model ready"],
|
|
7485
|
+
['el motor "{}" no usa llave del vault', `engine "{}" doesn't use a vault key`],
|
|
7486
|
+
["mutaci\xF3n desconocida", "unknown mutation"],
|
|
7487
|
+
["tama\xF1o inv\xE1lido: {} (usa {} MB)", "invalid size: {} (use {} MB)"],
|
|
7488
|
+
["conversaciones por caja: 1 a 40", "conversations per box: 1 to 40"],
|
|
7489
|
+
["el agente est\xE1 trabajando ahora; cambia el motor cuando termine", "the agent is working right now; change the engine when it finishes"],
|
|
7490
|
+
["no pude saber si el agente est\xE1 trabajando; no cambio el motor a ciegas", "I couldn't tell whether the agent is working; I won't change the engine blindly"],
|
|
7491
|
+
["la identidad qued\xF3 guardada pero no lleg\xF3 a la m\xE1quina del agente: {}", "the identity was saved but didn't reach the agent's machine: {}"],
|
|
7492
|
+
["las tools quedaron guardadas pero no llegaron a la m\xE1quina del agente: {}", "the tools were saved but didn't reach the agent's machine: {}"],
|
|
7493
|
+
["el motor qued\xF3 guardado pero su m\xE1quina no arranc\xF3: {}", "the engine was saved but its machine didn't start: {}"],
|
|
7494
|
+
["el modelo qued\xF3 guardado pero no lleg\xF3 a la caja del agente: {}", "the model was saved but didn't reach the agent's box: {}"],
|
|
7495
|
+
["no se pudo aplicar el cambio en la m\xE1quina del agente, as\xED que no se cambi\xF3 nada: {}", "the change couldn't be applied on the agent's machine, so nothing was changed: {}"],
|
|
7496
|
+
['el plan de este espacio topa en {}\xD7 y ning\xFAn modelo de "{}" cabe ah\xED. Usa un motor m\xE1s barato, o s\xFAbele el tope con "Cap de modelo" en /admin/tenants.', `this workspace's plan caps at {}\xD7 and no "{}" model fits under it. Use a cheaper engine, or raise the cap with "Cap de modelo" in /admin/tenants.`],
|
|
7497
|
+
["{} no entra en tu plan: gasta {}\xD7 de saldo por turno y tu plan admite modelos de hasta {}\xD7. Elige otro modelo o sube de plan en /planes.", "{} isn't in your plan: it spends {}\xD7 balance per turn and your plan allows models up to {}\xD7. Choose another model or upgrade at /planes."],
|
|
7498
|
+
["este motor carga archivos, skills y MCP en el siguiente turno: no necesita restart", "this engine loads files, skills and MCP on the next turn: no restart needed"],
|
|
7499
|
+
["no tienes llave de {} que prestar", "you have no {} key to lend"],
|
|
7500
|
+
// — Prompt derivado (setPromptBase y la capa de ediciones)
|
|
7501
|
+
["este prompt se genera del de {} + una capa: cambia la base o la capa, o su\xE9ltalo (ghosty agents set {} --prompt-base none)", "this prompt is generated from {}'s plus a layer: change the base or the layer, or detach it (ghosty agents set {} --prompt-base none)"],
|
|
7502
|
+
["el agente base no existe", "the base agent doesn't exist"],
|
|
7503
|
+
["el agente base no tiene prompt", "the base agent has no prompt"],
|
|
7504
|
+
["el prompt de este agente no deriva de otro", "this agent's prompt doesn't derive from another"],
|
|
7505
|
+
["la base deriva de este agente: har\xEDa un ciclo", "the base derives from this agent: it would make a cycle"],
|
|
7506
|
+
["la base ya no tiene \xAB{}\xBB", 'the base no longer has "{}"'],
|
|
7507
|
+
["la capa necesita `edits`: [{what, find, replace}]", "the layer needs `edits`: [{what, find, replace}]"],
|
|
7508
|
+
["capa inv\xE1lida: {}", "invalid layer: {}"],
|
|
7509
|
+
["demasiadas ediciones (m\xE1x. 200)", "too many edits (max 200)"],
|
|
7510
|
+
["edits[{}]: falta find", "edits[{}]: missing find"],
|
|
7511
|
+
['edits[{}]: falta replace (puede ser "")', 'edits[{}]: missing replace (can be "")'],
|
|
7512
|
+
["edits[{}]: regex inv\xE1lida: {}", "edits[{}]: invalid regex: {}"],
|
|
7513
|
+
// — Skills del agente y de casa
|
|
7514
|
+
["el agente no tiene una skill propia \xAB{}\xBB", 'the agent has no own skill "{}"'],
|
|
7515
|
+
["ni el agente ni Ghosty tienen una skill \xAB{}\xBB (ghosty skills ls)", 'neither the agent nor Ghosty has a skill "{}" (ghosty skills ls)'],
|
|
7516
|
+
["no son skills de Ghosty: {} (ghosty skills house <id> las lista)", "not Ghosty skills: {} (ghosty skills house <id> lists them)"],
|
|
7517
|
+
["falta markdown (el contenido de SKILL.md)", "missing markdown (the contents of SKILL.md)"],
|
|
7518
|
+
["falta enabled (true|false)", "missing enabled (true|false)"],
|
|
7519
|
+
["slug inv\xE1lido: a-z, 0-9 y guiones, m\xE1x 64", "invalid slug: a-z, 0-9 and hyphens, max 64"],
|
|
7520
|
+
["Nombre inv\xE1lido: a-z, 0-9 y guiones, m\xE1x 64.", "Invalid name: a-z, 0-9 and hyphens, max 64."],
|
|
7521
|
+
["Nombre de archivo inv\xE1lido: {}", "Invalid file name: {}"],
|
|
7522
|
+
["SKILL.md empieza con un bloque --- name / description ---.", "SKILL.md starts with a --- name / description --- block."],
|
|
7523
|
+
["Falta `name:` en el frontmatter.", "Missing `name:` in the frontmatter."],
|
|
7524
|
+
["Falta `description:` en el frontmatter (es lo que el agente lee para decidir cu\xE1ndo usarla).", "Missing `description:` in the frontmatter (it's what the agent reads to decide when to use it)."],
|
|
7525
|
+
["`{}` empieza con un car\xE1cter que YAML no admite sin comillas.", "`{}` starts with a character YAML doesn't accept without quotes."],
|
|
7526
|
+
["`{}` lleva un \xAB: \xBB sin comillas; ponlo entre comillas dobles o el agente no ver\xE1 la habilidad.", '`{}` has an unquoted ": "; wrap it in double quotes or the agent won\'t see the skill.'],
|
|
7527
|
+
["La habilidad pesa m\xE1s de 25 MB.", "The skill is larger than 25 MB."],
|
|
7528
|
+
["Esa habilidad no est\xE1 en el cat\xE1logo de la comunidad.", "That skill isn't in the community catalog."],
|
|
7529
|
+
["aplica en el siguiente reinicio (POST \u2026/restart)", "applies on the next restart (POST \u2026/restart)"],
|
|
7530
|
+
["aplica en el siguiente turno", "applies on the next turn"],
|
|
7531
|
+
["aplica desde tu siguiente conversaci\xF3n (o tras un reinicio)", "applies from your next conversation (or after a restart)"],
|
|
7532
|
+
["desaparece de la lista del cerebro en el siguiente reinicio (POST \u2026/restart)", "disappears from the brain's list on the next restart (POST \u2026/restart)"],
|
|
7533
|
+
["desaparece en el siguiente turno", "disappears on the next turn"],
|
|
7534
|
+
["el cerebro la ve en el siguiente turno", "the brain sees it on the next turn"],
|
|
7535
|
+
["el cerebro la ve tras el siguiente reinicio: POST \u2026/restart", "the brain sees it after the next restart: POST \u2026/restart"],
|
|
7536
|
+
["manda only, on u off (listas de slugs)", "send only, on or off (lists of slugs)"],
|
|
7537
|
+
["only va solo (o usa on/off)", "only goes alone (or use on/off)"],
|
|
7538
|
+
["en on y off a la vez: {}", "in both on and off: {}"],
|
|
7539
|
+
// — Archivo del agente (apply/export/bundle)
|
|
7540
|
+
["falta spec (el texto del archivo)", "missing spec (the file's text)"],
|
|
7541
|
+
["archivo inv\xE1lido: {}", "invalid file: {}"],
|
|
7542
|
+
["tools/, channels/ y schedules/ de Eve no se importan: las tools y automatizaciones se configuran en Studio", "Eve's tools/, channels/ and schedules/ aren't imported: tools and automations are configured in Studio"],
|
|
7543
|
+
["{}: demasiado grande (m\xE1x 10 MB)", "{}: too large (max 10 MB)"],
|
|
7544
|
+
// — MCP y cajas del agente (servers, config.yaml)
|
|
7545
|
+
["servers debe ser una lista", "servers must be a list"],
|
|
7546
|
+
["servers[{}]: no es un objeto", "servers[{}]: not an object"],
|
|
7547
|
+
["servers[{}]: name inv\xE1lido (a-z, 0-9, -, _)", "servers[{}]: invalid name (a-z, 0-9, -, _)"],
|
|
7548
|
+
["servers[{}]: hace falta url (http) o command (stdio)", "servers[{}]: url (http) or command (stdio) is required"],
|
|
7549
|
+
["m\xE1ximo 20 servidores", "max 20 servers"],
|
|
7550
|
+
["no se pudieron escribir las extensiones: {}", "couldn't write the extensions: {}"],
|
|
7551
|
+
["no se pudo escribir config.yaml: {}", "couldn't write config.yaml: {}"],
|
|
7552
|
+
// — Archivos del agente y de la cuenta
|
|
7553
|
+
["archivo demasiado grande (m\xE1x 10 MB)", "file too large (max 10 MB)"],
|
|
7554
|
+
["archivo vac\xEDo o >25MB", "empty file or >25MB"],
|
|
7555
|
+
["archivo vac\xEDo o de m\xE1s de 8 MB", "empty file or larger than 8 MB"],
|
|
7556
|
+
["el archivo pasa de 8 MB", "the file exceeds 8 MB"],
|
|
7557
|
+
["el archivo pesa {} y el m\xE1ximo son {}", "the file is {} and the max is {}"],
|
|
7558
|
+
["el archivo viene vac\xEDo", "the file is empty"],
|
|
7559
|
+
["el nombre del archivo no es utilizable", "the file name isn't usable"],
|
|
7560
|
+
["el objeto est\xE1 vac\xEDo", "the object is empty"],
|
|
7561
|
+
["el objeto {} ya no est\xE1 en el bucket", "object {} is no longer in the bucket"],
|
|
7562
|
+
["esta conversaci\xF3n ya tiene {} archivos y el m\xE1ximo son {}", "this conversation already has {} files and the max is {}"],
|
|
7563
|
+
["pesa {}; el m\xE1ximo son {}", "it's {}; the max is {}"],
|
|
7564
|
+
["pesa m\xE1s de {} MB", "larger than {} MB"],
|
|
7565
|
+
["no cabe: llevas {} de {} GB. Se ampl\xEDa en paquetes de 50 GB desde /planes.", "doesn't fit: you're using {} of {} GB. You can add 50 GB packs at /planes."],
|
|
7566
|
+
["no cabe: llevas {} de {} GB. Borra algo que ya no uses para hacer sitio.", "doesn't fit: you're using {} of {} GB. Delete something you no longer use to make room."],
|
|
7567
|
+
["no cabe: llevas {} de {} GB. {}", "doesn't fit: you're using {} of {} GB. {}"],
|
|
7568
|
+
["falta el par\xE1metro nombre", "missing the nombre parameter"],
|
|
7569
|
+
["el almacenamiento no est\xE1 configurado", "storage isn't configured"],
|
|
7570
|
+
["storage sin configurar (TIGRIS_*/STORAGE_*)", "storage not configured (TIGRIS_*/STORAGE_*)"],
|
|
7571
|
+
["storage sin configurar", "storage not configured"],
|
|
7572
|
+
["no es JPEG/PNG/WebP ({})", "not JPEG/PNG/WebP ({})"],
|
|
7573
|
+
["no se pudo bajar (respuesta vac\xEDa, no 2xx o m\xE1s de 10 MB)", "couldn't download (empty response, not 2xx or over 10 MB)"],
|
|
7574
|
+
["no se pudo bajar: {}", "couldn't download: {}"],
|
|
7575
|
+
// — Documentos
|
|
7576
|
+
["no hay un documento \xAB{}\xBB", `there's no document "{}"`],
|
|
7577
|
+
["manda url, o contentBase64 con name", "send url, or contentBase64 with name"],
|
|
7578
|
+
["faltan los bloques", "missing the blocks"],
|
|
7579
|
+
["este agente no tiene acceso a ese documento (mira doc_list)", "this agent has no access to that document (see doc_list)"],
|
|
7580
|
+
["ese documento es s\xF3lo para consultarlo, no para entregarlo", "that document is for reference only, not for sending"],
|
|
7581
|
+
// — Bases de datos
|
|
7582
|
+
["no hay una base \xAB{}\xBB", `there's no database "{}"`],
|
|
7583
|
+
["Ya tienes una base con ese nombre.", "You already have a database with that name."],
|
|
7584
|
+
["Tu plan incluye {} base{} de datos.", "Your plan includes {} database{}."],
|
|
7585
|
+
["El nombre lleva letras min\xFAsculas, n\xFAmeros, guiones o guiones bajos (m\xE1x. 64).", "The name takes lowercase letters, numbers, hyphens or underscores (max 64)."],
|
|
7586
|
+
["Nombre de tabla inv\xE1lido (letras, n\xFAmeros y _).", "Invalid table name (letters, numbers and _)."],
|
|
7587
|
+
["Nombre de tabla inv\xE1lido", "Invalid table name"],
|
|
7588
|
+
["nombres de tabla no v\xE1lidos: {}", "invalid table names: {}"],
|
|
7589
|
+
["nombres de tabla o columna inv\xE1lidos", "invalid table or column names"],
|
|
7590
|
+
["Tabla inv\xE1lida", "Invalid table"],
|
|
7591
|
+
["Tabla o columna inv\xE1lida", "Invalid table or column"],
|
|
7592
|
+
["Columna inv\xE1lida: {}", "Invalid column: {}"],
|
|
7593
|
+
["Columnas inv\xE1lidas", "Invalid columns"],
|
|
7594
|
+
["Una tabla necesita al menos una columna.", "A table needs at least one column."],
|
|
7595
|
+
["Sin valores", "No values"],
|
|
7596
|
+
["SQL vac\xEDo", "Empty SQL"],
|
|
7597
|
+
["falta sql", "missing sql"],
|
|
7598
|
+
["Aqu\xED s\xF3lo caben consultas SELECT", "Only SELECT queries are allowed here"],
|
|
7599
|
+
["S\xF3lo una sentencia por consulta", "Only one statement per query"],
|
|
7600
|
+
["S\xF3lo una sentencia por llamada", "Only one statement per call"],
|
|
7601
|
+
["La consulta intenta escribir; este acceso es de lectura", "The query tries to write; this access is read-only"],
|
|
7602
|
+
["Desde este canal s\xF3lo INSERT, UPDATE o DELETE", "From this channel only INSERT, UPDATE or DELETE"],
|
|
7603
|
+
["Desde este canal s\xF3lo puedes escribir en: {}", "From this channel you can only write to: {}"],
|
|
7604
|
+
["desde este canal no puedes leer \xAB{}\xBB", `from this channel you can't read "{}"`],
|
|
7605
|
+
["desde este canal no se puede usar el prefijo main.; consulta las tablas por su nombre", "from this channel you can't use the main. prefix; query tables by name"],
|
|
7606
|
+
["desde este canal s\xF3lo puedes agregar en \xAB{}\xBB; cambiar o borrar se hace desde el equipo", 'from this channel you can only add to "{}"; changing or deleting is done from the team'],
|
|
7607
|
+
["externalRowScope es { tabla: [columnas de tel\xE9fono] }", "externalRowScope is { table: [phone columns] }"],
|
|
7608
|
+
["externalWriteTables es una lista de tablas", "externalWriteTables is a list of tables"],
|
|
7609
|
+
["Cu\xE9ntame para qu\xE9 es la tabla.", "Tell me what the table is for."],
|
|
7610
|
+
["Dime qu\xE9 quieres consultar.", "Tell me what you want to query."],
|
|
7611
|
+
["No hay llave para pedir ayuda al modelo.", "There's no key to ask the model for help."],
|
|
7612
|
+
["El modelo no contest\xF3; int\xE9ntalo otra vez.", "The model didn't answer; try again."],
|
|
7613
|
+
["El modelo no contest\xF3 ({}).", "The model didn't answer ({})."],
|
|
7614
|
+
["El modelo no devolvi\xF3 SQL.", "The model returned no SQL."],
|
|
7615
|
+
["El modelo no propuso columnas; int\xE9ntalo con m\xE1s detalle.", "The model proposed no columns; try again with more detail."],
|
|
7616
|
+
["No parece un archivo .xlsx", "Doesn't look like an .xlsx file"],
|
|
7617
|
+
["No encontr\xE9 la hoja en el archivo", "I couldn't find the sheet in the file"],
|
|
7618
|
+
["el destino es la cuenta del original: usa un sufijo para no escribir en su base real {}", "the target is the original's account: use a suffix so you don't write to its real database {}"],
|
|
7619
|
+
["este agente no tiene acceso a esa base (mira db_list)", "this agent has no access to that database (see db_list)"],
|
|
7620
|
+
// importar de EasyBits y re-hospedar imágenes
|
|
7621
|
+
["from: s\xF3lo \xABeasybits\xBB por ahora", 'from: only "easybits" for now'],
|
|
7622
|
+
["falta sourceDb (id de la base en EasyBits)", "missing sourceDb (the database id in EasyBits)"],
|
|
7623
|
+
["falta easybitsKey", "missing easybitsKey"],
|
|
7624
|
+
["EasyBits rechaz\xF3 la llave (EASYBITS_API_KEY)", "EasyBits rejected the key (EASYBITS_API_KEY)"],
|
|
7625
|
+
["EasyBits no tiene la base {} (easybits db ls)", "EasyBits has no database {} (easybits db ls)"],
|
|
7626
|
+
["EasyBits no tiene: {}", "EasyBits doesn't have: {}"],
|
|
7627
|
+
["rehost: \xAB{}\xBB no es tabla.columna", `rehost: "{}" isn't table.column`],
|
|
7628
|
+
["{} tiene {} filas (m\xE1x. {})", "{} has {} rows (max {})"],
|
|
7629
|
+
["table y column: nombres v\xE1lidos", "table and column: valid names"],
|
|
7630
|
+
["{} no tiene la columna {}", "{} has no column {}"],
|
|
7631
|
+
["{} no tiene la tabla {}", "{} has no table {}"],
|
|
7632
|
+
["no parece un sqlite", "doesn't look like a sqlite file"],
|
|
7633
|
+
// — Tableros, conversaciones y CRM (moveCards, boards)
|
|
7634
|
+
["el tablero principal no se borra", "the main board can't be deleted"],
|
|
7635
|
+
["tiene leads: mu\xE9velos o arch\xEDvalos antes de borrarlo", "it has leads: move or archive them before deleting it"],
|
|
7636
|
+
["no hay un tablero \xAB{}\xBB en {} (ghosty boards ls {})", `there's no board "{}" in {} (ghosty boards ls {})`],
|
|
7637
|
+
["no hay un tablero \xAB{}\xBB en {}", `there's no board "{}" in {}`],
|
|
7638
|
+
["ya hay un tablero \xAB{}\xBB en {}", `there's already a board "{}" in {}`],
|
|
7639
|
+
["falta name", "missing name"],
|
|
7640
|
+
["no pude crear el tablero de {}", "I couldn't create the board for {}"],
|
|
7641
|
+
["ese tablero no es del espacio del agente", "that board isn't in the agent's workspace"],
|
|
7642
|
+
["ese tablero ya no existe", "that board no longer exists"],
|
|
7643
|
+
["ese tablero no existe o ya caduc\xF3", "that board doesn't exist or has expired"],
|
|
7644
|
+
["ese tablero no lo armaste t\xFA", "you didn't build that board"],
|
|
7645
|
+
["los tableros viven en un espacio de equipo, no en la cuenta", "boards live in a team workspace, not in the account"],
|
|
7646
|
+
["los tableros viven en un espacio de equipo; este agente es personal", "boards live in a team workspace; this agent is personal"],
|
|
7647
|
+
["espacio sin due\xF1o: {}", "workspace without an owner: {}"],
|
|
7648
|
+
["espacio sin due\xF1o", "workspace without an owner"],
|
|
7649
|
+
["from y to son el mismo tablero", "from and to are the same board"],
|
|
7650
|
+
["from y to: tableros de {} (id, nombre o main)", "from and to: boards in {} (id, name or main)"],
|
|
7651
|
+
["hace falta al menos un filtro: integration, column o before", "at least one filter is required: integration, column or before"],
|
|
7652
|
+
["hace falta un filtro: integration, phone o all", "a filter is required: integration, phone or all"],
|
|
7653
|
+
["phone: al menos 7 d\xEDgitos", "phone: at least 7 digits"],
|
|
7654
|
+
["no hay una conversaci\xF3n \xAB{}\xBB en el tablero {}", `there's no conversation "{}" on board {}`],
|
|
7655
|
+
["\xAB{}\xBB coincide con {} conversaciones: usa el folio o el id", '"{}" matches {} conversations: use the folio or the id'],
|
|
7656
|
+
["columna inv\xE1lida", "invalid column"],
|
|
7657
|
+
["columnas inv\xE1lidas", "invalid columns"],
|
|
7658
|
+
["direcci\xF3n inv\xE1lida", "invalid address"],
|
|
7659
|
+
["tags inv\xE1lidas", "invalid tags"],
|
|
7660
|
+
["pausa inv\xE1lida", "invalid pause"],
|
|
7661
|
+
["el nombre es obligatorio", "name is required"],
|
|
7662
|
+
["falta conversationId", "missing conversationId"],
|
|
7663
|
+
["intent desconocido: {}", "unknown intent: {}"],
|
|
7664
|
+
["un pedido necesita columna", "an order needs a column"],
|
|
7665
|
+
// — WhatsApp: ligar números, importar sesiones (importAgentWhatsApp) y grupos
|
|
7666
|
+
["answer: true (contesta a clientes reales) o false (registra sin contestar); no hay default", "answer: true (replies to real customers) or false (records without replying); there's no default"],
|
|
7667
|
+
["integrationId: el id de la Integration de Formmy (24 hex)", "integrationId: the Formmy Integration id (24 hex)"],
|
|
7668
|
+
["ese n\xFAmero no est\xE1 ligado a este agente: ghosty channels whatsapp link {} --integration {} --answer on|off", "that number isn't linked to this agent: ghosty channels whatsapp link {} --integration {} --answer on|off"],
|
|
7669
|
+
["ese n\xFAmero ya est\xE1 ligado en gs a otra cuenta o espacio", "that number is already linked in gs to another account or workspace"],
|
|
7670
|
+
["este n\xFAmero no est\xE1 ligado en gs", "this number isn't linked in gs"],
|
|
7671
|
+
["el n\xFAmero no tiene espacio ni tablero", "the number has no workspace or board"],
|
|
7672
|
+
["s\xF3lo el due\xF1o, un admin del agente o staff mueven un n\xFAmero", "only the owner, an agent admin or staff can move a number"],
|
|
7673
|
+
["s\xF3lo el due\xF1o, un admin del agente o staff importan una sesi\xF3n de WhatsApp", "only the owner, an agent admin or staff can import a WhatsApp session"],
|
|
7674
|
+
["esta caja de gs no es la activa (deploy en curso): reintenta en un minuto", "this gs box isn't the active one (deploy in progress): try again in a minute"],
|
|
7675
|
+
["WhatsApp rechaz\xF3 la sesi\xF3n (logout): hay que volver a parear", "WhatsApp rejected the session (logout): it has to be paired again"],
|
|
7676
|
+
["WhatsApp no est\xE1 conectado", "WhatsApp isn't connected"],
|
|
7677
|
+
["este agente tiene WhatsApp conectado en gs: p\xE1usalo primero (DELETE \u2026/wa, sin olvidar)", "this agent has WhatsApp connected in gs: pause it first (DELETE \u2026/wa, without forgetting)"],
|
|
7678
|
+
["este agente ya tiene una sesi\xF3n guardada{}: usa replace para reemplazarla", "this agent already has a saved session{}: use replace to replace it"],
|
|
7679
|
+
["su WhatsApp en gs est\xE1 \xAB{}\xBB: p\xE1usalo antes de importar", 'its WhatsApp in gs is "{}": pause it before importing'],
|
|
7680
|
+
["authCreds no parece una sesi\xF3n de Baileys (faltan noiseKey / signedIdentityKey)", "authCreds doesn't look like a Baileys session (missing noiseKey / signedIdentityKey)"],
|
|
7681
|
+
["la sesi\xF3n nunca termin\xF3 de parearse (authCreds.me.id vac\xEDo)", "the session never finished pairing (authCreds.me.id is empty)"],
|
|
7682
|
+
["el archivo dice +{} pero las credenciales son de +{}", "the file says +{} but the credentials are for +{}"],
|
|
7683
|
+
["faltan authKeys", "missing authKeys"],
|
|
7684
|
+
["falta ownNumber: \xBFel n\xFAmero es del agente (true) o de una persona (false)?", "missing ownNumber: is the number the agent's (true) or a person's (false)?"],
|
|
7685
|
+
["grupo con jid inv\xE1lido: {}", "group with invalid jid: {}"],
|
|
7686
|
+
["falta jid", "missing jid"],
|
|
7687
|
+
["olvidar la sesi\xF3n de WhatsApp lo hace una persona desde el panel", "forgetting the WhatsApp session is done by a person from the dashboard"],
|
|
7688
|
+
["no pude obtener el enlace del grupo", "I couldn't get the group link"],
|
|
7689
|
+
["name + 2 opciones m\xEDnimo", "name + at least 2 options"],
|
|
7690
|
+
["emoji + un mensaje reciente", "emoji + a recent message"],
|
|
7691
|
+
["text o url requerido", "text or url required"],
|
|
7692
|
+
["sesi\xF3n desconocida", "unknown session"],
|
|
7693
|
+
["este turno no viene de tu WhatsApp personal", "this turn doesn't come from your personal WhatsApp"],
|
|
7694
|
+
["ya hay una respuesta en camino", "there's already a reply on its way"],
|
|
7695
|
+
// — Probar al agente (try) y turnos
|
|
7696
|
+
["falta text", "missing text"],
|
|
7697
|
+
["asFormmy va con as=waba y media", "asFormmy goes with as=waba and media"],
|
|
7698
|
+
["con as={}, session es el tel\xE9fono (10-15 d\xEDgitos)", "with as={}, session is the phone number (10-15 digits)"],
|
|
7699
|
+
["media: { url, mime?, filename?, type? } con url https", "media: { url, mime?, filename?, type? } with an https url"],
|
|
7700
|
+
['history: [{ role: "user"|"agent", text, at? }] (m\xE1x. 200)', 'history: [{ role: "user"|"agent", text, at? }] (max 200)'],
|
|
7701
|
+
["este hilo tiene un turno en curso pedido por otra persona", "this thread has a turn in progress requested by someone else"],
|
|
7702
|
+
["gs se reinici\xF3 a mitad del turno", "gs restarted mid-turn"],
|
|
7703
|
+
["el turno fall\xF3", "the turn failed"],
|
|
7704
|
+
["turno sin terminar tras {} ms", "turn unfinished after {} ms"],
|
|
7705
|
+
["steer sin respuesta", "steer got no response"],
|
|
7706
|
+
["el agente dej\xF3 de responder; turno cerrado", "the agent stopped responding; turn closed"],
|
|
7707
|
+
["el agente no devolvi\xF3 sessionId", "the agent returned no sessionId"],
|
|
7708
|
+
["el agente no pudo contestar; intenta de nuevo", "the agent couldn't answer; try again"],
|
|
7709
|
+
["la caja cerr\xF3 la conexi\xF3n a media respuesta", "the box closed the connection mid-response"],
|
|
7710
|
+
["la conversaci\xF3n se enfri\xF3; \xE1brela de nuevo para seguir", "the conversation went cold; open it again to continue"],
|
|
7711
|
+
["tu agente ya atiende todas las conversaciones que puede a la vez; cierra alguna o espera a que termine", "your agent is already handling as many conversations as it can at once; close one or wait for one to finish"],
|
|
7712
|
+
["no se pudo firmar el ticket de la caja de este agente", "couldn't sign this agent's box ticket"],
|
|
7713
|
+
["{}: sin respuesta en {} ms", "{}: no response in {} ms"],
|
|
7714
|
+
["Seguimos llenos tras {}s de espera. Tu mensaje NO se perdi\xF3: vuelve a enviarlo en un momento.", "Still full after {}s of waiting. Your message was NOT lost: send it again in a moment."],
|
|
7715
|
+
["admisi\xF3n: {}", "admission: {}"],
|
|
7716
|
+
["sin noticias del encargo en 45 min", "no news from the task in 45 min"],
|
|
7717
|
+
["sin cierre: se dio por perdido", "never closed: given up as lost"],
|
|
7718
|
+
["sin cierre: se perdi\xF3 el rastro del montaje (la caja pudo reciclarse)", "never closed: the video job's trace was lost (the box may have been recycled)"],
|
|
7719
|
+
// chat de la app (/me): conversaciones, archivos y docs
|
|
7720
|
+
["falta el contenido", "missing the content"],
|
|
7721
|
+
["falta el modelo", "missing the model"],
|
|
7722
|
+
["falta el t\xEDtulo", "missing the title"],
|
|
7723
|
+
["falta id u optionId", "missing id or optionId"],
|
|
7724
|
+
// — Turnos programados
|
|
7725
|
+
["dueAt no es una fecha", "dueAt isn't a date"],
|
|
7726
|
+
["falta qu\xE9 hacer", "missing what to do"],
|
|
7727
|
+
["cada {} min como m\xEDnimo", "at least every {} min"],
|
|
7728
|
+
["`until` como mucho a 30 d\xEDas", "`until` at most 30 days out"],
|
|
7729
|
+
["como mucho a 30 d\xEDas", "at most 30 days out"],
|
|
7730
|
+
["una repetici\xF3n necesita `until`", "a recurrence needs `until`"],
|
|
7731
|
+
["este agente ya tiene {} turnos pendientes", "this agent already has {} pending turns"],
|
|
7732
|
+
["ya hay {} pendientes en esta conversaci\xF3n", "there are already {} pending in this conversation"],
|
|
7733
|
+
["ya programaste {} turnos hoy; p\xEDdele al usuario que programe el resto", "you already scheduled {} turns today; ask the user to schedule the rest"],
|
|
7734
|
+
// — Saldo, planes y cuotas
|
|
7735
|
+
["Se acab\xF3 el saldo de este agente ({}). Se reinicia el {}. Los dem\xE1s agentes del espacio siguen con el suyo.", "This agent's balance ran out ({}). It resets on {}. The other agents in the workspace keep their own."],
|
|
7736
|
+
["Se acab\xF3 la bolsa \xAB{}\xBB ({}), que este agente comparte con otros {}. Se reinicia el {}. Los agentes con otra bolsa siguen trabajando.", 'The "{}" pool ran out ({}), which this agent shares with {} others. It resets on {}. Agents on another pool keep working.'],
|
|
7737
|
+
["Se acab\xF3 tu sesi\xF3n. Vuelve a las {}.", "Your session limit is used up. Come back at {}."],
|
|
7738
|
+
["Se acab\xF3 tu uso de este modelo esta semana. Cambia a otro modelo para seguir.", "You've used up this model for the week. Switch to another model to continue."],
|
|
7739
|
+
["Se acab\xF3 tu uso de esta semana. Se renueva el {}.", "You've used up this week's usage. It renews on {}."],
|
|
7740
|
+
["Se acab\xF3 tu uso de este mes. Se renueva el {}.", "You've used up this month's usage. It renews on {}."],
|
|
7741
|
+
["Se acab\xF3 tu uso {}. Se renueva el {}.", "You've used up your usage {}. It renews on {}."],
|
|
7742
|
+
["Ese modelo no est\xE1 disponible en tu plan. Cambia de modelo para seguir.", "That model isn't available on your plan. Switch models to continue."],
|
|
7743
|
+
["Tu periodo de prueba termin\xF3 el {}. Escr\xEDbenos para seguir.", "Your trial period ended on {}. Write to us to continue."],
|
|
7744
|
+
["Este agente corre con Claude y no tiene llave. Conecta tu cuenta de Claude en ghosty.studio \u2192 Credenciales, o c\xE1mbialo a otro motor en Ajustes \u2192 Agentes.", "This agent runs on Claude and has no key. Connect your Claude account in ghosty.studio \u2192 Credentials, or switch it to another engine in Settings \u2192 Agents."],
|
|
7745
|
+
["Este agente de regalo se qued\xF3 sin llave. Av\xEDsale a quien te lo comparti\xF3.", "This shared agent has no key anymore. Let whoever shared it with you know."],
|
|
7746
|
+
["{} corre s\xF3lo con la llave de OpenAI del due\xF1o del espacio, y todav\xEDa no hay ninguna. P\xE9gala en ghosty.studio \u2192 Credenciales \u2192 OpenAI (o cambia el agente a un modelo de Claude en Ajustes \u2192 Agentes).", "{} only runs with the workspace owner's OpenAI key, and there isn't one yet. Paste it in ghosty.studio \u2192 Credentials \u2192 OpenAI (or switch the agent to a Claude model in Settings \u2192 Agents)."],
|
|
7747
|
+
["sin saldo", "no balance"],
|
|
7748
|
+
["cache_read_input_tokens y cache_creation_input_tokens deben ser n\xFAmeros", "cache_read_input_tokens and cache_creation_input_tokens must be numbers"],
|
|
7749
|
+
["suffixes requerido", "suffixes required"],
|
|
7750
|
+
["{}MB de {}MB de snapshots ({}%), tope de altas al {}%", "{}MB of {}MB of snapshots ({}%), sign-up cap at {}%"],
|
|
7751
|
+
["el host no reporta el disco de snapshots", "the host doesn't report the snapshots disk"],
|
|
7752
|
+
["el proveedor no reporta capacidad", "the provider doesn't report capacity"],
|
|
7753
|
+
["no se pudo medir: {}", "couldn't measure: {}"],
|
|
7754
|
+
// — Credenciales y vault
|
|
7755
|
+
["proveedor desconocido: {}", "unknown provider: {}"],
|
|
7756
|
+
["falta key", "missing key"],
|
|
7757
|
+
["el vault no est\xE1 configurado en este servidor", "the vault isn't configured on this server"],
|
|
7758
|
+
["El .env no puede ir vac\xEDo.", "The .env can't be empty."],
|
|
7759
|
+
['El repositorio va como "due\xF1o/repo".', 'The repository goes as "owner/repo".'],
|
|
7760
|
+
// — Partners (Deník y compañía) y Formmy
|
|
7761
|
+
["falta workspace (el slug de tu espacio)", "missing workspace (your workspace's slug)"],
|
|
7762
|
+
["espacio no encontrado", "workspace not found"],
|
|
7763
|
+
["el espacio {} no es partner (act\xEDvalo con: ghosty partner enable {})", "workspace {} isn't a partner (enable it with: ghosty partner enable {})"],
|
|
7764
|
+
["no tienes espacios de partner (act\xEDvalo con: ghosty partner enable <espacio>)", "you have no partner workspaces (enable one with: ghosty partner enable <workspace>)"],
|
|
7765
|
+
["tienes {} espacios de partner: di cu\xE1l con --workspace ({})", "you have {} partner workspaces: pick one with --workspace ({})"],
|
|
7766
|
+
["el partner est\xE1 apagado", "the partner is turned off"],
|
|
7767
|
+
["credencial revocada: emite una nueva", "credential revoked: issue a new one"],
|
|
7768
|
+
["m\xE1ximo {} credenciales activas: revoca una antes", "max {} active credentials: revoke one first"],
|
|
7769
|
+
["el partner a\xFAn no tiene agente (PUT /api/v2/partner/agent)", "the partner has no agent yet (PUT /api/v2/partner/agent)"],
|
|
7770
|
+
["el partner a\xFAn no tiene agente (PUT para crearlo)", "the partner has no agent yet (PUT to create it)"],
|
|
7771
|
+
["el partner a\xFAn no tiene agente", "the partner has no agent yet"],
|
|
7772
|
+
["el borrador es igual a la versi\xF3n vigente", "the draft is the same as the current version"],
|
|
7773
|
+
["no existe la versi\xF3n {}", "version {} doesn't exist"],
|
|
7774
|
+
["config debe ser un objeto", "config must be an object"],
|
|
7775
|
+
["config.timezone inv\xE1lida: {}", "invalid config.timezone: {}"],
|
|
7776
|
+
["config.{} no existe ({})", "config.{} doesn't exist ({})"],
|
|
7777
|
+
["mcp debe ser una lista", "mcp must be a list"],
|
|
7778
|
+
["m\xE1ximo 5 servidores MCP", "max 5 MCP servers"],
|
|
7779
|
+
["mcp[{}]: headers debe ser un objeto", "mcp[{}]: headers must be an object"],
|
|
7780
|
+
["mcp[{}]: header {} inv\xE1lido", "mcp[{}]: invalid header {}"],
|
|
7781
|
+
["mcp[{}]: name inv\xE1lido o repetido", "mcp[{}]: invalid or repeated name"],
|
|
7782
|
+
["mcp[{}]: placeholder desconocido {} (usa {})", "mcp[{}]: unknown placeholder {} (use {})"],
|
|
7783
|
+
["mcp[{}]: s\xF3lo servidores HTTP (url); stdio no existe en modo solo-MCP", "mcp[{}]: only HTTP servers (url); stdio doesn't exist in MCP-only mode"],
|
|
7784
|
+
["motor {} no corre en modo solo-MCP (hoy: {})", "engine {} doesn't run in MCP-only mode (today: {})"],
|
|
7785
|
+
["motor {} sin proveedor solo-MCP", "engine {} has no MCP-only provider"],
|
|
7786
|
+
["{} no entra en el plan del espacio (tope {}\xD7)", "{} isn't in the workspace's plan (cap {}\xD7)"],
|
|
7787
|
+
["externalId inv\xE1lido (1-128: letras, d\xEDgitos, . _ -)", "invalid externalId (1-128: letters, digits, . _ -)"],
|
|
7788
|
+
["tenant.externalId inv\xE1lido", "invalid tenant.externalId"],
|
|
7789
|
+
["tenant no existe", "tenant doesn't exist"],
|
|
7790
|
+
["groupId inv\xE1lido", "invalid groupId"],
|
|
7791
|
+
["groupId requerido", "groupId required"],
|
|
7792
|
+
["text requerido", "text required"],
|
|
7793
|
+
["este negocio est\xE1 suspendido", "this business is suspended"],
|
|
7794
|
+
["sin llave de {} para este espacio", "no {} key for this workspace"],
|
|
7795
|
+
["FORMMY_PARTNER_SECRET_GHOSTY ausente: no se puede hablar con Formmy", "FORMMY_PARTNER_SECRET_GHOSTY missing: can't talk to Formmy"],
|
|
7796
|
+
["Formmy respondi\xF3 {}", "Formmy responded {}"],
|
|
7797
|
+
// — Demos
|
|
7798
|
+
["falta la liga (slug) la primera vez", "missing the link (slug) the first time"],
|
|
7799
|
+
["el nombre de la liga va en min\xFAsculas, n\xFAmeros y guiones (3 a 40)", "the link name takes lowercase letters, numbers and hyphens (3 to 40)"],
|
|
7800
|
+
["esa liga ya la usa otro agente", "that link is already used by another agent"],
|
|
7801
|
+
["este demo ya no est\xE1 disponible", "this demo is no longer available"],
|
|
7802
|
+
["por ahora el demo s\xF3lo corre con motores de pool (Claude, Codex\u2026), no con ghosty-lite/goose", "for now demos only run on pool engines (Claude, Codex\u2026), not on ghosty-lite/goose"],
|
|
7803
|
+
// — Cajas de trabajo, puertos y ACP
|
|
7804
|
+
["Ahora mismo no hay capacidad para otra caja. Reintenta en unos minutos o suelta alguna con destroy.", "There's no capacity for another box right now. Try again in a few minutes or release one with destroy."],
|
|
7805
|
+
["Esa caja no existe o no es tuya. Lista las tuyas con list y usa un id de ah\xED.", "That box doesn't exist or isn't yours. List yours with list and use an id from there."],
|
|
7806
|
+
["Ya tienes {} cajas de trabajo (tope {}). Suelta una con destroy \u2014 las de tu lista: {}.", "You already have {} work boxes (limit {}). Release one with destroy \u2014 the ones on your list: {}."],
|
|
7807
|
+
["Template desconocido. Los que puedes pedir: {}.", "Unknown template. The ones you can ask for: {}."],
|
|
7808
|
+
["Tama\xF1o desconocido. Elige: {}.", "Unknown size. Choose: {}."],
|
|
7809
|
+
["La caja no termin\xF3 de despertar. Reintenta en unos segundos.", "The box didn't finish waking up. Try again in a few seconds."],
|
|
7810
|
+
["command requerido", "command required"],
|
|
7811
|
+
["Puerto inv\xE1lido: va entre 1024 y 65535, y los puertos {} (runtime de la caja) y {} (sistema) no se publican \u2014 levanta el server en otro (5173, 4321, 8080\u2026).", "Invalid port: it must be between 1024 and 65535, and ports {} (box runtime) and {} (system) aren't published \u2014 run the server on another one (5173, 4321, 8080\u2026)."],
|
|
7812
|
+
["Puerto inv\xE1lido: va entre 1024 y 65535.", "Invalid port: it must be between 1024 and 65535."],
|
|
7813
|
+
["El puerto {} es del sistema de la caja y no se publica. Usa el de tu app.", "Port {} belongs to the box's system and isn't published. Use your app's port."],
|
|
7814
|
+
["Esta conversaci\xF3n no tiene caja viva todav\xEDa; corre algo primero y reintenta.", "This conversation has no live box yet; run something first and try again."],
|
|
7815
|
+
["La caja de esta conversaci\xF3n ya se recicl\xF3; corre cualquier comando primero (eso la revive) y reintenta.", "This conversation's box was already recycled; run any command first (that revives it) and try again."],
|
|
7816
|
+
["Los subagentes corren con el Claude de Ghosty y este espacio no lo tiene habilitado. Haz el trabajo en este mismo turno.", "Subagents run on Ghosty's Claude and this workspace doesn't have it enabled. Do the work in this same turn."],
|
|
7817
|
+
["L\xEDmite de subagentes concurrentes alcanzado, intenta en un momento.", "Concurrent subagent limit reached, try again in a moment."],
|
|
7818
|
+
["prompt requerido", "prompt required"],
|
|
7819
|
+
["el motor {} no es ACP: no tiene caja que gobernar aqu\xED", "engine {} isn't ACP: there's no box to manage here"],
|
|
7820
|
+
["el proveedor de cajas no soporta agent/start: no puede levantar un rel\xE9 ACP", "the box provider doesn't support agent/start: it can't bring up an ACP relay"],
|
|
7821
|
+
["falta ACP_TICKET_SECRET en el env de gs: sin \xE9l la caja ACP no autentica a nadie", "ACP_TICKET_SECRET is missing from gs's env: without it the ACP box authenticates no one"],
|
|
7822
|
+
["no puedo resolver el workspace de {}: sin ACP_TENANT la caja aceptar\xEDa tickets de otros clientes", "I can't resolve the workspace for {}: without ACP_TENANT the box would accept tickets from other customers"],
|
|
7823
|
+
["la caja ACP {} arranc\xF3 en error: {}", "ACP box {} started in error: {}"],
|
|
7824
|
+
["la caja ACP {} no lleg\xF3 a running en 90s", "ACP box {} didn't reach running in 90s"],
|
|
7825
|
+
["la caja {} no lleg\xF3 a running (\xFAltimo estado: {})", "box {} didn't reach running (last state: {})"],
|
|
7826
|
+
["la caja del agente naci\xF3 muda (no arranc\xF3 {}-runtime): {}", "the agent's box came up mute ({}-runtime didn't start): {}"],
|
|
7827
|
+
["no se pudo re-rutear tras caja muerta", "couldn't re-route after a dead box"],
|
|
7828
|
+
["fleet route race sin ganador", "fleet route race with no winner"],
|
|
7829
|
+
["create: respuesta sin sandboxId", "create: response without sandboxId"],
|
|
7830
|
+
["warmpool.claim: respuesta sin sandboxId", "warmpool.claim: response without sandboxId"],
|
|
7831
|
+
["expose: respuesta sin url", "expose: response without url"],
|
|
7832
|
+
["expose-raw: respuesta sin hostPort", "expose-raw: response without hostPort"],
|
|
7833
|
+
// — Cajas compartidas: voz, render, video, ortografía, eventos
|
|
7834
|
+
["sin caja de eventos", "no events box"],
|
|
7835
|
+
["sin caja de llamadas", "no calls box"],
|
|
7836
|
+
["sin caja de ortograf\xEDa", "no spelling box"],
|
|
7837
|
+
["sin caja de render", "no render box"],
|
|
7838
|
+
["sin caja de transcripci\xF3n", "no transcription box"],
|
|
7839
|
+
["sin caja de video", "no video box"],
|
|
7840
|
+
["sin caja de voz", "no voice box"],
|
|
7841
|
+
["audio requerido", "audio required"],
|
|
7842
|
+
["audio vac\xEDo", "empty audio"],
|
|
7843
|
+
["base64 inv\xE1lido", "invalid base64"],
|
|
7844
|
+
["texto requerido", "text required"],
|
|
7845
|
+
["html requerido", "html required"],
|
|
7846
|
+
["file requerido", "file required"],
|
|
7847
|
+
["room requerido", "room required"],
|
|
7848
|
+
["jobId requerido", "jobId required"],
|
|
7849
|
+
["montaje desconocido", "unknown video job"],
|
|
7850
|
+
["clips requerido: [{name, url}]", "clips required: [{name, url}]"],
|
|
7851
|
+
["captions: falta segments [{start, end, text}] (de stt.transcribe)", "captions: missing segments [{start, end, text}] (from stt.transcribe)"],
|
|
7852
|
+
["captions: falta url https del video", "captions: missing the video's https url"],
|
|
7853
|
+
["video-download: falta url https del video (LinkedIn, Facebook, Instagram, YouTube, Mux, Vimeo, Wistia, Loom o un .mp4 directo)", "video-download: missing the video's https url (LinkedIn, Facebook, Instagram, YouTube, Mux, Vimeo, Wistia, Loom or a direct .mp4)"],
|
|
7854
|
+
["youtube-audio: falta url (video o playlist), queries (canciones) o query (b\xFAsqueda)", "youtube-audio: missing url (video or playlist), queries (songs) or query (search)"],
|
|
7855
|
+
["youtube-audio: s\xF3lo links de youtube.com / youtu.be", "youtube-audio: only youtube.com / youtu.be links"],
|
|
7856
|
+
// — Meta Ads y Messenger
|
|
7857
|
+
["Falta configurar la app de Meta Ads en gs.", "The Meta Ads app isn't configured in gs yet."],
|
|
7858
|
+
["Fall\xF3 la llamada a Meta; intenta de nuevo.", "The call to Meta failed; try again."],
|
|
7859
|
+
["Este espacio no tiene Meta conectado: con\xE9ctalo en Ajustes \u2192 Apps \u2192 Ghosty Ads.", "This workspace has no Meta connected: connect it in Settings \u2192 Apps \u2192 Ghosty Ads."],
|
|
7860
|
+
["Elige la cuenta de anuncios en Ajustes \u2192 Apps \u2192 Ghosty Ads.", "Choose the ad account in Settings \u2192 Apps \u2192 Ghosty Ads."],
|
|
7861
|
+
["Elige la p\xE1gina en Ajustes \u2192 Apps \u2192 Ghosty Ads.", "Choose the page in Settings \u2192 Apps \u2192 Ghosty Ads."],
|
|
7862
|
+
["Esa campa\xF1a no es de la cuenta conectada.", "That campaign doesn't belong to the connected account."],
|
|
7863
|
+
["Esa cuenta de anuncios no est\xE1 entre las tuyas.", "That ad account isn't one of yours."],
|
|
7864
|
+
["Esa p\xE1gina no est\xE1 entre las tuyas.", "That page isn't one of yours."],
|
|
7865
|
+
["Esa p\xE1gina ya est\xE1 conectada en otra cuenta.", "That page is already connected in another account."],
|
|
7866
|
+
["El id de la p\xE1gina son s\xF3lo d\xEDgitos (est\xE1 en la URL de Meta Business o en \xABAcerca de\xBB).", `The page id is digits only (it's in the Meta Business URL or under "About").`],
|
|
7867
|
+
["El link del anuncio tiene que ser https.", "The ad link must be https."],
|
|
7868
|
+
["El presupuesto diario m\xEDnimo es de $20.", "The minimum daily budget is $20."],
|
|
7869
|
+
["Deja al menos una ubicaci\xF3n.", "Keep at least one placement."],
|
|
7870
|
+
["Faltan el texto o el creativo del anuncio.", "The ad text or creative is missing."],
|
|
7871
|
+
["La campa\xF1a no tiene conjuntos.", "The campaign has no ad sets."],
|
|
7872
|
+
["La columna derecha s\xF3lo acepta imagen.", "The right column only accepts images."],
|
|
7873
|
+
["Un anuncio de columna derecha s\xF3lo sale en Facebook.", "A right-column ad only runs on Facebook."],
|
|
7874
|
+
["La fecha de fin tiene que ser a futuro.", "The end date must be in the future."],
|
|
7875
|
+
["La fecha de fin tiene que ser al menos ma\xF1ana.", "The end date must be at least tomorrow."],
|
|
7876
|
+
["Llegamos al tope de cambios por hora en esta cuenta (protege la cuenta ante Meta). Intenta en un rato.", "We hit the hourly change limit on this account (it protects the account with Meta). Try again in a while."],
|
|
7877
|
+
["Meta no acept\xF3 el token: {}", "Meta didn't accept the token: {}"],
|
|
7878
|
+
["Meta no devolvi\xF3 la imagen subida.", "Meta didn't return the uploaded image."],
|
|
7879
|
+
["Meta no devolvi\xF3 token", "Meta returned no token"],
|
|
7880
|
+
["Meta no pudo procesar el video: revisa que sea un mp4 p\xFAblico.", "Meta couldn't process the video: check that it's a public mp4."],
|
|
7881
|
+
["Meta sigue procesando el video: vuelve a intentar en un minuto.", "Meta is still processing the video: try again in a minute."],
|
|
7882
|
+
["No pude descargar la imagen del creativo: revisa que la URL sea p\xFAblica.", "I couldn't download the creative's image: check that the URL is public."],
|
|
7883
|
+
["No pude suscribir la p\xE1gina al webhook: {}", "I couldn't subscribe the page to the webhook: {}"],
|
|
7884
|
+
// — Ajustes de plataforma (staff)
|
|
7885
|
+
["Campo desconocido \xAB{}\xBB en {}.", 'Unknown field "{}" in {}.'],
|
|
7886
|
+
["El ajuste de {} no es un objeto.", "The setting for {} is not an object."],
|
|
7887
|
+
["Plan inv\xE1lido en {}.", "Invalid plan in {}."],
|
|
7888
|
+
["imageQuality inv\xE1lida en {}.", "invalid imageQuality in {}."],
|
|
7889
|
+
["imagesPlan inv\xE1lido en {}.", "invalid imagesPlan in {}."],
|
|
7890
|
+
["Tiene que ser un objeto {correo: ajuste}.", "Must be an object {email: setting}."],
|
|
7891
|
+
["Tiene que ser un objeto {\u2026}", "Must be an object {\u2026}"],
|
|
7892
|
+
["flags tiene que ser un objeto {nombre: true|false}", "flags must be an object {name: true|false}"],
|
|
7893
|
+
["flags.{} tiene que ser true o false", "flags.{} must be true or false"],
|
|
7894
|
+
["minBuild tiene que ser un entero \u2265 0", "minBuild must be an integer \u2265 0"],
|
|
7895
|
+
["suggestBuild tiene que ser un entero \u2265 0", "suggestBuild must be an integer \u2265 0"],
|
|
7896
|
+
["\xAB{}\xBB no parece correo.", `"{}" doesn't look like an email.`],
|
|
7897
|
+
// — Tools del agente (tools/dispatch): los lee el modelo, pero también pueden salir por el CLI
|
|
7898
|
+
["Pon en tu respuesta: [Nota de voz]({})", "Put in your reply: [Nota de voz]({})"],
|
|
7899
|
+
["Pon en tu respuesta: ", "Put in your reply: "],
|
|
7900
|
+
["Pon en tu respuesta: [{}]({})", "Put in your reply: [{}]({})"],
|
|
7901
|
+
["La URL caduca en 6 h. B\xE1jalo y l\xE9elo con tus skills de lectura.", "The URL expires in 6 h. Download it and read it with your reading skills."],
|
|
7902
|
+
["Tarjeta creada ({}), pero {}.", "Card created ({}), but {}."],
|
|
7903
|
+
["{}. Lo dem\xE1s s\xED se guard\xF3.", "{}. Everything else was saved."],
|
|
7904
|
+
["Ya avis\xE9 al equipo y dej\xE9 de contestar aqu\xED; desp\xEDdete diciendo que una persona lo atiende en breve.", "I've alerted the team and stopped replying here; sign off saying a person will help them shortly."],
|
|
7905
|
+
["Ya la escuch\xF3; no repitas lo que dijiste por texto.", "They already heard it; don't repeat what you said in text."],
|
|
7906
|
+
["Ya lo tiene; no repitas el enlace.", "They already have it; don't repeat the link."],
|
|
7907
|
+
["WhatsApp no acept\xF3 la nota de voz; contesta por texto", "WhatsApp didn't accept the voice note; reply in text"],
|
|
7908
|
+
["no se pudo generar la voz; contesta por texto", "couldn't generate the voice; reply in text"],
|
|
7909
|
+
["no se mand\xF3: {}", "not sent: {}"],
|
|
7910
|
+
["`emoji`: un emoji", "`emoji`: one emoji"],
|
|
7911
|
+
["`para` no es una direcci\xF3n v\xE1lida", "`para` isn't a valid address"],
|
|
7912
|
+
["`responder_a` no es una direcci\xF3n v\xE1lida", "`responder_a` isn't a valid address"],
|
|
7913
|
+
["`pausa` inv\xE1lida", "invalid `pausa`"],
|
|
7914
|
+
["falta `nombre`", "missing `nombre`"],
|
|
7915
|
+
["falta `que`", "missing `que`"],
|
|
7916
|
+
["falta `tarjeta` (o `tarjetas`)", "missing `tarjeta` (or `tarjetas`)"],
|
|
7917
|
+
["falta `texto`", "missing `texto`"],
|
|
7918
|
+
["falta `titulo`", "missing `titulo`"],
|
|
7919
|
+
["faltan `asunto` o `cuerpo`", "missing `asunto` or `cuerpo`"],
|
|
7920
|
+
["falta base64", "missing base64"],
|
|
7921
|
+
["falta el texto", "missing the text"],
|
|
7922
|
+
["manda al menos un rengl\xF3n en `items`", "send at least one row in `items`"],
|
|
7923
|
+
["manda al menos una columna", "send at least one column"],
|
|
7924
|
+
["monto inv\xE1lido", "invalid amount"],
|
|
7925
|
+
["el cuerpo pasa de 20,000 caracteres", "the body exceeds 20,000 characters"],
|
|
7926
|
+
["desde esta conversaci\xF3n s\xF3lo puedo mandar correo a la direcci\xF3n que te dio esta persona (la de su tarjeta o la que escribi\xF3 en el chat)", "from this conversation I can only email the address this person gave you (the one on their card or the one they wrote in the chat)"],
|
|
7927
|
+
["desde este canal s\xF3lo puedes leer los archivos; escribir lo tiene que permitir el due\xF1o (ficha \u2192 Conectores)", "from this channel you can only read files; writing must be allowed by the owner (agent page \u2192 Connectors)"],
|
|
7928
|
+
["desde este canal s\xF3lo puedes tocar la tarjeta de la persona con la que hablas; el resto del tablero se trabaja desde el equipo", "from this channel you can only touch the card of the person you're talking to; the rest of the board is handled by the team"],
|
|
7929
|
+
["el due\xF1o no ha conectado Google Drive (Conectores \u2192 Google Drive)", "the owner hasn't connected Google Drive (Connectors \u2192 Google Drive)"],
|
|
7930
|
+
["el due\xF1o no ha conectado MercadoPago en /app/settings/credentials", "the owner hasn't connected MercadoPago in /app/settings/credentials"],
|
|
7931
|
+
["el due\xF1o no ha conectado Skydropx en /app/settings/credentials", "the owner hasn't connected Skydropx in /app/settings/credentials"],
|
|
7932
|
+
["no tienes acceso a \xAB{}\xBB; usa drive_archivos para ver los que el due\xF1o eligi\xF3", `you don't have access to "{}"; use drive_archivos to see the ones the owner chose`],
|
|
7933
|
+
["el turno de esta llamada ya termin\xF3", "this call's turn already ended"],
|
|
7934
|
+
["no hay ning\xFAn turno en curso de este agente", "this agent has no turn in progress"],
|
|
7935
|
+
["hay varias conversaciones trabajando a la vez; int\xE9ntalo en un momento", "several conversations are running at once; try again in a moment"],
|
|
7936
|
+
["esta conversaci\xF3n no tiene tarjeta en el CRM", "this conversation has no card in the CRM"],
|
|
7937
|
+
["esta tool s\xF3lo la puede usar tu due\xF1o", "only your owner can use this tool"],
|
|
7938
|
+
["este agente no es de un espacio con Ghosty Ads", "this agent isn't in a workspace with Ghosty Ads"],
|
|
7939
|
+
["la tool {} no est\xE1 disponible en este canal ({})", "tool {} isn't available on this channel ({})"],
|
|
7940
|
+
["la tool {} pertenece al toolset \xAB{}\xBB, que este agente no tiene", `tool {} belongs to the "{}" toolset, which this agent doesn't have`],
|
|
7941
|
+
["no existe la tool \xAB{}\xBB", `tool "{}" doesn't exist`],
|
|
7942
|
+
["los n\xFAmeros de los anuncios son del equipo; no se comparten en conversaciones con clientes", "ad numbers belong to the team; they aren't shared in conversations with customers"],
|
|
7943
|
+
["no encontr\xE9 esa tarea", "I couldn't find that task"],
|
|
7944
|
+
["no encontr\xE9 esa tarjeta", "I couldn't find that card"],
|
|
7945
|
+
["no encontr\xE9 ese seguimiento pendiente en esta conversaci\xF3n", "I couldn't find that pending follow-up in this conversation"],
|
|
7946
|
+
["no encuentro la tarjeta \xAB{}\xBB; l\xE9elas con crm_board_read", `I can't find the card "{}"; read them with crm_board_read`],
|
|
7947
|
+
["no tienes tablero de ventas asignado: p\xEDdele al due\xF1o que te asigne uno (ghosty.studio \u2192 Ventas)", "you have no sales board assigned: ask the owner to assign you one (ghosty.studio \u2192 Sales)"],
|
|
7948
|
+
["no tienes tablero de ventas asignado", "you have no sales board assigned"],
|
|
7949
|
+
["tu identidad se cambia desde el equipo, no en una conversaci\xF3n con clientes", "your identity is changed from the team, not in a conversation with customers"],
|
|
7950
|
+
// — Infra interna (casi nunca llega al CLI, pero por si acaso)
|
|
7951
|
+
["SESSION_SECRET ausente en producci\xF3n \u2014 las cookies quedar\xEDan firmadas con un valor p\xFAblico. Inyectarlo con ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32).", "SESSION_SECRET missing in production \u2014 cookies would be signed with a public value. Inject it with ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32)."],
|
|
7952
|
+
["sqld: fetch sin namespace", "sqld: fetch without namespace"],
|
|
7953
|
+
["sqld: token sin namespace ser\xEDa un token maestro", "sqld: a token without namespace would be a master token"]
|
|
7954
|
+
];
|
|
7955
|
+
|
|
7956
|
+
// src/i18n.ts
|
|
7957
|
+
var current = null;
|
|
7958
|
+
function detectLang(flag, env = process.env) {
|
|
7959
|
+
const pick = (v) => v && /^es\b|^es[_-]/i.test(v) ? "es" : v && /^en\b|^en[_-]|^c$|^posix$/i.test(v) ? "en" : null;
|
|
7960
|
+
return pick(flag) ?? pick(env.GHOSTY_LANG) ?? pick(env.LC_ALL) ?? pick(env.LC_MESSAGES) ?? pick(env.LANG) ?? "en";
|
|
7961
|
+
}
|
|
7962
|
+
function setLang(l) {
|
|
7963
|
+
current = l;
|
|
7964
|
+
}
|
|
7965
|
+
var lang = () => current ?? (current = detectLang());
|
|
7966
|
+
var t = (en, es) => lang() === "es" ? es : en;
|
|
7967
|
+
var compiled = null;
|
|
7968
|
+
var escape = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7969
|
+
function compile(es) {
|
|
7970
|
+
return new RegExp(`^${es.split("{}").map(escape).join("(.+?)")}$`, "s");
|
|
7971
|
+
}
|
|
7972
|
+
function serverText(msg, l = lang()) {
|
|
7973
|
+
if (l === "es" || !msg) return msg;
|
|
7974
|
+
compiled ??= SERVER_MESSAGES.map(([es, en]) => ({ re: compile(es), en }));
|
|
7975
|
+
for (const c of compiled) {
|
|
7976
|
+
const m = c.re.exec(msg);
|
|
7977
|
+
if (!m) continue;
|
|
7978
|
+
let i = 1;
|
|
7979
|
+
return c.en.replace(/\{\}/g, () => m[i++] ?? "");
|
|
7980
|
+
}
|
|
7981
|
+
return msg;
|
|
7982
|
+
}
|
|
7983
|
+
|
|
7386
7984
|
// src/util.ts
|
|
7387
7985
|
var EXIT = { OK: 0, API: 1, USAGE: 2, AUTH: 3 };
|
|
7388
7986
|
var CliError = class extends Error {
|
|
@@ -7408,7 +8006,7 @@ function out(data, human) {
|
|
|
7408
8006
|
else process.stdout.write(human() + "\n");
|
|
7409
8007
|
}
|
|
7410
8008
|
function table(rows, cols) {
|
|
7411
|
-
if (!rows.length) return dim("(empty)");
|
|
8009
|
+
if (!rows.length) return dim(t("(empty)", "(vac\xEDo)"));
|
|
7412
8010
|
const cell = (v) => v === null || v === void 0 ? "\u2014" : String(v);
|
|
7413
8011
|
const w = cols.map((c) => Math.max(c.length, ...rows.map((r) => cell(r[c]).length)));
|
|
7414
8012
|
const line2 = (vals) => vals.map((v, i) => v.padEnd(w[i])).join(" ").trimEnd();
|
|
@@ -7425,7 +8023,7 @@ function fail(e) {
|
|
|
7425
8023
|
if (globals.json) {
|
|
7426
8024
|
process.stdout.write(JSON.stringify({ error: err.message, code: err.code, ...err.hint ? { hint: err.hint } : {} }) + "\n");
|
|
7427
8025
|
} else {
|
|
7428
|
-
info(red("Error: ") + err.message);
|
|
8026
|
+
info(red(t("Error: ", "Error: ")) + err.message);
|
|
7429
8027
|
if (err.hint) info(dim(err.hint));
|
|
7430
8028
|
}
|
|
7431
8029
|
return exitAfterFlush(err.code);
|
|
@@ -7448,7 +8046,7 @@ var LOGIN_TIMEOUT_MS = 5 * 6e4;
|
|
|
7448
8046
|
function configPath() {
|
|
7449
8047
|
const profile = globals.profile || process.env.GHOSTY_PROFILE || "";
|
|
7450
8048
|
if (profile && !/^[A-Za-z0-9_-]{1,40}$/.test(profile)) {
|
|
7451
|
-
throw new CliError(`Invalid profile: ${profile}.`, EXIT.USAGE, "Use letters, digits, - or _");
|
|
8049
|
+
throw new CliError(t(`Invalid profile: ${profile}.`, `Perfil inv\xE1lido: ${profile}.`), EXIT.USAGE, t("Use letters, digits, - or _", "Usa letras, d\xEDgitos, - o _"));
|
|
7452
8050
|
}
|
|
7453
8051
|
return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "ghosty", profile ? `config.${profile}.json` : "config.json");
|
|
7454
8052
|
}
|
|
@@ -7480,17 +8078,17 @@ async function tokenRequest(base, form) {
|
|
|
7480
8078
|
});
|
|
7481
8079
|
const body = await res.json().catch(() => ({}));
|
|
7482
8080
|
if (!res.ok || !body.access_token) {
|
|
7483
|
-
throw new CliError(`Login failed (${body.error ?? res.status}).`, EXIT.AUTH, "Run: ghosty login");
|
|
8081
|
+
throw new CliError(t(`Login failed (${body.error ?? res.status}).`, `Fall\xF3 el inicio de sesi\xF3n (${body.error ?? res.status}).`), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
|
|
7484
8082
|
}
|
|
7485
8083
|
return body;
|
|
7486
8084
|
}
|
|
7487
|
-
var CLOSE_PAGE = (ok, base) => `<!doctype html><html lang="
|
|
8085
|
+
var CLOSE_PAGE = (ok, base) => `<!doctype html><html lang="${lang()}"><meta charset="utf-8">
|
|
7488
8086
|
<meta name="viewport" content="width=device-width,initial-scale=1"><title>Ghosty CLI</title>
|
|
7489
8087
|
<body style="font-family:system-ui,sans-serif;background:#F4F5FB;color:#191A20;display:grid;place-items:center;min-height:100vh;margin:0;padding:16px;box-sizing:border-box">
|
|
7490
8088
|
<div style="text-align:center;max-width:420px">
|
|
7491
8089
|
<img src="${base}/mascot.png" alt="Ghosty" width="112" height="133" style="display:block;margin:0 auto 20px">
|
|
7492
|
-
<h1 style="font-weight:700;font-size:28px;margin:0 0 8px">${ok ? "\xA1Listo, ya entraste!" : "No se pudo entrar"}</h1>
|
|
7493
|
-
<p style="margin:0;color:#4b4d57;line-height:1.5">${ok ? "Ya puedes cerrar esta pesta\xF1a y volver a la terminal." : "Vuelve a la terminal y corre <code>ghosty login</code> otra vez."}</p>
|
|
8090
|
+
<h1 style="font-weight:700;font-size:28px;margin:0 0 8px">${ok ? t("You're in!", "\xA1Listo, ya entraste!") : t("Sign-in failed", "No se pudo entrar")}</h1>
|
|
8091
|
+
<p style="margin:0;color:#4b4d57;line-height:1.5">${ok ? t("You can close this tab and go back to the terminal.", "Ya puedes cerrar esta pesta\xF1a y volver a la terminal.") : t("Go back to the terminal and run <code>ghosty login</code> again.", "Vuelve a la terminal y corre <code>ghosty login</code> otra vez.")}</p>
|
|
7494
8092
|
</div>`;
|
|
7495
8093
|
async function login() {
|
|
7496
8094
|
const base = await baseUrl();
|
|
@@ -7509,12 +8107,12 @@ async function login() {
|
|
|
7509
8107
|
clearTimeout(timer);
|
|
7510
8108
|
server.close();
|
|
7511
8109
|
if (ok) resolve3({ code: u.searchParams.get("code"), redirectUri: redirectUri2 });
|
|
7512
|
-
else reject(new CliError(`Login was not completed (${u.searchParams.get("error") ?? "state mismatch"}).`, EXIT.AUTH, "Run: ghosty login"));
|
|
8110
|
+
else reject(new CliError(t(`Login was not completed (${u.searchParams.get("error") ?? "state mismatch"}).`, `No se complet\xF3 el inicio de sesi\xF3n (${u.searchParams.get("error") ?? "state mismatch"}).`), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login")));
|
|
7513
8111
|
});
|
|
7514
8112
|
let redirectUri2 = "";
|
|
7515
8113
|
const timer = setTimeout(() => {
|
|
7516
8114
|
server.close();
|
|
7517
|
-
reject(new CliError("Login timed out after 5 minutes.", EXIT.AUTH, "Run: ghosty login"));
|
|
8115
|
+
reject(new CliError(t("Login timed out after 5 minutes.", "El inicio de sesi\xF3n caduc\xF3 a los 5 minutos."), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login")));
|
|
7518
8116
|
}, LOGIN_TIMEOUT_MS);
|
|
7519
8117
|
server.listen(0, "127.0.0.1", () => {
|
|
7520
8118
|
const port = server.address().port;
|
|
@@ -7529,8 +8127,8 @@ async function login() {
|
|
|
7529
8127
|
code_challenge_method: "S256"
|
|
7530
8128
|
});
|
|
7531
8129
|
if (globals.json) process.stdout.write(JSON.stringify({ event: "login_url", url }) + "\n");
|
|
7532
|
-
info(`${lavender("\u203A")} Opening your browser to sign in to Ghosty\u2026`);
|
|
7533
|
-
info(dim(` If it doesn't open, visit: ${url}`));
|
|
8130
|
+
info(`${lavender("\u203A")} ${t("Opening your browser to sign in to Ghosty\u2026", "Abriendo tu navegador para entrar a Ghosty\u2026")}`);
|
|
8131
|
+
info(dim(t(` If it doesn't open, visit: ${url}`, ` Si no se abre, entra a: ${url}`)));
|
|
7534
8132
|
void openUrl(url);
|
|
7535
8133
|
});
|
|
7536
8134
|
});
|
|
@@ -7563,21 +8161,21 @@ async function loginDevice() {
|
|
|
7563
8161
|
body: new URLSearchParams({ client_id: CLIENT_ID, scope: SCOPES })
|
|
7564
8162
|
});
|
|
7565
8163
|
const d = await res.json().catch(() => ({}));
|
|
7566
|
-
if (!res.ok || !d.device_code) throw new CliError(`Could not start the login (${d.error ?? res.status}).`, EXIT.AUTH);
|
|
8164
|
+
if (!res.ok || !d.device_code) throw new CliError(t(`Could not start the login (${d.error ?? res.status}).`, `No se pudo iniciar el inicio de sesi\xF3n (${d.error ?? res.status}).`), EXIT.AUTH);
|
|
7567
8165
|
if (globals.json) process.stdout.write(JSON.stringify({ event: "login_url", url: d.verification_uri_complete, code: d.user_code, method: "device" }) + "\n");
|
|
7568
|
-
info(`${lavender("\u203A")} Open this link on any device and confirm the code ${lavender(d.user_code)}:`);
|
|
8166
|
+
info(`${lavender("\u203A")} ${t("Open this link on any device and confirm the code", "Abre esta liga en cualquier dispositivo y confirma el c\xF3digo")} ${lavender(d.user_code)}:`);
|
|
7569
8167
|
info(` ${d.verification_uri_complete}`);
|
|
7570
8168
|
let interval = (d.interval ?? 5) * 1e3;
|
|
7571
8169
|
const deadline = Date.now() + (d.expires_in ?? 600) * 1e3;
|
|
7572
8170
|
while (Date.now() < deadline) {
|
|
7573
8171
|
await new Promise((r) => setTimeout(r, interval));
|
|
7574
|
-
const
|
|
8172
|
+
const tokenRes = await fetch(`${base}/oauth2/token`, {
|
|
7575
8173
|
method: "POST",
|
|
7576
8174
|
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
7577
8175
|
body: new URLSearchParams({ client_id: CLIENT_ID, grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code: d.device_code })
|
|
7578
8176
|
});
|
|
7579
|
-
const body = await
|
|
7580
|
-
if (
|
|
8177
|
+
const body = await tokenRes.json().catch(() => ({}));
|
|
8178
|
+
if (tokenRes.ok && body.access_token) {
|
|
7581
8179
|
const prev = await readConfig();
|
|
7582
8180
|
const cfg = {
|
|
7583
8181
|
...prev.baseUrl ? { baseUrl: prev.baseUrl } : {},
|
|
@@ -7593,10 +8191,10 @@ async function loginDevice() {
|
|
|
7593
8191
|
interval += 5e3;
|
|
7594
8192
|
continue;
|
|
7595
8193
|
}
|
|
7596
|
-
if (body.error === "access_denied") throw new CliError("The login was rejected.", EXIT.AUTH, "Run: ghosty login");
|
|
7597
|
-
throw new CliError(`Login failed (${body.error ??
|
|
8194
|
+
if (body.error === "access_denied") throw new CliError(t("The login was rejected.", "Se rechaz\xF3 el inicio de sesi\xF3n."), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
|
|
8195
|
+
throw new CliError(t(`Login failed (${body.error ?? tokenRes.status}).`, `Fall\xF3 el inicio de sesi\xF3n (${body.error ?? tokenRes.status}).`), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
|
|
7598
8196
|
}
|
|
7599
|
-
throw new CliError("The code expired before it was confirmed.", EXIT.AUTH, "Run: ghosty login");
|
|
8197
|
+
throw new CliError(t("The code expired before it was confirmed.", "El c\xF3digo caduc\xF3 antes de confirmarse."), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
|
|
7600
8198
|
}
|
|
7601
8199
|
async function refresh(cfg) {
|
|
7602
8200
|
if (!cfg.refreshToken) return null;
|
|
@@ -7634,8 +8232,8 @@ async function currentToken() {
|
|
|
7634
8232
|
if (explicit) return explicit;
|
|
7635
8233
|
let cfg = await readConfig();
|
|
7636
8234
|
if (!cfg.accessToken) {
|
|
7637
|
-
if (!interactive()) throw new CliError("Not logged in.", EXIT.AUTH, "Run: ghosty login (or set GHOSTY_TOKEN)");
|
|
7638
|
-
info(dim("Not logged in yet \u2014 let's fix that."));
|
|
8235
|
+
if (!interactive()) throw new CliError(t("Not logged in.", "No has iniciado sesi\xF3n."), EXIT.AUTH, t("Run: ghosty login (or set GHOSTY_TOKEN)", "Corre: ghosty login (o pon GHOSTY_TOKEN)"));
|
|
8236
|
+
info(dim(t("Not logged in yet \u2014 let's fix that.", "Todav\xEDa no inicias sesi\xF3n; vamos a arreglarlo.")));
|
|
7639
8237
|
cfg = await (hasLocalBrowser() ? login() : loginDevice());
|
|
7640
8238
|
}
|
|
7641
8239
|
if (cfg.expiresAt !== void 0 && cfg.expiresAt - Date.now() < 6e4) cfg = await refresh(cfg) ?? cfg;
|
|
@@ -7650,7 +8248,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
7650
8248
|
const req = httpsRequest(`${base}${path}`, {
|
|
7651
8249
|
method: "POST",
|
|
7652
8250
|
timeout: timeoutMs,
|
|
7653
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
8251
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.20.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
7654
8252
|
}, (res) => {
|
|
7655
8253
|
let buf = "";
|
|
7656
8254
|
res.on("data", (c) => buf += c);
|
|
@@ -7664,7 +8262,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
7664
8262
|
resolve3({ status: res.statusCode ?? 0, retryAfter: retryAfterSeconds(res.headers["retry-after"]), body: parsed });
|
|
7665
8263
|
});
|
|
7666
8264
|
});
|
|
7667
|
-
req.on("timeout", () => req.destroy(new Error(`no answer in ${Math.round(timeoutMs / 6e4)} min`)));
|
|
8265
|
+
req.on("timeout", () => req.destroy(new Error(t(`no answer in ${Math.round(timeoutMs / 6e4)} min`, `sin respuesta en ${Math.round(timeoutMs / 6e4)} min`))));
|
|
7668
8266
|
req.on("error", (e) => resolve3({ status: 0, retryAfter: null, body: { error: String(e) } }));
|
|
7669
8267
|
req.end(data);
|
|
7670
8268
|
});
|
|
@@ -7672,8 +8270,8 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
7672
8270
|
function retryAfterSeconds(h, now = Date.now()) {
|
|
7673
8271
|
if (!h) return null;
|
|
7674
8272
|
if (/^\d+$/.test(h.trim())) return Number(h.trim());
|
|
7675
|
-
const
|
|
7676
|
-
return Number.isNaN(
|
|
8273
|
+
const t2 = Date.parse(h);
|
|
8274
|
+
return Number.isNaN(t2) ? null : Math.max(0, Math.round((t2 - now) / 1e3));
|
|
7677
8275
|
}
|
|
7678
8276
|
async function api(path, opts = {}) {
|
|
7679
8277
|
const res = await request(path, opts);
|
|
@@ -7692,7 +8290,8 @@ async function request(path, opts = {}, retried = false) {
|
|
|
7692
8290
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
7693
8291
|
headers: {
|
|
7694
8292
|
authorization: `Bearer ${token}`,
|
|
7695
|
-
"user-agent": `ghosty-cli/${"0.
|
|
8293
|
+
"user-agent": `ghosty-cli/${"0.20.0"}`,
|
|
8294
|
+
"accept-language": lang(),
|
|
7696
8295
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
7697
8296
|
...opts.headers
|
|
7698
8297
|
},
|
|
@@ -7713,12 +8312,13 @@ async function toError(res) {
|
|
|
7713
8312
|
body = JSON.parse(text);
|
|
7714
8313
|
} catch {
|
|
7715
8314
|
}
|
|
7716
|
-
const
|
|
7717
|
-
|
|
7718
|
-
if (res.status ===
|
|
7719
|
-
if (res.status ===
|
|
8315
|
+
const raw = body.message || body.error_description || (body.detalle && body.error ? `${body.error}: ${body.detalle}` : body.error) || text.slice(0, 300) || res.statusText;
|
|
8316
|
+
const detail = serverText(raw);
|
|
8317
|
+
if (res.status === 401) return new CliError(t("Your session is not valid anymore.", "Tu sesi\xF3n ya no es v\xE1lida."), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
|
|
8318
|
+
if (res.status === 403) return new CliError(t(`Not allowed: ${detail}`, `No permitido: ${detail}`), EXIT.API, t("If the agent was shared with you, its workspace (board, channels, numbers) stays with the owner: ask them to add you to that workspace. Otherwise run: ghosty login", "Si el agente se comparti\xF3 contigo, su espacio (tablero, canales, n\xFAmeros) sigue siendo del due\xF1o: p\xEDdele que te agregue a ese espacio. Si no, corre: ghosty login"));
|
|
8319
|
+
if (res.status === 404) return new CliError(t("Not found.", "No se encontr\xF3."), EXIT.API, t("Check the agent id (ghosty agents ls) or that your token belongs to that agent.", "Revisa el id del agente (ghosty agents ls) o que tu token sea de ese agente."));
|
|
7720
8320
|
if (body.error === "restart_no_aplica" || body.error === "agente_sin_maquina") {
|
|
7721
|
-
return new CliError("This agent doesn't need a restart: new files, skills and MCP load on its next turn.", EXIT.API);
|
|
8321
|
+
return new CliError(t("This agent doesn't need a restart: new files, skills and MCP load on its next turn.", "Este agente no necesita reinicio: los archivos, skills y MCP nuevos se cargan en su siguiente turno."), EXIT.API);
|
|
7722
8322
|
}
|
|
7723
8323
|
return new CliError(`${res.status} ${detail}`, EXIT.API);
|
|
7724
8324
|
}
|
|
@@ -7732,18 +8332,18 @@ import { dirname as dirname2, join as join3, relative, resolve } from "node:path
|
|
|
7732
8332
|
import { createInterface } from "node:readline/promises";
|
|
7733
8333
|
var canPrompt = () => Boolean(process.stdin.isTTY && process.stderr.isTTY);
|
|
7734
8334
|
function requireYesIfHeadless(yes, retry) {
|
|
7735
|
-
if (!yes && !canPrompt()) throw usage("--yes required when not running interactively.", `Run: ${retry} --yes`);
|
|
8335
|
+
if (!yes && !canPrompt()) throw usage(t("--yes required when not running interactively.", "Hace falta --yes cuando no corre en una terminal."), t(`Run: ${retry} --yes`, `Corre: ${retry} --yes`));
|
|
7736
8336
|
}
|
|
7737
8337
|
async function confirm(question, opts) {
|
|
7738
8338
|
if (opts.yes) return;
|
|
7739
|
-
if (!canPrompt()) throw usage("--yes required when not running interactively.", `Run: ${opts.retry} --yes`);
|
|
8339
|
+
if (!canPrompt()) throw usage(t("--yes required when not running interactively.", "Hace falta --yes cuando no corre en una terminal."), t(`Run: ${opts.retry} --yes`, `Corre: ${opts.retry} --yes`));
|
|
7740
8340
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
7741
8341
|
try {
|
|
7742
8342
|
const ask = opts.expect ? `${question}
|
|
7743
|
-
Type ${bold(opts.expect)} to confirm: ` : `${question} [y/N] `;
|
|
8343
|
+
${t("Type", "Escribe")} ${bold(opts.expect)} ${t("to confirm", "para confirmar")}: ` : `${question} ${t("[y/N]", "[s/N]")} `;
|
|
7744
8344
|
const a = (await rl.question(ask)).trim();
|
|
7745
|
-
const ok = opts.expect ? a === opts.expect : /^y(es)
|
|
7746
|
-
if (!ok) throw usage("Cancelled.");
|
|
8345
|
+
const ok = opts.expect ? a === opts.expect : /^(y(es)?|s[ií]?)$/i.test(a);
|
|
8346
|
+
if (!ok) throw usage(t("Cancelled.", "Cancelado."));
|
|
7747
8347
|
} finally {
|
|
7748
8348
|
rl.close();
|
|
7749
8349
|
}
|
|
@@ -7772,7 +8372,7 @@ async function tryPost(id, body) {
|
|
|
7772
8372
|
const r = await postLong(`/api/v2/agents/${enc(id)}/try`, body);
|
|
7773
8373
|
if ((r.status === 503 || r.status === 409 || r.status === 0) && attempt < 40) {
|
|
7774
8374
|
const wait = r.status === 503 ? r.retryAfter ?? 15 : Math.min(30, 2 ** Math.min(attempt, 4) + 1);
|
|
7775
|
-
if (!globals.json) info(dim(` ${r.status === 503 ? "Ghosty is restarting" : r.status === 409 ? "previous turn still running" : "network error"} \u2014 retrying in ${wait}s`));
|
|
8375
|
+
if (!globals.json) info(dim(` ${r.status === 503 ? t("Ghosty is restarting", "Ghosty se est\xE1 reiniciando") : r.status === 409 ? t("previous turn still running", "el turno anterior sigue en curso") : t("network error", "error de red")} \u2014 ${t(`retrying in ${wait}s`, `reintento en ${wait}s`)}`));
|
|
7776
8376
|
await sleep(wait * 1e3);
|
|
7777
8377
|
continue;
|
|
7778
8378
|
}
|
|
@@ -7795,7 +8395,7 @@ function richLine(s) {
|
|
|
7795
8395
|
const str = (k) => typeof d[k] === "string" ? d[k] : "";
|
|
7796
8396
|
switch (s.rich) {
|
|
7797
8397
|
case "ubicacion":
|
|
7798
|
-
return `\u{1F4CD} ${str("nombre") || "location"} \xB7 ${Number(d.latitud).toFixed(4)},${Number(d.longitud).toFixed(4)}${str("direccion") ? dim(` ${str("direccion")}`) : ""}`;
|
|
8398
|
+
return `\u{1F4CD} ${str("nombre") || t("location", "ubicaci\xF3n")} \xB7 ${Number(d.latitud).toFixed(4)},${Number(d.longitud).toFixed(4)}${str("direccion") ? dim(` ${str("direccion")}`) : ""}`;
|
|
7799
8399
|
case "boton":
|
|
7800
8400
|
return `\u{1F518} ${str("boton")} ${dim(str("url"))} ${str("texto")}`;
|
|
7801
8401
|
case "contacto":
|
|
@@ -7803,9 +8403,9 @@ function richLine(s) {
|
|
|
7803
8403
|
case "encuesta":
|
|
7804
8404
|
return `\u{1F4CA} ${str("pregunta")}`;
|
|
7805
8405
|
case "reaccion":
|
|
7806
|
-
return `${str("emoji")} ${dim(`reaction to ${str("mensajeId") || "the last message"}`)}`;
|
|
8406
|
+
return `${str("emoji")} ${dim(t(`reaction to ${str("mensajeId") || "the last message"}`, `reacci\xF3n a ${str("mensajeId") || "el \xFAltimo mensaje"}`))}`;
|
|
7807
8407
|
case "cita":
|
|
7808
|
-
return `\u21A9\uFE0F ${str("texto")} ${dim(`quoting ${str("mensajeId") || "the last message"}`)}`;
|
|
8408
|
+
return `\u21A9\uFE0F ${str("texto")} ${dim(t(`quoting ${str("mensajeId") || "the last message"}`, `citando ${str("mensajeId") || "el \xFAltimo mensaje"}`))}`;
|
|
7809
8409
|
}
|
|
7810
8410
|
}
|
|
7811
8411
|
function sentText(sent) {
|
|
@@ -7815,33 +8415,33 @@ function sentText(sent) {
|
|
|
7815
8415
|
}
|
|
7816
8416
|
async function tryWaba(id, text, v) {
|
|
7817
8417
|
const phone = v.session ?? "";
|
|
7818
|
-
if (!/^\d{10,15}$/.test(phone)) throw usage("--waba needs --session <phone> (10-15 digits, a made-up number).", '
|
|
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.", '
|
|
8418
|
+
if (!/^\d{10,15}$/.test(phone)) throw usage(t("--waba needs --session <phone> (10-15 digits, a made-up number).", "--waba necesita --session <tel\xE9fono> (10-15 d\xEDgitos, un n\xFAmero inventado)."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "hola" --waba --session 5210000000009');
|
|
8419
|
+
if ((v.media?.length ?? 0) > 1) throw usage(t("One --media per turn (WhatsApp sends one file per message).", "Un --media por turno (WhatsApp manda un archivo por mensaje)."), t("Send several turns, one file each.", "Manda varios turnos, un archivo en cada uno."));
|
|
8420
|
+
if (v["as-formmy"] && !v.media?.length) throw usage(t("--as-formmy goes with --media FILE.", "--as-formmy va con --media ARCHIVO."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "" --waba --session 5210000000009 --media nota.ogg --as-formmy');
|
|
8421
|
+
if (!text && !v.media?.length && !v.reset) throw usage(t("Missing message.", "Falta el mensaje."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "hola" --waba --session 5210000000009');
|
|
7822
8422
|
let history;
|
|
7823
8423
|
if (v.history) {
|
|
7824
8424
|
const raw = JSON.parse(await readFile2(v.history, "utf8"));
|
|
7825
8425
|
history = Array.isArray(raw) ? raw : raw.messages;
|
|
7826
|
-
if (!Array.isArray(history)) throw usage(`${v.history}: expected [{role, text, at?}] or {messages: [...]}.`);
|
|
7827
|
-
if (!v.reset) info(dim("Note: history only counts on the first turn of a session; add --reset to start over."));
|
|
8426
|
+
if (!Array.isArray(history)) throw usage(t(`${v.history}: expected [{role, text, at?}] or {messages: [...]}.`, `${v.history}: se esperaba [{role, text, at?}] o {messages: [...]}.`));
|
|
8427
|
+
if (!v.reset) info(dim(t("Note: history only counts on the first turn of a session; add --reset to start over.", "Nota: el historial s\xF3lo cuenta en el primer turno de una sesi\xF3n; agrega --reset para empezar de nuevo.")));
|
|
7828
8428
|
}
|
|
7829
8429
|
const media = v.media?.[0] ? await uploadMedia(v.media[0]) : void 0;
|
|
7830
|
-
if (process.stderr.isTTY && !globals.json && (text || media)) info(dim("Thinking\u2026 (the real WhatsApp path; nothing is sent)"));
|
|
8430
|
+
if (process.stderr.isTTY && !globals.json && (text || media)) info(dim(t("Thinking\u2026 (the real WhatsApp path; nothing is sent)", "Pensando\u2026 (el camino real de WhatsApp; no se manda nada)")));
|
|
7831
8431
|
const r = await tryPost(id, { as: "waba", session: phone, reset: v.reset, text: text ?? "", ...media ? { media } : {}, ...history ? { history } : {}, ...v["as-formmy"] ? { asFormmy: true } : {} });
|
|
7832
|
-
if (r.status !== 200) throw Object.assign(new Error(`${r.status} ${r.body.error
|
|
8432
|
+
if (r.status !== 200) throw Object.assign(new Error(`${r.status} ${r.body.error ? serverText(r.body.error) : t("turn failed", "el turno fall\xF3")}`), { code: 1 });
|
|
7833
8433
|
const via = r.body.media ? `
|
|
7834
|
-
${r.body.media.copied ? dim("attachment: downloaded through the Formmy-like proxy and copied to gs") : red("attachment: NOT copied to gs (the proxy download failed)")}` : "";
|
|
7835
|
-
out(r.body, () => (r.body.sent ? sentText(r.body.sent) || dim("(the agent sent nothing)") : dim("Session reset.")) + via + (r.body.error ? `
|
|
7836
|
-
${dim(`(warning: ${r.body.error})`)}` : ""));
|
|
8434
|
+
${r.body.media.copied ? dim(t("attachment: downloaded through the Formmy-like proxy and copied to gs", "adjunto: descargado por el proxy tipo Formmy y copiado a gs")) : red(t("attachment: NOT copied to gs (the proxy download failed)", "adjunto: NO se copi\xF3 a gs (fall\xF3 la descarga por el proxy)"))}` : "";
|
|
8435
|
+
out(r.body, () => (r.body.sent ? sentText(r.body.sent) || dim(t("(the agent sent nothing)", "(el agente no mand\xF3 nada)")) : dim(t("Session reset.", "Sesi\xF3n reiniciada."))) + via + (r.body.error ? `
|
|
8436
|
+
${dim(`(${t("warning", "aviso")}: ${serverText(r.body.error)})`)}` : ""));
|
|
7837
8437
|
}
|
|
7838
8438
|
async function tryReplay(id, v) {
|
|
7839
|
-
if (!v.out) throw usage("--replay needs --out DIR (the report is written there after every step).", "Example: ghosty try <agent-id> --replay muestra.json --out ./replay");
|
|
8439
|
+
if (!v.out) throw usage(t("--replay needs --out DIR (the report is written there after every step).", "--replay necesita --out DIR (ah\xED se escribe el reporte despu\xE9s de cada paso)."), t("Example", "Ejemplo") + ": ghosty try <agent-id> --replay muestra.json --out ./replay");
|
|
7840
8440
|
const max = Number(v.max ?? 15);
|
|
7841
8441
|
const conc = Math.max(1, Math.min(30, Number(v.concurrency ?? 8)));
|
|
7842
8442
|
const input = JSON.parse(await readFile2(v.replay, "utf8"));
|
|
7843
8443
|
const convs = input.sample ?? [];
|
|
7844
|
-
if (!convs.length) throw usage(`${v.replay}: no conversations in "sample".`, '
|
|
8444
|
+
if (!convs.length) throw usage(t(`${v.replay}: no conversations in "sample".`, `${v.replay}: no hay conversaciones en "sample".`), t("Format", "Formato") + ': {"sample": [{"id", "fakePhone", "messages": [{"role": "user"|"agent", "text"}]}]}');
|
|
7845
8445
|
await mkdir2(v.out, { recursive: true });
|
|
7846
8446
|
const file = join2(v.out, "replay.json");
|
|
7847
8447
|
const prev = v.resume ? JSON.parse(await readFile2(file, "utf8").catch(() => "null")) : null;
|
|
@@ -7849,7 +8449,7 @@ async function tryReplay(id, v) {
|
|
|
7849
8449
|
report.done = false;
|
|
7850
8450
|
let saving = Promise.resolve();
|
|
7851
8451
|
const save = () => saving = saving.then(() => writeFile2(file, JSON.stringify(report, null, 1)));
|
|
7852
|
-
info(`${bold("Replay")} ${convs.length} conversation(s), up to ${max} customer turns each, ${conc} at a time \u2192 ${file}`);
|
|
8452
|
+
info(`${bold("Replay")} ${t(`${convs.length} conversation(s), up to ${max} customer turns each, ${conc} at a time`, `${convs.length} conversaci\xF3n(es), hasta ${max} turnos del cliente cada una, ${conc} a la vez`)} \u2192 ${file}`);
|
|
7853
8453
|
const queue = [...convs];
|
|
7854
8454
|
const worker = async () => {
|
|
7855
8455
|
for (let c = queue.shift(); c; c = queue.shift()) {
|
|
@@ -7869,7 +8469,7 @@ async function tryReplay(id, v) {
|
|
|
7869
8469
|
await save();
|
|
7870
8470
|
}
|
|
7871
8471
|
const bad = entry.steps.filter((s) => s.status !== 200 || !s.reply).length;
|
|
7872
|
-
info(` ${bad ? red("!") : lavender("\u2713")} ${c.id}: ${entry.steps.length} turn(s)${bad ? `, ${bad} without an answer` : ""}`);
|
|
8472
|
+
info(` ${bad ? red("!") : lavender("\u2713")} ${c.id}: ${t(`${entry.steps.length} turn(s)`, `${entry.steps.length} turno(s)`)}${bad ? t(`, ${bad} without an answer`, `, ${bad} sin respuesta`) : ""}`);
|
|
7873
8473
|
}
|
|
7874
8474
|
};
|
|
7875
8475
|
await Promise.all(Array.from({ length: Math.min(conc, convs.length) }, worker));
|
|
@@ -7879,16 +8479,17 @@ async function tryReplay(id, v) {
|
|
|
7879
8479
|
const steps = Object.values(report.results).flatMap((r) => r.steps);
|
|
7880
8480
|
const failed = steps.filter((s) => s.status !== 200 || !s.reply).length;
|
|
7881
8481
|
const summary = { file, conversations: Object.keys(report.results).length, turns: steps.length, failed, medianMs: steps.map((s) => s.ms).sort((a, b) => a - b)[Math.floor(steps.length / 2)] ?? 0 };
|
|
7882
|
-
out(summary, () => `${failed ? red("!") : lavender("\u2713")} ${summary.turns} turn(s) in ${summary.conversations} conversation(s), ${failed} without an answer, median ${Math.round(summary.medianMs / 1e3)}s
|
|
7883
|
-
${dim(`Report: ${file} Failed turns: ghosty turns ls ${id} --errors --since 2h`)}`);
|
|
8482
|
+
out(summary, () => `${failed ? red("!") : lavender("\u2713")} ${t(`${summary.turns} turn(s) in ${summary.conversations} conversation(s), ${failed} without an answer, median ${Math.round(summary.medianMs / 1e3)}s`, `${summary.turns} turno(s) en ${summary.conversations} conversaci\xF3n(es), ${failed} sin respuesta, mediana ${Math.round(summary.medianMs / 1e3)}s`)}
|
|
8483
|
+
${dim(t(`Report: ${file} Failed turns: ghosty turns ls ${id} --errors --since 2h`, `Reporte: ${file} Turnos fallidos: ghosty turns ls ${id} --errors --since 2h`))}`);
|
|
7884
8484
|
if (failed) process.exitCode = 1;
|
|
7885
8485
|
}
|
|
7886
8486
|
|
|
7887
8487
|
// src/commands/agents.ts
|
|
7888
8488
|
var enc2 = encodeURIComponent;
|
|
7889
8489
|
var agentPath = (id, rest = "") => `/api/v2/agents/${enc2(id)}${rest}`;
|
|
8490
|
+
var WHAT_ES = { "agent id": "el id del agente", path: "la ruta", email: "el correo", "skill slug": "el slug de la skill" };
|
|
7890
8491
|
var need = (v, what, example) => {
|
|
7891
|
-
if (!v) throw usage(`Missing ${what}.`, `Example: ${example}`);
|
|
8492
|
+
if (!v) throw usage(t(`Missing ${what}.`, `Falta ${WHAT_ES[what] ?? what}.`), `${t("Example", "Ejemplo")}: ${example}`);
|
|
7892
8493
|
return v;
|
|
7893
8494
|
};
|
|
7894
8495
|
async function agentsLs(workspace) {
|
|
@@ -7905,14 +8506,14 @@ async function agentsLs(workspace) {
|
|
|
7905
8506
|
...a.compartidoPor ? { sharedBy: a.compartidoPor } : {}
|
|
7906
8507
|
}));
|
|
7907
8508
|
out(rows, () => `${table(rows, ["id", "name", "engine", "workspace", "lastActivity"])}${workspace ? `
|
|
7908
|
-
${dim(`only ${workspace} (ghosty use) \xB7 all: --all`)}` : ""}`);
|
|
8509
|
+
${dim(t(`only ${workspace} (ghosty use) \xB7 all: --all`, `s\xF3lo ${workspace} (ghosty use) \xB7 todos: --all`))}` : ""}`);
|
|
7909
8510
|
}
|
|
7910
8511
|
function parseEnv(raw) {
|
|
7911
8512
|
if (!raw) return void 0;
|
|
7912
8513
|
const env = {};
|
|
7913
8514
|
for (const pair of raw.split(",")) {
|
|
7914
8515
|
const i = pair.indexOf("=");
|
|
7915
|
-
if (i < 1) throw usage(`Bad --env entry: ${pair}.`, "Example: --env GHOSTY_THINKING_EFFORT=max");
|
|
8516
|
+
if (i < 1) throw usage(t(`Bad --env entry: ${pair}.`, `Entrada de --env inv\xE1lida: ${pair}.`), t("Example", "Ejemplo") + ": --env GHOSTY_THINKING_EFFORT=max");
|
|
7916
8517
|
env[pair.slice(0, i).trim()] = pair.slice(i + 1);
|
|
7917
8518
|
}
|
|
7918
8519
|
return env;
|
|
@@ -7922,7 +8523,7 @@ function parseChannels(raw) {
|
|
|
7922
8523
|
const ch = {};
|
|
7923
8524
|
for (const pair of raw.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
7924
8525
|
const [k, val] = pair.split("=");
|
|
7925
|
-
if (!["teams", "web", "whatsapp", "messenger", "wa"].includes(k) || val !== "on" && val !== "off") throw usage(`Bad --channels entry: ${pair}.`, "Use teams|web|whatsapp|messenger|wa=on|off, comma-separated (wa = personal WhatsApp)");
|
|
8526
|
+
if (!["teams", "web", "whatsapp", "messenger", "wa"].includes(k) || val !== "on" && val !== "off") throw usage(t(`Bad --channels entry: ${pair}.`, `Entrada de --channels inv\xE1lida: ${pair}.`), t("Use teams|web|whatsapp|messenger|wa=on|off, comma-separated (wa = personal WhatsApp)", "Usa teams|web|whatsapp|messenger|wa=on|off, separados por comas (wa = WhatsApp personal)"));
|
|
7926
8527
|
ch[k] = val === "on";
|
|
7927
8528
|
}
|
|
7928
8529
|
return ch;
|
|
@@ -7940,20 +8541,20 @@ async function agentsCreate(v) {
|
|
|
7940
8541
|
const channels = parseChannels(v.channels);
|
|
7941
8542
|
if (channels) body.channels = channels;
|
|
7942
8543
|
if (v.like) Object.assign(body, { like: v.like, ...v["copy-dbs"] ? { copyDbs: true } : {}, ...v["dry-run"] ? { dryRun: true } : {}, ...v.board ? { board: v.board } : {}, ...v["no-notify"] ? { notify: false } : {} });
|
|
7943
|
-
else if (v["dry-run"] || v.board || v["no-notify"]) throw usage("--dry-run, --board and --no-notify go with --like.");
|
|
8544
|
+
else if (v["dry-run"] || v.board || v["no-notify"]) throw usage(t("--dry-run, --board and --no-notify go with --like.", "--dry-run, --board y --no-notify van con --like."));
|
|
7944
8545
|
if (v.like && v["dry-run"]) {
|
|
7945
8546
|
const p = await api("/api/v2/me/agents", { method: "POST", body });
|
|
7946
|
-
return out(p, () => `${dim(
|
|
7947
|
-
${p.plan.join("\n")}
|
|
7948
|
-
${dim("Not copied: prompt, env, channels. Dry run: nothing created.")}`);
|
|
8547
|
+
return out(p, () => `${dim(`${t("like", "como")} ${v.like} \xB7 ${p.engine ?? "?"} \xB7 ${p.model ?? t("default", "por defecto")}${p.workspace ? ` \xB7 ${t("workspace", "espacio")} ${p.workspace}` : ""}`)}
|
|
8548
|
+
${p.plan.map((x) => serverText(x)).join("\n")}
|
|
8549
|
+
${dim(t("Not copied: prompt, env, channels. Dry run: nothing created.", "No se copian: prompt, env, canales. Simulacro: no se cre\xF3 nada."))}`);
|
|
7949
8550
|
}
|
|
7950
8551
|
const r = await api("/api/v2/me/agents", { method: "POST", body });
|
|
7951
8552
|
const where = r.workspace ? `
|
|
7952
|
-
workspace: ${r.workspace}${r.handle ? ` \xB7 @${r.handle} in Teams` : dim(" \xB7 not active in Teams")}` : "";
|
|
8553
|
+
${t("workspace", "espacio")}: ${r.workspace}${r.handle ? ` \xB7 @${r.handle} ${t("in Teams", "en Teams")}` : dim(t(" \xB7 not active in Teams", " \xB7 no est\xE1 activo en Teams"))}` : "";
|
|
7953
8554
|
const copied = r.copied?.length ? `
|
|
7954
|
-
${r.copied.map((l) => ` ${l}`).join("\n")}
|
|
7955
|
-
${dim(" Now give it its own prompt: ghosty agents set " + r.id + " --prompt-file PROMPT.md")}` : "";
|
|
7956
|
-
out(r, () => `${lavender("\u2713")} Created ${r.name} ${dim(`(${r.engine} \xB7 ${r.model ?? "default"})`)}
|
|
8555
|
+
${r.copied.map((l) => ` ${serverText(l)}`).join("\n")}
|
|
8556
|
+
${dim(t(" Now give it its own prompt: ghosty agents set ", " Ahora dale su propio prompt: ghosty agents set ") + r.id + " --prompt-file PROMPT.md")}` : "";
|
|
8557
|
+
out(r, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${r.name} ${dim(`(${r.engine} \xB7 ${r.model ?? t("default", "por defecto")})`)}
|
|
7957
8558
|
id: ${r.id}${where}${copied}`);
|
|
7958
8559
|
}
|
|
7959
8560
|
async function agentsGet(id, fields, promptOut) {
|
|
@@ -7962,7 +8563,7 @@ async function agentsGet(id, fields, promptOut) {
|
|
|
7962
8563
|
const r = await api(agentPath(id, "?fields=prompt"));
|
|
7963
8564
|
const prompt = r.prompt ?? "";
|
|
7964
8565
|
await writeFile3(promptOut, prompt);
|
|
7965
|
-
return out({ saved: resolve(promptOut), bytes: Buffer.byteLength(prompt) }, () => `${lavender("\u2713")} Prompt \u2192 ${promptOut} ${dim(`(${Buffer.byteLength(prompt)} bytes) \xB7 back: ghosty agents set ${id} --prompt-file ${promptOut}`)}`);
|
|
8566
|
+
return out({ saved: resolve(promptOut), bytes: Buffer.byteLength(prompt) }, () => `${lavender("\u2713")} Prompt \u2192 ${promptOut} ${dim(`(${Buffer.byteLength(prompt)} bytes) \xB7 ${t("back", "de vuelta")}: ghosty agents set ${id} --prompt-file ${promptOut}`)}`);
|
|
7966
8567
|
}
|
|
7967
8568
|
const q = fields ? `?fields=${enc2(fields)}` : "?full=1";
|
|
7968
8569
|
out(await api(agentPath(id, q)));
|
|
@@ -7971,16 +8572,16 @@ async function agentsSet(id, v) {
|
|
|
7971
8572
|
id = need(id, "agent id", "ghosty agents set <agent-id> --prompt-file PROMPT.md");
|
|
7972
8573
|
if (v["prompt-base"]) {
|
|
7973
8574
|
const unlink = v["prompt-base"] === "none";
|
|
7974
|
-
if (!unlink && !v["prompt-overlay"]) throw usage("Missing --prompt-overlay FILE.",
|
|
8575
|
+
if (!unlink && !v["prompt-overlay"]) throw usage(t("Missing --prompt-overlay FILE.", "Falta --prompt-overlay FILE."), t("Its format", "Su formato") + ': {"edits":[{"what":"name","find":"Tania","replace":"Pia","all":true}]} (regex: true ' + t("for patterns", "para patrones") + ")");
|
|
7975
8576
|
const promptBase = unlink ? null : { agentId: v["prompt-base"], overlay: JSON.parse(await readFile3(v["prompt-overlay"], "utf8")) };
|
|
7976
8577
|
const r2 = await api(agentPath(id), { method: "PATCH", body: { promptBase, ...v["dry-run"] ? { dryRun: true } : {} } });
|
|
7977
8578
|
if (v["dry-run"] && r2.prompt) {
|
|
7978
8579
|
const out_ = v.out;
|
|
7979
8580
|
if (out_) await writeFile3(out_, r2.prompt);
|
|
7980
|
-
return out({ ...r2, ...out_ ? { prompt: void 0, saved: resolve(out_) } : {} }, () => `${r2.chars} chars (base ${r2.baseChars}): every edit applies
|
|
7981
|
-
${dim("Dry run: nothing changed.")}`);
|
|
8581
|
+
return out({ ...r2, ...out_ ? { prompt: void 0, saved: resolve(out_) } : {} }, () => `${t(`${r2.chars} chars (base ${r2.baseChars}): every edit applies.`, `${r2.chars} caracteres (base ${r2.baseChars}): todas las ediciones aplican.`)}${out_ ? t(` Written to ${out_}.`, ` Escrito en ${out_}.`) : dim(t(" --out FILE to read it.", " --out FILE para leerlo."))}
|
|
8582
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
7982
8583
|
}
|
|
7983
|
-
return out(r2, () => r2.unlinked ? `${lavender("\u2713")} Unlinked: the prompt stays as it is and is edited by hand again
|
|
8584
|
+
return out(r2, () => r2.unlinked ? `${lavender("\u2713")} ${t("Unlinked: the prompt stays as it is and is edited by hand again.", "Desenlazado: el prompt se queda como est\xE1 y se vuelve a editar a mano.")}` : `${lavender("\u2713")} Prompt = ${v["prompt-base"]} + ${v["prompt-overlay"]} ${t(`(${r2.chars} chars). It regenerates when the base changes; ghosty agents doctor ${id} tells if the overlay stops applying.`, `(${r2.chars} caracteres). Se regenera cuando cambia la base; ghosty agents doctor ${id} avisa si la capa deja de aplicar.`)}`);
|
|
7984
8585
|
}
|
|
7985
8586
|
const body = {};
|
|
7986
8587
|
if (v.name) body.name = v.name;
|
|
@@ -7995,9 +8596,9 @@ ${dim("Dry run: nothing changed.")}`);
|
|
|
7995
8596
|
const cur = await api(agentPath(id, "?fields=tools"));
|
|
7996
8597
|
const known = Object.keys(cur.tools?.toolsets ?? {});
|
|
7997
8598
|
const bad = items.map((x) => x.replace(/^[+-]/, "")).filter((x) => !known.includes(x));
|
|
7998
|
-
if (bad.length) throw usage(`Unknown toolset: ${bad.join(", ")}.`, `Known: ${known.join(", ")}`);
|
|
8599
|
+
if (bad.length) throw usage(t(`Unknown toolset: ${bad.join(", ")}.`, `Toolset desconocido: ${bad.join(", ")}.`), `${t("Known", "Conocidos")}: ${known.join(", ")}`);
|
|
7999
8600
|
const delta = items.every((x) => /^[+-]/.test(x));
|
|
8000
|
-
if (!delta && items.some((x) => /^[+-]/.test(x))) throw usage("Mix of plain and +/- toolsets.", "Either a full list (crm,sales) or changes (+crm,-sales).");
|
|
8601
|
+
if (!delta && items.some((x) => /^[+-]/.test(x))) throw usage(t("Mix of plain and +/- toolsets.", "Mezcla de toolsets simples y con +/-."), t("Either a full list (crm,sales) or changes (+crm,-sales).", "O una lista completa (crm,sales) o cambios (+crm,-sales)."));
|
|
8001
8602
|
const next = new Set(delta ? known.filter((k) => cur.tools?.toolsets?.[k]?.granted) : []);
|
|
8002
8603
|
for (const x of items) {
|
|
8003
8604
|
if (x.startsWith("-")) next.delete(x.slice(1));
|
|
@@ -8015,67 +8616,67 @@ ${dim("Dry run: nothing changed.")}`);
|
|
|
8015
8616
|
const voice = {};
|
|
8016
8617
|
if (v.voice) {
|
|
8017
8618
|
const [engine, ...rest] = v.voice.split(":");
|
|
8018
|
-
if (engine !== "elevenlabs" && engine !== "kokoro" || !rest.join(":")) throw usage(`Bad --voice: ${v.voice}.`, "Use elevenlabs:<voiceId> or kokoro:em_santa|em_alex|ef_dora");
|
|
8619
|
+
if (engine !== "elevenlabs" && engine !== "kokoro" || !rest.join(":")) throw usage(t(`Bad --voice: ${v.voice}.`, `--voice inv\xE1lido: ${v.voice}.`), t("Use", "Usa") + " elevenlabs:<voiceId> " + t("or", "o") + " kokoro:em_santa|em_alex|ef_dora");
|
|
8019
8620
|
voice.engine = engine;
|
|
8020
8621
|
voice.voiceId = rest.join(":");
|
|
8021
8622
|
}
|
|
8022
8623
|
if (v["voice-replies"]) {
|
|
8023
|
-
if (v["voice-replies"] !== "auto" && v["voice-replies"] !== "off") throw usage("--voice-replies takes auto or off.");
|
|
8624
|
+
if (v["voice-replies"] !== "auto" && v["voice-replies"] !== "off") throw usage(t("--voice-replies takes auto or off.", "--voice-replies acepta auto u off."));
|
|
8024
8625
|
voice.replies = v["voice-replies"];
|
|
8025
8626
|
}
|
|
8026
8627
|
if (Object.keys(voice).length) body.voice = voice;
|
|
8027
8628
|
if (v["auto-stage"]) {
|
|
8028
|
-
if (v["auto-stage"] !== "on" && v["auto-stage"] !== "off") throw usage("--auto-stage takes on or off.");
|
|
8629
|
+
if (v["auto-stage"] !== "on" && v["auto-stage"] !== "off") throw usage(t("--auto-stage takes on or off.", "--auto-stage acepta on u off."));
|
|
8029
8630
|
body.autoStage = v["auto-stage"] === "on";
|
|
8030
8631
|
}
|
|
8031
|
-
if (!Object.keys(body).length) throw usage("Nothing to change.", "Use --name, --engine, --model, --prompt, --prompt-file, --env, --channels, --toolsets, --connect, --disconnect, --voice, --voice-replies or --auto-stage.");
|
|
8632
|
+
if (!Object.keys(body).length) throw usage(t("Nothing to change.", "Nada que cambiar."), t("Use", "Usa") + " --name, --engine, --model, --prompt, --prompt-file, --env, --channels, --toolsets, --connect, --disconnect, --voice, --voice-replies " + t("or", "o") + " --auto-stage.");
|
|
8032
8633
|
const r = await api(agentPath(id), { method: "PATCH", body });
|
|
8033
|
-
out(r, () => `${lavender("\u2713")} Updated ${Object.keys(body).join(", ")}.`);
|
|
8634
|
+
out(r, () => `${lavender("\u2713")} ${t("Updated", "Actualizado")}: ${Object.keys(body).join(", ")}.`);
|
|
8034
8635
|
}
|
|
8035
8636
|
async function agentsRm(id, v = {}) {
|
|
8036
8637
|
id = need(id, "agent id", "ghosty agents rm <agent-id>");
|
|
8037
8638
|
requireYesIfHeadless(v.yes, `ghosty agents rm ${id}`);
|
|
8038
8639
|
if (!v.yes) {
|
|
8039
8640
|
const a = await api(agentPath(id, "?fields=name"));
|
|
8040
|
-
await confirm(`Delete agent ${a.name ?? id}? Its machine, memory and files go too. This can't be undone.`, { expect: a.name || id, retry: `ghosty agents rm ${id}` });
|
|
8641
|
+
await confirm(t(`Delete agent ${a.name ?? id}? Its machine, memory and files go too. This can't be undone.`, `\xBFBorrar el agente ${a.name ?? id}? Se van tambi\xE9n su m\xE1quina, memoria y archivos. No se puede deshacer.`), { expect: a.name || id, retry: `ghosty agents rm ${id}` });
|
|
8041
8642
|
}
|
|
8042
|
-
out(await api(agentPath(id), { method: "DELETE" }), () => `${lavender("\u2713")} Deleted ${id}.`);
|
|
8643
|
+
out(await api(agentPath(id), { method: "DELETE" }), () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${id}.`);
|
|
8043
8644
|
}
|
|
8044
8645
|
async function agentsShare(sub, id, email, v) {
|
|
8045
8646
|
id = need(id, "agent id", "ghosty agents share <agent-id> <email> [--role admin|editor]");
|
|
8046
8647
|
if (sub === "shares") {
|
|
8047
8648
|
const r2 = await api(agentPath(id, "/shares"));
|
|
8048
|
-
const rows = r2.shares.map((x) => ({ email: x.email, role: x.role, accepted: x.acceptedAt ? "yes" : "not yet" }));
|
|
8049
|
-
return out(r2, () => `${dim(
|
|
8050
|
-
${rows.length ? table(rows, ["email", "role", "accepted"]) : dim("Not shared with anyone.")}`);
|
|
8649
|
+
const rows = r2.shares.map((x) => ({ email: x.email, role: x.role, accepted: x.acceptedAt ? t("yes", "s\xED") : t("not yet", "todav\xEDa no") }));
|
|
8650
|
+
return out(r2, () => `${dim(`${t("owner", "due\xF1o")}: ${r2.owner ?? "?"}`)}
|
|
8651
|
+
${rows.length ? table(rows, ["email", "role", "accepted"]) : dim(t("Not shared with anyone.", "No est\xE1 compartido con nadie."))}`);
|
|
8051
8652
|
}
|
|
8052
8653
|
email = need(email, "email", `ghosty agents ${sub} ${id} ana@empresa.com`);
|
|
8053
8654
|
if (sub === "unshare") {
|
|
8054
8655
|
requireYesIfHeadless(v.yes, `ghosty agents unshare ${id} ${email}`);
|
|
8055
|
-
if (!v.yes) await confirm(`Remove ${email}'s access? On ACP agents this rotates the connection token (apps reconnect).`, { retry: `ghosty agents unshare ${id} ${email}` });
|
|
8656
|
+
if (!v.yes) await confirm(t(`Remove ${email}'s access? On ACP agents this rotates the connection token (apps reconnect).`, `\xBFQuitarle el acceso a ${email}? En agentes ACP esto rota el token de conexi\xF3n (las apps se reconectan).`), { retry: `ghosty agents unshare ${id} ${email}` });
|
|
8056
8657
|
const r2 = await api(agentPath(id, "/shares"), { method: "DELETE", body: { email } });
|
|
8057
|
-
return out(r2, () => `${lavender("\u2713")} ${email} no longer has access
|
|
8658
|
+
return out(r2, () => `${lavender("\u2713")} ${t(`${email} no longer has access.`, `${email} ya no tiene acceso.`)}${r2.tokenRotated ? dim(t(" Token rotated.", " Token rotado.")) : ""}`);
|
|
8058
8659
|
}
|
|
8059
|
-
if (v.role && v.role !== "admin" && v.role !== "editor") throw usage("--role takes admin or editor.");
|
|
8660
|
+
if (v.role && v.role !== "admin" && v.role !== "editor") throw usage(t("--role takes admin or editor.", "--role acepta admin o editor."));
|
|
8060
8661
|
const r = await api(agentPath(id, "/shares"), { method: "POST", body: { email, ...v.role ? { role: v.role } : {} } });
|
|
8061
|
-
out(r, () => `${lavender("\u2713")} ${r.already ? `Already shared with ${r.email}` : `Shared with ${r.email}; they got an email`} ${dim(`(${r.role})`)}.`);
|
|
8662
|
+
out(r, () => `${lavender("\u2713")} ${r.already ? t(`Already shared with ${r.email}`, `Ya estaba compartido con ${r.email}`) : t(`Shared with ${r.email}; they got an email`, `Compartido con ${r.email}; le lleg\xF3 un correo`)} ${dim(`(${r.role})`)}.`);
|
|
8062
8663
|
}
|
|
8063
8664
|
async function agentsRestart(id) {
|
|
8064
8665
|
id = need(id, "agent id", "ghosty agents restart <agent-id>");
|
|
8065
|
-
out(await api(agentPath(id, "/restart"), { method: "POST", body: {} }), () => `${lavender("\u2713")} Restarting \u2014 the next turn uses the new config
|
|
8666
|
+
out(await api(agentPath(id, "/restart"), { method: "POST", body: {} }), () => `${lavender("\u2713")} ${t("Restarting \u2014 the next turn uses the new config.", "Reiniciando: el siguiente turno usa la config nueva.")}`);
|
|
8066
8667
|
}
|
|
8067
8668
|
async function tryTurn(id, text, v) {
|
|
8068
8669
|
id = need(id, "agent id", 'ghosty try <agent-id> "hello"');
|
|
8069
8670
|
if (v.replay) return tryReplay(id, v);
|
|
8070
|
-
if (v.whatsapp && !v.waba) info(dim("Note: --whatsapp is now --waba (the real WhatsApp path)."));
|
|
8671
|
+
if (v.whatsapp && !v.waba) info(dim(t("Note: --whatsapp is now --waba (the real WhatsApp path).", "Nota: --whatsapp ahora es --waba (el camino real de WhatsApp).")));
|
|
8071
8672
|
if (v.waba || v.whatsapp || v.media?.length || v.history) return tryWaba(id, text, v);
|
|
8072
|
-
if (!text && !v.reset) throw usage("Missing message.", '
|
|
8073
|
-
if (process.stderr.isTTY && text && !globals.json) info(dim("Thinking\u2026 (a test turn can take up to 3 minutes)"));
|
|
8673
|
+
if (!text && !v.reset) throw usage(t("Missing message.", "Falta el mensaje."), t("Example", "Ejemplo") + ': ghosty try <agent-id> "hello"');
|
|
8674
|
+
if (process.stderr.isTTY && text && !globals.json) info(dim(t("Thinking\u2026 (a test turn can take up to 3 minutes)", "Pensando\u2026 (un turno de prueba puede tardar hasta 3 minutos)")));
|
|
8074
8675
|
const r = await api(agentPath(id, "/try"), {
|
|
8075
8676
|
body: { text, session: v.session, reset: v.reset }
|
|
8076
8677
|
});
|
|
8077
8678
|
out(r, () => (r.text ?? "") + (r.error ? `
|
|
8078
|
-
${dim(`(warning: ${r.error})`)}` : ""));
|
|
8679
|
+
${dim(`(${t("warning", "aviso")}: ${serverText(r.error)})`)}` : ""));
|
|
8079
8680
|
}
|
|
8080
8681
|
async function filesCmd(sub, id, path, v) {
|
|
8081
8682
|
const ex = "ghosty files ls <agent-id> | get <agent-id> <path> | put <agent-id> <path> --file <local> | rm <agent-id> <path>";
|
|
@@ -8095,7 +8696,7 @@ ${table(r.files ?? [], cols)}`);
|
|
|
8095
8696
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
8096
8697
|
if (v.out) {
|
|
8097
8698
|
await writeFile3(v.out, bytes);
|
|
8098
|
-
out({ path: p, saved: v.out, bytes: bytes.length }, () => `${lavender("\u2713")} Saved ${bytes.length} bytes to ${v.out}`);
|
|
8699
|
+
out({ path: p, saved: v.out, bytes: bytes.length }, () => `${lavender("\u2713")} ${t(`Saved ${bytes.length} bytes to ${v.out}`, `Se guardaron ${bytes.length} bytes en ${v.out}`)}`);
|
|
8099
8700
|
} else process.stdout.write(bytes);
|
|
8100
8701
|
return;
|
|
8101
8702
|
}
|
|
@@ -8103,21 +8704,21 @@ ${table(r.files ?? [], cols)}`);
|
|
|
8103
8704
|
need(path, "path", ex);
|
|
8104
8705
|
const data = v.file ? await readFile3(v.file) : await readStdin();
|
|
8105
8706
|
const r = await api(agentPath(id, `/files/${p}`), { method: "PUT", raw: new Uint8Array(data), headers: { "content-type": "application/octet-stream" } });
|
|
8106
|
-
out(r ?? { path: p, bytes: data.length }, () => `${lavender("\u2713")} Wrote ${p} (${data.length} bytes).`);
|
|
8707
|
+
out(r ?? { path: p, bytes: data.length }, () => `${lavender("\u2713")} ${t("Wrote", "Escrito")} ${p} (${data.length} bytes).`);
|
|
8107
8708
|
return;
|
|
8108
8709
|
}
|
|
8109
8710
|
case "rm": {
|
|
8110
8711
|
need(path, "path", ex);
|
|
8111
|
-
await confirm(`Delete ${p} from the agent's machine?`, { yes: v.yes, retry: `ghosty files rm ${id} ${p}` });
|
|
8112
|
-
out(await api(agentPath(id, `/files/${p}`), { method: "DELETE" }) ?? { deleted: p }, () => `${lavender("\u2713")} Deleted ${p}`);
|
|
8712
|
+
await confirm(t(`Delete ${p} from the agent's machine?`, `\xBFBorrar ${p} de la m\xE1quina del agente?`), { yes: v.yes, retry: `ghosty files rm ${id} ${p}` });
|
|
8713
|
+
out(await api(agentPath(id, `/files/${p}`), { method: "DELETE" }) ?? { deleted: p }, () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${p}`);
|
|
8113
8714
|
return;
|
|
8114
8715
|
}
|
|
8115
8716
|
default:
|
|
8116
|
-
throw usage(`Unknown files subcommand: ${sub ?? "(none)"}.`, ex);
|
|
8717
|
+
throw usage(t(`Unknown files subcommand: ${sub ?? "(none)"}.`, `Subcomando de files desconocido: ${sub ?? "(ninguno)"}.`), ex);
|
|
8117
8718
|
}
|
|
8118
8719
|
}
|
|
8119
8720
|
async function readStdin() {
|
|
8120
|
-
if (process.stdin.isTTY) throw usage("Nothing to upload.", "Use --file <local> or pipe the content through stdin.");
|
|
8721
|
+
if (process.stdin.isTTY) throw usage(t("Nothing to upload.", "Nada que subir."), t("Use --file <local> or pipe the content through stdin.", "Usa --file <local> o pasa el contenido por stdin."));
|
|
8121
8722
|
const parts = [];
|
|
8122
8723
|
for await (const c of process.stdin) parts.push(c);
|
|
8123
8724
|
return Buffer.concat(parts);
|
|
@@ -8144,7 +8745,7 @@ async function readSkillDir(dir) {
|
|
|
8144
8745
|
if (rel === "SKILL.md") markdown = await readFile3(full, "utf8");
|
|
8145
8746
|
else if (!rel.split("/").some((part) => part.startsWith("."))) assets.push({ name: rel, contentBase64: (await readFile3(full)).toString("base64") });
|
|
8146
8747
|
}
|
|
8147
|
-
if (!markdown) throw usage(`No SKILL.md in ${dir}.`, "A skill folder has SKILL.md at its root (agentskills.io).");
|
|
8748
|
+
if (!markdown) throw usage(t(`No SKILL.md in ${dir}.`, `No hay SKILL.md en ${dir}.`), t("A skill folder has SKILL.md at its root (agentskills.io).", "Una carpeta de skill lleva SKILL.md en su ra\xEDz (agentskills.io)."));
|
|
8148
8749
|
return { markdown, assets };
|
|
8149
8750
|
}
|
|
8150
8751
|
var trashDir = (id, slug) => join3(process.env.XDG_CONFIG_HOME || join3(homedir2(), ".config"), "ghosty", "trash", id, `${slug}-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`);
|
|
@@ -8162,7 +8763,7 @@ async function skillsCmd(sub, id, slug, v) {
|
|
|
8162
8763
|
];
|
|
8163
8764
|
const more = r.community.filter((c) => !c.installed).length;
|
|
8164
8765
|
out(r, () => `${table(rows, ["slug", "source", "on", "description"])}
|
|
8165
|
-
${dim(`${more} more in the community catalog: ghosty skills add <agent-id> <slug> (--json lists them) \xB7 overlaps: ghosty agents doctor ${id}`)}`);
|
|
8766
|
+
${dim(t(`${more} more in the community catalog: ghosty skills add <agent-id> <slug> (--json lists them) \xB7 overlaps: ghosty agents doctor ${id}`, `${more} m\xE1s en el cat\xE1logo de la comunidad: ghosty skills add <agent-id> <slug> (--json las lista) \xB7 traslapes: ghosty agents doctor ${id}`))}`);
|
|
8166
8767
|
return;
|
|
8167
8768
|
}
|
|
8168
8769
|
case "house": {
|
|
@@ -8171,20 +8772,20 @@ ${dim(`${more} more in the community catalog: ghosty skills add <agent-id> <slug
|
|
|
8171
8772
|
if (!only && !on && !off) {
|
|
8172
8773
|
const r2 = await api(agentPath(id, "/skills/house"));
|
|
8173
8774
|
out(r2, () => `${table(r2.house.map((h) => ({ slug: h.slug, on: h.enabled ? "on" : "off" })), ["slug", "on"])}
|
|
8174
|
-
${dim(
|
|
8775
|
+
${dim(`${t("Change", "Cambiar")}: ghosty skills house ${id} --only a,b | --enable a | --disable b [--dry-run]`)}`);
|
|
8175
8776
|
return;
|
|
8176
8777
|
}
|
|
8177
8778
|
const body = { ...only ? { only } : {}, ...on ? { on } : {}, ...off ? { off } : {}, ...v["dry-run"] ? { dryRun: true } : {} };
|
|
8178
8779
|
const r = await api(agentPath(id, "/skills/house"), { method: "PUT", body });
|
|
8179
|
-
out(r, () => `${r.changes.map((c) => `${c.enabled ? "+" : "-"} ${c.slug}`).join("\n") || dim("Nothing to change.")}
|
|
8180
|
-
${dim(
|
|
8780
|
+
out(r, () => `${r.changes.map((c) => `${c.enabled ? "+" : "-"} ${c.slug}`).join("\n") || dim(t("Nothing to change.", "Nada que cambiar."))}
|
|
8781
|
+
${dim(`${t("On now", "Encendidas ahora")}: ${r.on.join(", ") || t("none", "ninguna")}${r.dryRun ? t(" \xB7 dry run: nothing changed", " \xB7 simulacro: no se cambi\xF3 nada") : ""}`)}`);
|
|
8181
8782
|
return;
|
|
8182
8783
|
}
|
|
8183
8784
|
case "on":
|
|
8184
8785
|
case "off": {
|
|
8185
8786
|
slug = need(slug, "skill slug", `ghosty skills ${sub} <agent-id> <slug>`);
|
|
8186
8787
|
const r = await api(agentPath(id, `/skills/${enc2(slug)}`), { method: "PATCH", body: { enabled: sub === "on" } });
|
|
8187
|
-
out(r, () => `${lavender("\u2713")} ${slug} ${sub === "on" ? "on" : "off"} ${dim(`(${r.source === "house" ? "Ghosty's" : "own"}${r.nota ? `; ${r.nota}` : ""})`)}`);
|
|
8788
|
+
out(r, () => `${lavender("\u2713")} ${slug} ${sub === "on" ? "on" : "off"} ${dim(`(${r.source === "house" ? t("Ghosty's", "de Ghosty") : t("own", "propia")}${r.nota ? `; ${serverText(r.nota)}` : ""})`)}`);
|
|
8188
8789
|
return;
|
|
8189
8790
|
}
|
|
8190
8791
|
case "get": {
|
|
@@ -8193,27 +8794,27 @@ ${dim(`On now: ${r.on.join(", ") || "none"}${r.dryRun ? " \xB7 dry run: nothing
|
|
|
8193
8794
|
if (!v.out) {
|
|
8194
8795
|
const files = sk.files.map((f) => ({ path: f.path, bytes: Buffer.from(f.contentBase64, "base64").length }));
|
|
8195
8796
|
out({ slug, files, saved: null }, () => `${table(files, ["path", "bytes"])}
|
|
8196
|
-
${dim(
|
|
8797
|
+
${dim(`${t("Download", "Descargar")}: ghosty skills get ${id} ${slug} --out DIR`)}`);
|
|
8197
8798
|
return;
|
|
8198
8799
|
}
|
|
8199
8800
|
const dir = resolve(v.out);
|
|
8200
8801
|
const bytes = await writeSkill(dir, sk);
|
|
8201
|
-
out({ slug, saved: dir, files: sk.files.map((f) => f.path), bytes }, () => `${lavender("\u2713")} ${slug} \u2192 ${dir}/ ${dim(`(${sk.files.length} file(s), ${bytes} bytes)`)}`);
|
|
8802
|
+
out({ slug, saved: dir, files: sk.files.map((f) => f.path), bytes }, () => `${lavender("\u2713")} ${slug} \u2192 ${dir}/ ${dim(t(`(${sk.files.length} file(s), ${bytes} bytes)`, `(${sk.files.length} archivo(s), ${bytes} bytes)`))}`);
|
|
8202
8803
|
return;
|
|
8203
8804
|
}
|
|
8204
8805
|
case "add": {
|
|
8205
8806
|
slug = need(slug, "skill slug", ex);
|
|
8206
|
-
if (v.file && v.dir) throw usage("Use --file or --dir, not both.");
|
|
8807
|
+
if (v.file && v.dir) throw usage(t("Use --file or --dir, not both.", "Usa --file o --dir, no los dos."));
|
|
8207
8808
|
const body = v.dir ? await readSkillDir(v.dir) : v.file ? { markdown: await readFile3(v.file, "utf8") } : { fromCatalog: true };
|
|
8208
8809
|
const r = await api(agentPath(id, `/skills/${enc2(slug)}`), { method: "PUT", body });
|
|
8209
|
-
out(r, () => `${lavender("\u2713")} Skill ${slug} installed
|
|
8810
|
+
out(r, () => `${lavender("\u2713")} ${t(`Skill ${slug} installed.`, `Skill ${slug} instalada.`)}${r?.nota ? dim(` (${serverText(r.nota)})`) : ""}`);
|
|
8210
8811
|
return;
|
|
8211
8812
|
}
|
|
8212
8813
|
case "templates": {
|
|
8213
8814
|
const r = await api(agentPath(id, "/templates"));
|
|
8214
|
-
const rows = r.templates.map((
|
|
8815
|
+
const rows = r.templates.map((tp) => ({ name: tp.name, source: tp.source, description: (tp.description ?? "").replace(/\s+/g, " ").slice(0, 60) }));
|
|
8215
8816
|
out(r, () => `${table(rows, ["name", "source", "description"])}
|
|
8216
|
-
${dim("Its own: put <name>.html in <skill>/pdf-templates/ of any of its skills (it wins over a house one with the same name).")}`);
|
|
8817
|
+
${dim(t("Its own: put <name>.html in <skill>/pdf-templates/ of any of its skills (it wins over a house one with the same name).", "Las suyas: pon <name>.html en <skill>/pdf-templates/ de cualquiera de sus skills (le gana a la de casa con el mismo nombre)."))}`);
|
|
8217
8818
|
return;
|
|
8218
8819
|
}
|
|
8219
8820
|
case "rm":
|
|
@@ -8221,18 +8822,19 @@ ${dim("Its own: put <name>.html in <skill>/pdf-templates/ of any of its skills (
|
|
|
8221
8822
|
slug = need(slug, "skill slug", ex);
|
|
8222
8823
|
requireYesIfHeadless(v.yes, `ghosty skills rm ${id} ${slug}`);
|
|
8223
8824
|
const sk = await api(agentPath(id, `/skills/${enc2(slug)}`)).catch(() => null);
|
|
8224
|
-
await confirm(`Remove skill ${slug} from agent ${id}?${sk ? " A local copy is kept." : ""}`, { yes: v.yes, retry: `ghosty skills rm ${id} ${slug}` });
|
|
8825
|
+
await confirm(t(`Remove skill ${slug} from agent ${id}?${sk ? " A local copy is kept." : ""}`, `\xBFQuitarle la skill ${slug} al agente ${id}?${sk ? " Se guarda una copia local." : ""}`), { yes: v.yes, retry: `ghosty skills rm ${id} ${slug}` });
|
|
8225
8826
|
const backup = sk ? trashDir(id, slug) : null;
|
|
8226
8827
|
if (sk && backup) await writeSkill(backup, sk);
|
|
8227
8828
|
const r = await api(agentPath(id, `/skills/${enc2(slug)}`), { method: "DELETE" }) ?? { deleted: slug };
|
|
8228
8829
|
const restore = backup ? `ghosty skills add ${id} ${slug} --dir "${backup}"` : null;
|
|
8229
|
-
out({ ...r, backup, restore }, () => `${lavender("\u2713")} Skill ${slug} removed
|
|
8230
|
-
${dim(`Backup: ${backup}
|
|
8231
|
-
Undo: ${restore}`
|
|
8830
|
+
out({ ...r, backup, restore }, () => `${lavender("\u2713")} ${t(`Skill ${slug} removed.`, `Skill ${slug} quitada.`)}${restore ? `
|
|
8831
|
+
${dim(t(`Backup: ${backup}
|
|
8832
|
+
Undo: ${restore}`, `Respaldo: ${backup}
|
|
8833
|
+
Deshacer: ${restore}`))}` : ""}`);
|
|
8232
8834
|
return;
|
|
8233
8835
|
}
|
|
8234
8836
|
default:
|
|
8235
|
-
throw usage(`Unknown skills subcommand: ${sub}.`, ex);
|
|
8837
|
+
throw usage(t(`Unknown skills subcommand: ${sub}.`, `Subcomando de skills desconocido: ${sub}.`), ex);
|
|
8236
8838
|
}
|
|
8237
8839
|
}
|
|
8238
8840
|
async function mcpCmd(sub, id, v) {
|
|
@@ -8244,13 +8846,13 @@ async function mcpCmd(sub, id, v) {
|
|
|
8244
8846
|
const parsed = JSON.parse(await readFile3(file, "utf8"));
|
|
8245
8847
|
const body = { servers: Array.isArray(parsed) ? parsed : parsed.servers ?? [] };
|
|
8246
8848
|
const r = await api(agentPath(id, "/mcp"), { method: "PUT", body });
|
|
8247
|
-
return out(r, () => `${lavender("\u2713")} MCP servers replaced \u2014 ${r?.reiniciado ? "the agent restarted with them." : "they load on the next turn."}`);
|
|
8849
|
+
return out(r, () => `${lavender("\u2713")} ${t("MCP servers replaced \u2014", "Servidores MCP reemplazados:")} ${r?.reiniciado ? t("the agent restarted with them.", "el agente se reinici\xF3 con ellos.") : t("they load on the next turn.", "entran en el siguiente turno.")}`);
|
|
8248
8850
|
}
|
|
8249
|
-
throw usage(`Unknown mcp subcommand: ${sub ?? "(none)"}.`, ex);
|
|
8851
|
+
throw usage(t(`Unknown mcp subcommand: ${sub ?? "(none)"}.`, `Subcomando de mcp desconocido: ${sub ?? "(ninguno)"}.`), ex);
|
|
8250
8852
|
}
|
|
8251
8853
|
async function agentsDemo(id, v) {
|
|
8252
8854
|
id = need(id, "agent id", "ghosty agents demo <agent-id> --slug guardias --on");
|
|
8253
|
-
if (v.on && v.off) throw usage("Use --on or --off, not both.");
|
|
8855
|
+
if (v.on && v.off) throw usage(t("Use --on or --off, not both.", "Usa --on o --off, no los dos."));
|
|
8254
8856
|
const body = {};
|
|
8255
8857
|
if (v.slug) body.slug = v.slug;
|
|
8256
8858
|
if (v.on) body.enabled = true;
|
|
@@ -8263,20 +8865,28 @@ async function agentsDemo(id, v) {
|
|
|
8263
8865
|
if (v.starter !== void 0) body.starter = v.starter || null;
|
|
8264
8866
|
if (v.rotate) body.rotate = true;
|
|
8265
8867
|
if (v.sponsor !== void 0) {
|
|
8266
|
-
if (v.sponsor !== "on" && v.sponsor !== "off") throw usage("--sponsor takes on or off.");
|
|
8868
|
+
if (v.sponsor !== "on" && v.sponsor !== "off") throw usage(t("--sponsor takes on or off.", "--sponsor acepta on u off."));
|
|
8267
8869
|
body.sponsor = v.sponsor === "on";
|
|
8268
8870
|
}
|
|
8269
8871
|
const d = Object.keys(body).length ? await api(agentPath(id, "/demo"), { method: "PUT", body }) : await api(agentPath(id, "/demo"));
|
|
8270
8872
|
out(
|
|
8271
8873
|
d,
|
|
8272
8874
|
() => [
|
|
8273
|
-
`${d.enabled ? lavender("\u25CF on") : dim("\u25CB off")} ${d.url ?? dim("(no link yet: --slug NAME)")}`,
|
|
8274
|
-
dim(
|
|
8275
|
-
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
|
|
8875
|
+
`${d.enabled ? lavender(t("\u25CF on", "\u25CF encendido")) : dim(t("\u25CB off", "\u25CB apagado"))} ${d.url ?? dim(t("(no link yet: --slug NAME)", "(todav\xEDa sin liga: --slug NAME)"))}`,
|
|
8876
|
+
dim(
|
|
8877
|
+
t(
|
|
8878
|
+
`expires: ${d.venceEl ? new Date(d.venceEl).toLocaleString("es-MX") : "never"} \xB7 cap ${d.maxTurns} messages per visitor \xB7 ${d.perIpDay} per IP per day`,
|
|
8879
|
+
`vence: ${d.venceEl ? new Date(d.venceEl).toLocaleString("es-MX") : "nunca"} \xB7 tope ${d.maxTurns} mensajes por visitante \xB7 ${d.perIpDay} por IP al d\xEDa`
|
|
8880
|
+
)
|
|
8881
|
+
),
|
|
8882
|
+
d.welcome ? `${t("welcome", "bienvenida")}: ${d.welcome}` : dim(t("welcome: (default greeting)", "bienvenida: (saludo por defecto)")),
|
|
8883
|
+
d.starter ? `${t("starter", "inicial")}: \u2728 ${d.starter}` : dim(t("starter: (none)", "inicial: (ninguna)")),
|
|
8884
|
+
d.chips.length ? `chips: ${d.chips.join(" | ")}` : dim(t("chips: (default)", "chips: (por defecto)")),
|
|
8885
|
+
t(
|
|
8886
|
+
`tried ${d.stats.pruebas} \xB7 messages ${d.stats.mensajes} \xB7 kept ${d.stats.clones} (${d.stats.patrocinados} on your key)`,
|
|
8887
|
+
`pruebas ${d.stats.pruebas} \xB7 mensajes ${d.stats.mensajes} \xB7 se lo quedaron ${d.stats.clones} (${d.stats.patrocinados} con tu llave)`
|
|
8888
|
+
),
|
|
8889
|
+
...d.lastError ? [red(`${t("last failure", "\xFAltima falla")} ${d.lastError.at}: ${serverText(d.lastError.message ?? d.lastError.error ?? "")}`)] : []
|
|
8280
8890
|
].join("\n")
|
|
8281
8891
|
);
|
|
8282
8892
|
}
|
|
@@ -8284,8 +8894,8 @@ async function agentsBox(id, v) {
|
|
|
8284
8894
|
id = need(id, "agent id", "ghosty agents box <agent-id> [--check PATH] [--contains PATH=TEXT] [--recycle]");
|
|
8285
8895
|
if (v.recycle) {
|
|
8286
8896
|
const r2 = await api(agentPath(id, "/box"), { method: "POST", body: { recycle: true } });
|
|
8287
|
-
out(r2, () => `${lavender("\u2713")} Recycled. ${dim(r2?.note ?? "")}
|
|
8288
|
-
${dim(
|
|
8897
|
+
out(r2, () => `${lavender("\u2713")} ${t("Recycled.", "Reciclada.")} ${dim(serverText(r2?.note ?? ""))}
|
|
8898
|
+
${dim(`${t("Verify after one turn", "Verifica despu\xE9s de un turno")}: ghosty agents box ${id} --check <path>`)}`);
|
|
8289
8899
|
return;
|
|
8290
8900
|
}
|
|
8291
8901
|
const q = new URLSearchParams();
|
|
@@ -8293,12 +8903,12 @@ ${dim(`Verify after one turn: ghosty agents box ${id} --check <path>`)}`);
|
|
|
8293
8903
|
for (const c of v.contains ?? []) q.append("contains", c);
|
|
8294
8904
|
const r = await api(agentPath(id, `/box?${q}`));
|
|
8295
8905
|
out(r, () => {
|
|
8296
|
-
if (!r.boxes.length) return dim(r.note
|
|
8906
|
+
if (!r.boxes.length) return dim(r.note ? serverText(r.note) : t("No live box.", "No hay caja viva."));
|
|
8297
8907
|
const lines = r.boxes.flatMap((b) => [
|
|
8298
|
-
`${bold(b.sandboxId)} ${dim(b.lastMessageAt ?
|
|
8908
|
+
`${bold(b.sandboxId)} ${dim(b.lastMessageAt ? `${t("last message", "\xFAltimo mensaje")} ${b.lastMessageAt}` : r.kind)}${b.error ? ` ${red(serverText(b.error))}` : ""}`,
|
|
8299
8909
|
...b.checks.map((c) => ` ${c.ok ? lavender("\u2713") : red("\u2717")} ${c.path}${c.contains !== void 0 ? ` \u220B ${c.contains}` : ""}`)
|
|
8300
8910
|
]);
|
|
8301
|
-
const verdict = !(v.check?.length || v.contains?.length) ? dim("Alive. Add --check PATH to confirm the new image.") : r.stale ? red(`${r.stale} box(es) on an OLD image: ghosty agents box ${id} --recycle`) : `${lavender("\u2713")} Serving with the expected image
|
|
8911
|
+
const verdict = !(v.check?.length || v.contains?.length) ? dim(t("Alive. Add --check PATH to confirm the new image.", "Viva. Agrega --check PATH para confirmar la imagen nueva.")) : r.stale ? red(t(`${r.stale} box(es) on an OLD image: ghosty agents box ${id} --recycle`, `${r.stale} caja(s) con imagen VIEJA: ghosty agents box ${id} --recycle`)) : `${lavender("\u2713")} ${t("Serving with the expected image.", "Atiende con la imagen esperada.")}`;
|
|
8302
8912
|
return [...lines, verdict].join("\n");
|
|
8303
8913
|
});
|
|
8304
8914
|
}
|
|
@@ -8345,20 +8955,20 @@ function parseData(e) {
|
|
|
8345
8955
|
var enc3 = encodeURIComponent;
|
|
8346
8956
|
var convPath = (agent, rest = "") => `/api/v2/me/agents/${enc3(agent)}/conversations${rest}`;
|
|
8347
8957
|
async function conversationsLs(agent) {
|
|
8348
|
-
if (!agent) throw usage("Missing agent id.", "Example: ghosty conversations ls <agent-id>");
|
|
8958
|
+
if (!agent) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example: ghosty conversations ls <agent-id>", "Ejemplo: ghosty conversations ls <agent-id>"));
|
|
8349
8959
|
const r = await api(convPath(agent));
|
|
8350
8960
|
const rows = r.conversaciones.map((c) => ({ id: c.id, title: c.titulo, updated: c.actualizada }));
|
|
8351
8961
|
out(rows, () => table(rows, ["id", "title", "updated"]));
|
|
8352
8962
|
}
|
|
8353
8963
|
async function conversationsShow(agent, sid, v) {
|
|
8354
|
-
if (!agent || !sid) throw usage("Missing agent id or conversation id.", "Example: ghosty conversations show <agent-id> <conversation-id> [--limit 20]");
|
|
8964
|
+
if (!agent || !sid) throw usage(t("Missing agent id or conversation id.", "Falta el id del agente o de la conversaci\xF3n."), t("Example: ", "Ejemplo: ") + "ghosty conversations show <agent-id> <conversation-id> [--limit 20]");
|
|
8355
8965
|
const tail = v.limit ?? "20";
|
|
8356
8966
|
const r = await api(convPath(agent, `/${enc3(sid)}?tail=${enc3(tail)}`));
|
|
8357
8967
|
out(r, () => {
|
|
8358
|
-
const lines = r.messages.map((m) => `${bold(m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role)} ${m.text.trim()}`);
|
|
8359
|
-
const head = r.saltados ? [dim(`(${r.saltados} earlier messages not shown \xB7 --limit 0 for all)`)] : [];
|
|
8360
|
-
const tailNote = r.enCurso ? [dim("(a turn is running: its answer is not here yet)")] : [];
|
|
8361
|
-
return [...head, ...lines, ...tailNote].join("\n\n") || dim("(empty)");
|
|
8968
|
+
const lines = r.messages.map((m) => `${bold(m.role === "user" ? t("User", "Usuario") : m.role === "assistant" ? t("Agent", "Agente") : m.role)} ${m.text.trim()}`);
|
|
8969
|
+
const head = r.saltados ? [dim(t(`(${r.saltados} earlier messages not shown \xB7 --limit 0 for all)`, `(${r.saltados} mensajes anteriores sin mostrar \xB7 --limit 0 para verlos todos)`))] : [];
|
|
8970
|
+
const tailNote = r.enCurso ? [dim(t("(a turn is running: its answer is not here yet)", "(hay un turno en curso: su respuesta todav\xEDa no est\xE1 aqu\xED)"))] : [];
|
|
8971
|
+
return [...head, ...lines, ...tailNote].join("\n\n") || dim(t("(empty)", "(vac\xEDa)"));
|
|
8362
8972
|
});
|
|
8363
8973
|
}
|
|
8364
8974
|
async function turn(agent, sid, text, first) {
|
|
@@ -8369,7 +8979,7 @@ async function turn(agent, sid, text, first) {
|
|
|
8369
8979
|
const onSigint = () => {
|
|
8370
8980
|
if (cancelled) process.exit(130);
|
|
8371
8981
|
cancelled = true;
|
|
8372
|
-
info(dim("\n(cancelling\u2026 press Ctrl-C again to quit)"));
|
|
8982
|
+
info(dim(t("\n(cancelling\u2026 press Ctrl-C again to quit)", "\n(cancelando\u2026 presiona Ctrl-C otra vez para salir)")));
|
|
8373
8983
|
void api(convPath(agent, `/${enc3(sid)}/cancel`), { method: "POST", body: {} }).catch(() => {
|
|
8374
8984
|
});
|
|
8375
8985
|
};
|
|
@@ -8390,11 +9000,11 @@ async function turn(agent, sid, text, first) {
|
|
|
8390
9000
|
else process.stdout.write(d.text);
|
|
8391
9001
|
} else if (ev.event === "tool" && d.title) {
|
|
8392
9002
|
if (globals.json) process.stdout.write(JSON.stringify({ event: "tool", ...d }) + "\n");
|
|
8393
|
-
else if (!d.status || d.status === "pending" || d.status === "in_progress") info(dim(` \u2699 ${d.title}`));
|
|
9003
|
+
else if (!d.status || d.status === "pending" || d.status === "in_progress") info(dim(` \u2699 ${serverText(d.title)}`));
|
|
8394
9004
|
} else if (ev.event === "usage") {
|
|
8395
9005
|
usageData = d;
|
|
8396
9006
|
} else if (ev.event === "error") {
|
|
8397
|
-
throw new CliError(d.message
|
|
9007
|
+
throw new CliError(d.message ? serverText(d.message) : t("The turn failed.", "El turno fall\xF3."), EXIT.API);
|
|
8398
9008
|
} else if (ev.event === "done") {
|
|
8399
9009
|
break;
|
|
8400
9010
|
}
|
|
@@ -8408,13 +9018,13 @@ async function turn(agent, sid, text, first) {
|
|
|
8408
9018
|
return { text: answer, usage: usageData };
|
|
8409
9019
|
}
|
|
8410
9020
|
async function chat(agent, message, v) {
|
|
8411
|
-
if (!agent) throw usage("Missing agent id.", 'Example: ghosty chat <agent-id> "hello" (ghosty agents ls to find it)');
|
|
9021
|
+
if (!agent) throw usage(t("Missing agent id.", "Falta el id del agente."), t('Example: ghosty chat <agent-id> "hello" (ghosty agents ls to find it)', 'Ejemplo: ghosty chat <agent-id> "hola" (ghosty agents ls para encontrarlo)'));
|
|
8412
9022
|
let sid = v.conversation;
|
|
8413
9023
|
let first = false;
|
|
8414
9024
|
if (!sid) {
|
|
8415
9025
|
sid = (await api(convPath(agent), { method: "POST", body: {} })).id;
|
|
8416
9026
|
first = true;
|
|
8417
|
-
if (!globals.json) info(dim(`conversation ${sid} (continue it with --conversation ${sid})`));
|
|
9027
|
+
if (!globals.json) info(dim(t(`conversation ${sid} (continue it with --conversation ${sid})`, `conversaci\xF3n ${sid} (s\xEDguela con --conversation ${sid})`)));
|
|
8418
9028
|
}
|
|
8419
9029
|
if (message) {
|
|
8420
9030
|
await turn(agent, sid, message, first);
|
|
@@ -8422,12 +9032,12 @@ async function chat(agent, message, v) {
|
|
|
8422
9032
|
}
|
|
8423
9033
|
if (!process.stdin.isTTY) {
|
|
8424
9034
|
const piped = (await readAll()).trim();
|
|
8425
|
-
if (!piped) throw usage("Nothing to send.", 'Example: ghosty chat <agent-id> "hello"');
|
|
9035
|
+
if (!piped) throw usage(t("Nothing to send.", "No hay nada que mandar."), t('Example: ghosty chat <agent-id> "hello"', 'Ejemplo: ghosty chat <agent-id> "hola"'));
|
|
8426
9036
|
await turn(agent, sid, piped, first);
|
|
8427
9037
|
return;
|
|
8428
9038
|
}
|
|
8429
9039
|
const rl = createInterface2({ input: process.stdin, output: process.stderr });
|
|
8430
|
-
info(dim("Type your message. Ctrl-C cancels a turn, Ctrl-D exits."));
|
|
9040
|
+
info(dim(t("Type your message. Ctrl-C cancels a turn, Ctrl-D exits.", "Escribe tu mensaje. Ctrl-C cancela un turno, Ctrl-D sale.")));
|
|
8431
9041
|
for (; ; ) {
|
|
8432
9042
|
let line2;
|
|
8433
9043
|
try {
|
|
@@ -8458,28 +9068,28 @@ async function meFiles(sub, arg, v) {
|
|
|
8458
9068
|
out(r, () => {
|
|
8459
9069
|
const rows = r.files.map((f) => ({ id: f.id, name: f.name, kind: f.kind, size: f.size, created: f.createdAt.slice(0, 10) }));
|
|
8460
9070
|
return `${table(rows, ["id", "name", "kind", "size", "created"])}
|
|
8461
|
-
${dim(`${(r.storage.usedBytes / 1e9).toFixed(2)} GB of ${r.storage.limitGb} GB used`)}`;
|
|
9071
|
+
${dim(t(`${(r.storage.usedBytes / 1e9).toFixed(2)} GB of ${r.storage.limitGb} GB used`, `${(r.storage.usedBytes / 1e9).toFixed(2)} GB de ${r.storage.limitGb} GB usados`))}`;
|
|
8462
9072
|
});
|
|
8463
9073
|
return;
|
|
8464
9074
|
}
|
|
8465
9075
|
case "upload": {
|
|
8466
|
-
if (!arg) throw usage("Missing file.", ex);
|
|
9076
|
+
if (!arg) throw usage(t("Missing file.", "Falta el archivo."), ex);
|
|
8467
9077
|
const bytes = await readFile4(arg);
|
|
8468
9078
|
const r = await api(`/api/v2/me/files?nombre=${enc3(basename2(arg))}`, {
|
|
8469
9079
|
method: "POST",
|
|
8470
9080
|
raw: new Uint8Array(bytes),
|
|
8471
9081
|
headers: { "content-type": "application/octet-stream" }
|
|
8472
9082
|
});
|
|
8473
|
-
out(r, () => `${lavender("\u2713")} Uploaded ${r.name} (${r.id})`);
|
|
9083
|
+
out(r, () => `${lavender("\u2713")} ${t("Uploaded", "Subido")} ${r.name} (${r.id})`);
|
|
8474
9084
|
return;
|
|
8475
9085
|
}
|
|
8476
9086
|
case "rm": {
|
|
8477
|
-
if (!arg) throw usage("Missing file id.", ex);
|
|
8478
|
-
out(await api(`/api/v2/me/files/${enc3(arg)}`, { method: "DELETE" }) ?? { deleted: arg }, () => `${lavender("\u2713")} Deleted ${arg}`);
|
|
9087
|
+
if (!arg) throw usage(t("Missing file id.", "Falta el id del archivo."), ex);
|
|
9088
|
+
out(await api(`/api/v2/me/files/${enc3(arg)}`, { method: "DELETE" }) ?? { deleted: arg }, () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${arg}`);
|
|
8479
9089
|
return;
|
|
8480
9090
|
}
|
|
8481
9091
|
default:
|
|
8482
|
-
throw usage(`Unknown files subcommand: ${sub ?? "(none)"}.`, ex);
|
|
9092
|
+
throw usage(t(`Unknown files subcommand: ${sub ?? "(none)"}.`, `Subcomando de files desconocido: ${sub ?? "(ninguno)"}.`), ex);
|
|
8483
9093
|
}
|
|
8484
9094
|
}
|
|
8485
9095
|
async function meUsage() {
|
|
@@ -8488,11 +9098,11 @@ async function meUsage() {
|
|
|
8488
9098
|
out(
|
|
8489
9099
|
r,
|
|
8490
9100
|
() => [
|
|
8491
|
-
`Plan ${r.plan.name}${r.exhausted ? " \u2014 out of usage for now" : ""}`,
|
|
8492
|
-
` This week
|
|
8493
|
-
...r.month.pct !== null ? [` This month
|
|
8494
|
-
` Top-ups
|
|
8495
|
-
dim("Change plan or top up: https://www.ghosty.studio/c/uso")
|
|
9101
|
+
`Plan ${r.plan.name}${r.exhausted ? t(" \u2014 out of usage for now", " \u2014 sin uso disponible por ahora") : ""}`,
|
|
9102
|
+
` ${t("This week ", "Esta semana")} ${pct(r.week.pct).padStart(4)} ${t("used", "usado")} ${dim(`${t("resets", "se reinicia")} ${r.week.resetsAt.slice(0, 10)}`)}`,
|
|
9103
|
+
...r.month.pct !== null ? [` ${t("This month ", "Este mes ")} ${pct(r.month.pct).padStart(4)} ${t("used", "usado")} ${dim(`${t("resets", "se reinicia")} ${r.month.resetsAt.slice(0, 10)}`)}`] : [],
|
|
9104
|
+
` ${t("Top-ups ", "Recargas ")} ${r.creditMxn > 0 ? t("available", "disponibles") : t("none", "ninguna")}`,
|
|
9105
|
+
dim(t("Change plan or top up: https://www.ghosty.studio/c/uso", "Cambia de plan o recarga: https://www.ghosty.studio/c/uso"))
|
|
8496
9106
|
].join("\n")
|
|
8497
9107
|
);
|
|
8498
9108
|
}
|
|
@@ -8501,14 +9111,14 @@ async function meUsage() {
|
|
|
8501
9111
|
var enc4 = encodeURIComponent;
|
|
8502
9112
|
function minutesOf(s, flag) {
|
|
8503
9113
|
const m = /^(\d+(?:\.\d+)?)\s*(m|min|h|d)?$/i.exec(s.trim());
|
|
8504
|
-
if (!m) throw usage(`Invalid ${flag}: ${s}.`, "Use minutes, hours or days: 30m, 12h, 1d");
|
|
9114
|
+
if (!m) throw usage(t(`Invalid ${flag}: ${s}.`, `${flag} inv\xE1lido: ${s}.`), t("Use minutes, hours or days: 30m, 12h, 1d", "Usa minutos, horas o d\xEDas: 30m, 12h, 1d"));
|
|
8505
9115
|
const n = Number(m[1]);
|
|
8506
9116
|
const unit = (m[2] ?? "m").toLowerCase();
|
|
8507
9117
|
return Math.round(unit === "d" ? n * 1440 : unit === "h" ? n * 60 : n);
|
|
8508
9118
|
}
|
|
8509
9119
|
function isoOf(s, flag) {
|
|
8510
9120
|
const d = new Date(s);
|
|
8511
|
-
if (Number.isNaN(d.getTime())) throw usage(`Invalid ${flag}: ${s}.`, "Use an ISO date, e.g. 2026-10-01T09:00-06:00");
|
|
9121
|
+
if (Number.isNaN(d.getTime())) throw usage(t(`Invalid ${flag}: ${s}.`, `${flag} inv\xE1lido: ${s}.`), t("Use an ISO date, e.g. 2026-10-01T09:00-06:00", "Usa una fecha ISO, p. ej. 2026-10-01T09:00-06:00"));
|
|
8512
9122
|
return d.toISOString();
|
|
8513
9123
|
}
|
|
8514
9124
|
var every = (min) => min == null ? "once" : min % 1440 === 0 ? `${min / 1440}d` : min % 60 === 0 ? `${min / 60}h` : `${min}m`;
|
|
@@ -8518,7 +9128,7 @@ async function agentSchedule(agent) {
|
|
|
8518
9128
|
}
|
|
8519
9129
|
async function scheduleCmd(sub, agent, arg, v) {
|
|
8520
9130
|
if (sub === "ls" || sub === "list") {
|
|
8521
|
-
if (!agent) throw usage("Missing agent id.", "Example: ghosty schedule ls <agent-id>");
|
|
9131
|
+
if (!agent) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty schedule ls <agent-id>");
|
|
8522
9132
|
const r = await agentSchedule(agent);
|
|
8523
9133
|
if (globals.json) return out({ pending: r.pendientes, runs: r.corridas });
|
|
8524
9134
|
const pending = r.pendientes.map((p) => ({
|
|
@@ -8533,27 +9143,27 @@ async function scheduleCmd(sub, agent, arg, v) {
|
|
|
8533
9143
|
return out(
|
|
8534
9144
|
null,
|
|
8535
9145
|
() => [
|
|
8536
|
-
pending.length ? table(pending, ["id", "due", "every", "until", "conversation", "prompt"]) : dim("No scheduled turns."),
|
|
8537
|
-
...runs.length ? ["", lavender("Recent runs"), table(runs, ["fired", "conversation", "answer"])] : []
|
|
9146
|
+
pending.length ? table(pending, ["id", "due", "every", "until", "conversation", "prompt"]) : dim(t("No scheduled turns.", "No hay turnos programados.")),
|
|
9147
|
+
...runs.length ? ["", lavender(t("Recent runs", "Corridas recientes")), table(runs, ["fired", "conversation", "answer"])] : []
|
|
8538
9148
|
].join("\n")
|
|
8539
9149
|
);
|
|
8540
9150
|
}
|
|
8541
9151
|
if (sub === "add") {
|
|
8542
|
-
if (!agent || !arg) throw usage("Missing agent id or prompt.", 'Example: ghosty schedule add <agent-id> "Check the campaign" --in 30m');
|
|
8543
|
-
if (!v.at && !v.in) throw usage("Say when.", "Use --in 30m or --at 2026-10-01T09:00-06:00");
|
|
8544
|
-
if (v.at && v.in) throw usage("Use --at or --in, not both.");
|
|
9152
|
+
if (!agent || !arg) throw usage(t("Missing agent id or prompt.", "Falta el id del agente o el prompt."), t('Example: ghosty schedule add <agent-id> "Check the campaign" --in 30m', 'Ejemplo: ghosty schedule add <agent-id> "Revisa la campa\xF1a" --in 30m'));
|
|
9153
|
+
if (!v.at && !v.in) throw usage(t("Say when.", "Di cu\xE1ndo."), t("Use --in 30m or --at 2026-10-01T09:00-06:00", "Usa --in 30m o --at 2026-10-01T09:00-06:00"));
|
|
9154
|
+
if (v.at && v.in) throw usage(t("Use --at or --in, not both.", "Usa --at o --in, no ambos."));
|
|
8545
9155
|
const body = {
|
|
8546
9156
|
prompt: arg,
|
|
8547
9157
|
...v.in ? { inMinutes: minutesOf(v.in, "--in") } : { dueAt: isoOf(v.at, "--at") },
|
|
8548
9158
|
repeatMin: v.every ? minutesOf(v.every, "--every") : null,
|
|
8549
9159
|
until: v.until ? isoOf(v.until, "--until") : null
|
|
8550
9160
|
};
|
|
8551
|
-
if (body.repeatMin && !body.until) throw usage("A repeating turn needs --until.", "Example: --every 12h --until 2026-10-25");
|
|
9161
|
+
if (body.repeatMin && !body.until) throw usage(t("A repeating turn needs --until.", "Un turno que se repite necesita --until."), t("Example", "Ejemplo") + ": --every 12h --until 2026-10-25");
|
|
8552
9162
|
let sid = v.conversation;
|
|
8553
9163
|
const created = !sid;
|
|
8554
9164
|
if (!sid) {
|
|
8555
9165
|
sid = (await api(convPath(agent), { method: "POST", body: {} })).id;
|
|
8556
|
-
if (!globals.json) info(dim(`new conversation ${sid}`));
|
|
9166
|
+
if (!globals.json) info(dim(t(`new conversation ${sid}`, `conversaci\xF3n nueva ${sid}`)));
|
|
8557
9167
|
}
|
|
8558
9168
|
let r;
|
|
8559
9169
|
try {
|
|
@@ -8567,24 +9177,25 @@ async function scheduleCmd(sub, agent, arg, v) {
|
|
|
8567
9177
|
const p = r.programado;
|
|
8568
9178
|
return out(
|
|
8569
9179
|
{ scheduled: p },
|
|
8570
|
-
() => `${lavender("\u2713")} Scheduled ${p.id} \xB7 ${p.dueAt}${p.repeatMin ? ` \xB7 every ${every(p.repeatMin)} until ${p.until}` : ""}
|
|
8571
|
-
${dim(
|
|
9180
|
+
() => `${lavender("\u2713")} ${t("Scheduled", "Programado")} ${p.id} \xB7 ${p.dueAt}${p.repeatMin ? t(` \xB7 every ${every(p.repeatMin)} until ${p.until}`, ` \xB7 cada ${every(p.repeatMin)} hasta ${p.until}`) : ""}
|
|
9181
|
+
${dim(`${t("conversation", "conversaci\xF3n")} ${p.sessionId}`)}`
|
|
8572
9182
|
);
|
|
8573
9183
|
}
|
|
8574
9184
|
if (sub === "rm" || sub === "cancel") {
|
|
8575
|
-
if (!agent || !arg) throw usage("Missing agent id or schedule id.", "Example: ghosty schedule rm <agent-id> <schedule-id> (ids: ghosty schedule ls <agent-id>)");
|
|
9185
|
+
if (!agent || !arg) throw usage(t("Missing agent id or schedule id.", "Falta el id del agente o de lo programado."), t("Example", "Ejemplo") + ": ghosty schedule rm <agent-id> <schedule-id> (ids: ghosty schedule ls <agent-id>)");
|
|
8576
9186
|
const row = (await agentSchedule(agent)).pendientes.find((p) => p.id === arg);
|
|
8577
|
-
if (!row) throw usage(`No pending scheduled turn ${arg}.`, "See them with: ghosty schedule ls <agent-id>");
|
|
9187
|
+
if (!row) throw usage(t(`No pending scheduled turn ${arg}.`, `No hay un turno programado pendiente ${arg}.`), t("See them with", "M\xEDralos con") + ": ghosty schedule ls <agent-id>");
|
|
8578
9188
|
await api(convPath(agent, `/${enc4(row.sessionId)}/schedule/${enc4(arg)}`), { method: "DELETE" });
|
|
8579
|
-
return out({ canceled: arg }, () => `${lavender("\u2713")} Canceled ${arg}`);
|
|
9189
|
+
return out({ canceled: arg }, () => `${lavender("\u2713")} ${t("Canceled", "Cancelado")} ${arg}`);
|
|
8580
9190
|
}
|
|
8581
|
-
throw usage(`Unknown schedule subcommand: ${sub ?? "(none)"}.`, "Try: ghosty schedule ls | add | rm");
|
|
9191
|
+
throw usage(t(`Unknown schedule subcommand: ${sub ?? "(none)"}.`, `Subcomando de schedule desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty schedule ls | add | rm");
|
|
8582
9192
|
}
|
|
8583
9193
|
|
|
8584
9194
|
// src/commands/partner.ts
|
|
8585
9195
|
import { createHmac } from "node:crypto";
|
|
8586
9196
|
import { readFile as readFile5 } from "node:fs/promises";
|
|
8587
|
-
var
|
|
9197
|
+
var partnerHelp = () => t(
|
|
9198
|
+
`${bold("Usage:")} ghosty partner <command> [options]
|
|
8588
9199
|
|
|
8589
9200
|
${bold("Partner space")} ${dim("(you, signed in as the space owner)")}
|
|
8590
9201
|
partner ls Your partner spaces
|
|
@@ -8614,7 +9225,39 @@ ${bold("Options")}
|
|
|
8614
9225
|
--json Machine-readable output
|
|
8615
9226
|
|
|
8616
9227
|
${bold("Server credential mode")} GHOSTY_PARTNER_KEY=gpk_\u2026 GHOSTY_PARTNER_SECRET=gps_\u2026 ghosty partner agent get
|
|
8617
|
-
${dim("Docs: https://www.ghosty.studio/docs/cli/partners")}
|
|
9228
|
+
${dim("Docs: https://www.ghosty.studio/docs/cli/partners")}`,
|
|
9229
|
+
`${bold("Uso:")} ghosty partner <comando> [opciones]
|
|
9230
|
+
|
|
9231
|
+
${bold("Espacio de partner")} ${dim("(t\xFA, con sesi\xF3n como due\xF1a del espacio)")}
|
|
9232
|
+
partner ls Tus espacios de partner
|
|
9233
|
+
partner enable <workspace> Convierte uno de tus espacios en espacio de partner
|
|
9234
|
+
partner credentials ls Credenciales de servidor (sin secretos)
|
|
9235
|
+
partner credentials create [--label L] Emite una \u2014 el secreto se muestra UNA vez
|
|
9236
|
+
partner credentials rotate <keyId> [--grace MIN] Secreto nuevo, mismo keyId (el viejo vale MIN, default 60)
|
|
9237
|
+
partner credentials revoke <keyId> La revoca (idempotente)
|
|
9238
|
+
partner audit Qui\xE9n hizo qu\xE9 y cu\xE1ndo
|
|
9239
|
+
|
|
9240
|
+
${bold("Agente compartido (solo-MCP)")}
|
|
9241
|
+
partner agent get La plantilla del agente y su versi\xF3n
|
|
9242
|
+
partner agent apply --file agent.json Lo crea o lo actualiza (idempotente)
|
|
9243
|
+
|
|
9244
|
+
${bold("Tenants")} ${dim("(tus clientes, por externalId \u2014 s\xF3lo config, sin caja)")}
|
|
9245
|
+
partner tenants ls
|
|
9246
|
+
partner tenants get <externalId>
|
|
9247
|
+
partner tenants set <externalId> --config '{"name":"\u2026","timezone":"America/Mexico_City"}'
|
|
9248
|
+
partner tenants rm <externalId> Borra su config Y su memoria
|
|
9249
|
+
|
|
9250
|
+
${bold("Pru\xE9balo")}
|
|
9251
|
+
partner try <externalId> "mensaje" Un turno [--group END_USER_ID] [--turn-token TOKEN_DE_VIDA_CORTA]
|
|
9252
|
+
(el token del turno llega a TU MCP como \${turn.token}; --token tambi\xE9n sirve aqu\xED)
|
|
9253
|
+
|
|
9254
|
+
${bold("Opciones")}
|
|
9255
|
+
--workspace SLUG Qu\xE9 espacio de partner (s\xF3lo si tienes m\xE1s de uno)
|
|
9256
|
+
--json Salida para m\xE1quinas
|
|
9257
|
+
|
|
9258
|
+
${bold("Modo credencial de servidor")} GHOSTY_PARTNER_KEY=gpk_\u2026 GHOSTY_PARTNER_SECRET=gps_\u2026 ghosty partner agent get
|
|
9259
|
+
${dim("Docs: https://www.ghosty.studio/docs/cli/partners")}`
|
|
9260
|
+
);
|
|
8618
9261
|
var enc5 = encodeURIComponent;
|
|
8619
9262
|
function partnerCreds() {
|
|
8620
9263
|
const key = process.env.GHOSTY_PARTNER_KEY;
|
|
@@ -8635,7 +9278,7 @@ async function call(path, v, init = {}) {
|
|
|
8635
9278
|
"x-ghosty-ts": ts,
|
|
8636
9279
|
"x-ghosty-sig": sig,
|
|
8637
9280
|
"content-type": "application/json",
|
|
8638
|
-
"user-agent": `ghosty-cli/${"0.
|
|
9281
|
+
"user-agent": `ghosty-cli/${"0.20.0"}`
|
|
8639
9282
|
},
|
|
8640
9283
|
body: method === "GET" ? void 0 : raw
|
|
8641
9284
|
});
|
|
@@ -8650,15 +9293,15 @@ async function call(path, v, init = {}) {
|
|
|
8650
9293
|
}
|
|
8651
9294
|
async function json(path, v, init = {}) {
|
|
8652
9295
|
const res = await call(path, v, init);
|
|
8653
|
-
const
|
|
8654
|
-
return
|
|
9296
|
+
const text = await res.text();
|
|
9297
|
+
return text ? JSON.parse(text) : void 0;
|
|
8655
9298
|
}
|
|
8656
9299
|
var fmt = (d) => d ? d.replace("T", " ").slice(0, 16) : "\u2014";
|
|
8657
9300
|
function showSecret(r) {
|
|
8658
9301
|
out(
|
|
8659
9302
|
r,
|
|
8660
9303
|
() => [
|
|
8661
|
-
`${lavender("\u2713")} Save these now \u2014 the secret is not shown again
|
|
9304
|
+
`${lavender("\u2713")} ${t("Save these now \u2014 the secret is not shown again:", "Gu\xE1rdalas ya \u2014 el secreto no se vuelve a mostrar:")}`,
|
|
8662
9305
|
` GHOSTY_PARTNER_KEY=${r.keyId}`,
|
|
8663
9306
|
` GHOSTY_PARTNER_SECRET=${r.secret}`
|
|
8664
9307
|
].join("\n")
|
|
@@ -8679,17 +9322,17 @@ async function credentialsCmd(sub, id, v) {
|
|
|
8679
9322
|
if (sub === "create" || sub === "new") {
|
|
8680
9323
|
return showSecret(await json("/api/v2/partner/credentials", v, { ...owner, method: "POST", body: { label: v.label } }));
|
|
8681
9324
|
}
|
|
8682
|
-
if (!id) throw usage("Missing keyId.",
|
|
9325
|
+
if (!id) throw usage(t("Missing keyId.", "Falta el keyId."), `${t("Example", "Ejemplo")}: ghosty partner credentials ${sub} gpk_\u2026`);
|
|
8683
9326
|
if (sub === "rotate") {
|
|
8684
9327
|
const grace = v.grace !== void 0 ? Number(v.grace) : void 0;
|
|
8685
|
-
if (grace !== void 0 && (!Number.isFinite(grace) || grace < 0)) throw usage("--grace must be minutes \u2265 0.");
|
|
9328
|
+
if (grace !== void 0 && (!Number.isFinite(grace) || grace < 0)) throw usage(t("--grace must be minutes \u2265 0.", "--grace deben ser minutos \u2265 0."));
|
|
8686
9329
|
return showSecret(await json(`/api/v2/partner/credentials/${enc5(id)}/rotate`, v, { ...owner, method: "POST", body: { graceMinutes: grace } }));
|
|
8687
9330
|
}
|
|
8688
9331
|
if (sub === "revoke" || sub === "rm") {
|
|
8689
9332
|
const r = await json(`/api/v2/partner/credentials/${enc5(id)}`, v, { ...owner, method: "DELETE" });
|
|
8690
|
-
return out(r, () => `${lavender("\u2713")} Revoked ${r.keyId}.`);
|
|
9333
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Revoked ${r.keyId}.`, `${r.keyId} revocada.`)}`);
|
|
8691
9334
|
}
|
|
8692
|
-
throw usage(`Unknown credentials subcommand: ${sub}.`, "Try: ghosty partner credentials ls | create | rotate | revoke");
|
|
9335
|
+
throw usage(t(`Unknown credentials subcommand: ${sub}.`, `Subcomando de credentials desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner credentials ls | create | rotate | revoke");
|
|
8693
9336
|
}
|
|
8694
9337
|
async function agentCmd(sub, v) {
|
|
8695
9338
|
if (!sub || sub === "get") {
|
|
@@ -8698,46 +9341,46 @@ async function agentCmd(sub, v) {
|
|
|
8698
9341
|
const a = r.agent;
|
|
8699
9342
|
return [
|
|
8700
9343
|
`${bold(a.name)} ${dim(`v${a.version} \xB7 solo-MCP \xB7 ${a.engine}/${a.model}`)}`,
|
|
8701
|
-
`MCP: ${a.mcp.map((m) => `${m.name} (${m.url})`).join(", ") || "none"}`,
|
|
9344
|
+
`MCP: ${a.mcp.map((m) => `${m.name} (${m.url})`).join(", ") || t("none", "ninguno")}`,
|
|
8702
9345
|
dim(a.prompt.slice(0, 300))
|
|
8703
9346
|
].join("\n");
|
|
8704
9347
|
});
|
|
8705
9348
|
}
|
|
8706
9349
|
if (sub === "apply" || sub === "set") {
|
|
8707
|
-
if (!v.file) throw usage("Missing --file.", "Example: ghosty partner agent apply --file agent.json");
|
|
9350
|
+
if (!v.file) throw usage(t("Missing --file.", "Falta --file."), t("Example", "Ejemplo") + ": ghosty partner agent apply --file agent.json");
|
|
8708
9351
|
const body = JSON.parse(await readFile5(v.file, "utf8"));
|
|
8709
9352
|
const r = await json("/api/v2/partner/agent", v, { method: "PUT", body });
|
|
8710
|
-
return out(r, () => `${lavender("\u2713")} ${r.created ? "Created" : r.changed ? "Updated" : "No changes"} \xB7 v${r.agent.version}`);
|
|
9353
|
+
return out(r, () => `${lavender("\u2713")} ${r.created ? t("Created", "Creado") : r.changed ? t("Updated", "Actualizado") : t("No changes", "Sin cambios")} \xB7 v${r.agent.version}`);
|
|
8711
9354
|
}
|
|
8712
|
-
throw usage(`Unknown agent subcommand: ${sub}.`, "Try: ghosty partner agent get | apply --file agent.json");
|
|
9355
|
+
throw usage(t(`Unknown agent subcommand: ${sub}.`, `Subcomando de agent desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner agent get | apply --file agent.json");
|
|
8713
9356
|
}
|
|
8714
9357
|
async function tenantsCmd(sub, id, v) {
|
|
8715
9358
|
if (!sub || sub === "ls" || sub === "list") {
|
|
8716
9359
|
const r = await json("/api/v2/partner/tenants", v);
|
|
8717
|
-
return out(r, () => table(r.tenants.map((
|
|
9360
|
+
return out(r, () => table(r.tenants.map((tn) => ({ externalId: tn.externalId, config: JSON.stringify(tn.config ?? {}), updated: fmt(tn.updatedAt) })), ["externalId", "config", "updated"]));
|
|
8718
9361
|
}
|
|
8719
|
-
if (!id) throw usage("Missing externalId.",
|
|
9362
|
+
if (!id) throw usage(t("Missing externalId.", "Falta el externalId."), `${t("Example", "Ejemplo")}: ghosty partner tenants ${sub} org_123`);
|
|
8720
9363
|
const path = `/api/v2/partner/tenants/${enc5(id)}`;
|
|
8721
9364
|
if (sub === "get") return out(await json(path, v));
|
|
8722
9365
|
if (sub === "set" || sub === "apply") {
|
|
8723
|
-
if (!v.config) throw usage("Missing --config.",
|
|
9366
|
+
if (!v.config) throw usage(t("Missing --config.", "Falta --config."), `${t("Example", "Ejemplo")}: --config '{"name":"Spa","timezone":"America/Mexico_City"}'`);
|
|
8724
9367
|
let config;
|
|
8725
9368
|
try {
|
|
8726
9369
|
config = JSON.parse(v.config);
|
|
8727
9370
|
} catch {
|
|
8728
|
-
throw usage("--config must be JSON.");
|
|
9371
|
+
throw usage(t("--config must be JSON.", "--config debe ser JSON."));
|
|
8729
9372
|
}
|
|
8730
9373
|
const r = await json(path, v, { method: "PUT", body: { config } });
|
|
8731
|
-
return out(r, () => `${lavender("\u2713")} ${r.created ? "Created" : r.changed ? "Updated" : "No changes"} ${id}`);
|
|
9374
|
+
return out(r, () => `${lavender("\u2713")} ${r.created ? t("Created", "Creado") : r.changed ? t("Updated", "Actualizado") : t("No changes", "Sin cambios")} ${id}`);
|
|
8732
9375
|
}
|
|
8733
9376
|
if (sub === "rm" || sub === "delete") {
|
|
8734
9377
|
const r = await json(path, v, { method: "DELETE" });
|
|
8735
|
-
return out(r, () => r.deleted ? `${lavender("\u2713")} Deleted ${id} (${r.conversations} conversations)` : dim(`${id} did not exist.`));
|
|
9378
|
+
return out(r, () => r.deleted ? `${lavender("\u2713")} ${t(`Deleted ${id} (${r.conversations} conversations)`, `${id} borrado (${r.conversations} conversaciones)`)}` : dim(t(`${id} did not exist.`, `${id} no exist\xEDa.`)));
|
|
8736
9379
|
}
|
|
8737
|
-
throw usage(`Unknown tenants subcommand: ${sub}.`, "Try: ghosty partner tenants ls | get | set | rm");
|
|
9380
|
+
throw usage(t(`Unknown tenants subcommand: ${sub}.`, `Subcomando de tenants desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner tenants ls | get | set | rm");
|
|
8738
9381
|
}
|
|
8739
9382
|
async function tryCmd(externalId, text, v) {
|
|
8740
|
-
if (!externalId || !text) throw usage("Missing externalId or message.", '
|
|
9383
|
+
if (!externalId || !text) throw usage(t("Missing externalId or message.", "Falta el externalId o el mensaje."), t("Example", "Ejemplo") + ': ghosty partner try org_123 "\xBFqu\xE9 citas tengo hoy?"');
|
|
8741
9384
|
const res = await call("/api/v2/partner/turns", v, {
|
|
8742
9385
|
method: "POST",
|
|
8743
9386
|
body: { tenant: { externalId }, groupId: v.group ?? "cli", text, ...v.token ? { token: v.token } : {} }
|
|
@@ -8762,7 +9405,7 @@ async function tryCmd(externalId, text, v) {
|
|
|
8762
9405
|
full += ev.value;
|
|
8763
9406
|
process.stdout.write(ev.value);
|
|
8764
9407
|
} else if (ev.type === "tool" && ev.phase === "start") info(dim(` \u21B3 ${ev.name}`));
|
|
8765
|
-
else if (ev.type === "error") info(`Error: ${ev.message}`);
|
|
9408
|
+
else if (ev.type === "error") info(`Error: ${serverText(ev.message ?? "")}`);
|
|
8766
9409
|
}
|
|
8767
9410
|
}
|
|
8768
9411
|
if (!globals.json && full) process.stdout.write("\n");
|
|
@@ -8771,16 +9414,16 @@ async function partnerCmd(sub, a1, a2, v) {
|
|
|
8771
9414
|
switch (sub) {
|
|
8772
9415
|
case void 0:
|
|
8773
9416
|
case "help":
|
|
8774
|
-
return out({ help:
|
|
9417
|
+
return out({ help: partnerHelp() }, () => partnerHelp());
|
|
8775
9418
|
case "ls":
|
|
8776
9419
|
case "list": {
|
|
8777
9420
|
const r = await json("/api/v2/partner", v, { ownerOnly: true });
|
|
8778
9421
|
return out(r, () => table(r.partners.map((p) => ({ workspace: p.workspace, credentials: p.credentials, agent: p.agents ? "yes" : "no", status: p.disabledAt ? "off" : "on" })), ["workspace", "credentials", "agent", "status"]));
|
|
8779
9422
|
}
|
|
8780
9423
|
case "enable": {
|
|
8781
|
-
if (!a1) throw usage("Missing workspace.", "Example: ghosty partner enable denik");
|
|
9424
|
+
if (!a1) throw usage(t("Missing workspace.", "Falta el workspace."), t("Example", "Ejemplo") + ": ghosty partner enable denik");
|
|
8782
9425
|
const r = await json("/api/v2/partner", v, { ownerOnly: true, method: "POST", body: { workspace: a1 } });
|
|
8783
|
-
return out(r, () => `${lavender("\u2713")} ${r.partner.workspace} is a partner space.`);
|
|
9426
|
+
return out(r, () => `${lavender("\u2713")} ${t(`${r.partner.workspace} is a partner space.`, `${r.partner.workspace} ya es espacio de partner.`)}`);
|
|
8784
9427
|
}
|
|
8785
9428
|
case "credentials":
|
|
8786
9429
|
case "creds":
|
|
@@ -8796,7 +9439,7 @@ async function partnerCmd(sub, a1, a2, v) {
|
|
|
8796
9439
|
case "try":
|
|
8797
9440
|
return tryCmd(a1, a2, v);
|
|
8798
9441
|
default:
|
|
8799
|
-
throw usage(`Unknown partner subcommand: ${sub}.`, "Run: ghosty partner --help");
|
|
9442
|
+
throw usage(t(`Unknown partner subcommand: ${sub}.`, `Subcomando de partner desconocido: ${sub}.`), t("Run", "Corre") + ": ghosty partner --help");
|
|
8800
9443
|
}
|
|
8801
9444
|
}
|
|
8802
9445
|
|
|
@@ -8820,40 +9463,40 @@ function parseRowScope(raw) {
|
|
|
8820
9463
|
}
|
|
8821
9464
|
return out2;
|
|
8822
9465
|
}
|
|
8823
|
-
var describeRowScope = (scope) => Object.entries(scope ?? {}).map(([
|
|
9466
|
+
var describeRowScope = (scope) => Object.entries(scope ?? {}).map(([t2, c]) => `${t2} (${c.join(", ")})`).join("; ");
|
|
8824
9467
|
|
|
8825
9468
|
// src/commands/data.ts
|
|
8826
9469
|
var enc6 = encodeURIComponent;
|
|
8827
9470
|
var agentPath2 = (id, rest = "") => `/api/v2/agents/${enc6(id)}${rest}`;
|
|
8828
9471
|
var need2 = (v, what, example) => {
|
|
8829
|
-
if (!v) throw usage(`Missing ${what}.`, `Example: ${example}`);
|
|
9472
|
+
if (!v) throw usage(t(`Missing ${what}.`, `Falta ${what}.`), `${t("Example", "Ejemplo")}: ${example}`);
|
|
8830
9473
|
return v;
|
|
8831
9474
|
};
|
|
8832
9475
|
var csv = (s) => (s ?? "").split(",").map((x) => x.trim()).filter(Boolean);
|
|
8833
9476
|
function planText(plan) {
|
|
8834
|
-
return plan.map((p) => p.action === "create" ? ` + ${p.table} create, ${p.sourceRows} row(s)` : ` ~ ${p.table} ${p.action} ${p.targetRows} \u2192 ${p.resultRows} row(s)`).join("\n");
|
|
9477
|
+
return plan.map((p) => p.action === "create" ? t(` + ${p.table} create, ${p.sourceRows} row(s)`, ` + ${p.table} crear, ${p.sourceRows} fila(s)`) : t(` ~ ${p.table} ${p.action} ${p.targetRows} \u2192 ${p.resultRows} row(s)`, ` ~ ${p.table} ${p.action === "replace" ? "reemplazar" : "agregar"} ${p.targetRows} \u2192 ${p.resultRows} fila(s)`)).join("\n");
|
|
8835
9478
|
}
|
|
8836
9479
|
async function dbsImport(id, db, v) {
|
|
8837
9480
|
const ex = "ghosty dbs import <agent-id> <db> --from easybits:<db-id> [--tables a,b] [--dry-run]";
|
|
8838
9481
|
const m = /^easybits:(.+)$/.exec(v.from ?? "");
|
|
8839
|
-
if (!m) throw usage("Missing or bad --from.",
|
|
9482
|
+
if (!m) throw usage(t("Missing or bad --from.", "Falta --from o no es v\xE1lido."), `${t("Example", "Ejemplo")}: ${ex}`);
|
|
8840
9483
|
const easybitsKey = process.env.EASYBITS_API_KEY;
|
|
8841
|
-
if (!easybitsKey) throw usage("EASYBITS_API_KEY is not set.", "Export your EasyBits API key (https://www.easybits.cloud/dash/developer) and run again.");
|
|
9484
|
+
if (!easybitsKey) throw usage(t("EASYBITS_API_KEY is not set.", "EASYBITS_API_KEY no est\xE1 definida."), t("Export your EasyBits API key (https://www.easybits.cloud/dash/developer) and run again.", "Exporta tu API key de EasyBits (https://www.easybits.cloud/dash/developer) y vuelve a correrlo."));
|
|
8842
9485
|
const body = { from: "easybits", sourceDb: m[1], easybitsKey, mode: v.append ? "append" : "replace", ...v.tables ? { tables: csv(v.tables) } : {}, ...v["rehost-images"] ? { rehost: csv(v["rehost-images"]) } : {} };
|
|
8843
9486
|
const path = agentPath2(id, `/dbs/${enc6(db)}/import`);
|
|
8844
9487
|
const dry = await api(path, { body: { ...body, dryRun: true } });
|
|
8845
9488
|
if (v["dry-run"]) return out(dry, () => `${dry.db} \u2190 easybits:${m[1]}
|
|
8846
9489
|
${planText(dry.plan)}
|
|
8847
|
-
${dim("Dry run: nothing changed.")}`);
|
|
9490
|
+
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
8848
9491
|
const destructive = dry.plan.some((p) => p.action === "replace" && p.targetRows > 0);
|
|
8849
9492
|
if (destructive) await confirm(`${planText(dry.plan)}
|
|
8850
|
-
Replace the rows above in ${dry.db}?`, { yes: v.yes, retry: `ghosty dbs import ${id} ${db} --from ${v.from}${v.tables ? ` --tables ${v.tables}` : ""}` });
|
|
9493
|
+
${t(`Replace the rows above in ${dry.db}?`, `\xBFReemplazar las filas de arriba en ${dry.db}?`)}`, { yes: v.yes, retry: `ghosty dbs import ${id} ${db} --from ${v.from}${v.tables ? ` --tables ${v.tables}` : ""}` });
|
|
8851
9494
|
const r = await api(path, { body });
|
|
8852
9495
|
const img = r.rehosted ? `
|
|
8853
|
-
${r.rehosted.copied} image link(s) now permanent${r.rehosted.failed.length ? `, ${red(String(r.rehosted.failed.length))} kept as they were:
|
|
9496
|
+
${t(`${r.rehosted.copied} image link(s) now permanent`, `${r.rehosted.copied} liga(s) de imagen ya permanentes`)}${r.rehosted.failed.length ? `, ${red(String(r.rehosted.failed.length))} ${t("kept as they were", "se quedaron como estaban")}:
|
|
8854
9497
|
${r.rehosted.failed.map((f) => ` ${f.url} ${dim(f.error)}`).join("\n")}` : ""}` : "";
|
|
8855
9498
|
return out(r, () => `${planText(r.plan)}${img}
|
|
8856
|
-
${lavender("\u2713")} Imported into ${r.db}. ${dim("Run it again any time: with replace it ends in the same state.")}`);
|
|
9499
|
+
${lavender("\u2713")} ${t(`Imported into ${r.db}.`, `Importado en ${r.db}.`)} ${dim(t("Run it again any time: with replace it ends in the same state.", "C\xF3rrelo otra vez cuando quieras: con replace termina en el mismo estado."))}`);
|
|
8857
9500
|
}
|
|
8858
9501
|
async function dbsRehost(id, db, v) {
|
|
8859
9502
|
const ex = "ghosty dbs rehost <agent-id> <db> --table catalogo --column imagen_url [--dry-run]";
|
|
@@ -8861,9 +9504,9 @@ async function dbsRehost(id, db, v) {
|
|
|
8861
9504
|
const column = need2(v.column, "--column", ex);
|
|
8862
9505
|
const path = agentPath2(id, `/dbs/${enc6(db)}/rehost`);
|
|
8863
9506
|
const dry = await api(path, { body: { table: table2, column, dryRun: true } });
|
|
8864
|
-
const head = `${dry.db} \xB7 ${table2}.${column}: ${dry.distinct} distinct link(s), ${dry.alreadyOurs} already permanent, ${dry.toCopy} to copy`;
|
|
9507
|
+
const head = `${dry.db} \xB7 ${table2}.${column}: ${t(`${dry.distinct} distinct link(s), ${dry.alreadyOurs} already permanent, ${dry.toCopy} to copy`, `${dry.distinct} liga(s) distintas, ${dry.alreadyOurs} ya permanentes, ${dry.toCopy} por copiar`)}`;
|
|
8865
9508
|
if (v["dry-run"] || !dry.toCopy) return out(dry, () => `${head}
|
|
8866
|
-
${dim(v["dry-run"] ? "Dry run: nothing changed." : "Nothing to do.")}`);
|
|
9509
|
+
${dim(v["dry-run"] ? t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada.") : t("Nothing to do.", "No hay nada que hacer."))}`);
|
|
8867
9510
|
info(head);
|
|
8868
9511
|
const total = { copied: 0, reused: 0, failed: [], warnings: [] };
|
|
8869
9512
|
let after;
|
|
@@ -8880,50 +9523,50 @@ ${dim(v["dry-run"] ? "Dry run: nothing changed." : "Nothing to do.")}`);
|
|
|
8880
9523
|
return out(
|
|
8881
9524
|
total,
|
|
8882
9525
|
() => [
|
|
8883
|
-
`${lavender("\u2713")} ${total.copied + total.reused} link(s) now permanent (${total.copied} copied, ${total.reused} already stored).`,
|
|
8884
|
-
total.warnings.length ? `${total.warnings.length} warning(s):
|
|
9526
|
+
`${lavender("\u2713")} ${t(`${total.copied + total.reused} link(s) now permanent (${total.copied} copied, ${total.reused} already stored).`, `${total.copied + total.reused} liga(s) ya permanentes (${total.copied} copiadas, ${total.reused} ya guardadas).`)}`,
|
|
9527
|
+
total.warnings.length ? `${total.warnings.length} ${t("warning(s)", "aviso(s)")}:
|
|
8885
9528
|
${total.warnings.map((w) => ` ${w.url} ${dim(w.warning)}`).join("\n")}` : "",
|
|
8886
|
-
total.failed.length ? `${red(String(total.failed.length))} kept as they were:
|
|
9529
|
+
total.failed.length ? `${red(String(total.failed.length))} ${t("kept as they were", "se quedaron como estaban")}:
|
|
8887
9530
|
${total.failed.map((f) => ` ${f.url} ${dim(f.error)}`).join("\n")}` : ""
|
|
8888
9531
|
].filter(Boolean).join("\n")
|
|
8889
9532
|
);
|
|
8890
9533
|
}
|
|
8891
9534
|
async function dbsCmd(sub, id, db, rest, v) {
|
|
8892
9535
|
const ex = "ghosty dbs ls <agent-id>";
|
|
8893
|
-
id = need2(id, "agent id", ex);
|
|
9536
|
+
id = need2(id, t("agent id", "el id del agente"), ex);
|
|
8894
9537
|
switch (sub ?? "ls") {
|
|
8895
9538
|
case "ls":
|
|
8896
9539
|
case "list": {
|
|
8897
9540
|
const r = await api(agentPath2(id, "/dbs"));
|
|
8898
9541
|
const rows = r.dbs.map((d) => ({ name: d.name, access: d.granted ? "granted" : "\u2014", "writes from customers": d.granted ? d.externalWriteTables.join(", ") || "(none)" : "", "own rows": d.granted ? Object.keys(d.externalRowScope ?? {}).join(", ") : "", id: d.id }));
|
|
8899
|
-
return out(r.dbs, () => rows.length ? table(rows, ["name", "access", "writes from customers", "own rows", "id"]) : dim("This account has no databases yet: create one at /app/dbs."));
|
|
9542
|
+
return out(r.dbs, () => rows.length ? table(rows, ["name", "access", "writes from customers", "own rows", "id"]) : dim(t("This account has no databases yet: create one at /app/dbs.", "Esta cuenta todav\xEDa no tiene bases: crea una en /app/dbs.")));
|
|
8900
9543
|
}
|
|
8901
9544
|
case "tables": {
|
|
8902
|
-
db = need2(db, "database (id or name)", "ghosty dbs tables <agent-id> <db>");
|
|
9545
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs tables <agent-id> <db>");
|
|
8903
9546
|
const r = await api(agentPath2(id, `/dbs/${enc6(db)}`));
|
|
8904
9547
|
const ext = new Set(r.externalWriteTables);
|
|
8905
9548
|
const own = r.externalRowScope ?? {};
|
|
8906
|
-
const rows = r.tables.map((
|
|
8907
|
-
return out(r, () => `${bold(r.name)} ${r.granted ? lavender("granted") : dim("not granted")}
|
|
9549
|
+
const rows = r.tables.map((tb) => ({ table: tb.name, rows: tb.rows, "customer writes": ext.has(tb.name) ? "yes" : "", "customer sees": own[tb.name] ? `own rows (${own[tb.name].join(", ")})` : "", columns: tb.columns.map((c) => `${c.name}${c.pk ? "*" : ""}`).join(", ") }));
|
|
9550
|
+
return out(r, () => `${bold(r.name)} ${r.granted ? lavender(t("granted", "concedida")) : dim(t("not granted", "no concedida"))}
|
|
8908
9551
|
${table(rows, ["table", "rows", "customer writes", "customer sees", "columns"])}`);
|
|
8909
9552
|
}
|
|
8910
9553
|
case "query":
|
|
8911
9554
|
case "sql": {
|
|
8912
|
-
db = need2(db, "database (id or name)", 'ghosty dbs query <agent-id> <db> "SELECT \u2026"');
|
|
8913
|
-
const sql = need2(rest.join(" ").trim() || void 0, "SQL", 'ghosty dbs query <agent-id> <db> "SELECT * FROM t LIMIT 10"');
|
|
9555
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), 'ghosty dbs query <agent-id> <db> "SELECT \u2026"');
|
|
9556
|
+
const sql = need2(rest.join(" ").trim() || void 0, t("SQL", "el SQL"), 'ghosty dbs query <agent-id> <db> "SELECT * FROM t LIMIT 10"');
|
|
8914
9557
|
const r = await api(agentPath2(id, `/dbs/${enc6(db)}/query`), { body: { sql, args: v.arg ?? [], write: v.write === true } }).catch((e) => {
|
|
8915
|
-
if (e instanceof CliError && !v.write && /lectura|SELECT|escribir/i.test(e.message)) throw new CliError(e.message, EXIT.API, "To write on purpose, add --write.");
|
|
9558
|
+
if (e instanceof CliError && !v.write && /lectura|SELECT|escribir/i.test(e.message)) throw new CliError(e.message, EXIT.API, t("To write on purpose, add --write.", "Para escribir a prop\xF3sito, agrega --write."));
|
|
8916
9559
|
throw e;
|
|
8917
9560
|
});
|
|
8918
9561
|
return out(r, () => {
|
|
8919
|
-
if (!r.cols.length) return `${lavender("\u2713")} ${r.affected} row(s) affected ${dim(`(${Math.round(r.ms)} ms)`)}`;
|
|
9562
|
+
if (!r.cols.length) return `${lavender("\u2713")} ${t(`${r.affected} row(s) affected`, `${r.affected} fila(s) afectadas`)} ${dim(`(${Math.round(r.ms)} ms)`)}`;
|
|
8920
9563
|
const rows = r.rows.map((row) => Object.fromEntries(r.cols.map((c, i) => [c, row[i]])));
|
|
8921
9564
|
return `${table(rows, r.cols)}
|
|
8922
|
-
${dim(`${rows.length} row(s) \xB7 ${Math.round(r.ms)} ms`)}`;
|
|
9565
|
+
${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round(r.ms)} ms`)}`;
|
|
8923
9566
|
});
|
|
8924
9567
|
}
|
|
8925
9568
|
case "grant": {
|
|
8926
|
-
db = need2(db, "database (id or name)", "ghosty dbs grant <agent-id> <db> [--external-write t1,t2] [--external-rows t:col+col]");
|
|
9569
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs grant <agent-id> <db> [--external-write t1,t2] [--external-rows t:col+col]");
|
|
8927
9570
|
let rowScope;
|
|
8928
9571
|
try {
|
|
8929
9572
|
rowScope = v["external-rows"] !== void 0 ? parseRowScope(v["external-rows"]) : void 0;
|
|
@@ -8936,44 +9579,44 @@ ${dim(`${rows.length} row(s) \xB7 ${Math.round(r.ms)} ms`)}`;
|
|
|
8936
9579
|
};
|
|
8937
9580
|
const r = await api(agentPath2(id, `/dbs/${enc6(db)}`), { method: "PUT", body });
|
|
8938
9581
|
const own = describeRowScope(r.externalRowScope);
|
|
8939
|
-
return out(r, () => `${lavender("\u2713")} ${r.name} granted. ${dim(`From customer channels it writes to: ${r.externalWriteTables.join(", ") || "nothing (read only)"}.${own ? ` Each customer sees only their own rows of: ${own}.` : ""}`)}`);
|
|
9582
|
+
return out(r, () => `${lavender("\u2713")} ${r.name} ${t("granted.", "concedida.")} ${dim(t(`From customer channels it writes to: ${r.externalWriteTables.join(", ") || "nothing (read only)"}.${own ? ` Each customer sees only their own rows of: ${own}.` : ""}`, `Desde canales de clientes escribe en: ${r.externalWriteTables.join(", ") || "nada (s\xF3lo lectura)"}.${own ? ` Cada cliente ve s\xF3lo sus propias filas de: ${own}.` : ""}`))}`);
|
|
8940
9583
|
}
|
|
8941
9584
|
case "import": {
|
|
8942
|
-
db = need2(db, "database (id or name)", "ghosty dbs import <agent-id> <db> --from easybits:<db-id>");
|
|
9585
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs import <agent-id> <db> --from easybits:<db-id>");
|
|
8943
9586
|
return dbsImport(id, db, v);
|
|
8944
9587
|
}
|
|
8945
9588
|
case "rehost": {
|
|
8946
|
-
db = need2(db, "database (id or name)", "ghosty dbs rehost <agent-id> <db> --table T --column C");
|
|
9589
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs rehost <agent-id> <db> --table T --column C");
|
|
8947
9590
|
return dbsRehost(id, db, v);
|
|
8948
9591
|
}
|
|
8949
9592
|
case "revoke": {
|
|
8950
|
-
db = need2(db, "database (id or name)", "ghosty dbs revoke <agent-id> <db>");
|
|
9593
|
+
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs revoke <agent-id> <db>");
|
|
8951
9594
|
const r = await api(agentPath2(id, `/dbs/${enc6(db)}`), { method: "DELETE" });
|
|
8952
|
-
return out(r, () => `${lavender("\u2713")} ${r.name} revoked. ${dim(
|
|
9595
|
+
return out(r, () => `${lavender("\u2713")} ${r.name} ${t("revoked.", "revocada.")} ${dim(`${t("Undo", "Deshacer")}: ghosty dbs grant ${id} ${r.name}`)}`);
|
|
8953
9596
|
}
|
|
8954
9597
|
default:
|
|
8955
|
-
throw usage(`Unknown dbs subcommand: ${sub}.`, "Try: ghosty dbs ls | tables | query | import | rehost | grant | revoke (ghosty dbs --help)");
|
|
9598
|
+
throw usage(t(`Unknown dbs subcommand: ${sub}.`, `Subcomando de dbs desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty dbs ls | tables | query | import | rehost | grant | revoke (ghosty dbs --help)");
|
|
8956
9599
|
}
|
|
8957
9600
|
}
|
|
8958
9601
|
async function agentDocsCmd(sub, id, docs, v) {
|
|
8959
9602
|
let doc = docs[0];
|
|
8960
|
-
id = need2(id, "agent id", "ghosty agents docs ls <agent-id>");
|
|
9603
|
+
id = need2(id, t("agent id", "el id del agente"), "ghosty agents docs ls <agent-id>");
|
|
8961
9604
|
switch (sub ?? "ls") {
|
|
8962
9605
|
case "ls":
|
|
8963
9606
|
case "list": {
|
|
8964
9607
|
const r = await api(agentPath2(id, "/documents"));
|
|
8965
9608
|
const rows = r.documents.map((d) => ({ name: d.name, access: d.granted ? d.mode === "write" ? "read+replace" : "read" : "\u2014", delivers: d.granted ? d.deliverable ? "yes" : "no" : "", id: d.id }));
|
|
8966
|
-
return out(r.documents, () => rows.length ? table(rows, ["name", "access", "delivers", "id"]) : dim("This account has no documents yet: upload them at /app/datos."));
|
|
9609
|
+
return out(r.documents, () => rows.length ? table(rows, ["name", "access", "delivers", "id"]) : dim(t("This account has no documents yet: upload them at /app/datos.", "Esta cuenta todav\xEDa no tiene documentos: s\xFAbelos en /app/datos.")));
|
|
8967
9610
|
}
|
|
8968
9611
|
case "grant": {
|
|
8969
9612
|
let wanted;
|
|
8970
9613
|
if (v.like) {
|
|
8971
|
-
if (docs.length) throw usage("Use document names or --like, not both.");
|
|
9614
|
+
if (docs.length) throw usage(t("Use document names or --like, not both.", "Usa nombres de documentos o --like, no ambos."));
|
|
8972
9615
|
const other = await api(agentPath2(v.like, "/documents"));
|
|
8973
9616
|
wanted = other.documents.filter((d) => d.granted).map((d) => ({ ref: d.id, body: { mode: d.mode ?? "read", deliverable: d.deliverable ?? true } }));
|
|
8974
|
-
if (!wanted.length) return out({ granted: [] }, () => dim(`${v.like} has no documents granted.`));
|
|
9617
|
+
if (!wanted.length) return out({ granted: [] }, () => dim(t(`${v.like} has no documents granted.`, `${v.like} no tiene documentos concedidos.`)));
|
|
8975
9618
|
} else {
|
|
8976
|
-
if (!docs.length) throw usage("Missing document (id or exact name).", 'Example: ghosty agents docs grant <agent-id> "Cat\xE1logo.pdf" "Precios.xlsx" (or --like <other-agent>)');
|
|
9619
|
+
if (!docs.length) throw usage(t("Missing document (id or exact name).", "Falta el documento (id o nombre exacto)."), t('Example: ghosty agents docs grant <agent-id> "Cat\xE1logo.pdf" "Precios.xlsx" (or --like <other-agent>)', 'Ejemplo: ghosty agents docs grant <agent-id> "Cat\xE1logo.pdf" "Precios.xlsx" (o --like <otro-agente>)'));
|
|
8977
9620
|
const body = {};
|
|
8978
9621
|
if (v.write) body.mode = "write";
|
|
8979
9622
|
if (v["no-deliver"]) body.deliverable = false;
|
|
@@ -8982,11 +9625,11 @@ async function agentDocsCmd(sub, id, docs, v) {
|
|
|
8982
9625
|
}
|
|
8983
9626
|
const done = [];
|
|
8984
9627
|
for (const w of wanted) done.push(await api(agentPath2(id, `/documents/${enc6(w.ref)}`), { method: "PUT", body: w.body }));
|
|
8985
|
-
return out(done.length === 1 ? done[0] : { granted: done }, () => done.map((r) => `${lavender("\u2713")} ${r.name}: ${r.mode === "write" ? "reads and replaces it" : "reads it"}${r.deliverable ? ", and can send it" : ", never sends it"}.`).join("\n"));
|
|
9628
|
+
return out(done.length === 1 ? done[0] : { granted: done }, () => done.map((r) => `${lavender("\u2713")} ${r.name}: ${r.mode === "write" ? t("reads and replaces it", "lo lee y lo reemplaza") : t("reads it", "lo lee")}${r.deliverable ? t(", and can send it", ", y puede mandarlo") : t(", never sends it", ", nunca lo manda")}.`).join("\n"));
|
|
8986
9629
|
}
|
|
8987
9630
|
case "add": {
|
|
8988
|
-
if (!v.url === !v.file) throw usage("Use --url URL or --file PATH (one of them).", '
|
|
8989
|
-
if (v.grant && !["read", "write", "no"].includes(v.grant)) throw usage("--grant takes read, write or no.");
|
|
9631
|
+
if (!v.url === !v.file) throw usage(t("Use --url URL or --file PATH (one of them).", "Usa --url URL o --file RUTA (uno de los dos)."), t("Example", "Ejemplo") + ': ghosty agents docs add <agent-id> --url https://\u2026/lista.pdf --description "Precios 2026"');
|
|
9632
|
+
if (v.grant && !["read", "write", "no"].includes(v.grant)) throw usage(t("--grant takes read, write or no.", "--grant acepta read, write o no."));
|
|
8990
9633
|
const body = {
|
|
8991
9634
|
...v.url ? { url: v.url } : { contentBase64: (await readFile6(v.file)).toString("base64"), name: v.name ?? basename3(v.file) },
|
|
8992
9635
|
...v.url && v.name ? { name: v.name } : {},
|
|
@@ -8995,21 +9638,21 @@ async function agentDocsCmd(sub, id, docs, v) {
|
|
|
8995
9638
|
...v["no-deliver"] ? { deliverable: false } : {}
|
|
8996
9639
|
};
|
|
8997
9640
|
const r = await api(agentPath2(id, "/documents"), { method: "POST", body });
|
|
8998
|
-
return out(r, () => `${lavender("\u2713")} ${r.name} uploaded ${dim(`(${r.size} bytes)`)}${r.granted ? `, ${r.mode === "write" ? "it reads and replaces it" : "it reads it"}.` : ". Not granted: ghosty agents docs grant " + id + ` "${r.name}"`}`);
|
|
9641
|
+
return out(r, () => `${lavender("\u2713")} ${r.name} ${t("uploaded", "subido")} ${dim(`(${r.size} bytes)`)}${r.granted ? `, ${r.mode === "write" ? t("it reads and replaces it", "lo lee y lo reemplaza") : t("it reads it", "lo lee")}.` : t(". Not granted: ", ". Sin conceder: ") + "ghosty agents docs grant " + id + ` "${r.name}"`}`);
|
|
8999
9642
|
}
|
|
9000
9643
|
case "revoke": {
|
|
9001
|
-
doc = need2(doc, "document (id or exact name)", 'ghosty agents docs revoke <agent-id> "Cat\xE1logo.pdf"');
|
|
9644
|
+
doc = need2(doc, t("document (id or exact name)", "el documento (id o nombre exacto)"), 'ghosty agents docs revoke <agent-id> "Cat\xE1logo.pdf"');
|
|
9002
9645
|
const r = await api(agentPath2(id, `/documents/${enc6(doc)}`), { method: "DELETE" });
|
|
9003
|
-
return out(r, () => `${lavender("\u2713")} ${r.name} revoked. ${dim(
|
|
9646
|
+
return out(r, () => `${lavender("\u2713")} ${r.name} ${t("revoked.", "revocado.")} ${dim(`${t("Undo", "Deshacer")}: ghosty agents docs grant ${id} "${r.name}"`)}`);
|
|
9004
9647
|
}
|
|
9005
9648
|
default:
|
|
9006
|
-
throw usage(`Unknown agents docs subcommand: ${sub}.`, "Try: ghosty agents docs ls | add | grant | revoke");
|
|
9649
|
+
throw usage(t(`Unknown agents docs subcommand: ${sub}.`, `Subcomando de agents docs desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty agents docs ls | add | grant | revoke");
|
|
9007
9650
|
}
|
|
9008
9651
|
}
|
|
9009
9652
|
async function turnsCmd(sub, id, turnId, v) {
|
|
9010
9653
|
if (sub === "show") return turnShow(id, turnId, !!v.full);
|
|
9011
|
-
if (sub && sub !== "ls" && sub !== "list") throw usage(`Unknown turns subcommand: ${sub}.`, "Try: ghosty turns ls <agent-id> --errors --since 1h \xB7 ghosty turns show <agent-id> [turn-id|last]");
|
|
9012
|
-
id = need2(id, "agent id", "ghosty turns ls <agent-id> --errors --since 1h");
|
|
9654
|
+
if (sub && sub !== "ls" && sub !== "list") throw usage(t(`Unknown turns subcommand: ${sub}.`, `Subcomando de turns desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty turns ls <agent-id> --errors --since 1h \xB7 ghosty turns show <agent-id> [turn-id|last]");
|
|
9655
|
+
id = need2(id, t("agent id", "el id del agente"), "ghosty turns ls <agent-id> --errors --since 1h");
|
|
9013
9656
|
const q = new URLSearchParams();
|
|
9014
9657
|
const status = v.errors ? "error" : v.status;
|
|
9015
9658
|
if (status) q.set("state", status);
|
|
@@ -9017,44 +9660,44 @@ async function turnsCmd(sub, id, turnId, v) {
|
|
|
9017
9660
|
q.set("limit", v.limit ?? "20");
|
|
9018
9661
|
const r = await api(agentPath2(id, `/turns?${q}`));
|
|
9019
9662
|
const when = (s) => s.slice(0, 16).replace("T", " ");
|
|
9020
|
-
const rows = r.turns.map((
|
|
9021
|
-
id:
|
|
9022
|
-
started: when(
|
|
9023
|
-
state:
|
|
9024
|
-
secs:
|
|
9025
|
-
from:
|
|
9026
|
-
session:
|
|
9027
|
-
error: (
|
|
9663
|
+
const rows = r.turns.map((tu) => ({
|
|
9664
|
+
id: tu.id,
|
|
9665
|
+
started: when(tu.startedAt),
|
|
9666
|
+
state: tu.state === "error" ? red(tu.state) : tu.state,
|
|
9667
|
+
secs: tu.ms == null ? "" : Math.round(tu.ms / 1e3),
|
|
9668
|
+
from: tu.surface ?? "",
|
|
9669
|
+
session: tu.sessionId.slice(0, 24),
|
|
9670
|
+
error: serverText(tu.error ?? "").replace(/\s+/g, " ").slice(0, 90)
|
|
9028
9671
|
}));
|
|
9029
9672
|
const tally = Object.entries(r.counts).map(([k, n]) => `${k} ${n}`).join(" \xB7 ");
|
|
9030
9673
|
out(r, () => rows.length ? `${table(rows, ["id", "started", "state", "secs", "from", "session", "error"])}
|
|
9031
|
-
${dim(`${v.since ? `since ${v.since}: ` : ""}${tally} \xB7 where the time went: ghosty turns show ${id} <turn-id>`)}` : dim(`No ${status ?? ""} turns${v.since ? ` since ${v.since}` : ""}.`));
|
|
9674
|
+
${dim(`${v.since ? t(`since ${v.since}: `, `desde ${v.since}: `) : ""}${tally} \xB7 ${t("where the time went", "en qu\xE9 se fue el tiempo")}: ghosty turns show ${id} <turn-id>`)}` : dim(t(`No ${status ?? ""} turns${v.since ? ` since ${v.since}` : ""}.`, `Sin turnos${status ? ` ${status}` : ""}${v.since ? ` desde ${v.since}` : ""}.`)));
|
|
9032
9675
|
}
|
|
9033
9676
|
async function turnShow(id, turnId, full = false) {
|
|
9034
|
-
id = need2(id, "agent id", "ghosty turns show <agent-id> [turn-id|last] [--full]");
|
|
9677
|
+
id = need2(id, t("agent id", "el id del agente"), "ghosty turns show <agent-id> [turn-id|last] [--full]");
|
|
9035
9678
|
const r = await api(agentPath2(id, `/turns/${enc6(turnId ?? "last")}`));
|
|
9036
9679
|
out(r, () => {
|
|
9037
|
-
const
|
|
9038
|
-
const clip = (s) => full || s.length <= 600 ? s : `${s.slice(0, 600)}\u2026 ${dim(`(+${s.length - 600} chars, --full)`)}`;
|
|
9680
|
+
const tu = r.turn;
|
|
9681
|
+
const clip = (s) => full || s.length <= 600 ? s : `${s.slice(0, 600)}\u2026 ${dim(t(`(+${s.length - 600} chars, --full)`, `(+${s.length - 600} caracteres, --full)`))}`;
|
|
9039
9682
|
const io = [
|
|
9040
|
-
`${lavender("in ")} ${r.input ? clip(r.input) : dim("(no copy of the message)")}`,
|
|
9041
|
-
`${lavender("out")} ${r.output ? clip(r.output) : dim("(no answer saved)")}`
|
|
9683
|
+
`${lavender("in ")} ${r.input ? clip(r.input) : dim(t("(no copy of the message)", "(sin copia del mensaje)"))}`,
|
|
9684
|
+
`${lavender("out")} ${r.output ? clip(r.output) : dim(t("(no answer saved)", "(sin respuesta guardada)"))}`
|
|
9042
9685
|
];
|
|
9043
|
-
const state = `${bold(
|
|
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`)}`;
|
|
9686
|
+
const state = `${bold(tu.id)} ${tu.state === "error" ? red(tu.state) : tu.state} ${tu.secs ?? "?"} s${tu.session ? dim(` ${tu.session}`) : ""}`;
|
|
9687
|
+
if (!r.totals) return [state, ...io, dim(`${t("no timeline", "sin l\xEDnea de tiempo")}: ${r.why ? serverText(r.why) : t("no transcript", "sin transcript")}`)].join("\n");
|
|
9688
|
+
const head = `${state} ${dim(t(`${r.totals.model ?? ""} \xB7 ${r.totals.modelCalls} model calls \xB7 ${r.totals.outputTokens} output tokens \xB7 ${r.totals.toolSecs} s in tools`, `${r.totals.model ?? ""} \xB7 ${r.totals.modelCalls} llamadas al modelo \xB7 ${r.totals.outputTokens} tokens de salida \xB7 ${r.totals.toolSecs} s en tools`))}`;
|
|
9046
9689
|
const line2 = (s) => {
|
|
9047
9690
|
const at = `+${s.at.toFixed(1)}s`.padStart(8);
|
|
9048
|
-
if (s.kind === "tool") return `${at} ${bold(s.tool ?? "?")} ${dim(s.input ?? "")} ${s.secs === void 0 ? red("(no result)") : `${s.secs} s`}${s.error ? red(" error") : ""}`;
|
|
9691
|
+
if (s.kind === "tool") return `${at} ${bold(s.tool ?? "?")} ${dim(s.input ?? "")} ${s.secs === void 0 ? red(t("(no result)", "(sin resultado)")) : `${s.secs} s`}${s.error ? red(" error") : ""}`;
|
|
9049
9692
|
const tok = s.outTokens ? dim(` [${s.outTokens} out]`) : "";
|
|
9050
|
-
return s.kind === "thinking" ? `${at} ${dim("thinking")}${tok}` : `${at} ${s.text}${tok}`;
|
|
9693
|
+
return s.kind === "thinking" ? `${at} ${dim(t("thinking", "pensando"))}${tok}` : `${at} ${s.text}${tok}`;
|
|
9051
9694
|
};
|
|
9052
9695
|
const top = r.byTool.slice(0, 5).map((b) => `${b.tool} ${b.secs} s (${b.calls})`).join(" \xB7 ");
|
|
9053
|
-
return [head, ...io, ...r.steps.map(line2), dim(
|
|
9696
|
+
return [head, ...io, ...r.steps.map(line2), dim(`${t("slowest", "lo m\xE1s lento")}: ${top || "\u2014"}`)].join("\n");
|
|
9054
9697
|
});
|
|
9055
9698
|
}
|
|
9056
9699
|
async function agentsDoctor(id, v) {
|
|
9057
|
-
id = need2(id, "agent id", "ghosty agents doctor <agent-id>");
|
|
9700
|
+
id = need2(id, t("agent id", "el id del agente"), "ghosty agents doctor <agent-id>");
|
|
9058
9701
|
const r = await api(agentPath2(id, "/doctor"));
|
|
9059
9702
|
const problems = r.summary.warn + r.summary.error;
|
|
9060
9703
|
if (globals.json) out(r);
|
|
@@ -9063,15 +9706,15 @@ async function agentsDoctor(id, v) {
|
|
|
9063
9706
|
const lines = [];
|
|
9064
9707
|
for (const c of r.checks) {
|
|
9065
9708
|
if (c.level === "ok" && !v.verbose) continue;
|
|
9066
|
-
lines.push(`${process.stdout.isTTY ? mark[c.level] : { ok: "\u2713", warn: "!", error: "\u2717" }[c.level]} ${c.title}`);
|
|
9067
|
-
if (c.detail && c.level !== "ok") lines.push(...c.detail.split("\n").map((l) => ` ${dim(l)}`));
|
|
9068
|
-
if (c.fix && c.level !== "ok") lines.push(` ${dim("fix:")} ${c.fix}`);
|
|
9709
|
+
lines.push(`${process.stdout.isTTY ? mark[c.level] : { ok: "\u2713", warn: "!", error: "\u2717" }[c.level]} ${serverText(c.title)}`);
|
|
9710
|
+
if (c.detail && c.level !== "ok") lines.push(...serverText(c.detail).split("\n").map((l) => ` ${dim(l)}`));
|
|
9711
|
+
if (c.fix && c.level !== "ok") lines.push(` ${dim(t("fix:", "arreglo:"))} ${serverText(c.fix)}`);
|
|
9069
9712
|
}
|
|
9070
|
-
lines.push(problems ? `Doctor found ${r.summary.error} error(s) and ${r.summary.warn} warning(s) in ${r.checks.length} checks.` : `${lavender("\u2713")} No issues found (${r.checks.length} checks).`);
|
|
9713
|
+
lines.push(problems ? t(`Doctor found ${r.summary.error} error(s) and ${r.summary.warn} warning(s) in ${r.checks.length} checks.`, `Doctor encontr\xF3 ${r.summary.error} error(es) y ${r.summary.warn} aviso(s) en ${r.checks.length} revisiones.`) : `${lavender("\u2713")} ${t(`No issues found (${r.checks.length} checks).`, `Sin problemas (${r.checks.length} revisiones).`)}`);
|
|
9071
9714
|
process.stdout.write(lines.join("\n") + "\n");
|
|
9072
9715
|
}
|
|
9073
9716
|
if (problems) {
|
|
9074
|
-
if (!globals.json) info(dim("Exit 1: problems found."));
|
|
9717
|
+
if (!globals.json) info(dim(t("Exit 1: problems found.", "Salida 1: hay problemas.")));
|
|
9075
9718
|
return exitAfterFlush(EXIT.API);
|
|
9076
9719
|
}
|
|
9077
9720
|
}
|
|
@@ -9097,7 +9740,7 @@ async function hiddenPrompt(label) {
|
|
|
9097
9740
|
if (ch === "") {
|
|
9098
9741
|
stdin.setRawMode(false);
|
|
9099
9742
|
process.stderr.write("\n");
|
|
9100
|
-
return reject(usage("Cancelled."));
|
|
9743
|
+
return reject(usage(t("Cancelled.", "Cancelado.")));
|
|
9101
9744
|
}
|
|
9102
9745
|
if (ch === "\x7F") value = value.slice(0, -1);
|
|
9103
9746
|
else value += ch;
|
|
@@ -9109,7 +9752,7 @@ async function hiddenPrompt(label) {
|
|
|
9109
9752
|
async function readSecret(provider, fromEnv) {
|
|
9110
9753
|
if (fromEnv) {
|
|
9111
9754
|
const v = process.env[fromEnv];
|
|
9112
|
-
if (!v) throw usage(`$${fromEnv} is empty or not set.`,
|
|
9755
|
+
if (!v) throw usage(t(`$${fromEnv} is empty or not set.`, `$${fromEnv} est\xE1 vac\xEDa o no existe.`), `${t("Example", "Ejemplo")}: export ${fromEnv}=\u2026 && ghosty credentials set ${provider} --from-env ${fromEnv}`);
|
|
9113
9756
|
return v;
|
|
9114
9757
|
}
|
|
9115
9758
|
if (!process.stdin.isTTY) {
|
|
@@ -9117,8 +9760,8 @@ async function readSecret(provider, fromEnv) {
|
|
|
9117
9760
|
for await (const c of process.stdin) parts.push(c);
|
|
9118
9761
|
return Buffer.concat(parts).toString("utf8").replace(/\r?\n$/, "");
|
|
9119
9762
|
}
|
|
9120
|
-
if (!canPrompt()) throw usage("No value to save.", `Pipe it (\u2026 | ghosty credentials set ${provider}) or use --from-env VAR.`);
|
|
9121
|
-
return hiddenPrompt(`${provider} key (hidden): `);
|
|
9763
|
+
if (!canPrompt()) throw usage(t("No value to save.", "No hay valor que guardar."), t(`Pipe it (\u2026 | ghosty credentials set ${provider}) or use --from-env VAR.`, `P\xE1salo por tuber\xEDa (\u2026 | ghosty credentials set ${provider}) o usa --from-env VAR.`));
|
|
9764
|
+
return hiddenPrompt(t(`${provider} key (hidden): `, `Llave de ${provider} (oculta): `));
|
|
9122
9765
|
}
|
|
9123
9766
|
async function credentialsCmd2(sub, provider, v) {
|
|
9124
9767
|
switch (sub ?? "ls") {
|
|
@@ -9129,21 +9772,21 @@ async function credentialsCmd2(sub, provider, v) {
|
|
|
9129
9772
|
return out(r.credentials, () => table(rows, ["provider", "set", "updated"]));
|
|
9130
9773
|
}
|
|
9131
9774
|
case "set": {
|
|
9132
|
-
if (!provider) throw usage("Missing provider.", "Example: ghosty credentials set elevenlabs --from-env ELEVENLABS_API_KEY (ghosty credentials ls lists them)");
|
|
9775
|
+
if (!provider) throw usage(t("Missing provider.", "Falta el proveedor."), t("Example: ghosty credentials set elevenlabs --from-env ELEVENLABS_API_KEY (ghosty credentials ls lists them)", "Ejemplo: ghosty credentials set elevenlabs --from-env ELEVENLABS_API_KEY (ghosty credentials ls los lista)"));
|
|
9133
9776
|
const key = (await readSecret(provider, v["from-env"])).trim();
|
|
9134
|
-
if (!key) throw usage("Empty key: nothing saved.");
|
|
9777
|
+
if (!key) throw usage(t("Empty key: nothing saved.", "Llave vac\xEDa: no se guard\xF3 nada."));
|
|
9135
9778
|
const r = await api(`/api/v2/me/credentials/${encodeURIComponent(provider)}`, { method: "PUT", body: { key } });
|
|
9136
|
-
return out(r, () => `${lavender("\u2713")} ${provider} saved ${dim(`(\u2026${r.lastFour ?? ""}). Agents with a machine pick it up on their next turn.`)}`);
|
|
9779
|
+
return out(r, () => `${lavender("\u2713")} ${provider} ${t("saved", "guardada")} ${dim(t(`(\u2026${r.lastFour ?? ""}). Agents with a machine pick it up on their next turn.`, `(\u2026${r.lastFour ?? ""}). Los agentes con m\xE1quina la toman en su siguiente turno.`))}`);
|
|
9137
9780
|
}
|
|
9138
9781
|
case "rm":
|
|
9139
9782
|
case "remove": {
|
|
9140
|
-
if (!provider) throw usage("Missing provider.", "Example: ghosty credentials rm elevenlabs");
|
|
9141
|
-
await confirm(`Remove your ${provider} key? Agents using it stop working until you set another.`, { yes: v.yes, retry: `ghosty credentials rm ${provider}` });
|
|
9783
|
+
if (!provider) throw usage(t("Missing provider.", "Falta el proveedor."), t("Example", "Ejemplo") + ": ghosty credentials rm elevenlabs");
|
|
9784
|
+
await confirm(t(`Remove your ${provider} key? Agents using it stop working until you set another.`, `\xBFQuitar tu llave de ${provider}? Los agentes que la usan dejan de funcionar hasta que pongas otra.`), { yes: v.yes, retry: `ghosty credentials rm ${provider}` });
|
|
9142
9785
|
const r = await api(`/api/v2/me/credentials/${encodeURIComponent(provider)}`, { method: "DELETE" });
|
|
9143
|
-
return out(r, () => `${lavender("\u2713")} ${provider} removed
|
|
9786
|
+
return out(r, () => `${lavender("\u2713")} ${provider} ${t("removed.", "quitada.")}`);
|
|
9144
9787
|
}
|
|
9145
9788
|
default:
|
|
9146
|
-
throw usage(`Unknown credentials subcommand: ${sub}.`, "Try: ghosty credentials ls | set | rm (ghosty credentials --help)");
|
|
9789
|
+
throw usage(t(`Unknown credentials subcommand: ${sub}.`, `Subcomando de credentials desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty credentials ls | set | rm (ghosty credentials --help)");
|
|
9147
9790
|
}
|
|
9148
9791
|
}
|
|
9149
9792
|
|
|
@@ -9189,12 +9832,12 @@ var digits = (s) => s.replace(/\D/g, "");
|
|
|
9189
9832
|
function find(nums, ref) {
|
|
9190
9833
|
const d = digits(ref);
|
|
9191
9834
|
const hit = nums.find((n) => n.id === ref) ?? nums.find((n) => n.label.toLowerCase() === ref.toLowerCase()) ?? (d.length >= 6 ? nums.find((n) => digits(n.label).endsWith(d)) : void 0);
|
|
9192
|
-
if (!hit) throw usage(`No WhatsApp number "${ref}" for this agent.`, nums.length ?
|
|
9835
|
+
if (!hit) throw usage(t(`No WhatsApp number "${ref}" for this agent.`, `Este agente no tiene el n\xFAmero de WhatsApp "${ref}".`), nums.length ? `${t("Available", "Disponibles")}: ${nums.map((n) => n.label).join(", ")}` : t("Pair a number first in the web (the agent's Channels page).", "Primero parea un n\xFAmero en la web (p\xE1gina Canales del agente)."));
|
|
9193
9836
|
return hit;
|
|
9194
9837
|
}
|
|
9195
9838
|
async function channelsCmd(kind, sub, id, ref, v) {
|
|
9196
|
-
if (kind !== "whatsapp" && kind !== "wa") throw usage(`Unknown channel: ${kind ?? "(none)"}.`, "Try: ghosty channels whatsapp ls <agent-id>");
|
|
9197
|
-
if (!id) throw usage("Missing agent id.", "Example: ghosty channels whatsapp ls <agent-id>");
|
|
9839
|
+
if (kind !== "whatsapp" && kind !== "wa") throw usage(t(`Unknown channel: ${kind ?? "(none)"}.`, `Canal desconocido: ${kind ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty channels whatsapp ls <agent-id>");
|
|
9840
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty channels whatsapp ls <agent-id>");
|
|
9198
9841
|
if (sub === "link" || sub === "unlink") return linkCmd(sub, id, v);
|
|
9199
9842
|
if (sub === "history") return historyCmd(id, v);
|
|
9200
9843
|
if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd(sub, id, v);
|
|
@@ -9203,25 +9846,25 @@ async function channelsCmd(kind, sub, id, ref, v) {
|
|
|
9203
9846
|
const listing = !sub || sub === "ls" || sub === "list";
|
|
9204
9847
|
const cur = await api(listing ? `${path}?formmy=1` : path);
|
|
9205
9848
|
if (listing) {
|
|
9206
|
-
const fwd = (n2) => !n2.formmy ? dim("\u2014") : "error" in n2.formmy ? dim("?") : n2.formmy.forwards ? "yes" : lavender("NO");
|
|
9849
|
+
const fwd = (n2) => !n2.formmy ? dim("\u2014") : "error" in n2.formmy ? dim("?") : n2.formmy.forwards ? t("yes", "s\xED") : lavender("NO");
|
|
9207
9850
|
const rows = cur.numbers.map((n2) => ({
|
|
9208
9851
|
number: n2.phoneNumber ? `+${n2.phoneNumber}${n2.label !== n2.phoneNumber && n2.label !== n2.id ? ` ${n2.label}` : ""}` : n2.label,
|
|
9209
|
-
answers: n2.mine ? lavender("this agent") : n2.recordsOnly ? "this agent (records only)" : n2.takenByOther ? "another agent" : dim("nobody (silent)"),
|
|
9852
|
+
answers: n2.mine ? lavender(t("this agent", "este agente")) : n2.recordsOnly ? t("this agent (records only)", "este agente (s\xF3lo registra)") : n2.takenByOther ? t("another agent", "otro agente") : dim(t("nobody (silent)", "nadie (en silencio)")),
|
|
9210
9853
|
"formmy forwards": fwd(n2),
|
|
9211
9854
|
id: n2.id
|
|
9212
9855
|
}));
|
|
9213
|
-
return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "id"]) : dim(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`));
|
|
9856
|
+
return out(cur, () => rows.length ? table(rows, ["number", "answers", "formmy forwards", "id"]) : dim(t(`No WhatsApp Business numbers yet. Pair one at ${cur.salesUrl}`, `Todav\xEDa no hay n\xFAmeros de WhatsApp Business. Parea uno en ${cur.salesUrl}`)));
|
|
9214
9857
|
}
|
|
9215
|
-
if (sub !== "enable" && sub !== "disable") throw usage(`Unknown subcommand: ${sub}.`, "Try: ghosty channels whatsapp ls | enable | disable | link | unlink | history \xB7 ghosty channels wa import | status | pause | groups");
|
|
9216
|
-
if (!ref) throw usage("Missing number.",
|
|
9858
|
+
if (sub !== "enable" && sub !== "disable") throw usage(t(`Unknown subcommand: ${sub}.`, `Subcomando desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty channels whatsapp ls | enable | disable | link | unlink | history \xB7 ghosty channels wa import | status | pause | groups");
|
|
9859
|
+
if (!ref) throw usage(t("Missing number.", "Falta el n\xFAmero."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} ${id} +52 55 1234 5678`);
|
|
9217
9860
|
const n = find(cur.numbers, ref);
|
|
9218
9861
|
const on = sub === "enable";
|
|
9219
|
-
if (on === n.mine) return out({ changed: 0, number: n.label, answers: on }, () => dim(`Nothing to do: ${n.label} is already ${on ? "answered by this agent" : "not answered by it"}.`));
|
|
9220
|
-
const plan = on ? `+ ${n.label}: this agent starts answering its customers${n.takenByOther ? " (takes it from the agent answering today)" : ""}` : `- ${n.label}: this agent stops answering (messages are still logged)`;
|
|
9862
|
+
if (on === n.mine) return out({ changed: 0, number: n.label, answers: on }, () => dim(t(`Nothing to do: ${n.label} is already ${on ? "answered by this agent" : "not answered by it"}.`, `Nada que hacer: ${n.label} ya ${on ? "lo contesta este agente" : "no lo contesta este agente"}.`)));
|
|
9863
|
+
const plan = on ? `+ ${n.label}: ${t(`this agent starts answering its customers${n.takenByOther ? " (takes it from the agent answering today)" : ""}`, `este agente empieza a contestarles a sus clientes${n.takenByOther ? " (se lo quita al agente que contesta hoy)" : ""}`)}` : `- ${n.label}: ${t("this agent stops answering (messages are still logged)", "este agente deja de contestar (los mensajes se siguen registrando)")}`;
|
|
9221
9864
|
if (v["dry-run"]) return out({ dryRun: true, plan: [plan] }, () => `${plan}
|
|
9222
|
-
${dim("Dry run: nothing changed.")}`);
|
|
9865
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
9223
9866
|
if (on) await confirm(`${bold(plan)}
|
|
9224
|
-
Real customers will get answers from this agent
|
|
9867
|
+
${t("Real customers will get answers from this agent.", "Clientes reales recibir\xE1n respuestas de este agente.")}`, { yes: v.yes, expect: digits(n.label).slice(-4) || n.id, retry: `ghosty channels whatsapp enable ${id} ${ref}` });
|
|
9225
9868
|
const wanted = cur.numbers.filter((x) => x.id === n.id ? on : x.mine).map((x) => x.id);
|
|
9226
9869
|
const r = await api(path, { method: "PUT", body: { numbers: wanted } });
|
|
9227
9870
|
return out(r, () => `${lavender("\u2713")} ${plan.slice(2)}`);
|
|
@@ -9231,118 +9874,121 @@ function findGroup(groups, ref) {
|
|
|
9231
9874
|
if (byJid) return byJid;
|
|
9232
9875
|
const byName = groups.filter((g) => g.name.toLowerCase() === ref.toLowerCase());
|
|
9233
9876
|
if (byName.length === 1) return byName[0];
|
|
9234
|
-
if (byName.length > 1) throw usage(`${byName.length} groups are called "${ref}".`,
|
|
9235
|
-
throw usage(`No group "${ref}" for this agent.`, groups.length ?
|
|
9877
|
+
if (byName.length > 1) throw usage(t(`${byName.length} groups are called "${ref}".`, `${byName.length} grupos se llaman "${ref}".`), `${t("Use its jid", "Usa su jid")}: ${byName.map((g) => g.jid).join(", ")}`);
|
|
9878
|
+
throw usage(t(`No group "${ref}" for this agent.`, `Este agente no tiene el grupo "${ref}".`), groups.length ? `${t("Groups", "Grupos")}: ${groups.map((g) => g.name).join(", ")}` : t("Is its personal WhatsApp paired and in some group? See the agent's Channels page.", "\xBFSu WhatsApp personal est\xE1 pareado y en alg\xFAn grupo? Revisa la p\xE1gina Canales del agente."));
|
|
9236
9879
|
}
|
|
9237
9880
|
async function groupsCmd(args, v) {
|
|
9238
9881
|
const [sub, id, ...rest] = args;
|
|
9239
9882
|
const ex = 'ghosty channels wa groups ls <agent-id> | set <agent-id> <group> [--role equipo|cliente] [--on|--off] | create <agent-id> "Name"';
|
|
9240
|
-
if (!id) throw usage("Missing agent id.",
|
|
9883
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), `${t("Example", "Ejemplo")}: ${ex}`);
|
|
9241
9884
|
const path = `/api/v2/agents/${enc7(id)}/wa/groups`;
|
|
9242
|
-
if (v.role !== void 0 && v.role !== "equipo" && v.role !== "cliente") throw usage("--role takes equipo or cliente.");
|
|
9885
|
+
if (v.role !== void 0 && v.role !== "equipo" && v.role !== "cliente") throw usage(t("--role takes equipo or cliente.", "--role acepta equipo o cliente."));
|
|
9243
9886
|
if (!sub || sub === "ls" || sub === "list") {
|
|
9244
9887
|
const r2 = await api(`${path}${v.cached ? "?live=0" : ""}`);
|
|
9245
9888
|
const rows = r2.groups.map((g2) => ({ name: g2.name, on: g2.enabled ? lavender("on") : dim("off"), role: g2.role, jid: g2.jid }));
|
|
9246
|
-
return out(r2, () => rows.length ? table(rows, ["name", "on", "role", "jid"]) : dim('No groups. Pair its personal WhatsApp in the web, or: ghosty channels wa groups create <agent-id> "Name"'));
|
|
9889
|
+
return out(r2, () => rows.length ? table(rows, ["name", "on", "role", "jid"]) : dim(t('No groups. Pair its personal WhatsApp in the web, or: ghosty channels wa groups create <agent-id> "Name"', 'Sin grupos. Parea su WhatsApp personal en la web, o: ghosty channels wa groups create <agent-id> "Nombre"')));
|
|
9247
9890
|
}
|
|
9248
9891
|
if (sub === "create") {
|
|
9249
9892
|
const name = rest.join(" ").trim();
|
|
9250
|
-
if (!name) throw usage("Missing group name.",
|
|
9893
|
+
if (!name) throw usage(t("Missing group name.", "Falta el nombre del grupo."), t("Example", "Ejemplo") + `: ghosty channels wa groups create ${id} "Equipo ventas" --role equipo`);
|
|
9251
9894
|
const r2 = await api(path, { method: "POST", body: { create: name, role: v.role ?? "equipo" } });
|
|
9252
|
-
return out(r2, () => `${lavender("\u2713")} Created ${bold(name)} (${v.role ?? "equipo"}, on)
|
|
9253
|
-
invite: ${r2.inviteUrl}`);
|
|
9895
|
+
return out(r2, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${bold(name)} (${v.role ?? "equipo"}, on)
|
|
9896
|
+
${t("invite", "invitaci\xF3n")}: ${r2.inviteUrl}`);
|
|
9254
9897
|
}
|
|
9255
|
-
if (sub !== "set") throw usage(`Unknown groups subcommand: ${sub}.`, ex);
|
|
9898
|
+
if (sub !== "set") throw usage(t(`Unknown groups subcommand: ${sub}.`, `Subcomando de groups desconocido: ${sub}.`), ex);
|
|
9256
9899
|
const ref = rest.join(" ").trim();
|
|
9257
|
-
if (!ref) throw usage("Missing group (name or jid).",
|
|
9258
|
-
if (v.on && v.off) throw usage("Use --on or --off, not both.");
|
|
9900
|
+
if (!ref) throw usage(t("Missing group (name or jid).", "Falta el grupo (nombre o jid)."), t("Example", "Ejemplo") + `: ghosty channels wa groups set ${id} "Pia-cotizadora" --role equipo --on`);
|
|
9901
|
+
if (v.on && v.off) throw usage(t("Use --on or --off, not both.", "Usa --on o --off, no los dos."));
|
|
9259
9902
|
const cur = await api(`${path}?live=0`);
|
|
9260
9903
|
const g = findGroup(cur.groups, ref);
|
|
9261
9904
|
const enabled = v.on ? true : v.off ? false : g.enabled;
|
|
9262
9905
|
const role = v.role ?? g.role;
|
|
9263
9906
|
const changes = [...enabled !== g.enabled ? [`${g.enabled ? "on" : "off"}\u2192${enabled ? "on" : "off"}`] : [], ...role !== g.role ? [`${g.role}\u2192${role}`] : []];
|
|
9264
|
-
if (!changes.length) return out({ changed: 0, group: g }, () => dim(`Nothing to do: ${g.name} is already ${g.enabled ? "on" : "off"} as ${g.role}.`));
|
|
9907
|
+
if (!changes.length) return out({ changed: 0, group: g }, () => dim(t(`Nothing to do: ${g.name} is already ${g.enabled ? "on" : "off"} as ${g.role}.`, `Nada que hacer: ${g.name} ya est\xE1 ${g.enabled ? "encendido" : "apagado"} como ${g.role}.`)));
|
|
9265
9908
|
const plan = `~ ${g.name}: ${changes.join(", ")}`;
|
|
9266
9909
|
if (v["dry-run"]) return out({ dryRun: true, plan: [plan] }, () => `${plan}
|
|
9267
|
-
${dim("Dry run: nothing changed.")}`);
|
|
9910
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
9268
9911
|
if (enabled && !g.enabled) await confirm(`${bold(plan)}
|
|
9269
|
-
The agent will start answering in this group as ${role}.`, { yes: v.yes, retry: `ghosty channels wa groups set ${id} "${ref}"${v.role ? ` --role ${v.role}` : ""} --on` });
|
|
9912
|
+
${t(`The agent will start answering in this group as ${role}.`, `El agente empezar\xE1 a contestar en este grupo como ${role}.`)}`, { yes: v.yes, retry: `ghosty channels wa groups set ${id} "${ref}"${v.role ? ` --role ${v.role}` : ""} --on` });
|
|
9270
9913
|
const r = await api(path, { method: "POST", body: { jid: g.jid, enabled, role } });
|
|
9271
9914
|
const now = r.groups.find((x) => x.jid === g.jid);
|
|
9272
|
-
return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${g.name}: ${now?.enabled ? "on" : "off"} as ${now?.role ?? role}`);
|
|
9915
|
+
return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${g.name}: ${now?.enabled ? "on" : "off"} ${t("as", "como")} ${now?.role ?? role}`);
|
|
9273
9916
|
}
|
|
9274
9917
|
async function waCmd(sub, id, v) {
|
|
9275
|
-
if (!id) throw usage("Missing agent id.",
|
|
9918
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + `: ghosty channels wa ${sub} <agent-id>`);
|
|
9276
9919
|
const base = `/api/v2/agents/${enc7(id)}/wa`;
|
|
9277
9920
|
if (sub === "status") {
|
|
9278
9921
|
const s = await api(base);
|
|
9279
|
-
return out(s, () => `${s.status === "connected" ? lavender("\u25CF connected") : dim(`\u25CB ${s.status}`)} ${s.phone ? `+${s.phone}` : dim("(no number)")}${s.live ? "" : dim(" \xB7 no socket in gs")}${s.hasCreds ? "" : dim(" \xB7 no saved session")}${s.reason ? `
|
|
9280
|
-
${dim(s.reason)}` : ""}`);
|
|
9922
|
+
return out(s, () => `${s.status === "connected" ? lavender(t("\u25CF connected", "\u25CF conectado")) : dim(`\u25CB ${s.status}`)} ${s.phone ? `+${s.phone}` : dim(t("(no number)", "(sin n\xFAmero)"))}${s.live ? "" : dim(t(" \xB7 no socket in gs", " \xB7 sin socket en gs"))}${s.hasCreds ? "" : dim(t(" \xB7 no saved session", " \xB7 sin sesi\xF3n guardada"))}${s.reason ? `
|
|
9923
|
+
${dim(serverText(s.reason))}` : ""}`);
|
|
9281
9924
|
}
|
|
9282
9925
|
if (sub === "pair") {
|
|
9283
9926
|
const phone = (v.phone ?? "").replace(/\D/g, "");
|
|
9284
|
-
if (phone.length < 10) throw usage("Missing --phone (10-15 digits, with country code).",
|
|
9927
|
+
if (phone.length < 10) throw usage(t("Missing --phone (10-15 digits, with country code).", "Falta --phone (10-15 d\xEDgitos, con lada de pa\xEDs)."), t("Example", "Ejemplo") + `: ghosty channels wa pair ${id} --phone 5215512345678`);
|
|
9285
9928
|
await api(base, { method: "POST", body: { phone } });
|
|
9286
9929
|
let s = { status: "connecting", phone: null, live: false, hasCreds: false };
|
|
9287
9930
|
for (let i = 0; i < 15 && !s.pairingCode && s.status !== "connected"; i++) {
|
|
9288
9931
|
await new Promise((r2) => setTimeout(r2, 1e3));
|
|
9289
9932
|
s = await api(base);
|
|
9290
9933
|
}
|
|
9291
|
-
return out(s, () => s.status === "connected" ? `${lavender("\u2713")} Already connected (+${s.phone}).` : s.pairingCode ?
|
|
9292
|
-
${dim("On the phone: WhatsApp \u2192 Linked devices \u2192 Link with phone number \u2192 type it. Then: ghosty channels wa status " + id)}` : dim(`No code yet (status: ${s.status}${s.reason ? `, ${s.reason}` : ""}). Try again in a minute.`));
|
|
9934
|
+
return out(s, () => s.status === "connected" ? `${lavender("\u2713")} ${t("Already connected", "Ya est\xE1 conectado")} (+${s.phone}).` : s.pairingCode ? `${t("Code", "C\xF3digo")}: ${bold(s.pairingCode)}
|
|
9935
|
+
${dim(t("On the phone: WhatsApp \u2192 Linked devices \u2192 Link with phone number \u2192 type it. Then: ghosty channels wa status ", "En el tel\xE9fono: WhatsApp \u2192 Dispositivos vinculados \u2192 Vincular con n\xFAmero de tel\xE9fono \u2192 escr\xEDbelo. Luego: ghosty channels wa status ") + id)}` : dim(t(`No code yet (status: ${s.status}${s.reason ? `, ${serverText(s.reason)}` : ""}). Try again in a minute.`, `Todav\xEDa no hay c\xF3digo (estado: ${s.status}${s.reason ? `, ${s.reason}` : ""}). Intenta de nuevo en un minuto.`)));
|
|
9293
9936
|
}
|
|
9294
9937
|
if (sub === "pause") {
|
|
9295
9938
|
await api(base, { method: "DELETE" });
|
|
9296
|
-
return out({ paused: true }, () => `${lavender("\u2713")} Paused. The session is kept: ${dim(
|
|
9939
|
+
return out({ paused: true }, () => `${lavender("\u2713")} ${t("Paused. The session is kept:", "En pausa. La sesi\xF3n se conserva:")} ${dim(t("ghosty channels wa import \u2026 or the Channels page reconnects it", "ghosty channels wa import \u2026 o la p\xE1gina Canales la reconecta"))}`);
|
|
9297
9940
|
}
|
|
9298
9941
|
const file = v["from-file"] ?? v["from-dir"];
|
|
9299
|
-
if (!file || v["from-file"] && v["from-dir"]) throw usage("Use --from-file F (EasyBits JSON) or --from-dir DIR (Baileys multi-file folder).",
|
|
9942
|
+
if (!file || v["from-file"] && v["from-dir"]) throw usage(t("Use --from-file F (EasyBits JSON) or --from-dir DIR (Baileys multi-file folder).", "Usa --from-file F (JSON de EasyBits) o --from-dir DIR (carpeta multi-archivo de Baileys)."), t("Example", "Ejemplo") + `: ghosty channels wa import ${id} --from-file creds.json --own-number no --dry-run`);
|
|
9300
9943
|
const own = v["own-number"];
|
|
9301
|
-
if (own !== "yes" && own !== "no") throw usage("Missing --own-number yes|no.", "yes = the number belongs to the agent \xB7 no = it is a person's number (only their chat with themselves is the owner's)");
|
|
9944
|
+
if (own !== "yes" && own !== "no") throw usage(t("Missing --own-number yes|no.", "Falta --own-number yes|no."), t("yes = the number belongs to the agent \xB7 no = it is a person's number (only their chat with themselves is the owner's)", "yes = el n\xFAmero es del agente \xB7 no = es el n\xFAmero de una persona (s\xF3lo su chat consigo misma es \xABdel due\xF1o\xBB)"));
|
|
9302
9945
|
let data;
|
|
9303
9946
|
if (v["from-dir"]) {
|
|
9304
9947
|
const m = await readMultiFileAuth(v["from-dir"]);
|
|
9305
|
-
info(dim(`${v["from-dir"]}: ${Object.entries(m.counts).map(([
|
|
9948
|
+
info(dim(`${v["from-dir"]}: ${Object.entries(m.counts).map(([k, n]) => `${k} ${n}`).join(" \xB7 ") || t("no keys", "sin llaves")}${m.skipped.length ? t(` \xB7 skipped ${m.skipped.length} unknown file(s)`, ` \xB7 se omitieron ${m.skipped.length} archivo(s) desconocido(s)`) : ""}`));
|
|
9306
9949
|
data = { authCreds: m.authCreds, authKeys: m.authKeys };
|
|
9307
9950
|
} else data = JSON.parse(await readFile8(file, "utf8"));
|
|
9308
9951
|
const body = { authCreds: data.authCreds, authKeys: data.authKeys, phone: data.phone, groups: data.groups, ownNumber: own === "yes", replace: !!v.replace };
|
|
9309
9952
|
const plan = await api(`${base}/import`, { method: "POST", body: { ...body, dryRun: true } });
|
|
9310
|
-
if (v["dry-run"]) return out(plan, () => `${plan.plan.join("\n")}
|
|
9311
|
-
${dim("Dry run: nothing changed.")}`);
|
|
9312
|
-
await confirm(`${bold(plan.plan.join("\n"))}
|
|
9313
|
-
This number starts living in gs. Turn off its EasyBits socket first, WITHOUT logout (two sockets knock each other off)
|
|
9953
|
+
if (v["dry-run"]) return out(plan, () => `${plan.plan.map((x) => serverText(x)).join("\n")}
|
|
9954
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
9955
|
+
await confirm(`${bold(plan.plan.map((x) => serverText(x)).join("\n"))}
|
|
9956
|
+
${t("This number starts living in gs. Turn off its EasyBits socket first, WITHOUT logout (two sockets knock each other off).", "Este n\xFAmero empieza a vivir en gs. Apaga antes su socket de EasyBits, SIN logout (dos sockets se tumban entre s\xED).")}`, { yes: v.yes, expect: plan.phone.slice(-4), retry: `ghosty channels wa import ${id} ${v["from-dir"] ? "--from-dir" : "--from-file"} ${file} --own-number ${own}${v.replace ? " --replace" : ""}` });
|
|
9314
9957
|
const r = await api(`${base}/import`, { method: "POST", body: { ...body, connect: !v["no-connect"] } });
|
|
9315
|
-
return out(r, () => `${lavender("\u2713")} +${r.phone} imported \xB7 status: ${r.status === "connected" ? lavender("connected") : r.status}${r.reason ? dim(` (${r.reason})`) : ""}
|
|
9316
|
-
${dim(`Next: ghosty channels wa groups ls ${id} \xB7 delete ${file}: it holds the account's keys`)}`);
|
|
9958
|
+
return out(r, () => `${lavender("\u2713")} +${r.phone} ${t("imported \xB7 status", "importado \xB7 estado")}: ${r.status === "connected" ? lavender(t("connected", "conectado")) : r.status}${r.reason ? dim(` (${serverText(r.reason)})`) : ""}
|
|
9959
|
+
${dim(t(`Next: ghosty channels wa groups ls ${id} \xB7 delete ${file}: it holds the account's keys`, `Sigue: ghosty channels wa groups ls ${id} \xB7 borra ${file}: guarda las llaves de la cuenta`))}`);
|
|
9317
9960
|
}
|
|
9318
9961
|
async function linkCmd(sub, id, v) {
|
|
9319
|
-
if (!id || !v.integration) throw usage("Missing agent id or --integration.",
|
|
9320
|
-
if (sub === "link" && v.answer !== "on" && v.answer !== "off") throw usage("Missing --answer on|off.", "on = this agent answers real customers \xB7 off = it only records (add --forward so gs still sees the conversations)");
|
|
9962
|
+
if (!id || !v.integration) throw usage(t("Missing agent id or --integration.", "Falta el id del agente o --integration."), t("Example", "Ejemplo") + `: ghosty channels whatsapp ${sub} <agent-id> --integration <formmy-integration-id> --answer off --dry-run`);
|
|
9963
|
+
if (sub === "link" && v.answer !== "on" && v.answer !== "off") throw usage(t("Missing --answer on|off.", "Falta --answer on|off."), t("on = this agent answers real customers \xB7 off = it only records (add --forward so gs still sees the conversations)", "on = este agente contesta a clientes reales \xB7 off = s\xF3lo registra (agrega --forward para que gs siga viendo las conversaciones)"));
|
|
9321
9964
|
const path = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
|
|
9322
9965
|
const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {} } };
|
|
9323
9966
|
const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
|
|
9324
|
-
const text = `${plan.plan.join("\n")}
|
|
9325
|
-
${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (as ${plan.formmy.by})`)}`;
|
|
9967
|
+
const text = `${plan.plan.map((x) => serverText(x)).join("\n")}
|
|
9968
|
+
${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (${t("as", "como")} ${plan.formmy.by})`)}`;
|
|
9326
9969
|
if (v["dry-run"]) return out(plan, () => `${text}
|
|
9327
|
-
${dim("Dry run: nothing changed.")}`);
|
|
9970
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
9328
9971
|
const digits4 = (plan.number.phoneNumberId ?? v.integration).slice(-4);
|
|
9329
9972
|
await confirm(`${bold(text)}
|
|
9330
|
-
${sub === "link" ? "From now on this number's messages reach this agent in gs." : "This number goes back to where it was before."}`, { yes: v.yes, expect: digits4, retry: `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}` : ""}` });
|
|
9973
|
+
${sub === "link" ? t("From now on this number's messages reach this agent in gs.", "Desde ahora los mensajes de este n\xFAmero le llegan a este agente en gs.") : t("This number goes back to where it was before.", "Este n\xFAmero regresa a donde estaba antes.")}`, { yes: v.yes, expect: digits4, retry: `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}` : ""}` });
|
|
9331
9974
|
const r = await api(path, { method: "POST", body });
|
|
9332
|
-
return out(r, () => `${lavender("\u2713")} ${sub === "link" ? `Linked (link ${r.linkId}). Check: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m` : "Unlinked: Formmy points where it did before."}`);
|
|
9975
|
+
return out(r, () => `${lavender("\u2713")} ${sub === "link" ? t(`Linked (link ${r.linkId}). Check: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m`, `Enlazado (enlace ${r.linkId}). Revisa: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m`) : t("Unlinked: Formmy points where it did before.", "Desenlazado: Formmy apunta a donde apuntaba antes.")}`);
|
|
9333
9976
|
}
|
|
9334
9977
|
async function historyCmd(id, v) {
|
|
9335
|
-
if (!v.integration) throw usage("Missing --integration.",
|
|
9978
|
+
if (!v.integration) throw usage(t("Missing --integration.", "Falta --integration."), t("Example", "Ejemplo") + `: ghosty channels whatsapp history ${id} --integration <formmy-integration-id> --days 7 --dry-run`);
|
|
9336
9979
|
const days = Number(v.days ?? 7);
|
|
9337
|
-
if (!Number.isInteger(days) || days < 1 || days > 30) throw usage("--days takes 1 to 30.");
|
|
9980
|
+
if (!Number.isInteger(days) || days < 1 || days > 30) throw usage(t("--days takes 1 to 30.", "--days acepta de 1 a 30."));
|
|
9338
9981
|
const path = `/api/v2/agents/${enc7(id)}/whatsapp/history`;
|
|
9339
9982
|
const c = await api(path, { method: "POST", body: { integrationId: v.integration, days, dryRun: true } });
|
|
9340
|
-
const plan =
|
|
9983
|
+
const plan = t(
|
|
9984
|
+
`${c.conversations} conversation(s), ${c.messages} message(s), ${c.orders} order(s) from the last ${days} day(s)${c.phoneNumber ? ` of +${c.phoneNumber}` : ""} \u2192 board ${c.board}`,
|
|
9985
|
+
`${c.conversations} conversaci\xF3n(es), ${c.messages} mensaje(s), ${c.orders} pedido(s) de los \xFAltimos ${days} d\xEDa(s)${c.phoneNumber ? ` de +${c.phoneNumber}` : ""} \u2192 tablero ${c.board}`
|
|
9986
|
+
);
|
|
9341
9987
|
if (v["dry-run"]) return out(c, () => `${plan}
|
|
9342
|
-
${dim("Dry run: nothing imported. Re-running later never duplicates.")}`);
|
|
9343
|
-
if (!c.conversations) return out({ ...c, imported: 0 }, () => dim(`Nothing to import: ${plan}.`));
|
|
9988
|
+
${dim(t("Dry run: nothing imported. Re-running later never duplicates.", "Simulacro: no se import\xF3 nada. Correrlo otra vez nunca duplica."))}`);
|
|
9989
|
+
if (!c.conversations) return out({ ...c, imported: 0 }, () => dim(t(`Nothing to import: ${plan}.`, `Nada que importar: ${plan}.`)));
|
|
9344
9990
|
await confirm(`${bold(plan)}
|
|
9345
|
-
Cards appear on the team's board (no "new lead" alerts).`, { yes: v.yes, retry: `ghosty channels whatsapp history ${id} --integration ${v.integration} --days ${days}` });
|
|
9991
|
+
${t(`Cards appear on the team's board (no "new lead" alerts).`, "Las tarjetas aparecen en el tablero del equipo (sin avisos de \xABlead nuevo\xBB).")}`, { yes: v.yes, retry: `ghosty channels whatsapp history ${id} --integration ${v.integration} --days ${days}` });
|
|
9346
9992
|
const sum = { newCards: 0, updatedCards: 0, messages: 0, orders: 0, suppressed: 0 };
|
|
9347
9993
|
let cursor = 0;
|
|
9348
9994
|
let done = 0;
|
|
@@ -9350,47 +9996,47 @@ Cards appear on the team's board (no "new lead" alerts).`, { yes: v.yes, retry:
|
|
|
9350
9996
|
const p = await api(path, { method: "POST", body: { integrationId: v.integration, days, cursor } });
|
|
9351
9997
|
for (const k of Object.keys(sum)) sum[k] += p[k];
|
|
9352
9998
|
done += p.page;
|
|
9353
|
-
if (!globals.json) info(dim(` ${done}/${p.total} conversations`));
|
|
9999
|
+
if (!globals.json) info(dim(` ${done}/${p.total} ${t("conversations", "conversaciones")}`));
|
|
9354
10000
|
cursor = p.nextCursor;
|
|
9355
10001
|
}
|
|
9356
|
-
out({ ...sum, conversations: done }, () => `${lavender("\u2713")} ${sum.newCards} new card(s), ${sum.updatedCards} updated, ${sum.messages} message(s), ${sum.orders} order(s)${sum.suppressed ? dim(` \xB7 ${sum.suppressed} bot draft(s) the customer never saw, skipped`) : ""}`);
|
|
10002
|
+
out({ ...sum, conversations: done }, () => `${lavender("\u2713")} ${t(`${sum.newCards} new card(s), ${sum.updatedCards} updated, ${sum.messages} message(s), ${sum.orders} order(s)`, `${sum.newCards} tarjeta(s) nueva(s), ${sum.updatedCards} actualizada(s), ${sum.messages} mensaje(s), ${sum.orders} pedido(s)`)}${sum.suppressed ? dim(t(` \xB7 ${sum.suppressed} bot draft(s) the customer never saw, skipped`, ` \xB7 se omitieron ${sum.suppressed} borrador(es) del bot que el cliente nunca vio`)) : ""}`);
|
|
9357
10003
|
}
|
|
9358
10004
|
|
|
9359
10005
|
// src/commands/board.ts
|
|
9360
|
-
var
|
|
10006
|
+
var who = (role) => ({ user: t("customer", "cliente"), assistant: t("agent", "agente"), operator: t("operator", "operador") })[role] ?? role;
|
|
9361
10007
|
async function boardLs(id, v) {
|
|
9362
10008
|
const qs = new URLSearchParams({ ...v.q ? { q: v.q } : {}, ...v.limit ? { limit: v.limit } : {} });
|
|
9363
10009
|
const r = await api(`/api/v2/agents/${encodeURIComponent(id)}/board/conversations?${qs}`);
|
|
9364
10010
|
const rows = r.conversations.map((c) => ({ folio: c.folio ? `#${c.folio}` : "", name: c.name ?? "", phone: c.phone, column: c.column ?? "", msgs: c.messages, last: (c.lastActivity ?? "").slice(0, 16).replace("T", " ") }));
|
|
9365
10011
|
out(r, () => rows.length ? `${table(rows, ["folio", "name", "phone", "column", "msgs", "last"])}
|
|
9366
|
-
${dim(`\xAB${r.board}\xBB \xB7 thread: ghosty board show ${id} <folio|phone>`)}` : dim(`No cards on \xAB${r.board}\xBB${v.q ? ` matching "${v.q}"` : ""}.`));
|
|
10012
|
+
${dim(`\xAB${r.board}\xBB \xB7 ${t("thread", "hilo")}: ghosty board show ${id} <folio|phone>`)}` : dim(t(`No cards on \xAB${r.board}\xBB${v.q ? ` matching "${v.q}"` : ""}.`, `No hay tarjetas en \xAB${r.board}\xBB${v.q ? ` que coincidan con "${v.q}"` : ""}.`)));
|
|
9367
10013
|
}
|
|
9368
10014
|
async function boardShow(id, ref, v) {
|
|
9369
|
-
if (!ref) throw usage("Missing conversation.", `Example: ghosty board show ${id} "#12" (folio, phone, session or id; list them with ghosty board ls ${id})`);
|
|
10015
|
+
if (!ref) throw usage(t("Missing conversation.", "Falta la conversaci\xF3n."), t(`Example: ghosty board show ${id} "#12" (folio, phone, session or id; list them with ghosty board ls ${id})`, `Ejemplo: ghosty board show ${id} "#12" (folio, tel\xE9fono, sesi\xF3n o id; l\xEDstalas con ghosty board ls ${id})`));
|
|
9370
10016
|
const r = await api(`/api/v2/agents/${encodeURIComponent(id)}/board/conversations/${encodeURIComponent(ref)}${v.limit ? `?limit=${encodeURIComponent(v.limit)}` : ""}`);
|
|
9371
10017
|
const c = r.conversation;
|
|
9372
|
-
const head = `${bold(c.name ?? c.phone)} ${dim(`${c.folio ? `#${c.folio} \xB7 ` : ""}+${c.phone} \xB7 ${c.column ?? "no column"} \xB7 ${c.session}`)}`;
|
|
10018
|
+
const head = `${bold(c.name ?? c.phone)} ${dim(`${c.folio ? `#${c.folio} \xB7 ` : ""}+${c.phone} \xB7 ${c.column ?? t("no column", "sin columna")} \xB7 ${c.session}`)}`;
|
|
9373
10019
|
const line2 = (m) => {
|
|
9374
10020
|
const at = m.createdAt.slice(5, 16).replace("T", " ");
|
|
9375
10021
|
const media = m.mediaType || m.mediaFilename ? `
|
|
9376
|
-
${dim(`\u{1F4CE} ${m.mediaType ?? "file"} ${m.mediaFilename ?? ""} \xB7 ${m.mediaCopied ? "copy in gs" : "no copy"}`)}` : "";
|
|
9377
|
-
return `${dim(at)} ${lavender((
|
|
10022
|
+
${dim(`\u{1F4CE} ${m.mediaType ?? t("file", "archivo")} ${m.mediaFilename ?? ""} \xB7 ${m.mediaCopied ? t("copy in gs", "copia en gs") : t("no copy", "sin copia")}`)}` : "";
|
|
10023
|
+
return `${dim(at)} ${lavender(who(m.role).padEnd(8))} ${m.content.replace(/\n/g, "\n ")}${media}`;
|
|
9378
10024
|
};
|
|
9379
10025
|
out(r, () => [head, ...r.messages.map(line2)].join("\n"));
|
|
9380
10026
|
}
|
|
9381
10027
|
async function boardCmd(sub, id, v, ref) {
|
|
9382
10028
|
if (sub === "ls" || sub === "list") {
|
|
9383
|
-
if (!id) throw usage("Missing agent id.", "Example: ghosty board ls <agent-id> [--q text] [--limit 20]");
|
|
10029
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty board ls <agent-id> [--q text] [--limit 20]");
|
|
9384
10030
|
return boardLs(id, v);
|
|
9385
10031
|
}
|
|
9386
10032
|
if (sub === "show") {
|
|
9387
|
-
if (!id) throw usage("Missing agent id.", '
|
|
10033
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ': ghosty board show <agent-id> "#12"');
|
|
9388
10034
|
return boardShow(id, ref, v);
|
|
9389
10035
|
}
|
|
9390
|
-
if (sub !== "archive") throw usage(`Unknown board subcommand: ${sub ?? "(none)"}.`, "Try: ghosty board ls <agent-id> \xB7 ghosty board show <agent-id> <folio|phone> \xB7 ghosty board archive <agent-id> --integration prueba --dry-run");
|
|
9391
|
-
if (!id) throw usage("Missing agent id.", "Example: ghosty board archive <agent-id> --integration prueba --dry-run");
|
|
10036
|
+
if (sub !== "archive") throw usage(t(`Unknown board subcommand: ${sub ?? "(none)"}.`, `Subcomando de board desconocido: ${sub ?? "(ninguno)"}.`), t("Try", "Prueba") + ": ghosty board ls <agent-id> \xB7 ghosty board show <agent-id> <folio|phone> \xB7 ghosty board archive <agent-id> --integration prueba --dry-run");
|
|
10037
|
+
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty board archive <agent-id> --integration prueba --dry-run");
|
|
9392
10038
|
const filter = { ...v.integration ? { integration: v.integration } : {}, ...v.column ? { column: v.column } : {}, ...v.before ? { before: v.before } : {} };
|
|
9393
|
-
if (!Object.keys(filter).length) throw usage("Add at least one filter: --integration, --column or --before.", "A filter-less archive would empty the board.");
|
|
10039
|
+
if (!Object.keys(filter).length) throw usage(t("Add at least one filter: --integration, --column or --before.", "Agrega al menos un filtro: --integration, --column o --before."), t("A filter-less archive would empty the board.", "Archivar sin filtro vaciar\xEDa el tablero."));
|
|
9394
10040
|
const retry = `ghosty board archive ${id} ${Object.entries(filter).map(([k, x]) => `--${k} ${JSON.stringify(x)}`).join(" ")}`;
|
|
9395
10041
|
if (!v["dry-run"]) requireYesIfHeadless(v.yes, retry);
|
|
9396
10042
|
const path = `/api/v2/agents/${encodeURIComponent(id)}/board/archive`;
|
|
@@ -9398,12 +10044,12 @@ async function boardCmd(sub, id, v, ref) {
|
|
|
9398
10044
|
const sample = () => table(plan.sample.map((c) => ({ name: c.name ?? "", phone: c.phone, column: c.estado_label ?? "" })), ["name", "phone", "column"]);
|
|
9399
10045
|
if (v["dry-run"] || plan.matched === 0) {
|
|
9400
10046
|
return out(plan, () => plan.matched ? `${sample()}
|
|
9401
|
-
${dim(`Would archive ${plan.matched} card(s) on \xAB${plan.board}\xBB. Dry run: nothing changed.`)}` : dim(`No cards match on \xAB${plan.board}\xBB.`));
|
|
10047
|
+
${dim(t(`Would archive ${plan.matched} card(s) on \xAB${plan.board}\xBB. Dry run: nothing changed.`, `Se archivar\xEDan ${plan.matched} tarjeta(s) en \xAB${plan.board}\xBB. Simulacro: no se cambi\xF3 nada.`))}` : dim(t(`No cards match on \xAB${plan.board}\xBB.`, `Ninguna tarjeta coincide en \xAB${plan.board}\xBB.`)));
|
|
9402
10048
|
}
|
|
9403
10049
|
await confirm(`${sample()}
|
|
9404
|
-
Archive ${plan.matched} card(s) on \xAB${plan.board}\xBB? (reversible: a new message brings a card back)`, { yes: v.yes, retry });
|
|
10050
|
+
${t(`Archive ${plan.matched} card(s) on \xAB${plan.board}\xBB? (reversible: a new message brings a card back)`, `\xBFArchivar ${plan.matched} tarjeta(s) en \xAB${plan.board}\xBB? (reversible: un mensaje nuevo regresa la tarjeta)`)}`, { yes: v.yes, retry });
|
|
9405
10051
|
const r = await api(path, { body: filter });
|
|
9406
|
-
out(r, () => `${lavender("\u2713")} Archived ${r.archived} card(s) on \xAB${r.board}\xBB.`);
|
|
10052
|
+
out(r, () => `${lavender("\u2713")} ${t(`Archived ${r.archived} card(s) on \xAB${r.board}\xBB.`, `Se archivaron ${r.archived} tarjeta(s) en \xAB${r.board}\xBB.`)}`);
|
|
9407
10053
|
}
|
|
9408
10054
|
|
|
9409
10055
|
// src/commands/boards.ts
|
|
@@ -9416,31 +10062,31 @@ async function boardsCmd(sub, a1, a2, v, context) {
|
|
|
9416
10062
|
switch (sub ?? "ls") {
|
|
9417
10063
|
case "ls":
|
|
9418
10064
|
case "list": {
|
|
9419
|
-
if (!a1) throw usage("Missing workspace.", "Example: ghosty boards ls <workspace-slug>");
|
|
10065
|
+
if (!a1) throw usage(t("Missing workspace.", "Falta el espacio."), t("Example", "Ejemplo") + ": ghosty boards ls <workspace-slug>");
|
|
9420
10066
|
const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`);
|
|
9421
|
-
const rows = r.boards.map((b) => ({ name: b.main ? `${b.name} (main)` : b.name, cards: b.cards, agents: b.agents.map((x) => x.name ?? x.id).join(", "), columns: b.columns.length, id: b.id }));
|
|
10067
|
+
const rows = r.boards.map((b) => ({ name: b.main ? `${b.name} (${t("main", "principal")})` : b.name, cards: b.cards, agents: b.agents.map((x) => x.name ?? x.id).join(", "), columns: b.columns.length, id: b.id }));
|
|
9422
10068
|
return out(r, () => table(rows, ["name", "cards", "agents", "columns", "id"]));
|
|
9423
10069
|
}
|
|
9424
10070
|
case "create": {
|
|
9425
|
-
if (!a1 || !a2) throw usage("Missing workspace or name.", '
|
|
10071
|
+
if (!a1 || !a2) throw usage(t("Missing workspace or name.", "Falta el espacio o el nombre."), t("Example", "Ejemplo") + ': ghosty boards create <workspace> "Siiqtec - Ixmiquilpan" --columns-from main');
|
|
9426
10072
|
const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`, { method: "POST", body: { name: a2, ...v["columns-from"] ? { columnsFrom: v["columns-from"] } : {} } });
|
|
9427
|
-
return out(r, () => `${lavender("\u2713")} Board \xAB${r.name}\xBB created${r.columnsFrom ? ` with the ${r.columns} column(s) of \xAB${r.columnsFrom}\xBB` : ""}. ${dim(
|
|
10073
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.name}\xBB created${r.columnsFrom ? ` with the ${r.columns} column(s) of \xAB${r.columnsFrom}\xBB` : ""}.`, `Tablero \xAB${r.name}\xBB creado${r.columnsFrom ? ` con las ${r.columns} columna(s) de \xAB${r.columnsFrom}\xBB` : ""}.`)} ${dim(`${t("Send an agent's cards there", "Manda ah\xED las tarjetas de un agente")}: ghosty boards assign <agent-id> "${r.name}"`)}`);
|
|
9428
10074
|
}
|
|
9429
10075
|
case "rm": {
|
|
9430
|
-
if (!a1 || !a2) throw usage("Missing workspace or board.", '
|
|
10076
|
+
if (!a1 || !a2) throw usage(t("Missing workspace or board.", "Falta el espacio o el tablero."), t("Example", "Ejemplo") + ': ghosty boards rm <workspace> "Prueba"');
|
|
9431
10077
|
requireYesIfHeadless(v.yes, `ghosty boards rm ${a1} ${JSON.stringify(a2)}`);
|
|
9432
|
-
await confirm(`Delete the board \xAB${a2}\xBB of ${a1}? (only an empty one; its agents go back to the main board)`, { yes: v.yes, retry: `ghosty boards rm ${a1} ${JSON.stringify(a2)}` });
|
|
10078
|
+
await confirm(t(`Delete the board \xAB${a2}\xBB of ${a1}? (only an empty one; its agents go back to the main board)`, `\xBFBorrar el tablero \xAB${a2}\xBB de ${a1}? (s\xF3lo si est\xE1 vac\xEDo; sus agentes regresan al tablero principal)`), { yes: v.yes, retry: `ghosty boards rm ${a1} ${JSON.stringify(a2)}` });
|
|
9433
10079
|
const r = await api(`/api/v2/workspaces/${enc8(a1)}/boards`, { method: "DELETE", body: { board: a2 } });
|
|
9434
|
-
return out(r, () => `${lavender("\u2713")} Board \xAB${r.deleted}\xBB deleted.`);
|
|
10080
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.deleted}\xBB deleted.`, `Tablero \xAB${r.deleted}\xBB borrado.`)}`);
|
|
9435
10081
|
}
|
|
9436
10082
|
case "assign": {
|
|
9437
|
-
if (!a1 || !a2) throw usage("Missing agent or board.", '
|
|
10083
|
+
if (!a1 || !a2) throw usage(t("Missing agent or board.", "Falta el agente o el tablero."), t("Example", "Ejemplo") + ': ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan" (' + t("or main", "o main") + ")");
|
|
9438
10084
|
const r = await api(`/api/v2/agents/${enc8(a1)}`, { method: "PATCH", body: { board: a2 } });
|
|
9439
|
-
return out(r, () => `${lavender("\u2713")} New cards of ${a1} go to \xAB${r.board}\xBB. ${dim(
|
|
10085
|
+
return out(r, () => `${lavender("\u2713")} ${t(`New cards of ${a1} go to \xAB${r.board}\xBB.`, `Las tarjetas nuevas de ${a1} van a \xAB${r.board}\xBB.`)} ${dim(`${t("Existing ones stay where they are", "Las que ya existen se quedan donde est\xE1n")}: ghosty boards move-cards ${r.workspace} --from \u2026 --to "${r.board}" \u2026`)}`);
|
|
9440
10086
|
}
|
|
9441
10087
|
case "move-cards": {
|
|
9442
|
-
if (!a1 || !v.from || !v.to) throw usage("Missing workspace, --from or --to.", '
|
|
9443
|
-
if (!v.integration && !v.phone && !v.all) throw usage("Add a filter: --integration ID, --phone N or --all.", "A filter-less move would empty the board.");
|
|
10088
|
+
if (!a1 || !v.from || !v.to) throw usage(t("Missing workspace, --from or --to.", "Falta el espacio, --from o --to."), t("Example", "Ejemplo") + ': ghosty boards move-cards <workspace> --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run');
|
|
10089
|
+
if (!v.integration && !v.phone && !v.all) throw usage(t("Add a filter: --integration ID, --phone N or --all.", "Agrega un filtro: --integration ID, --phone N o --all."), t("A filter-less move would empty the board.", "Mover sin filtro vaciar\xEDa el tablero."));
|
|
9444
10090
|
const path = `/api/v2/workspaces/${enc8(a1)}/boards/move-cards`;
|
|
9445
10091
|
const body = { from: v.from, to: v.to, ...v.integration ? { integration: v.integration } : {}, ...v.phone ? { phone: v.phone } : {}, ...v.all ? { all: true } : {} };
|
|
9446
10092
|
const retry = `ghosty boards move-cards ${a1} --from ${JSON.stringify(v.from)} --to ${JSON.stringify(v.to)}${v.integration ? ` --integration ${v.integration}` : ""}${v.phone ? ` --phone ${v.phone}` : ""}${v.all ? " --all" : ""}`;
|
|
@@ -9448,19 +10094,268 @@ async function boardsCmd(sub, a1, a2, v, context) {
|
|
|
9448
10094
|
const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
|
|
9449
10095
|
const sample = table(plan.sample.map((c) => ({ folio: c.folio ? `#${c.folio}` : "", name: c.name ?? "", phone: c.phone })), ["folio", "name", "phone"]);
|
|
9450
10096
|
const text = `${sample}
|
|
9451
|
-
${plan.moved} of ${plan.matched} card(s) \xAB${plan.from}\xBB \u2192 \xAB${plan.to}\xBB${plan.skipped.length ? dim(` \xB7 ${plan.skipped.length} already in \xAB${plan.to}\xBB, skipped`) : ""}`;
|
|
10097
|
+
${t(`${plan.moved} of ${plan.matched} card(s)`, `${plan.moved} de ${plan.matched} tarjeta(s)`)} \xAB${plan.from}\xBB \u2192 \xAB${plan.to}\xBB${plan.skipped.length ? dim(t(` \xB7 ${plan.skipped.length} already in \xAB${plan.to}\xBB, skipped`, ` \xB7 ${plan.skipped.length} ya en \xAB${plan.to}\xBB, se omiten`)) : ""}`;
|
|
9452
10098
|
if (v["dry-run"] || !plan.moved) return out(plan, () => `${text}
|
|
9453
|
-
${dim("Dry run: nothing changed.")}`);
|
|
10099
|
+
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
9454
10100
|
await confirm(`${text}
|
|
9455
|
-
Move them with their threads and orders? (they get a new folio there)`, { yes: v.yes, retry });
|
|
10101
|
+
${t("Move them with their threads and orders? (they get a new folio there)", "\xBFMoverlas con sus hilos y pedidos? (all\xE1 reciben folio nuevo)")}`, { yes: v.yes, retry });
|
|
9456
10102
|
const r = await api(path, { method: "POST", body });
|
|
9457
|
-
return out(r, () => `${lavender("\u2713")} Moved ${r.moved} card(s) (${r.messages} message(s), ${r.orders} order(s)) to \xAB${r.to}\xBB
|
|
10103
|
+
return out(r, () => `${lavender("\u2713")} ${t(`Moved ${r.moved} card(s) (${r.messages} message(s), ${r.orders} order(s)) to \xAB${r.to}\xBB.`, `Se movieron ${r.moved} tarjeta(s) (${r.messages} mensaje(s), ${r.orders} pedido(s)) a \xAB${r.to}\xBB.`)}${r.skipped.length ? dim(t(` Skipped ${r.skipped.length}.`, ` Se omitieron ${r.skipped.length}.`)) : ""}`);
|
|
9458
10104
|
}
|
|
9459
10105
|
default:
|
|
9460
|
-
throw usage(`Unknown boards subcommand: ${sub}.`, "Try: ghosty boards ls | create | assign | move-cards | rm");
|
|
10106
|
+
throw usage(t(`Unknown boards subcommand: ${sub}.`, `Subcomando de boards desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty boards ls | create | assign | move-cards | rm");
|
|
9461
10107
|
}
|
|
9462
10108
|
}
|
|
9463
10109
|
|
|
10110
|
+
// src/help.es.ts
|
|
10111
|
+
var HELPS_ES = {
|
|
10112
|
+
agents: {
|
|
10113
|
+
summary: "Crea, revisa, cambia, diagnostica y borra agentes.",
|
|
10114
|
+
replace: [
|
|
10115
|
+
["# the agent as a file; see ghosty apply --help", "# el agente como archivo; ver ghosty apply --help"],
|
|
10116
|
+
["# what it really has: effective toolsets, connectors connected + granted", "# lo que de verdad tiene: toolsets efectivos, conectores conectados + concedidos"]
|
|
10117
|
+
],
|
|
10118
|
+
flags: [
|
|
10119
|
+
["--fields a,b", "get: regresa s\xF3lo estos campos"],
|
|
10120
|
+
["--prompt-out FILE", "get: escribe el prompt en FILE (ed\xEDtalo y vuelve con set --prompt-file)"],
|
|
10121
|
+
["--like AGENT", "create: copia la configuraci\xF3n de ese agente (skills y sus switches, toolsets, conectores, voz, bases de datos, documentos, tablero, compartidos) \u2014 no su prompt, env ni canales. docs grant: los mismos documentos"],
|
|
10122
|
+
["--copy-dbs", "create --like: duplica sus bases de datos con sus filas en vez de conceder las mismas"],
|
|
10123
|
+
["--board B", "create --like: same (el tablero del origen, default) \xB7 main \xB7 new (un tablero con el nombre del agente y las columnas del origen: un tablero por n\xFAmero) \xB7 o un tablero existente"],
|
|
10124
|
+
["--no-notify", "create --like: lo comparte con las mismas personas SIN mandarles correo"],
|
|
10125
|
+
["--workspace SLUG", "create: nace en ese espacio (del que eres due\xF1o o admin), a nombre de su due\xF1o y activo en su Teams"],
|
|
10126
|
+
["--channels k=on|off,\u2026", "create/set: prende/apaga canales: teams, web, whatsapp, messenger, wa (WhatsApp personal)"],
|
|
10127
|
+
["--toolsets LIST", "set: grupos de tools de gs que recibe (crm, sales\u2026): una lista reemplaza; +x / -x agrega o quita"],
|
|
10128
|
+
["--prompt-base ID", "set: el prompt de este agente = el prompt de ese agente + --prompt-overlay; se regenera cuando cambia la base. none = desliga (conserva el prompt de hoy)"],
|
|
10129
|
+
["--prompt-overlay FILE", 'set: JSON {"edits":[{"what","find","replace","regex"?,"all"?}]}, aplicados en orden; cada uno debe encontrar su lugar o no cambia nada'],
|
|
10130
|
+
["--voice E:ID", "set: voz de las notas de voz: elevenlabs:<voiceId> o kokoro:em_santa|em_alex|ef_dora"],
|
|
10131
|
+
["--voice-replies M", "set: auto = contesta con nota de voz cuando el cliente manda o pide audio; off = siempre texto"],
|
|
10132
|
+
["--auto-stage on|off", "set: mueve solo las tarjetas del tablero tras cada turno del cliente (una llamada extra al modelo por turno)"],
|
|
10133
|
+
["--verbose", "doctor: lista tambi\xE9n las revisiones que pasaron"],
|
|
10134
|
+
["--write", "docs grant: el agente puede reemplazar el documento (default: s\xF3lo lectura)"],
|
|
10135
|
+
["--no-deliver", "docs grant/add: puede leerlo pero nunca mand\xE1rselo a quien escribe"],
|
|
10136
|
+
["--url URL | --file PATH", "docs add: de d\xF3nde sale el documento (URL p\xFAblica, hasta 25 MB, o un archivo local)"],
|
|
10137
|
+
["--grant read|write|no", "docs add: acceso de este agente (default read; no = s\xF3lo subirlo a la cuenta)"],
|
|
10138
|
+
["--role R", "share: editor (lo configura y lo usa) o admin (adem\xE1s lo comparte); repetirlo sobre un correo ya compartido cambia el rol"],
|
|
10139
|
+
["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
|
|
10140
|
+
["--slug NAME", "demo: nombre de la liga; se le agrega un c\xF3digo de 6 caracteres (/p/NAME-CODE)"],
|
|
10141
|
+
["--expires DATE|no", 'demo: caducidad fija en la hora de tu m\xE1quina ("2026-10-15T20:00"); no = nunca'],
|
|
10142
|
+
["--turns N", "demo: mensajes por visitante (default 150)"],
|
|
10143
|
+
["--per-ip N", "demo: mensajes por IP al d\xEDa (default 30)"],
|
|
10144
|
+
["--welcome TEXT", "demo: saludo del chat (vac\xEDo = el de siempre)"],
|
|
10145
|
+
["--starter TEXT", 'demo: primera sugerencia destacada ("" la quita)'],
|
|
10146
|
+
['--chips "a|b|c"', "demo: sugerencias, separadas por |"],
|
|
10147
|
+
["--sponsor on|off", "demo: las copias que la gente se queda corren con TU llave del motor"],
|
|
10148
|
+
["--rotate", "demo: c\xF3digo nuevo; la liga vieja deja de servir"],
|
|
10149
|
+
["--check PATH", "box: este archivo debe existir en la m\xE1quina (repetible), p. ej. un archivo que trae la imagen nueva"],
|
|
10150
|
+
["--contains PATH=TEXT", "box: ese archivo debe contener TEXT (un cambio que no agrega archivos)"],
|
|
10151
|
+
["--recycle", "box: reemplaza sus m\xE1quinas (no a media respuesta); la nueva arranca en el siguiente turno. ACP: usa restart"]
|
|
10152
|
+
],
|
|
10153
|
+
notes: [
|
|
10154
|
+
"doctor sale con 1 cuando encuentra un problema, as\xED puede frenar un script o un CI.",
|
|
10155
|
+
"Un prompt derivado no se edita directo (409): cambia la base o el overlay, o desenl\xE1zalo con --prompt-base none.",
|
|
10156
|
+
"demo sin flags s\xF3lo imprime su estado: liga, caducidad, topes y n\xFAmeros."
|
|
10157
|
+
]
|
|
10158
|
+
},
|
|
10159
|
+
skills: {
|
|
10160
|
+
summary: "Skills de un agente (formato agentskills.io: una carpeta con SKILL.md).",
|
|
10161
|
+
replace: [
|
|
10162
|
+
["# from the community catalog", "# del cat\xE1logo de la comunidad"],
|
|
10163
|
+
["# keeps a local copy first", "# antes guarda una copia local"]
|
|
10164
|
+
],
|
|
10165
|
+
flags: [
|
|
10166
|
+
["--out DIR", "get: carpeta donde escribir; sin ella, get s\xF3lo lista los archivos"],
|
|
10167
|
+
["--only a,b", "house: deja prendidas S\xD3LO estas skills de Ghosty (las dem\xE1s apagadas)"],
|
|
10168
|
+
["--enable / --disable a,b", "house: prende / apaga estas skills de Ghosty"],
|
|
10169
|
+
["--file FILE", "add: sube un SKILL.md"],
|
|
10170
|
+
["--dir FOLDER", "add: sube la carpeta entera de una skill (SKILL.md + archivos); restaura un respaldo"],
|
|
10171
|
+
["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
10172
|
+
],
|
|
10173
|
+
notes: [
|
|
10174
|
+
"rm guarda una copia en ~/.config/ghosty/trash/ e imprime el comando que la restaura.",
|
|
10175
|
+
"templates lee una m\xE1quina viva (corre un turno antes). La plantilla propia de un agente es <skill>/pdf-templates/<name>.html en cualquiera de sus skills; le gana a una de casa con el mismo nombre."
|
|
10176
|
+
]
|
|
10177
|
+
},
|
|
10178
|
+
dbs: {
|
|
10179
|
+
summary: "Bases de datos que un agente puede usar, y qu\xE9 puede escribir desde canales de clientes.",
|
|
10180
|
+
replace: [["# reads $EASYBITS_API_KEY", "# lee $EASYBITS_API_KEY"]],
|
|
10181
|
+
flags: [
|
|
10182
|
+
["--arg V", "query: valor para un marcador ? (repetible)"],
|
|
10183
|
+
["--write", "query: permite INSERT/UPDATE/DELETE/DDL (default: s\xF3lo lectura)"],
|
|
10184
|
+
["--external-write t1,t2", 'grant: tablas que puede escribir desde WhatsApp/Messenger ("" = ninguna)'],
|
|
10185
|
+
["--external-rows t:col+col", 'grant: desde WhatsApp/Messenger cada cliente ve s\xF3lo SUS filas de t, por columna(s) de tel\xE9fono ("" = sin filtro)'],
|
|
10186
|
+
["--from easybits:ID", "import: base de origen en EasyBits (llave de $EASYBITS_API_KEY)"],
|
|
10187
|
+
["--tables a,b", "import: s\xF3lo estas tablas (default: todas)"],
|
|
10188
|
+
["--append", "import: agrega filas en vez de reemplazarlas (reemplazar es el default idempotente)"],
|
|
10189
|
+
["--rehost-images t.col,\u2026", "import: copia las ligas de imagen de esas columnas al almacenamiento p\xFAblico de Ghosty (ligas permanentes en img.ghosty.studio)"],
|
|
10190
|
+
["--table T --column C", "rehost: la columna de ligas de imagen (p. ej. catalogo imagen_url) que se pasa a ligas permanentes en img.ghosty.studio; idempotente, una liga que falla se queda como estaba"],
|
|
10191
|
+
["-n, --dry-run", "import: imprime el plan (+ crear, ~ reemplazar N \u2192 M filas), no cambia nada"]
|
|
10192
|
+
],
|
|
10193
|
+
notes: ["<db> es el id de la base o su nombre. Desde canales del due\xF1o el agente lee y escribe todo lo concedido."]
|
|
10194
|
+
},
|
|
10195
|
+
turns: {
|
|
10196
|
+
summary: "Los turnos recientes de un agente: qu\xE9 contest\xF3, qu\xE9 fall\xF3 y por qu\xE9, y en qu\xE9 se fue el tiempo de un turno.",
|
|
10197
|
+
replace: [["# last turn: each tool, its seconds, output tokens", "# \xFAltimo turno: cada tool, sus segundos, tokens de salida"]],
|
|
10198
|
+
flags: [
|
|
10199
|
+
["--errors", "s\xF3lo turnos fallidos (igual que --status error)"],
|
|
10200
|
+
["--status S", "running | done | stopped | error"],
|
|
10201
|
+
["--since T", "30m, 1h, 2d o una fecha ISO"],
|
|
10202
|
+
["--limit N", "m\xE1ximo de filas (default 20, m\xE1x. 500)"],
|
|
10203
|
+
["--full", "show: el mensaje que entr\xF3 y la respuesta completos (default: primeros 600 caracteres)"]
|
|
10204
|
+
],
|
|
10205
|
+
notes: ["show imprime lo que entr\xF3 y lo que sali\xF3 en cualquier motor o canal (grupos de WhatsApp tambi\xE9n); la l\xEDnea de tiempo lee el transcript del worker, as\xED que s\xF3lo motores del pool (no ACP/goose)."]
|
|
10206
|
+
},
|
|
10207
|
+
files: { summary: "Archivos en la m\xE1quina del agente." },
|
|
10208
|
+
mcp: { summary: "Servidores MCP de un agente. set REEMPLAZA la lista entera." },
|
|
10209
|
+
try: {
|
|
10210
|
+
summary: "Turnos de prueba: sencillos, por el camino real de WhatsApp (no se manda nada), o repetici\xF3n de conversaciones reales.",
|
|
10211
|
+
replace: [['"message"', '"mensaje"']],
|
|
10212
|
+
flags: [
|
|
10213
|
+
["--waba", "el camino real de WhatsApp Business: contexto heredado, regla de precios, tablero, voz. Muestra lo que MANDAR\xCDA"],
|
|
10214
|
+
["--session PHONE", "con --waba: un tel\xE9fono de cliente inventado (10-15 d\xEDgitos); cada tel\xE9fono es su propia conversaci\xF3n"],
|
|
10215
|
+
["--media FILE", "con --waba: el cliente manda esta foto/audio/PDF (uno por turno)"],
|
|
10216
|
+
["--as-formmy", "con --media: llega como un reenv\xEDo real de Formmy (proxy con credencial, copia a gs, transcripci\xF3n) y dice si la copia funcion\xF3"],
|
|
10217
|
+
["--history FILE", "con --waba: lo que se dijo antes, como [{role: user|agent, text, at?}]; cuenta en el primer turno (--reset)"],
|
|
10218
|
+
["--reset", "olvida esa sesi\xF3n de prueba (con --waba tambi\xE9n su hilo de prueba)"],
|
|
10219
|
+
["--replay FILE", "corre las conversaciones de FILE ({sample: [{id, fakePhone, messages}]}) contra el agente"],
|
|
10220
|
+
["--out DIR", "replay: d\xF3nde se escribe replay.json, tras cada paso"],
|
|
10221
|
+
["--max N", "replay: turnos del cliente por conversaci\xF3n (default 15)"],
|
|
10222
|
+
["--concurrency N", "replay: conversaciones al mismo tiempo (default 8, m\xE1x. 30)"],
|
|
10223
|
+
["--resume", "replay: contin\xFAa desde DIR/replay.json, salt\xE1ndose los pasos terminados"]
|
|
10224
|
+
],
|
|
10225
|
+
notes: [
|
|
10226
|
+
"Las repeticiones sobreviven a los deploys de Ghosty: un 503 se reintenta tras su Retry-After; el reporte vive en tu disco.",
|
|
10227
|
+
"--whatsapp ahora es alias de --waba.",
|
|
10228
|
+
"--waba lista todo lo que mandar\xEDa, tools incluidas: \u{1F4CD} ubicaci\xF3n, \u{1F4C4} archivos, \u{1F3A4} voz, \u21A9\uFE0F citas, \u{1F518} botones, \u{1F464} contactos, reacciones."
|
|
10229
|
+
]
|
|
10230
|
+
},
|
|
10231
|
+
chat: {
|
|
10232
|
+
summary: "Conversa en streaming; sin mensaje es interactivo.",
|
|
10233
|
+
replace: [['["message"]', '["mensaje"]']]
|
|
10234
|
+
},
|
|
10235
|
+
schedule: {
|
|
10236
|
+
summary: "Turnos que corren despu\xE9s, con el reloj propio del agente.",
|
|
10237
|
+
replace: [['"what to do"', '"qu\xE9 hacer"']]
|
|
10238
|
+
},
|
|
10239
|
+
me: { summary: "Tu cuenta: archivos y uso." },
|
|
10240
|
+
credentials: {
|
|
10241
|
+
summary: "Llaves de proveedores en la b\xF3veda de tu cuenta (cifradas; s\xF3lo regresan los \xFAltimos 4 caracteres).",
|
|
10242
|
+
replace: [["# hidden prompt", "# pregunta oculta"]],
|
|
10243
|
+
flags: [
|
|
10244
|
+
["--from-env VAR", "set: lee la llave de esta variable de entorno (pasa su NOMBRE)"],
|
|
10245
|
+
["--yes", "rm: se salta la confirmaci\xF3n"]
|
|
10246
|
+
],
|
|
10247
|
+
notes: ["La llave nunca se acepta como argumento: se quedar\xEDa en el historial de tu shell y en `ps`."]
|
|
10248
|
+
},
|
|
10249
|
+
channels: {
|
|
10250
|
+
summary: "Qu\xE9 n\xFAmeros de WhatsApp Business contesta un agente, y qu\xE9 grupos de su WhatsApp personal (y como qui\xE9n). Los n\xFAmeros se emparejan en la web.",
|
|
10251
|
+
replace: [['"Name"', '"Nombre"']],
|
|
10252
|
+
flags: [
|
|
10253
|
+
["-n, --dry-run", "imprime el plan (+ empieza a contestar, - deja de contestar) y no cambia nada"],
|
|
10254
|
+
["--yes", "enable / groups enable: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
|
|
10255
|
+
["--role R", "groups: equipo (el equipo: escribe en bases de datos, tools de admin) o cliente (como cualquier cliente)"],
|
|
10256
|
+
["--cached", "groups ls: no le pide a WhatsApp la lista en vivo (m\xE1s r\xE1pido; puede faltar alg\xFAn grupo nuevo)"],
|
|
10257
|
+
["--integration ID", "link/unlink/history: el id de la Integration del n\xFAmero de WhatsApp Business en Formmy"],
|
|
10258
|
+
["--days N", "history: conversaciones activas en los \xFAltimos N d\xEDas (1-30, default 7)"],
|
|
10259
|
+
["--answer on|off", "link (obligatorio, sin default): on = este agente contesta a clientes reales; off = s\xF3lo registra"],
|
|
10260
|
+
["--forward", "link --answer off: igual hace que Formmy reenv\xEDe, para que gs vea las conversaciones"],
|
|
10261
|
+
["--from-file F", "import: {authCreds, authKeys, phone, groups} exportado de EasyBits"],
|
|
10262
|
+
["--from-dir DIR", "import: una carpeta de auth multi-archivo de Baileys (creds.json + un archivo por llave; p. ej. store/auth de nanoclaw)"],
|
|
10263
|
+
["--own-number yes|no", "import (obligatorio): el n\xFAmero es del agente, o de una persona (s\xF3lo su chat consigo misma es del due\xF1o)"],
|
|
10264
|
+
["--replace", "import: reemplaza una sesi\xF3n ya guardada en gs"],
|
|
10265
|
+
["--no-connect", "import: la guarda pero todav\xEDa no conecta"]
|
|
10266
|
+
],
|
|
10267
|
+
notes: [
|
|
10268
|
+
"<number> es su id, su etiqueta o sus \xFAltimos d\xEDgitos. Un n\xFAmero, un agente: prenderlo se lo quita al otro agente.",
|
|
10269
|
+
"Apagarlo sigue registrando los mensajes; nadie los contesta.",
|
|
10270
|
+
"Los grupos son del WhatsApp PERSONAL emparejado al agente (no de WhatsApp Business). <group> es su nombre o su jid.",
|
|
10271
|
+
"link mueve un n\xFAmero que ya vive en Formmy (EasyBits, un droplet) sin rehacer el alta de Meta; unlink lo regresa. S\xF3lo el due\xF1o, un admin del agente o el staff de Ghosty.",
|
|
10272
|
+
"history trae al tablero las tarjetas, mensajes y pedidos recientes del n\xFAmero desde Formmy (enl\xE1zalo antes). Repetirlo nunca duplica: c\xF3rrelo antes del corte y otra vez justo en el corte.",
|
|
10273
|
+
"import: apaga primero el socket de EasyBits SIN logout (dos sockets se tumban entre s\xED; un logout invalida las llaves). Borra el archivo despu\xE9s."
|
|
10274
|
+
]
|
|
10275
|
+
},
|
|
10276
|
+
login: {
|
|
10277
|
+
summary: "Inicia sesi\xF3n con tu navegador (OAuth2, aqu\xED no hay contrase\xF1a). Un agente puede correrlo por ti: imprime la URL y espera.",
|
|
10278
|
+
replace: [["# no login: an agent token", "# sin login: un token de agente"]],
|
|
10279
|
+
flags: [
|
|
10280
|
+
["--device", "inicia sesi\xF3n desde otro dispositivo con liga + c\xF3digo (autom\xE1tico por SSH o en CI)"],
|
|
10281
|
+
["--profile NAME", "una sesi\xF3n guardada aparte, p. ej. una por cuenta"]
|
|
10282
|
+
]
|
|
10283
|
+
},
|
|
10284
|
+
logout: { summary: "Cierra sesi\xF3n y revoca en el servidor la sesi\xF3n de este dispositivo." },
|
|
10285
|
+
whoami: { summary: "Con qui\xE9n iniciaste sesi\xF3n (con --json, tambi\xE9n el plan de tu cuenta)." },
|
|
10286
|
+
docs: { summary: "Abre la documentaci\xF3n en tu navegador (con --json, s\xF3lo imprime la URL)." },
|
|
10287
|
+
apply: {
|
|
10288
|
+
summary: "Hace que un agente coincida con un archivo (de ghosty agents export): prompt, modelo, canales, tools, conectores, voz, tablero, skills, MCP, bases de datos, documentos, compartidos. Primero muestra el plan (+ agregar, ~ cambiar, - quitar, ! nota) y pregunta.",
|
|
10289
|
+
replace: [
|
|
10290
|
+
["# or --out DIR: agent.yaml + skills/<slug>/\u2026", "# o --out DIR: agent.yaml + skills/<slug>/\u2026"],
|
|
10291
|
+
["# a new agent from the file", "# un agente nuevo a partir del archivo"]
|
|
10292
|
+
],
|
|
10293
|
+
flags: [
|
|
10294
|
+
["--agent X", "aplica a este agente en vez del id del archivo (p. ej. el mismo archivo sobre una copia)"],
|
|
10295
|
+
["--create", "crea un agente nuevo con el archivo (su espacio, motor, canales) y aplica lo dem\xE1s"],
|
|
10296
|
+
["--prune", "tambi\xE9n QUITA lo que el archivo ya no lista (bases de datos, documentos, compartidos, skills propias, variables de entorno); sin esto s\xF3lo se se\xF1alan con !"],
|
|
10297
|
+
["-n, --dry-run", "imprime el plan, no cambia nada"],
|
|
10298
|
+
["--no-notify", "compartidos nuevos sin mandar correo a la gente"]
|
|
10299
|
+
],
|
|
10300
|
+
notes: [
|
|
10301
|
+
"Lo que el archivo no menciona no se toca; los campos que trae quedan exactamente como dice.",
|
|
10302
|
+
"Los secretos nunca salen: se exportan como ${NAME}; define NAME en tu entorno para cambiarlo, si no, se queda el valor actual.",
|
|
10303
|
+
"Antes de aplicar se guarda la versi\xF3n actual del agente (Versiones en la web)."
|
|
10304
|
+
]
|
|
10305
|
+
},
|
|
10306
|
+
completion: {
|
|
10307
|
+
summary: "Autocompletado con TAB en tu shell: comandos, subcomandos y opciones (de esta ayuda), nombres de agentes y espacios (de tu cuenta, con cach\xE9 de 5 min).",
|
|
10308
|
+
replace: [
|
|
10309
|
+
["# add to ~/.zshrc, after compinit", "# agr\xE9galo a ~/.zshrc, despu\xE9s de compinit"],
|
|
10310
|
+
["# add to ~/.bashrc", "# agr\xE9galo a ~/.bashrc"]
|
|
10311
|
+
],
|
|
10312
|
+
notes: ["Los nombres salen de ghosty agents ls; un agente nuevo aparece en m\xE1ximo 5 minutos (o borra ~/.cache/ghosty/complete.json)."]
|
|
10313
|
+
},
|
|
10314
|
+
use: {
|
|
10315
|
+
summary: "Un espacio de trabajo por default para esta sesi\xF3n (perfil): boards lo toma sin escribirlo, agents create nace ah\xED y agents ls lista s\xF3lo sus agentes.",
|
|
10316
|
+
replace: [["# which one", "# cu\xE1l es"]],
|
|
10317
|
+
flags: [["--clear", "sin espacio por default"]],
|
|
10318
|
+
notes: [
|
|
10319
|
+
"En cualquier lugar puedes nombrar a un agente en vez de usar su id; si hay varios con el mismo nombre, gana el de este espacio.",
|
|
10320
|
+
"agents create --workspace none crea un agente personal aunque haya espacio por default."
|
|
10321
|
+
]
|
|
10322
|
+
},
|
|
10323
|
+
boards: {
|
|
10324
|
+
summary: "Los tableros de ventas de un ESPACIO: listarlos, crear uno (copiando las columnas de otro), elegir a d\xF3nde manda sus tarjetas un agente y mover tarjetas entre tableros.",
|
|
10325
|
+
replace: [['"Name"', '"Nombre"']],
|
|
10326
|
+
flags: [
|
|
10327
|
+
["--columns-from B", "create: copia las columnas de ese tablero (main, un nombre o un id) en vez de las de siempre"],
|
|
10328
|
+
["--from / --to B", "move-cards: tableros por nombre, id o main"],
|
|
10329
|
+
["--integration ID | --phone N | --all", "move-cards: qu\xE9 tarjetas (el filtro es obligatorio)"],
|
|
10330
|
+
["-n, --dry-run", "move-cards: cuenta y muestra una muestra, no cambia nada"],
|
|
10331
|
+
["--yes", "move-cards: se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
10332
|
+
],
|
|
10333
|
+
notes: [
|
|
10334
|
+
"assign cambia a d\xF3nde van las tarjetas NUEVAS; move-cards trae las que ya existen (hilo, pedidos, actividad) con folio nuevo. Una tarjeta cuya conversaci\xF3n ya est\xE1 en el tablero destino se salta.",
|
|
10335
|
+
"Leer requiere ser miembro; cambiar requiere ser due\xF1o o staff del espacio.",
|
|
10336
|
+
"rm s\xF3lo borra un tablero vac\xEDo que no sea el principal."
|
|
10337
|
+
]
|
|
10338
|
+
},
|
|
10339
|
+
cards: {
|
|
10340
|
+
summary: "Las tarjetas del tablero de ventas de un agente (su tablero es del ESPACIO: debes ser miembro, un agente compartido no basta). Los tableros del espacio en s\xED: ghosty boards.",
|
|
10341
|
+
flags: [
|
|
10342
|
+
["--q TEXT", "ls: tarjetas cuyo nombre, tel\xE9fono o \xFAltimo mensaje contiene TEXT"],
|
|
10343
|
+
["--limit N", "ls: tarjetas (default 20) \xB7 show: \xFAltimos N mensajes (default 20, m\xE1x. 500)"],
|
|
10344
|
+
["--integration ID", "tarjetas de este canal/integraci\xF3n (p. ej. prueba)"],
|
|
10345
|
+
["--column NAME", "tarjetas en esta columna"],
|
|
10346
|
+
["--before T", "tarjetas sin actividad desde T (30d, 12h o una fecha ISO)"],
|
|
10347
|
+
["-n, --dry-run", "cuenta y muestra una muestra, no cambia nada"],
|
|
10348
|
+
["--yes", "se salta la confirmaci\xF3n (obligatorio sin terminal)"]
|
|
10349
|
+
],
|
|
10350
|
+
notes: [
|
|
10351
|
+
"show imprime cada mensaje con qui\xE9n lo escribi\xF3 (cliente, agente, operador), su adjunto y si gs guarda una copia.",
|
|
10352
|
+
"Archivar es reversible: cuando el cliente vuelve a escribir, la tarjeta regresa. Se necesita al menos un filtro."
|
|
10353
|
+
]
|
|
10354
|
+
},
|
|
10355
|
+
usage: { summary: "Tu plan y cu\xE1nto llevas usado esta semana y este mes." }
|
|
10356
|
+
};
|
|
10357
|
+
var HELP_ALIASES = { creds: "credentials", board: "cards", conversations: "chat" };
|
|
10358
|
+
|
|
9464
10359
|
// src/help.ts
|
|
9465
10360
|
var DOCS = "https://www.ghosty.studio/docs/cli";
|
|
9466
10361
|
var REF = `${DOCS}/comandos`;
|
|
@@ -9786,6 +10681,17 @@ HELPS.apply = {
|
|
|
9786
10681
|
],
|
|
9787
10682
|
learn: `${REF}#el-agente-como-archivo`
|
|
9788
10683
|
};
|
|
10684
|
+
HELPS.completion = {
|
|
10685
|
+
summary: "Tab completion for your shell: commands, subcommands and flags (from this help), agent and workspace names (from your account, cached 5 min).",
|
|
10686
|
+
usage: ["ghosty completion zsh|bash|fish"],
|
|
10687
|
+
examples: [
|
|
10688
|
+
"source <(ghosty completion zsh) # add to ~/.zshrc, after compinit",
|
|
10689
|
+
'eval "$(ghosty completion bash)" # add to ~/.bashrc',
|
|
10690
|
+
"ghosty completion fish > ~/.config/fish/completions/ghosty.fish"
|
|
10691
|
+
],
|
|
10692
|
+
notes: ["Names come from ghosty agents ls; a new agent shows up within 5 minutes (or delete ~/.cache/ghosty/complete.json)."],
|
|
10693
|
+
learn: `${REF}#autocompletado`
|
|
10694
|
+
};
|
|
9789
10695
|
HELPS.use = {
|
|
9790
10696
|
summary: "A default workspace for this session (profile): boards take it without typing it, agents create is born there and agents ls lists only its agents.",
|
|
9791
10697
|
usage: ["ghosty use # which one", "ghosty use <workspace>", "ghosty use --clear"],
|
|
@@ -9838,19 +10744,23 @@ HELPS.board = HELPS.cards;
|
|
|
9838
10744
|
HELPS.conversations = HELPS.chat;
|
|
9839
10745
|
HELPS.usage = { ...HELPS.me, summary: "Your plan and how much you've used this week and month.", usage: ["ghosty usage", "ghosty me usage"], examples: ["ghosty usage", "ghosty usage --json"], learn: `${REF}#uso` };
|
|
9840
10746
|
function helpText(cmd) {
|
|
9841
|
-
const
|
|
9842
|
-
if (!
|
|
9843
|
-
const
|
|
10747
|
+
const base = HELPS[cmd];
|
|
10748
|
+
if (!base) return void 0;
|
|
10749
|
+
const es = lang() === "es";
|
|
10750
|
+
const tr = es ? HELPS_ES[HELP_ALIASES[cmd] ?? cmd] : void 0;
|
|
10751
|
+
const fix = (l) => (tr?.replace ?? []).reduce((acc, [a, b]) => acc.split(a).join(b), l);
|
|
10752
|
+
const h = { ...base, ...tr ? { summary: tr.summary ?? base.summary, flags: tr.flags ?? base.flags, notes: tr.notes ?? base.notes } : {} };
|
|
10753
|
+
const section = (t2, lines) => [bold(t2), ...lines.map((l) => ` ${l}`), ""];
|
|
9844
10754
|
const w = Math.max(0, ...(h.flags ?? []).map(([f]) => f.length)) + 2;
|
|
9845
10755
|
return [
|
|
9846
10756
|
h.summary,
|
|
9847
10757
|
"",
|
|
9848
|
-
...section("USAGE", h.usage),
|
|
9849
|
-
...h.flags?.length ? section("FLAGS", h.flags.map(([f, d]) => `${f.padEnd(w)}${d}`)) : [],
|
|
9850
|
-
...section("EXAMPLES", h.examples.map((e) => `$ ${e}`)),
|
|
9851
|
-
...h.notes?.length ? section("NOTES", h.notes) : [],
|
|
9852
|
-
bold("LEARN MORE"),
|
|
9853
|
-
` Add --json for machine output. Exit codes: 0 ok \xB7 1 API error \xB7 2 usage \xB7 3 not signed in.`,
|
|
10758
|
+
...section(es ? "USO" : "USAGE", h.usage.map(fix)),
|
|
10759
|
+
...h.flags?.length ? section(es ? "OPCIONES" : "FLAGS", h.flags.map(([f, d]) => `${f.padEnd(w)}${d}`)) : [],
|
|
10760
|
+
...section(es ? "EJEMPLOS" : "EXAMPLES", h.examples.map((e) => `$ ${fix(e)}`)),
|
|
10761
|
+
...h.notes?.length ? section(es ? "NOTAS" : "NOTES", h.notes) : [],
|
|
10762
|
+
bold(es ? "M\xC1S INFORMACI\xD3N" : "LEARN MORE"),
|
|
10763
|
+
es ? ` Agrega --json para salida para m\xE1quinas. C\xF3digos de salida: 0 bien \xB7 1 error de la API \xB7 2 uso \xB7 3 sin sesi\xF3n.` : ` Add --json for machine output. Exit codes: 0 ok \xB7 1 API error \xB7 2 usage \xB7 3 not signed in.`,
|
|
9854
10764
|
` ${dim(h.learn ?? DOCS)}`
|
|
9855
10765
|
].join("\n");
|
|
9856
10766
|
}
|
|
@@ -9887,9 +10797,9 @@ async function latestVersion() {
|
|
|
9887
10797
|
}
|
|
9888
10798
|
return cache2.latest ?? null;
|
|
9889
10799
|
}
|
|
9890
|
-
async function updateNotice(
|
|
10800
|
+
async function updateNotice(current2) {
|
|
9891
10801
|
const latest = await latestVersion();
|
|
9892
|
-
return latest && newer(latest,
|
|
10802
|
+
return latest && newer(latest, current2) ? t(`Update available: ${current2} \u2192 ${latest}. Run: ${UPGRADE}`, `Hay versi\xF3n nueva: ${current2} \u2192 ${latest}. Corre: ${UPGRADE}`) : null;
|
|
9893
10803
|
}
|
|
9894
10804
|
|
|
9895
10805
|
// src/resolve.ts
|
|
@@ -9904,11 +10814,14 @@ function pickAgent(list, ref, workspace) {
|
|
|
9904
10814
|
if (hits.length === 1) return hits[0].id;
|
|
9905
10815
|
if (!hits.length) {
|
|
9906
10816
|
const near = list.filter((a) => a.nombre.toLowerCase().includes(want)).slice(0, 5);
|
|
9907
|
-
|
|
10817
|
+
const names2 = near.map((a) => `${a.nombre} (${a.id})`).join(", ");
|
|
10818
|
+
throw new CliError(t(`No agent named "${ref}".`, `No hay ning\xFAn agente llamado "${ref}".`), EXIT.USAGE, near.length ? t(`Did you mean: ${names2}?`, `\xBFQuisiste decir: ${names2}?`) : t("List them: ghosty agents ls", "L\xEDstalos: ghosty agents ls"));
|
|
9908
10819
|
}
|
|
9909
10820
|
const where = (a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "?";
|
|
9910
|
-
|
|
9911
|
-
|
|
10821
|
+
const ids = hits.map((a) => ` ${a.id} ${a.motor ?? ""} ${where(a)}`).join("\n");
|
|
10822
|
+
throw new CliError(t(`${hits.length} agents are named "${ref}".`, `${hits.length} agentes se llaman "${ref}".`), EXIT.USAGE, t(`Use the id:
|
|
10823
|
+
${ids}`, `Usa el id:
|
|
10824
|
+
${ids}`));
|
|
9912
10825
|
}
|
|
9913
10826
|
var cache = null;
|
|
9914
10827
|
function listAgents() {
|
|
@@ -9919,57 +10832,185 @@ async function resolveAgent(ref, workspace) {
|
|
|
9919
10832
|
if (!ref || looksLikeId(ref)) return ref;
|
|
9920
10833
|
return pickAgent(await listAgents(), ref, workspace);
|
|
9921
10834
|
}
|
|
10835
|
+
function agentArgIndex(pos) {
|
|
10836
|
+
const [cmd, sub] = pos;
|
|
10837
|
+
switch (cmd) {
|
|
10838
|
+
case "agents":
|
|
10839
|
+
if (sub === "docs" || sub === "documents") return 3;
|
|
10840
|
+
return sub && !["ls", "list", "create", "new"].includes(sub) ? 2 : null;
|
|
10841
|
+
case "files":
|
|
10842
|
+
case "skills":
|
|
10843
|
+
case "mcp":
|
|
10844
|
+
case "dbs":
|
|
10845
|
+
case "db":
|
|
10846
|
+
case "turns":
|
|
10847
|
+
case "board":
|
|
10848
|
+
case "conversations":
|
|
10849
|
+
case "schedule":
|
|
10850
|
+
return 2;
|
|
10851
|
+
case "try":
|
|
10852
|
+
case "chat":
|
|
10853
|
+
return 1;
|
|
10854
|
+
case "channels":
|
|
10855
|
+
return pos[2] === "groups" ? 4 : 3;
|
|
10856
|
+
case "boards":
|
|
10857
|
+
return sub === "assign" ? 2 : null;
|
|
10858
|
+
default:
|
|
10859
|
+
return null;
|
|
10860
|
+
}
|
|
10861
|
+
}
|
|
10862
|
+
|
|
10863
|
+
// src/completion.ts
|
|
10864
|
+
import { mkdir as mkdir5, readFile as readFile10, writeFile as writeFile5 } from "node:fs/promises";
|
|
10865
|
+
import { homedir as homedir4 } from "node:os";
|
|
10866
|
+
import { join as join6 } from "node:path";
|
|
10867
|
+
var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
|
|
10868
|
+
var AGENT_FLAGS = /* @__PURE__ */ new Set(["--like", "--agent", "--prompt-base"]);
|
|
10869
|
+
var WORKSPACE_FLAGS = /* @__PURE__ */ new Set(["--workspace"]);
|
|
10870
|
+
function subcommandsOf(cmd) {
|
|
10871
|
+
const subs = /* @__PURE__ */ new Set();
|
|
10872
|
+
for (const u of HELPS[cmd]?.usage ?? []) {
|
|
10873
|
+
const m = new RegExp(`^ghosty ${cmd} ([a-z][a-z|-]*)`).exec(u);
|
|
10874
|
+
if (m) {
|
|
10875
|
+
for (const s of m[1].split("|")) if (!s.includes("-") || /^[a-z]+-[a-z]+$/.test(s)) subs.add(s);
|
|
10876
|
+
}
|
|
10877
|
+
}
|
|
10878
|
+
return [...subs];
|
|
10879
|
+
}
|
|
10880
|
+
function flagsOf(cmd) {
|
|
10881
|
+
const own = /* @__PURE__ */ new Set();
|
|
10882
|
+
for (const [f] of HELPS[cmd]?.flags ?? []) for (const m of f.matchAll(/--[a-z][a-z-]*/g)) own.add(m[0]);
|
|
10883
|
+
for (const u of HELPS[cmd]?.usage ?? []) for (const m of u.matchAll(/--[a-z][a-z-]*/g)) own.add(m[0]);
|
|
10884
|
+
return [...own, ...GLOBAL_FLAGS];
|
|
10885
|
+
}
|
|
10886
|
+
function completionFor(words, current2, commands) {
|
|
10887
|
+
const prev = words[words.length - 1];
|
|
10888
|
+
if (prev && AGENT_FLAGS.has(prev)) return { kind: "agents" };
|
|
10889
|
+
if (prev && WORKSPACE_FLAGS.has(prev)) return { kind: "workspaces" };
|
|
10890
|
+
if (!words.length) return { kind: "words", words: commands };
|
|
10891
|
+
const [cmd, sub] = words;
|
|
10892
|
+
if (current2.startsWith("-")) return { kind: "words", words: flagsOf(cmd) };
|
|
10893
|
+
const pos = words.filter((w) => !w.startsWith("-"));
|
|
10894
|
+
if (cmd === "use" && pos.length === 1) return { kind: "workspaces" };
|
|
10895
|
+
if (cmd === "boards" && pos.length === 2 && sub !== "assign") return { kind: "workspaces" };
|
|
10896
|
+
if (cmd === "help" && pos.length === 1) return { kind: "words", words: commands };
|
|
10897
|
+
if (pos.length === 1 && cmd !== "try" && cmd !== "chat") {
|
|
10898
|
+
const subs = subcommandsOf(cmd);
|
|
10899
|
+
if (subs.length) return { kind: "words", words: subs };
|
|
10900
|
+
}
|
|
10901
|
+
if (agentArgIndex([...pos, current2]) === pos.length) return { kind: "agents" };
|
|
10902
|
+
return { kind: "words", words: [] };
|
|
10903
|
+
}
|
|
10904
|
+
var cacheFile = () => join6(process.env.XDG_CACHE_HOME || join6(homedir4(), ".cache"), "ghosty", `complete${process.env.GHOSTY_PROFILE ? `.${process.env.GHOSTY_PROFILE}` : ""}.json`);
|
|
10905
|
+
async function names() {
|
|
10906
|
+
try {
|
|
10907
|
+
const c = JSON.parse(await readFile10(cacheFile(), "utf8"));
|
|
10908
|
+
if (Date.now() - c.at < 5 * 6e4) return c;
|
|
10909
|
+
} catch {
|
|
10910
|
+
}
|
|
10911
|
+
const list = await listAgents();
|
|
10912
|
+
const agents = [...new Set(list.map((a) => a.nombre))];
|
|
10913
|
+
const workspaces = [...new Set(list.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
|
|
10914
|
+
await mkdir5(join6(cacheFile(), ".."), { recursive: true }).catch(() => {
|
|
10915
|
+
});
|
|
10916
|
+
await writeFile5(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
|
|
10917
|
+
});
|
|
10918
|
+
return { agents, workspaces };
|
|
10919
|
+
}
|
|
10920
|
+
async function completeCmd(args, commands) {
|
|
10921
|
+
const current2 = args.length ? args[args.length - 1] : "";
|
|
10922
|
+
const words = args.slice(0, -1);
|
|
10923
|
+
const want = completionFor(words, current2, commands);
|
|
10924
|
+
let out2 = [];
|
|
10925
|
+
try {
|
|
10926
|
+
if (want.kind === "words") out2 = want.words;
|
|
10927
|
+
else out2 = (await names())[want.kind];
|
|
10928
|
+
} catch {
|
|
10929
|
+
out2 = [];
|
|
10930
|
+
}
|
|
10931
|
+
process.stdout.write(out2.filter((w) => w.toLowerCase().startsWith(current2.toLowerCase())).join("\n") + "\n");
|
|
10932
|
+
}
|
|
10933
|
+
function completionScript(shell) {
|
|
10934
|
+
if (shell === "bash") {
|
|
10935
|
+
return `# ghosty \u2014 bash completion: eval "$(ghosty completion bash)" en ~/.bashrc
|
|
10936
|
+
_ghosty() {
|
|
10937
|
+
# Las palabras ANTES de cambiar IFS: el bash 3.2 de macOS junta \${arr[@]:1:n} con un IFS propio.
|
|
10938
|
+
local words=("\${COMP_WORDS[@]:1:COMP_CWORD-1}") cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
10939
|
+
local IFS=$'\\n'
|
|
10940
|
+
COMPREPLY=( $(ghosty __complete -- "\${words[@]}" "$cur" 2>/dev/null) )
|
|
10941
|
+
}
|
|
10942
|
+
complete -o default -F _ghosty ghosty
|
|
10943
|
+
`;
|
|
10944
|
+
}
|
|
10945
|
+
if (shell === "zsh") {
|
|
10946
|
+
return `#compdef ghosty
|
|
10947
|
+
# ghosty \u2014 zsh completion: source <(ghosty completion zsh) en ~/.zshrc (despu\xE9s de compinit)
|
|
10948
|
+
_ghosty() {
|
|
10949
|
+
local -a opts
|
|
10950
|
+
opts=("\${(@f)$(ghosty __complete -- "\${(@)words[2,CURRENT-1]}" "\${words[CURRENT]}" 2>/dev/null)}")
|
|
10951
|
+
compadd -a opts
|
|
10952
|
+
}
|
|
10953
|
+
compdef _ghosty ghosty
|
|
10954
|
+
`;
|
|
10955
|
+
}
|
|
10956
|
+
if (shell === "fish") {
|
|
10957
|
+
return `# ghosty \u2014 fish completion: ghosty completion fish > ~/.config/fish/completions/ghosty.fish
|
|
10958
|
+
complete -c ghosty -f -a "(ghosty __complete -- (commandline -opc)[2..-1] (commandline -ct) 2>/dev/null)"
|
|
10959
|
+
`;
|
|
10960
|
+
}
|
|
10961
|
+
return null;
|
|
10962
|
+
}
|
|
9922
10963
|
|
|
9923
10964
|
// src/aliases.ts
|
|
9924
10965
|
function normalizeArgs(pos, v) {
|
|
9925
10966
|
const notes = [];
|
|
9926
10967
|
const [cmd, sub] = pos;
|
|
9927
10968
|
if (cmd === "cards") pos[0] = "board";
|
|
9928
|
-
else if (cmd === "board") notes.push("`ghosty board` is now `ghosty cards` (same subcommands).");
|
|
10969
|
+
else if (cmd === "board") notes.push(t("`ghosty board` is now `ghosty cards` (same subcommands).", "`ghosty board` ahora es `ghosty cards` (mismos subcomandos)."));
|
|
9929
10970
|
if (cmd === "skills") {
|
|
9930
10971
|
if (sub === "enable") pos[1] = "on";
|
|
9931
10972
|
else if (sub === "disable") pos[1] = "off";
|
|
9932
|
-
else if (sub === "on" || sub === "off") notes.push(`\`skills ${sub}\` is now \`skills ${sub === "on" ? "enable" : "disable"}\`.`);
|
|
10973
|
+
else if (sub === "on" || sub === "off") notes.push(t(`\`skills ${sub}\` is now \`skills ${sub === "on" ? "enable" : "disable"}\`.`, `\`skills ${sub}\` ahora es \`skills ${sub === "on" ? "enable" : "disable"}\`.`));
|
|
9933
10974
|
}
|
|
9934
10975
|
if (cmd === "agents" && sub === "demo") {
|
|
9935
10976
|
if (pos[2] === "enable" || pos[2] === "disable") {
|
|
9936
10977
|
v[pos[2] === "enable" ? "on" : "off"] = true;
|
|
9937
10978
|
pos.splice(2, 1);
|
|
9938
|
-
} else if (v.on || v.off) notes.push(`\`agents demo <agent> --${v.on ? "on" : "off"}\` is now \`agents demo ${v.on ? "enable" : "disable"} <agent>\`.`);
|
|
10979
|
+
} else if (v.on || v.off) notes.push(t(`\`agents demo <agent> --${v.on ? "on" : "off"}\` is now \`agents demo ${v.on ? "enable" : "disable"} <agent>\`.`, `\`agents demo <agente> --${v.on ? "on" : "off"}\` ahora es \`agents demo ${v.on ? "enable" : "disable"} <agente>\`.`));
|
|
9939
10980
|
if (v.expires !== void 0) v.vence = v.expires;
|
|
9940
|
-
else if (v.vence !== void 0) notes.push("`--vence` is now `--expires`.");
|
|
10981
|
+
else if (v.vence !== void 0) notes.push(t("`--vence` is now `--expires`.", "`--vence` ahora es `--expires`."));
|
|
9941
10982
|
}
|
|
9942
10983
|
if (cmd === "channels" && pos[2] === "groups") {
|
|
9943
10984
|
if (pos[3] === "enable" || pos[3] === "disable") {
|
|
9944
10985
|
v[pos[3] === "enable" ? "on" : "off"] = true;
|
|
9945
10986
|
pos[3] = "set";
|
|
9946
|
-
} else if (pos[3] === "set" && (v.on || v.off)) notes.push(`\`groups set \u2026 --${v.on ? "on" : "off"}\` is now \`groups ${v.on ? "enable" : "disable"} <agent> <group>\` (set keeps --role).`);
|
|
10987
|
+
} else if (pos[3] === "set" && (v.on || v.off)) notes.push(t(`\`groups set \u2026 --${v.on ? "on" : "off"}\` is now \`groups ${v.on ? "enable" : "disable"} <agent> <group>\` (set keeps --role).`, `\`groups set \u2026 --${v.on ? "on" : "off"}\` ahora es \`groups ${v.on ? "enable" : "disable"} <agente> <grupo>\` (set se queda para --role).`));
|
|
9947
10988
|
}
|
|
9948
10989
|
return notes;
|
|
9949
10990
|
}
|
|
9950
10991
|
|
|
9951
10992
|
// src/commands/spec.ts
|
|
9952
10993
|
var import_yaml = __toESM(require_dist(), 1);
|
|
9953
|
-
import { mkdir as
|
|
9954
|
-
import { join as
|
|
10994
|
+
import { mkdir as mkdir6, readdir as readdir3, readFile as readFile11, stat, writeFile as writeFile6 } from "node:fs/promises";
|
|
10995
|
+
import { join as join7, resolve as resolve2 } from "node:path";
|
|
9955
10996
|
var enc9 = encodeURIComponent;
|
|
9956
10997
|
async function agentsExport(id, v) {
|
|
9957
|
-
if (!id) throw usage("Missing agent.", "Example: ghosty agents export pia-0 > pia.yaml \xB7 ghosty agents export pia-0 --out ./pia");
|
|
10998
|
+
if (!id) throw usage(t("Missing agent.", "Falta el agente."), t("Example", "Ejemplo") + ": ghosty agents export pia-0 > pia.yaml \xB7 ghosty agents export pia-0 --out ./pia");
|
|
9958
10999
|
const yaml = await (await request(`/api/v2/agents/${enc9(id)}/export`)).text();
|
|
9959
11000
|
if (!v.out) {
|
|
9960
11001
|
if (globals.json) return out((0, import_yaml.parse)(yaml));
|
|
9961
11002
|
return void process.stdout.write(yaml);
|
|
9962
11003
|
}
|
|
9963
11004
|
const dir = resolve2(v.out);
|
|
9964
|
-
await
|
|
9965
|
-
await
|
|
11005
|
+
await mkdir6(dir, { recursive: true });
|
|
11006
|
+
await writeFile6(join7(dir, "agent.yaml"), yaml);
|
|
9966
11007
|
const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
|
|
9967
11008
|
for (const slug of own) {
|
|
9968
11009
|
const sk = await api(`/api/v2/agents/${enc9(id)}/skills/${enc9(slug)}`);
|
|
9969
|
-
await writeSkill(
|
|
11010
|
+
await writeSkill(join7(dir, "skills", slug), sk);
|
|
9970
11011
|
}
|
|
9971
11012
|
out({ saved: dir, skills: own }, () => `${lavender("\u2713")} ${dir}/agent.yaml${own.length ? ` + skills/ (${own.join(", ")})` : ""}
|
|
9972
|
-
${dim(`Edit it, then: ghosty apply ${v.out} --dry-run`)}`);
|
|
11013
|
+
${dim(t(`Edit it, then: ghosty apply ${v.out} --dry-run`, `Ed\xEDtalo y luego: ghosty apply ${v.out} --dry-run`))}`);
|
|
9973
11014
|
}
|
|
9974
11015
|
function fillSecrets(file) {
|
|
9975
11016
|
const missing = [];
|
|
@@ -9987,61 +11028,61 @@ function fillSecrets(file) {
|
|
|
9987
11028
|
return missing;
|
|
9988
11029
|
}
|
|
9989
11030
|
async function readSkills(dir) {
|
|
9990
|
-
const root =
|
|
11031
|
+
const root = join7(dir, "skills");
|
|
9991
11032
|
const out2 = {};
|
|
9992
11033
|
const slugs = await readdir3(root, { withFileTypes: true }).catch(() => []);
|
|
9993
11034
|
for (const e of slugs) {
|
|
9994
11035
|
if (!e.isDirectory() || e.name.startsWith(".")) continue;
|
|
9995
|
-
const { markdown, assets } = await readSkillDir(
|
|
11036
|
+
const { markdown, assets } = await readSkillDir(join7(root, e.name));
|
|
9996
11037
|
out2[e.name] = [{ path: "SKILL.md", contentBase64: Buffer.from(markdown).toString("base64") }, ...assets.map((a) => ({ path: a.name, contentBase64: a.contentBase64 }))];
|
|
9997
11038
|
}
|
|
9998
11039
|
return out2;
|
|
9999
11040
|
}
|
|
10000
11041
|
var line = (c) => c.op === "!" ? dim(`! ${c.what}`) : `${c.op === "+" ? lavender("+") : c.op === "-" ? red("-") : "~"} ${c.what}`;
|
|
10001
11042
|
async function applyCmd(target, v) {
|
|
10002
|
-
if (!target) throw usage("Missing file or directory.", "Example: ghosty apply pia.yaml --dry-run \xB7 ghosty apply ./pia (from agents export --out)");
|
|
11043
|
+
if (!target) throw usage(t("Missing file or directory.", "Falta el archivo o el directorio."), t("Example: ghosty apply pia.yaml --dry-run \xB7 ghosty apply ./pia (from agents export --out)", "Ejemplo: ghosty apply pia.yaml --dry-run \xB7 ghosty apply ./pia (de agents export --out)"));
|
|
10003
11044
|
const isDir = (await stat(target).catch(() => null))?.isDirectory() ?? false;
|
|
10004
|
-
const text = await
|
|
11045
|
+
const text = await readFile11(isDir ? join7(target, "agent.yaml") : target, "utf8");
|
|
10005
11046
|
const file = (0, import_yaml.parse)(text);
|
|
10006
|
-
if (!file || typeof file !== "object") throw usage(`${target} is not a YAML/JSON agent file.`);
|
|
11047
|
+
if (!file || typeof file !== "object") throw usage(t(`${target} is not a YAML/JSON agent file.`, `${target} no es un archivo de agente YAML/JSON.`));
|
|
10007
11048
|
const missing = fillSecrets(file);
|
|
10008
|
-
if (missing.length && !globals.json) info(dim(`Secrets not in your environment keep their current value: ${missing.join(", ")}`));
|
|
11049
|
+
if (missing.length && !globals.json) info(dim(t(`Secrets not in your environment keep their current value: ${missing.join(", ")}`, `Los secretos que no est\xE1n en tu entorno conservan su valor actual: ${missing.join(", ")}`)));
|
|
10009
11050
|
const files = isDir ? await readSkills(target) : void 0;
|
|
10010
11051
|
let id = v.agent ?? file["x-ghosty"]?.id;
|
|
10011
11052
|
if (v.create) {
|
|
10012
11053
|
const x = file["x-ghosty"] ?? {};
|
|
10013
11054
|
const name = v.name ?? file.title;
|
|
10014
|
-
if (!name) throw usage("The file has no title: pass --name.");
|
|
10015
|
-
if (v["dry-run"]) return out({ create: true, name, engine: x.engine, workspace: x.workspace ?? null }, () => `${lavender("+")} agent ${name} (${x.engine ?? "default engine"}${x.workspace ? ` in ${x.workspace}` : ""})
|
|
10016
|
-
${dim("Then every setting of the file is applied. Dry run: nothing created.")}`);
|
|
11055
|
+
if (!name) throw usage(t("The file has no title: pass --name.", "El archivo no tiene t\xEDtulo: pasa --name."));
|
|
11056
|
+
if (v["dry-run"]) return out({ create: true, name, engine: x.engine, workspace: x.workspace ?? null }, () => `${lavender("+")} ${t("agent", "agente")} ${name} (${x.engine ?? t("default engine", "motor por default")}${x.workspace ? t(` in ${x.workspace}`, ` en ${x.workspace}`) : ""})
|
|
11057
|
+
${dim(t("Then every setting of the file is applied. Dry run: nothing created.", "Despu\xE9s se aplica cada ajuste del archivo. Simulaci\xF3n: no se cre\xF3 nada."))}`);
|
|
10017
11058
|
requireYesIfHeadless(v.yes, `ghosty apply ${target} --create`);
|
|
10018
11059
|
const created = await api("/api/v2/me/agents", { method: "POST", body: { name, ...x.engine ? { engine: x.engine } : {}, ...x.model ? { model: x.model } : {}, ...x.workspace ? { workspace: x.workspace } : {}, ...x.channels ? { channels: x.channels } : {} } });
|
|
10019
11060
|
id = created.id;
|
|
10020
|
-
info(dim(`Created ${name} (${id}); applying the rest\u2026`));
|
|
11061
|
+
info(dim(t(`Created ${name} (${id}); applying the rest\u2026`, `${name} creado (${id}); aplicando lo dem\xE1s\u2026`)));
|
|
10021
11062
|
} else {
|
|
10022
11063
|
id = await resolveAgent(id);
|
|
10023
|
-
if (!id) throw usage("The file has no agent id.", "Point it at one with --agent <name|id>, or make a new one with --create.");
|
|
11064
|
+
if (!id) throw usage(t("The file has no agent id.", "El archivo no trae id de agente."), t("Point it at one with --agent <name|id>, or make a new one with --create.", "Ap\xFAntalo a uno con --agent <nombre|id>, o crea uno nuevo con --create."));
|
|
10024
11065
|
}
|
|
10025
11066
|
const spec = JSON.stringify(v.create ? { ...file, title: void 0 } : file);
|
|
10026
11067
|
const body = { spec, ...files ? { files } : {}, ...v.prune ? { prune: true } : {}, ...v["no-notify"] ? { notify: false } : {} };
|
|
10027
11068
|
const path = `/api/v2/agents/${enc9(id)}/apply`;
|
|
10028
11069
|
const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
|
|
10029
11070
|
const actionable = plan.plan.filter((c) => c.op !== "!");
|
|
10030
|
-
const text2 = plan.plan.map(line).join("\n") || dim("No changes: the agent already matches the file.");
|
|
11071
|
+
const text2 = plan.plan.map(line).join("\n") || dim(t("No changes: the agent already matches the file.", "Sin cambios: el agente ya coincide con el archivo."));
|
|
10031
11072
|
if (v["dry-run"] || !actionable.length) return out(plan, () => `${bold(id)}
|
|
10032
11073
|
${text2}${actionable.length ? `
|
|
10033
|
-
${dim("Dry run: nothing changed.")}` : ""}`);
|
|
11074
|
+
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}` : ""}`);
|
|
10034
11075
|
const retry = `ghosty apply ${target}${v.agent ? ` --agent ${v.agent}` : ""}${v.prune ? " --prune" : ""}`;
|
|
10035
11076
|
if (!v.create) requireYesIfHeadless(v.yes, retry);
|
|
10036
11077
|
if (!v.create) await confirm(`${text2}
|
|
10037
|
-
Apply ${actionable.length} change(s) to ${id}?`, { yes: v.yes, retry });
|
|
11078
|
+
${t(`Apply ${actionable.length} change(s) to ${id}?`, `\xBFAplicar ${actionable.length} cambio(s) a ${id}?`)}`, { yes: v.yes, retry });
|
|
10038
11079
|
const r = await api(path, { method: "POST", body });
|
|
10039
|
-
out(r, () => `${lavender("\u2713")} ${r.applied?.length ?? 0} change(s) applied to ${id}
|
|
10040
|
-
${red(`Stopped at \xAB${r.failed.what}\xBB: ${r.failed.error}`)}` : ""}`);
|
|
11080
|
+
out(r, () => `${lavender("\u2713")} ${t(`${r.applied?.length ?? 0} change(s) applied to ${id}.`, `${r.applied?.length ?? 0} cambio(s) aplicados a ${id}.`)}${r.failed ? `
|
|
11081
|
+
${red(t(`Stopped at \xAB${r.failed.what}\xBB: ${serverText(r.failed.error)}`, `Se detuvo en \xAB${r.failed.what}\xBB: ${r.failed.error}`))}` : ""}`);
|
|
10041
11082
|
}
|
|
10042
11083
|
|
|
10043
11084
|
// src/index.ts
|
|
10044
|
-
var
|
|
11085
|
+
var HELP_EN = `${bold("Usage:")} ghosty <command> [options]
|
|
10045
11086
|
|
|
10046
11087
|
${bold("Session")}
|
|
10047
11088
|
login Sign in with your browser (OAuth2). An agent can run it for you.
|
|
@@ -10049,6 +11090,7 @@ ${bold("Session")}
|
|
|
10049
11090
|
logout Sign out and revoke this device's session
|
|
10050
11091
|
whoami Show who you are signed in as
|
|
10051
11092
|
use [workspace] Default workspace for boards, agents create and agents ls [--clear]
|
|
11093
|
+
completion zsh|bash|fish Tab completion for commands, flags, agents and workspaces
|
|
10052
11094
|
usage Your plan and how much you've used this week and month
|
|
10053
11095
|
|
|
10054
11096
|
${bold("Agents")}
|
|
@@ -10125,6 +11167,7 @@ ${bold("Global options")}
|
|
|
10125
11167
|
--json Machine-readable output (stdout is JSON only)
|
|
10126
11168
|
--token <token> Use this token (or env GHOSTY_TOKEN). A gat_\u2026 agent token reaches only that agent.
|
|
10127
11169
|
--profile <name> Use a separate saved session (or env GHOSTY_PROFILE), e.g. one per account
|
|
11170
|
+
--lang es|en CLI language (or env GHOSTY_LANG; otherwise from LANG)
|
|
10128
11171
|
--yes Skip confirmations (required for rm without a terminal)
|
|
10129
11172
|
-h, --help Show help (also: ghosty <command> --help, ghosty help <command>)
|
|
10130
11173
|
-v, --version Show version
|
|
@@ -10132,56 +11175,128 @@ ${bold("Global options")}
|
|
|
10132
11175
|
${bold("Exit codes")} 0 ok \xB7 1 API error \xB7 2 usage error \xB7 3 not signed in
|
|
10133
11176
|
${dim("Note: `ghosty` may collide with another binary on your PATH (e.g. the Ghosty TUI). Check with `which -a ghosty`; run `npx @ghostystudio/cli` to be sure.")}
|
|
10134
11177
|
${dim("Docs: https://www.ghosty.studio/docs/cli")}`;
|
|
10135
|
-
var
|
|
11178
|
+
var HELP_ES = `${bold("Uso:")} ghosty <comando> [opciones]
|
|
11179
|
+
|
|
11180
|
+
${bold("Sesi\xF3n")}
|
|
11181
|
+
login Inicia sesi\xF3n con tu navegador (OAuth2). Un agente puede correrlo por ti.
|
|
11182
|
+
--device Inicia sesi\xF3n desde otro dispositivo con liga + c\xF3digo (autom\xE1tico por SSH/CI)
|
|
11183
|
+
logout Cierra sesi\xF3n y revoca la sesi\xF3n de este dispositivo
|
|
11184
|
+
whoami Muestra con qu\xE9 cuenta entraste
|
|
11185
|
+
use [espacio] Espacio por default para boards, agents create y agents ls [--clear]
|
|
11186
|
+
completion zsh|bash|fish Autocompletado con TAB: comandos, opciones, agentes y espacios
|
|
11187
|
+
usage Tu plan y cu\xE1nto llevas usado esta semana y este mes
|
|
11188
|
+
|
|
11189
|
+
${bold("Agentes")}
|
|
11190
|
+
agents ls Lista tus agentes
|
|
11191
|
+
agents create Crea un agente --name [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
|
|
11192
|
+
[--workspace SLUG] [--channels teams=off,\u2026] nace en un espacio, activo en su Teams
|
|
11193
|
+
[--like AGENTE [--copy-dbs] [--board same|main|new|NOMBRE] [--no-notify] [--dry-run]] con la configuraci\xF3n de otro agente (no su prompt, env ni canales)
|
|
11194
|
+
agents export <id> El agente como archivo (YAML, sin secretos) [--out DIR: agent.yaml + sus skills]
|
|
11195
|
+
apply <archivo|dir> Deja un agente igual que un archivo: ense\xF1a el plan (+ ~ -), pregunta y aplica [--agent X] [--create] [--prune] [--dry-run]
|
|
11196
|
+
agents get <id> Muestra la configuraci\xF3n de un agente [--fields name,model,prompt] [--prompt-out ARCHIVO]
|
|
11197
|
+
agents set <id> Cambia identidad o motor [--name] [--engine] [--model] [--prompt | --prompt-file ARCHIVO] [--env K=V,\u2026]
|
|
11198
|
+
[--connect google-drive,\u2026] [--disconnect \u2026] dale o qu\xEDtale acceso a tus conectores
|
|
11199
|
+
[--channels wa=on,teams=off] prende/apaga canales (wa = WhatsApp personal)
|
|
11200
|
+
[--voice elevenlabs:ID | kokoro:em_santa] [--voice-replies auto|off] [--auto-stage on|off]
|
|
11201
|
+
[--toolsets crm,sales | +crm,-sales] grupos de tools de gs que recibe
|
|
11202
|
+
[--prompt-base AGENTE|none --prompt-overlay ARCHIVO [--dry-run]] prompt = el de otro agente + ediciones, sincronizado
|
|
11203
|
+
agents doctor <id> Revisa la configuraci\xF3n: skills encimadas, tools que nombra el prompt, permisos rotos, turnos que fallan
|
|
11204
|
+
agents docs ls|grant|revoke <id> [doc] Documentos que lee y puede enviar [--write] [--no-deliver]
|
|
11205
|
+
agents docs grant <id> A B\u2026 Da varios documentos a la vez [--like AGENTE] copia los de otro agente
|
|
11206
|
+
agents docs add <id> Sube un documento y d\xE1selo --url URL | --file RUTA [--description] [--grant read|write|no]
|
|
11207
|
+
agents share|unshare <id> <correo> Comp\xE1rtelo sin cederlo [--role editor|admin] \xB7 agents shares <id> los lista
|
|
11208
|
+
agents rm <id> Borra un agente (s\xF3lo el due\xF1o; te pide escribir su nombre, o --yes)
|
|
11209
|
+
agents restart <id> Reinicia un agente ACP (needsRestart) para que cargue skills nuevas
|
|
11210
|
+
agents box <id> La m\xE1quina que lo atiende: \xBFya trae la imagen nueva? [--check RUTA]\u2026 [--contains RUTA=TEXTO]\u2026 [--recycle]
|
|
11211
|
+
agents demo enable|disable <id> Prende o apaga su liga p\xFAblica de demo
|
|
11212
|
+
agents demo <id> Liga p\xFAblica de demo /p/<slug>: cualquiera lo prueba sin cuenta y \xABQued\xE1rmelo\xBB lo clona a la suya
|
|
11213
|
+
[--slug NOMBRE] [--expires "AAAA-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXTO] [--chips "a|b|c"] [--starter TEXTO] [--sponsor on|off] [--rotate]
|
|
11214
|
+
|
|
11215
|
+
${bold("Configurar un agente")}
|
|
11216
|
+
files ls|get|put|rm <id> [ruta] Archivos en la m\xE1quina del agente [--file LOCAL] [--out LOCAL]
|
|
11217
|
+
skills enable|disable <id> <slug> Prende o apaga una de sus skills (o una de Ghosty)
|
|
11218
|
+
skills house <id> Las skills de Ghosty en bloque [--only a,b | --enable a,b | --disable a,b] [--dry-run]
|
|
11219
|
+
skills ls|get|add|rm <id> [slug] Skills (add del cat\xE1logo, --file SKILL.md o --dir CARPETA; get lista, --out DIR guarda; rm deja copia)
|
|
11220
|
+
skills templates <id> Plantillas de pdf-doc que ve: las suyas y las de Ghosty, y qu\xE9 carpeta gana
|
|
11221
|
+
dbs ls|tables|query|import|rehost|grant|revoke <id> [db] Bases de datos que usa [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run] [--table T --column C]
|
|
11222
|
+
mcp get|set <id> Servidores MCP [--file servers.json]
|
|
11223
|
+
channels whatsapp ls|enable|disable <id> [n\xFAmero] Qu\xE9 n\xFAmeros de WhatsApp Business contesta [--dry-run]
|
|
11224
|
+
channels whatsapp link|unlink <id> --integration ID --answer on|off Trae un n\xFAmero de WhatsApp Business de Formmy/EasyBits a este agente, o devu\xE9lvelo [--forward] [--dry-run]
|
|
11225
|
+
channels whatsapp history <id> --integration ID [--days 7] Trae de Formmy sus tarjetas, mensajes y pedidos recientes (sin duplicar) [--dry-run]
|
|
11226
|
+
channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no Mueve aqu\xED una sesi\xF3n de WhatsApp personal, sin QR [--dry-run] [--replace] [--no-connect]
|
|
11227
|
+
channels wa pair <id> --phone N Vincula un WhatsApp personal con c\xF3digo (sin QR)
|
|
11228
|
+
channels wa status|pause <id> WhatsApp personal: c\xF3mo est\xE1 / pausarlo (conserva la sesi\xF3n)
|
|
11229
|
+
channels wa groups ls|enable|disable|set|create <id> [grupo] Grupos de WhatsApp personal que contesta, y como qui\xE9n [--role equipo|cliente] [--dry-run]
|
|
11230
|
+
boards ls|create|assign|move-cards|rm Los tableros de un espacio: crear (--columns-from), a d\xF3nde manda tarjetas un agente, mover tarjetas
|
|
11231
|
+
cards ls <id> Tarjetas recientes de su tablero de ventas [--q TEXTO] [--limit 20]
|
|
11232
|
+
cards show <id> <tarjeta> El hilo de una tarjeta: qui\xE9n escribi\xF3, texto, adjuntos y si gs guard\xF3 copia (folio, tel\xE9fono o id)
|
|
11233
|
+
cards archive <id> Archiva tarjetas en bloque (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
11234
|
+
|
|
11235
|
+
${bold("Hablar con un agente")}
|
|
11236
|
+
try <id> "mensaje" Un turno de prueba; imprime la respuesta completa [--session] [--reset]
|
|
11237
|
+
--waba --session TEL \u2026por el camino real de WhatsApp, sin enviar nada; lista lo que mandar\xEDa (tools tambi\xE9n) [--media ARCHIVO [--as-formmy]] [--history ARCHIVO]
|
|
11238
|
+
--replay ARCHIVO --out DIR Repite conversaciones reales; el reporte se escribe local [--max 15] [--concurrency 8] [--resume]
|
|
11239
|
+
chat <id> ["mensaje"] Conversaci\xF3n en streaming (sin mensaje = interactivo) [--conversation ID]
|
|
11240
|
+
conversations ls <id> Lista conversaciones
|
|
11241
|
+
conversations show <id> <conversaci\xF3n> Qu\xE9 se dijo [--limit 20] (0 = todo)
|
|
11242
|
+
turns ls <id> Turnos recientes y por qu\xE9 fallaron [--errors] [--since 1h] [--limit N]
|
|
11243
|
+
turns show <id> [turno] Qu\xE9 lleg\xF3, qu\xE9 contest\xF3 y en qu\xE9 se fue el tiempo (default: el \xFAltimo) [--full]
|
|
11244
|
+
|
|
11245
|
+
${bold("Turnos programados")} ${dim("(cada corrida cae en una conversaci\xF3n y avisa a tu tel\xE9fono; una respuesta de exactamente OK no avisa)")}
|
|
11246
|
+
schedule ls <id> Turnos pendientes y corridas recientes
|
|
11247
|
+
schedule add <id> "qu\xE9 hacer" Programa un turno --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]
|
|
11248
|
+
schedule rm <id> <schedule-id> Cancela un turno pendiente
|
|
11249
|
+
|
|
11250
|
+
${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ver: ghosty partner --help)")}
|
|
11251
|
+
partner ls | enable | credentials | agent | tenants | audit | try
|
|
11252
|
+
|
|
11253
|
+
${bold("Tu cuenta")}
|
|
11254
|
+
me files ls|upload|rm Archivos de tu cuenta [--kind image|video|audio|document|other]
|
|
11255
|
+
credentials ls|set|rm [proveedor] Llaves de proveedor en tu b\xF3veda (prompt oculto, stdin o --from-env VAR; nunca como argumento)
|
|
11256
|
+
docs [tema] Abre la documentaci\xF3n
|
|
11257
|
+
|
|
11258
|
+
${bold("Opciones globales")}
|
|
11259
|
+
<agente> En todos lados: el NOMBRE de un agente (sin importar may\xFAsculas) o su id
|
|
11260
|
+
--json Salida para m\xE1quinas (stdout es s\xF3lo JSON)
|
|
11261
|
+
--token <token> Usa este token (o env GHOSTY_TOKEN). Un token de agente gat_\u2026 s\xF3lo llega a ese agente.
|
|
11262
|
+
--profile <nombre> Usa una sesi\xF3n guardada aparte (o env GHOSTY_PROFILE), p. ej. una por cuenta
|
|
11263
|
+
--lang es|en Idioma del CLI (o env GHOSTY_LANG; si no, el de LANG)
|
|
11264
|
+
--yes S\xE1ltate las confirmaciones (obligatorio para rm sin terminal)
|
|
11265
|
+
-h, --help Muestra la ayuda (tambi\xE9n: ghosty <comando> --help, ghosty help <comando>)
|
|
11266
|
+
-v, --version Muestra la versi\xF3n
|
|
11267
|
+
|
|
11268
|
+
${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
|
|
11269
|
+
${dim("Nota: `ghosty` puede chocar con otro binario en tu PATH (p. ej. la TUI de Ghosty). Rev\xEDsalo con `which -a ghosty`; corre `npx @ghostystudio/cli` para ir a la segura.")}
|
|
11270
|
+
${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
|
|
11271
|
+
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
|
|
10136
11272
|
var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
|
|
10137
11273
|
function unknown(what, input, known, helpCmd) {
|
|
10138
11274
|
const s = suggest(input, known);
|
|
10139
|
-
return usage(`Unknown command: ${what}.`, s ? `Did you mean "${s}"? Run: ${helpCmd}` : `Run: ${helpCmd}`);
|
|
11275
|
+
return usage(t(`Unknown command: ${what}.`, `Comando desconocido: ${what}.`), s ? t(`Did you mean "${s}"? Run: ${helpCmd}`, `\xBFQuisiste decir "${s}"? Corre: ${helpCmd}`) : t(`Run: ${helpCmd}`, `Corre: ${helpCmd}`));
|
|
10140
11276
|
}
|
|
10141
11277
|
function showHelp() {
|
|
10142
11278
|
if (process.stdout.isTTY && !process.env.NO_COLOR && !globals.json) process.stdout.write(BANNER + "\n\n");
|
|
10143
|
-
process.stdout.write(
|
|
10144
|
-
}
|
|
10145
|
-
function agentArgIndex(pos) {
|
|
10146
|
-
const [cmd, sub] = pos;
|
|
10147
|
-
switch (cmd) {
|
|
10148
|
-
case "agents":
|
|
10149
|
-
if (sub === "docs" || sub === "documents") return 3;
|
|
10150
|
-
return sub && !["ls", "list", "create", "new"].includes(sub) ? 2 : null;
|
|
10151
|
-
case "files":
|
|
10152
|
-
case "skills":
|
|
10153
|
-
case "mcp":
|
|
10154
|
-
case "dbs":
|
|
10155
|
-
case "db":
|
|
10156
|
-
case "turns":
|
|
10157
|
-
case "board":
|
|
10158
|
-
case "conversations":
|
|
10159
|
-
case "schedule":
|
|
10160
|
-
return 2;
|
|
10161
|
-
case "try":
|
|
10162
|
-
case "chat":
|
|
10163
|
-
return 1;
|
|
10164
|
-
case "channels":
|
|
10165
|
-
return pos[2] === "groups" ? 4 : 3;
|
|
10166
|
-
case "boards":
|
|
10167
|
-
return sub === "assign" ? 2 : null;
|
|
10168
|
-
default:
|
|
10169
|
-
return null;
|
|
10170
|
-
}
|
|
11279
|
+
process.stdout.write(t(HELP_EN, HELP_ES) + "\n");
|
|
10171
11280
|
}
|
|
10172
11281
|
async function useCmd(slug, v) {
|
|
10173
11282
|
const cfg = await readConfig();
|
|
10174
11283
|
if (v.clear) {
|
|
10175
11284
|
delete cfg.workspace;
|
|
10176
11285
|
await writeConfig(cfg);
|
|
10177
|
-
return out({ workspace: null }, () => `${lavender("\u2713")} No default workspace
|
|
11286
|
+
return out({ workspace: null }, () => `${lavender("\u2713")} ${t("No default workspace.", "Sin espacio por default.")}`);
|
|
10178
11287
|
}
|
|
10179
|
-
if (!slug) return out({ workspace: cfg.workspace ?? null }, () => cfg.workspace ?
|
|
11288
|
+
if (!slug) return out({ workspace: cfg.workspace ?? null }, () => cfg.workspace ? `${t("Workspace", "Espacio")}: ${bold(cfg.workspace)}` : dim(t("No default workspace. Set one: ghosty use <workspace>", "Sin espacio por default. Elige uno: ghosty use <espacio>")));
|
|
10180
11289
|
await api(`/api/v2/workspaces/${encodeURIComponent(slug)}/boards`);
|
|
10181
11290
|
await writeConfig({ ...cfg, workspace: slug });
|
|
10182
|
-
return out({ workspace: slug }, () => `${lavender("\u2713")} Using ${bold(slug)}: boards, agents create and agents ls default to it. ${dim("(ghosty use --clear to stop)")}`);
|
|
11291
|
+
return out({ workspace: slug }, () => t(`${lavender("\u2713")} Using ${bold(slug)}: boards, agents create and agents ls default to it. ${dim("(ghosty use --clear to stop)")}`, `${lavender("\u2713")} Usando ${bold(slug)}: boards, agents create y agents ls lo toman por default. ${dim("(ghosty use --clear para dejar de usarlo)")}`));
|
|
10183
11292
|
}
|
|
10184
11293
|
async function main() {
|
|
11294
|
+
if (process.argv[2] === "__complete") {
|
|
11295
|
+
const rest = process.argv.slice(3);
|
|
11296
|
+
return completeCmd(rest[0] === "--" ? rest.slice(1) : rest, COMMANDS);
|
|
11297
|
+
}
|
|
11298
|
+
const langAt = process.argv.findIndex((a) => a === "--lang" || a.startsWith("--lang="));
|
|
11299
|
+
if (langAt >= 0) setLang(detectLang(process.argv[langAt].includes("=") ? process.argv[langAt].split("=")[1] : process.argv[langAt + 1]));
|
|
10185
11300
|
const { values: v, positionals: pos } = parseArgs({
|
|
10186
11301
|
allowPositionals: true,
|
|
10187
11302
|
strict: true,
|
|
@@ -10264,6 +11379,7 @@ async function main() {
|
|
|
10264
11379
|
all: { type: "boolean" },
|
|
10265
11380
|
clear: { type: "boolean" },
|
|
10266
11381
|
expires: { type: "string" },
|
|
11382
|
+
lang: { type: "string" },
|
|
10267
11383
|
agent: { type: "string" },
|
|
10268
11384
|
create: { type: "boolean" },
|
|
10269
11385
|
prune: { type: "boolean" },
|
|
@@ -10306,10 +11422,11 @@ async function main() {
|
|
|
10306
11422
|
}
|
|
10307
11423
|
});
|
|
10308
11424
|
globals.json = Boolean(v.json);
|
|
11425
|
+
setLang(detectLang(v.lang));
|
|
10309
11426
|
const tokens = splitTokens(pos, v);
|
|
10310
11427
|
globals.token = tokens.session;
|
|
10311
11428
|
globals.profile = v.profile;
|
|
10312
|
-
if (v.version) return out({ version: "0.
|
|
11429
|
+
if (v.version) return out({ version: "0.20.0" }, () => "0.20.0");
|
|
10313
11430
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
10314
11431
|
const context = (await readConfig()).workspace;
|
|
10315
11432
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -10321,30 +11438,35 @@ async function main() {
|
|
|
10321
11438
|
}
|
|
10322
11439
|
}
|
|
10323
11440
|
const [cmd, sub, a1, a2] = pos;
|
|
10324
|
-
if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help:
|
|
11441
|
+
if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: partnerHelp() }, () => partnerHelp());
|
|
10325
11442
|
const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
|
|
10326
|
-
if (topic === "partner") return out({ help:
|
|
11443
|
+
if (topic === "partner") return out({ help: partnerHelp() }, () => partnerHelp());
|
|
10327
11444
|
if (topic && helpText(topic)) return out({ help: helpText(topic) }, () => helpText(topic));
|
|
10328
11445
|
if (!cmd || v.help || cmd === "help") return showHelp();
|
|
10329
11446
|
switch (cmd) {
|
|
10330
11447
|
case "login": {
|
|
10331
11448
|
if (v.token) {
|
|
10332
|
-
throw usage("--token is used per command, not saved.", "Set GHOSTY_TOKEN=<token> in your environment instead.");
|
|
11449
|
+
throw usage(t("--token is used per command, not saved.", "--token se usa en cada comando, no se guarda."), t("Set GHOSTY_TOKEN=<token> in your environment instead.", "Mejor pon GHOSTY_TOKEN=<token> en tu entorno."));
|
|
10333
11450
|
}
|
|
10334
11451
|
await (v.device || !hasLocalBrowser() ? loginDevice() : login());
|
|
10335
11452
|
const me = await api("/api/v2/me");
|
|
10336
11453
|
const cfg = await readConfig();
|
|
10337
11454
|
await writeConfig({ ...cfg, email: me.email });
|
|
10338
|
-
return out({ event: "logged_in", email: me.email }, () => `${lavender("\u2713")} Signed in as ${bold(me.email)}`);
|
|
11455
|
+
return out({ event: "logged_in", email: me.email }, () => `${lavender("\u2713")} ${t("Signed in as", "Sesi\xF3n iniciada como")} ${bold(me.email)}`);
|
|
10339
11456
|
}
|
|
10340
11457
|
case "logout":
|
|
10341
11458
|
await logout();
|
|
10342
|
-
return out({ loggedOut: true }, () => `${lavender("\u2713")} Signed out
|
|
11459
|
+
return out({ loggedOut: true }, () => `${lavender("\u2713")} ${t("Signed out.", "Sesi\xF3n cerrada.")}`);
|
|
10343
11460
|
case "whoami": {
|
|
10344
|
-
if (explicitToken()?.startsWith("gat_")) return out({ tokenType: "agent" }, () => "Using an agent token (gat_\u2026): it reaches only its own agent.");
|
|
11461
|
+
if (explicitToken()?.startsWith("gat_")) return out({ tokenType: "agent" }, () => t("Using an agent token (gat_\u2026): it reaches only its own agent.", "Usando un token de agente (gat_\u2026): s\xF3lo llega a su propio agente."));
|
|
10345
11462
|
const me = await api("/api/v2/me");
|
|
10346
11463
|
return out(me, () => `${bold(me.email)}${dim(` \xB7 ${new URL(process.env.GHOSTY_URL || "https://www.ghosty.studio").host}`)}`);
|
|
10347
11464
|
}
|
|
11465
|
+
case "completion": {
|
|
11466
|
+
const script = completionScript(sub ?? "");
|
|
11467
|
+
if (!script) throw usage(t("Which shell?", "\xBFQu\xE9 shell?"), "ghosty completion zsh | bash | fish");
|
|
11468
|
+
return void process.stdout.write(script);
|
|
11469
|
+
}
|
|
10348
11470
|
case "use":
|
|
10349
11471
|
return useCmd(sub, v);
|
|
10350
11472
|
case "apply":
|
|
@@ -10355,7 +11477,7 @@ async function main() {
|
|
|
10355
11477
|
if (v.workspace === "none") delete v.workspace;
|
|
10356
11478
|
else if (!v.workspace && context) {
|
|
10357
11479
|
v.workspace = context;
|
|
10358
|
-
if (!globals.json) info(dim(`workspace ${context} (from ghosty use; --workspace none for a personal agent)`));
|
|
11480
|
+
if (!globals.json) info(dim(t(`workspace ${context} (from ghosty use; --workspace none for a personal agent)`, `espacio ${context} (de ghosty use; --workspace none para un agente personal)`)));
|
|
10359
11481
|
}
|
|
10360
11482
|
return agentsCreate(v);
|
|
10361
11483
|
}
|
|
@@ -10398,7 +11520,7 @@ async function main() {
|
|
|
10398
11520
|
case "conversations":
|
|
10399
11521
|
if (sub === "ls" || sub === "list") return conversationsLs(a1);
|
|
10400
11522
|
if (sub === "show") return conversationsShow(a1, a2, v);
|
|
10401
|
-
throw usage(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, "Try: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>");
|
|
11523
|
+
throw usage(t(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, `Subcomando de conversations desconocido: ${sub ?? "(ninguno)"}.`), t("Try: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>", "Prueba: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>"));
|
|
10402
11524
|
case "schedule":
|
|
10403
11525
|
return scheduleCmd(sub, a1, a2, v);
|
|
10404
11526
|
case "partner":
|
|
@@ -10408,7 +11530,7 @@ async function main() {
|
|
|
10408
11530
|
case "me":
|
|
10409
11531
|
if (sub === "files") return meFiles(a1, a2, v);
|
|
10410
11532
|
if (sub === "usage") return meUsage();
|
|
10411
|
-
throw usage(`Unknown me subcommand: ${sub ?? "(none)"}.`, "Try: ghosty me files ls");
|
|
11533
|
+
throw usage(t(`Unknown me subcommand: ${sub ?? "(none)"}.`, `Subcomando de me desconocido: ${sub ?? "(ninguno)"}.`), t("Try: ghosty me files ls", "Prueba: ghosty me files ls"));
|
|
10412
11534
|
case "docs": {
|
|
10413
11535
|
const url = `${await baseUrl()}/docs${sub ? `/${encodeURIComponent(sub)}` : "/cli"}`;
|
|
10414
11536
|
if (globals.json) return out({ url });
|
|
@@ -10419,7 +11541,7 @@ async function main() {
|
|
|
10419
11541
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
10420
11542
|
}
|
|
10421
11543
|
}
|
|
10422
|
-
var notice = updateNotice("0.
|
|
11544
|
+
var notice = updateNotice("0.20.0").catch(() => null);
|
|
10423
11545
|
main().then(
|
|
10424
11546
|
async () => {
|
|
10425
11547
|
const n = await notice;
|
|
@@ -10430,7 +11552,7 @@ main().then(
|
|
|
10430
11552
|
if (e && typeof e === "object" && "code" in e && String(e.code).startsWith("ERR_PARSE_ARGS")) {
|
|
10431
11553
|
const n = await notice;
|
|
10432
11554
|
return fail(usage(e.message, n ? `${n}
|
|
10433
|
-
This option may exist in the new version
|
|
11555
|
+
${t("This option may exist in the new version.", "Esta opci\xF3n puede existir en la versi\xF3n nueva.")}` : t(`Run: ghosty --help (or update: ${UPGRADE})`, `Corre: ghosty --help (o actualiza: ${UPGRADE})`)));
|
|
10434
11556
|
}
|
|
10435
11557
|
return fail(e);
|
|
10436
11558
|
}
|