@easybits.cloud/cli 0.13.0 → 0.14.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 +10 -0
- package/dist/index.js +161 -45
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -50,6 +50,8 @@ easybits sandboxes destroy sb_abc123 --yes
|
|
|
50
50
|
|
|
51
51
|
# Hosting: permanent machines (alias: deploy)
|
|
52
52
|
easybits machines ls
|
|
53
|
+
easybits machines launch --repo https://github.com/you/shop.git --tier micro --domain shop.example.com
|
|
54
|
+
easybits machines launch --machine shop --archive ./build.tgz --prebuilt -m v2 # redeploy
|
|
53
55
|
easybits deploy sb_abc123 -m "v1.2" # = machines deploy
|
|
54
56
|
easybits machines releases sb_abc123 --limit 5
|
|
55
57
|
easybits machines logs sb_abc123 --lines 100 --grep ERROR
|
|
@@ -164,6 +166,14 @@ Host *.ghosty
|
|
|
164
166
|
|
|
165
167
|
Full reference: https://www.easybits.cloud/docs#cli · https://www.easybits.cloud/en/docs/cli.md
|
|
166
168
|
|
|
169
|
+
## Changes in 0.14.0
|
|
170
|
+
|
|
171
|
+
- `machines launch` (aliases `create`, `new`): create a permanent machine and put an app on it in
|
|
172
|
+
one step — the CLI side of `launch_app`. Source is `--repo`, `--archive <url|file>` (a local file
|
|
173
|
+
is uploaded first) or `--machine` (redeploy onto an existing one). `--tier`, `--prebuilt`,
|
|
174
|
+
`--start`, `--port`, `--env K=V`, `--secret NAME`, `--data <path>`, `--domain`.
|
|
175
|
+
Without a plan it prints the checkout link instead of failing.
|
|
176
|
+
|
|
167
177
|
## Changes in 0.13.0
|
|
168
178
|
|
|
169
179
|
- Catalog photos: `db photos put <db> --table T --key-column sku --dir photos/ [--column C]
|
package/dist/index.js
CHANGED
|
@@ -1378,6 +1378,8 @@ var sandboxes = {
|
|
|
1378
1378
|
};
|
|
1379
1379
|
|
|
1380
1380
|
// src/commands/machines.ts
|
|
1381
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
|
|
1382
|
+
import { basename as basename2 } from "path";
|
|
1381
1383
|
var SECRETS_USAGE = "easybits machines secrets <ls|set|unset> <machine> [--dotenv <path> | NAME]";
|
|
1382
1384
|
var machines = {
|
|
1383
1385
|
name: "machines",
|
|
@@ -1405,6 +1407,103 @@ var machines = {
|
|
|
1405
1407
|
);
|
|
1406
1408
|
}
|
|
1407
1409
|
},
|
|
1410
|
+
launch: {
|
|
1411
|
+
aliases: ["create", "new"],
|
|
1412
|
+
summary: "Create a machine and put an app on it in one step (or redeploy onto an existing one)",
|
|
1413
|
+
usage: "easybits machines launch (--repo <url> | --archive <url|file> | --machine <machine>) [--tier micro] [--name <name>] [--prebuilt] [--start <cmd>] [--port 3000] [--env K=V]... [--secret NAME]... [--data <path>]... [--domain <host>]",
|
|
1414
|
+
options: {
|
|
1415
|
+
repo: { type: "string", value: "url", description: "Git repo to clone (clean URL; a token goes in --repo-token)" },
|
|
1416
|
+
branch: { type: "string", value: "name", description: "Branch to clone" },
|
|
1417
|
+
"repo-token": { type: "string", value: "$secret:NAME", description: "Token for a private repo; best as $secret:NAME from your vault" },
|
|
1418
|
+
archive: { type: "string", value: "url|file", description: "A .tar.gz/.zip of the app: URL, or a local file that gets uploaded first" },
|
|
1419
|
+
machine: { type: "string", value: "machine", description: "Deploy onto this existing machine instead of creating one" },
|
|
1420
|
+
tier: { type: "string", value: "tier", description: "Size for a new machine: nano, micro (default), estandar, focus, performance\u2026" },
|
|
1421
|
+
name: { type: "string", value: "name", description: "Human label" },
|
|
1422
|
+
"app-dir": { type: "string", value: "dir", description: "Where the app lives in the machine (default /app)" },
|
|
1423
|
+
build: { type: "string", value: "cmd", description: "Build command (default: npm ci && npm run build)" },
|
|
1424
|
+
start: { type: "string", value: "cmd", description: "Start command (default: npm start)" },
|
|
1425
|
+
port: { type: "string", value: "port", description: "Port the app listens on (default 3000)" },
|
|
1426
|
+
prebuilt: { type: "boolean", description: "The code is already built: skip the build, just start it" },
|
|
1427
|
+
env: { type: "string", multiple: true, value: "K=V", description: "Environment variable, non-secret (repeatable)" },
|
|
1428
|
+
secret: { type: "string", multiple: true, value: "NAME", description: "Vault secret the app needs, by name (repeatable)" },
|
|
1429
|
+
data: { type: "string", multiple: true, value: "path", description: "Path the nightly backup copies (repeatable). Without it nothing is backed up" },
|
|
1430
|
+
domain: { type: "string", value: "host", description: "Domain to attach; the output says which DNS record to create" },
|
|
1431
|
+
message: { type: "string", short: "m", value: "text", description: "Release note" }
|
|
1432
|
+
},
|
|
1433
|
+
examples: [
|
|
1434
|
+
"easybits machines launch --repo https://github.com/you/shop.git --tier micro --domain shop.example.com",
|
|
1435
|
+
"easybits machines launch --archive ./build.tgz --prebuilt --tier nano --name shop --start 'npm start'",
|
|
1436
|
+
"easybits machines launch --machine shop --archive ./build.tgz --prebuilt -m v2 # redeploy"
|
|
1437
|
+
],
|
|
1438
|
+
async run(ctx) {
|
|
1439
|
+
const repo = str(ctx, "repo");
|
|
1440
|
+
let archiveUrl = str(ctx, "archive");
|
|
1441
|
+
const sandboxId = str(ctx, "machine");
|
|
1442
|
+
if (!repo && !archiveUrl && !sandboxId) {
|
|
1443
|
+
throw usageError(t("Give a source: --repo, --archive or --machine.", "Pasa una fuente: --repo, --archive o --machine."), this.usage);
|
|
1444
|
+
}
|
|
1445
|
+
if (repo && archiveUrl) throw usageError(t("--repo and --archive are exclusive.", "--repo y --archive se excluyen."), this.usage);
|
|
1446
|
+
if (sandboxId && str(ctx, "tier")) {
|
|
1447
|
+
throw usageError(t("--tier is only for a new machine (resize = redeploy_machine).", "--tier s\xF3lo aplica a una m\xE1quina nueva (cambiar tama\xF1o = redeploy_machine)."), this.usage);
|
|
1448
|
+
}
|
|
1449
|
+
const eb = await getClient(ctx);
|
|
1450
|
+
if (archiveUrl && !/^https?:\/\//.test(archiveUrl)) {
|
|
1451
|
+
if (!existsSync6(archiveUrl)) throw usageError(t(`File not found: ${archiveUrl}`, `No existe el archivo: ${archiveUrl}`), this.usage);
|
|
1452
|
+
const size = statSync2(archiveUrl).size;
|
|
1453
|
+
const contentType = archiveUrl.endsWith(".zip") ? "application/zip" : "application/gzip";
|
|
1454
|
+
const up = await eb.uploadFile({ fileName: basename2(archiveUrl), contentType, size, access: "public" });
|
|
1455
|
+
const res = await fetch(up.putUrl, { method: "PUT", body: readFileSync6(archiveUrl), headers: { "Content-Type": contentType } });
|
|
1456
|
+
if (!res.ok) throw new CliError(t(`Upload failed: storage answered ${res.status}`, `Fall\xF3 la subida: el almacenamiento contest\xF3 ${res.status}`), 1, void 0, "upload_failed", res.status);
|
|
1457
|
+
archiveUrl = up.file.url;
|
|
1458
|
+
if (!archiveUrl) throw new CliError(t("The upload returned no public URL.", "La subida no devolvi\xF3 una URL p\xFAblica."), 1, void 0, "upload_failed");
|
|
1459
|
+
}
|
|
1460
|
+
const env = pairs(list(ctx, "env"), this.usage);
|
|
1461
|
+
const secretNames = list(ctx, "secret");
|
|
1462
|
+
const dataPaths = list(ctx, "data");
|
|
1463
|
+
const r = await eb.machines.launch({
|
|
1464
|
+
repo,
|
|
1465
|
+
branch: str(ctx, "branch"),
|
|
1466
|
+
repoToken: str(ctx, "repo-token"),
|
|
1467
|
+
archiveUrl,
|
|
1468
|
+
sandboxId,
|
|
1469
|
+
tier: str(ctx, "tier"),
|
|
1470
|
+
name: str(ctx, "name"),
|
|
1471
|
+
appDir: str(ctx, "app-dir"),
|
|
1472
|
+
buildCommand: str(ctx, "build"),
|
|
1473
|
+
startCommand: str(ctx, "start"),
|
|
1474
|
+
port: int(ctx, "port", this.usage),
|
|
1475
|
+
prebuilt: bool(ctx, "prebuilt") || void 0,
|
|
1476
|
+
env: Object.keys(env).length ? env : void 0,
|
|
1477
|
+
secretNames: secretNames.length ? secretNames : void 0,
|
|
1478
|
+
dataPaths: dataPaths.length ? dataPaths : void 0,
|
|
1479
|
+
domain: str(ctx, "domain"),
|
|
1480
|
+
message: str(ctx, "message")
|
|
1481
|
+
});
|
|
1482
|
+
if (r.checkoutUrl) {
|
|
1483
|
+
emit(ctx, r, () => {
|
|
1484
|
+
console.log(t("Your account has no plan: pay the machine here and it is created by itself:", "Tu cuenta no tiene plan: paga la m\xE1quina aqu\xED y se crea sola:"));
|
|
1485
|
+
console.log(` ${r.checkoutUrl}`);
|
|
1486
|
+
console.log(t("Then deploy onto it: easybits machines launch --machine <id> \u2026", "Luego despliega encima: easybits machines launch --machine <id> \u2026"));
|
|
1487
|
+
});
|
|
1488
|
+
return;
|
|
1489
|
+
}
|
|
1490
|
+
if (r.exitCode !== 0) {
|
|
1491
|
+
throw new CliError(
|
|
1492
|
+
t(`Launch failed (exit ${r.exitCode}) on ${r.sandboxId}`, `Fall\xF3 el lanzamiento (exit ${r.exitCode}) en ${r.sandboxId}`),
|
|
1493
|
+
1,
|
|
1494
|
+
r.buildOutput?.slice(-2e3),
|
|
1495
|
+
"launch_failed"
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
emit(ctx, r, () => {
|
|
1499
|
+
console.log(`${r.sandboxId} v${r.version} ${r.url}`);
|
|
1500
|
+
if (r.domain) {
|
|
1501
|
+
console.log(`${t("Domain", "Dominio")}: ${r.domain.domain} \u2192 ${r.domain.url}`);
|
|
1502
|
+
console.log(`DNS: ${JSON.stringify(r.domain.dns)}`);
|
|
1503
|
+
}
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
},
|
|
1408
1507
|
deploy: {
|
|
1409
1508
|
summary: "Publish the machine's current app code as a new release",
|
|
1410
1509
|
usage: "easybits machines deploy <machine> [--message <text>]",
|
|
@@ -1576,11 +1675,11 @@ var domains = {
|
|
|
1576
1675
|
};
|
|
1577
1676
|
|
|
1578
1677
|
// src/commands/db.ts
|
|
1579
|
-
import { existsSync as
|
|
1678
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
|
|
1580
1679
|
|
|
1581
1680
|
// src/photos.ts
|
|
1582
1681
|
import { createHash as createHash2 } from "crypto";
|
|
1583
|
-
import { readFileSync as
|
|
1682
|
+
import { readFileSync as readFileSync7, readdirSync, statSync as statSync3 } from "fs";
|
|
1584
1683
|
import { extname as extname2, join as join3 } from "path";
|
|
1585
1684
|
var PHOTO_MAX_BYTES = 10 * 1024 * 1024;
|
|
1586
1685
|
var WHATSAPP_MAX_BYTES = 5 * 1024 * 1024;
|
|
@@ -1600,7 +1699,7 @@ function photoFiles(dir) {
|
|
|
1600
1699
|
for (const name of readdirSync(dir).sort()) {
|
|
1601
1700
|
if (name.startsWith(".")) continue;
|
|
1602
1701
|
const path = join3(dir, name);
|
|
1603
|
-
const st =
|
|
1702
|
+
const st = statSync3(path);
|
|
1604
1703
|
if (!st.isFile()) continue;
|
|
1605
1704
|
if (st.size === 0) {
|
|
1606
1705
|
rejected.push({ file: name, reason: "empty" });
|
|
@@ -1610,7 +1709,7 @@ function photoFiles(dir) {
|
|
|
1610
1709
|
rejected.push({ file: name, reason: "too_large" });
|
|
1611
1710
|
continue;
|
|
1612
1711
|
}
|
|
1613
|
-
const bytes =
|
|
1712
|
+
const bytes = readFileSync7(path);
|
|
1614
1713
|
const type = imageTypeOf(bytes);
|
|
1615
1714
|
if (!type) {
|
|
1616
1715
|
rejected.push({ file: name, reason: "not_an_image" });
|
|
@@ -1901,7 +2000,7 @@ var db = {
|
|
|
1901
2000
|
const keyCol = str(ctx, "key-column");
|
|
1902
2001
|
const dir = str(ctx, "dir");
|
|
1903
2002
|
if (!tableName || !keyCol || !dir) throw usageError(t("Missing --table, --key-column or --dir.", "Faltan --table, --key-column o --dir."), this.usage);
|
|
1904
|
-
if (!
|
|
2003
|
+
if (!existsSync7(dir) || !statSync4(dir).isDirectory()) throw usageError(t(`Not a folder: ${dir}`, `No es una carpeta: ${dir}`), this.usage);
|
|
1905
2004
|
const { photos, rejected } = photoFiles(dir);
|
|
1906
2005
|
const eb = await getClient(ctx);
|
|
1907
2006
|
const hit = await findDb(ctx, ref);
|
|
@@ -1948,7 +2047,7 @@ var db = {
|
|
|
1948
2047
|
let url = urls.get(photo.sha);
|
|
1949
2048
|
if (!url) {
|
|
1950
2049
|
const up = await eb.uploadFile({ fileName: `${photo.sha.slice(0, 32)}.${photo.ext}`, contentType: photo.mime, size: photo.bytes, access: "public" });
|
|
1951
|
-
const res = await fetch(up.putUrl, { method: "PUT", body:
|
|
2050
|
+
const res = await fetch(up.putUrl, { method: "PUT", body: readFileSync8(photo.path), headers: { "Content-Type": photo.mime } });
|
|
1952
2051
|
if (!res.ok) throw new Error(t(`storage answered ${res.status}`, `el almacenamiento contest\xF3 ${res.status}`));
|
|
1953
2052
|
url = up.file.url;
|
|
1954
2053
|
if (!url) throw new Error(t("the API returned no public link", "la API no devolvi\xF3 liga p\xFAblica"));
|
|
@@ -2010,7 +2109,7 @@ var db = {
|
|
|
2010
2109
|
};
|
|
2011
2110
|
|
|
2012
2111
|
// src/commands/agents.ts
|
|
2013
|
-
import { existsSync as
|
|
2112
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync7 } from "fs";
|
|
2014
2113
|
|
|
2015
2114
|
// src/api.ts
|
|
2016
2115
|
import { EasybitsError as EasybitsError2, resolveBaseUrl as resolveBaseUrl4 } from "@easybits.cloud/sdk";
|
|
@@ -2040,8 +2139,8 @@ async function api(ctx, method, path, body, opts = {}) {
|
|
|
2040
2139
|
var encodePath = (p) => p.replace(/^\/+/, "").split("/").map(encodeURIComponent).join("/");
|
|
2041
2140
|
|
|
2042
2141
|
// src/commands/agents-config.ts
|
|
2043
|
-
import { existsSync as
|
|
2044
|
-
import { basename as
|
|
2142
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync9, readdirSync as readdirSync2, statSync as statSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
2143
|
+
import { basename as basename3, dirname, join as join4, relative, resolve } from "path";
|
|
2045
2144
|
var WORK_DIR = "/data/work";
|
|
2046
2145
|
var MACHINE_TEMPLATES = /* @__PURE__ */ new Set(["ghosty-lite", "goose"]);
|
|
2047
2146
|
var SKILLS_DIR = "/data/agent/skills";
|
|
@@ -2120,8 +2219,8 @@ function readLocalText(path, usage2) {
|
|
|
2120
2219
|
if (process.stdin.isTTY) throw usageError(t("Expected the content on stdin.", "Se esperaba el contenido por stdin."), usage2);
|
|
2121
2220
|
return readStdin().then((b) => b.toString("utf8"));
|
|
2122
2221
|
}
|
|
2123
|
-
if (!
|
|
2124
|
-
return
|
|
2222
|
+
if (!existsSync8(path)) throw usageError(t(`File not found: ${path}`, `No existe el archivo: ${path}`), usage2);
|
|
2223
|
+
return readFileSync9(path, "utf8");
|
|
2125
2224
|
}
|
|
2126
2225
|
function parseMcpFile(text, usage2) {
|
|
2127
2226
|
let j;
|
|
@@ -2336,8 +2435,8 @@ var files2 = {
|
|
|
2336
2435
|
if (!rest.length) throw usageError(t("Give one or more local files.", "Pasa uno o m\xE1s archivos locales."), FILES_USAGE2);
|
|
2337
2436
|
const to = (str(ctx, "to") ?? "").replace(/^\/+|\/+$/g, "");
|
|
2338
2437
|
const plan = rest.map((local) => {
|
|
2339
|
-
if (!
|
|
2340
|
-
return { local, path: to ? `${to}/${
|
|
2438
|
+
if (!existsSync8(local) || !statSync5(local).isFile()) throw usageError(t(`File not found: ${local}`, `No existe el archivo: ${local}`), FILES_USAGE2);
|
|
2439
|
+
return { local, path: to ? `${to}/${basename3(local)}` : basename3(local), bytes: statSync5(local).size };
|
|
2341
2440
|
});
|
|
2342
2441
|
if (bool(ctx, "dry-run")) {
|
|
2343
2442
|
emit(ctx, { agentId: id, dryRun: true, upload: plan }, () => {
|
|
@@ -2348,7 +2447,7 @@ var files2 = {
|
|
|
2348
2447
|
}
|
|
2349
2448
|
const done = [];
|
|
2350
2449
|
for (const p of plan) {
|
|
2351
|
-
const r = await api(ctx, "PUT", `/agents/${id}/files/${encodePath(p.path)}`,
|
|
2450
|
+
const r = await api(ctx, "PUT", `/agents/${id}/files/${encodePath(p.path)}`, readFileSync9(p.local), { raw: true });
|
|
2352
2451
|
done.push({ local: p.local, ...r });
|
|
2353
2452
|
if (!ctx.json) console.log(`${t("Uploaded", "Subido")} ${p.local} \u2192 ${WORK_DIR}/${p.path} (${fmtBytes(p.bytes)})`);
|
|
2354
2453
|
}
|
|
@@ -2378,7 +2477,7 @@ function readSkillDir(dir) {
|
|
|
2378
2477
|
if (e.name.startsWith(".") || e.name === "node_modules") continue;
|
|
2379
2478
|
const p = join4(d, e.name);
|
|
2380
2479
|
if (e.isDirectory()) visit(p);
|
|
2381
|
-
else if (e.isFile() && relative(dir, p) !== "SKILL.md") assets.push({ name: relative(dir, p).split("\\").join("/"), contentBase64:
|
|
2480
|
+
else if (e.isFile() && relative(dir, p) !== "SKILL.md") assets.push({ name: relative(dir, p).split("\\").join("/"), contentBase64: readFileSync9(p).toString("base64") });
|
|
2382
2481
|
}
|
|
2383
2482
|
};
|
|
2384
2483
|
visit(dir);
|
|
@@ -2442,11 +2541,11 @@ var skills = {
|
|
|
2442
2541
|
const dir = str(ctx, "dir");
|
|
2443
2542
|
if (!!file === !!dir) throw usageError(t("Pass --file SKILL.md or --dir <folder>.", "Pasa --file SKILL.md o --dir <carpeta>."), SKILLS_USAGE);
|
|
2444
2543
|
const mdPath = file ?? join4(dir, "SKILL.md");
|
|
2445
|
-
if (!
|
|
2446
|
-
const markdown =
|
|
2544
|
+
if (!existsSync8(mdPath)) throw usageError(t(`File not found: ${mdPath}`, `No existe el archivo: ${mdPath}`), SKILLS_USAGE);
|
|
2545
|
+
const markdown = readFileSync9(mdPath, "utf8");
|
|
2447
2546
|
const fm = skillFrontmatter(markdown);
|
|
2448
2547
|
if (fm.error) throw usageError(`${mdPath}: ${fm.error}`, SKILLS_USAGE);
|
|
2449
|
-
const slug = str(ctx, "slug") ?? (fm.name && SLUG_RE.test(fm.name) ? fm.name :
|
|
2548
|
+
const slug = str(ctx, "slug") ?? (fm.name && SLUG_RE.test(fm.name) ? fm.name : basename3(resolve(dir ?? dirname(mdPath))).toLowerCase());
|
|
2450
2549
|
if (!SLUG_RE.test(slug)) throw usageError(t(`Invalid slug "${slug}" ([a-z0-9-], max 64). Pass --slug.`, `Slug inv\xE1lido "${slug}" ([a-z0-9-], m\xE1x 64). Pasa --slug.`), SKILLS_USAGE);
|
|
2451
2550
|
const assets = dir ? readSkillDir(dir) : [];
|
|
2452
2551
|
const plan = { agentId: id, slug, files: ["SKILL.md", ...assets.map((a) => a.name)], restart: bool(ctx, "restart") };
|
|
@@ -2703,10 +2802,10 @@ var exportAgent = {
|
|
|
2703
2802
|
}
|
|
2704
2803
|
};
|
|
2705
2804
|
function readExport(path, usage2) {
|
|
2706
|
-
if (!
|
|
2805
|
+
if (!existsSync8(path)) throw usageError(t(`File not found: ${path}`, `No existe el archivo: ${path}`), usage2);
|
|
2707
2806
|
let x;
|
|
2708
2807
|
try {
|
|
2709
|
-
x = JSON.parse(
|
|
2808
|
+
x = JSON.parse(readFileSync9(path, "utf8"));
|
|
2710
2809
|
} catch (e) {
|
|
2711
2810
|
throw usageError(t(`${path} is not JSON: ${e.message}`, `${path} no es JSON: ${e.message}`), usage2);
|
|
2712
2811
|
}
|
|
@@ -2751,12 +2850,12 @@ async function applyExport(ctx, id, x, opts) {
|
|
|
2751
2850
|
}
|
|
2752
2851
|
|
|
2753
2852
|
// src/commands/spec.ts
|
|
2754
|
-
import { existsSync as
|
|
2853
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync11, statSync as statSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
2755
2854
|
import { homedir as homedir3 } from "os";
|
|
2756
2855
|
import { dirname as dirname2, join as join6, resolve as resolve2 } from "path";
|
|
2757
2856
|
|
|
2758
2857
|
// src/specfile.ts
|
|
2759
|
-
import { existsSync as
|
|
2858
|
+
import { existsSync as existsSync9, readFileSync as readFileSync10, readdirSync as readdirSync3 } from "fs";
|
|
2760
2859
|
import { join as join5, relative as relative2 } from "path";
|
|
2761
2860
|
function walkLocal(root) {
|
|
2762
2861
|
const out = [];
|
|
@@ -2768,20 +2867,20 @@ function walkLocal(root) {
|
|
|
2768
2867
|
else if (e.isFile()) out.push(relative2(root, p).split("\\").join("/"));
|
|
2769
2868
|
}
|
|
2770
2869
|
};
|
|
2771
|
-
if (
|
|
2870
|
+
if (existsSync9(root)) visit(root);
|
|
2772
2871
|
return out;
|
|
2773
2872
|
}
|
|
2774
2873
|
function readUploads(dir) {
|
|
2775
2874
|
const skills2 = {};
|
|
2776
2875
|
const skillsRoot = join5(dir, "skills");
|
|
2777
|
-
if (
|
|
2876
|
+
if (existsSync9(skillsRoot)) {
|
|
2778
2877
|
for (const e of readdirSync3(skillsRoot, { withFileTypes: true })) {
|
|
2779
2878
|
if (!e.isDirectory() || e.name.startsWith(".")) continue;
|
|
2780
|
-
skills2[e.name] = walkLocal(join5(skillsRoot, e.name)).map((path) => ({ path, contentBase64:
|
|
2879
|
+
skills2[e.name] = walkLocal(join5(skillsRoot, e.name)).map((path) => ({ path, contentBase64: readFileSync10(join5(skillsRoot, e.name, path)).toString("base64") }));
|
|
2781
2880
|
}
|
|
2782
2881
|
}
|
|
2783
2882
|
const filesRoot = join5(dir, "files");
|
|
2784
|
-
const files3 = Object.fromEntries(walkLocal(filesRoot).map((p) => [p,
|
|
2883
|
+
const files3 = Object.fromEntries(walkLocal(filesRoot).map((p) => [p, readFileSync10(join5(filesRoot, p)).toString("base64")]));
|
|
2785
2884
|
return { ...Object.keys(skills2).length ? { skills: skills2 } : {}, ...Object.keys(files3).length ? { files: files3 } : {} };
|
|
2786
2885
|
}
|
|
2787
2886
|
function secretsFor(text, env = process.env) {
|
|
@@ -2857,13 +2956,13 @@ ${t("Edit it, then", "Ed\xEDtalo y luego")}: easybits apply ${out} --dry-run`
|
|
|
2857
2956
|
}
|
|
2858
2957
|
};
|
|
2859
2958
|
function readTarget(target, usage2) {
|
|
2860
|
-
if (!
|
|
2861
|
-
if (
|
|
2862
|
-
const name = SPEC_NAMES.find((n) =>
|
|
2959
|
+
if (!existsSync10(target)) throw usageError(t(`Not found: ${target}`, `No existe: ${target}`), usage2);
|
|
2960
|
+
if (statSync6(target).isDirectory()) {
|
|
2961
|
+
const name = SPEC_NAMES.find((n) => existsSync10(join6(target, n)));
|
|
2863
2962
|
if (!name) throw usageError(t(`${target} has no agent.yaml.`, `${target} no tiene agent.yaml.`), usage2);
|
|
2864
|
-
return { text:
|
|
2963
|
+
return { text: readFileSync11(join6(target, name), "utf8"), dir: target };
|
|
2865
2964
|
}
|
|
2866
|
-
return { text:
|
|
2965
|
+
return { text: readFileSync11(target, "utf8") };
|
|
2867
2966
|
}
|
|
2868
2967
|
var line = (c) => `${c.op} ${c.what}`;
|
|
2869
2968
|
var printPlan = (plan) => console.log(plan.map(line).join("\n"));
|
|
@@ -2984,7 +3083,7 @@ var apply = {
|
|
|
2984
3083
|
// src/commands/agents.ts
|
|
2985
3084
|
function isLegacyExport(path) {
|
|
2986
3085
|
try {
|
|
2987
|
-
return
|
|
3086
|
+
return existsSync11(path) && !statSync7(path).isDirectory() && /"kind"\s*:\s*"easybits\.agent"/.test(readFileSync12(path, "utf8"));
|
|
2988
3087
|
} catch {
|
|
2989
3088
|
return false;
|
|
2990
3089
|
}
|
|
@@ -3051,9 +3150,9 @@ var agents = {
|
|
|
3051
3150
|
if (promptInline != null && promptFile) throw usageError(t("Use --prompt or --prompt-file, not both.", "Usa --prompt o --prompt-file, no los dos."), this.usage);
|
|
3052
3151
|
const mode = str(ctx, "prompt-mode");
|
|
3053
3152
|
if (mode && mode !== "append" && mode !== "replace") throw usageError(t("--prompt-mode must be append or replace.", "--prompt-mode debe ser append o replace."), this.usage);
|
|
3054
|
-
if (promptFile && !
|
|
3153
|
+
if (promptFile && !existsSync11(promptFile)) throw usageError(t(`File not found: ${promptFile}`, `No existe el archivo: ${promptFile}`), this.usage);
|
|
3055
3154
|
const mcpFile = str(ctx, "mcp-file");
|
|
3056
|
-
if (mcpFile && !
|
|
3155
|
+
if (mcpFile && !existsSync11(mcpFile)) throw usageError(t(`File not found: ${mcpFile}`, `No existe el archivo: ${mcpFile}`), this.usage);
|
|
3057
3156
|
if (bool(ctx, "copy-files") && !like && !from) throw usageError(t("--copy-files needs --like or --from.", "--copy-files necesita --like o --from."), this.usage);
|
|
3058
3157
|
if (from && !isLegacyExport(from)) {
|
|
3059
3158
|
await applyTarget(ctx, from, { create: true, name: str(ctx, "name"), dryRun: bool(ctx, "dry-run"), usage: this.usage });
|
|
@@ -3063,7 +3162,7 @@ var agents = {
|
|
|
3063
3162
|
const template = str(ctx, "template") ?? source?.template;
|
|
3064
3163
|
if (!template) throw usageError(t("Missing --template (or --like / --from).", "Falta --template (o --like / --from)."), this.usage);
|
|
3065
3164
|
const env = await envFrom(ctx, this.usage);
|
|
3066
|
-
const prompt = promptInline ?? (promptFile ?
|
|
3165
|
+
const prompt = promptInline ?? (promptFile ? readFileSync12(promptFile, "utf8") : source?.systemPrompt);
|
|
3067
3166
|
const promptMode = mode ?? source?.systemPromptMode;
|
|
3068
3167
|
const multiline = !!prompt && /[\r\n]/.test(prompt.trimEnd());
|
|
3069
3168
|
const promptAfter = multiline ? prompt : void 0;
|
|
@@ -3072,7 +3171,7 @@ var agents = {
|
|
|
3072
3171
|
}
|
|
3073
3172
|
if (prompt && !multiline) env.SYSTEM_PROMPT = prompt.trimEnd();
|
|
3074
3173
|
if (promptMode && (prompt || mode) && !multiline) env.SYSTEM_PROMPT_MODE = promptMode;
|
|
3075
|
-
const mcpServers = mcpFile ? parseMcpFile(
|
|
3174
|
+
const mcpServers = mcpFile ? parseMcpFile(readFileSync12(mcpFile, "utf8"), this.usage) : source?.mcpServers;
|
|
3076
3175
|
if (mcpServers && hasMasked(mcpServers)) {
|
|
3077
3176
|
throw usageError(t(`${from ?? "the source"} has masked MCP values (\u2026***).`, `${from ?? "la fuente"} trae valores del MCP tapados (\u2026***).`), t("Re-export with --show-secrets, or pass --mcp-file", "Vuelve a exportar con --show-secrets, o pasa --mcp-file"));
|
|
3078
3177
|
}
|
|
@@ -3164,7 +3263,7 @@ var agents = {
|
|
|
3164
3263
|
};
|
|
3165
3264
|
|
|
3166
3265
|
// src/commands/completion.ts
|
|
3167
|
-
import { mkdirSync as mkdirSync5, readFileSync as
|
|
3266
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
3168
3267
|
import { homedir as homedir4 } from "os";
|
|
3169
3268
|
import { dirname as dirname3, join as join7 } from "path";
|
|
3170
3269
|
var GLOBAL_FLAGS = ["--json", "--token", "--lang", "--help", "--version"];
|
|
@@ -3207,7 +3306,7 @@ var cacheFile = () => join7(process.env.XDG_CACHE_HOME || join7(homedir4(), ".ca
|
|
|
3207
3306
|
async function namesOf(ctx, kind) {
|
|
3208
3307
|
let cache2 = {};
|
|
3209
3308
|
try {
|
|
3210
|
-
cache2 = JSON.parse(
|
|
3309
|
+
cache2 = JSON.parse(readFileSync13(cacheFile(), "utf8"));
|
|
3211
3310
|
const c = cache2[kind];
|
|
3212
3311
|
if (c && Date.now() - c.at < 5 * 6e4) return c.names;
|
|
3213
3312
|
} catch {
|
|
@@ -3334,13 +3433,13 @@ var docs = {
|
|
|
3334
3433
|
};
|
|
3335
3434
|
|
|
3336
3435
|
// src/commands/doctor.ts
|
|
3337
|
-
import { existsSync as
|
|
3436
|
+
import { existsSync as existsSync12, statSync as statSync8 } from "fs";
|
|
3338
3437
|
import { homedir as homedir6 } from "os";
|
|
3339
3438
|
import { join as join9 } from "path";
|
|
3340
3439
|
import { resolveBaseUrl as resolveBaseUrl6 } from "@easybits.cloud/sdk";
|
|
3341
3440
|
|
|
3342
3441
|
// src/update.ts
|
|
3343
|
-
import { mkdirSync as mkdirSync6, readFileSync as
|
|
3442
|
+
import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
3344
3443
|
import { homedir as homedir5 } from "os";
|
|
3345
3444
|
import { dirname as dirname4, join as join8 } from "path";
|
|
3346
3445
|
var FILE = join8(process.env.XDG_CONFIG_HOME || join8(homedir5(), ".config"), "easybits", "update.json");
|
|
@@ -3355,7 +3454,7 @@ function newer(a, b) {
|
|
|
3355
3454
|
var updateCheckDisabled = () => Boolean(process.env.EASYBITS_NO_UPDATE_CHECK || process.env.CI);
|
|
3356
3455
|
function readCache() {
|
|
3357
3456
|
try {
|
|
3358
|
-
return JSON.parse(
|
|
3457
|
+
return JSON.parse(readFileSync14(FILE, "utf8"));
|
|
3359
3458
|
} catch {
|
|
3360
3459
|
return {};
|
|
3361
3460
|
}
|
|
@@ -3390,7 +3489,7 @@ async function updateNotice(current2) {
|
|
|
3390
3489
|
}
|
|
3391
3490
|
|
|
3392
3491
|
// src/commands/doctor.ts
|
|
3393
|
-
var VERSION = true ? "0.
|
|
3492
|
+
var VERSION = true ? "0.14.0" : "dev";
|
|
3394
3493
|
var doctor2 = {
|
|
3395
3494
|
name: "doctor",
|
|
3396
3495
|
group: "Account",
|
|
@@ -3434,8 +3533,8 @@ var doctor2 = {
|
|
|
3434
3533
|
}
|
|
3435
3534
|
}
|
|
3436
3535
|
const rcPath = join9(homedir6(), ".easybitsrc");
|
|
3437
|
-
if (
|
|
3438
|
-
const mode =
|
|
3536
|
+
if (existsSync12(rcPath)) {
|
|
3537
|
+
const mode = statSync8(rcPath).mode & 511;
|
|
3439
3538
|
const ok2 = (mode & 63) === 0;
|
|
3440
3539
|
checks.push({ check: "rc-file", ok: ok2, detail: `${rcPath} ${mode.toString(8).padStart(3, "0")}`, ...ok2 ? {} : { hint: t(`It holds tokens. Run: chmod 600 ${rcPath}`, `Guarda tokens. Corre: chmod 600 ${rcPath}`) } });
|
|
3441
3540
|
}
|
|
@@ -3620,6 +3719,23 @@ var HELP_ES = {
|
|
|
3620
3719
|
"List your permanent machines": "Enlista tus m\xE1quinas permanentes",
|
|
3621
3720
|
"Publish the machine's current app code as a new release": "Publica el c\xF3digo actual de la app como un release nuevo",
|
|
3622
3721
|
"Release note": "Nota del release",
|
|
3722
|
+
"Create a machine and put an app on it in one step (or redeploy onto an existing one)": "Crea una m\xE1quina y le pone la app en un paso (o redespliega sobre una existente)",
|
|
3723
|
+
"Git repo to clone (clean URL; a token goes in --repo-token)": "Repo git a clonar (URL limpia; el token va en --repo-token)",
|
|
3724
|
+
"Branch to clone": "Rama a clonar",
|
|
3725
|
+
"Token for a private repo; best as $secret:NAME from your vault": "Token de un repo privado; mejor como $secret:NOMBRE de tu vault",
|
|
3726
|
+
"A .tar.gz/.zip of the app: URL, or a local file that gets uploaded first": "Un .tar.gz/.zip de la app: URL, o un archivo local que se sube primero",
|
|
3727
|
+
"Deploy onto this existing machine instead of creating one": "Despliega sobre esta m\xE1quina existente en vez de crear una",
|
|
3728
|
+
"Size for a new machine: nano, micro (default), estandar, focus, performance\u2026": "Tama\xF1o de una m\xE1quina nueva: nano, micro (default), estandar, focus, performance\u2026",
|
|
3729
|
+
"Human label": "Nombre para personas",
|
|
3730
|
+
"Where the app lives in the machine (default /app)": "D\xF3nde vive la app en la m\xE1quina (default /app)",
|
|
3731
|
+
"Build command (default: npm ci && npm run build)": "Comando de build (default: npm ci && npm run build)",
|
|
3732
|
+
"Start command (default: npm start)": "Comando de arranque (default: npm start)",
|
|
3733
|
+
"Port the app listens on (default 3000)": "Puerto en el que escucha la app (default 3000)",
|
|
3734
|
+
"The code is already built: skip the build, just start it": "El c\xF3digo ya viene compilado: sin build, s\xF3lo arranca",
|
|
3735
|
+
"Vault secret the app needs, by name (repeatable)": "Secreto del vault que necesita la app, por nombre (repetible)",
|
|
3736
|
+
"Path the nightly backup copies (repeatable). Without it nothing is backed up": "Ruta que copia el respaldo nocturno (repetible). Sin ella no se respalda nada",
|
|
3737
|
+
"Domain to attach; the output says which DNS record to create": "Dominio a pegar; la salida dice qu\xE9 registro DNS crear",
|
|
3738
|
+
"redeploy": "redespliegue",
|
|
3623
3739
|
"same thing, shorter": "lo mismo, m\xE1s corto",
|
|
3624
3740
|
"List releases, newest first": "Enlista los releases, el m\xE1s nuevo primero",
|
|
3625
3741
|
"How many": "Cu\xE1ntos",
|
|
@@ -3871,7 +3987,7 @@ function normalizeArgs(input) {
|
|
|
3871
3987
|
}
|
|
3872
3988
|
|
|
3873
3989
|
// src/index.ts
|
|
3874
|
-
var VERSION2 = true ? "0.
|
|
3990
|
+
var VERSION2 = true ? "0.14.0" : "dev";
|
|
3875
3991
|
var GLOBAL_OPTIONS = {
|
|
3876
3992
|
json: { type: "boolean" },
|
|
3877
3993
|
token: { type: "string" },
|
package/package.json
CHANGED