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