@ghostystudio/cli 0.33.0 → 0.34.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 +1 -0
  2. package/dist/index.js +31 -20
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -135,6 +135,7 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
135
135
  - `turns ls --session S`, and on Claude agents a «resumed» column: ✓ when the turn kept its SDK session, ⚠ from scratch (orphan|stale|replaced) when it lost its memory. `turns show` says the same, with the Claude CLI version.
136
136
  - `partner credentials create|rotate --env-file PATH` (mode 600, nothing printed) or `--format env` (pipe to `fly secrets import`). The secret's env var is now `GHOSTY_PARTNER_KEY_SECRET` (`GHOSTY_PARTNER_SECRET` still works).
137
137
  - `partner agent apply --prompt-file prompt.md`; `partner try --verbose` prints each tool's arguments and milliseconds.
138
+ - `partner agent get|apply --agent SLUG` and `partner try --agent SLUG`: several agents per partner (e.g. `nik-admin`, `nik-public`). `channels whatsapp link … --partner-tenant <externalId> --partner-agent <slug>`: a business's WhatsApp Business number answered by that partner agent.
138
139
  - Empty `partner ls|credentials ls|tenants ls` say what to do next. `login --device --profile <client>` explains how to sign in a client's account (they approve the link).
139
140
 
140
141
  ### Changes in 0.27.1
package/dist/index.js CHANGED
@@ -8367,7 +8367,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8367
8367
  const req = httpsRequest(`${base}${path}`, {
8368
8368
  method: "POST",
8369
8369
  timeout: timeoutMs,
8370
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.33.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8370
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.34.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8371
8371
  }, (res) => {
8372
8372
  let buf = "";
8373
8373
  res.on("data", (c) => buf += c);
@@ -8409,7 +8409,7 @@ async function request(path, opts = {}, retried = false) {
8409
8409
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8410
8410
  headers: {
8411
8411
  authorization: `Bearer ${token}`,
8412
- "user-agent": `ghosty-cli/${"0.33.0"}`,
8412
+ "user-agent": `ghosty-cli/${"0.34.0"}`,
8413
8413
  "accept-language": lang(),
8414
8414
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8415
8415
  ...opts.headers
@@ -9495,9 +9495,9 @@ ${bold("Partner space")} ${dim("(you, signed in as the space owner)")}
9495
9495
  partner credentials revoke <keyId> Revoke it (idempotent)
9496
9496
  partner audit Who did what, when
9497
9497
 
9498
- ${bold("Shared agent (solo-MCP)")}
9499
- partner agent get The agent template and its version
9500
- partner agent apply --file agent.json Create or update it (idempotent) [--prompt-file prompt.md]
9498
+ ${bold("Shared agents (solo-MCP)")} ${dim("(--agent SLUG picks another agent of the partner: nik-admin, nik-public\u2026)")}
9499
+ partner agent get [--agent SLUG] The agent template and its version
9500
+ partner agent apply --file agent.json Create or update it (idempotent) [--prompt-file prompt.md] [--agent SLUG]
9501
9501
 
9502
9502
  ${bold("Tenants")} ${dim("(your customers, by externalId \u2014 config only, no box)")}
9503
9503
  partner tenants ls
@@ -9506,7 +9506,7 @@ ${bold("Tenants")} ${dim("(your customers, by externalId \u2014 config only, no
9506
9506
  partner tenants rm <externalId> Deletes its config AND its memory
9507
9507
 
9508
9508
  ${bold("Try it")}
9509
- partner try <externalId> "message" One turn [--group END_USER_ID] [--turn-token SHORT_LIVED_TOKEN] [--verbose: tool args and ms]
9509
+ partner try <externalId> "message" One turn [--agent SLUG] [--group END_USER_ID] [--turn-token SHORT_LIVED_TOKEN] [--verbose: tool args and ms]
9510
9510
  (the turn token goes to YOUR MCP as \${turn.token}; --token also works here)
9511
9511
 
9512
9512
  ${bold("Options")}
@@ -9527,9 +9527,9 @@ ${bold("Espacio de partner")} ${dim("(t\xFA, con sesi\xF3n como due\xF1a del es
9527
9527
  partner credentials revoke <keyId> La revoca (idempotente)
9528
9528
  partner audit Qui\xE9n hizo qu\xE9 y cu\xE1ndo
9529
9529
 
9530
- ${bold("Agente compartido (solo-MCP)")}
9531
- partner agent get La plantilla del agente y su versi\xF3n
9532
- partner agent apply --file agent.json Lo crea o lo actualiza (idempotente) [--prompt-file prompt.md]
9530
+ ${bold("Agentes compartidos (solo-MCP)")} ${dim("(--agent SLUG elige otro agente del partner: nik-admin, nik-public\u2026)")}
9531
+ partner agent get [--agent SLUG] La plantilla del agente y su versi\xF3n
9532
+ partner agent apply --file agent.json Lo crea o lo actualiza (idempotente) [--prompt-file prompt.md] [--agent SLUG]
9533
9533
 
9534
9534
  ${bold("Tenants")} ${dim("(tus clientes, por externalId \u2014 s\xF3lo config, sin caja)")}
9535
9535
  partner tenants ls
@@ -9538,7 +9538,7 @@ ${bold("Tenants")} ${dim("(tus clientes, por externalId \u2014 s\xF3lo config,
9538
9538
  partner tenants rm <externalId> Borra su config Y su memoria
9539
9539
 
9540
9540
  ${bold("Pru\xE9balo")}
9541
- partner try <externalId> "mensaje" Un turno [--group END_USER_ID] [--turn-token TOKEN_DE_VIDA_CORTA] [--verbose: args y ms de cada tool]
9541
+ partner try <externalId> "mensaje" Un turno [--agent SLUG] [--group END_USER_ID] [--turn-token TOKEN_DE_VIDA_CORTA] [--verbose: args y ms de cada tool]
9542
9542
  (el token del turno llega a TU MCP como \${turn.token}; --token tambi\xE9n sirve aqu\xED)
9543
9543
 
9544
9544
  ${bold("Opciones")}
@@ -9569,7 +9569,7 @@ async function call(path, v, init = {}) {
9569
9569
  "x-ghosty-ts": ts,
9570
9570
  "x-ghosty-sig": sig,
9571
9571
  "content-type": "application/json",
9572
- "user-agent": `ghosty-cli/${"0.33.0"}`
9572
+ "user-agent": `ghosty-cli/${"0.34.0"}`
9573
9573
  },
9574
9574
  body: method === "GET" ? void 0 : raw
9575
9575
  });
@@ -9637,11 +9637,11 @@ async function credentialsCmd(sub, id, v) {
9637
9637
  }
9638
9638
  async function agentCmd(sub, v) {
9639
9639
  if (!sub || sub === "get") {
9640
- const r = await json("/api/v2/partner/agent", v);
9640
+ const r = await json(`/api/v2/partner/agent${v.agent ? `?slug=${enc5(v.agent)}` : ""}`, v);
9641
9641
  return out(r, () => {
9642
9642
  const a = r.agent;
9643
9643
  return [
9644
- `${bold(a.name)} ${dim(`v${a.version} \xB7 solo-MCP \xB7 ${a.engine}/${a.model}`)}`,
9644
+ `${bold(a.name)} ${dim(`${a.slug ? `${a.slug} \xB7 ` : ""}v${a.version} \xB7 solo-MCP \xB7 ${a.engine}/${a.model} \xB7 ${a.id}`)}`,
9645
9645
  `MCP: ${a.mcp.map((m) => `${m.name} (${m.url})`).join(", ") || t("none", "ninguno")}`,
9646
9646
  dim(a.prompt.slice(0, 300))
9647
9647
  ].join("\n");
@@ -9654,6 +9654,7 @@ async function agentCmd(sub, v) {
9654
9654
  body.prompt = await readFile5(v["prompt-file"], "utf8");
9655
9655
  if (body.prompt.length > 6e4) throw usage(t(`The prompt has ${body.prompt.length} chars; the limit is 60,000.`, `El prompt tiene ${body.prompt.length} caracteres; el tope es 60,000.`));
9656
9656
  }
9657
+ if (v.agent) body.slug = v.agent;
9657
9658
  const r = await json("/api/v2/partner/agent", v, { method: "PUT", body });
9658
9659
  return out(r, () => `${lavender("\u2713")} ${r.created ? t("Created", "Creado") : r.changed ? t("Updated", "Actualizado") : t("No changes", "Sin cambios")} \xB7 v${r.agent.version}`);
9659
9660
  }
@@ -9688,7 +9689,7 @@ async function tryCmd(externalId, text, v) {
9688
9689
  if (!externalId || !text) throw usage(t("Missing externalId or message.", "Falta el externalId o el mensaje."), t("Example", "Ejemplo") + ': ghosty partner try org_123 "\xBFqu\xE9 citas tengo hoy?"');
9689
9690
  const res = await call("/api/v2/partner/turns", v, {
9690
9691
  method: "POST",
9691
- body: { tenant: { externalId }, groupId: v.group ?? "cli", text, ...v.token ? { token: v.token } : {} }
9692
+ body: { tenant: { externalId }, groupId: v.group ?? "cli", text, ...v.token ? { token: v.token } : {}, ...v.agent ? { agent: v.agent } : {} }
9692
9693
  });
9693
9694
  const dec = new TextDecoder();
9694
9695
  let buf = "";
@@ -10109,7 +10110,9 @@ function skipReason(r) {
10109
10110
  paused: ["conversation paused on the board", "conversaci\xF3n pausada en el tablero"],
10110
10111
  reaction: ["only a reaction", "s\xF3lo una reacci\xF3n"],
10111
10112
  channel_off: ["WhatsApp channel off for this agent", "canal de WhatsApp apagado en este agente"],
10112
- no_quota: ["out of balance", "sin saldo"]
10113
+ no_quota: ["out of balance", "sin saldo"],
10114
+ partner_token: ["partner did not issue a turn token", "el partner no dio token de turno"],
10115
+ partner_busy: ["partner at its concurrent-turn limit", "el partner lleg\xF3 a su tope de turnos a la vez"]
10113
10116
  };
10114
10117
  const pair = es[r];
10115
10118
  return pair ? t(pair[0], pair[1]) : r;
@@ -10561,8 +10564,11 @@ ${dim(applyHint(`ghosty channels whatsapp link ${id} --integration ${v.integrati
10561
10564
  }
10562
10565
  if (sub === "link" && v.answer !== "on" && v.answer !== "off") throw usage(t("Missing --answer on|off.", "Falta --answer on|off."), t("on = this agent answers real customers \xB7 off = it only records (add --forward so gs still sees the conversations)", "on = este agente contesta a clientes reales \xB7 off = s\xF3lo registra (agrega --forward para que gs siga viendo las conversaciones)"));
10563
10566
  const path = `/api/v2/agents/${enc7(id)}/whatsapp/link`;
10564
- const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {}, ...adsOnly !== void 0 ? { adsOnly } : {} } };
10565
- const cmd = `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}` : ""}`;
10567
+ if (v["partner-agent"] && !v["partner-tenant"]) throw usage(t("--partner-agent needs --partner-tenant <externalId>.", "--partner-agent necesita --partner-tenant <externalId>."));
10568
+ const partner = v["partner-tenant"] ? { partnerExternalId: v["partner-tenant"], ...v["partner-agent"] ? { partnerAgentSlug: v["partner-agent"] } : {} } : {};
10569
+ const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {}, ...adsOnly !== void 0 ? { adsOnly } : {}, ...partner } };
10570
+ const partnerFlags = v["partner-tenant"] ? ` --partner-tenant ${v["partner-tenant"]}${v["partner-agent"] ? ` --partner-agent ${v["partner-agent"]}` : ""}` : "";
10571
+ const cmd = `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${partnerFlags}` : ""}`;
10566
10572
  const plan = await api(path, { method: "POST", body: { ...body, dryRun: true } });
10567
10573
  const text = `${plan.plan.map((x) => serverText(x)).join("\n")}
10568
10574
  ${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (${t("as", "como")} ${plan.formmy.by})`)}`;
@@ -10992,6 +10998,7 @@ var HELPS_ES = {
10992
10998
  ["--media", "history: s\xF3lo re-copia los archivos que faltan (fotos, PDF, audios) de las conversaciones ya importadas"],
10993
10999
  ["--answer on|off", "link (obligatorio, sin default): on = este agente contesta a clientes reales; off = s\xF3lo registra"],
10994
11000
  ["--forward", "link --answer off: igual hace que Formmy reenv\xEDe, para que gs vea las conversaciones"],
11001
+ ["--partner-tenant ID / --partner-agent SLUG", "link: el n\xFAmero es de un negocio del partner (externalId); <id> es el agente de ese partner (ghosty partner agent get --agent SLUG) y contesta solo-MCP con un token de turno que gs le pide al partner"],
10995
11002
  ["--from easybits:ID", "import: directo del agente de EasyBits ($EASYBITS_API_KEY del due\xF1o); primero dice si su sesi\xF3n est\xE1 viva, sus llaves y grupos; las llaves s\xF3lo viven en memoria"],
10996
11003
  ["--from-file F", "import: {authCreds, authKeys, phone, groups} exportado de EasyBits"],
10997
11004
  ["--from-dir DIR", "import: una carpeta de auth multi-archivo de Baileys (creds.json + un archivo por llave; p. ej. store/auth de nanoclaw)"],
@@ -11499,6 +11506,7 @@ HELPS.channels = {
11499
11506
  ["--media", "history: only re-copy the missing files (photos, PDFs, audio) of conversations already imported"],
11500
11507
  ["--answer on|off", "link (required, no default): on = this agent answers real customers; off = it only records"],
11501
11508
  ["--forward", "link --answer off: still make Formmy forward, so gs sees the conversations"],
11509
+ ["--partner-tenant ID / --partner-agent SLUG", "link: the number belongs to a partner's business (externalId); <id> is that partner's agent (ghosty partner agent get --agent SLUG) and it answers solo-MCP with a turn token gs asks the partner for"],
11502
11510
  ["--from easybits:ID", "import: straight from the EasyBits agent (owner's $EASYBITS_API_KEY); first prints whether its session is alive, its keys and groups; the keys stay in memory"],
11503
11511
  ["--from-file F", "import: {authCreds, authKeys, phone, groups} exported from EasyBits"],
11504
11512
  ["--from-dir DIR", "import: a Baileys multi-file auth folder (creds.json + one file per key; e.g. nanoclaw's store/auth)"],
@@ -12601,7 +12609,7 @@ async function imageUsage(v) {
12601
12609
  gap ? red(t(" \u26A0\uFE0F far more deliveries than charges: the meter is probably not reporting", " \u26A0\uFE0F muchas m\xE1s entregas que cargos: el medidor probablemente no est\xE1 reportando")) : "",
12602
12610
  "",
12603
12611
  table(r.byPlan.map((p) => ({ plan: p.plan, ...cols(p) })), ["plan", "charges", "delivered", "hd", "byok", "usd", "table usd", "avg"]),
12604
- ...r.byUser ? ["", table(r.byUser.map((u) => ({ user: u.user, plan: u.plan, ...cols(u), bag: u.budget ? `$${u.budget.usedMxn.toFixed(0)}/$${u.budget.limitMxn} MXN` : "" })), ["user", "plan", "charges", "delivered", "hd", "usd", "table usd", "bag"])] : [],
12612
+ ...r.byUser ? ["", table(r.byUser.map((u) => ({ user: u.user, plan: u.plan, ...cols(u), bag: u.budget ? `$${u.budget.usedMxn.toFixed(2)}/$${u.budget.limitMxn} MXN` : "" })), ["user", "plan", "charges", "delivered", "hd", "usd", "table usd", "bag"])] : [],
12605
12613
  dim(t("delivered = agent images in the library (includes forwarded photos: a ceiling) \xB7 usd = stored (OpenAI's real usage when sent) \xB7 table = IMAGE_COST_USD", "entregadas = im\xE1genes del agente en la biblioteca (incluye fotos reenviadas: es un techo) \xB7 usd = guardado (el usage real de OpenAI cuando llega) \xB7 tabla = IMAGE_COST_USD"))
12606
12614
  ].filter((x) => x !== "").join("\n");
12607
12615
  });
@@ -13082,6 +13090,8 @@ async function main() {
13082
13090
  publish: { type: "boolean" },
13083
13091
  draft: { type: "boolean" },
13084
13092
  "ads-only": { type: "string" },
13093
+ "partner-tenant": { type: "string" },
13094
+ "partner-agent": { type: "string" },
13085
13095
  list: { type: "boolean" },
13086
13096
  "only-ads": { type: "boolean" },
13087
13097
  phones: { type: "string" },
@@ -13106,13 +13116,14 @@ async function main() {
13106
13116
  const tokens = splitTokens(pos, v);
13107
13117
  globals.token = tokens.session;
13108
13118
  globals.profile = v.profile;
13109
- if (v.version) return out({ version: "0.33.0" }, () => "0.33.0");
13119
+ if (v.version) return out({ version: "0.34.0" }, () => "0.34.0");
13110
13120
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
13111
13121
  const context = (await readConfig()).workspace;
13112
13122
  if (!explicitToken()?.startsWith("gat_")) {
13113
13123
  const at = agentArgIndex(pos);
13114
13124
  if (at !== null && pos[at]) pos[at] = await resolveAgent(pos[at], context);
13115
13125
  for (const f of ["like", "prompt-base", "agent"]) {
13126
+ if (f === "agent" && pos[0] === "partner") continue;
13116
13127
  const val = v[f];
13117
13128
  if (typeof val === "string" && val !== "none") v[f] = await resolveAgent(val, context);
13118
13129
  }
@@ -13254,7 +13265,7 @@ async function main() {
13254
13265
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
13255
13266
  }
13256
13267
  }
13257
- var notice = updateNotice("0.33.0").catch(() => null);
13268
+ var notice = updateNotice("0.34.0").catch(() => null);
13258
13269
  main().then(
13259
13270
  async () => {
13260
13271
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.33.0",
3
+ "version": "0.34.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": {