@ghostystudio/cli 0.46.0 → 0.47.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 +64 -16
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -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.47.0
107
+
108
+ - `agents set --quote` takes VAT rules for the house `cotizar` tool: `iva-incluido=true` (prices already include VAT), `iva.CATEGORY=16` (VAT for that category), `solo-factura=true` (VAT only when the customer asks for an invoice).
109
+ - New `agents set --catalog products.csv|json`: the house `cotizar` tool's catalog (sku, nombre, precio, unidad, categoria). The category comes from here, not from the model.
110
+
106
111
  ### Changes in 0.46.0
107
112
 
108
113
  - New `staff ls|assign|unassign <staff-email> <client-email>` (Ghosty staff): a staff member assigned to a client account sees and runs its agents (with or without a workspace), documents, databases and boards as themselves — no keys, billing or transfers, every write logged. The client sees «Your Ghosty account manager».
package/dist/index.js CHANGED
@@ -8422,7 +8422,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8422
8422
  const req = httpsRequest(`${base}${path}`, {
8423
8423
  method: "POST",
8424
8424
  timeout: timeoutMs,
8425
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.46.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8425
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.47.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8426
8426
  }, (res) => {
8427
8427
  let buf = "";
8428
8428
  res.on("data", (c) => buf += c);
@@ -8464,7 +8464,7 @@ async function request(path, opts = {}, retried = false) {
8464
8464
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8465
8465
  headers: {
8466
8466
  authorization: `Bearer ${token}`,
8467
- "user-agent": `ghosty-cli/${"0.46.0"}`,
8467
+ "user-agent": `ghosty-cli/${"0.47.0"}`,
8468
8468
  "accept-language": lang(),
8469
8469
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8470
8470
  ...opts.headers
@@ -8764,6 +8764,36 @@ async function agentsGet(id, fields, promptOut) {
8764
8764
  const q = fields ? `?fields=${enc2(fields)}` : "?full=1";
8765
8765
  out(await api(agentPath(id, q)));
8766
8766
  }
8767
+ function parseCsv(text) {
8768
+ const rows = [];
8769
+ let row = [], cell = "", q = false;
8770
+ for (let i = 0; i < text.length; i++) {
8771
+ const c = text[i];
8772
+ if (q) {
8773
+ if (c === '"' && text[i + 1] === '"') {
8774
+ cell += '"';
8775
+ i++;
8776
+ } else if (c === '"') q = false;
8777
+ else cell += c;
8778
+ } else if (c === '"') q = true;
8779
+ else if (c === ",") {
8780
+ row.push(cell);
8781
+ cell = "";
8782
+ } else if (c === "\n" || c === "\r") {
8783
+ if (c === "\r" && text[i + 1] === "\n") i++;
8784
+ row.push(cell);
8785
+ cell = "";
8786
+ if (row.some((x) => x.trim())) rows.push(row);
8787
+ row = [];
8788
+ } else cell += c;
8789
+ }
8790
+ row.push(cell);
8791
+ if (row.some((x) => x.trim())) rows.push(row);
8792
+ const [head, ...body] = rows;
8793
+ if (!head) return [];
8794
+ const keys = head.map((h) => h.trim().toLowerCase());
8795
+ return body.map((r) => Object.fromEntries(keys.map((k, i) => [k, k === "precio" ? Number((r[i] ?? "").replace(/[$,\s]/g, "")) : (r[i] ?? "").trim()])));
8796
+ }
8767
8797
  async function agentsSet(id, v) {
8768
8798
  id = need(id, "agent id", "ghosty agents set <agent-id> --prompt-file PROMPT.md");
8769
8799
  const done = [];
@@ -8864,19 +8894,32 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
8864
8894
  body.autoStage = v["auto-stage"] === "on";
8865
8895
  }
8866
8896
  if (v.quote) {
8867
- const alias = { iva: "ivaPct", vigencia: "vigenciaDias", prefix: "folioPrefijo", prefijo: "folioPrefijo", currency: "moneda", company: "empresa", phone: "telefono", email: "correo", address: "direccion", notes: "notas" };
8897
+ const alias = { iva: "ivaPct", vigencia: "vigenciaDias", prefix: "folioPrefijo", prefijo: "folioPrefijo", currency: "moneda", company: "empresa", phone: "telefono", email: "correo", address: "direccion", notes: "notas", "iva-incluido": "ivaIncluido", "solo-factura": "ivaSoloConFactura" };
8868
8898
  const cot = {};
8869
- for (const part of v.quote.split(/,(?=\s*[a-zA-Z]+=)/)) {
8870
- const m = /^\s*([a-zA-Z]+)=(.*)$/.exec(part);
8899
+ let cats = null;
8900
+ for (const part of v.quote.split(/,(?=\s*[a-zA-Z-]+(?:\.[^=,]+)?=)/)) {
8901
+ const m = /^\s*([a-zA-Z-]+)(?:\.([^=]+))?=(.*)$/.exec(part);
8871
8902
  if (!m) throw usage(t(`Bad --quote: ${part}.`, `--quote inv\xE1lido: ${part}.`), "--quote iva=0,prefix=SFI,vigencia=15");
8872
- const key = alias[m[1]] ?? m[1];
8873
- const val = m[2].trim().replace(/^"(.*)"$/, "$1");
8874
- cot[key] = key === "ivaPct" || key === "vigenciaDias" ? Number(val) : val;
8903
+ const [, rawKey, category, rawVal] = m;
8904
+ const val = rawVal.trim().replace(/^"(.*)"$/, "$1");
8905
+ if (rawKey.toLowerCase() === "iva" && category) {
8906
+ if (!cats) cats = { ...(await api(agentPath(id, "?fields=cotizacion"))).cotizacion?.ivaPorCategoria ?? {} };
8907
+ if (val === "") delete cats[category];
8908
+ else cats[category] = Number(val);
8909
+ continue;
8910
+ }
8911
+ const key = alias[rawKey] ?? rawKey;
8912
+ cot[key] = key === "ivaPct" || key === "vigenciaDias" ? Number(val) : key === "ivaIncluido" || key === "ivaSoloConFactura" ? /^(true|on|yes|si|sí|1)$/i.test(val) : val;
8875
8913
  }
8914
+ if (cats) cot.ivaPorCategoria = cats;
8876
8915
  body.cotizacion = cot;
8877
8916
  }
8917
+ if (v.catalog) {
8918
+ const raw = await readFile3(resolve(v.catalog), "utf8");
8919
+ body.catalogo = v.catalog.toLowerCase().endsWith(".json") ? JSON.parse(raw) : parseCsv(raw);
8920
+ }
8878
8921
  if (!Object.keys(body).length && done.length) return out({ id, updated: done }, () => `${lavender("\u2713")} ${t("Updated", "Actualizado")}: ${done.join(" \xB7 ")}`);
8879
- if (!Object.keys(body).length) throw usage(t("Nothing to change.", "Nada que cambiar."), t("Use", "Usa") + " --name, --engine, --model, --prompt, --prompt-file, --env, --unset-env, --channels, --toolsets, --tool, --connect, --disconnect, --voice, --voice-replies, --auto-stage, --quote, --bag " + t("or", "o") + " --avatar.");
8922
+ if (!Object.keys(body).length) throw usage(t("Nothing to change.", "Nada que cambiar."), t("Use", "Usa") + " --name, --engine, --model, --prompt, --prompt-file, --env, --unset-env, --channels, --toolsets, --tool, --connect, --disconnect, --voice, --voice-replies, --auto-stage, --quote, --catalog, --bag " + t("or", "o") + " --avatar.");
8880
8923
  const r = await api(agentPath(id), { method: "PATCH", body });
8881
8924
  out(r, () => `${lavender("\u2713")} ${t("Updated", "Actualizado")}: ${[...Object.keys(body), ...done].join(", ")}.`);
8882
8925
  }
@@ -9738,7 +9781,7 @@ async function call(path, v, init = {}) {
9738
9781
  "x-ghosty-ts": ts,
9739
9782
  "x-ghosty-sig": sig,
9740
9783
  "content-type": "application/json",
9741
- "user-agent": `ghosty-cli/${"0.46.0"}`
9784
+ "user-agent": `ghosty-cli/${"0.47.0"}`
9742
9785
  },
9743
9786
  body: method === "GET" ? void 0 : raw
9744
9787
  });
@@ -11225,7 +11268,8 @@ var HELPS_ES = {
11225
11268
  ["--voice E:ID", "set: voz de las notas de voz: elevenlabs:<voiceId> o kokoro:em_santa|em_alex|ef_dora"],
11226
11269
  ["--voice-replies M", "set: auto = contesta con nota de voz cuando el cliente manda o pide audio; off = siempre texto"],
11227
11270
  ["--auto-stage on|off", "set: mueve solo las tarjetas del tablero tras cada turno del cliente (una llamada extra al modelo por turno)"],
11228
- ["--quote k=v,\u2026", "set: config de la tool de casa `cotizar` \u2014 iva (0-100), vigencia (d\xEDas), prefix (folio), moneda, empresa, rfc, telefono, correo, direccion, notas, color. Vac\xEDo lo borra (rfc=). El contador de folio no se toca"],
11271
+ ["--quote k=v,\u2026", "set: config de la tool de casa `cotizar` \u2014 iva (0-100), iva-incluido=true (los precios del cat\xE1logo ya traen IVA), iva.CATEGORIA=16 (IVA de esa categor\xEDa del cat\xE1logo; iva.CATEGORIA= la quita), solo-factura=true (IVA s\xF3lo si el cliente pide factura), vigencia (d\xEDas), prefix (folio), moneda, empresa, rfc, telefono, correo, direccion, notas, color. Vac\xEDo lo borra (rfc=). El contador de folio no se toca"],
11272
+ ["--catalog ARCHIVO", "set: el cat\xE1logo de la tool de casa `cotizar` desde un CSV (sku,nombre,precio[,unidad,categoria,descripcion]) o una lista JSON; reemplaza al anterior. Su `categoria` decide las reglas de IVA"],
11229
11273
  ["--verbose", "doctor: lista tambi\xE9n las revisiones que pasaron"],
11230
11274
  ["--user EMAIL", "doctor (staff de Ghosty): los agentes de esa persona \u2014 doctor, \xFAltimos turnos (estado, segundos, error y \xABfetched\xBB: si su app pidi\xF3 el hilo despu\xE9s de que termin\xF3 el turno), plan/bolsa y cajas (marca duplicadas); queda en bit\xE1cora"],
11231
11275
  ["--turns N", "doctor --user: cu\xE1ntos turnos recientes por agente (default 8, m\xE1x. 50)"],
@@ -11664,7 +11708,8 @@ var HELPS = {
11664
11708
  ["--voice E:ID", "set: voice for voice notes: elevenlabs:<voiceId> or kokoro:em_santa|em_alex|ef_dora"],
11665
11709
  ["--voice-replies M", "set: auto = answer with a voice note when the customer sends or asks for audio; off = always text"],
11666
11710
  ["--auto-stage on|off", "set: move board cards by itself after each customer turn (one extra model call per turn)"],
11667
- ["--quote k=v,\u2026", "set: config of the house `cotizar` tool \u2014 iva (0-100), vigencia (days), prefix (folio), moneda, empresa, rfc, telefono, correo, direccion, notas, color. Empty value clears it (rfc=). The folio counter is never touched"],
11711
+ ["--quote k=v,\u2026", "set: config of the house `cotizar` tool \u2014 iva (0-100), iva-incluido=true (catalog prices already include VAT), iva.CATEGORY=16 (VAT for that catalog category; iva.CATEGORY= removes it), solo-factura=true (VAT only when the customer asks for an invoice), vigencia (days), prefix (folio), moneda, empresa, rfc, telefono, correo, direccion, notas, color. Empty value clears it (rfc=). The folio counter is never touched"],
11712
+ ["--catalog FILE", "set: the house `cotizar` tool's catalog from a CSV (sku,nombre,precio[,unidad,categoria,descripcion]) or JSON list; replaces the previous one. Its `categoria` drives the VAT rules"],
11668
11713
  ["--verbose", "doctor: also list the checks that passed"],
11669
11714
  ["--user EMAIL", "doctor (Ghosty staff): that person's agents \u2014 doctor, latest turns (state, seconds, error, and \xABfetched\xBB: whether their app asked for the thread after the turn ended), plan/bag and boxes (duplicates flagged); logged"],
11670
11715
  ["--turns N", "doctor --user: how many recent turns per agent (default 8, max 50)"],
@@ -13370,7 +13415,8 @@ ${bold("Agents")}
13370
13415
  [--connect google-drive,\u2026] [--disconnect \u2026] give/remove access to your connectors
13371
13416
  [--channels wa=on,teams=off] turn channels on/off (wa = personal WhatsApp)
13372
13417
  [--voice elevenlabs:ID | kokoro:em_santa] [--voice-replies auto|off] [--auto-stage on|off]
13373
- [--quote iva=0,prefix=SFI,vigencia=15] house cotizar tool config (issuer, VAT, folio)
13418
+ [--quote iva=16,iva-incluido=true,iva.GRANEL=16,solo-factura=true,prefix=SFI] house cotizar tool config (issuer, VAT rules, folio)
13419
+ [--catalog productos.csv|json] its catalog: sku,nombre,precio[,unidad,categoria]
13374
13420
  [--toolsets crm,sales | +crm,-sales] gs tool groups it gets
13375
13421
  [--prompt-base AGENT|none --prompt-overlay FILE [--dry-run]] prompt = another agent's + edits, kept in sync
13376
13422
  [--tool wa:tool=off,\u2026] [--bag NAME|none] [--avatar URL|FILE] a tool per channel \xB7 its token bag (staff) \xB7 its face in Teams
@@ -13515,7 +13561,8 @@ ${bold("Agentes")}
13515
13561
  [--connect google-drive,\u2026] [--disconnect \u2026] dale o qu\xEDtale acceso a tus conectores
13516
13562
  [--channels wa=on,teams=off] prende/apaga canales (wa = WhatsApp personal)
13517
13563
  [--voice elevenlabs:ID | kokoro:em_santa] [--voice-replies auto|off] [--auto-stage on|off]
13518
- [--quote iva=0,prefix=SFI,vigencia=15] config de la tool de casa cotizar (emisor, IVA, folio)
13564
+ [--quote iva=16,iva-incluido=true,iva.GRANEL=16,solo-factura=true,prefix=SFI] config de la tool de casa cotizar (emisor, reglas de IVA, folio)
13565
+ [--catalog productos.csv|json] su cat\xE1logo: sku,nombre,precio[,unidad,categoria]
13519
13566
  [--toolsets crm,sales | +crm,-sales] grupos de tools de gs que recibe
13520
13567
  [--prompt-base AGENTE|none --prompt-overlay ARCHIVO [--dry-run]] prompt = el de otro agente + ediciones, sincronizado
13521
13568
  [--tool wa:tool=off,\u2026] [--bag NOMBRE|none] [--avatar URL|ARCHIVO] una tool por canal \xB7 su bolsa de tokens (staff) \xB7 su cara en Teams
@@ -13762,6 +13809,7 @@ async function main() {
13762
13809
  days: { type: "string" },
13763
13810
  voice: { type: "string" },
13764
13811
  quote: { type: "string" },
13812
+ catalog: { type: "string" },
13765
13813
  "voice-replies": { type: "string" },
13766
13814
  "auto-stage": { type: "string" },
13767
13815
  "from-env": { type: "string" },
@@ -13850,7 +13898,7 @@ async function main() {
13850
13898
  const tokens = splitTokens(pos, v);
13851
13899
  globals.token = tokens.session;
13852
13900
  globals.profile = v.profile;
13853
- if (v.version) return out({ version: "0.46.0" }, () => "0.46.0");
13901
+ if (v.version) return out({ version: "0.47.0" }, () => "0.47.0");
13854
13902
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
13855
13903
  const context = (await readConfig()).workspace;
13856
13904
  if (!explicitToken()?.startsWith("gat_")) {
@@ -14010,7 +14058,7 @@ async function main() {
14010
14058
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
14011
14059
  }
14012
14060
  }
14013
- var notice = updateNotice("0.46.0").catch(() => null);
14061
+ var notice = updateNotice("0.47.0").catch(() => null);
14014
14062
  main().then(
14015
14063
  async () => {
14016
14064
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.46.0",
3
+ "version": "0.47.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": {