@ghostystudio/cli 0.45.0 → 0.46.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.
package/README.md CHANGED
@@ -103,6 +103,10 @@ 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.46.0
107
+
108
+ - 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».
109
+
106
110
  ### Changes in 0.45.0
107
111
 
108
112
  - New `turns grep <id> "text" [--since 7d] [--role user|agent]`: finds what the customer or the agent said, with its turn, to open with `turns show`.
package/dist/index.js CHANGED
@@ -3122,9 +3122,9 @@ var require_timestamp = __commonJS({
3122
3122
  const match = str.match(timestamp.test);
3123
3123
  if (!match)
3124
3124
  throw new Error("!!timestamp expects a date, starting with yyyy-mm-dd");
3125
- const [, year, month, day2, hour, minute, second] = match.map(Number);
3125
+ const [, year, month, day3, hour, minute, second] = match.map(Number);
3126
3126
  const millisec = match[7] ? Number((match[7] + "00").substr(1, 3)) : 0;
3127
- let date = Date.UTC(year, month - 1, day2, hour || 0, minute || 0, second || 0, millisec);
3127
+ let date = Date.UTC(year, month - 1, day3, hour || 0, minute || 0, second || 0, millisec);
3128
3128
  const tz = match[8];
3129
3129
  if (tz && tz !== "Z") {
3130
3130
  let d = parseSexagesimal(tz, false);
@@ -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.45.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8425
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.46.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.45.0"}`,
8467
+ "user-agent": `ghosty-cli/${"0.46.0"}`,
8468
8468
  "accept-language": lang(),
8469
8469
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8470
8470
  ...opts.headers
@@ -9738,7 +9738,7 @@ async function call(path, v, init = {}) {
9738
9738
  "x-ghosty-ts": ts,
9739
9739
  "x-ghosty-sig": sig,
9740
9740
  "content-type": "application/json",
9741
- "user-agent": `ghosty-cli/${"0.45.0"}`
9741
+ "user-agent": `ghosty-cli/${"0.46.0"}`
9742
9742
  },
9743
9743
  body: method === "GET" ? void 0 : raw
9744
9744
  });
@@ -11598,6 +11598,18 @@ var HELPS_ES = {
11598
11598
  ["--yes", "s\xE1ltate la confirmaci\xF3n (obligatorio sin terminal)"]
11599
11599
  ],
11600
11600
  notes: ["create sigue las reglas de la web: queda a tu nombre, nace como prueba de 30 d\xEDas, tu correo debe estar confirmado y tu plan topa cu\xE1ntos tienes. Sigue: ghosty partner enable <slug>.", "Con suscripci\xF3n de Stripe renew se niega: la fecha la mueve la factura pagada.", "rename cambia s\xF3lo el slug (id y namespace se quedan, as\xED que tablero, Stripe y WhatsApp siguen funcionando); las ligas viejas <slug>.sales dejan de servir.", "transfer se lleva los agentes con el espacio y recicla sus cajas (el due\xF1o va horneado en la caja); sin --copy-keys la simulaci\xF3n dice qu\xE9 llaves le faltan al nuevo \u2014 esos agentes dejar\xEDan de contestar."]
11601
+ },
11602
+ staff: {
11603
+ summary: "Staff de Ghosty asignado a CUENTAS cliente (admin delegado que act\xFAa como s\xED mismo): ve y opera con su propia sesi\xF3n los agentes (con y sin espacio), documentos, bases y tableros de esa cuenta. Sin llaves, cobro, traspasos ni borrados. S\xF3lo el staff de Ghosty asigna; el cliente s\xF3lo ve \xABTu ejecutivo de Ghosty\xBB. Cada cambio y cada escritura quedan en la bit\xE1cora.",
11604
+ flags: [
11605
+ ["--all", "ls: tambi\xE9n las asignaciones revocadas"],
11606
+ ["--reason TEXT", "assign: por qu\xE9 (va a la bit\xE1cora)"],
11607
+ ["--yes", "unassign: s\xE1ltate la confirmaci\xF3n (obligatorio sin terminal)"]
11608
+ ],
11609
+ notes: [
11610
+ "En el panel web el staff elige \xABAtendiendo: <cliente>\xBB (Documentos y bases, Agentes); es una cookie que se revalida en cada request, nunca otra sesi\xF3n.",
11611
+ "Revocar corta en el siguiente request. Asignar dos veces no hace nada (una sola activa por par)."
11612
+ ]
11601
11613
  }
11602
11614
  };
11603
11615
  var HELP_ALIASES = { creds: "credentials", conectores: "connectors", board: "cards", conversations: "chat", workspaces: "spaces" };
@@ -12229,6 +12241,25 @@ HELPS.sites = {
12229
12241
  notes: ["A folder needs index.html at its root; hidden files and node_modules are skipped. Limits: 1 MB per file, 8 MB and 40 files per site.", "Without --site, publish always creates a new site (house sites are never matched by title).", "Slug errors: slug_invalid, slug_taken, and slug_unsupported while EasyBits does not accept a slug yet (exit 1, nothing changes)."],
12230
12242
  learn: `${REF}#sitios`
12231
12243
  };
12244
+ HELPS.staff = {
12245
+ summary: "Ghosty staff assigned to client ACCOUNTS (delegated admin acting as themselves): the staff member sees and runs that account's agents (with and without a workspace), documents, databases and boards with their own session. No keys, billing, transfers or deletes. Only Ghosty staff assigns; the customer just sees \xABYour Ghosty account executive\xBB. Every change and every write is logged.",
12246
+ usage: [
12247
+ "ghosty staff ls [staff-email] [client-email] [--all]",
12248
+ "ghosty staff assign <staff-email> <client-email> [--reason TEXT]",
12249
+ "ghosty staff unassign <staff-email> <client-email> [--yes]"
12250
+ ],
12251
+ flags: [
12252
+ ["--all", "ls: also revoked assignments"],
12253
+ ["--reason TEXT", "assign: why (goes to the audit log)"],
12254
+ ["--yes", "unassign: skip the confirmation (required without a terminal)"]
12255
+ ],
12256
+ examples: ['ghosty staff assign brenda@siiqtec.com siiqtec@gmail.com --reason "migraci\xF3n SIIQTEC"', "ghosty staff ls brenda@siiqtec.com", "ghosty staff unassign brenda@siiqtec.com siiqtec@gmail.com --yes"],
12257
+ notes: [
12258
+ "In the web panel the staff member picks \xABAtendiendo: <client>\xBB (Documents and databases, Agents); it is a cookie revalidated on every request, never another session.",
12259
+ "Revoking cuts on the next request. Assigning twice is a no-op (one active assignment per pair)."
12260
+ ],
12261
+ learn: `${REF}#staff-asignado`
12262
+ };
12232
12263
  function helpText(cmd) {
12233
12264
  const base = HELPS[cmd];
12234
12265
  if (!base) return void 0;
@@ -12843,7 +12874,7 @@ ${dim(t(`${total2} message(s) unanswered for lack of balance in ${r.days} day(s)
12843
12874
  d.noQuota += x.noQuota;
12844
12875
  days.set(x.day, d);
12845
12876
  }
12846
- rows = [...days].map(([day2, x]) => ({ day: day2, ...fmt2(x) }));
12877
+ rows = [...days].map(([day3, x]) => ({ day: day3, ...fmt2(x) }));
12847
12878
  } else rows = r.agents.map((x) => ({ agent: x.agent, ...fmt2(x) }));
12848
12879
  const cols = [...by.has("day") ? ["day"] : [], ...by.has("agent") ? ["agent"] : [], "turns", "failed", "tokens", "no balance"];
12849
12880
  const total = r.agents.reduce((n, a) => n + a.billable, 0);
@@ -13044,6 +13075,43 @@ ${dim(t("Links with the old slug may stop working.", "Las ligas con el slug viej
13044
13075
  }
13045
13076
  }
13046
13077
 
13078
+ // src/commands/staff.ts
13079
+ var day2 = (iso) => iso ? iso.slice(0, 10) : "";
13080
+ async function staffCmd(sub, staff, client, v) {
13081
+ if (!sub || sub === "ls" || sub === "list") {
13082
+ const q = new URLSearchParams();
13083
+ if (staff) q.set("staff", staff);
13084
+ if (client) q.set("client", client);
13085
+ if (v.all) q.set("all", "1");
13086
+ const r = await api(`/api/v2/staff/assignments${q.toString() ? `?${q}` : ""}`);
13087
+ const rows = r.assignments.map((a) => ({
13088
+ staff: a.staff.email,
13089
+ client: a.client.email,
13090
+ since: day2(a.createdAt),
13091
+ by: a.grantedBy,
13092
+ reason: a.reason ?? "",
13093
+ ...v.all ? { revoked: day2(a.revokedAt) } : {}
13094
+ }));
13095
+ return out(r, () => rows.length ? table(rows, ["staff", "client", "since", "by", "reason", ...v.all ? ["revoked"] : []]) : dim(t("No assignments.", "Sin asignaciones.")));
13096
+ }
13097
+ if (sub === "assign" || sub === "unassign") {
13098
+ if (!staff || !client) throw usage(t("Missing emails.", "Faltan correos."), `ghosty staff ${sub} <staff-email> <client-email>${sub === "assign" ? " [--reason TEXT]" : ""}`);
13099
+ if (sub === "assign") {
13100
+ const r2 = await api("/api/v2/staff/assignments", { method: "POST", body: { staff, client, ...v.reason ? { reason: v.reason } : {} } });
13101
+ return out(
13102
+ r2,
13103
+ () => r2.already ? dim(t(`${r2.staff} already serves ${r2.client}.`, `${r2.staff} ya atend\xEDa a ${r2.client}.`)) : `${lavender("\u2713")} ${t(`${r2.staff} now serves ${r2.client}: agents, documents, databases and boards, with no keys, billing or transfers (logged).`, `${r2.staff} ahora atiende a ${r2.client}: agentes, documentos, bases y tableros, sin llaves, cobro ni traspasos (queda en bit\xE1cora).`)}`
13104
+ );
13105
+ }
13106
+ const retry = `ghosty staff unassign ${staff} ${client} --yes`;
13107
+ requireYesIfHeadless(v.yes, retry);
13108
+ if (!v.yes) await confirm(t(`Stop ${staff} serving ${client}? It cuts on their next click.`, `\xBFQue ${staff} deje de atender a ${client}? Se corta en su siguiente clic.`), { retry });
13109
+ const r = await api("/api/v2/staff/assignments", { method: "DELETE", body: { staff, client } });
13110
+ return out(r, () => r.revoked ? `${lavender("\u2713")} ${t(`${r.staff} no longer serves ${r.client}.`, `${r.staff} ya no atiende a ${r.client}.`)}` : dim(t("There was no active assignment.", "No hab\xEDa asignaci\xF3n activa.")));
13111
+ }
13112
+ throw usage(t(`Unknown staff subcommand: ${sub}.`, `Subcomando de staff desconocido: ${sub}.`), "ghosty staff ls|assign|unassign <staff-email> [client-email]");
13113
+ }
13114
+
13047
13115
  // src/commands/support.ts
13048
13116
  import { readFile as readFile14 } from "node:fs/promises";
13049
13117
  var enc12 = encodeURIComponent;
@@ -13286,6 +13354,8 @@ ${bold("Workspaces")} ${dim("(changing them: Ghosty staff)")}
13286
13354
  spaces rename <old> <new> Change its slug only [--dry-run]
13287
13355
  spaces transfer <workspace> --to EMAIL New owner; agents go with it and their boxes are recycled [--keep-as admin|member|none] [--copy-keys] [--dry-run]
13288
13356
  spaces log <workspace> Its log: transfers, staff in/out, renewals (owner or staff) [--since 30d] [--action workspace]
13357
+ staff ls [staff-email] [client-email] Ghosty staff assigned to client accounts (staff) [--all: also revoked]
13358
+ staff assign|unassign <staff> <client> Staff sees and runs that account's agents, documents, databases and boards as themselves (no keys, billing or transfers; logged) [--reason]
13289
13359
 
13290
13360
  ${bold("Agents")}
13291
13361
  agents ls List your agents [--engine E] [--ids: only ids, for xargs] \xB7 --user EMAIL --app all|android|ios|desktop: as their app sees it (staff)
@@ -13429,6 +13499,8 @@ ${bold("Espacios")} ${dim("(cambiarlos: staff de Ghosty)")}
13429
13499
  spaces rename <viejo> <nuevo> Cambia s\xF3lo su slug [--dry-run]
13430
13500
  spaces transfer <espacio> --to CORREO Due\xF1o nuevo; los agentes se van con \xE9l y se reciclan sus cajas [--keep-as admin|member|none] [--copy-keys] [--dry-run]
13431
13501
  spaces log <espacio> Su bit\xE1cora: traspasos, staff que entra o sale, renovaciones (due\xF1o o staff) [--since 30d] [--action workspace]
13502
+ staff ls [correo-staff] [correo-cliente] Staff de Ghosty asignado a cuentas cliente (staff) [--all: tambi\xE9n revocadas]
13503
+ staff assign|unassign <staff> <cliente> El staff ve y opera agentes, documentos, bases y tableros de esa cuenta como s\xED mismo (sin llaves, cobro ni traspasos; queda en bit\xE1cora) [--reason]
13432
13504
 
13433
13505
  ${bold("Agentes")}
13434
13506
  agents ls Lista tus agentes [--engine E] [--ids: s\xF3lo ids, para xargs] \xB7 --user CORREO --app all|android|ios|desktop: como lo ve su app (staff)
@@ -13548,7 +13620,7 @@ ${bold("Opciones globales")}
13548
13620
  ${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
13549
13621
  ${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.")}
13550
13622
  ${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
13551
- var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "connectors", "browser", "docs", "help"];
13623
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "connectors", "browser", "docs", "help"];
13552
13624
  var AGENTS_SUBS = ["ls", "create", "get", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "box"];
13553
13625
  function unknown(what, input, known, helpCmd) {
13554
13626
  const s = suggest(input, known);
@@ -13724,6 +13796,7 @@ async function main() {
13724
13796
  recurring: { type: "string" },
13725
13797
  "hard-cap": { type: "string" },
13726
13798
  note: { type: "string" },
13799
+ reason: { type: "string" },
13727
13800
  bag: { type: "string" },
13728
13801
  avatar: { type: "string" },
13729
13802
  tool: { type: "string" },
@@ -13777,7 +13850,7 @@ async function main() {
13777
13850
  const tokens = splitTokens(pos, v);
13778
13851
  globals.token = tokens.session;
13779
13852
  globals.profile = v.profile;
13780
- if (v.version) return out({ version: "0.45.0" }, () => "0.45.0");
13853
+ if (v.version) return out({ version: "0.46.0" }, () => "0.46.0");
13781
13854
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
13782
13855
  const context = (await readConfig()).workspace;
13783
13856
  if (!explicitToken()?.startsWith("gat_")) {
@@ -13918,6 +13991,8 @@ async function main() {
13918
13991
  case "space":
13919
13992
  case "workspaces":
13920
13993
  return spacesCmd(sub, a1, a2, v, context);
13994
+ case "staff":
13995
+ return staffCmd(sub, a1, a2, v);
13921
13996
  case "plan":
13922
13997
  return planCmd(sub, a1, v, context);
13923
13998
  case "me":
@@ -13935,7 +14010,7 @@ async function main() {
13935
14010
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
13936
14011
  }
13937
14012
  }
13938
- var notice = updateNotice("0.45.0").catch(() => null);
14013
+ var notice = updateNotice("0.46.0").catch(() => null);
13939
14014
  main().then(
13940
14015
  async () => {
13941
14016
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.45.0",
3
+ "version": "0.46.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": {