@ghostystudio/cli 0.48.0 → 0.49.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.
Files changed (3) hide show
  1. package/README.md +7 -2
  2. package/dist/index.js +170 -51
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -47,7 +47,7 @@ ghosty files ls|get|put|rm <id> [path] [--file LOCAL] [--out LOCAL]
47
47
  ghosty skills ls|get|add|rm <id> [slug] [--file SKILL.md | --dir FOLDER] [--out DIR] # get without --out only lists
48
48
  ghosty skills enable|disable <id> <slug> # turn one of its skills (or one of Ghosty's) on or off
49
49
  ghosty skills templates <id> # pdf-doc templates: its own and Ghosty's, and which folder wins
50
- ghosty dbs ls|tables|query|import|grant|revoke <id> [db] [--write] [--arg V]… [--external-write t1,t2] [--external-rows t:col+col] [--from easybits:ID --tables a,b --append --dry-run]
50
+ ghosty dbs ls|tables|query|export|import|grant|revoke <id> [db] [--write] [--arg V]… [--external-write t1,t2] [--external-rows t:col+col] [--from easybits:ID --tables a,b --append --dry-run]
51
51
  ghosty turns ls <id> [--errors | --status running|done|stopped|error] [--since 1h] [--limit N]
52
52
  ghosty turns ls --workspace SLUG --quota-hits [--days 14] # messages that got no turn because the balance ran out
53
53
  ghosty turns show <id> [turn-id|last] # what came in, what it answered, where the time went [--full]
@@ -76,7 +76,7 @@ ghosty schedule ls <id>
76
76
  ghosty schedule rm <id> <schedule-id>
77
77
  ghosty schedule add|ls|rm <id> … --teams <workspace>#<room>[/<thread-id>] # in a Teams room/thread: it answers there as if mentioned
78
78
 
79
- ghosty me files ls|get|upload|rm [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]
79
+ ghosty me files ls|get|export|upload|rm [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]
80
80
  ghosty me memories ls|add|edit|rm
81
81
  ghosty conversations archive|rm <id> --kind test|channel [--before 7d] [--dry-run]
82
82
  ghosty agents docs dedupe [--dry-run] # your account: merge documents with the same content
@@ -103,6 +103,11 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
103
103
 
104
104
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
105
105
 
106
+ ### Changes in 0.49.0
107
+
108
+ - `dbs export <id> <db> [--table T] [--out FILE]`: download a database — the whole thing as `.sqlite` (sqlite3, DB Browser, DBeaver) or one table as CSV (Excel). Same buttons on each database's page in /app/datos.
109
+ - `me files export [--out DIR] [--kind K] [--dry-run]`: download every file in your account into `documents/`, `conversations/` and `library/`; rerunning only brings what's new.
110
+
106
111
  ### Changes in 0.48.0
107
112
 
108
113
  - `agents teams on <id> <workspace> --factory plan,build,check`: make an agent those Software Factory roles in that workspace (same as «Team» in /factory; each role keeps its name and avatar). `agents teams ls` shows the roles it holds.
package/dist/index.js CHANGED
@@ -8127,6 +8127,11 @@ var SERVER_MESSAGES = [
8127
8127
  ["no pude conectar con el servicio de sitios ({})", "couldn't reach the sites service ({})"],
8128
8128
  ["no se pudo crear el sitio; int\xE9ntalo en un momento", "couldn't create the site; try again in a moment"],
8129
8129
  ["no se pudo subir {}: {}", "couldn't upload {}: {}"],
8130
+ // — Exportar bases (dbs export)
8131
+ ["el CSV es de una tabla: falta table", "a CSV is one table: table is missing"],
8132
+ ["el token del agente no descarga bases: usa tu sesi\xF3n (ghosty login)", "the agent's token doesn't download databases: use your session (ghosty login)"],
8133
+ ["format es sqlite o csv", "format is sqlite or csv"],
8134
+ ["no existe la tabla {} en {}", "there's no table {} in {}"],
8130
8135
  // — Infra interna (casi nunca llega al CLI, pero por si acaso)
8131
8136
  ["SESSION_SECRET ausente en producci\xF3n \u2014 las cookies quedar\xEDan firmadas con un valor p\xFAblico. Inyectarlo con ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32).", "SESSION_SECRET missing in production \u2014 cookies would be signed with a public value. Inject it with ./scripts/set_gs_secret.sh SESSION_SECRET (openssl rand -hex 32)."],
8132
8137
  ["sqld: fetch sin namespace", "sqld: fetch without namespace"],
@@ -8429,7 +8434,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8429
8434
  const req = httpsRequest(`${base}${path}`, {
8430
8435
  method: "POST",
8431
8436
  timeout: timeoutMs,
8432
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.48.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8437
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.49.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8433
8438
  }, (res) => {
8434
8439
  let buf = "";
8435
8440
  res.on("data", (c) => buf += c);
@@ -8471,7 +8476,7 @@ async function request(path, opts = {}, retried = false) {
8471
8476
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8472
8477
  headers: {
8473
8478
  authorization: `Bearer ${token}`,
8474
- "user-agent": `ghosty-cli/${"0.48.0"}`,
8479
+ "user-agent": `ghosty-cli/${"0.49.0"}`,
8475
8480
  "accept-language": lang(),
8476
8481
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8477
8482
  ...opts.headers
@@ -9271,8 +9276,8 @@ function capacityLine(c) {
9271
9276
  // src/commands/chat.ts
9272
9277
  import { randomUUID } from "node:crypto";
9273
9278
  import { createInterface as createInterface2 } from "node:readline/promises";
9274
- import { readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
9275
- import { basename as basename2 } from "node:path";
9279
+ import { mkdir as mkdir4, readFile as readFile4, stat, writeFile as writeFile4 } from "node:fs/promises";
9280
+ import { basename as basename2, extname, join as join4 } from "node:path";
9276
9281
 
9277
9282
  // src/sse.ts
9278
9283
  async function* readSse(body) {
@@ -9482,9 +9487,65 @@ async function meMemories(sub, arg, text) {
9482
9487
  throw usage(t(`Unknown memories subcommand: ${sub}.`, `Subcomando de memories desconocido: ${sub}.`), ex);
9483
9488
  }
9484
9489
  }
9490
+ var safeName = (s) => s.replace(/[\\/:*?"<>|\u0000-\u001f]+/g, "_").trim() || "archivo";
9491
+ async function meFilesExport(v) {
9492
+ const dir = v.out ?? "ghosty-archivos";
9493
+ const all = [];
9494
+ let cursor = null;
9495
+ do {
9496
+ const q = new URLSearchParams({ all: "1", ...cursor ? { cursor } : {} });
9497
+ const r = await api(`/api/v2/me/files?${q}`);
9498
+ all.push(...r.files);
9499
+ cursor = r.nextCursor;
9500
+ } while (cursor);
9501
+ const files = v.kind ? all.filter((f) => f.kind === v.kind) : all;
9502
+ const folder = { documents: t("documents", "documentos"), conversations: t("conversations", "conversaciones"), library: t("library", "biblioteca") };
9503
+ const total = files.reduce((n, f) => n + f.size, 0);
9504
+ if (v["dry-run"]) {
9505
+ const byGroup = ["documents", "conversations", "library"].map((g) => files.filter((f) => f.group === g)).filter((fs) => fs.length).map((fs) => ({ folder: folder[fs[0].group], files: fs.length, MB: (fs.reduce((n, f) => n + f.size, 0) / 1e6).toFixed(1) }));
9506
+ return out({ dir, files: files.length, bytes: total, groups: byGroup }, () => `${table(byGroup, ["folder", "files", "MB"])}
9507
+ ${dim(t(`${files.length} file(s), ${(total / 1e6).toFixed(1)} MB \u2192 ${dir}/ (without --dry-run it downloads them)`, `${files.length} archivo(s), ${(total / 1e6).toFixed(1)} MB \u2192 ${dir}/ (sin --dry-run los baja)`))}`);
9508
+ }
9509
+ const used = /* @__PURE__ */ new Set();
9510
+ let saved = 0, skipped = 0;
9511
+ const failed = [];
9512
+ for (const [i, f] of files.entries()) {
9513
+ const sub = join4(dir, folder[f.group]);
9514
+ let name = safeName(f.name);
9515
+ if (used.has(join4(sub, name).toLowerCase())) name = `${basename2(name, extname(name))}-${f.id.slice(-6)}${extname(name)}`;
9516
+ used.add(join4(sub, name).toLowerCase());
9517
+ const path = join4(sub, name);
9518
+ const prev = await stat(path).catch(() => null);
9519
+ if (prev && prev.size === f.size) {
9520
+ skipped++;
9521
+ continue;
9522
+ }
9523
+ if (!globals.json) process.stderr.write(dim(`\r${i + 1}/${files.length} ${name.slice(0, 60)}`.padEnd(80)));
9524
+ try {
9525
+ const { url } = await api(`/api/v2/me/files/${enc3(f.id)}`);
9526
+ const res = await fetch(url);
9527
+ if (!res.ok) throw new Error(`${res.status}`);
9528
+ await mkdir4(sub, { recursive: true });
9529
+ await writeFile4(path, Buffer.from(await res.arrayBuffer()));
9530
+ saved++;
9531
+ } catch (e) {
9532
+ failed.push({ id: f.id, name: f.name, error: e.message });
9533
+ }
9534
+ }
9535
+ if (!globals.json) process.stderr.write("\r".padEnd(81) + "\r");
9536
+ return out(
9537
+ { dir, saved, skipped, failed },
9538
+ () => `${lavender("\u2713")} ${t(`${saved} downloaded, ${skipped} already there \u2192 ${dir}/`, `${saved} bajado(s), ${skipped} ya estaban \u2192 ${dir}/`)}` + (failed.length ? `
9539
+ ${red(t(`${failed.length} failed:`, `${failed.length} fallaron:`))}
9540
+ ${failed.map((x) => ` ${x.name} ${dim(`${x.id} \xB7 ${x.error}`)}`).join("\n")}` : "")
9541
+ );
9542
+ }
9485
9543
  async function meFiles(sub, arg, v) {
9486
- const ex = "ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] | get <file-id> [--out FILE] | upload <file> | rm <file-id>";
9544
+ const ex = "ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] | get <file-id> [--out FILE] | export [--out DIR] | upload <file> | rm <file-id>";
9487
9545
  switch (sub) {
9546
+ case "export":
9547
+ case "download":
9548
+ return meFilesExport(v);
9488
9549
  case "ls": {
9489
9550
  if (v.q !== void 0 || v.cursor) {
9490
9551
  const q = new URLSearchParams({ q: v.q ?? "", ...v.kind ? { kind: v.kind } : {}, ...v.cursor ? { cursor: v.cursor } : {}, ...v.limit ? { limit: v.limit } : {} });
@@ -9795,7 +9856,7 @@ async function call(path, v, init = {}) {
9795
9856
  "x-ghosty-ts": ts,
9796
9857
  "x-ghosty-sig": sig,
9797
9858
  "content-type": "application/json",
9798
- "user-agent": `ghosty-cli/${"0.48.0"}`
9859
+ "user-agent": `ghosty-cli/${"0.49.0"}`
9799
9860
  },
9800
9861
  body: method === "GET" ? void 0 : raw
9801
9862
  });
@@ -10050,8 +10111,8 @@ async function partnerCmd(sub, a1, a2, v, a3) {
10050
10111
  }
10051
10112
 
10052
10113
  // src/commands/data.ts
10053
- import { readdir as readdir2, readFile as readFile6 } from "node:fs/promises";
10054
- import { basename as basename3, join as join4 } from "node:path";
10114
+ import { readdir as readdir2, readFile as readFile6, writeFile as writeFile6 } from "node:fs/promises";
10115
+ import { basename as basename3, join as join5 } from "node:path";
10055
10116
 
10056
10117
  // src/rows.ts
10057
10118
  var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -10208,6 +10269,26 @@ ${t(`Delete ${dry.name} for good? This cannot be undone.`, `\xBFBorrar ${dry.nam
10208
10269
  const r = await api(path, { body: {} });
10209
10270
  return out(r, () => `${lavender("\u2713")} ${t(`${r.name} deleted.`, `${r.name} borrada.`)}`);
10210
10271
  }
10272
+ async function dbsExport(id, db, v) {
10273
+ const format = v.format ?? (v.table ? "csv" : "sqlite");
10274
+ if (format !== "sqlite" && format !== "csv") throw usage(t("--format takes sqlite or csv.", "--format acepta sqlite o csv."), `ghosty dbs export ${id} ${db} --format csv --table <table>`);
10275
+ if (format === "csv" && !v.table) throw usage(t("A CSV is one table: add --table.", "Un CSV es de una tabla: agrega --table."), `ghosty dbs export ${id} ${db} --table <table> (ghosty dbs tables ${id} ${db})`);
10276
+ const q = new URLSearchParams({ format, ...v.table ? { table: v.table } : {} });
10277
+ const res = await request(agentPath2(id, `/dbs/${enc6(db)}/export?${q}`));
10278
+ const suggested = /filename="([^"]+)"/.exec(res.headers.get("content-disposition") ?? "")?.[1] ?? (format === "csv" ? `${db}-${v.table}.csv` : `${db}.sqlite`);
10279
+ const file = v.out ?? suggested;
10280
+ const bytes = Buffer.from(await res.arrayBuffer());
10281
+ await writeFile6(file, bytes);
10282
+ const countsHeader = res.headers.get("x-gs-row-counts");
10283
+ const counts = countsHeader ? JSON.parse(Buffer.from(countsHeader, "base64").toString("utf8")) : {};
10284
+ const rows = Object.entries(counts).map(([tb, n]) => ({ table: tb, rows: n }));
10285
+ return out(
10286
+ { saved: file, bytes: bytes.length, format, ...v.table ? { table: v.table } : { tables: counts } },
10287
+ () => `${lavender("\u2713")} ${file} ${dim(`(${bytes.length} bytes)`)}` + (rows.length ? `
10288
+ ${table(rows, ["table", "rows"])}
10289
+ ${dim(t("Open it with sqlite3, DB Browser for SQLite or DBeaver.", "\xC1brela con sqlite3, DB Browser for SQLite o DBeaver."))}` : "")
10290
+ );
10291
+ }
10211
10292
  async function dbsCmd(sub, id, db, rest, v) {
10212
10293
  const ex = "ghosty dbs ls <agent-id>";
10213
10294
  id = need2(id, t("agent id", "el id del agente"), ex);
@@ -10282,6 +10363,11 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
10282
10363
  db = need2(db, t("database (id or name)", "la base (id o nombre)"), ex2);
10283
10364
  return dbsRename(id, db, need2(rest[0], t("new name", "el nombre nuevo"), ex2), !!v["dry-run"]);
10284
10365
  }
10366
+ case "export":
10367
+ case "download": {
10368
+ db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs export <agent-id> <db> [--table T] [--out FILE]");
10369
+ return dbsExport(id, db, v);
10370
+ }
10285
10371
  case "rm":
10286
10372
  case "delete": {
10287
10373
  db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs rm <agent-id> <db> [--dry-run] [--yes]");
@@ -10557,7 +10643,7 @@ async function dbsPhotosCmd(verb, id, db, v) {
10557
10643
  const results = [];
10558
10644
  for (let i = 0; i < files.length; i += 8) {
10559
10645
  const batch = files.slice(i, i + 8);
10560
- const items = await Promise.all(batch.map(async (f) => ({ key: f.key, filename: f.file, contentBase64: (await readFile6(join4(dir, f.file))).toString("base64") })));
10646
+ const items = await Promise.all(batch.map(async (f) => ({ key: f.key, filename: f.file, contentBase64: (await readFile6(join5(dir, f.file))).toString("base64") })));
10561
10647
  const r = await api(path, { body: { table: tableName, keyColumn, column, items, ...v["dry-run"] ? { dryRun: true } : {} } });
10562
10648
  results.push(...r.items);
10563
10649
  if (!globals.json && files.length > 8) info(dim(` ${Math.min(i + 8, files.length)}/${files.length}`));
@@ -10789,7 +10875,7 @@ import { readFile as readFile8 } from "node:fs/promises";
10789
10875
 
10790
10876
  // src/wa-auth.ts
10791
10877
  import { readdir as readdir3, readFile as readFile7 } from "node:fs/promises";
10792
- import { join as join5 } from "node:path";
10878
+ import { join as join6 } from "node:path";
10793
10879
  var TYPES = ["app-state-sync-version", "app-state-sync-key", "sender-key-memory", "sender-key", "lid-mapping", "device-list", "identity-key", "pre-key", "session", "tctoken"];
10794
10880
  function keyOfFileName(name) {
10795
10881
  for (const type of TYPES) {
@@ -10803,7 +10889,7 @@ function keyOfFileName(name) {
10803
10889
  async function readMultiFileAuth(dir) {
10804
10890
  const files = (await readdir3(dir)).filter((f) => f.endsWith(".json"));
10805
10891
  if (!files.includes("creds.json")) throw new Error(`${dir} has no creds.json: not a Baileys multi-file auth folder`);
10806
- const authCreds = JSON.parse(await readFile7(join5(dir, "creds.json"), "utf8"));
10892
+ const authCreds = JSON.parse(await readFile7(join6(dir, "creds.json"), "utf8"));
10807
10893
  const authKeys = {};
10808
10894
  const counts = {};
10809
10895
  const skipped = [];
@@ -10814,7 +10900,7 @@ async function readMultiFileAuth(dir) {
10814
10900
  skipped.push(f);
10815
10901
  continue;
10816
10902
  }
10817
- (authKeys[k.type] ??= {})[k.id] = JSON.parse(await readFile7(join5(dir, f), "utf8"));
10903
+ (authKeys[k.type] ??= {})[k.id] = JSON.parse(await readFile7(join6(dir, f), "utf8"));
10818
10904
  counts[k.type] = (counts[k.type] ?? 0) + 1;
10819
10905
  }
10820
10906
  return { authCreds, authKeys, counts, skipped };
@@ -11346,13 +11432,18 @@ var HELPS_ES = {
11346
11432
  },
11347
11433
  dbs: {
11348
11434
  summary: "Bases de datos que un agente puede usar, y qu\xE9 puede escribir desde canales de clientes.",
11349
- replace: [["# reads $EASYBITS_API_KEY", "# lee $EASYBITS_API_KEY"]],
11435
+ replace: [
11436
+ ["# reads $EASYBITS_API_KEY", "# lee $EASYBITS_API_KEY"],
11437
+ ["# totequim-productos.csv (Excel)", "# totequim-productos.csv (Excel)"]
11438
+ ],
11350
11439
  flags: [
11351
11440
  ["--orphans", "ls: s\xF3lo las bases que ning\xFAn agente tiene concedidas (candidatas a rm)"],
11352
11441
  ["--arg V", "query: valor para un marcador ? (repetible)"],
11353
11442
  ["--write", "query: permite INSERT/UPDATE/DELETE/DDL (default: s\xF3lo lectura)"],
11354
11443
  ["--external-write t1,t2", 'grant: tablas que puede escribir desde WhatsApp/Messenger ("" = ninguna)'],
11355
11444
  ["--external-rows t:col+col", 'grant: desde WhatsApp/Messenger cada cliente ve s\xF3lo SUS filas de t, por columna(s) de tel\xE9fono ("" = sin filtro)'],
11445
+ ["--table T", "export: baja s\xF3lo esa tabla, en CSV (sin ella: la base entera como .sqlite)"],
11446
+ ["--out FILE", "export: d\xF3nde guardarla (default: el nombre de la base, p. ej. totequim.sqlite o totequim-productos.csv)"],
11356
11447
  ["--from easybits:ID", "import: base de origen en EasyBits (llave de $EASYBITS_API_KEY)"],
11357
11448
  ["--tables a,b", "import: s\xF3lo estas tablas (default: todas)"],
11358
11449
  ["--append", "import: agrega filas en vez de reemplazarlas (reemplazar es el default idempotente)"],
@@ -11364,6 +11455,7 @@ var HELPS_ES = {
11364
11455
  ],
11365
11456
  notes: [
11366
11457
  "<db> es el id de la base o su nombre. Desde canales del due\xF1o el agente lee y escribe todo lo concedido.",
11458
+ "export usa tu sesi\xF3n (no el token gat_ del agente) y deja registro en la bit\xE1cora; las mismas descargas est\xE1n en la p\xE1gina de la base, en /app/datos?tab=dbs.",
11367
11459
  "tables dice, por columna de fotos/ligas, cu\xE1ntas son permanentes (img.ghosty.studio), de fuera o vac\xEDas; import avisa antes de reemplazar ligas permanentes por ligas de fuera.",
11368
11460
  "import con replace avisa de las filas escritas en gs que EasyBits no tiene (una base compartida con agentes que ya corren en gs) y pregunta antes de borrarlas.",
11369
11461
  "rename tambi\xE9n reescribe cada prompt, valor de env y SKILL.md que nombra la base; un prompt derivado sigue a su base. rm borra la base misma (revoke s\xF3lo le quita el acceso a este agente); las dos son s\xF3lo del due\xF1o (o del staff de Ghosty)."
@@ -11435,7 +11527,16 @@ var HELPS_ES = {
11435
11527
  },
11436
11528
  me: {
11437
11529
  summary: "Tu cuenta: archivos, memorias y uso.",
11438
- notes: ["Las memorias viajan a tus agentes s\xF3lo en tus chats personales (app, /c, notch), nunca a WhatsApp, Messenger ni demos."]
11530
+ replace: [["# documents/, conversations/, library/", "# documentos/, conversaciones/, biblioteca/"]],
11531
+ flags: [
11532
+ ["--out DIR", "export: carpeta destino (default ghosty-archivos); get: archivo donde guardarlo"],
11533
+ ["--kind K", "ls, export: s\xF3lo image | video | audio | document | other"],
11534
+ ["-n, --dry-run", "export: cu\xE1ntos archivos y MB por carpeta, sin bajar nada"]
11535
+ ],
11536
+ notes: [
11537
+ "Las memorias viajan a tus agentes s\xF3lo en tus chats personales (app, /c, notch), nunca a WhatsApp, Messenger ni demos.",
11538
+ "export baja TODO lo de tu cuenta: documentos, archivos de tus chats y de los canales de tus agentes (lo que mandaron los clientes). Lo ya bajado con el mismo tama\xF1o se salta, as\xED que repetirlo s\xF3lo trae lo nuevo."
11539
+ ]
11439
11540
  },
11440
11541
  credentials: {
11441
11542
  summary: "Llaves de proveedores en la b\xF3veda de tu cuenta (cifradas; s\xF3lo regresan los \xFAltimos 4 caracteres).",
@@ -11828,6 +11929,7 @@ var HELPS = {
11828
11929
  "ghosty dbs ls <agent-id> [--orphans]",
11829
11930
  "ghosty dbs tables <agent-id> <db>",
11830
11931
  'ghosty dbs query <agent-id> <db> "SQL" [--arg V]\u2026 [--write]',
11932
+ "ghosty dbs export <agent-id> <db> [--table T] [--out FILE]",
11831
11933
  "ghosty dbs import <agent-id> <db> --from easybits:<db-id> [--tables a,b] [--append] [--rehost-images t.col,\u2026] [--dry-run] [--yes]",
11832
11934
  "ghosty dbs rehost <agent-id> <db> --table T --column C [--dry-run]",
11833
11935
  "ghosty dbs photos put <agent-id> <db> --table T --key-column K --dir DIR [--column C] [--dry-run]",
@@ -11842,6 +11944,8 @@ var HELPS = {
11842
11944
  ["--write", "query: allow INSERT/UPDATE/DELETE/DDL (default: read only)"],
11843
11945
  ["--external-write t1,t2", 'grant: tables it may write from WhatsApp/Messenger ("" = none)'],
11844
11946
  ["--external-rows t:col+col", 'grant: from WhatsApp/Messenger each customer sees only THEIR rows of t, matched by phone column(s) ("" = no filter)'],
11947
+ ["--table T", "export: download only that table, as CSV (without it: the whole database as .sqlite)"],
11948
+ ["--out FILE", "export: where to save it (default: the database name, e.g. totequim.sqlite or totequim-productos.csv)"],
11845
11949
  ["--from easybits:ID", "import: source database in EasyBits (key from $EASYBITS_API_KEY)"],
11846
11950
  ["--tables a,b", "import: only these tables (default: all)"],
11847
11951
  ["--append", "import: add rows instead of replacing them (replace is the idempotent default)"],
@@ -11855,6 +11959,8 @@ var HELPS = {
11855
11959
  "ghosty dbs ls <id>",
11856
11960
  "ghosty dbs tables <id> totequim",
11857
11961
  'ghosty dbs query <id> totequim "SELECT count(*) AS n FROM productos"',
11962
+ "ghosty dbs export <id> totequim # totequim.sqlite (sqlite3, DB Browser, DBeaver)",
11963
+ "ghosty dbs export <id> totequim --table productos # totequim-productos.csv (Excel)",
11858
11964
  "ghosty dbs grant <id> totequim --external-write cotizaciones,folio",
11859
11965
  "ghosty dbs grant <id> totequim --external-rows clientes:telefono+tel_secundario,cotizaciones:cliente_tel",
11860
11966
  "ghosty dbs import <id> totequim --from easybits:6a10c84c\u2026 --dry-run # reads $EASYBITS_API_KEY",
@@ -11864,6 +11970,7 @@ var HELPS = {
11864
11970
  ],
11865
11971
  notes: [
11866
11972
  "<db> is the database id or its name. From owner channels the agent reads and writes everything granted.",
11973
+ "export uses your session (not the agent's gat_ token) and leaves an audit entry; the same downloads are on the database page at /app/datos?tab=dbs.",
11867
11974
  "tables shows, per photo/link column, how many links are permanent (img.ghosty.studio), external or empty; import warns before replacing permanent links with external ones.",
11868
11975
  "import with replace warns about rows written in gs that EasyBits doesn't have (a database shared with agents already running in gs) and asks before deleting them.",
11869
11976
  "rename also rewrites every prompt, env value and SKILL.md that names the database; a derived prompt follows its base. rm deletes the database itself (revoke only removes this agent's access); only its owner (or Ghosty staff) can do either."
@@ -11974,9 +12081,17 @@ var HELPS = {
11974
12081
  },
11975
12082
  me: {
11976
12083
  summary: "Your account: files, memories and usage.",
11977
- usage: ["ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]", "ghosty me files get <file-id> [--out FILE]", "ghosty me files upload <file> | rm <file-id>", 'ghosty me memories ls | add "text" | edit <id> "text" | rm <id>', "ghosty usage"],
11978
- examples: ["ghosty me files ls --kind document", "ghosty me files ls --q contrato", "ghosty me files get <id> --out contrato.pdf", 'ghosty chat <agent> "res\xFAmelo" --file <id>', 'ghosty me memories add "I prefer short answers"'],
11979
- notes: ["Memories travel to your agents only in your personal chats (app, /c, notch), never to WhatsApp, Messenger or demo threads."],
12084
+ usage: ["ghosty me files ls [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]", "ghosty me files get <file-id> [--out FILE]", "ghosty me files export [--out DIR] [--kind K] [--dry-run]", "ghosty me files upload <file> | rm <file-id>", 'ghosty me memories ls | add "text" | edit <id> "text" | rm <id>', "ghosty usage"],
12085
+ examples: ["ghosty me files ls --kind document", "ghosty me files ls --q contrato", "ghosty me files get <id> --out contrato.pdf", "ghosty me files export --dry-run", "ghosty me files export --out respaldo/ # documents/, conversations/, library/", 'ghosty chat <agent> "res\xFAmelo" --file <id>', 'ghosty me memories add "I prefer short answers"'],
12086
+ flags: [
12087
+ ["--out DIR", "export: destination folder (default ghosty-archivos); get: file to save"],
12088
+ ["--kind K", "ls, export: only image | video | audio | document | other"],
12089
+ ["-n, --dry-run", "export: how many files and MB per folder, without downloading"]
12090
+ ],
12091
+ notes: [
12092
+ "Memories travel to your agents only in your personal chats (app, /c, notch), never to WhatsApp, Messenger or demo threads.",
12093
+ "export brings EVERYTHING in your account: documents, files from your chats and from your agents' channels (what customers sent). A file already downloaded with the same size is skipped, so rerunning it only brings what's new."
12094
+ ],
11980
12095
  learn: `${REF}#tu-cuenta`
11981
12096
  }
11982
12097
  };
@@ -12346,10 +12461,10 @@ function helpText(cmd) {
12346
12461
  }
12347
12462
 
12348
12463
  // src/update.ts
12349
- import { mkdir as mkdir4, readFile as readFile9, writeFile as writeFile6 } from "node:fs/promises";
12464
+ import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile7 } from "node:fs/promises";
12350
12465
  import { homedir as homedir3 } from "node:os";
12351
- import { dirname as dirname3, join as join6 } from "node:path";
12352
- var FILE = join6(process.env.XDG_CONFIG_HOME || join6(homedir3(), ".config"), "ghosty", "update.json");
12466
+ import { dirname as dirname3, join as join7 } from "node:path";
12467
+ var FILE = join7(process.env.XDG_CONFIG_HOME || join7(homedir3(), ".config"), "ghosty", "update.json");
12353
12468
  var TTL = 60 * 60 * 1e3;
12354
12469
  var UPGRADE = "npm i -g @ghostystudio/cli@latest";
12355
12470
  function newer(a, b) {
@@ -12371,8 +12486,8 @@ async function latestVersion(current2) {
12371
12486
  const r = await fetch("https://registry.npmjs.org/@ghostystudio/cli/latest", { signal: AbortSignal.timeout(1500) });
12372
12487
  const latest = (await r.json()).version;
12373
12488
  if (latest) cache2 = { latest, checkedAt: Date.now() };
12374
- await mkdir4(dirname3(FILE), { recursive: true });
12375
- await writeFile6(FILE, JSON.stringify(cache2));
12489
+ await mkdir5(dirname3(FILE), { recursive: true });
12490
+ await writeFile7(FILE, JSON.stringify(cache2));
12376
12491
  } catch {
12377
12492
  }
12378
12493
  }
@@ -12447,9 +12562,9 @@ function agentArgIndex(pos) {
12447
12562
  }
12448
12563
 
12449
12564
  // src/completion.ts
12450
- import { mkdir as mkdir5, readFile as readFile10, writeFile as writeFile7 } from "node:fs/promises";
12565
+ import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile8 } from "node:fs/promises";
12451
12566
  import { homedir as homedir4 } from "node:os";
12452
- import { join as join7 } from "node:path";
12567
+ import { join as join8 } from "node:path";
12453
12568
  var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
12454
12569
  var AGENT_FLAGS = /* @__PURE__ */ new Set(["--like", "--agent", "--prompt-base"]);
12455
12570
  var WORKSPACE_FLAGS = /* @__PURE__ */ new Set(["--workspace"]);
@@ -12487,7 +12602,7 @@ function completionFor(words, current2, commands) {
12487
12602
  if (agentArgIndex([...pos, current2]) === pos.length) return { kind: "agents" };
12488
12603
  return { kind: "words", words: [] };
12489
12604
  }
12490
- var cacheFile = () => join7(process.env.XDG_CACHE_HOME || join7(homedir4(), ".cache"), "ghosty", `complete${process.env.GHOSTY_PROFILE ? `.${process.env.GHOSTY_PROFILE}` : ""}.json`);
12605
+ var cacheFile = () => join8(process.env.XDG_CACHE_HOME || join8(homedir4(), ".cache"), "ghosty", `complete${process.env.GHOSTY_PROFILE ? `.${process.env.GHOSTY_PROFILE}` : ""}.json`);
12491
12606
  async function names() {
12492
12607
  try {
12493
12608
  const c = JSON.parse(await readFile10(cacheFile(), "utf8"));
@@ -12497,9 +12612,9 @@ async function names() {
12497
12612
  const list = await listAgents();
12498
12613
  const agents = [...new Set(list.map((a) => a.nombre))];
12499
12614
  const workspaces = [...new Set(list.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
12500
- await mkdir5(join7(cacheFile(), ".."), { recursive: true }).catch(() => {
12615
+ await mkdir6(join8(cacheFile(), ".."), { recursive: true }).catch(() => {
12501
12616
  });
12502
- await writeFile7(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
12617
+ await writeFile8(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
12503
12618
  });
12504
12619
  return { agents, workspaces };
12505
12620
  }
@@ -12577,8 +12692,8 @@ function normalizeArgs(pos, v) {
12577
12692
 
12578
12693
  // src/commands/spec.ts
12579
12694
  var import_yaml = __toESM(require_dist(), 1);
12580
- import { mkdir as mkdir6, readdir as readdir4, readFile as readFile11, stat, writeFile as writeFile8 } from "node:fs/promises";
12581
- import { join as join8, resolve as resolve2 } from "node:path";
12695
+ import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile9 } from "node:fs/promises";
12696
+ import { join as join9, resolve as resolve2 } from "node:path";
12582
12697
  var enc9 = encodeURIComponent;
12583
12698
  async function agentsExport(id, v) {
12584
12699
  if (!id) throw usage(t("Missing agent.", "Falta el agente."), t("Example", "Ejemplo") + ": ghosty agents export pia-0 > pia.yaml \xB7 ghosty agents export pia-0 --out ./pia");
@@ -12588,12 +12703,12 @@ async function agentsExport(id, v) {
12588
12703
  return void process.stdout.write(yaml);
12589
12704
  }
12590
12705
  const dir = resolve2(v.out);
12591
- await mkdir6(dir, { recursive: true });
12592
- await writeFile8(join8(dir, "agent.yaml"), yaml);
12706
+ await mkdir7(dir, { recursive: true });
12707
+ await writeFile9(join9(dir, "agent.yaml"), yaml);
12593
12708
  const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
12594
12709
  for (const slug of own) {
12595
12710
  const sk = await api(`/api/v2/agents/${enc9(id)}/skills/${enc9(slug)}`);
12596
- await writeSkill(join8(dir, "skills", slug), sk);
12711
+ await writeSkill(join9(dir, "skills", slug), sk);
12597
12712
  }
12598
12713
  out({ saved: dir, skills: own }, () => `${lavender("\u2713")} ${dir}/agent.yaml${own.length ? ` + skills/ (${own.join(", ")})` : ""}
12599
12714
  ${dim(t(`Edit it, then: ghosty apply ${v.out} --dry-run`, `Ed\xEDtalo y luego: ghosty apply ${v.out} --dry-run`))}`);
@@ -12614,12 +12729,12 @@ function fillSecrets(file) {
12614
12729
  return missing;
12615
12730
  }
12616
12731
  async function readSkills(dir) {
12617
- const root = join8(dir, "skills");
12732
+ const root = join9(dir, "skills");
12618
12733
  const out2 = {};
12619
12734
  const slugs = await readdir4(root, { withFileTypes: true }).catch(() => []);
12620
12735
  for (const e of slugs) {
12621
12736
  if (!e.isDirectory() || e.name.startsWith(".")) continue;
12622
- const { markdown, assets } = await readSkillDir(join8(root, e.name));
12737
+ const { markdown, assets } = await readSkillDir(join9(root, e.name));
12623
12738
  out2[e.name] = [{ path: "SKILL.md", contentBase64: Buffer.from(markdown).toString("base64") }, ...assets.map((a) => ({ path: a.name, contentBase64: a.contentBase64 }))];
12624
12739
  }
12625
12740
  return out2;
@@ -12627,8 +12742,8 @@ async function readSkills(dir) {
12627
12742
  var line = (c) => c.op === "!" ? dim(`! ${c.what}`) : `${c.op === "+" ? lavender("+") : c.op === "-" ? red("-") : "~"} ${c.what}`;
12628
12743
  async function applyCmd(target, v) {
12629
12744
  if (!target) throw usage(t("Missing file or directory.", "Falta el archivo o el directorio."), t("Example: ghosty apply pia.yaml --dry-run \xB7 ghosty apply ./pia (from agents export --out)", "Ejemplo: ghosty apply pia.yaml --dry-run \xB7 ghosty apply ./pia (de agents export --out)"));
12630
- const isDir = (await stat(target).catch(() => null))?.isDirectory() ?? false;
12631
- const text = await readFile11(isDir ? join8(target, "agent.yaml") : target, "utf8");
12745
+ const isDir = (await stat2(target).catch(() => null))?.isDirectory() ?? false;
12746
+ const text = await readFile11(isDir ? join9(target, "agent.yaml") : target, "utf8");
12632
12747
  const file = (0, import_yaml.parse)(text);
12633
12748
  if (!file || typeof file !== "object") throw usage(t(`${target} is not a YAML/JSON agent file.`, `${target} no es un archivo de agente YAML/JSON.`));
12634
12749
  const missing = fillSecrets(file);
@@ -12948,9 +13063,9 @@ ${dim(t(`${fmtTokens(total)} billable tokens \xB7 failed = error or stopped \xB7
12948
13063
 
12949
13064
  // src/commands/demos.ts
12950
13065
  import { randomUUID as randomUUID2 } from "node:crypto";
12951
- import { mkdir as mkdir7, readFile as readFile12, writeFile as writeFile9 } from "node:fs/promises";
13066
+ import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile10 } from "node:fs/promises";
12952
13067
  import { homedir as homedir5 } from "node:os";
12953
- import { dirname as dirname4, join as join9 } from "node:path";
13068
+ import { dirname as dirname4, join as join10 } from "node:path";
12954
13069
  var enc11 = encodeURIComponent;
12955
13070
  var hora = (iso) => new Date(iso).toLocaleString("es-MX", { timeZone: "America/Mexico_City", hour12: false, dateStyle: "short", timeStyle: "short" });
12956
13071
  async function history(agent, v) {
@@ -12970,7 +13085,7 @@ async function history(agent, v) {
12970
13085
  return lines.join("\n");
12971
13086
  });
12972
13087
  }
12973
- var jarPath = () => join9(process.env.XDG_CONFIG_HOME || join9(homedir5(), ".config"), "ghosty", "demo-cookies.json");
13088
+ var jarPath = () => join10(process.env.XDG_CONFIG_HOME || join10(homedir5(), ".config"), "ghosty", "demo-cookies.json");
12974
13089
  async function readJar() {
12975
13090
  try {
12976
13091
  return JSON.parse(await readFile12(jarPath(), "utf8"));
@@ -12990,8 +13105,8 @@ async function visitorCookie(base, slug, fresh) {
12990
13105
  const cookie = anonCookieOf(res.headers.get("set-cookie"));
12991
13106
  if (!cookie) throw new CliError(t("The demo didn't hand out a visitor cookie.", "El demo no entreg\xF3 cookie de visitante."), EXIT.API);
12992
13107
  jar[slug] = cookie;
12993
- await mkdir7(dirname4(jarPath()), { recursive: true });
12994
- await writeFile9(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
13108
+ await mkdir8(dirname4(jarPath()), { recursive: true });
13109
+ await writeFile10(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
12995
13110
  return cookie;
12996
13111
  }
12997
13112
  async function visitorTurn(base, slug, cookie, text) {
@@ -13063,12 +13178,12 @@ async function demosCmd(sub, a1, rest, v) {
13063
13178
  }
13064
13179
 
13065
13180
  // src/commands/sites.ts
13066
- import { readdir as readdir5, readFile as readFile13, stat as stat2 } from "node:fs/promises";
13067
- import { basename as basename4, join as join10, relative as relative2, sep } from "node:path";
13181
+ import { readdir as readdir5, readFile as readFile13, stat as stat3 } from "node:fs/promises";
13182
+ import { basename as basename4, join as join11, relative as relative2, sep } from "node:path";
13068
13183
  var TEXT_EXT = /* @__PURE__ */ new Set(["html", "htm", "css", "js", "mjs", "json", "txt", "xml", "svg", "webmanifest", "md"]);
13069
13184
  var SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".DS_Store"]);
13070
13185
  async function collectSiteFiles(target) {
13071
- const st = await stat2(target).catch(() => null);
13186
+ const st = await stat3(target).catch(() => null);
13072
13187
  if (!st) throw usage(t(`Not found: ${target}`, `No existe: ${target}`));
13073
13188
  if (st.isFile()) {
13074
13189
  if (!/\.html?$/i.test(target)) throw usage(t("A single file must be .html (or pass a folder).", "Un archivo suelto debe ser .html (o pasa una carpeta)."));
@@ -13079,7 +13194,7 @@ async function collectSiteFiles(target) {
13079
13194
  const walk = async (dir) => {
13080
13195
  for (const e of await readdir5(dir, { withFileTypes: true })) {
13081
13196
  if (e.name.startsWith(".") || SKIP.has(e.name)) continue;
13082
- const full = join10(dir, e.name);
13197
+ const full = join11(dir, e.name);
13083
13198
  if (e.isDirectory()) await walk(full);
13084
13199
  else if (e.isFile()) {
13085
13200
  const path = relative2(target, full).split(sep).join("/");
@@ -13461,7 +13576,8 @@ ${bold("Configure an agent")}
13461
13576
  skills house <id> Ghosty's skills in bulk [--only a,b | --enable a,b | --disable a,b] [--dry-run]
13462
13577
  skills ls|get|add|rm <id> [slug] Skills (add from catalog, --file SKILL.md or --dir FOLDER; get lists, --out DIR saves; rm keeps a copy)
13463
13578
  skills templates <id> pdf-doc templates it sees: its own and Ghosty's, and which folder wins
13464
- dbs ls|tables|query|import|rehost|grant|revoke|rename|rm <id> [db] Databases it uses [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run] [--table T --column C]
13579
+ dbs ls|tables|query|export|import|rehost|grant|revoke|rename|rm <id> [db] Databases it uses [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run] [--table T --column C]
13580
+ dbs export <id> <db> Download a database: whole as .sqlite, or one table as CSV [--table T] [--out FILE]
13465
13581
  dbs photos put <id> <db> --table T --key-column K --dir DIR Loose photos to a catalog (SKU-123.jpg \u2192 its row), permanent links [--column C] [--dry-run]
13466
13582
  mcp get|set <id> MCP servers [--file servers.json] (http headers accept \${turn.token})
13467
13583
  channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
@@ -13520,7 +13636,8 @@ ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see:
13520
13636
  partner ls | enable | credentials | agent | tenants | audit | try
13521
13637
 
13522
13638
  ${bold("Your account")}
13523
- me files ls|get|upload|rm Files in your account [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out FILE]
13639
+ me files ls|get|export|upload|rm Files in your account [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out FILE]
13640
+ me files export Download ALL your files to a folder (documents, conversations, library); rerun = only new [--out DIR] [--kind K] [--dry-run]
13524
13641
  me memories ls|add|edit|rm What your agents remember about you (personal chats only) add "text" \xB7 edit <id> "text" \xB7 rm <id>
13525
13642
  credentials ls|set|rm [provider] Provider keys in your vault (hidden prompt, stdin or --from-env VAR; never as an argument)
13526
13643
  connectors ls|connect|disconnect [id] Your connectors (Shopify, Stripe, monday\u2026): connect opens the browser or asks for the key
@@ -13607,7 +13724,8 @@ ${bold("Configurar un agente")}
13607
13724
  skills house <id> Las skills de Ghosty en bloque [--only a,b | --enable a,b | --disable a,b] [--dry-run]
13608
13725
  skills ls|get|add|rm <id> [slug] Skills (add del cat\xE1logo, --file SKILL.md o --dir CARPETA; get lista, --out DIR guarda; rm deja copia)
13609
13726
  skills templates <id> Plantillas de pdf-doc que ve: las suyas y las de Ghosty, y qu\xE9 carpeta gana
13610
- dbs ls|tables|query|import|rehost|grant|revoke|rename|rm <id> [db] Bases de datos que usa [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run] [--table T --column C]
13727
+ dbs ls|tables|query|export|import|rehost|grant|revoke|rename|rm <id> [db] Bases de datos que usa [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run] [--table T --column C]
13728
+ dbs export <id> <db> Descarga una base: entera como .sqlite, o una tabla en CSV [--table T] [--out ARCHIVO]
13611
13729
  dbs photos put <id> <db> --table T --key-column K --dir DIR Fotos sueltas a un cat\xE1logo (SKU-123.jpg \u2192 su fila), ligas permanentes [--column C] [--dry-run]
13612
13730
  mcp get|set <id> Servidores MCP [--file servers.json] (headers http admiten \${turn.token})
13613
13731
  channels whatsapp ls|enable|disable <id> [n\xFAmero] Qu\xE9 n\xFAmeros de WhatsApp Business contesta [--dry-run]
@@ -13666,7 +13784,8 @@ ${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ve
13666
13784
  partner ls | enable | credentials | agent | tenants | audit | try
13667
13785
 
13668
13786
  ${bold("Tu cuenta")}
13669
- me files ls|get|upload|rm Archivos de tu cuenta [--q TEXTO] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out ARCHIVO]
13787
+ me files ls|get|export|upload|rm Archivos de tu cuenta [--q TEXTO] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out ARCHIVO]
13788
+ me files export Baja TODOS tus archivos a una carpeta (documentos, conversaciones, biblioteca); repetirlo = s\xF3lo lo nuevo [--out CARPETA] [--kind K] [--dry-run]
13670
13789
  me memories ls|add|edit|rm Lo que tus agentes recuerdan de ti (s\xF3lo chats personales) add "texto" \xB7 edit <id> "texto" \xB7 rm <id>
13671
13790
  credentials ls|set|rm [proveedor] Llaves de proveedor en tu b\xF3veda (prompt oculto, stdin o --from-env VAR; nunca como argumento)
13672
13791
  connectors ls|connect|disconnect [id] Tus conectores (Shopify, Stripe, monday\u2026): connect abre el navegador o pide la llave
@@ -13917,7 +14036,7 @@ async function main() {
13917
14036
  const tokens = splitTokens(pos, v);
13918
14037
  globals.token = tokens.session;
13919
14038
  globals.profile = v.profile;
13920
- if (v.version) return out({ version: "0.48.0" }, () => "0.48.0");
14039
+ if (v.version) return out({ version: "0.49.0" }, () => "0.49.0");
13921
14040
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
13922
14041
  const context = (await readConfig()).workspace;
13923
14042
  if (!explicitToken()?.startsWith("gat_")) {
@@ -14077,7 +14196,7 @@ async function main() {
14077
14196
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
14078
14197
  }
14079
14198
  }
14080
- var notice = updateNotice("0.48.0").catch(() => null);
14199
+ var notice = updateNotice("0.49.0").catch(() => null);
14081
14200
  main().then(
14082
14201
  async () => {
14083
14202
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "description": "Official Ghosty Studio CLI — configure, test and chat with your agents from the terminal. Built for humans and coding agents.",
5
5
  "type": "module",
6
6
  "bin": {