@easybits.cloud/cli 0.15.0 → 0.15.2

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.
Files changed (3) hide show
  1. package/README.md +11 -0
  2. package/dist/index.js +142 -44
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -107,6 +107,8 @@ easybits files ls
107
107
  easybits files upload ./report.pdf
108
108
  easybits files rm FILE_ID --yes
109
109
  easybits websites ls
110
+ easybits websites create "My shop" --slug my-shop # slug optional
111
+ easybits websites slug my-shop my-store # change it; the old URL 301s
110
112
  easybits providers # storage provider (Tigris by default)
111
113
  easybits usage
112
114
 
@@ -168,6 +170,15 @@ Host *.ghosty
168
170
 
169
171
  Full reference: https://www.easybits.cloud/docs#cli · https://www.easybits.cloud/en/docs/cli.md
170
172
 
173
+ ## Changes in 0.15.1
174
+
175
+ - After a `--dry-run`, the last line is the real command (`To apply: …`); without a terminal it
176
+ already carries `--yes` when the command asks for confirmation.
177
+ - A 404 with a reason prints the reason; a bare «Not found» says it may also be missing access
178
+ (another account's resource) and points to `easybits whoami`.
179
+ - The «new version» notice checks npm every hour (it was once a day), and asks again when the
180
+ cache is older than what you have installed.
181
+
171
182
  ## Changes in 0.15.0
172
183
 
173
184
  - `db rename <db> <new-name> [--skip-prompts] [--dry-run]` (alias `mv`): renames the database (the
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { parseArgs } from "util";
5
5
 
6
6
  // src/commands/account.ts
7
- import { resolveBaseUrl as resolveBaseUrl3 } from "@easybits.cloud/sdk";
7
+ import { resolveBaseUrl as resolveBaseUrl4 } from "@easybits.cloud/sdk";
8
8
 
9
9
  // src/args.ts
10
10
  import { existsSync, readFileSync } from "fs";
@@ -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
  }
@@ -284,6 +303,9 @@ function maskSecret(v) {
284
303
  return v.length <= 8 ? "***" : `${v.slice(0, 4)}\u2026***`;
285
304
  }
286
305
 
306
+ // src/api.ts
307
+ import { EasybitsError as EasybitsError2, resolveBaseUrl as resolveBaseUrl3 } from "@easybits.cloud/sdk";
308
+
287
309
  // src/client.ts
288
310
  import { EasybitsClient, resolveBaseUrl as resolveBaseUrl2 } from "@easybits.cloud/sdk";
289
311
 
@@ -481,6 +503,32 @@ async function getClient(ctx) {
481
503
  return new EasybitsClient({ apiKey, baseUrl: await resolveBaseUrl2() });
482
504
  }
483
505
 
506
+ // src/api.ts
507
+ async function api(ctx, method, path, body, opts = {}) {
508
+ const token = await resolveCredential(ctx);
509
+ const base = (await resolveBaseUrl3()).replace(/\/$/, "");
510
+ const headers = { Authorization: `Bearer ${token}` };
511
+ let payload;
512
+ if (body !== void 0) {
513
+ if (opts.raw) {
514
+ payload = body;
515
+ headers["Content-Type"] = "application/octet-stream";
516
+ } else {
517
+ payload = JSON.stringify(body);
518
+ headers["Content-Type"] = "application/json";
519
+ }
520
+ }
521
+ const res = await fetch(`${base}/api/v2${path}`, { method, headers, body: payload });
522
+ const text = await res.text();
523
+ if (!res.ok) throw new EasybitsError2(res.status, text);
524
+ try {
525
+ return JSON.parse(text);
526
+ } catch {
527
+ return text;
528
+ }
529
+ }
530
+ var encodePath = (p) => p.replace(/^\/+/, "").split("/").map(encodeURIComponent).join("/");
531
+
484
532
  // src/commands/account.ts
485
533
  var login = {
486
534
  name: "login",
@@ -573,8 +621,8 @@ Webhooks: ${u.counts.webhooks}`
573
621
  var websites = {
574
622
  name: "websites",
575
623
  group: "Storage",
576
- summary: "List your static websites",
577
- synopsis: "websites",
624
+ summary: "Static websites: list, create, change slug",
625
+ synopsis: "websites <ls|create|slug>",
578
626
  subs: {
579
627
  ls: {
580
628
  aliases: ["list"],
@@ -594,10 +642,55 @@ var websites = {
594
642
  )
595
643
  );
596
644
  }
645
+ },
646
+ create: {
647
+ summary: "Create an empty static website (optionally with your own slug)",
648
+ usage: "easybits websites create <name> [--slug <slug>]",
649
+ options: { slug: { type: "string", value: "slug", description: "Public slug: /s/<slug>/ (a-z, 0-9, hyphens, 3-60 chars)" } },
650
+ examples: ['easybits websites create "Mi tienda" --slug mi-tienda'],
651
+ async run(ctx) {
652
+ const name = need(ctx, 0, "name", this.usage);
653
+ const slug = str(ctx, "slug");
654
+ const { website } = await api(ctx, "POST", "/websites", { name, ...slug ? { slug } : {} });
655
+ emit(ctx, website, () => console.log(t(`Created ${website.slug} \u2192 ${website.url}`, `Creado ${website.slug} \u2192 ${website.url}`)));
656
+ }
657
+ },
658
+ slug: {
659
+ summary: "Change a website's slug (the old URL redirects 301 to the new one)",
660
+ usage: "easybits websites slug <websiteId|slug> <new-slug>",
661
+ examples: ["easybits websites slug rio-durmiente mi-tienda"],
662
+ async run(ctx) {
663
+ const ref = need(ctx, 0, "website", this.usage);
664
+ const slug = need(ctx, 1, "new-slug", this.usage);
665
+ const websiteId = await resolveWebsiteId(ctx, ref);
666
+ const { website } = await api(ctx, "PATCH", `/websites/${websiteId}`, { slug });
667
+ emit(ctx, website, () => console.log(t(`Slug changed \u2192 ${website.url}`, `Slug cambiado \u2192 ${website.url}`)));
668
+ }
597
669
  }
598
670
  },
599
671
  defaultSub: "ls"
600
672
  };
673
+ function matchWebsite(ref, items) {
674
+ if (/^[0-9a-f]{24}$/i.test(ref)) return ref;
675
+ const r = ref.trim().toLowerCase();
676
+ return items.find((w) => w.slug === r)?.id ?? null;
677
+ }
678
+ async function resolveWebsiteId(ctx, ref) {
679
+ if (/^[0-9a-f]{24}$/i.test(ref)) return ref;
680
+ for (let offset = 0; ; offset += 100) {
681
+ const page = await api(ctx, "GET", `/websites?limit=100&offset=${offset}`);
682
+ const id = matchWebsite(ref, page.items);
683
+ if (id) return id;
684
+ if (page.items.length < 100 || offset + 100 >= page.total) break;
685
+ }
686
+ throw new CliError(
687
+ t(`No website with id or slug "${ref}".`, `No hay sitio con id o slug "${ref}".`),
688
+ EXIT.API,
689
+ `${t("List them with", "Enl\xEDstalos con")}: easybits websites ls`,
690
+ "not_found",
691
+ 404
692
+ );
693
+ }
601
694
  var providers = {
602
695
  name: "providers",
603
696
  group: "Storage",
@@ -641,7 +734,7 @@ var mcp = {
641
734
  return;
642
735
  }
643
736
  const apiKey = resolveApiKey(ctx);
644
- const baseUrl = await resolveBaseUrl3();
737
+ const baseUrl = await resolveBaseUrl4();
645
738
  const config = {
646
739
  mcpServers: {
647
740
  easybits: {
@@ -669,7 +762,7 @@ var whoami = {
669
762
  const source = process.env.EASYBITS_API_KEY ? "env EASYBITS_API_KEY" : ctx.token ? "--token" : readRc().oauth?.accessToken ? "browser session (~/.easybitsrc)" : readRc().apiKey ? "API key (~/.easybitsrc)" : void 0;
670
763
  if (!source) throw notLoggedIn();
671
764
  const email = await fetchEmail(await resolveCredential(ctx));
672
- emit(ctx, { email, source, baseUrl: await resolveBaseUrl3() }, () => console.log(`${email ?? t("(unknown email)", "(correo desconocido)")} ${t("via", "por")} ${source}`));
765
+ emit(ctx, { email, source, baseUrl: await resolveBaseUrl4() }, () => console.log(`${email ?? t("(unknown email)", "(correo desconocido)")} ${t("via", "por")} ${source}`));
673
766
  }
674
767
  }
675
768
  };
@@ -683,9 +776,18 @@ import { createInterface } from "readline/promises";
683
776
  var YES_OPTION = {
684
777
  yes: { type: "boolean", short: "y", description: "Skip the confirmation (required without a terminal or with --json)" }
685
778
  };
686
- function sameCommand() {
687
- const argv2 = process.argv.slice(2).map((a) => /^[\w@%+=:,./-]+$/.test(a) ? a : `'${a.replace(/'/g, `'\\''`)}'`);
688
- return `easybits ${argv2.join(" ")}`;
779
+ function sameCommand(argv2 = process.argv.slice(2)) {
780
+ const quoted = argv2.map((a) => /^[\w@%+=:,./-]+$/.test(a) ? a : `'${a.replace(/'/g, `'\\''`)}'`);
781
+ return `easybits ${quoted.join(" ")}`;
782
+ }
783
+ function applyCommand(argv2, opts) {
784
+ const rest = argv2.filter((a) => a !== "--dry-run");
785
+ const hasYes = rest.some((a) => a === "--yes" || a === "-y");
786
+ return `${sameCommand(rest)}${opts.needsYes && opts.headless && !hasYes ? " --yes" : ""}`;
787
+ }
788
+ function applyHint(ctx, opts = {}) {
789
+ const cmd = applyCommand(process.argv.slice(2), { headless: !interactive(ctx), needsYes: opts.needsYes });
790
+ return t(`To apply: ${cmd}`, `Para aplicarlo: ${cmd}`);
689
791
  }
690
792
  async function ask(question) {
691
793
  const rl = createInterface({ input: process.stdin, output: process.stderr });
@@ -1873,33 +1975,6 @@ function parseLinkStats(columns, row) {
1873
1975
  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);
1874
1976
  }
1875
1977
 
1876
- // src/api.ts
1877
- import { EasybitsError as EasybitsError2, resolveBaseUrl as resolveBaseUrl4 } from "@easybits.cloud/sdk";
1878
- async function api(ctx, method, path, body, opts = {}) {
1879
- const token = await resolveCredential(ctx);
1880
- const base = (await resolveBaseUrl4()).replace(/\/$/, "");
1881
- const headers = { Authorization: `Bearer ${token}` };
1882
- let payload;
1883
- if (body !== void 0) {
1884
- if (opts.raw) {
1885
- payload = body;
1886
- headers["Content-Type"] = "application/octet-stream";
1887
- } else {
1888
- payload = JSON.stringify(body);
1889
- headers["Content-Type"] = "application/json";
1890
- }
1891
- }
1892
- const res = await fetch(`${base}/api/v2${path}`, { method, headers, body: payload });
1893
- const text = await res.text();
1894
- if (!res.ok) throw new EasybitsError2(res.status, text);
1895
- try {
1896
- return JSON.parse(text);
1897
- } catch {
1898
- return text;
1899
- }
1900
- }
1901
- var encodePath = (p) => p.replace(/^\/+/, "").split("/").map(encodeURIComponent).join("/");
1902
-
1903
1978
  // src/commands/agents-config.ts
1904
1979
  import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync8, readdirSync as readdirSync2, statSync as statSync4, writeFileSync as writeFileSync4 } from "fs";
1905
1980
  import { basename as basename3, dirname, join as join4, relative, resolve } from "path";
@@ -2138,6 +2213,7 @@ var set = {
2138
2213
  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."));
2139
2214
  for (const [k, v] of Object.entries(changes)) console.log(`${k}: ${v.from} \u2192 ${v.to}`);
2140
2215
  console.log(t("(dry run: nothing changed)", "(simulaci\xF3n: no cambi\xF3 nada)"));
2216
+ console.log(applyHint(ctx));
2141
2217
  });
2142
2218
  return;
2143
2219
  }
@@ -2204,6 +2280,7 @@ var files2 = {
2204
2280
  emit(ctx, { agentId: id, dryRun: true, upload: plan }, () => {
2205
2281
  for (const p of plan) console.log(`${p.local} \u2192 ${WORK_DIR}/${p.path} (${fmtBytes(p.bytes)})`);
2206
2282
  console.log(t("(dry run: nothing uploaded)", "(simulaci\xF3n: no se subi\xF3 nada)"));
2283
+ console.log(applyHint(ctx));
2207
2284
  });
2208
2285
  return;
2209
2286
  }
@@ -2220,6 +2297,7 @@ var files2 = {
2220
2297
  if (bool(ctx, "dry-run")) {
2221
2298
  emit(ctx, { agentId: id, dryRun: true, delete: rest }, () => {
2222
2299
  for (const p of rest) console.log(`${t("would delete", "borrar\xEDa")} ${WORK_DIR}/${p}`);
2300
+ console.log(applyHint(ctx, { needsYes: true }));
2223
2301
  });
2224
2302
  return;
2225
2303
  }
@@ -2312,9 +2390,12 @@ var skills = {
2312
2390
  const assets = dir ? readSkillDir(dir) : [];
2313
2391
  const plan = { agentId: id, slug, files: ["SKILL.md", ...assets.map((a) => a.name)], restart: bool(ctx, "restart") };
2314
2392
  if (bool(ctx, "dry-run")) {
2315
- emit(ctx, { ...plan, dryRun: true }, () => console.log(t(`would add skill ${slug} (${plan.files.length} file(s))${plan.restart ? " and restart" : ""}
2393
+ emit(ctx, { ...plan, dryRun: true }, () => {
2394
+ console.log(t(`would add skill ${slug} (${plan.files.length} file(s))${plan.restart ? " and restart" : ""}
2316
2395
  (dry run: nothing changed)`, `agregar\xEDa la skill ${slug} (${plan.files.length} archivo(s))${plan.restart ? " y reiniciar\xEDa" : ""}
2317
- (simulaci\xF3n: no cambi\xF3 nada)`)));
2396
+ (simulaci\xF3n: no cambi\xF3 nada)`));
2397
+ console.log(applyHint(ctx));
2398
+ });
2318
2399
  return;
2319
2400
  }
2320
2401
  const r = await api(ctx, "PUT", `/agents/${id}/skills/${slug}`, { markdown, assets });
@@ -2327,7 +2408,10 @@ var skills = {
2327
2408
  const slugs = ctx.args.slice(2);
2328
2409
  if (!slugs.length) throw usageError(t("Give one or more slugs.", "Pasa uno o m\xE1s slugs."), SKILLS_USAGE);
2329
2410
  if (bool(ctx, "dry-run")) {
2330
- emit(ctx, { agentId: id, dryRun: true, delete: slugs }, () => slugs.forEach((s) => console.log(`${t("would remove skill", "quitar\xEDa la skill")} ${s}`)));
2411
+ emit(ctx, { agentId: id, dryRun: true, delete: slugs }, () => {
2412
+ slugs.forEach((s) => console.log(`${t("would remove skill", "quitar\xEDa la skill")} ${s}`));
2413
+ console.log(applyHint(ctx, { needsYes: true }));
2414
+ });
2331
2415
  return;
2332
2416
  }
2333
2417
  requireYesIfHeadless(ctx);
@@ -2391,6 +2475,7 @@ var mcp2 = {
2391
2475
  removed: ${diff.removed.join(", ") || "-"}
2392
2476
  changed: ${diff.changed.join(", ") || "-"}`);
2393
2477
  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)"));
2478
+ console.log(applyHint(ctx));
2394
2479
  });
2395
2480
  return;
2396
2481
  }
@@ -2720,6 +2805,7 @@ var db = {
2720
2805
  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)`));
2721
2806
  for (const x of tables) console.log(` ${x.name}: ${x.rows}`);
2722
2807
  console.log(t("(dry run: nothing deleted)", "(simulaci\xF3n: no se borr\xF3 nada)"));
2808
+ console.log(applyHint(ctx, { needsYes: true }));
2723
2809
  });
2724
2810
  return;
2725
2811
  }
@@ -2773,6 +2859,7 @@ var db = {
2773
2859
  console.log(`${from} \u2192 ${to}`);
2774
2860
  printRefs();
2775
2861
  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)"));
2862
+ console.log(applyHint(ctx));
2776
2863
  });
2777
2864
  return;
2778
2865
  }
@@ -3115,6 +3202,7 @@ async function applyTarget(ctx, target, o) {
3115
3202
  for (const s of Object.keys(uploads.skills ?? {})) console.log(`+ skill ${s}`);
3116
3203
  for (const f of Object.keys(uploads.files ?? {})) console.log(`+ file ${f}`);
3117
3204
  console.log(t("(dry run: nothing created)", "(simulaci\xF3n: no se cre\xF3 nada)"));
3205
+ console.log(applyHint(ctx));
3118
3206
  });
3119
3207
  return;
3120
3208
  }
@@ -3155,6 +3243,7 @@ async function applyTarget(ctx, target, o) {
3155
3243
  console.log(
3156
3244
  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.")
3157
3245
  );
3246
+ if (o.dryRun && actionable.length) console.log(applyHint(ctx, { needsYes: true }));
3158
3247
  });
3159
3248
  return;
3160
3249
  }
@@ -3320,6 +3409,7 @@ var agents = {
3320
3409
  for (const [k, v] of Object.entries(plan)) console.log(`${k.padEnd(12)} ${Array.isArray(v) ? v.join(", ") || "-" : v ?? "-"}`);
3321
3410
  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."));
3322
3411
  console.log(t("(dry run: nothing created)", "(simulaci\xF3n: no se cre\xF3 nada)"));
3412
+ console.log(applyHint(ctx));
3323
3413
  });
3324
3414
  return;
3325
3415
  }
@@ -3575,7 +3665,7 @@ import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync
3575
3665
  import { homedir as homedir5 } from "os";
3576
3666
  import { dirname as dirname4, join as join8 } from "path";
3577
3667
  var FILE = join8(process.env.XDG_CONFIG_HOME || join8(homedir5(), ".config"), "easybits", "update.json");
3578
- var DAY = 24 * 60 * 60 * 1e3;
3668
+ var TTL = 60 * 60 * 1e3;
3579
3669
  var UPGRADE = "npm i -g @easybits.cloud/cli@latest";
3580
3670
  function newer(a, b) {
3581
3671
  const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
@@ -3603,10 +3693,14 @@ async function fetchLatest(timeoutMs = 1500) {
3603
3693
  return null;
3604
3694
  }
3605
3695
  }
3606
- async function latestVersion() {
3696
+ function cacheFresh(c, current2, now = Date.now()) {
3697
+ if (!c.latest || !c.checkedAt || now - c.checkedAt >= TTL) return false;
3698
+ return !(current2 && current2 !== "dev" && newer(current2, c.latest));
3699
+ }
3700
+ async function latestVersion(current2) {
3607
3701
  if (updateCheckDisabled()) return null;
3608
3702
  const c = readCache();
3609
- if (c.latest && c.checkedAt && Date.now() - c.checkedAt < DAY) return c.latest;
3703
+ if (cacheFresh(c, current2)) return c.latest;
3610
3704
  return await fetchLatest() ?? c.latest ?? null;
3611
3705
  }
3612
3706
  function cachedNewer(current2) {
@@ -3616,12 +3710,12 @@ function cachedNewer(current2) {
3616
3710
  }
3617
3711
  async function updateNotice(current2) {
3618
3712
  if (current2 === "dev") return null;
3619
- const latest = await latestVersion();
3713
+ const latest = await latestVersion(current2);
3620
3714
  return latest && newer(latest, current2) ? t(`Update available: ${current2} \u2192 ${latest}. Run: ${UPGRADE}`, `Hay versi\xF3n nueva: ${current2} \u2192 ${latest}. Corre: ${UPGRADE}`) : null;
3621
3715
  }
3622
3716
 
3623
3717
  // src/commands/doctor.ts
3624
- var VERSION = true ? "0.15.0" : "dev";
3718
+ var VERSION = true ? "0.15.2" : "dev";
3625
3719
  var doctor2 = {
3626
3720
  name: "doctor",
3627
3721
  group: "Account",
@@ -3908,6 +4002,10 @@ var HELP_ES = {
3908
4002
  "Upload a local file": "Sube un archivo local",
3909
4003
  "Delete a file (soft delete, 7-day trash)": "Borra un archivo (a la papelera 7 d\xEDas)",
3910
4004
  "List your static websites": "Enlista tus sitios est\xE1ticos",
4005
+ "Static websites: list, create, change slug": "Sitios est\xE1ticos: enlistar, crear, cambiar slug",
4006
+ "Create an empty static website (optionally with your own slug)": "Crea un sitio est\xE1tico vac\xEDo (con tu propio slug si quieres)",
4007
+ "Public slug: /s/<slug>/ (a-z, 0-9, hyphens, 3-60 chars)": "Slug p\xFAblico: /s/<slug>/ (a-z, 0-9, guiones, 3-60 caracteres)",
4008
+ "Change a website's slug (the old URL redirects 301 to the new one)": "Cambia el slug de un sitio (la URL vieja redirige 301 a la nueva)",
3911
4009
  "Show storage providers": "Muestra los proveedores de almacenamiento",
3912
4010
  "MCP": "MCP",
3913
4011
  "Print the MCP config JSON for your agent or editor": "Imprime el JSON de configuraci\xF3n MCP para tu agente o editor",
@@ -4125,7 +4223,7 @@ function normalizeArgs(input) {
4125
4223
  }
4126
4224
 
4127
4225
  // src/index.ts
4128
- var VERSION2 = true ? "0.15.0" : "dev";
4226
+ var VERSION2 = true ? "0.15.2" : "dev";
4129
4227
  var GLOBAL_OPTIONS = {
4130
4228
  json: { type: "boolean" },
4131
4229
  token: { type: "string" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@easybits.cloud/cli",
3
- "version": "0.15.0",
3
+ "version": "0.15.2",
4
4
  "description": "Official CLI for EasyBits, the cloud for AI agents — sandboxes, hosting, domains, databases, agents and files from your terminal",
5
5
  "type": "module",
6
6
  "bin": {