@easybits.cloud/cli 0.19.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -0
- package/dist/index.js +160 -36
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,6 +54,9 @@ easybits sandboxes bg kill sb_abc123 exec_123 # the whole process group
|
|
|
54
54
|
easybits sandboxes git clone sb_abc123 --repo https://github.com/you/app.git --dir /data/work/app --git-token '$secret:GITHUB_TOKEN'
|
|
55
55
|
easybits sandboxes git status sb_abc123 --dir /data/work/app
|
|
56
56
|
easybits sandboxes git commit sb_abc123 --dir /data/work/app --all -m "fix: checkout"
|
|
57
|
+
easybits sandboxes codex login sb_abc123 # Codex on YOUR ChatGPT plan: shows a link + code, you sign in, saved to your vault
|
|
58
|
+
easybits sandboxes codex connect sb_def456 # puts that session in another box (installs Codex if missing)
|
|
59
|
+
easybits sandboxes codex save sb_abc123 # saves the renewed session back to the vault
|
|
57
60
|
easybits sandboxes git push sb_abc123 --dir /data/work/app --git-token '$secret:GITHUB_TOKEN'
|
|
58
61
|
easybits sandboxes git pull|checkout|log sb_abc123 --dir /data/work/app …
|
|
59
62
|
easybits sandboxes suspend sb_abc123
|
|
@@ -131,6 +134,8 @@ easybits crons create "Heads-up" --at 23:00 --url https://my-app.com/api/notice
|
|
|
131
134
|
easybits crons create "Heads-up" --in 2h --url https://my-app.com/api/notice --method POST # once; --until/--times end repeating ones
|
|
132
135
|
easybits crons create "Review" --at 23:00 --run "python3 asc.py submit" --file scripts/asc.py \
|
|
133
136
|
--secret-file ASC_P8:~/.appstoreconnect/private_keys/AuthKey_X.p8 # a command in a throwaway box; the .p8 only this cron reads
|
|
137
|
+
easybits crons create "Daily signups" --cron "0 9 * * *" --run "python3 count.py" --file count.py \
|
|
138
|
+
--machine my-app # runs with my-app's env and secrets (its DATABASE_URL), resolved each run
|
|
134
139
|
easybits crons get "Review" # what it does, how it notifies you, next run (--json: all of it)
|
|
135
140
|
easybits crons runs "Review" --logs --limit 5 # exit code + stdout/stderr
|
|
136
141
|
easybits crons wait "Review" --timeout 2h # blocks until it runs; exits with its exit code
|
|
@@ -167,6 +172,7 @@ easybits db query leads "SELECT * FROM leads" --json
|
|
|
167
172
|
easybits db rename leads prospects --dry-run # which agent prompts name it
|
|
168
173
|
easybits db rm leads --dry-run # tables and rows that would be lost
|
|
169
174
|
easybits db rm leads --yes
|
|
175
|
+
easybits db attach clave # the database your machine opens at /v1/db/clave/
|
|
170
176
|
|
|
171
177
|
# Agents
|
|
172
178
|
easybits agents ls
|
|
@@ -310,6 +316,14 @@ Host *.ghosty
|
|
|
310
316
|
|
|
311
317
|
Full reference: https://www.easybits.cloud/docs#cli · https://www.easybits.cloud/en/docs/cli.md
|
|
312
318
|
|
|
319
|
+
## Changes in 0.20.0
|
|
320
|
+
|
|
321
|
+
- `db attach <name> [--description <text>]`: adds to your account the database one of your machines
|
|
322
|
+
already uses (`DATABASE_URL=http://172.20.0.1:8090/v1/db/<name>/`). Nothing is created or copied;
|
|
323
|
+
from then on `db query|tables|ls` work on it. It does not count toward your plan's database limit,
|
|
324
|
+
and `db rm` only removes it from your list: the machine keeps its data. New API:
|
|
325
|
+
`POST /api/v2/databases { name, attach: true }`.
|
|
326
|
+
|
|
313
327
|
## Changes in 0.17.1
|
|
314
328
|
|
|
315
329
|
- `easybits crons ls|runs|run|pause|resume`: EasyBits' scheduled jobs, the same as the
|
package/dist/index.js
CHANGED
|
@@ -1754,6 +1754,14 @@ function printGit(op, r) {
|
|
|
1754
1754
|
async function sandboxAt(ctx, i, usage2) {
|
|
1755
1755
|
return (await getClient(ctx)).sandboxes.get(need(ctx, i, "sandbox", usage2));
|
|
1756
1756
|
}
|
|
1757
|
+
var CODEX_USAGE = "easybits sandboxes codex <login|connect|save> <sandbox> [--force] [--no-install]";
|
|
1758
|
+
function parseDeviceAuth(out) {
|
|
1759
|
+
const plain = out.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1760
|
+
return {
|
|
1761
|
+
url: plain.match(/https:\/\/auth\.openai\.com\/\S+/)?.[0],
|
|
1762
|
+
code: plain.match(/\b[A-Z0-9]{4}-[A-Z0-9]{4,6}\b/)?.[0]
|
|
1763
|
+
};
|
|
1764
|
+
}
|
|
1757
1765
|
var FILES_USAGE = "easybits sandboxes files <ls|read|write|rm|mv|mkdir> <sandbox> <path> [local-file|new-path]";
|
|
1758
1766
|
var sandboxes = {
|
|
1759
1767
|
name: "sandboxes",
|
|
@@ -2066,6 +2074,89 @@ var sandboxes = {
|
|
|
2066
2074
|
}
|
|
2067
2075
|
}
|
|
2068
2076
|
},
|
|
2077
|
+
codex: {
|
|
2078
|
+
summary: "Codex on YOUR ChatGPT plan in a box: login once, then connect any box from your vault",
|
|
2079
|
+
usage: CODEX_USAGE,
|
|
2080
|
+
options: {
|
|
2081
|
+
force: { type: "boolean", description: "connect: replace the session the box already has" },
|
|
2082
|
+
"no-install": { type: "boolean", description: "connect/login: don't install Codex if it's missing" }
|
|
2083
|
+
},
|
|
2084
|
+
examples: [
|
|
2085
|
+
"easybits sb codex login sb_abc123 # shows a link + code; you sign in; the session goes to your vault",
|
|
2086
|
+
"easybits sb codex connect sb_def456 # puts that session in another box (installs Codex if missing)",
|
|
2087
|
+
"easybits sb codex save sb_abc123 # saves the box's (renewed) session back to the vault"
|
|
2088
|
+
],
|
|
2089
|
+
async run(ctx) {
|
|
2090
|
+
const action = need(ctx, 0, "login|connect|save", CODEX_USAGE);
|
|
2091
|
+
const id = need(ctx, 1, "sandbox", CODEX_USAGE);
|
|
2092
|
+
const path = `/sandboxes/${encodeURIComponent(id)}`;
|
|
2093
|
+
if (action === "connect") {
|
|
2094
|
+
const r2 = await api(ctx, "POST", `${path}/codex-connect`, {
|
|
2095
|
+
force: bool(ctx, "force"),
|
|
2096
|
+
install: !bool(ctx, "no-install")
|
|
2097
|
+
});
|
|
2098
|
+
return emit(ctx, r2, () => {
|
|
2099
|
+
console.log(
|
|
2100
|
+
r2.session === "kept" ? t("The box already had a Codex session: kept it (use --force to replace it).", "La caja ya ten\xEDa sesi\xF3n de Codex: se conserv\xF3 (usa --force para reemplazarla).") : t("Codex session from your vault is in the box.", "La sesi\xF3n de Codex de tu vault ya est\xE1 en la caja.")
|
|
2101
|
+
);
|
|
2102
|
+
if (r2.codex === "no_npm") console.log(t("Codex isn't installed and the box has no npm: use a node template.", "Codex no est\xE1 instalado y la caja no tiene npm: usa un template node."));
|
|
2103
|
+
console.log(t(`Try it: easybits sb exec ${id} -- codex exec "hola"`, `Pru\xE9balo: easybits sb exec ${id} -- codex exec "hola"`));
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
if (action === "save") {
|
|
2107
|
+
const r2 = await api(ctx, "POST", `${path}/codex-save`, {});
|
|
2108
|
+
return emit(
|
|
2109
|
+
ctx,
|
|
2110
|
+
r2,
|
|
2111
|
+
() => console.log(r2.saved ? t("Saved to your vault as CODEX_AUTH.", "Guardada en tu vault como CODEX_AUTH.") : t(`Not saved: ${r2.reason}`, `No se guard\xF3: ${r2.reason}`))
|
|
2112
|
+
);
|
|
2113
|
+
}
|
|
2114
|
+
if (action !== "login") throw usageError(t(`Unknown codex action "${action}".`, `Acci\xF3n de codex desconocida "${action}".`), CODEX_USAGE);
|
|
2115
|
+
const sb = await sandboxAt(ctx, 1, CODEX_USAGE);
|
|
2116
|
+
if (!bool(ctx, "no-install")) {
|
|
2117
|
+
const i = await sb.exec("command -v codex >/dev/null 2>&1 || npm i -g @openai/codex >/dev/null 2>&1; command -v codex", { timeoutSeconds: 240 });
|
|
2118
|
+
if (!i.stdout.trim()) throw new CliError(t("Couldn't install Codex in the box (needs npm: use a node template).", "No se pudo instalar Codex en la caja (necesita npm: usa un template node)."));
|
|
2119
|
+
}
|
|
2120
|
+
const bg = await sb.execBackground("codex login --device-auth");
|
|
2121
|
+
const started = Date.now();
|
|
2122
|
+
let shown = false;
|
|
2123
|
+
for (; ; ) {
|
|
2124
|
+
await new Promise((r2) => setTimeout(r2, 2e3));
|
|
2125
|
+
const st = await sb.bgStatus(bg.execId);
|
|
2126
|
+
const { url, code } = parseDeviceAuth(`${st.stdout ?? ""}
|
|
2127
|
+
${st.stderr ?? ""}`);
|
|
2128
|
+
if (!shown && url && code) {
|
|
2129
|
+
shown = true;
|
|
2130
|
+
if (ctx.json) console.log(JSON.stringify({ url, code, expiresInMinutes: 15 }));
|
|
2131
|
+
else {
|
|
2132
|
+
console.log(t("1. Open: ", "1. Abre: ") + url);
|
|
2133
|
+
console.log(t("2. Code: ", "2. C\xF3digo: ") + code + t(" (15 min)", " (15 min)"));
|
|
2134
|
+
console.log(t("Waiting for you to sign in with your ChatGPT account\u2026", "Esperando a que inicies sesi\xF3n con tu cuenta de ChatGPT\u2026"));
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
if (st.status !== "running") {
|
|
2138
|
+
if (st.exitCode !== 0) {
|
|
2139
|
+
const tail = `${st.stderr ?? ""}${st.stdout ?? ""}`.replace(/\x1b\[[0-9;]*m/g, "").trim().split("\n").slice(-3).join(" ");
|
|
2140
|
+
throw new CliError(t(`Codex login failed: ${tail}`, `El login de Codex fall\xF3: ${tail}`));
|
|
2141
|
+
}
|
|
2142
|
+
break;
|
|
2143
|
+
}
|
|
2144
|
+
if (Date.now() - started > 16 * 6e4) {
|
|
2145
|
+
await sb.bgKill(bg.execId).catch(() => void 0);
|
|
2146
|
+
throw new CliError(t("The code expired (15 min). Run it again.", "El c\xF3digo venci\xF3 (15 min). Vuelve a correrlo."));
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
const r = await api(ctx, "POST", `${path}/codex-save`, {});
|
|
2150
|
+
return emit(
|
|
2151
|
+
ctx,
|
|
2152
|
+
{ signedIn: true, ...r },
|
|
2153
|
+
() => console.log(t(
|
|
2154
|
+
`Signed in. Session saved to your vault (CODEX_AUTH): connect any other box with easybits sb codex connect <sandbox>`,
|
|
2155
|
+
`Listo. Sesi\xF3n guardada en tu vault (CODEX_AUTH): conecta cualquier otra caja con easybits sb codex connect <sandbox>`
|
|
2156
|
+
))
|
|
2157
|
+
);
|
|
2158
|
+
}
|
|
2159
|
+
},
|
|
2069
2160
|
git: {
|
|
2070
2161
|
summary: "Git inside a sandbox: clone, status, commit, push, pull, checkout, log",
|
|
2071
2162
|
usage: GIT_USAGE,
|
|
@@ -3797,6 +3888,22 @@ var db = {
|
|
|
3797
3888
|
emit(ctx, d, () => console.log(t(`Created ${d.name} (${d.id})`, `Creada ${d.name} (${d.id})`)));
|
|
3798
3889
|
}
|
|
3799
3890
|
},
|
|
3891
|
+
attach: {
|
|
3892
|
+
summary: "Add to your account the database one of your machines already uses",
|
|
3893
|
+
usage: "easybits db attach <name> [--description <text>]",
|
|
3894
|
+
options: { description: { type: "string", value: "text", description: "What it is for" } },
|
|
3895
|
+
examples: [
|
|
3896
|
+
"easybits db attach clave # the machine's DATABASE_URL ends in /v1/db/clave/",
|
|
3897
|
+
'easybits db query clave "SELECT count(*) FROM users" --json'
|
|
3898
|
+
],
|
|
3899
|
+
// No crea nada: el servidor deriva el namespace de tu cuenta + nombre y exige que exista.
|
|
3900
|
+
// Desde ahí sirven query/tables/export; `db rm` sólo la quita de la lista.
|
|
3901
|
+
async run(ctx) {
|
|
3902
|
+
const name = need(ctx, 0, "name", this.usage);
|
|
3903
|
+
const d = await api(ctx, "POST", "/databases", { name, attach: true, description: str(ctx, "description") });
|
|
3904
|
+
emit(ctx, d, () => console.log(t(`Attached ${d.name} (${d.id})`, `Adjunta ${d.name} (${d.id})`)));
|
|
3905
|
+
}
|
|
3906
|
+
},
|
|
3800
3907
|
rm: {
|
|
3801
3908
|
aliases: ["delete", "drop"],
|
|
3802
3909
|
summary: "Delete a database (irreversible)",
|
|
@@ -3826,7 +3933,13 @@ var db = {
|
|
|
3826
3933
|
const hit = await findDb(ctx, ref2);
|
|
3827
3934
|
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 });
|
|
3828
3935
|
const r = await eb.deleteDatabase(hit.id);
|
|
3829
|
-
emit(
|
|
3936
|
+
emit(
|
|
3937
|
+
ctx,
|
|
3938
|
+
{ ...r, id: hit.id, name: hit.name },
|
|
3939
|
+
() => console.log(
|
|
3940
|
+
r.detached ? t(`Removed ${hit.name} from your list; its machine keeps the data`, `${hit.name} sali\xF3 de tu lista; su m\xE1quina conserva los datos`) : t(`Deleted ${hit.name} (${hit.id})`, `Borrada ${hit.name} (${hit.id})`)
|
|
3941
|
+
)
|
|
3942
|
+
);
|
|
3830
3943
|
}
|
|
3831
3944
|
},
|
|
3832
3945
|
rename: {
|
|
@@ -4918,7 +5031,7 @@ async function updateNotice(current2) {
|
|
|
4918
5031
|
}
|
|
4919
5032
|
|
|
4920
5033
|
// src/commands/doctor.ts
|
|
4921
|
-
var VERSION = true ? "0.
|
|
5034
|
+
var VERSION = true ? "0.21.0" : "dev";
|
|
4922
5035
|
var doctor2 = {
|
|
4923
5036
|
name: "doctor",
|
|
4924
5037
|
group: "Account",
|
|
@@ -5444,7 +5557,7 @@ function describeCron(c) {
|
|
|
5444
5557
|
const state = c.enabled ? t("active", "activo") : c.done ? t("done", "terminado") : t("paused", "en pausa");
|
|
5445
5558
|
return [
|
|
5446
5559
|
`${c.id} \xAB${c.name}\xBB (${state})`,
|
|
5447
|
-
`${t("Schedule", "Horario")}: ${c.schedule} (${c.timezone})${c.until ? ` ${t("until", "hasta")} ${c.until}` : ""}${c.timesLeft != null ? ` \xB7 ${t("runs left", "
|
|
5560
|
+
`${t("Schedule", "Horario")}: ${c.schedule} (${c.timezone})${c.until ? ` ${t("until", "hasta")} ${c.until}` : ""}${c.timesLeft != null ? ` \xB7 ${t("runs left", "ejecuciones restantes")}: ${c.timesLeft}` : ""}`,
|
|
5448
5561
|
`${t("Next run", "Pr\xF3xima")}: ${c.enabled && c.nextRunLocal ? `${c.nextRunLocal} (${c.in})` : "\u2014"}`,
|
|
5449
5562
|
`${t("Does", "Hace")}: ${describeCronAction(c.action)}`,
|
|
5450
5563
|
`${t("Notifies", "Avisa")}: ${chans ? `${chans} (${n.on === "always" ? t("always", "siempre") : t("on failure", "si falla")})` : "\u2014"}`,
|
|
@@ -5477,7 +5590,8 @@ var ACTION_OPTIONS = {
|
|
|
5477
5590
|
file: { type: "string", multiple: true, value: "local[=remote]", description: "File mounted for --run, next to the command by default (repeatable)" },
|
|
5478
5591
|
"secret-file": { type: "string", multiple: true, value: "NAME:local[=remote]", description: "Upload a local file as a secret only this cron can read, written at the same ~ path in the box (repeatable; e.g. a .p8)" },
|
|
5479
5592
|
template: { type: "string", value: "python|node|bun|ubuntu", description: "Box image for --run (default python)" },
|
|
5480
|
-
timeout: { type: "string", value: "seconds", description: "Time limit for --run (default 300, max 600)" }
|
|
5593
|
+
timeout: { type: "string", value: "seconds", description: "Time limit for --run (default 300, max 600)" },
|
|
5594
|
+
machine: { type: "string", value: "id|name", description: "Run --run with that machine's config (its env and secrets, e.g. DATABASE_URL), resolved on every run" }
|
|
5481
5595
|
};
|
|
5482
5596
|
function remotePathFor(local, home = homedir7()) {
|
|
5483
5597
|
const abs = resolve3(local.replace(/^~(?=\/)/, home));
|
|
@@ -5512,7 +5626,8 @@ function execFromFlags(f, read, home = homedir7()) {
|
|
|
5512
5626
|
...files3.length ? { files: files3 } : {},
|
|
5513
5627
|
...secrets2.length ? { secrets: secrets2 } : {},
|
|
5514
5628
|
...f.template ? { template: f.template } : {},
|
|
5515
|
-
...f.timeout ? { timeoutSeconds: Number(f.timeout) } : {}
|
|
5629
|
+
...f.timeout ? { timeoutSeconds: Number(f.timeout) } : {},
|
|
5630
|
+
...f.machine ? { machine: f.machine } : {}
|
|
5516
5631
|
},
|
|
5517
5632
|
...Object.keys(secretValues).length ? { secretValues } : {}
|
|
5518
5633
|
};
|
|
@@ -5592,7 +5707,7 @@ var whenOf = (ctx) => ({
|
|
|
5592
5707
|
times: str(ctx, "times"),
|
|
5593
5708
|
tz: str(ctx, "tz")
|
|
5594
5709
|
});
|
|
5595
|
-
var nextLine = (c) => c.nextRunLocal ? t(`next ${c.nextRunLocal} ${c.timezone} (in ${c.in})`, `pr\xF3xima ${c.nextRunLocal} ${c.timezone} (en ${c.in})`) : t("no next run", "sin pr\xF3xima
|
|
5710
|
+
var nextLine = (c) => c.nextRunLocal ? t(`next ${c.nextRunLocal} ${c.timezone} (in ${c.in})`, `pr\xF3xima ${c.nextRunLocal} ${c.timezone} (en ${c.in})`) : t("no next run", "sin pr\xF3xima ejecuci\xF3n");
|
|
5596
5711
|
var flagsOf = (ctx) => ({
|
|
5597
5712
|
url: str(ctx, "url"),
|
|
5598
5713
|
method: str(ctx, "method"),
|
|
@@ -5607,7 +5722,7 @@ function actionOf(ctx) {
|
|
|
5607
5722
|
if (run && (str(ctx, "url") || str(ctx, "agent"))) return { error: "--run, --url and --agent are exclusive" };
|
|
5608
5723
|
if (run) {
|
|
5609
5724
|
return execFromFlags(
|
|
5610
|
-
{ run, files: list(ctx, "file"), secrets: list(ctx, "secret"), secretFiles: list(ctx, "secret-file"), template: str(ctx, "template"), timeout: str(ctx, "timeout") },
|
|
5725
|
+
{ run, files: list(ctx, "file"), secrets: list(ctx, "secret"), secretFiles: list(ctx, "secret-file"), template: str(ctx, "template"), timeout: str(ctx, "timeout"), machine: str(ctx, "machine") },
|
|
5611
5726
|
(p) => {
|
|
5612
5727
|
try {
|
|
5613
5728
|
return readFileSync18(p, "utf8");
|
|
@@ -5625,10 +5740,10 @@ var STATUS = {
|
|
|
5625
5740
|
tarde: ["late", "tarde"],
|
|
5626
5741
|
fallo: ["failed", "fall\xF3"],
|
|
5627
5742
|
pausado: ["paused", "pausado"],
|
|
5628
|
-
nuevo: ["no runs yet", "sin
|
|
5743
|
+
nuevo: ["no runs yet", "sin ejecuciones"]
|
|
5629
5744
|
};
|
|
5630
5745
|
function cronStatus(c) {
|
|
5631
|
-
if (c.running) return t("running", "
|
|
5746
|
+
if (c.running) return t("running", "en ejecuci\xF3n");
|
|
5632
5747
|
const s = STATUS[c.status];
|
|
5633
5748
|
return s ? t(s[0], s[1]) : c.status;
|
|
5634
5749
|
}
|
|
@@ -5732,14 +5847,14 @@ var crons = {
|
|
|
5732
5847
|
table(
|
|
5733
5848
|
items.map((r) => ({
|
|
5734
5849
|
at: fmtDate(r.startedAt),
|
|
5735
|
-
ok: r.ok == null ? t("running", "
|
|
5850
|
+
ok: r.ok == null ? t("running", "en ejecuci\xF3n") : r.ok ? "ok" : t("failed", "fall\xF3"),
|
|
5736
5851
|
status: r.exitCode != null ? `exit ${r.exitCode}` : r.status ?? "\u2014",
|
|
5737
5852
|
took: duration(r.durationMs),
|
|
5738
5853
|
trigger: r.trigger,
|
|
5739
5854
|
summary: (r.error ?? r.summary ?? "").slice(0, 80)
|
|
5740
5855
|
})),
|
|
5741
5856
|
[["at", "STARTED"], ["ok", "RESULT"], ["status", "HTTP"], ["took", "TOOK"], ["trigger", "TRIGGER"], ["summary", "SUMMARY"]],
|
|
5742
|
-
t("No runs yet.", "Todav\xEDa no hay
|
|
5857
|
+
t("No runs yet.", "Todav\xEDa no hay ejecuciones.")
|
|
5743
5858
|
);
|
|
5744
5859
|
if (withLogs) {
|
|
5745
5860
|
const last = items.find((r) => r.logs);
|
|
@@ -5786,7 +5901,7 @@ ${failed.error}`);
|
|
|
5786
5901
|
}
|
|
5787
5902
|
if (Date.now() >= deadline) {
|
|
5788
5903
|
throw new CliError(
|
|
5789
|
-
t(`Gave up waiting for \xAB${mine.name}\xBB${r.nextRunLocal ? ` (next run ${r.nextRunLocal})` : ""}.`, `Me cans\xE9 de esperar \xAB${mine.name}\xBB${r.nextRunLocal ? ` (pr\xF3xima
|
|
5904
|
+
t(`Gave up waiting for \xAB${mine.name}\xBB${r.nextRunLocal ? ` (next run ${r.nextRunLocal})` : ""}.`, `Me cans\xE9 de esperar \xAB${mine.name}\xBB${r.nextRunLocal ? ` (pr\xF3xima ejecuci\xF3n ${r.nextRunLocal})` : ""}.`),
|
|
5790
5905
|
EXIT.API,
|
|
5791
5906
|
`easybits crons wait ${mine.id} --timeout 2h`,
|
|
5792
5907
|
"timeout"
|
|
@@ -5835,7 +5950,7 @@ ${failed.error}`);
|
|
|
5835
5950
|
create: {
|
|
5836
5951
|
aliases: ["add"],
|
|
5837
5952
|
summary: "Create a cron: call a public https URL, send a turn to one of your agents or run a command in a throwaway box, repeating (at most every 5 min) or once",
|
|
5838
|
-
usage: 'easybits crons create <name> (--cron "<5 fields>" [--until <when>] [--times N] | --at <when> | --in <duration>) [--tz <IANA>] (--url <https-url> [--method] [--body] [--secret NAME] | --agent <id> --prompt <text> | --run <cmd> [--file f]\u2026 [--secret NAME]\u2026 [--secret-file NAME:path]\u2026 [--template] [--timeout]) [--dry-run]',
|
|
5953
|
+
usage: 'easybits crons create <name> (--cron "<5 fields>" [--until <when>] [--times N] | --at <when> | --in <duration>) [--tz <IANA>] (--url <https-url> [--method] [--body] [--secret NAME] | --agent <id> --prompt <text> | --run <cmd> [--file f]\u2026 [--secret NAME]\u2026 [--secret-file NAME:path]\u2026 [--template] [--timeout] [--machine id|name]) [--dry-run]',
|
|
5839
5954
|
options: {
|
|
5840
5955
|
cron: { type: "string", value: "expr", description: 'Cron of 5 fields, e.g. "30 8 * * *" = daily 8:30' },
|
|
5841
5956
|
...WHEN_OPTIONS,
|
|
@@ -5867,7 +5982,7 @@ ${failed.error}`);
|
|
|
5867
5982
|
emit(ctx, p, () => {
|
|
5868
5983
|
console.log(t(`OK, nothing created. \xAB${name}\xBB \u2014 ${p.schedule}`, `V\xE1lido, no se cre\xF3 nada. \xAB${name}\xBB \u2014 ${p.schedule}`));
|
|
5869
5984
|
for (const r of p.nextRuns) console.log(` ${r.local} ${p.timezone} (${r.at})`);
|
|
5870
|
-
if (p.in) console.log(t(`First run in ${p.in}.`, `La primera
|
|
5985
|
+
if (p.in) console.log(t(`First run in ${p.in}.`, `La primera se ejecuta en ${p.in}.`));
|
|
5871
5986
|
});
|
|
5872
5987
|
return;
|
|
5873
5988
|
}
|
|
@@ -5922,7 +6037,7 @@ ${failed.error}`);
|
|
|
5922
6037
|
const mine = await resolveUserCron(ctx, ref2);
|
|
5923
6038
|
if (!mine) throw new CliError(t(`You have no cron "${ref2}".`, `No tienes un cron "${ref2}".`), EXIT.API, "easybits crons ls", "not_found", 404);
|
|
5924
6039
|
requireYesIfHeadless(ctx);
|
|
5925
|
-
await confirm(ctx, t(`Delete cron \xAB${mine.name}\xBB? It stops running right away.`, `\xBFBorrar el cron \xAB${mine.name}\xBB? Deja de
|
|
6040
|
+
await confirm(ctx, t(`Delete cron \xAB${mine.name}\xBB? It stops running right away.`, `\xBFBorrar el cron \xAB${mine.name}\xBB? Deja de ejecutarse de inmediato.`));
|
|
5926
6041
|
const r = await api(ctx, "DELETE", `/crons/${mine.id}`);
|
|
5927
6042
|
emit(ctx, r, () => console.log(t(`Deleted \xAB${mine.name}\xBB`, `Borrado \xAB${mine.name}\xBB`)));
|
|
5928
6043
|
}
|
|
@@ -7016,7 +7131,7 @@ var HELP_ES = {
|
|
|
7016
7131
|
"Compute": "C\xF3mputo",
|
|
7017
7132
|
"Firecracker microVMs (alias: sb)": "MicroVMs Firecracker (alias: sb)",
|
|
7018
7133
|
"List your sandboxes": "Enlista tus sandboxes",
|
|
7019
|
-
"Create a sandbox and wait until it is running": "Crea un sandbox y espera a que est\xE9
|
|
7134
|
+
"Create a sandbox and wait until it is running": "Crea un sandbox y espera a que est\xE9 en marcha",
|
|
7020
7135
|
"Base template (default ubuntu): ubuntu, python, node, code-interpreter\u2026": "Template base (default ubuntu): ubuntu, python, node, code-interpreter\u2026",
|
|
7021
7136
|
"Human label (lets you `ssh <name>.ghosty`)": "Nombre para personas (permite `ssh <nombre>.ghosty`)",
|
|
7022
7137
|
"Time to live before auto-destroy (was --timeout)": "Tiempo de vida antes de autodestruirse (antes --timeout)",
|
|
@@ -7025,7 +7140,7 @@ var HELP_ES = {
|
|
|
7025
7140
|
"KEY=VALUE lines (dotenv); - reads stdin. Use it for secrets": "Renglones KEY=VALUE (dotenv); - lee stdin. \xDAsalo para secretos",
|
|
7026
7141
|
"Return right away instead of waiting for running": "Regresa de inmediato en vez de esperar a que corra",
|
|
7027
7142
|
"Show one sandbox": "Muestra un sandbox",
|
|
7028
|
-
"Run a shell command inside a sandbox": "
|
|
7143
|
+
"Run a shell command inside a sandbox": "Ejecuta un comando de shell dentro de un sandbox",
|
|
7029
7144
|
"Working directory": "Directorio de trabajo",
|
|
7030
7145
|
"Kill the command after this long": "Mata el comando despu\xE9s de este tiempo",
|
|
7031
7146
|
"Read the sandbox journal (optionally one systemd unit)": "Lee el journal del sandbox (opcionalmente de una unit de systemd)",
|
|
@@ -7043,7 +7158,7 @@ var HELP_ES = {
|
|
|
7043
7158
|
"Skip the confirmation (required without a terminal or with --json)": "Salta la confirmaci\xF3n (obligatorio sin terminal o con --json)",
|
|
7044
7159
|
"Capture a copy-on-write snapshot without stopping the box": "Toma un snapshot copy-on-write sin detener la caja",
|
|
7045
7160
|
"Snapshot label": "Nombre del snapshot",
|
|
7046
|
-
"Agents running in their own sandbox": "Agentes que
|
|
7161
|
+
"Agents running in their own sandbox": "Agentes que viven en su propio sandbox",
|
|
7047
7162
|
"List your agents": "Enlista tus agentes",
|
|
7048
7163
|
"Show one agent: record, prompt, MCP servers, skills and files": "Muestra un agente: registro, prompt, servidores MCP, skills y archivos",
|
|
7049
7164
|
"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",
|
|
@@ -7189,6 +7304,8 @@ var HELP_ES = {
|
|
|
7189
7304
|
"SQL databases (libSQL)": "Bases de datos SQL (libSQL)",
|
|
7190
7305
|
"List your databases": "Enlista tus bases de datos",
|
|
7191
7306
|
"Create a database": "Crea una base de datos",
|
|
7307
|
+
"Add to your account the database one of your machines already uses": "Agrega a tu cuenta la base que ya usa una de tus m\xE1quinas",
|
|
7308
|
+
"the machine's DATABASE_URL ends in /v1/db/clave/": "el DATABASE_URL de la m\xE1quina termina en /v1/db/clave/",
|
|
7192
7309
|
"What it is for": "Para qu\xE9 es",
|
|
7193
7310
|
"Delete a database (irreversible)": "Borra una base de datos (irreversible)",
|
|
7194
7311
|
"Show its tables and rows; delete nothing": "Ense\xF1a sus tablas y filas; no borra nada",
|
|
@@ -7200,7 +7317,7 @@ var HELP_ES = {
|
|
|
7200
7317
|
"asks you to type the name": "te pide teclear el nombre",
|
|
7201
7318
|
"scripts and agents": "scripts y agentes",
|
|
7202
7319
|
"List tables with row counts and columns, and how many links are permanent": "Enlista las tablas con sus filas y columnas, y cu\xE1ntas ligas son permanentes",
|
|
7203
|
-
"Run one SQL statement (by database id or name)": "
|
|
7320
|
+
"Run one SQL statement (by database id or name)": "Ejecuta una sentencia SQL (por id o nombre de la base)",
|
|
7204
7321
|
"Positional ? parameter (repeatable)": "Par\xE1metro ? posicional (repetible)",
|
|
7205
7322
|
"Storage": "Almacenamiento",
|
|
7206
7323
|
"Your files in EasyBits storage": "Tus archivos en el almacenamiento de EasyBits",
|
|
@@ -7283,24 +7400,25 @@ var HELP_ES = {
|
|
|
7283
7400
|
"Release a machine: stops billing, takes a last backup and destroys it": "Libera una m\xE1quina: deja de cobrarse, toma un \xFAltimo respaldo y la destruye",
|
|
7284
7401
|
"The hosting catalog: sizes and monthly prices in MXN": "El cat\xE1logo de hosting: tama\xF1os y precio mensual en MXN",
|
|
7285
7402
|
// crons (tuyos y de la plataforma)
|
|
7286
|
-
"Your scheduled jobs (call a URL, message an agent or run a command; repeating or once) and, for admins, the platform's": "Tus tareas programadas (pegarle a una URL, escribirle a un agente o
|
|
7403
|
+
"Your scheduled jobs (call a URL, message an agent or run a command; repeating or once) and, for admins, the platform's": "Tus tareas programadas (pegarle a una URL, escribirle a un agente o ejecutar un comando; repetidas o de una sola vez) y, para admins, las de la plataforma",
|
|
7287
7404
|
"List your crons (or, with --platform, the platform's scheduled jobs)": "Enlista tus crons (o, con --platform, las tareas de la plataforma)",
|
|
7288
7405
|
"Platform jobs (backups, purges\u2026; platform admins only)": "Tareas de la plataforma (respaldos, purgas\u2026; s\xF3lo admins)",
|
|
7289
|
-
"Show the last runs of one of your crons (id or name) or of a platform job, with the full error": "Muestra las \xFAltimas
|
|
7290
|
-
"Run one of your crons or a platform job now, in the background (trigger manual)": "
|
|
7406
|
+
"Show the last runs of one of your crons (id or name) or of a platform job, with the full error": "Muestra las \xFAltimas ejecuciones de un cron tuyo (id o nombre) o de una tarea de la plataforma, con el error completo",
|
|
7407
|
+
"Run one of your crons or a platform job now, in the background (trigger manual)": "Ejecuta ya un cron tuyo o una tarea de la plataforma, en segundo plano (manual)",
|
|
7291
7408
|
"Pause one of your crons or a platform job until you resume it": "Pausa un cron tuyo o una tarea de la plataforma hasta que lo reanudes",
|
|
7292
7409
|
"Resume one of your crons or a platform job": "Reanuda un cron tuyo o una tarea de la plataforma",
|
|
7293
|
-
"Create a cron: call a public https URL, send a turn to one of your agents or run a command in a throwaway box, repeating (at most every 5 min) or once": "Crea un cron: pegarle a una URL https p\xFAblica, mandarle un turno a uno de tus agentes o
|
|
7410
|
+
"Create a cron: call a public https URL, send a turn to one of your agents or run a command in a throwaway box, repeating (at most every 5 min) or once": "Crea un cron: pegarle a una URL https p\xFAblica, mandarle un turno a uno de tus agentes o ejecutar un comando en una caja desechable, repetido (como mucho cada 5 min) o de una sola vez",
|
|
7294
7411
|
"Vault secret: a header with --url (default Authorization: Bearer); an env var with --run (repeatable)": "Secreto de la b\xF3veda: header con --url (default Authorization: Bearer); variable de entorno con --run (repetible)",
|
|
7295
|
-
"Run this shell command in a throwaway box instead (python, files and secrets included; success = exit 0)": "
|
|
7412
|
+
"Run this shell command in a throwaway box instead (python, files and secrets included; success = exit 0)": "Ejecutar este comando de shell en una caja desechable (con python, tus archivos y secretos; \xE9xito = exit 0)",
|
|
7296
7413
|
"File mounted for --run, next to the command by default (repeatable)": "Archivo que se monta para --run, junto al comando por default (repetible)",
|
|
7297
7414
|
"Upload a local file as a secret only this cron can read, written at the same ~ path in the box (repeatable; e.g. a .p8)": "Sube un archivo local como secreto que s\xF3lo este cron puede leer; se escribe en la misma ruta ~ dentro de la caja (repetible; p. ej. un .p8)",
|
|
7298
7415
|
"Box image for --run (default python)": "Imagen de la caja para --run (default python)",
|
|
7299
7416
|
"Time limit for --run (default 300, max 600)": "Tiempo l\xEDmite de --run (default 300, m\xE1ximo 600)",
|
|
7300
|
-
"
|
|
7301
|
-
"
|
|
7417
|
+
"Run --run with that machine's config (its env and secrets, e.g. DATABASE_URL), resolved on every run": "Corre --run con la configuraci\xF3n de esa m\xE1quina (su env y sus secretos, p. ej. DATABASE_URL), resuelta en cada ejecuci\xF3n",
|
|
7418
|
+
"Also print stdout/stderr of --run crons (last 64 KB per run)": "Muestra tambi\xE9n stdout/stderr de los crons --run (\xFAltimos 64 KB por ejecuci\xF3n)",
|
|
7419
|
+
"Wait for the next run of one of your crons to finish and exit with its result (exec: its exit code)": "Espera a que termine la siguiente ejecuci\xF3n de un cron tuyo y sale con su resultado (exec: su c\xF3digo de salida)",
|
|
7302
7420
|
"Give up after this long (default 30m; 10m, 2h\u2026)": "Se rinde despu\xE9s de este tiempo (default 30m; 10m, 2h\u2026)",
|
|
7303
|
-
"Tell you after each run: push to the Ghosty app (vault secret GHOSTY_PUSH_TOKEN), email to your account and/or an event in one of your inboxes; none removes it": "Te avisa tras cada
|
|
7421
|
+
"Tell you after each run: push to the Ghosty app (vault secret GHOSTY_PUSH_TOKEN), email to your account and/or an event in one of your inboxes; none removes it": "Te avisa tras cada ejecuci\xF3n: push a la app de Ghosty (secreto GHOSTY_PUSH_TOKEN de la b\xF3veda), correo a tu cuenta y/o un evento en uno de tus buzones; none lo quita",
|
|
7304
7422
|
// inbox (buzones de eventos)
|
|
7305
7423
|
"Event inboxes: your own webhook URL whose events reach your terminal live or wait for you to read them": "Buzones de eventos: tu propia URL de webhook; lo que llega te alcanza en la terminal en vivo o te espera para que lo leas",
|
|
7306
7424
|
"List your inboxes with their URL and unread events": "Enlista tus buzones con su URL y los eventos sin leer",
|
|
@@ -7318,22 +7436,22 @@ var HELP_ES = {
|
|
|
7318
7436
|
"New name, unique in your account": "Nombre nuevo, \xFAnico en tu cuenta",
|
|
7319
7437
|
"Days events are kept (1-30)": "D\xEDas que se guardan los eventos (1-30)",
|
|
7320
7438
|
"Give an inbox a new URL and print it; the old one stops accepting writes right away (stored events stay)": "Le da al buz\xF3n una URL nueva y la imprime; la vieja deja de aceptar escrituras de inmediato (los eventos guardados se quedan)",
|
|
7321
|
-
"Show one of your crons: what it does, how it notifies you and when it runs (--json: the whole object)": "Muestra un cron tuyo: qu\xE9 hace, c\xF3mo te avisa y cu\xE1ndo
|
|
7322
|
-
"At most N runs of one of your crons (1-50, default 50)": "Como m\xE1ximo N
|
|
7439
|
+
"Show one of your crons: what it does, how it notifies you and when it runs (--json: the whole object)": "Muestra un cron tuyo: qu\xE9 hace, c\xF3mo te avisa y cu\xE1ndo se ejecuta (--json: el objeto completo)",
|
|
7440
|
+
"At most N runs of one of your crons (1-50, default 50)": "Como m\xE1ximo N ejecuciones de un cron tuyo (1-50, default 50)",
|
|
7323
7441
|
"When to notify (default failure)": "Cu\xE1ndo avisar (default failure: s\xF3lo si falla)",
|
|
7324
|
-
'Run ONCE at this time: ISO ("2026-10-18T23:00", read in --tz) or "23:00" (today)': '
|
|
7325
|
-
"Run ONCE after this long: 10m, 2h, 1h30m, 1d": "
|
|
7442
|
+
'Run ONCE at this time: ISO ("2026-10-18T23:00", read in --tz) or "23:00" (today)': 'Ejecutar UNA SOLA VEZ a esta hora: ISO ("2026-10-18T23:00", en la zona de --tz) o "23:00" (hoy)',
|
|
7443
|
+
"Run ONCE after this long: 10m, 2h, 1h30m, 1d": "Ejecutar UNA SOLA VEZ dentro de: 10m, 2h, 1h30m, 1d",
|
|
7326
7444
|
"A repeating cron stops after this date": "Un cron que se repite se detiene despu\xE9s de esta fecha",
|
|
7327
|
-
"A repeating cron stops after N more runs": "Un cron que se repite se detiene tras N
|
|
7328
|
-
"Validate and show the next 3 runs without creating anything": "Valida y muestra las pr\xF3ximas 3
|
|
7445
|
+
"A repeating cron stops after N more runs": "Un cron que se repite se detiene tras N ejecuciones m\xE1s",
|
|
7446
|
+
"Validate and show the next 3 runs without creating anything": "Valida y muestra las pr\xF3ximas 3 ejecuciones sin crear nada",
|
|
7329
7447
|
'Cron of 5 fields, e.g. "30 8 * * *" = daily 8:30': 'Cron de 5 campos, p. ej. "30 8 * * *" = diario 8:30',
|
|
7330
7448
|
"IANA time zone (default America/Mexico_City)": "Zona horaria IANA (default America/Mexico_City)",
|
|
7331
|
-
"Call this public https URL on each run": "Pegarle a esta URL https p\xFAblica en cada
|
|
7449
|
+
"Call this public https URL on each run": "Pegarle a esta URL https p\xFAblica en cada ejecuci\xF3n",
|
|
7332
7450
|
"HTTP method (default GET)": "M\xE9todo HTTP (default GET)",
|
|
7333
7451
|
"Request body (not with GET)": "Cuerpo de la petici\xF3n (no con GET)",
|
|
7334
7452
|
"Header for --secret": "Header para --secret",
|
|
7335
7453
|
"Send a turn to this fleet agent instead of calling a URL": "Mandarle un turno a este agente de la flota en vez de pegarle a una URL",
|
|
7336
|
-
"What the agent is told on each run (with --agent)": "Lo que se le dice al agente en cada
|
|
7454
|
+
"What the agent is told on each run (with --agent)": "Lo que se le dice al agente en cada ejecuci\xF3n (con --agent)",
|
|
7337
7455
|
"Change one of your crons (only what you pass)": "Cambia un cron tuyo (s\xF3lo lo que pases)",
|
|
7338
7456
|
"New name": "Nombre nuevo",
|
|
7339
7457
|
"New schedule": "Horario nuevo",
|
|
@@ -7350,6 +7468,12 @@ var HELP_ES = {
|
|
|
7350
7468
|
"Long-running processes: start one in the background, list, poll its output, kill it": "Procesos largos: arranca uno en segundo plano, enl\xEDstalos, lee su salida, detenlo",
|
|
7351
7469
|
"kill: seconds between SIGTERM and SIGKILL (default 5)": "kill: segundos entre SIGTERM y SIGKILL (default 5)",
|
|
7352
7470
|
"status, exit code, stdout/stderr": "estado, c\xF3digo de salida, stdout/stderr",
|
|
7471
|
+
"Codex on YOUR ChatGPT plan in a box: login once, then connect any box from your vault": "Codex con TU plan de ChatGPT en una caja: inicia sesi\xF3n una vez y conecta cualquier caja desde tu vault",
|
|
7472
|
+
"connect: replace the session the box already has": "connect: reemplaza la sesi\xF3n que ya tenga la caja",
|
|
7473
|
+
"shows a link + code; you sign in; the session goes to your vault": "muestra un link y un c\xF3digo; inicias sesi\xF3n; la sesi\xF3n se guarda en tu vault",
|
|
7474
|
+
"puts that session in another box (installs Codex if missing)": "pone esa sesi\xF3n en otra caja (instala Codex si falta)",
|
|
7475
|
+
"saves the box's (renewed) session back to the vault": "devuelve al vault la sesi\xF3n (renovada) de la caja",
|
|
7476
|
+
"connect/login: don't install Codex if it's missing": "connect/login: no instalar Codex si falta",
|
|
7353
7477
|
"Git inside a sandbox: clone, status, commit, push, pull, checkout, log": "Git dentro de un sandbox: clone, status, commit, push, pull, checkout, log",
|
|
7354
7478
|
"Repo folder inside the sandbox (required)": "Carpeta del repo dentro del sandbox (obligatoria)",
|
|
7355
7479
|
"clone: repo URL (clean; the token goes in --git-token)": "clone: URL del repo (limpia; el token va en --git-token)",
|
|
@@ -7653,7 +7777,7 @@ function normalizeArgs(input) {
|
|
|
7653
7777
|
}
|
|
7654
7778
|
|
|
7655
7779
|
// src/index.ts
|
|
7656
|
-
var VERSION2 = true ? "0.
|
|
7780
|
+
var VERSION2 = true ? "0.21.0" : "dev";
|
|
7657
7781
|
var GLOBAL_OPTIONS = {
|
|
7658
7782
|
json: { type: "boolean" },
|
|
7659
7783
|
token: { type: "string" },
|
package/package.json
CHANGED