@ghostystudio/cli 0.85.0 → 0.86.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 +5 -0
  2. package/dist/index.js +67 -21
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -105,6 +105,7 @@ ghosty watch add <id> <url> [--every 24h] [--focus "heading"] [--instruction "
105
105
  ghosty watch ls <id>
106
106
  ghosty watch check <id> <watch-id> [--dry-run]
107
107
  ghosty watch rm|resume <id> <watch-id>
108
+ ghosty tts "Hola, te aviso por WhatsApp." [--voice em_santa|em_alex|ef_dora] [--out voz.wav] # the house voice (Mexican Spanish + pronunciation lexicon)
108
109
  ghosty teams post <workspace>#<room>[/<thread-id>] "msg" [--wait] # write there as you; any @agent it mentions answers
109
110
 
110
111
  ghosty me files ls|get|export|upload|rm [--q TEXT] [--kind image|video|audio|document|other] [--cursor C]
@@ -150,6 +151,10 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
150
151
 
151
152
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
152
153
 
154
+ ### Changes in 0.86.0
155
+
156
+ - `ghosty tts "text" [--voice em_santa|em_alex|ef_dora] [--out FILE]`: hear the house voice (Kokoro, Mexican Spanish, with Ghosty's pronunciation lexicon) say a text, saved as a .wav. Up to 400 characters.
157
+
153
158
  ### Changes in 0.84.0
154
159
 
155
160
  - `coupons create|set` take `--from` (the lent plan starts then), `--redeem-by` (last moment to redeem) and `--convert-promo` (Stripe promotion code offered in /c/uso when the lent plan is about to end). New `coupons show <CODE>` (staff): who redeemed it, their plan window and who converted to a paid plan. Codes are accent- and case-insensitive.
package/dist/index.js CHANGED
@@ -1175,7 +1175,7 @@ async function postLong(path2, body, timeoutMs = 9e5) {
1175
1175
  const req = httpsRequest(`${base}${path2}`, {
1176
1176
  method: "POST",
1177
1177
  timeout: timeoutMs,
1178
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.85.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
1178
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.86.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
1179
1179
  }, (res) => {
1180
1180
  let buf = "";
1181
1181
  res.on("data", (c) => buf += c);
@@ -1217,7 +1217,7 @@ async function request(path2, opts = {}, retried = false) {
1217
1217
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
1218
1218
  headers: {
1219
1219
  authorization: `Bearer ${token}`,
1220
- "user-agent": `ghosty-cli/${"0.85.0"}`,
1220
+ "user-agent": `ghosty-cli/${"0.86.0"}`,
1221
1221
  "accept-language": lang(),
1222
1222
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
1223
1223
  ...opts.headers
@@ -1264,7 +1264,7 @@ var mteams_exports = {};
1264
1264
  __export(mteams_exports, {
1265
1265
  mteamsCmd: () => mteamsCmd
1266
1266
  });
1267
- import { writeFile as writeFile8 } from "node:fs/promises";
1267
+ import { writeFile as writeFile9 } from "node:fs/promises";
1268
1268
  function list(bots) {
1269
1269
  return bots.length ? table(
1270
1270
  bots.map((b) => ({
@@ -1306,7 +1306,7 @@ ${dim(`${t("Next", "Sigue")}: ghosty channels mteams zip ${id} ${b?.id ?? "<bot-
1306
1306
  const res = await request(`${path2}/${enc10(botId)}/zip`);
1307
1307
  const bytes = Buffer.from(await res.arrayBuffer());
1308
1308
  const file = v.out ?? "ghosty-teams.zip";
1309
- await writeFile8(file, bytes);
1309
+ await writeFile9(file, bytes);
1310
1310
  return out({ saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${t(`Saved ${file}: upload it in Teams admin \u2192 Manage apps.`, `Se guard\xF3 ${file}: s\xFAbelo en Teams admin \u2192 Administrar apps.`)}`);
1311
1311
  }
1312
1312
  case "on":
@@ -11237,7 +11237,7 @@ async function call(path2, v, init = {}) {
11237
11237
  "x-ghosty-ts": ts,
11238
11238
  "x-ghosty-sig": sig,
11239
11239
  "content-type": "application/json",
11240
- "user-agent": `ghosty-cli/${"0.85.0"}`
11240
+ "user-agent": `ghosty-cli/${"0.86.0"}`
11241
11241
  },
11242
11242
  body: method === "GET" ? void 0 : raw
11243
11243
  });
@@ -11571,6 +11571,28 @@ async function imageCmd(sub, a1, a2, v) {
11571
11571
  );
11572
11572
  }
11573
11573
 
11574
+ // src/commands/tts.ts
11575
+ init_api();
11576
+ init_util();
11577
+ init_i18n();
11578
+ import { writeFile as writeFile7 } from "node:fs/promises";
11579
+ var MAX_CHARS = 400;
11580
+ var VOICES = ["em_santa", "em_alex", "ef_dora"];
11581
+ async function ttsCmd(text, v) {
11582
+ const ex = 'ghosty tts "Hola, gracias por escribir." [--voice em_santa|em_alex|ef_dora] [--out voz.wav]';
11583
+ const said = text?.trim();
11584
+ if (!said) throw usage(t("Missing the text to say.", "Falta el texto a decir."), ex);
11585
+ if (said.length > MAX_CHARS) throw usage(t(`Too long: ${said.length} characters (max ${MAX_CHARS}). Split it into sentences.`, `Demasiado largo: ${said.length} caracteres (m\xE1x. ${MAX_CHARS}). P\xE1rtelo en frases.`), ex);
11586
+ const voice = v.voice ?? "em_santa";
11587
+ if (!VOICES.includes(voice)) throw usage(t(`Unknown voice: ${voice}.`, `Voz desconocida: ${voice}.`), `${t("Voices", "Voces")}: ${VOICES.join(", ")}`);
11588
+ const res = await request("/api/v2/me/tts", { body: { text: said, voice } });
11589
+ const bytes = Buffer.from(await res.arrayBuffer());
11590
+ const file = v.out ?? "voz.wav";
11591
+ await writeFile7(file, bytes);
11592
+ const durationMs = Number(res.headers.get("x-duration-ms") ?? 0);
11593
+ return out({ saved: file, voice, bytes: bytes.length, durationMs }, () => `${lavender("\u2713")} ${file} (${(durationMs / 1e3).toFixed(1)} s) ${dim(t(`\xB7 play it: afplay ${file}`, `\xB7 esc\xFAchalo: afplay ${file}`))}`);
11594
+ }
11595
+
11574
11596
  // src/commands/watch.ts
11575
11597
  init_api();
11576
11598
  init_util();
@@ -11652,7 +11674,7 @@ ${dim(`id ${w.id} \xB7 ${t("conversation", "conversaci\xF3n")} ${w.sessionId} \x
11652
11674
 
11653
11675
  // src/commands/data.ts
11654
11676
  init_api();
11655
- import { readdir as readdir2, readFile as readFile6, writeFile as writeFile7 } from "node:fs/promises";
11677
+ import { readdir as readdir2, readFile as readFile6, writeFile as writeFile8 } from "node:fs/promises";
11656
11678
  import { basename as basename3, join as join5 } from "node:path";
11657
11679
 
11658
11680
  // src/rows.ts
@@ -11821,7 +11843,7 @@ async function dbsExport(id, db, v) {
11821
11843
  const suggested = /filename="([^"]+)"/.exec(res.headers.get("content-disposition") ?? "")?.[1] ?? (format === "csv" ? `${db}-${v.table}.csv` : `${db}.sqlite`);
11822
11844
  const file = v.out ?? suggested;
11823
11845
  const bytes = Buffer.from(await res.arrayBuffer());
11824
- await writeFile7(file, bytes);
11846
+ await writeFile8(file, bytes);
11825
11847
  const countsHeader = res.headers.get("x-gs-row-counts");
11826
11848
  const counts = countsHeader ? JSON.parse(Buffer.from(countsHeader, "base64").toString("utf8")) : {};
11827
11849
  const rows = Object.entries(counts).map(([tb, n]) => ({ table: tb, rows: n }));
@@ -13432,6 +13454,16 @@ var HELPS_ES = {
13432
13454
  summary: "Ghosty para Chrome: cerrar la sesi\xF3n del navegador.",
13433
13455
  notes: ["Revoca todos los tokens de Ghosty para Chrome que tengan tus agentes de terminal (Claude Code, CLI, @ghostystudio/browser-mcp) y desconecta la extensi\xF3n. Para volver, copia un comando nuevo del panel de la extensi\xF3n."]
13434
13456
  },
13457
+ tts: {
13458
+ summary: "Oye la voz de la casa decir un texto (Kokoro, espa\xF1ol de M\xE9xico, con el l\xE9xico de pronunciaci\xF3n de Ghosty): la misma de las notas de voz. Guarda un .wav.",
13459
+ usage: ['ghosty tts "texto" [--voice em_santa|em_alex|ef_dora] [--out ARCHIVO]'],
13460
+ flags: [
13461
+ ["--voice V", "em_santa (hombre, la de la casa, default), em_alex (hombre) o ef_dora (mujer)"],
13462
+ ["--out FILE", "d\xF3nde guardar el .wav (default voz.wav)"]
13463
+ ],
13464
+ examples: ['ghosty tts "Hola, gracias por escribir. Te aviso por WhatsApp."', 'ghosty tts "Tu cita qued\xF3 el jueves a las cinco." --voice ef_dora --out cita.wav && afplay cita.wav'],
13465
+ notes: ["Hasta 400 caracteres y 60 frases por minuto. Las marcas (WhatsApp, Claude, GitHub, ghosty.studio\u2026) se escriben normal: el l\xE9xico corrige c\xF3mo suenan."]
13466
+ },
13435
13467
  image: {
13436
13468
  summary: "Im\xE1genes con IA (Gemini \xABNano Banana Pro\xBB) para los negocios de tu espacio de partner: generar, editar una que ya existe o componer con referencias; cobradas del saldo del espacio.",
13437
13469
  replace: [
@@ -14261,6 +14293,16 @@ var HELPS = {
14261
14293
  notes: ["Revokes every Ghosty for Chrome token your terminal agents hold (Claude Code, CLI, @ghostystudio/browser-mcp) and disconnects the extension. Copy a new command from the extension panel to reconnect."],
14262
14294
  learn: `${REF}#navegador`
14263
14295
  },
14296
+ tts: {
14297
+ summary: "Hear the house voice say a text (Kokoro, Mexican Spanish, with Ghosty's pronunciation lexicon): the same voice as the voice notes. Saves a .wav.",
14298
+ usage: ['ghosty tts "text" [--voice em_santa|em_alex|ef_dora] [--out FILE]'],
14299
+ flags: [
14300
+ ["--voice V", "em_santa (man, the house voice, default), em_alex (man) or ef_dora (woman)"],
14301
+ ["--out FILE", "where to save the .wav (default voz.wav)"]
14302
+ ],
14303
+ examples: ['ghosty tts "Hola, gracias por escribir. Te aviso por WhatsApp."', 'ghosty tts "Tu cita qued\xF3 el jueves a las cinco." --voice ef_dora --out cita.wav && afplay cita.wav'],
14304
+ notes: ["Up to 400 characters and 60 sentences per minute. Brand names (WhatsApp, Claude, GitHub, ghosty.studio\u2026) are written normally: the lexicon fixes how they sound."]
14305
+ },
14264
14306
  image: {
14265
14307
  summary: "AI images (Gemini \xABNano Banana Pro\xBB) for your partner businesses: generate, edit an existing one, or compose from references; charged to the space's balance.",
14266
14308
  usage: [
@@ -14860,7 +14902,7 @@ function helpText(cmd) {
14860
14902
 
14861
14903
  // src/update.ts
14862
14904
  init_i18n();
14863
- import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile9 } from "node:fs/promises";
14905
+ import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile10 } from "node:fs/promises";
14864
14906
  import { homedir as homedir3 } from "node:os";
14865
14907
  import { dirname as dirname3, join as join7 } from "node:path";
14866
14908
  var FILE = join7(process.env.XDG_CONFIG_HOME || join7(homedir3(), ".config"), "ghosty", "update.json");
@@ -14886,7 +14928,7 @@ async function latestVersion(current2) {
14886
14928
  const latest = (await r.json()).version;
14887
14929
  if (latest) cache2 = { latest, checkedAt: Date.now() };
14888
14930
  await mkdir5(dirname3(FILE), { recursive: true });
14889
- await writeFile9(FILE, JSON.stringify(cache2));
14931
+ await writeFile10(FILE, JSON.stringify(cache2));
14890
14932
  } catch {
14891
14933
  }
14892
14934
  }
@@ -14967,7 +15009,7 @@ function agentArgIndex(pos) {
14967
15009
  }
14968
15010
 
14969
15011
  // src/completion.ts
14970
- import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile10 } from "node:fs/promises";
15012
+ import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile11 } from "node:fs/promises";
14971
15013
  import { homedir as homedir4 } from "node:os";
14972
15014
  import { join as join8 } from "node:path";
14973
15015
  var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
@@ -15019,7 +15061,7 @@ async function names() {
15019
15061
  const workspaces = [...new Set(list2.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
15020
15062
  await mkdir6(join8(cacheFile(), ".."), { recursive: true }).catch(() => {
15021
15063
  });
15022
- await writeFile10(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
15064
+ await writeFile11(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
15023
15065
  });
15024
15066
  return { agents, workspaces };
15025
15067
  }
@@ -15106,7 +15148,7 @@ init_i18n();
15106
15148
  // src/commands/spec.ts
15107
15149
  var import_yaml = __toESM(require_dist(), 1);
15108
15150
  init_api();
15109
- import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile11 } from "node:fs/promises";
15151
+ import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile12 } from "node:fs/promises";
15110
15152
  import { join as join9, resolve as resolve2 } from "node:path";
15111
15153
  init_util();
15112
15154
  init_i18n();
@@ -15120,7 +15162,7 @@ async function agentsExport(id, v) {
15120
15162
  }
15121
15163
  const dir = resolve2(v.out);
15122
15164
  await mkdir7(dir, { recursive: true });
15123
- await writeFile11(join9(dir, "agent.yaml"), yaml);
15165
+ await writeFile12(join9(dir, "agent.yaml"), yaml);
15124
15166
  const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
15125
15167
  for (const slug of own) {
15126
15168
  const sk = await api(`/api/v2/agents/${enc13(id)}/skills/${enc13(slug)}`);
@@ -15243,7 +15285,7 @@ ${dim(t(`${r.totals.activeTurns} turn(s) running`, `${r.totals.activeTurns} turn
15243
15285
  init_api();
15244
15286
  init_auth();
15245
15287
  import { randomUUID as randomUUID2 } from "node:crypto";
15246
- import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile12 } from "node:fs/promises";
15288
+ import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile13 } from "node:fs/promises";
15247
15289
  import { homedir as homedir5 } from "node:os";
15248
15290
  import { dirname as dirname4, join as join10 } from "node:path";
15249
15291
  init_i18n();
@@ -15288,7 +15330,7 @@ async function visitorCookie(base, slug, fresh) {
15288
15330
  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);
15289
15331
  jar[slug] = cookie;
15290
15332
  await mkdir8(dirname4(jarPath()), { recursive: true });
15291
- await writeFile12(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
15333
+ await writeFile13(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
15292
15334
  return cookie;
15293
15335
  }
15294
15336
  async function visitorTurn(base, slug, cookie, text) {
@@ -15528,7 +15570,7 @@ ${domainLine(r, t("en", "es") === "es" ? "es" : "en").replace("<slug>", slug)}`)
15528
15570
 
15529
15571
  // src/commands/docs.ts
15530
15572
  init_api();
15531
- import { writeFile as writeFile13 } from "node:fs/promises";
15573
+ import { writeFile as writeFile14 } from "node:fs/promises";
15532
15574
 
15533
15575
  // ../docs/src/core/doc-blocks.ts
15534
15576
  var DOC_ENVELOPE_VERSION = 1;
@@ -15691,7 +15733,7 @@ ${t("Ids: ghosty me docs ls", "Ids: ghosty me docs ls")}`);
15691
15733
  }
15692
15734
  const named = /filename\*=UTF-8''([^;]+)/i.exec(res.headers.get("content-disposition") ?? "")?.[1];
15693
15735
  const file = v.out ?? (named ? decodeURIComponent(named) : fileName(id, format));
15694
- await writeFile13(file, bytes);
15736
+ await writeFile14(file, bytes);
15695
15737
  return out({ id, format, saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${file} (${bytes.length} bytes)`);
15696
15738
  }
15697
15739
  case "check": {
@@ -15717,7 +15759,7 @@ ${t("Ids: ghosty me docs ls", "Ids: ghosty me docs ls")}`);
15717
15759
  if (res.status === 204) return out({ id, block, segment, segments, saved: null }, () => dim(t("That block has no text to read.", "Ese bloque no tiene texto que leer.")));
15718
15760
  const bytes = Buffer.from(await res.arrayBuffer());
15719
15761
  const file = v.out ?? `${id}-${block}${segment !== null ? `-${segment}` : ""}.${audioExt(res.headers.get("content-type"))}`;
15720
- await writeFile13(file, bytes);
15762
+ await writeFile14(file, bytes);
15721
15763
  return out({ id, block, segment, segments, saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${file} (${bytes.length} bytes) ${dim(t(`\xB7 the block has ${segments} sentence(s): --segment 0\u2026${Math.max(segments - 1, 0)}`, `\xB7 el bloque tiene ${segments} frase(s): --segment 0\u2026${Math.max(segments - 1, 0)}`))}`);
15722
15764
  }
15723
15765
  case "restore": {
@@ -16945,6 +16987,7 @@ ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see:
16945
16987
  partner ls | enable | credentials | agent | tenants | audit | try
16946
16988
  image generate "prompt" An AI image for your businesses, charged to the space's balance [--ref URL (repeat, keeps the character)] [--style-ref URL] [--scene URL] [--text "-50 %"] [--direction D]\u2026 [--variants N] [--size 1K|2K|4K] [--aspect 16:9] [--tenant ID] [--out FILE]
16947
16989
  image edit URL "change" Edit an existing image: only that change, the rest stays [--ref URL] [--text T] [--variants N] [--size] [--aspect] [--tenant ID] [--out FILE]
16990
+ tts "text" Hear the house voice (Mexican Spanish, em_santa) say it, to a .wav [--voice em_santa|em_alex|ef_dora] [--out FILE]
16948
16991
 
16949
16992
  ${bold("Your account")}
16950
16993
  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]
@@ -17159,6 +17202,7 @@ ${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ve
17159
17202
  partner ls | enable | credentials | agent | tenants | audit | try
17160
17203
  image generate "prompt" Una imagen con IA para tus negocios, cobrada del saldo del espacio [--ref URL (se repite, conserva al personaje)] [--style-ref URL] [--scene URL] [--text "-50 %"] [--direction D]\u2026 [--variants N] [--size 1K|2K|4K] [--aspect 16:9] [--tenant ID] [--out ARCHIVO]
17161
17204
  image edit URL "cambio" Edita una imagen que ya existe: s\xF3lo ese cambio, lo dem\xE1s se queda [--ref URL] [--text T] [--variants N] [--size] [--aspect] [--tenant ID] [--out ARCHIVO]
17205
+ tts "texto" Oye la voz de la casa (espa\xF1ol de M\xE9xico, em_santa) decirlo, en un .wav [--voice em_santa|em_alex|ef_dora] [--out ARCHIVO]
17162
17206
 
17163
17207
  ${bold("Tu cuenta")}
17164
17208
  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]
@@ -17184,7 +17228,7 @@ ${bold("Opciones globales")}
17184
17228
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
17185
17229
  ${dim("Nota: `ghosty` puede chocar con otro binario en tu PATH (p. ej. la TUI de Ghosty). Rev\xEDsalo con `which -a ghosty`; corre `npx @ghostystudio/cli` para ir a la segura.")}
17186
17230
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
17187
- var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "coupons", "ads", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "watch", "teams", "partner", "image", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
17231
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "coupons", "ads", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "watch", "teams", "partner", "image", "tts", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
17188
17232
  var AGENTS_SUBS = ["ls", "create", "get", "show", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box", "token", "memory", "transfer"];
17189
17233
  function unknown(what, input, known, helpCmd) {
17190
17234
  const s2 = suggest(input, known);
@@ -17501,7 +17545,7 @@ async function main() {
17501
17545
  const tokens = splitTokens(pos, v);
17502
17546
  globals.token = tokens.session;
17503
17547
  globals.profile = v.profile;
17504
- if (v.version) return out({ version: "0.85.0" }, () => "0.85.0");
17548
+ if (v.version) return out({ version: "0.86.0" }, () => "0.86.0");
17505
17549
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
17506
17550
  const context = (await readConfig()).workspace;
17507
17551
  if (!explicitToken()?.startsWith("gat_")) {
@@ -17646,6 +17690,8 @@ async function main() {
17646
17690
  return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn }, pos[4]);
17647
17691
  case "image":
17648
17692
  return imageCmd(sub, a1, a2, v);
17693
+ case "tts":
17694
+ return ttsCmd(sub, v);
17649
17695
  case "usage":
17650
17696
  if (v.images) return imageUsage(v);
17651
17697
  if (v.boxes && v.by === "template") return fleetBoxes();
@@ -17702,7 +17748,7 @@ async function main() {
17702
17748
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
17703
17749
  }
17704
17750
  }
17705
- var notice = updateNotice("0.85.0").catch(() => null);
17751
+ var notice = updateNotice("0.86.0").catch(() => null);
17706
17752
  main().then(
17707
17753
  async () => {
17708
17754
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.85.0",
3
+ "version": "0.86.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": {