@ghostystudio/cli 0.26.0 → 0.27.1

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
@@ -70,6 +70,7 @@ ghosty conversations show <id> <conversation-id> [--limit 20] # what was said
70
70
  ghosty schedule add <id> "what to do" --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]
71
71
  ghosty schedule ls <id>
72
72
  ghosty schedule rm <id> <schedule-id>
73
+ ghosty schedule add|ls|rm <id> … --teams <workspace>#<room>[/<thread-id>] # in a Teams room/thread: it answers there as if mentioned
73
74
 
74
75
  ghosty me files ls|upload|rm [--kind image|video|audio|document|other]
75
76
  ghosty credentials ls|set|rm [provider] [--from-env VAR] # never as an argument
@@ -86,6 +87,14 @@ Every command has its own help with examples: `ghosty <command> --help` or `ghos
86
87
 
87
88
  Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
88
89
 
90
+ ### Changes in 0.27.1
91
+
92
+ - A 404 with a reason (no such bag, room or thread) prints that reason instead of a generic «Not found».
93
+
94
+ ### Changes in 0.27.0
95
+
96
+ - `schedule add <id> "…" --teams <workspace>#<room>[/<thread-id>] --at ISO | --in 30m`: at that time the agent answers in that Teams room or thread as if mentioned (with the room's memory). `schedule ls|rm <id> --teams <workspace>`.
97
+
89
98
  ### Changes in 0.26.0
90
99
 
91
100
  - `ghosty engines ls [--models]`: engines and models valid for `--engine`/`--model`; an unknown model is rejected with the valid list (also without `--engine`).
package/dist/index.js CHANGED
@@ -8002,6 +8002,18 @@ var SERVER_MESSAGES = [
8002
8002
  ["s\xF3lo el due\xF1o (o staff) puede borrar el agente", "only the owner (or staff) can delete the agent"],
8003
8003
  ["ghosty agents rm <agente> --from-workspace lo quita de Teams y lo borra", "ghosty agents rm <agent> --from-workspace takes it out of Teams and deletes it"],
8004
8004
  ["Se acab\xF3 el saldo de este agente ({}). Era una carga \xFAnica y no se recarga. Los dem\xE1s agentes del espacio siguen con el suyo.", "This agent ran out of balance ({}). It was a one-time load and doesn't refill. The other agents in the workspace keep theirs."],
8005
+ ["el token del agente no programa turnos en Teams: usa tu sesi\xF3n", "the agent's token doesn't schedule turns in Teams: use your session"],
8006
+ ["falta workspace (el espacio de Teams)", "missing workspace (the Teams workspace)"],
8007
+ ["no eres miembro de \xAB{}\xBB", "you're not a member of \xAB{}\xBB"],
8008
+ ["el agente no est\xE1 activo en el Teams de {}", "the agent isn't active in {}'s Teams"],
8009
+ ["faltan channel, text o dueAt (ISO)", "missing channel, text or dueAt (ISO)"],
8010
+ ["no pude hablar con Teams de {}: {}", "couldn't reach {}'s Teams: {}"],
8011
+ ["@{} no est\xE1 activo en este espacio", "@{} isn't active in this workspace"],
8012
+ ["no hay un room \xAB{}\xBB en este espacio", "there's no room \xAB{}\xBB in this workspace"],
8013
+ ["el mensaje {} no es de #{}", "message {} isn't from #{}"],
8014
+ ["el mensaje {} es una respuesta; usa el del inicio del hilo ({})", "message {} is a reply; use the one that starts the thread ({})"],
8015
+ ["dueAt tiene que ser una fecha futura (epoch en segundos)", "dueAt must be a future date (epoch seconds)"],
8016
+ ["no hay un turno pendiente con ese id (\xBFya corri\xF3?)", "no pending turn with that id (did it already run?)"],
8005
8017
  // — Infra interna (casi nunca llega al CLI, pero por si acaso)
8006
8018
  ["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)."],
8007
8019
  ["sqld: fetch sin namespace", "sqld: fetch without namespace"],
@@ -8303,7 +8315,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
8303
8315
  const req = httpsRequest(`${base}${path}`, {
8304
8316
  method: "POST",
8305
8317
  timeout: timeoutMs,
8306
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.26.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8318
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.27.1"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
8307
8319
  }, (res) => {
8308
8320
  let buf = "";
8309
8321
  res.on("data", (c) => buf += c);
@@ -8345,7 +8357,7 @@ async function request(path, opts = {}, retried = false) {
8345
8357
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
8346
8358
  headers: {
8347
8359
  authorization: `Bearer ${token}`,
8348
- "user-agent": `ghosty-cli/${"0.26.0"}`,
8360
+ "user-agent": `ghosty-cli/${"0.27.1"}`,
8349
8361
  "accept-language": lang(),
8350
8362
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
8351
8363
  ...opts.headers
@@ -8371,6 +8383,7 @@ async function toError(res) {
8371
8383
  const detail = serverText(raw);
8372
8384
  if (res.status === 401) return new CliError(t("Your session is not valid anymore.", "Tu sesi\xF3n ya no es v\xE1lida."), EXIT.AUTH, t("Run: ghosty login", "Corre: ghosty login"));
8373
8385
  if (res.status === 403) return new CliError(t(`Not allowed: ${detail}`, `No permitido: ${detail}`), EXIT.API, t("If the agent was shared with you, its workspace (board, channels, numbers) stays with the owner: ask them to add you to that workspace. Otherwise run: ghosty login", "Si el agente se comparti\xF3 contigo, su espacio (tablero, canales, n\xFAmeros) sigue siendo del due\xF1o: p\xEDdele que te agregue a ese espacio. Si no, corre: ghosty login"));
8386
+ if (res.status === 404 && (body.message || body.error)) return new CliError(detail, EXIT.API);
8374
8387
  if (res.status === 404) return new CliError(t("Not found.", "No se encontr\xF3."), EXIT.API, t("Check the agent id (ghosty agents ls) or that your token belongs to that agent.", "Revisa el id del agente (ghosty agents ls) o que tu token sea de ese agente."));
8375
8388
  if (body.error === "restart_no_aplica" || body.error === "agente_sin_maquina") {
8376
8389
  return new CliError(t("This agent doesn't need a restart: new files, skills and MCP load on its next turn.", "Este agente no necesita reinicio: los archivos, skills y MCP nuevos se cargan en su siguiente turno."), EXIT.API);
@@ -9227,7 +9240,40 @@ var short = (s, n = 60) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
9227
9240
  async function agentSchedule(agent) {
9228
9241
  return api(`/api/v2/me/agents/${enc4(agent)}/schedule`);
9229
9242
  }
9243
+ function parseTeamsTarget(raw) {
9244
+ const m = /^([a-z0-9-]+)(?:#([^/\s]+)(?:\/(\d+))?)?$/i.exec(raw.trim());
9245
+ if (!m) throw usage(t(`Bad --teams: ${raw}.`, `--teams inv\xE1lido: ${raw}.`), t("Use workspace#room or workspace#room/<thread message id>, e.g. descti#general/4812", "Usa espacio#room o espacio#room/<id del mensaje del hilo>, p. ej. descti#general/4812"));
9246
+ return { workspace: m[1], ...m[2] ? { channel: m[2] } : {}, ...m[3] ? { parentId: Number(m[3]) } : {} };
9247
+ }
9248
+ async function teamsSchedule(sub, agent, arg, v) {
9249
+ const target = parseTeamsTarget(v.teams);
9250
+ const path = `/api/v2/agents/${enc4(agent)}/teams-schedule`;
9251
+ const when = (epoch) => new Date(epoch * 1e3).toLocaleString("es-MX", { timeZone: "America/Mexico_City", dateStyle: "medium", timeStyle: "short" });
9252
+ if (sub === "ls" || sub === "list") {
9253
+ const r2 = await api(`${path}?workspace=${enc4(target.workspace)}`);
9254
+ const rows = r2.scheduled.map((x) => ({ id: x.id, due: when(x.dueAt), where: `#${x.channel}${x.parentId ? `/${x.parentId}` : ""}`, by: x.by, text: short(x.text) }));
9255
+ return out(r2, () => rows.length ? table(rows, ["id", "due", "where", "by", "text"]) : dim(t("No scheduled turns in Teams.", "No hay turnos programados en Teams.")));
9256
+ }
9257
+ if (sub === "rm" || sub === "cancel") {
9258
+ if (!arg) throw usage(t("Missing schedule id.", "Falta el id del turno."), `ghosty schedule rm <agent-id> <id> --teams ${target.workspace}`);
9259
+ const r2 = await api(path, { method: "DELETE", body: { workspace: target.workspace, id: arg } });
9260
+ return out(r2, () => `${lavender("\u2713")} ${t("Canceled.", "Cancelado.")}`);
9261
+ }
9262
+ if (sub !== "add") throw usage(t(`Unknown schedule subcommand: ${sub ?? "(none)"}.`, `Subcomando de schedule desconocido: ${sub ?? "(ninguno)"}.`), "ghosty schedule add|ls|rm <agent-id> \u2026 --teams workspace#room[/thread]");
9263
+ if (!arg) throw usage(t("Missing what to do.", "Falta qu\xE9 hacer."), `ghosty schedule add <agent-id> "\u2026" --teams ${v.teams} --at 2026-10-01T09:00`);
9264
+ if (!target.channel) throw usage(t("Missing the room.", "Falta el room."), t("Use --teams workspace#room or workspace#room/<thread id>", "Usa --teams espacio#room o espacio#room/<id del hilo>"));
9265
+ if (!v.at && !v.in) throw usage(t("Say when.", "Di cu\xE1ndo."), t("Use --in 30m or --at 2026-10-01T09:00-06:00", "Usa --in 30m o --at 2026-10-01T09:00-06:00"));
9266
+ if (v.at && v.in) throw usage(t("Use --at or --in, not both.", "Usa --at o --in, no ambos."));
9267
+ if (v.every) throw usage(t("--every is not supported with --teams yet.", "--every todav\xEDa no va con --teams."));
9268
+ const dueAt = v.in ? new Date(Date.now() + minutesOf(v.in, "--in") * 6e4).toISOString() : isoOf(v.at, "--at");
9269
+ const r = await api(path, { method: "POST", body: { workspace: target.workspace, channel: target.channel, ...target.parentId ? { parentId: target.parentId } : {}, text: arg, dueAt } });
9270
+ out(r, () => `${lavender("\u2713")} ${t(`@${r.handle} will answer in #${r.channel}${r.parentId ? ` (thread ${r.parentId})` : ""} on ${when(r.dueAt)}.`, `@${r.handle} contestar\xE1 en #${r.channel}${r.parentId ? ` (hilo ${r.parentId})` : ""} el ${when(r.dueAt)}.`)} ${dim(`id ${r.id} \xB7 ${t("cancel", "cancelar")}: ghosty schedule rm ${agent} ${r.id} --teams ${target.workspace}`)}`);
9271
+ }
9230
9272
  async function scheduleCmd(sub, agent, arg, v) {
9273
+ if (v.teams) {
9274
+ if (!agent) throw usage(t("Missing agent id.", "Falta el id del agente."), 'ghosty schedule add <agent-id> "\u2026" --teams workspace#room/thread --at ISO');
9275
+ return teamsSchedule(sub, agent, arg, v);
9276
+ }
9231
9277
  if (sub === "ls" || sub === "list") {
9232
9278
  if (!agent) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + ": ghosty schedule ls <agent-id>");
9233
9279
  const r = await agentSchedule(agent);
@@ -9379,7 +9425,7 @@ async function call(path, v, init = {}) {
9379
9425
  "x-ghosty-ts": ts,
9380
9426
  "x-ghosty-sig": sig,
9381
9427
  "content-type": "application/json",
9382
- "user-agent": `ghosty-cli/${"0.26.0"}`
9428
+ "user-agent": `ghosty-cli/${"0.27.1"}`
9383
9429
  },
9384
9430
  body: method === "GET" ? void 0 : raw
9385
9431
  });
@@ -10533,6 +10579,7 @@ var HELPS_ES = {
10533
10579
  ["--no-deliver", "docs grant/add: puede leerlo pero nunca mand\xE1rselo a quien escribe"],
10534
10580
  ["--url URL | --file PATH", "docs add: de d\xF3nde sale el documento (URL p\xFAblica, hasta 25 MB, o un archivo local)"],
10535
10581
  ["--grant read|write|no", "docs add: acceso de este agente (default read; no = s\xF3lo subirlo a la cuenta)"],
10582
+ ["--note TEXT", "docs replace/restore: por qu\xE9 cambi\xF3 (sale en docs history)"],
10536
10583
  ["--role R", "share: editor (lo configura y lo usa) o admin (adem\xE1s lo comparte); repetirlo sobre un correo ya compartido cambia el rol"],
10537
10584
  ["--yes", "rm: se salta la confirmaci\xF3n (obligatorio sin terminal)"],
10538
10585
  ["--slug NAME", "demo: nombre de la liga; se le agrega un c\xF3digo de 6 caracteres (/p/NAME-CODE)"],
@@ -10648,7 +10695,8 @@ var HELPS_ES = {
10648
10695
  },
10649
10696
  schedule: {
10650
10697
  summary: "Turnos que corren despu\xE9s, con el reloj propio del agente.",
10651
- replace: [['"what to do"', '"qu\xE9 hacer"']]
10698
+ replace: [['"what to do"', '"qu\xE9 hacer"'], ["# answers in that thread as if mentioned", "# contesta en ese hilo como si lo mencionaran"]],
10699
+ notes: ["--teams: el agente tiene que estar activo en el Teams de ese espacio y t\xFA ser miembro. El id del hilo es el del mensaje con que empieza."]
10652
10700
  },
10653
10701
  me: { summary: "Tu cuenta: archivos y uso." },
10654
10702
  credentials: {
@@ -10831,6 +10879,8 @@ var HELPS = {
10831
10879
  "ghosty agents doctor <agent-id> [--verbose]",
10832
10880
  "ghosty agents docs ls|grant|revoke <agent-id> [doc\u2026] [--write] [--no-deliver] [--like AGENT]",
10833
10881
  "ghosty agents docs add <agent-id> --url URL | --file PATH [--name N] [--description TEXT] [--grant read|write|no] [--no-deliver]",
10882
+ "ghosty agents docs replace <agent-id> <doc> --url URL | --file PATH [--note TEXT] # new version: keeps name, access and the agents that use it",
10883
+ "ghosty agents docs history|restore <agent-id> <doc> [n]",
10834
10884
  "ghosty agents share <agent-id> <email> [--role editor|admin]",
10835
10885
  "ghosty agents unshare <agent-id> <email> [--yes]",
10836
10886
  "ghosty agents shares <agent-id>",
@@ -10860,6 +10910,7 @@ var HELPS = {
10860
10910
  ["--no-deliver", "docs grant/add: it can read it but never send it to whoever writes"],
10861
10911
  ["--url URL | --file PATH", "docs add: where the document comes from (public URL, up to 25 MB, or a local file)"],
10862
10912
  ["--grant read|write|no", "docs add: access for this agent (default read; no = only upload to the account)"],
10913
+ ["--note TEXT", "docs replace/restore: why it changed (shown in docs history)"],
10863
10914
  ["--role R", "share: editor (configures and uses it) or admin (also shares it); again on a shared email changes the role"],
10864
10915
  ["--yes", "rm: skip the confirmation (required without a terminal)"],
10865
10916
  ["--slug NAME", "demo: link name; a 6-char code is appended (/p/NAME-CODE)"],
@@ -10887,6 +10938,8 @@ var HELPS = {
10887
10938
  'ghosty agents docs grant <id> "Cat\xE1logo 2026.pdf"',
10888
10939
  "ghosty agents docs grant <id> precios.xlsx --write --no-deliver",
10889
10940
  'ghosty agents docs add <id> --url https://example.com/lista.pdf --description "Precios 2026"',
10941
+ 'ghosty agents docs replace <id> "Cat\xE1logo.pdf" --file catalogo-oct.pdf --note "precios de octubre"',
10942
+ 'ghosty agents docs history <id> "Cat\xE1logo.pdf" \xB7 ghosty agents docs restore <id> "Cat\xE1logo.pdf" 3',
10890
10943
  "ghosty agents create --name Pia --engine claude --workspace ventas --channels teams=off",
10891
10944
  "ghosty agents set <id> --toolsets +crm",
10892
10945
  "ghosty agents share <id> ana@empresa.com --role admin",
@@ -11054,8 +11107,9 @@ var HELPS = {
11054
11107
  },
11055
11108
  schedule: {
11056
11109
  summary: "Turns that run later, on the agent's own clock.",
11057
- usage: ['ghosty schedule add <agent-id> "what to do" --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]', "ghosty schedule ls <agent-id>", "ghosty schedule rm <agent-id> <schedule-id>"],
11058
- examples: ['ghosty schedule add <id> "revisa pedidos pendientes" --every 12h --in 1h'],
11110
+ usage: ['ghosty schedule add <agent-id> "what to do" --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]', "ghosty schedule ls <agent-id>", "ghosty schedule rm <agent-id> <schedule-id>", 'ghosty schedule add <agent-id> "what to do" --teams <workspace>#<room>[/<thread-id>] --in 30m | --at ISO', "ghosty schedule ls|rm <agent-id> [id] --teams <workspace>"],
11111
+ examples: ['ghosty schedule add <id> "revisa pedidos pendientes" --every 12h --in 1h', 'ghosty schedule add minighosty "Pres\xE9ntate al equipo" --teams descti#general/4812 --at 2026-10-01T09:00 # answers in that thread as if mentioned'],
11112
+ notes: ["--teams: the agent must be active in that workspace's Teams and you must be a member. The thread id is the id of the message that starts it."],
11059
11113
  learn: `${REF}#programar-turnos`
11060
11114
  },
11061
11115
  me: {
@@ -11878,6 +11932,8 @@ ${bold("Agents")}
11878
11932
  agents docs ls|grant|revoke <id> [doc] Documents it reads and may send [--write] [--no-deliver]
11879
11933
  agents docs grant <id> A B\u2026 Grant several documents at once [--like AGENT] copies another agent's
11880
11934
  agents docs add <id> Upload a document and grant it --url URL | --file PATH [--description] [--grant read|write|no]
11935
+ agents docs replace <id> <doc> New version (keeps name and access) --url URL | --file PATH [--note]
11936
+ agents docs history|restore <id> <doc> [n] Its versions; restore n as a new version
11881
11937
  agents share|unshare <id> <email> Share it without handing it over [--role editor|admin] \xB7 agents shares <id> lists them
11882
11938
  agents rm <id> Delete an agent (owner only; asks you to type its name, or --yes) [--from-workspace: turn it off in Teams first]
11883
11939
  agents restart <id> Restart an ACP agent (needsRestart) so it loads new skills
@@ -11923,6 +11979,7 @@ ${bold("Schedule turns")} ${dim("(each run lands in a conversation and notifies
11923
11979
  schedule ls <id> Pending turns and recent runs
11924
11980
  schedule add <id> "what to do" Schedule a turn --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]
11925
11981
  schedule rm <id> <schedule-id> Cancel a pending turn
11982
+ schedule add|ls|rm <id> \u2026 --teams WS#room[/thread] The same in a Teams room or thread: it answers there as if mentioned
11926
11983
 
11927
11984
  ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see: ghosty partner --help)")}
11928
11985
  partner ls | enable | credentials | agent | tenants | audit | try
@@ -11983,6 +12040,8 @@ ${bold("Agentes")}
11983
12040
  agents docs ls|grant|revoke <id> [doc] Documentos que lee y puede enviar [--write] [--no-deliver]
11984
12041
  agents docs grant <id> A B\u2026 Da varios documentos a la vez [--like AGENTE] copia los de otro agente
11985
12042
  agents docs add <id> Sube un documento y d\xE1selo --url URL | --file RUTA [--description] [--grant read|write|no]
12043
+ agents docs replace <id> <doc> Versi\xF3n nueva (conserva nombre y accesos) --url URL | --file RUTA [--note]
12044
+ agents docs history|restore <id> <doc> [n] Sus versiones; restaura la n como versi\xF3n nueva
11986
12045
  agents share|unshare <id> <correo> Comp\xE1rtelo sin cederlo [--role editor|admin] \xB7 agents shares <id> los lista
11987
12046
  agents rm <id> Borra un agente (s\xF3lo el due\xF1o; te pide escribir su nombre, o --yes) [--from-workspace: antes lo desactiva en Teams]
11988
12047
  agents restart <id> Reinicia un agente ACP (needsRestart) para que cargue skills nuevas
@@ -12028,6 +12087,7 @@ ${bold("Turnos programados")} ${dim("(cada corrida cae en una conversaci\xF3n y
12028
12087
  schedule ls <id> Turnos pendientes y corridas recientes
12029
12088
  schedule add <id> "qu\xE9 hacer" Programa un turno --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]
12030
12089
  schedule rm <id> <schedule-id> Cancela un turno pendiente
12090
+ schedule add|ls|rm <id> \u2026 --teams ESP#room[/hilo] Lo mismo en un room o hilo de Teams: contesta ah\xED como si lo mencionaran
12031
12091
 
12032
12092
  ${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ver: ghosty partner --help)")}
12033
12093
  partner ls | enable | credentials | agent | tenants | audit | try
@@ -12220,7 +12280,8 @@ async function main() {
12220
12280
  "from-workspace": { type: "boolean" },
12221
12281
  "quota-hits": { type: "boolean" },
12222
12282
  by: { type: "string" },
12223
- models: { type: "boolean" }
12283
+ models: { type: "boolean" },
12284
+ teams: { type: "string" }
12224
12285
  }
12225
12286
  });
12226
12287
  globals.json = Boolean(v.json);
@@ -12228,7 +12289,7 @@ async function main() {
12228
12289
  const tokens = splitTokens(pos, v);
12229
12290
  globals.token = tokens.session;
12230
12291
  globals.profile = v.profile;
12231
- if (v.version) return out({ version: "0.26.0" }, () => "0.26.0");
12292
+ if (v.version) return out({ version: "0.27.1" }, () => "0.27.1");
12232
12293
  for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
12233
12294
  const context = (await readConfig()).workspace;
12234
12295
  if (!explicitToken()?.startsWith("gat_")) {
@@ -12357,7 +12418,7 @@ async function main() {
12357
12418
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
12358
12419
  }
12359
12420
  }
12360
- var notice = updateNotice("0.26.0").catch(() => null);
12421
+ var notice = updateNotice("0.27.1").catch(() => null);
12361
12422
  main().then(
12362
12423
  async () => {
12363
12424
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.26.0",
3
+ "version": "0.27.1",
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": {