@easybits.cloud/cli 0.14.1 → 0.15.1
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 +21 -0
- package/dist/index.js +454 -272
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,6 +73,8 @@ easybits db create leads
|
|
|
73
73
|
easybits db tables leads # tables, row counts, columns, permanent vs external links
|
|
74
74
|
easybits db photos put catalog --table products --key-column sku --dir photos/ # SKU-123.jpg → row sku=SKU-123
|
|
75
75
|
easybits db query leads "SELECT * FROM leads" --json
|
|
76
|
+
easybits db rename leads prospects --dry-run # which agent prompts name it
|
|
77
|
+
easybits db rm leads --dry-run # tables and rows that would be lost
|
|
76
78
|
easybits db rm leads --yes
|
|
77
79
|
|
|
78
80
|
# Agents
|
|
@@ -166,6 +168,25 @@ Host *.ghosty
|
|
|
166
168
|
|
|
167
169
|
Full reference: https://www.easybits.cloud/docs#cli · https://www.easybits.cloud/en/docs/cli.md
|
|
168
170
|
|
|
171
|
+
## Changes in 0.15.1
|
|
172
|
+
|
|
173
|
+
- After a `--dry-run`, the last line is the real command (`To apply: …`); without a terminal it
|
|
174
|
+
already carries `--yes` when the command asks for confirmation.
|
|
175
|
+
- A 404 with a reason prints the reason; a bare «Not found» says it may also be missing access
|
|
176
|
+
(another account's resource) and points to `easybits whoami`.
|
|
177
|
+
- The «new version» notice checks npm every hour (it was once a day), and asks again when the
|
|
178
|
+
cache is older than what you have installed.
|
|
179
|
+
|
|
180
|
+
## Changes in 0.15.0
|
|
181
|
+
|
|
182
|
+
- `db rename <db> <new-name> [--skip-prompts] [--dry-run]` (alias `mv`): renames the database (the
|
|
183
|
+
id does not change, so the `db_*` tools keep working) and rewrites the name in your ghosty-lite /
|
|
184
|
+
goose agents' prompts where it appears as a standalone word — never inside `x.com`, `@x`, `x_test`
|
|
185
|
+
or in capitals. `--dry-run` lists each prompt and line; a prompt that fails is reported (exit 1)
|
|
186
|
+
and the rename stays. New API: `PATCH /api/v2/databases/:dbId { name?, description? }`.
|
|
187
|
+
- `db rm --dry-run`: the tables and row counts you would lose.
|
|
188
|
+
- `agents create --prompt/--prompt-file` prints which prompt was applied and its size.
|
|
189
|
+
|
|
169
190
|
## Changes in 0.14.1
|
|
170
191
|
|
|
171
192
|
- `machines launch --machine` takes a name too, like every other command.
|
package/dist/index.js
CHANGED
|
@@ -108,6 +108,17 @@ function notLoggedIn() {
|
|
|
108
108
|
"not_logged_in"
|
|
109
109
|
);
|
|
110
110
|
}
|
|
111
|
+
function isGenericNotFound(body) {
|
|
112
|
+
let msg = body;
|
|
113
|
+
try {
|
|
114
|
+
const j = JSON.parse(body);
|
|
115
|
+
msg = j?.message ?? j?.error ?? j?.detail ?? "";
|
|
116
|
+
} catch {
|
|
117
|
+
}
|
|
118
|
+
if (typeof msg !== "string") return false;
|
|
119
|
+
const s = msg.trim();
|
|
120
|
+
return !s || s.startsWith("<") || /^(404\s*:?\s*)?not found\.?$/i.test(s);
|
|
121
|
+
}
|
|
111
122
|
function apiMessage(body) {
|
|
112
123
|
try {
|
|
113
124
|
const j = JSON.parse(body);
|
|
@@ -142,6 +153,14 @@ function toCliError(err) {
|
|
|
142
153
|
401
|
|
143
154
|
);
|
|
144
155
|
}
|
|
156
|
+
if (err.status === 404) {
|
|
157
|
+
const hint404 = t(
|
|
158
|
+
"Check the id (list what you have with the matching `ls` command) and that your key is from the account that owns it (easybits whoami).",
|
|
159
|
+
"Revisa el id (enlista lo que tienes con el `ls` que corresponda) y que tu key sea de la cuenta due\xF1a (easybits whoami)."
|
|
160
|
+
);
|
|
161
|
+
if (isGenericNotFound(err.body)) return new CliError(t("Not found \u2014 or you don't have access to it.", "No se encontr\xF3, o no tienes acceso."), EXIT.API, hint404, "not_found", 404);
|
|
162
|
+
return new CliError(message2, EXIT.API, (kind && KIND_HINTS()[kind]) ?? hint404, "not_found", 404);
|
|
163
|
+
}
|
|
145
164
|
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
165
|
return new CliError(`${t("API error", "Error de la API")} ${err.status}: ${message2}`, EXIT.API, hint, "api_error", err.status);
|
|
147
166
|
}
|
|
@@ -683,9 +702,18 @@ import { createInterface } from "readline/promises";
|
|
|
683
702
|
var YES_OPTION = {
|
|
684
703
|
yes: { type: "boolean", short: "y", description: "Skip the confirmation (required without a terminal or with --json)" }
|
|
685
704
|
};
|
|
686
|
-
function sameCommand() {
|
|
687
|
-
const
|
|
688
|
-
return `easybits ${
|
|
705
|
+
function sameCommand(argv2 = process.argv.slice(2)) {
|
|
706
|
+
const quoted = argv2.map((a) => /^[\w@%+=:,./-]+$/.test(a) ? a : `'${a.replace(/'/g, `'\\''`)}'`);
|
|
707
|
+
return `easybits ${quoted.join(" ")}`;
|
|
708
|
+
}
|
|
709
|
+
function applyCommand(argv2, opts) {
|
|
710
|
+
const rest = argv2.filter((a) => a !== "--dry-run");
|
|
711
|
+
const hasYes = rest.some((a) => a === "--yes" || a === "-y");
|
|
712
|
+
return `${sameCommand(rest)}${opts.needsYes && opts.headless && !hasYes ? " --yes" : ""}`;
|
|
713
|
+
}
|
|
714
|
+
function applyHint(ctx, opts = {}) {
|
|
715
|
+
const cmd = applyCommand(process.argv.slice(2), { headless: !interactive(ctx), needsYes: opts.needsYes });
|
|
716
|
+
return t(`To apply: ${cmd}`, `Para aplicarlo: ${cmd}`);
|
|
689
717
|
}
|
|
690
718
|
async function ask(question) {
|
|
691
719
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -1419,6 +1447,7 @@ var REF_ARGS = {
|
|
|
1419
1447
|
"domains verify": { index: 0, kind: "sandbox" },
|
|
1420
1448
|
"domains rm": { index: 0, kind: "sandbox" },
|
|
1421
1449
|
"db rm": { index: 0, kind: "db" },
|
|
1450
|
+
"db rename": { index: 0, kind: "db" },
|
|
1422
1451
|
"db tables": { index: 0, kind: "db" },
|
|
1423
1452
|
"db query": { index: 0, kind: "db" },
|
|
1424
1453
|
"db photos": { index: 1, kind: "db" }
|
|
@@ -1778,7 +1807,7 @@ var domains = {
|
|
|
1778
1807
|
};
|
|
1779
1808
|
|
|
1780
1809
|
// src/commands/db.ts
|
|
1781
|
-
import { existsSync as
|
|
1810
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9, statSync as statSync5 } from "fs";
|
|
1782
1811
|
|
|
1783
1812
|
// src/photos.ts
|
|
1784
1813
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -1872,248 +1901,6 @@ function parseLinkStats(columns, row) {
|
|
|
1872
1901
|
return columns.map((column, i) => ({ column, permanent: n(i * 4), expiring: n(i * 4 + 1), external: n(i * 4 + 2), empty: n(i * 4 + 3) })).filter((s) => s.permanent + s.expiring + s.external > 0);
|
|
1873
1902
|
}
|
|
1874
1903
|
|
|
1875
|
-
// src/commands/db.ts
|
|
1876
|
-
async function findDb(ctx, id) {
|
|
1877
|
-
const hit = (await listRefs(ctx, "db")).find((d) => d.id === id);
|
|
1878
|
-
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);
|
|
1879
|
-
return { id, name: hit.name ?? id };
|
|
1880
|
-
}
|
|
1881
|
-
var db = {
|
|
1882
|
-
name: "db",
|
|
1883
|
-
aliases: ["databases", "database"],
|
|
1884
|
-
group: "Data",
|
|
1885
|
-
summary: "SQL databases (libSQL)",
|
|
1886
|
-
synopsis: "db",
|
|
1887
|
-
subs: {
|
|
1888
|
-
ls: {
|
|
1889
|
-
aliases: ["list"],
|
|
1890
|
-
summary: "List your databases",
|
|
1891
|
-
usage: "easybits db ls",
|
|
1892
|
-
examples: ["easybits db ls --json"],
|
|
1893
|
-
async run(ctx) {
|
|
1894
|
-
const eb = await getClient(ctx);
|
|
1895
|
-
const { items } = await eb.listDatabases();
|
|
1896
|
-
emit(
|
|
1897
|
-
ctx,
|
|
1898
|
-
items,
|
|
1899
|
-
() => table(
|
|
1900
|
-
items.map((d) => ({ ...d, createdAt: fmtDate(d.createdAt) })),
|
|
1901
|
-
[["id", "ID"], ["name", "NAME"], ["createdAt", "CREATED"], ["description", "DESCRIPTION"]],
|
|
1902
|
-
t("No databases. Create one: easybits db create <name>", "No hay bases. Crea una: easybits db create <nombre>")
|
|
1903
|
-
)
|
|
1904
|
-
);
|
|
1905
|
-
}
|
|
1906
|
-
},
|
|
1907
|
-
create: {
|
|
1908
|
-
summary: "Create a database",
|
|
1909
|
-
usage: "easybits db create <name> [--description <text>]",
|
|
1910
|
-
options: { description: { type: "string", value: "text", description: "What it is for" } },
|
|
1911
|
-
examples: ["easybits db create leads --description 'CRM leads'"],
|
|
1912
|
-
async run(ctx) {
|
|
1913
|
-
const name = need(ctx, 0, "name", this.usage);
|
|
1914
|
-
const eb = await getClient(ctx);
|
|
1915
|
-
const d = await eb.createDatabase({ name, description: str(ctx, "description") });
|
|
1916
|
-
emit(ctx, d, () => console.log(t(`Created ${d.name} (${d.id})`, `Creada ${d.name} (${d.id})`)));
|
|
1917
|
-
}
|
|
1918
|
-
},
|
|
1919
|
-
rm: {
|
|
1920
|
-
aliases: ["delete", "drop"],
|
|
1921
|
-
summary: "Delete a database (irreversible)",
|
|
1922
|
-
usage: "easybits db rm <db-id|name> [--yes]",
|
|
1923
|
-
options: { ...YES_OPTION },
|
|
1924
|
-
examples: ["easybits db rm leads # asks you to type the name", "easybits db rm leads --yes # scripts and agents"],
|
|
1925
|
-
async run(ctx) {
|
|
1926
|
-
const ref = need(ctx, 0, "db-id|name", this.usage);
|
|
1927
|
-
requireYesIfHeadless(ctx);
|
|
1928
|
-
const eb = await getClient(ctx);
|
|
1929
|
-
const hit = await findDb(ctx, ref);
|
|
1930
|
-
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 });
|
|
1931
|
-
const r = await eb.deleteDatabase(hit.id);
|
|
1932
|
-
emit(ctx, { ...r, id: hit.id, name: hit.name }, () => console.log(t(`Deleted ${hit.name} (${hit.id})`, `Borrada ${hit.name} (${hit.id})`)));
|
|
1933
|
-
}
|
|
1934
|
-
},
|
|
1935
|
-
tables: {
|
|
1936
|
-
aliases: ["inspect", "schema"],
|
|
1937
|
-
summary: "List tables with row counts and columns, and how many links are permanent",
|
|
1938
|
-
usage: "easybits db tables <db-id|name>",
|
|
1939
|
-
examples: ["easybits db tables leads", "easybits db tables leads --json"],
|
|
1940
|
-
// Patrón de `turso db inspect` / `.tables` + `.schema` de sqlite3, en una sola vista.
|
|
1941
|
-
async run(ctx) {
|
|
1942
|
-
const ref = need(ctx, 0, "db-id|name", this.usage);
|
|
1943
|
-
const eb = await getClient(ctx);
|
|
1944
|
-
const hit = await findDb(ctx, ref);
|
|
1945
|
-
const q = (sql, args) => eb.queryDatabase(hit.id, sql, args);
|
|
1946
|
-
const names = (await q("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_litestream_%' ORDER BY name")).rows.map((r) => String(r[0]));
|
|
1947
|
-
const ident2 = (n) => `"${n.replace(/"/g, '""')}"`;
|
|
1948
|
-
const tables = [];
|
|
1949
|
-
for (const name of names) {
|
|
1950
|
-
const info2 = await q(`PRAGMA table_info(${ident2(name)})`);
|
|
1951
|
-
const col = (k) => info2.cols.indexOf(k);
|
|
1952
|
-
const columns = info2.rows.map((r) => ({ name: String(r[col("name")]), type: String(r[col("type")] ?? ""), pk: Number(r[col("pk")]) > 0 }));
|
|
1953
|
-
const count = await q(`SELECT count(*) FROM ${ident2(name)}`);
|
|
1954
|
-
const textCols = columns.filter((c) => !c.type || /CHAR|TEXT|CLOB/i.test(c.type)).map((c) => c.name);
|
|
1955
|
-
const links = textCols.length ? parseLinkStats(textCols, (await q(linkStatsSql(name, textCols))).rows[0] ?? []) : [];
|
|
1956
|
-
tables.push({ name, rows: Number(count.rows[0]?.[0] ?? 0), columns, links });
|
|
1957
|
-
}
|
|
1958
|
-
emit(ctx, tables, () => {
|
|
1959
|
-
table(
|
|
1960
|
-
tables.map((t2) => ({
|
|
1961
|
-
name: t2.name,
|
|
1962
|
-
rows: t2.rows,
|
|
1963
|
-
columns: t2.columns.map((c) => `${c.name}${c.type ? ` ${c.type}` : ""}${c.pk ? " PK" : ""}`).join(", ")
|
|
1964
|
-
})),
|
|
1965
|
-
[["name", "TABLE"], ["rows", "ROWS"], ["columns", "COLUMNS"]],
|
|
1966
|
-
t(`No tables in ${hit.name}. Create one: easybits db query ${hit.name} 'CREATE TABLE \u2026'`, `No hay tablas en ${hit.name}. Crea una: easybits db query ${hit.name} 'CREATE TABLE \u2026'`)
|
|
1967
|
-
);
|
|
1968
|
-
const withLinks = tables.flatMap((tb) => tb.links.map((l) => ({ ...l, table: tb.name })));
|
|
1969
|
-
if (withLinks.length) {
|
|
1970
|
-
console.log("\n" + t("Links (permanent = EasyBits public storage; expiring = signed URL):", "Ligas (permanente = almacenamiento p\xFAblico de EasyBits; caduca = URL firmada):"));
|
|
1971
|
-
table(
|
|
1972
|
-
withLinks.map((l) => ({ where: `${l.table}.${l.column}`, permanent: l.permanent, expiring: l.expiring, external: l.external, empty: l.empty })),
|
|
1973
|
-
[["where", "COLUMN"], ["permanent", "PERMANENT"], ["expiring", "EXPIRING"], ["external", "EXTERNAL"], ["empty", "EMPTY"]]
|
|
1974
|
-
);
|
|
1975
|
-
if (withLinks.some((l) => l.external + l.expiring > 0)) {
|
|
1976
|
-
console.log(t("Make them permanent: easybits db photos put <db> --table T --key-column sku --dir photos/", "Hazlas permanentes: easybits db photos put <db> --table T --key-column sku --dir fotos/"));
|
|
1977
|
-
}
|
|
1978
|
-
}
|
|
1979
|
-
});
|
|
1980
|
-
}
|
|
1981
|
-
},
|
|
1982
|
-
photos: {
|
|
1983
|
-
summary: "Put catalog photos in a table: SKU-123.jpg \u2192 the row whose key is SKU-123, as a permanent public link",
|
|
1984
|
-
usage: "easybits db photos put <db> --table <table> --key-column <col> --dir <folder> [--column <col>] [--replace] [--dry-run]",
|
|
1985
|
-
options: {
|
|
1986
|
-
table: { type: "string", value: "table", description: "Table to update" },
|
|
1987
|
-
"key-column": { type: "string", value: "col", description: "Column that matches the file name (e.g. sku)" },
|
|
1988
|
-
dir: { type: "string", value: "folder", description: "Folder with the photos (JPEG, PNG or WebP, max 10 MB each)" },
|
|
1989
|
-
column: { type: "string", value: "col", description: "Column for the link (default: image_url, photo_url, imagen\u2026)" },
|
|
1990
|
-
replace: { type: "boolean", description: "Also replace rows that already have a permanent link" },
|
|
1991
|
-
"dry-run": { type: "boolean", description: "Show the plan; upload nothing" }
|
|
1992
|
-
},
|
|
1993
|
-
examples: [
|
|
1994
|
-
"easybits db photos put catalogo --table productos --key-column sku --dir fotos/ --dry-run",
|
|
1995
|
-
"easybits db photos put catalogo --table productos --key-column sku --dir fotos/ --column foto",
|
|
1996
|
-
"easybits db tables catalogo # how many links are permanent, external or empty"
|
|
1997
|
-
],
|
|
1998
|
-
async run(ctx) {
|
|
1999
|
-
const action = need(ctx, 0, "put", this.usage);
|
|
2000
|
-
if (action !== "put") throw usageError(t(`Unknown photos action "${action}".`, `Acci\xF3n de photos desconocida "${action}".`), this.usage);
|
|
2001
|
-
const ref = need(ctx, 1, "db", this.usage);
|
|
2002
|
-
const tableName = str(ctx, "table");
|
|
2003
|
-
const keyCol = str(ctx, "key-column");
|
|
2004
|
-
const dir = str(ctx, "dir");
|
|
2005
|
-
if (!tableName || !keyCol || !dir) throw usageError(t("Missing --table, --key-column or --dir.", "Faltan --table, --key-column o --dir."), this.usage);
|
|
2006
|
-
if (!existsSync7(dir) || !statSync4(dir).isDirectory()) throw usageError(t(`Not a folder: ${dir}`, `No es una carpeta: ${dir}`), this.usage);
|
|
2007
|
-
const { photos, rejected } = photoFiles(dir);
|
|
2008
|
-
const eb = await getClient(ctx);
|
|
2009
|
-
const hit = await findDb(ctx, ref);
|
|
2010
|
-
const q = (sql, args) => eb.queryDatabase(hit.id, sql, args);
|
|
2011
|
-
const ident2 = (n) => `"${n.replace(/"/g, '""')}"`;
|
|
2012
|
-
const info2 = await q(`PRAGMA table_info(${ident2(tableName)})`);
|
|
2013
|
-
const cols = info2.rows.map((r) => String(r[info2.cols.indexOf("name")]));
|
|
2014
|
-
if (!cols.length) throw new CliError(t(`No table "${tableName}" in ${hit.name}.`, `No hay tabla "${tableName}" en ${hit.name}.`), 1, t(`See: easybits db tables ${hit.name}`, `Mira: easybits db tables ${hit.name}`), "not_found", 404);
|
|
2015
|
-
if (!cols.includes(keyCol)) throw usageError(t(`No column "${keyCol}" in ${tableName} (${cols.join(", ")}).`, `No hay columna "${keyCol}" en ${tableName} (${cols.join(", ")}).`), this.usage);
|
|
2016
|
-
const column = str(ctx, "column") ?? defaultPhotoColumn(cols);
|
|
2017
|
-
if (!column) throw usageError(t(`${tableName} has no photo column: pass --column (${cols.join(", ")}).`, `${tableName} no tiene columna de foto: pasa --column (${cols.join(", ")}).`), this.usage);
|
|
2018
|
-
if (!cols.includes(column)) throw usageError(t(`No column "${column}" in ${tableName} (${cols.join(", ")}).`, `No hay columna "${column}" en ${tableName} (${cols.join(", ")}).`), this.usage);
|
|
2019
|
-
const current2 = /* @__PURE__ */ new Map();
|
|
2020
|
-
for (let i = 0; i < photos.length; i += 100) {
|
|
2021
|
-
const keys = photos.slice(i, i + 100).map((p) => p.key);
|
|
2022
|
-
const r = await q(`SELECT ${ident2(keyCol)}, ${ident2(column)} FROM ${ident2(tableName)} WHERE ${ident2(keyCol)} IN (${keys.map(() => "?").join(",")})`, keys);
|
|
2023
|
-
for (const row of r.rows) current2.set(String(row[0]), row[1]);
|
|
2024
|
-
}
|
|
2025
|
-
const plan = planPhotos(photos, current2, bool(ctx, "replace"));
|
|
2026
|
-
const big = photos.filter((p) => p.bytes > WHATSAPP_MAX_BYTES).map((p) => p.file);
|
|
2027
|
-
const summary = { db: hit.name, table: tableName, keyColumn: keyCol, column, plan, rejected, overWhatsappLimit: big };
|
|
2028
|
-
const count = (a) => plan.filter((p) => p.action === a).length;
|
|
2029
|
-
const why = (r) => r === "not_an_image" ? t("not a JPEG/PNG/WebP", "no es JPEG/PNG/WebP") : r === "too_large" ? t("over 10 MB", "m\xE1s de 10 MB") : t("empty file", "archivo vac\xEDo");
|
|
2030
|
-
const printPlan2 = () => {
|
|
2031
|
-
for (const p of plan) {
|
|
2032
|
-
const what = p.action === "update" ? t("update", "actualizar") : p.action === "keep" ? t("keep (already permanent; --replace to redo)", "se queda (ya es permanente; --replace para rehacerla)") : t("no row with that key", "no hay fila con esa llave");
|
|
2033
|
-
console.log(`${p.action === "update" ? "~" : "!"} ${p.file} \u2192 ${keyCol}=${p.key}: ${what}`);
|
|
2034
|
-
}
|
|
2035
|
-
for (const r of rejected) console.log(`! ${r.file}: ${why(r.reason)}`);
|
|
2036
|
-
if (big.length) console.log(t(`Over 5 MB (WhatsApp won't send them by link): ${big.join(", ")}`, `M\xE1s de 5 MB (WhatsApp no las manda por liga): ${big.join(", ")}`));
|
|
2037
|
-
};
|
|
2038
|
-
if (bool(ctx, "dry-run") || !count("update")) {
|
|
2039
|
-
emit(ctx, { ...summary, dryRun: bool(ctx, "dry-run") }, () => {
|
|
2040
|
-
printPlan2();
|
|
2041
|
-
console.log(count("update") ? t("(dry run: nothing uploaded)", "(simulaci\xF3n: no se subi\xF3 nada)") : t("Nothing to update.", "Nada que actualizar."));
|
|
2042
|
-
});
|
|
2043
|
-
return;
|
|
2044
|
-
}
|
|
2045
|
-
const urls = /* @__PURE__ */ new Map();
|
|
2046
|
-
const results = [];
|
|
2047
|
-
for (const item of plan.filter((p) => p.action === "update")) {
|
|
2048
|
-
const photo = photos.find((p) => p.key === item.key);
|
|
2049
|
-
try {
|
|
2050
|
-
let url = urls.get(photo.sha);
|
|
2051
|
-
if (!url) {
|
|
2052
|
-
const up = await eb.uploadFile({ fileName: `${photo.sha.slice(0, 32)}.${photo.ext}`, contentType: photo.mime, size: photo.bytes, access: "public" });
|
|
2053
|
-
const res = await fetch(up.putUrl, { method: "PUT", body: readFileSync8(photo.path), headers: { "Content-Type": photo.mime } });
|
|
2054
|
-
if (!res.ok) throw new Error(t(`storage answered ${res.status}`, `el almacenamiento contest\xF3 ${res.status}`));
|
|
2055
|
-
url = up.file.url;
|
|
2056
|
-
if (!url) throw new Error(t("the API returned no public link", "la API no devolvi\xF3 liga p\xFAblica"));
|
|
2057
|
-
urls.set(photo.sha, url);
|
|
2058
|
-
}
|
|
2059
|
-
await q(`UPDATE ${ident2(tableName)} SET ${ident2(column)} = ? WHERE ${ident2(keyCol)} = ?`, [url, item.key]);
|
|
2060
|
-
results.push({ key: item.key, file: item.file, url });
|
|
2061
|
-
if (!ctx.json) console.log(`~ ${item.file} \u2192 ${keyCol}=${item.key}: ${url}`);
|
|
2062
|
-
} catch (e) {
|
|
2063
|
-
results.push({ key: item.key, file: item.file, error: e.message });
|
|
2064
|
-
if (!ctx.json) console.error(`! ${item.file}: ${e.message}`);
|
|
2065
|
-
}
|
|
2066
|
-
}
|
|
2067
|
-
const failed = results.filter((r) => r.error).length;
|
|
2068
|
-
emit(ctx, { ...summary, results }, () => {
|
|
2069
|
-
for (const p of plan.filter((x) => x.action !== "update")) console.log(`! ${p.file} \u2192 ${keyCol}=${p.key}: ${p.action === "keep" ? t("kept", "se qued\xF3") : t("no row with that key", "no hay fila con esa llave")}`);
|
|
2070
|
-
for (const r of rejected) console.log(`! ${r.file}: ${why(r.reason)}`);
|
|
2071
|
-
console.log(t(`Updated ${results.length - failed} row(s)${failed ? `, ${failed} failed` : ""}.`, `${results.length - failed} fila(s) actualizadas${failed ? `, ${failed} fallaron` : ""}.`));
|
|
2072
|
-
});
|
|
2073
|
-
if (failed) process.exitCode = 1;
|
|
2074
|
-
}
|
|
2075
|
-
},
|
|
2076
|
-
query: {
|
|
2077
|
-
aliases: ["sql"],
|
|
2078
|
-
summary: "Run one SQL statement (by database id or name)",
|
|
2079
|
-
usage: "easybits db query <db-id|name> <sql> [--arg <value>]...",
|
|
2080
|
-
options: { arg: { type: "string", multiple: true, value: "value", description: "Positional ? parameter (repeatable)" } },
|
|
2081
|
-
examples: [
|
|
2082
|
-
"easybits db query leads 'SELECT * FROM leads LIMIT 10'",
|
|
2083
|
-
"easybits db query leads 'INSERT INTO leads(name) VALUES (?)' --arg Ana",
|
|
2084
|
-
"easybits db query leads 'SELECT count(*) AS n FROM leads' --json"
|
|
2085
|
-
],
|
|
2086
|
-
async run(ctx) {
|
|
2087
|
-
const ref = need(ctx, 0, "db-id|name", this.usage);
|
|
2088
|
-
const sql = need(ctx, 1, "sql", this.usage);
|
|
2089
|
-
const eb = await getClient(ctx);
|
|
2090
|
-
const hit = await findDb(ctx, ref);
|
|
2091
|
-
const args = list(ctx, "arg");
|
|
2092
|
-
const r = await eb.queryDatabase(hit.id, sql, args.length ? args : void 0);
|
|
2093
|
-
emit(ctx, r, () => {
|
|
2094
|
-
if (r.cols.length) {
|
|
2095
|
-
table(
|
|
2096
|
-
r.rows.map((row) => Object.fromEntries(r.cols.map((c, i) => [c, row[i]]))),
|
|
2097
|
-
r.cols.map((c) => [c, c]),
|
|
2098
|
-
"(0 rows)"
|
|
2099
|
-
);
|
|
2100
|
-
} else {
|
|
2101
|
-
console.log(
|
|
2102
|
-
t(
|
|
2103
|
-
`OK, ${r.affected_row_count} row(s) affected${r.last_insert_rowid ? `, last id ${r.last_insert_rowid}` : ""}`,
|
|
2104
|
-
`OK, ${r.affected_row_count} fila(s) afectadas${r.last_insert_rowid ? `, \xFAltimo id ${r.last_insert_rowid}` : ""}`
|
|
2105
|
-
)
|
|
2106
|
-
);
|
|
2107
|
-
}
|
|
2108
|
-
});
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
|
-
}
|
|
2112
|
-
};
|
|
2113
|
-
|
|
2114
|
-
// src/commands/agents.ts
|
|
2115
|
-
import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync7 } from "fs";
|
|
2116
|
-
|
|
2117
1904
|
// src/api.ts
|
|
2118
1905
|
import { EasybitsError as EasybitsError2, resolveBaseUrl as resolveBaseUrl4 } from "@easybits.cloud/sdk";
|
|
2119
1906
|
async function api(ctx, method, path, body, opts = {}) {
|
|
@@ -2142,7 +1929,7 @@ async function api(ctx, method, path, body, opts = {}) {
|
|
|
2142
1929
|
var encodePath = (p) => p.replace(/^\/+/, "").split("/").map(encodeURIComponent).join("/");
|
|
2143
1930
|
|
|
2144
1931
|
// src/commands/agents-config.ts
|
|
2145
|
-
import { existsSync as
|
|
1932
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync as readdirSync2, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
2146
1933
|
import { basename as basename3, dirname, join as join4, relative, resolve } from "path";
|
|
2147
1934
|
var WORK_DIR = "/data/work";
|
|
2148
1935
|
var MACHINE_TEMPLATES = /* @__PURE__ */ new Set(["ghosty-lite", "goose"]);
|
|
@@ -2222,8 +2009,8 @@ function readLocalText(path, usage2) {
|
|
|
2222
2009
|
if (process.stdin.isTTY) throw usageError(t("Expected the content on stdin.", "Se esperaba el contenido por stdin."), usage2);
|
|
2223
2010
|
return readStdin().then((b) => b.toString("utf8"));
|
|
2224
2011
|
}
|
|
2225
|
-
if (!
|
|
2226
|
-
return
|
|
2012
|
+
if (!existsSync7(path)) throw usageError(t(`File not found: ${path}`, `No existe el archivo: ${path}`), usage2);
|
|
2013
|
+
return readFileSync8(path, "utf8");
|
|
2227
2014
|
}
|
|
2228
2015
|
function parseMcpFile(text, usage2) {
|
|
2229
2016
|
let j;
|
|
@@ -2379,6 +2166,7 @@ var set = {
|
|
|
2379
2166
|
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."));
|
|
2380
2167
|
for (const [k, v] of Object.entries(changes)) console.log(`${k}: ${v.from} \u2192 ${v.to}`);
|
|
2381
2168
|
console.log(t("(dry run: nothing changed)", "(simulaci\xF3n: no cambi\xF3 nada)"));
|
|
2169
|
+
console.log(applyHint(ctx));
|
|
2382
2170
|
});
|
|
2383
2171
|
return;
|
|
2384
2172
|
}
|
|
@@ -2438,19 +2226,20 @@ var files2 = {
|
|
|
2438
2226
|
if (!rest.length) throw usageError(t("Give one or more local files.", "Pasa uno o m\xE1s archivos locales."), FILES_USAGE2);
|
|
2439
2227
|
const to = (str(ctx, "to") ?? "").replace(/^\/+|\/+$/g, "");
|
|
2440
2228
|
const plan = rest.map((local) => {
|
|
2441
|
-
if (!
|
|
2442
|
-
return { local, path: to ? `${to}/${basename3(local)}` : basename3(local), bytes:
|
|
2229
|
+
if (!existsSync7(local) || !statSync4(local).isFile()) throw usageError(t(`File not found: ${local}`, `No existe el archivo: ${local}`), FILES_USAGE2);
|
|
2230
|
+
return { local, path: to ? `${to}/${basename3(local)}` : basename3(local), bytes: statSync4(local).size };
|
|
2443
2231
|
});
|
|
2444
2232
|
if (bool(ctx, "dry-run")) {
|
|
2445
2233
|
emit(ctx, { agentId: id, dryRun: true, upload: plan }, () => {
|
|
2446
2234
|
for (const p of plan) console.log(`${p.local} \u2192 ${WORK_DIR}/${p.path} (${fmtBytes(p.bytes)})`);
|
|
2447
2235
|
console.log(t("(dry run: nothing uploaded)", "(simulaci\xF3n: no se subi\xF3 nada)"));
|
|
2236
|
+
console.log(applyHint(ctx));
|
|
2448
2237
|
});
|
|
2449
2238
|
return;
|
|
2450
2239
|
}
|
|
2451
2240
|
const done = [];
|
|
2452
2241
|
for (const p of plan) {
|
|
2453
|
-
const r = await api(ctx, "PUT", `/agents/${id}/files/${encodePath(p.path)}`,
|
|
2242
|
+
const r = await api(ctx, "PUT", `/agents/${id}/files/${encodePath(p.path)}`, readFileSync8(p.local), { raw: true });
|
|
2454
2243
|
done.push({ local: p.local, ...r });
|
|
2455
2244
|
if (!ctx.json) console.log(`${t("Uploaded", "Subido")} ${p.local} \u2192 ${WORK_DIR}/${p.path} (${fmtBytes(p.bytes)})`);
|
|
2456
2245
|
}
|
|
@@ -2461,6 +2250,7 @@ var files2 = {
|
|
|
2461
2250
|
if (bool(ctx, "dry-run")) {
|
|
2462
2251
|
emit(ctx, { agentId: id, dryRun: true, delete: rest }, () => {
|
|
2463
2252
|
for (const p of rest) console.log(`${t("would delete", "borrar\xEDa")} ${WORK_DIR}/${p}`);
|
|
2253
|
+
console.log(applyHint(ctx, { needsYes: true }));
|
|
2464
2254
|
});
|
|
2465
2255
|
return;
|
|
2466
2256
|
}
|
|
@@ -2480,7 +2270,7 @@ function readSkillDir(dir) {
|
|
|
2480
2270
|
if (e.name.startsWith(".") || e.name === "node_modules") continue;
|
|
2481
2271
|
const p = join4(d, e.name);
|
|
2482
2272
|
if (e.isDirectory()) visit(p);
|
|
2483
|
-
else if (e.isFile() && relative(dir, p) !== "SKILL.md") assets.push({ name: relative(dir, p).split("\\").join("/"), contentBase64:
|
|
2273
|
+
else if (e.isFile() && relative(dir, p) !== "SKILL.md") assets.push({ name: relative(dir, p).split("\\").join("/"), contentBase64: readFileSync8(p).toString("base64") });
|
|
2484
2274
|
}
|
|
2485
2275
|
};
|
|
2486
2276
|
visit(dir);
|
|
@@ -2544,8 +2334,8 @@ var skills = {
|
|
|
2544
2334
|
const dir = str(ctx, "dir");
|
|
2545
2335
|
if (!!file === !!dir) throw usageError(t("Pass --file SKILL.md or --dir <folder>.", "Pasa --file SKILL.md o --dir <carpeta>."), SKILLS_USAGE);
|
|
2546
2336
|
const mdPath = file ?? join4(dir, "SKILL.md");
|
|
2547
|
-
if (!
|
|
2548
|
-
const markdown =
|
|
2337
|
+
if (!existsSync7(mdPath)) throw usageError(t(`File not found: ${mdPath}`, `No existe el archivo: ${mdPath}`), SKILLS_USAGE);
|
|
2338
|
+
const markdown = readFileSync8(mdPath, "utf8");
|
|
2549
2339
|
const fm = skillFrontmatter(markdown);
|
|
2550
2340
|
if (fm.error) throw usageError(`${mdPath}: ${fm.error}`, SKILLS_USAGE);
|
|
2551
2341
|
const slug = str(ctx, "slug") ?? (fm.name && SLUG_RE.test(fm.name) ? fm.name : basename3(resolve(dir ?? dirname(mdPath))).toLowerCase());
|
|
@@ -2553,9 +2343,12 @@ var skills = {
|
|
|
2553
2343
|
const assets = dir ? readSkillDir(dir) : [];
|
|
2554
2344
|
const plan = { agentId: id, slug, files: ["SKILL.md", ...assets.map((a) => a.name)], restart: bool(ctx, "restart") };
|
|
2555
2345
|
if (bool(ctx, "dry-run")) {
|
|
2556
|
-
emit(ctx, { ...plan, dryRun: true }, () =>
|
|
2346
|
+
emit(ctx, { ...plan, dryRun: true }, () => {
|
|
2347
|
+
console.log(t(`would add skill ${slug} (${plan.files.length} file(s))${plan.restart ? " and restart" : ""}
|
|
2557
2348
|
(dry run: nothing changed)`, `agregar\xEDa la skill ${slug} (${plan.files.length} archivo(s))${plan.restart ? " y reiniciar\xEDa" : ""}
|
|
2558
|
-
(simulaci\xF3n: no cambi\xF3 nada)`))
|
|
2349
|
+
(simulaci\xF3n: no cambi\xF3 nada)`));
|
|
2350
|
+
console.log(applyHint(ctx));
|
|
2351
|
+
});
|
|
2559
2352
|
return;
|
|
2560
2353
|
}
|
|
2561
2354
|
const r = await api(ctx, "PUT", `/agents/${id}/skills/${slug}`, { markdown, assets });
|
|
@@ -2568,7 +2361,10 @@ var skills = {
|
|
|
2568
2361
|
const slugs = ctx.args.slice(2);
|
|
2569
2362
|
if (!slugs.length) throw usageError(t("Give one or more slugs.", "Pasa uno o m\xE1s slugs."), SKILLS_USAGE);
|
|
2570
2363
|
if (bool(ctx, "dry-run")) {
|
|
2571
|
-
emit(ctx, { agentId: id, dryRun: true, delete: slugs }, () =>
|
|
2364
|
+
emit(ctx, { agentId: id, dryRun: true, delete: slugs }, () => {
|
|
2365
|
+
slugs.forEach((s) => console.log(`${t("would remove skill", "quitar\xEDa la skill")} ${s}`));
|
|
2366
|
+
console.log(applyHint(ctx, { needsYes: true }));
|
|
2367
|
+
});
|
|
2572
2368
|
return;
|
|
2573
2369
|
}
|
|
2574
2370
|
requireYesIfHeadless(ctx);
|
|
@@ -2632,6 +2428,7 @@ var mcp2 = {
|
|
|
2632
2428
|
removed: ${diff.removed.join(", ") || "-"}
|
|
2633
2429
|
changed: ${diff.changed.join(", ") || "-"}`);
|
|
2634
2430
|
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)"));
|
|
2431
|
+
console.log(applyHint(ctx));
|
|
2635
2432
|
});
|
|
2636
2433
|
return;
|
|
2637
2434
|
}
|
|
@@ -2805,10 +2602,10 @@ var exportAgent = {
|
|
|
2805
2602
|
}
|
|
2806
2603
|
};
|
|
2807
2604
|
function readExport(path, usage2) {
|
|
2808
|
-
if (!
|
|
2605
|
+
if (!existsSync7(path)) throw usageError(t(`File not found: ${path}`, `No existe el archivo: ${path}`), usage2);
|
|
2809
2606
|
let x;
|
|
2810
2607
|
try {
|
|
2811
|
-
x = JSON.parse(
|
|
2608
|
+
x = JSON.parse(readFileSync8(path, "utf8"));
|
|
2812
2609
|
} catch (e) {
|
|
2813
2610
|
throw usageError(t(`${path} is not JSON: ${e.message}`, `${path} no es JSON: ${e.message}`), usage2);
|
|
2814
2611
|
}
|
|
@@ -2852,13 +2649,381 @@ async function applyExport(ctx, id, x, opts) {
|
|
|
2852
2649
|
return copied;
|
|
2853
2650
|
}
|
|
2854
2651
|
|
|
2855
|
-
// src/
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2652
|
+
// src/db-name-refs.ts
|
|
2653
|
+
var EDGE = "A-Za-z0-9_.@/-";
|
|
2654
|
+
var escape2 = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2655
|
+
var nameRe = (name) => new RegExp(`(?<![${EDGE}])${escape2(name)}(?![${EDGE}])`, "g");
|
|
2656
|
+
function findNameRefs(text, name) {
|
|
2657
|
+
const re = nameRe(name);
|
|
2658
|
+
return text.split("\n").flatMap((l, i) => {
|
|
2659
|
+
re.lastIndex = 0;
|
|
2660
|
+
return re.test(l) ? [{ line: i + 1, text: l.trim().slice(0, 160) }] : [];
|
|
2661
|
+
});
|
|
2662
|
+
}
|
|
2663
|
+
function replaceName(text, from, to) {
|
|
2664
|
+
return text.replace(nameRe(from), to);
|
|
2665
|
+
}
|
|
2859
2666
|
|
|
2860
|
-
// src/
|
|
2861
|
-
|
|
2667
|
+
// src/commands/db.ts
|
|
2668
|
+
async function findDb(ctx, id) {
|
|
2669
|
+
const hit = (await listRefs(ctx, "db")).find((d) => d.id === id);
|
|
2670
|
+
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);
|
|
2671
|
+
return { id, name: hit.name ?? id };
|
|
2672
|
+
}
|
|
2673
|
+
var ident2 = (n) => `"${n.replace(/"/g, '""')}"`;
|
|
2674
|
+
async function tableCounts(ctx, id) {
|
|
2675
|
+
const eb = await getClient(ctx);
|
|
2676
|
+
const q = (sql) => eb.queryDatabase(id, sql);
|
|
2677
|
+
const names = (await q("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_litestream_%' AND name NOT LIKE '_easybits_%' ORDER BY name")).rows.map((r) => String(r[0]));
|
|
2678
|
+
const out = [];
|
|
2679
|
+
for (const name of names) out.push({ name, rows: Number((await q(`SELECT count(*) FROM ${ident2(name)}`)).rows[0]?.[0] ?? 0) });
|
|
2680
|
+
return out;
|
|
2681
|
+
}
|
|
2682
|
+
async function promptRefs(ctx, name) {
|
|
2683
|
+
const eb = await getClient(ctx);
|
|
2684
|
+
const found = [];
|
|
2685
|
+
const skipped = [];
|
|
2686
|
+
for (const a of await eb.listAgents()) {
|
|
2687
|
+
if (!MACHINE_TEMPLATES.has(a.template)) continue;
|
|
2688
|
+
const agent = a.name ?? a.agentId;
|
|
2689
|
+
if (a.status === "lost" || a.status === "error") {
|
|
2690
|
+
skipped.push({ agentId: a.agentId, agent, reason: a.status });
|
|
2691
|
+
continue;
|
|
2692
|
+
}
|
|
2693
|
+
try {
|
|
2694
|
+
const p = await api(ctx, "GET", `/agents/${a.agentId}/prompt`);
|
|
2695
|
+
const refs = findNameRefs(p.systemPrompt ?? "", name);
|
|
2696
|
+
if (refs.length) found.push({ agentId: a.agentId, agent, refs });
|
|
2697
|
+
} catch (e) {
|
|
2698
|
+
skipped.push({ agentId: a.agentId, agent, reason: e.message });
|
|
2699
|
+
}
|
|
2700
|
+
}
|
|
2701
|
+
return { found, skipped };
|
|
2702
|
+
}
|
|
2703
|
+
var db = {
|
|
2704
|
+
name: "db",
|
|
2705
|
+
aliases: ["databases", "database"],
|
|
2706
|
+
group: "Data",
|
|
2707
|
+
summary: "SQL databases (libSQL)",
|
|
2708
|
+
synopsis: "db",
|
|
2709
|
+
subs: {
|
|
2710
|
+
ls: {
|
|
2711
|
+
aliases: ["list"],
|
|
2712
|
+
summary: "List your databases",
|
|
2713
|
+
usage: "easybits db ls",
|
|
2714
|
+
examples: ["easybits db ls --json"],
|
|
2715
|
+
async run(ctx) {
|
|
2716
|
+
const eb = await getClient(ctx);
|
|
2717
|
+
const { items } = await eb.listDatabases();
|
|
2718
|
+
emit(
|
|
2719
|
+
ctx,
|
|
2720
|
+
items,
|
|
2721
|
+
() => table(
|
|
2722
|
+
items.map((d) => ({ ...d, createdAt: fmtDate(d.createdAt) })),
|
|
2723
|
+
[["id", "ID"], ["name", "NAME"], ["createdAt", "CREATED"], ["description", "DESCRIPTION"]],
|
|
2724
|
+
t("No databases. Create one: easybits db create <name>", "No hay bases. Crea una: easybits db create <nombre>")
|
|
2725
|
+
)
|
|
2726
|
+
);
|
|
2727
|
+
}
|
|
2728
|
+
},
|
|
2729
|
+
create: {
|
|
2730
|
+
summary: "Create a database",
|
|
2731
|
+
usage: "easybits db create <name> [--description <text>]",
|
|
2732
|
+
options: { description: { type: "string", value: "text", description: "What it is for" } },
|
|
2733
|
+
examples: ["easybits db create leads --description 'CRM leads'"],
|
|
2734
|
+
async run(ctx) {
|
|
2735
|
+
const name = need(ctx, 0, "name", this.usage);
|
|
2736
|
+
const eb = await getClient(ctx);
|
|
2737
|
+
const d = await eb.createDatabase({ name, description: str(ctx, "description") });
|
|
2738
|
+
emit(ctx, d, () => console.log(t(`Created ${d.name} (${d.id})`, `Creada ${d.name} (${d.id})`)));
|
|
2739
|
+
}
|
|
2740
|
+
},
|
|
2741
|
+
rm: {
|
|
2742
|
+
aliases: ["delete", "drop"],
|
|
2743
|
+
summary: "Delete a database (irreversible)",
|
|
2744
|
+
usage: "easybits db rm <db-id|name> [--dry-run] [--yes]",
|
|
2745
|
+
options: { "dry-run": { type: "boolean", description: "Show its tables and rows; delete nothing" }, ...YES_OPTION },
|
|
2746
|
+
examples: [
|
|
2747
|
+
"easybits db rm leads --dry-run # what would be lost",
|
|
2748
|
+
"easybits db rm leads # asks you to type the name",
|
|
2749
|
+
"easybits db rm leads --yes # scripts and agents"
|
|
2750
|
+
],
|
|
2751
|
+
async run(ctx) {
|
|
2752
|
+
const ref = need(ctx, 0, "db-id|name", this.usage);
|
|
2753
|
+
if (bool(ctx, "dry-run")) {
|
|
2754
|
+
const hit2 = await findDb(ctx, ref);
|
|
2755
|
+
const tables = await tableCounts(ctx, hit2.id);
|
|
2756
|
+
const rows = tables.reduce((n, x) => n + x.rows, 0);
|
|
2757
|
+
emit(ctx, { id: hit2.id, name: hit2.name, dryRun: true, tables }, () => {
|
|
2758
|
+
console.log(t(`${hit2.name} (${hit2.id}): ${tables.length} table(s), ${rows} row(s)`, `${hit2.name} (${hit2.id}): ${tables.length} tabla(s), ${rows} fila(s)`));
|
|
2759
|
+
for (const x of tables) console.log(` ${x.name}: ${x.rows}`);
|
|
2760
|
+
console.log(t("(dry run: nothing deleted)", "(simulaci\xF3n: no se borr\xF3 nada)"));
|
|
2761
|
+
console.log(applyHint(ctx, { needsYes: true }));
|
|
2762
|
+
});
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
requireYesIfHeadless(ctx);
|
|
2766
|
+
const eb = await getClient(ctx);
|
|
2767
|
+
const hit = await findDb(ctx, ref);
|
|
2768
|
+
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 });
|
|
2769
|
+
const r = await eb.deleteDatabase(hit.id);
|
|
2770
|
+
emit(ctx, { ...r, id: hit.id, name: hit.name }, () => console.log(t(`Deleted ${hit.name} (${hit.id})`, `Borrada ${hit.name} (${hit.id})`)));
|
|
2771
|
+
}
|
|
2772
|
+
},
|
|
2773
|
+
rename: {
|
|
2774
|
+
aliases: ["mv"],
|
|
2775
|
+
summary: "Rename a database and rewrite the agent prompts that name it",
|
|
2776
|
+
usage: "easybits db rename <db-id|name> <new-name> [--skip-prompts] [--dry-run]",
|
|
2777
|
+
options: {
|
|
2778
|
+
"skip-prompts": { type: "boolean", description: "Rename only; leave your agents' prompts as they are" },
|
|
2779
|
+
"dry-run": { type: "boolean", description: "Show the prompts that name it; change nothing" }
|
|
2780
|
+
},
|
|
2781
|
+
examples: [
|
|
2782
|
+
"easybits db rename totequim catalogo --dry-run # which prompts name it",
|
|
2783
|
+
"easybits db rename totequim catalogo",
|
|
2784
|
+
"easybits db rename totequim catalogo --skip-prompts --json"
|
|
2785
|
+
],
|
|
2786
|
+
// El id no cambia (las tools db_* van por id). Se reescribe el nombre como palabra suelta:
|
|
2787
|
+
// `totequim.com`, `@totequim`, `totequim_prueba` y «TOTEQUIM» se quedan (db-name-refs.ts).
|
|
2788
|
+
async run(ctx) {
|
|
2789
|
+
const ref = need(ctx, 0, "db-id|name", this.usage);
|
|
2790
|
+
const to = need(ctx, 1, "new-name", this.usage).trim();
|
|
2791
|
+
if (!/^[a-zA-Z0-9_-]{1,64}$/.test(to)) throw usageError(t("The name takes letters, digits, - and _ (max 64).", "El nombre lleva letras, d\xEDgitos, - y _ (m\xE1x 64)."), this.usage);
|
|
2792
|
+
const hit = await findDb(ctx, ref);
|
|
2793
|
+
const from = hit.name;
|
|
2794
|
+
if (to === from) throw usageError(t(`${from} already has that name.`, `${from} ya se llama as\xED.`), this.usage);
|
|
2795
|
+
const taken = (await listRefs(ctx, "db")).find((d) => d.name === to);
|
|
2796
|
+
if (taken) throw new CliError(t(`A database named "${to}" already exists (${taken.id}).`, `Ya hay una base "${to}" (${taken.id}).`), 1, void 0, "conflict", 409);
|
|
2797
|
+
const scan = bool(ctx, "skip-prompts") ? { found: [], skipped: [] } : await promptRefs(ctx, from);
|
|
2798
|
+
const base = { id: hit.id, from, to, references: scan.found, skipped: scan.skipped };
|
|
2799
|
+
const printRefs = () => {
|
|
2800
|
+
if (bool(ctx, "skip-prompts")) return console.log(t("Prompts not checked (--skip-prompts).", "Prompts sin revisar (--skip-prompts)."));
|
|
2801
|
+
if (!scan.found.length) console.log(t("No agent prompt names it.", "Ning\xFAn prompt de tus agentes la nombra."));
|
|
2802
|
+
else console.log(t(`Named in ${scan.found.length} prompt(s):`, `La nombran ${scan.found.length} prompt(s):`));
|
|
2803
|
+
for (const r of scan.found) {
|
|
2804
|
+
console.log(` ${r.agent} (${r.agentId})`);
|
|
2805
|
+
for (const l of r.refs.slice(0, 3)) console.log(` ${l.line}: ${l.text}`);
|
|
2806
|
+
if (r.refs.length > 3) console.log(t(` \u2026 +${r.refs.length - 3} more`, ` \u2026 +${r.refs.length - 3} m\xE1s`));
|
|
2807
|
+
}
|
|
2808
|
+
for (const x of scan.skipped) console.log(t(` ! ${x.agent} (${x.agentId}) not checked: ${x.reason}`, ` ! ${x.agent} (${x.agentId}) sin revisar: ${x.reason}`));
|
|
2809
|
+
};
|
|
2810
|
+
if (bool(ctx, "dry-run")) {
|
|
2811
|
+
emit(ctx, { ...base, dryRun: true }, () => {
|
|
2812
|
+
console.log(`${from} \u2192 ${to}`);
|
|
2813
|
+
printRefs();
|
|
2814
|
+
console.log(t("(dry run: nothing changed; without --dry-run those prompts are rewritten too)", "(simulaci\xF3n: no cambi\xF3 nada; sin --dry-run tambi\xE9n se reescriben esos prompts)"));
|
|
2815
|
+
console.log(applyHint(ctx));
|
|
2816
|
+
});
|
|
2817
|
+
return;
|
|
2818
|
+
}
|
|
2819
|
+
await api(ctx, "PATCH", `/databases/${hit.id}`, { name: to });
|
|
2820
|
+
const rewritten = [];
|
|
2821
|
+
const failed = [];
|
|
2822
|
+
for (const r of scan.found) {
|
|
2823
|
+
try {
|
|
2824
|
+
const cur = await api(ctx, "GET", `/agents/${r.agentId}/prompt`);
|
|
2825
|
+
await api(ctx, "PATCH", `/agents/${r.agentId}`, { systemPrompt: replaceName(cur.systemPrompt, from, to) });
|
|
2826
|
+
rewritten.push(r.agentId);
|
|
2827
|
+
} catch (e) {
|
|
2828
|
+
failed.push({ agentId: r.agentId, error: e.message });
|
|
2829
|
+
}
|
|
2830
|
+
}
|
|
2831
|
+
emit(ctx, { ...base, dryRun: false, rewritten, failed }, () => {
|
|
2832
|
+
console.log(t(`Renamed ${from} \u2192 ${to} (${hit.id}). ${rewritten.length} prompt(s) rewritten.`, `Renombrada ${from} \u2192 ${to} (${hit.id}). ${rewritten.length} prompt(s) reescritos.`));
|
|
2833
|
+
for (const f of failed) console.error(`! ${f.agentId}: ${f.error}`);
|
|
2834
|
+
for (const x of scan.skipped) console.log(t(`! ${x.agent} (${x.agentId}) not checked: ${x.reason}`, `! ${x.agent} (${x.agentId}) sin revisar: ${x.reason}`));
|
|
2835
|
+
console.log(`${t("Undo", "Deshacer")}: easybits db rename ${to} ${from}`);
|
|
2836
|
+
});
|
|
2837
|
+
if (failed.length) process.exitCode = 1;
|
|
2838
|
+
}
|
|
2839
|
+
},
|
|
2840
|
+
tables: {
|
|
2841
|
+
aliases: ["inspect", "schema"],
|
|
2842
|
+
summary: "List tables with row counts and columns, and how many links are permanent",
|
|
2843
|
+
usage: "easybits db tables <db-id|name>",
|
|
2844
|
+
examples: ["easybits db tables leads", "easybits db tables leads --json"],
|
|
2845
|
+
// Patrón de `turso db inspect` / `.tables` + `.schema` de sqlite3, en una sola vista.
|
|
2846
|
+
async run(ctx) {
|
|
2847
|
+
const ref = need(ctx, 0, "db-id|name", this.usage);
|
|
2848
|
+
const eb = await getClient(ctx);
|
|
2849
|
+
const hit = await findDb(ctx, ref);
|
|
2850
|
+
const q = (sql, args) => eb.queryDatabase(hit.id, sql, args);
|
|
2851
|
+
const names = (await q("SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name NOT LIKE '_litestream_%' ORDER BY name")).rows.map((r) => String(r[0]));
|
|
2852
|
+
const tables = [];
|
|
2853
|
+
for (const name of names) {
|
|
2854
|
+
const info2 = await q(`PRAGMA table_info(${ident2(name)})`);
|
|
2855
|
+
const col = (k) => info2.cols.indexOf(k);
|
|
2856
|
+
const columns = info2.rows.map((r) => ({ name: String(r[col("name")]), type: String(r[col("type")] ?? ""), pk: Number(r[col("pk")]) > 0 }));
|
|
2857
|
+
const count = await q(`SELECT count(*) FROM ${ident2(name)}`);
|
|
2858
|
+
const textCols = columns.filter((c) => !c.type || /CHAR|TEXT|CLOB/i.test(c.type)).map((c) => c.name);
|
|
2859
|
+
const links = textCols.length ? parseLinkStats(textCols, (await q(linkStatsSql(name, textCols))).rows[0] ?? []) : [];
|
|
2860
|
+
tables.push({ name, rows: Number(count.rows[0]?.[0] ?? 0), columns, links });
|
|
2861
|
+
}
|
|
2862
|
+
emit(ctx, tables, () => {
|
|
2863
|
+
table(
|
|
2864
|
+
tables.map((t2) => ({
|
|
2865
|
+
name: t2.name,
|
|
2866
|
+
rows: t2.rows,
|
|
2867
|
+
columns: t2.columns.map((c) => `${c.name}${c.type ? ` ${c.type}` : ""}${c.pk ? " PK" : ""}`).join(", ")
|
|
2868
|
+
})),
|
|
2869
|
+
[["name", "TABLE"], ["rows", "ROWS"], ["columns", "COLUMNS"]],
|
|
2870
|
+
t(`No tables in ${hit.name}. Create one: easybits db query ${hit.name} 'CREATE TABLE \u2026'`, `No hay tablas en ${hit.name}. Crea una: easybits db query ${hit.name} 'CREATE TABLE \u2026'`)
|
|
2871
|
+
);
|
|
2872
|
+
const withLinks = tables.flatMap((tb) => tb.links.map((l) => ({ ...l, table: tb.name })));
|
|
2873
|
+
if (withLinks.length) {
|
|
2874
|
+
console.log("\n" + t("Links (permanent = EasyBits public storage; expiring = signed URL):", "Ligas (permanente = almacenamiento p\xFAblico de EasyBits; caduca = URL firmada):"));
|
|
2875
|
+
table(
|
|
2876
|
+
withLinks.map((l) => ({ where: `${l.table}.${l.column}`, permanent: l.permanent, expiring: l.expiring, external: l.external, empty: l.empty })),
|
|
2877
|
+
[["where", "COLUMN"], ["permanent", "PERMANENT"], ["expiring", "EXPIRING"], ["external", "EXTERNAL"], ["empty", "EMPTY"]]
|
|
2878
|
+
);
|
|
2879
|
+
if (withLinks.some((l) => l.external + l.expiring > 0)) {
|
|
2880
|
+
console.log(t("Make them permanent: easybits db photos put <db> --table T --key-column sku --dir photos/", "Hazlas permanentes: easybits db photos put <db> --table T --key-column sku --dir fotos/"));
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
});
|
|
2884
|
+
}
|
|
2885
|
+
},
|
|
2886
|
+
photos: {
|
|
2887
|
+
summary: "Put catalog photos in a table: SKU-123.jpg \u2192 the row whose key is SKU-123, as a permanent public link",
|
|
2888
|
+
usage: "easybits db photos put <db> --table <table> --key-column <col> --dir <folder> [--column <col>] [--replace] [--dry-run]",
|
|
2889
|
+
options: {
|
|
2890
|
+
table: { type: "string", value: "table", description: "Table to update" },
|
|
2891
|
+
"key-column": { type: "string", value: "col", description: "Column that matches the file name (e.g. sku)" },
|
|
2892
|
+
dir: { type: "string", value: "folder", description: "Folder with the photos (JPEG, PNG or WebP, max 10 MB each)" },
|
|
2893
|
+
column: { type: "string", value: "col", description: "Column for the link (default: image_url, photo_url, imagen\u2026)" },
|
|
2894
|
+
replace: { type: "boolean", description: "Also replace rows that already have a permanent link" },
|
|
2895
|
+
"dry-run": { type: "boolean", description: "Show the plan; upload nothing" }
|
|
2896
|
+
},
|
|
2897
|
+
examples: [
|
|
2898
|
+
"easybits db photos put catalogo --table productos --key-column sku --dir fotos/ --dry-run",
|
|
2899
|
+
"easybits db photos put catalogo --table productos --key-column sku --dir fotos/ --column foto",
|
|
2900
|
+
"easybits db tables catalogo # how many links are permanent, external or empty"
|
|
2901
|
+
],
|
|
2902
|
+
async run(ctx) {
|
|
2903
|
+
const action = need(ctx, 0, "put", this.usage);
|
|
2904
|
+
if (action !== "put") throw usageError(t(`Unknown photos action "${action}".`, `Acci\xF3n de photos desconocida "${action}".`), this.usage);
|
|
2905
|
+
const ref = need(ctx, 1, "db", this.usage);
|
|
2906
|
+
const tableName = str(ctx, "table");
|
|
2907
|
+
const keyCol = str(ctx, "key-column");
|
|
2908
|
+
const dir = str(ctx, "dir");
|
|
2909
|
+
if (!tableName || !keyCol || !dir) throw usageError(t("Missing --table, --key-column or --dir.", "Faltan --table, --key-column o --dir."), this.usage);
|
|
2910
|
+
if (!existsSync8(dir) || !statSync5(dir).isDirectory()) throw usageError(t(`Not a folder: ${dir}`, `No es una carpeta: ${dir}`), this.usage);
|
|
2911
|
+
const { photos, rejected } = photoFiles(dir);
|
|
2912
|
+
const eb = await getClient(ctx);
|
|
2913
|
+
const hit = await findDb(ctx, ref);
|
|
2914
|
+
const q = (sql, args) => eb.queryDatabase(hit.id, sql, args);
|
|
2915
|
+
const info2 = await q(`PRAGMA table_info(${ident2(tableName)})`);
|
|
2916
|
+
const cols = info2.rows.map((r) => String(r[info2.cols.indexOf("name")]));
|
|
2917
|
+
if (!cols.length) throw new CliError(t(`No table "${tableName}" in ${hit.name}.`, `No hay tabla "${tableName}" en ${hit.name}.`), 1, t(`See: easybits db tables ${hit.name}`, `Mira: easybits db tables ${hit.name}`), "not_found", 404);
|
|
2918
|
+
if (!cols.includes(keyCol)) throw usageError(t(`No column "${keyCol}" in ${tableName} (${cols.join(", ")}).`, `No hay columna "${keyCol}" en ${tableName} (${cols.join(", ")}).`), this.usage);
|
|
2919
|
+
const column = str(ctx, "column") ?? defaultPhotoColumn(cols);
|
|
2920
|
+
if (!column) throw usageError(t(`${tableName} has no photo column: pass --column (${cols.join(", ")}).`, `${tableName} no tiene columna de foto: pasa --column (${cols.join(", ")}).`), this.usage);
|
|
2921
|
+
if (!cols.includes(column)) throw usageError(t(`No column "${column}" in ${tableName} (${cols.join(", ")}).`, `No hay columna "${column}" en ${tableName} (${cols.join(", ")}).`), this.usage);
|
|
2922
|
+
const current2 = /* @__PURE__ */ new Map();
|
|
2923
|
+
for (let i = 0; i < photos.length; i += 100) {
|
|
2924
|
+
const keys = photos.slice(i, i + 100).map((p) => p.key);
|
|
2925
|
+
const r = await q(`SELECT ${ident2(keyCol)}, ${ident2(column)} FROM ${ident2(tableName)} WHERE ${ident2(keyCol)} IN (${keys.map(() => "?").join(",")})`, keys);
|
|
2926
|
+
for (const row of r.rows) current2.set(String(row[0]), row[1]);
|
|
2927
|
+
}
|
|
2928
|
+
const plan = planPhotos(photos, current2, bool(ctx, "replace"));
|
|
2929
|
+
const big = photos.filter((p) => p.bytes > WHATSAPP_MAX_BYTES).map((p) => p.file);
|
|
2930
|
+
const summary = { db: hit.name, table: tableName, keyColumn: keyCol, column, plan, rejected, overWhatsappLimit: big };
|
|
2931
|
+
const count = (a) => plan.filter((p) => p.action === a).length;
|
|
2932
|
+
const why = (r) => r === "not_an_image" ? t("not a JPEG/PNG/WebP", "no es JPEG/PNG/WebP") : r === "too_large" ? t("over 10 MB", "m\xE1s de 10 MB") : t("empty file", "archivo vac\xEDo");
|
|
2933
|
+
const printPlan2 = () => {
|
|
2934
|
+
for (const p of plan) {
|
|
2935
|
+
const what = p.action === "update" ? t("update", "actualizar") : p.action === "keep" ? t("keep (already permanent; --replace to redo)", "se queda (ya es permanente; --replace para rehacerla)") : t("no row with that key", "no hay fila con esa llave");
|
|
2936
|
+
console.log(`${p.action === "update" ? "~" : "!"} ${p.file} \u2192 ${keyCol}=${p.key}: ${what}`);
|
|
2937
|
+
}
|
|
2938
|
+
for (const r of rejected) console.log(`! ${r.file}: ${why(r.reason)}`);
|
|
2939
|
+
if (big.length) console.log(t(`Over 5 MB (WhatsApp won't send them by link): ${big.join(", ")}`, `M\xE1s de 5 MB (WhatsApp no las manda por liga): ${big.join(", ")}`));
|
|
2940
|
+
};
|
|
2941
|
+
if (bool(ctx, "dry-run") || !count("update")) {
|
|
2942
|
+
emit(ctx, { ...summary, dryRun: bool(ctx, "dry-run") }, () => {
|
|
2943
|
+
printPlan2();
|
|
2944
|
+
console.log(count("update") ? t("(dry run: nothing uploaded)", "(simulaci\xF3n: no se subi\xF3 nada)") : t("Nothing to update.", "Nada que actualizar."));
|
|
2945
|
+
});
|
|
2946
|
+
return;
|
|
2947
|
+
}
|
|
2948
|
+
const urls = /* @__PURE__ */ new Map();
|
|
2949
|
+
const results = [];
|
|
2950
|
+
for (const item of plan.filter((p) => p.action === "update")) {
|
|
2951
|
+
const photo = photos.find((p) => p.key === item.key);
|
|
2952
|
+
try {
|
|
2953
|
+
let url = urls.get(photo.sha);
|
|
2954
|
+
if (!url) {
|
|
2955
|
+
const up = await eb.uploadFile({ fileName: `${photo.sha.slice(0, 32)}.${photo.ext}`, contentType: photo.mime, size: photo.bytes, access: "public" });
|
|
2956
|
+
const res = await fetch(up.putUrl, { method: "PUT", body: readFileSync9(photo.path), headers: { "Content-Type": photo.mime } });
|
|
2957
|
+
if (!res.ok) throw new Error(t(`storage answered ${res.status}`, `el almacenamiento contest\xF3 ${res.status}`));
|
|
2958
|
+
url = up.file.url;
|
|
2959
|
+
if (!url) throw new Error(t("the API returned no public link", "la API no devolvi\xF3 liga p\xFAblica"));
|
|
2960
|
+
urls.set(photo.sha, url);
|
|
2961
|
+
}
|
|
2962
|
+
await q(`UPDATE ${ident2(tableName)} SET ${ident2(column)} = ? WHERE ${ident2(keyCol)} = ?`, [url, item.key]);
|
|
2963
|
+
results.push({ key: item.key, file: item.file, url });
|
|
2964
|
+
if (!ctx.json) console.log(`~ ${item.file} \u2192 ${keyCol}=${item.key}: ${url}`);
|
|
2965
|
+
} catch (e) {
|
|
2966
|
+
results.push({ key: item.key, file: item.file, error: e.message });
|
|
2967
|
+
if (!ctx.json) console.error(`! ${item.file}: ${e.message}`);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
const failed = results.filter((r) => r.error).length;
|
|
2971
|
+
emit(ctx, { ...summary, results }, () => {
|
|
2972
|
+
for (const p of plan.filter((x) => x.action !== "update")) console.log(`! ${p.file} \u2192 ${keyCol}=${p.key}: ${p.action === "keep" ? t("kept", "se qued\xF3") : t("no row with that key", "no hay fila con esa llave")}`);
|
|
2973
|
+
for (const r of rejected) console.log(`! ${r.file}: ${why(r.reason)}`);
|
|
2974
|
+
console.log(t(`Updated ${results.length - failed} row(s)${failed ? `, ${failed} failed` : ""}.`, `${results.length - failed} fila(s) actualizadas${failed ? `, ${failed} fallaron` : ""}.`));
|
|
2975
|
+
});
|
|
2976
|
+
if (failed) process.exitCode = 1;
|
|
2977
|
+
}
|
|
2978
|
+
},
|
|
2979
|
+
query: {
|
|
2980
|
+
aliases: ["sql"],
|
|
2981
|
+
summary: "Run one SQL statement (by database id or name)",
|
|
2982
|
+
usage: "easybits db query <db-id|name> <sql> [--arg <value>]...",
|
|
2983
|
+
options: { arg: { type: "string", multiple: true, value: "value", description: "Positional ? parameter (repeatable)" } },
|
|
2984
|
+
examples: [
|
|
2985
|
+
"easybits db query leads 'SELECT * FROM leads LIMIT 10'",
|
|
2986
|
+
"easybits db query leads 'INSERT INTO leads(name) VALUES (?)' --arg Ana",
|
|
2987
|
+
"easybits db query leads 'SELECT count(*) AS n FROM leads' --json"
|
|
2988
|
+
],
|
|
2989
|
+
async run(ctx) {
|
|
2990
|
+
const ref = need(ctx, 0, "db-id|name", this.usage);
|
|
2991
|
+
const sql = need(ctx, 1, "sql", this.usage);
|
|
2992
|
+
const eb = await getClient(ctx);
|
|
2993
|
+
const hit = await findDb(ctx, ref);
|
|
2994
|
+
const args = list(ctx, "arg");
|
|
2995
|
+
const r = await eb.queryDatabase(hit.id, sql, args.length ? args : void 0);
|
|
2996
|
+
emit(ctx, r, () => {
|
|
2997
|
+
if (r.cols.length) {
|
|
2998
|
+
table(
|
|
2999
|
+
r.rows.map((row) => Object.fromEntries(r.cols.map((c, i) => [c, row[i]]))),
|
|
3000
|
+
r.cols.map((c) => [c, c]),
|
|
3001
|
+
"(0 rows)"
|
|
3002
|
+
);
|
|
3003
|
+
} else {
|
|
3004
|
+
console.log(
|
|
3005
|
+
t(
|
|
3006
|
+
`OK, ${r.affected_row_count} row(s) affected${r.last_insert_rowid ? `, last id ${r.last_insert_rowid}` : ""}`,
|
|
3007
|
+
`OK, ${r.affected_row_count} fila(s) afectadas${r.last_insert_rowid ? `, \xFAltimo id ${r.last_insert_rowid}` : ""}`
|
|
3008
|
+
)
|
|
3009
|
+
);
|
|
3010
|
+
}
|
|
3011
|
+
});
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
};
|
|
3016
|
+
|
|
3017
|
+
// src/commands/agents.ts
|
|
3018
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync7 } from "fs";
|
|
3019
|
+
|
|
3020
|
+
// src/commands/spec.ts
|
|
3021
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync11, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
3022
|
+
import { homedir as homedir3 } from "os";
|
|
3023
|
+
import { dirname as dirname2, join as join6, resolve as resolve2 } from "path";
|
|
3024
|
+
|
|
3025
|
+
// src/specfile.ts
|
|
3026
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10, readdirSync as readdirSync3 } from "fs";
|
|
2862
3027
|
import { join as join5, relative as relative2 } from "path";
|
|
2863
3028
|
function walkLocal(root) {
|
|
2864
3029
|
const out = [];
|
|
@@ -2990,6 +3155,7 @@ async function applyTarget(ctx, target, o) {
|
|
|
2990
3155
|
for (const s of Object.keys(uploads.skills ?? {})) console.log(`+ skill ${s}`);
|
|
2991
3156
|
for (const f of Object.keys(uploads.files ?? {})) console.log(`+ file ${f}`);
|
|
2992
3157
|
console.log(t("(dry run: nothing created)", "(simulaci\xF3n: no se cre\xF3 nada)"));
|
|
3158
|
+
console.log(applyHint(ctx));
|
|
2993
3159
|
});
|
|
2994
3160
|
return;
|
|
2995
3161
|
}
|
|
@@ -3030,6 +3196,7 @@ async function applyTarget(ctx, target, o) {
|
|
|
3030
3196
|
console.log(
|
|
3031
3197
|
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.")
|
|
3032
3198
|
);
|
|
3199
|
+
if (o.dryRun && actionable.length) console.log(applyHint(ctx, { needsYes: true }));
|
|
3033
3200
|
});
|
|
3034
3201
|
return;
|
|
3035
3202
|
}
|
|
@@ -3195,6 +3362,7 @@ var agents = {
|
|
|
3195
3362
|
for (const [k, v] of Object.entries(plan)) console.log(`${k.padEnd(12)} ${Array.isArray(v) ? v.join(", ") || "-" : v ?? "-"}`);
|
|
3196
3363
|
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."));
|
|
3197
3364
|
console.log(t("(dry run: nothing created)", "(simulaci\xF3n: no se cre\xF3 nada)"));
|
|
3365
|
+
console.log(applyHint(ctx));
|
|
3198
3366
|
});
|
|
3199
3367
|
return;
|
|
3200
3368
|
}
|
|
@@ -3212,6 +3380,10 @@ var agents = {
|
|
|
3212
3380
|
console.log(`${t("Agent: ", "Agente: ")} ${a.agentId}`);
|
|
3213
3381
|
console.log(`Sandbox: ${a.sandboxId}`);
|
|
3214
3382
|
console.log(`URL: ${a.agentUrl}`);
|
|
3383
|
+
if (prompt) {
|
|
3384
|
+
const origin = promptFile ?? (promptInline != null ? "--prompt" : `${t("from", "de")} ${source?.source.agentId ?? "export"}`);
|
|
3385
|
+
console.log(`Prompt: ${origin}, ${prompt.length.toLocaleString("en-US")} ${t("chars", "caracteres")}`);
|
|
3386
|
+
}
|
|
3215
3387
|
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)` : ""}`));
|
|
3216
3388
|
console.log(t(`Talk to it: easybits agents try ${a.agentId} "hello"`, `H\xE1blale: easybits agents try ${a.agentId} "hola"`));
|
|
3217
3389
|
});
|
|
@@ -3446,7 +3618,7 @@ import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync
|
|
|
3446
3618
|
import { homedir as homedir5 } from "os";
|
|
3447
3619
|
import { dirname as dirname4, join as join8 } from "path";
|
|
3448
3620
|
var FILE = join8(process.env.XDG_CONFIG_HOME || join8(homedir5(), ".config"), "easybits", "update.json");
|
|
3449
|
-
var
|
|
3621
|
+
var TTL = 60 * 60 * 1e3;
|
|
3450
3622
|
var UPGRADE = "npm i -g @easybits.cloud/cli@latest";
|
|
3451
3623
|
function newer(a, b) {
|
|
3452
3624
|
const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
|
|
@@ -3474,10 +3646,14 @@ async function fetchLatest(timeoutMs = 1500) {
|
|
|
3474
3646
|
return null;
|
|
3475
3647
|
}
|
|
3476
3648
|
}
|
|
3477
|
-
|
|
3649
|
+
function cacheFresh(c, current2, now = Date.now()) {
|
|
3650
|
+
if (!c.latest || !c.checkedAt || now - c.checkedAt >= TTL) return false;
|
|
3651
|
+
return !(current2 && current2 !== "dev" && newer(current2, c.latest));
|
|
3652
|
+
}
|
|
3653
|
+
async function latestVersion(current2) {
|
|
3478
3654
|
if (updateCheckDisabled()) return null;
|
|
3479
3655
|
const c = readCache();
|
|
3480
|
-
if (
|
|
3656
|
+
if (cacheFresh(c, current2)) return c.latest;
|
|
3481
3657
|
return await fetchLatest() ?? c.latest ?? null;
|
|
3482
3658
|
}
|
|
3483
3659
|
function cachedNewer(current2) {
|
|
@@ -3487,12 +3663,12 @@ function cachedNewer(current2) {
|
|
|
3487
3663
|
}
|
|
3488
3664
|
async function updateNotice(current2) {
|
|
3489
3665
|
if (current2 === "dev") return null;
|
|
3490
|
-
const latest = await latestVersion();
|
|
3666
|
+
const latest = await latestVersion(current2);
|
|
3491
3667
|
return latest && newer(latest, current2) ? t(`Update available: ${current2} \u2192 ${latest}. Run: ${UPGRADE}`, `Hay versi\xF3n nueva: ${current2} \u2192 ${latest}. Corre: ${UPGRADE}`) : null;
|
|
3492
3668
|
}
|
|
3493
3669
|
|
|
3494
3670
|
// src/commands/doctor.ts
|
|
3495
|
-
var VERSION = true ? "0.
|
|
3671
|
+
var VERSION = true ? "0.15.1" : "dev";
|
|
3496
3672
|
var doctor2 = {
|
|
3497
3673
|
name: "doctor",
|
|
3498
3674
|
group: "Account",
|
|
@@ -3762,6 +3938,12 @@ var HELP_ES = {
|
|
|
3762
3938
|
"Create a database": "Crea una base de datos",
|
|
3763
3939
|
"What it is for": "Para qu\xE9 es",
|
|
3764
3940
|
"Delete a database (irreversible)": "Borra una base de datos (irreversible)",
|
|
3941
|
+
"Show its tables and rows; delete nothing": "Ense\xF1a sus tablas y filas; no borra nada",
|
|
3942
|
+
"what would be lost": "lo que se perder\xEDa",
|
|
3943
|
+
"Rename a database and rewrite the agent prompts that name it": "Renombra una base y reescribe los prompts de agentes que la nombran",
|
|
3944
|
+
"Rename only; leave your agents' prompts as they are": "S\xF3lo renombra; deja los prompts de tus agentes como est\xE1n",
|
|
3945
|
+
"Show the prompts that name it; change nothing": "Ense\xF1a los prompts que la nombran; no cambia nada",
|
|
3946
|
+
"which prompts name it": "qu\xE9 prompts la nombran",
|
|
3765
3947
|
"asks you to type the name": "te pide teclear el nombre",
|
|
3766
3948
|
"scripts and agents": "scripts y agentes",
|
|
3767
3949
|
"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",
|
|
@@ -3990,7 +4172,7 @@ function normalizeArgs(input) {
|
|
|
3990
4172
|
}
|
|
3991
4173
|
|
|
3992
4174
|
// src/index.ts
|
|
3993
|
-
var VERSION2 = true ? "0.
|
|
4175
|
+
var VERSION2 = true ? "0.15.1" : "dev";
|
|
3994
4176
|
var GLOBAL_OPTIONS = {
|
|
3995
4177
|
json: { type: "boolean" },
|
|
3996
4178
|
token: { type: "string" },
|
package/package.json
CHANGED