@ghostystudio/cli 0.16.1 → 0.18.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 +17 -6
  2. package/dist/index.js +187 -40
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -24,7 +24,8 @@ ghosty agents share|unshare <id> <email> [--role editor|admin] # share without
24
24
  ghosty agents rm <id> [--yes]
25
25
  ghosty agents restart <id>
26
26
  ghosty agents box <id> [--check PATH]… [--contains PATH=TEXT]… [--recycle] # is its machine on the new image?
27
- ghosty agents demo <id> [--slug NAME] [--on | --off] [--vence "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N]
27
+ ghosty agents demo enable|disable <id>
28
+ ghosty agents demo <id> [--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N]
28
29
  [--welcome TEXT] [--starter TEXT] [--chips "a|b|c"] [--sponsor on|off] [--rotate] # public /p/ link, no account needed
29
30
  ghosty agents doctor <id> [--verbose] # exits 1 when it finds a problem
30
31
  ghosty agents docs ls|grant|revoke <id> [doc] [--write] [--no-deliver]
@@ -33,7 +34,7 @@ ghosty agents set <id> --voice elevenlabs:ID --voice-replies auto --auto-stage o
33
34
 
34
35
  ghosty files ls|get|put|rm <id> [path] [--file LOCAL] [--out LOCAL]
35
36
  ghosty skills ls|get|add|rm <id> [slug] [--file SKILL.md | --dir FOLDER] [--out DIR] # get without --out only lists
36
- ghosty skills on|off <id> <slug> # turn one of its skills (or one of Ghosty's) on or off
37
+ ghosty skills enable|disable <id> <slug> # turn one of its skills (or one of Ghosty's) on or off
37
38
  ghosty skills templates <id> # pdf-doc templates: its own and Ghosty's, and which folder wins
38
39
  ghosty dbs ls|tables|query|import|grant|revoke <id> [db] [--write] [--arg V]… [--external-write t1,t2] [--external-rows t:col+col] [--from easybits:ID --tables a,b --append --dry-run]
39
40
  ghosty turns ls <id> [--errors | --status running|done|stopped|error] [--since 1h] [--limit N]
@@ -43,11 +44,11 @@ ghosty channels whatsapp link|unlink <id> --integration ID --answer on|off [--
43
44
  ghosty channels whatsapp history <id> --integration ID [--days 7] [--dry-run] # its recent cards, messages and orders (no duplicates)
44
45
  ghosty channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no [--dry-run] # personal WhatsApp session, no QR
45
46
  ghosty channels wa pair|status|pause <id> [--phone N]
46
- ghosty channels wa groups ls|set|create <id> [group] [--role equipo|cliente] [--on|--off] [--dry-run] # personal WhatsApp groups
47
+ ghosty channels wa groups ls|enable|disable|set|create <id> [group] [--role equipo|cliente] [--dry-run] # personal WhatsApp groups
47
48
  ghosty boards ls|create|assign|move-cards|rm <workspace|agent> … [--columns-from main] [--from A --to B --integration X --dry-run]
48
- ghosty board ls <id> [--q TEXT] [--limit 20] # recent cards on its sales board
49
- ghosty board show <id> <folio|phone|id> [--limit 20] # a card's thread, attachments and whether gs kept a copy
50
- ghosty board archive <id> --integration X | --column X | --before 30d [--dry-run]
49
+ ghosty cards ls <id> [--q TEXT] [--limit 20] # recent cards on its sales board
50
+ ghosty cards show <id> <folio|phone|id> [--limit 20] # a card's thread, attachments and whether gs kept a copy
51
+ ghosty cards archive <id> --integration X | --column X | --before 30d [--dry-run]
51
52
  ghosty mcp get|set <id> [--file servers.json]
52
53
 
53
54
  ghosty try <id> "message" [--session] [--reset]
@@ -87,6 +88,16 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
87
88
  - `try --replay FILE --out DIR`: replays real conversations from your machine. The report is written after every step, survives Ghosty deploys (it honors `Retry-After`), and `--resume` continues a partial run.
88
89
  - `agents doctor` speaks English like the rest of the CLI; `channels whatsapp ls` returns a full URL.
89
90
 
91
+ ### Changes in 0.18.0
92
+
93
+ - One naming rule (like `gh`): noun then verb; turning things on/off is `enable|disable`; `on|off` only as a flag value. `ghosty cards` (an agent's board cards; `board` still works), `skills enable|disable`, `agents demo enable|disable <agent>`, `channels wa groups enable|disable <agent> <group>`, `--expires` (was `--vence`). Old forms still work and print the new one.
94
+ - Help for `login`, `logout`, `whoami`, `docs`, `use` and `cards`.
95
+
96
+ ### Changes in 0.17.0
97
+
98
+ - Agents by **name** everywhere: `ghosty agents get pia-0`, `ghosty try "Ghosty Ventas" "hola"` (ids still work). Same-name agents: the one in the `ghosty use` workspace wins, otherwise the CLI lists their ids.
99
+ - `ghosty use <workspace>`: default workspace for `boards`, `agents create` and `agents ls` (`--all`, `--workspace none`, `use --clear`).
100
+
90
101
  ### Changes in 0.16.1
91
102
 
92
103
  - `agents create --like`: `--board same|main|new|NAME` (`new` = its own board with the source's columns, one per number) and `--no-notify` (shared with the same people, no email).
package/dist/index.js CHANGED
@@ -288,7 +288,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
288
288
  const req = httpsRequest(`${base}${path}`, {
289
289
  method: "POST",
290
290
  timeout: timeoutMs,
291
- headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.16.1"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
291
+ headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.18.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
292
292
  }, (res) => {
293
293
  let buf = "";
294
294
  res.on("data", (c) => buf += c);
@@ -330,7 +330,7 @@ async function request(path, opts = {}, retried = false) {
330
330
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
331
331
  headers: {
332
332
  authorization: `Bearer ${token}`,
333
- "user-agent": `ghosty-cli/${"0.16.1"}`,
333
+ "user-agent": `ghosty-cli/${"0.18.0"}`,
334
334
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
335
335
  ...opts.headers
336
336
  },
@@ -529,17 +529,21 @@ var need = (v, what, example) => {
529
529
  if (!v) throw usage(`Missing ${what}.`, `Example: ${example}`);
530
530
  return v;
531
531
  };
532
- async function agentsLs() {
532
+ async function agentsLs(workspace) {
533
533
  const r = await api("/api/v2/me/agents?client=cli");
534
- const rows = r.agentes.map((a) => ({
534
+ const list = workspace ? r.agentes.filter((a) => a.espacio?.nombre === workspace) : r.agentes;
535
+ const spaceOf = (a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "";
536
+ const rows = list.map((a) => ({
535
537
  id: a.id,
536
538
  name: a.nombre,
537
539
  engine: a.motor ?? a.origen,
538
540
  source: a.origen,
541
+ workspace: spaceOf(a),
539
542
  lastActivity: a.ultimaActividad ?? null,
540
543
  ...a.compartidoPor ? { sharedBy: a.compartidoPor } : {}
541
544
  }));
542
- out(rows, () => table(rows, ["id", "name", "engine", "lastActivity"]));
545
+ out(rows, () => `${table(rows, ["id", "name", "engine", "workspace", "lastActivity"])}${workspace ? `
546
+ ${dim(`only ${workspace} (ghosty use) \xB7 all: --all`)}` : ""}`);
543
547
  }
544
548
  function parseEnv(raw) {
545
549
  if (!raw) return void 0;
@@ -1269,7 +1273,7 @@ async function call(path, v, init = {}) {
1269
1273
  "x-ghosty-ts": ts,
1270
1274
  "x-ghosty-sig": sig,
1271
1275
  "content-type": "application/json",
1272
- "user-agent": `ghosty-cli/${"0.16.1"}`
1276
+ "user-agent": `ghosty-cli/${"0.18.0"}`
1273
1277
  },
1274
1278
  body: method === "GET" ? void 0 : raw
1275
1279
  });
@@ -2002,7 +2006,11 @@ Archive ${plan.matched} card(s) on \xAB${plan.board}\xBB? (reversible: a new mes
2002
2006
 
2003
2007
  // src/commands/boards.ts
2004
2008
  var enc8 = encodeURIComponent;
2005
- async function boardsCmd(sub, a1, a2, v) {
2009
+ async function boardsCmd(sub, a1, a2, v, context) {
2010
+ if (context && sub !== "assign") {
2011
+ if ((sub === "ls" || sub === "list" || !sub || sub === "move-cards") && !a1) a1 = context;
2012
+ else if ((sub === "create" || sub === "rm") && a1 && !a2) [a1, a2] = [context, a1];
2013
+ }
2006
2014
  switch (sub ?? "ls") {
2007
2015
  case "ls":
2008
2016
  case "list": {
@@ -2071,7 +2079,8 @@ var HELPS = {
2071
2079
  "ghosty agents shares <agent-id>",
2072
2080
  "ghosty agents restart <agent-id>",
2073
2081
  "ghosty agents box <agent-id> [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle]",
2074
- 'ghosty agents demo <agent-id> [--slug NAME] [--on | --off] [--vence "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXT] [--starter TEXT] [--chips "a|b|c"] [--sponsor on|off] [--rotate]',
2082
+ "ghosty agents demo enable|disable <agent-id>",
2083
+ 'ghosty agents demo <agent-id> [--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXT] [--starter TEXT] [--chips "a|b|c"] [--sponsor on|off] [--rotate]',
2075
2084
  "ghosty agents rm <agent-id> [--yes]"
2076
2085
  ],
2077
2086
  flags: [
@@ -2097,8 +2106,7 @@ var HELPS = {
2097
2106
  ["--role R", "share: editor (configures and uses it) or admin (also shares it); again on a shared email changes the role"],
2098
2107
  ["--yes", "rm: skip the confirmation (required without a terminal)"],
2099
2108
  ["--slug NAME", "demo: link name; a 6-char code is appended (/p/NAME-CODE)"],
2100
- ["--on | --off", "demo: turn the public link on or off"],
2101
- ["--vence DATE|no", `demo: fixed expiry in your machine's time ("2026-10-15T20:00"); no = never`],
2109
+ ["--expires DATE|no", `demo: fixed expiry in your machine's time ("2026-10-15T20:00"); no = never`],
2102
2110
  ["--turns N", "demo: messages per visitor (default 150)"],
2103
2111
  ["--per-ip N", "demo: messages per IP per day (default 30)"],
2104
2112
  ["--welcome TEXT", "demo: chat greeting (empty = default)"],
@@ -2125,7 +2133,8 @@ var HELPS = {
2125
2133
  "ghosty agents create --name tania-mkt --like <pia-id> --board new --no-notify --dry-run",
2126
2134
  'ghosty agents docs grant <id> "Cat\xE1logo.pdf" "Precios.xlsx" \xB7 ghosty agents docs grant <id> --like <other-id>',
2127
2135
  "ghosty agents set <pia-id> --prompt-base <tania-id> --prompt-overlay capa.json --dry-run --out /tmp/pia.md",
2128
- 'ghosty agents demo <id> --slug guardias --on --vence "2026-10-15T20:00" --starter "Ens\xE9\xF1ame c\xF3mo empezar"',
2136
+ "ghosty agents demo enable <id>",
2137
+ 'ghosty agents demo <id> --slug guardias --expires "2026-10-15T20:00" --starter "Ens\xE9\xF1ame c\xF3mo empezar"',
2129
2138
  "ghosty agents box <id> --contains /opt/claude-worker/skills/pdf-doc/render-doc.mjs=pdf-templates"
2130
2139
  ],
2131
2140
  notes: ["doctor exits 1 when it finds a problem, so it can gate a script or CI.", "A derived prompt can't be edited directly (409): change the base or the overlay, or unlink it with --prompt-base none.", "demo with no flags only prints its status: link, expiry, caps and numbers."],
@@ -2135,7 +2144,7 @@ var HELPS = {
2135
2144
  summary: "Skills of an agent (agentskills.io format: a folder with SKILL.md).",
2136
2145
  usage: [
2137
2146
  "ghosty skills ls <agent-id>",
2138
- "ghosty skills on|off <agent-id> <slug>",
2147
+ "ghosty skills enable|disable <agent-id> <slug>",
2139
2148
  "ghosty skills house <agent-id> [--only a,b | --enable a,b | --disable a,b] [--dry-run]",
2140
2149
  "ghosty skills get <agent-id> <slug> [--out DIR]",
2141
2150
  "ghosty skills templates <agent-id>",
@@ -2300,14 +2309,14 @@ HELPS.channels = {
2300
2309
  "ghosty channels wa pair <agent-id> --phone 5215512345678",
2301
2310
  "ghosty channels wa status|pause <agent-id>",
2302
2311
  "ghosty channels wa groups ls <agent-id> [--cached]",
2303
- "ghosty channels wa groups set <agent-id> <group> [--role equipo|cliente] [--on | --off] [--dry-run] [--yes]",
2312
+ "ghosty channels wa groups enable|disable <agent-id> <group> [--role equipo|cliente] [--dry-run] [--yes]",
2313
+ "ghosty channels wa groups set <agent-id> <group> --role equipo|cliente [--dry-run]",
2304
2314
  'ghosty channels wa groups create <agent-id> "Name" [--role equipo|cliente]'
2305
2315
  ],
2306
2316
  flags: [
2307
2317
  ["-n, --dry-run", "print the plan (+ starts answering, - stops) and change nothing"],
2308
- ["--yes", "enable / groups set --on: skip the confirmation (required without a terminal)"],
2318
+ ["--yes", "enable / groups enable: skip the confirmation (required without a terminal)"],
2309
2319
  ["--role R", "groups: equipo (the team: writes to databases, admin tools) or cliente (like any customer)"],
2310
- ["--on | --off", "groups set: the agent answers in that group, or stops"],
2311
2320
  ["--cached", "groups ls: don't ask WhatsApp for the live list (faster; may miss new groups)"],
2312
2321
  ["--integration ID", "link/unlink/history: the WhatsApp Business number's Integration id in Formmy"],
2313
2322
  ["--days N", "history: conversations active in the last N days (1-30, default 7)"],
@@ -2324,7 +2333,7 @@ HELPS.channels = {
2324
2333
  "ghosty channels whatsapp enable <id> 5512345678 --dry-run",
2325
2334
  "ghosty channels whatsapp disable <id> 5512345678",
2326
2335
  "ghosty channels wa groups ls <id>",
2327
- 'ghosty channels wa groups set <id> "Pia-cotizadora" --role equipo --on --dry-run',
2336
+ 'ghosty channels wa groups enable <id> "Pia-cotizadora" --role equipo --dry-run',
2328
2337
  "ghosty channels whatsapp link <id> --integration 6a3af395\u2026 --answer off --dry-run",
2329
2338
  "ghosty channels whatsapp history <id> --integration 6a3af395\u2026 --days 7 --dry-run",
2330
2339
  "ghosty channels wa import <id> --from-file pia-wa.json --own-number no --dry-run"
@@ -2339,6 +2348,24 @@ HELPS.channels = {
2339
2348
  ],
2340
2349
  learn: `${REF}#canales-de-whatsapp`
2341
2350
  };
2351
+ HELPS.login = {
2352
+ summary: "Sign in with your browser (OAuth2, no password here). An agent can run it for you: it prints the URL and waits.",
2353
+ usage: ["ghosty login", "ghosty login --device"],
2354
+ flags: [["--device", "sign in from another device with a link + code (automatic over SSH or CI)"], ["--profile NAME", "a separate saved session, e.g. one per account"]],
2355
+ examples: ["ghosty login", "ghosty login --profile cliente", "GHOSTY_TOKEN=gat_\u2026 ghosty agents get <id> # no login: an agent token"],
2356
+ learn: `${REF}#sesion`
2357
+ };
2358
+ HELPS.logout = { summary: "Sign out and revoke this device's session on the server.", usage: ["ghosty logout [--profile NAME]"], examples: ["ghosty logout"], learn: `${REF}#sesion` };
2359
+ HELPS.whoami = { summary: "Who you are signed in as (with --json, also your account's plan).", usage: ["ghosty whoami [--json]"], examples: ["ghosty whoami"], learn: `${REF}#sesion` };
2360
+ HELPS.docs = { summary: "Open the docs in your browser (with --json, just print the URL).", usage: ["ghosty docs [topic]"], examples: ["ghosty docs", "ghosty docs cli"], learn: REF };
2361
+ HELPS.use = {
2362
+ summary: "A default workspace for this session (profile): boards take it without typing it, agents create is born there and agents ls lists only its agents.",
2363
+ usage: ["ghosty use # which one", "ghosty use <workspace>", "ghosty use --clear"],
2364
+ flags: [["--clear", "no default workspace"]],
2365
+ examples: ["ghosty use siiqtec", "ghosty boards ls", "ghosty agents create --name tania-mkt --like pia-0 --board new", "ghosty agents ls --all"],
2366
+ notes: ["Agents can be named instead of using their id anywhere; with several of the same name, the one in this workspace wins.", "agents create --workspace none makes a personal agent even with a default workspace."],
2367
+ learn: `${REF}#sesion`
2368
+ };
2342
2369
  HELPS.boards = {
2343
2370
  summary: "A WORKSPACE's sales boards: list them, create one (copying another's columns), choose where an agent sends its cards, and move cards between boards.",
2344
2371
  usage: [
@@ -2359,12 +2386,12 @@ HELPS.boards = {
2359
2386
  notes: ["assign changes where NEW cards go; move-cards brings the existing ones (thread, orders, activity) with a new folio. A card whose conversation is already on the target board is skipped.", "Reading needs membership; changing needs owner or staff of the workspace.", "rm only deletes an empty board that is not the main one."],
2360
2387
  learn: `${REF}#tablero`
2361
2388
  };
2362
- HELPS.board = {
2363
- summary: "The agent's sales board. It belongs to the WORKSPACE: you must be a member, a shared agent is not enough.",
2389
+ HELPS.cards = {
2390
+ summary: "The cards on an agent's sales board (its board belongs to the WORKSPACE: you must be a member, a shared agent is not enough). The workspace's boards themselves: ghosty boards.",
2364
2391
  usage: [
2365
- "ghosty board ls <agent-id> [--q TEXT] [--limit 20]",
2366
- "ghosty board show <agent-id> <folio|phone|session|id> [--limit 20]",
2367
- "ghosty board archive <agent-id> [--integration ID] [--column NAME] [--before 30d|ISO] [--dry-run] [--yes]"
2392
+ "ghosty cards ls <agent-id> [--q TEXT] [--limit 20]",
2393
+ "ghosty cards show <agent-id> <folio|phone|session|id> [--limit 20]",
2394
+ "ghosty cards archive <agent-id> [--integration ID] [--column NAME] [--before 30d|ISO] [--dry-run] [--yes]"
2368
2395
  ],
2369
2396
  flags: [
2370
2397
  ["--q TEXT", "ls: cards whose name, phone or last message contains TEXT"],
@@ -2375,10 +2402,11 @@ HELPS.board = {
2375
2402
  ["-n, --dry-run", "count and show a sample, change nothing"],
2376
2403
  ["--yes", "skip the confirmation (required without a terminal)"]
2377
2404
  ],
2378
- examples: ["ghosty board ls <id> --q 7712", 'ghosty board show <id> "#12"', "ghosty board show <id> 5210000009922 --limit 50", "ghosty board archive <id> --integration prueba --dry-run", "ghosty board archive <id> --column Perdido --before 30d --yes"],
2405
+ examples: ["ghosty cards ls <id> --q 7712", 'ghosty cards show <id> "#12"', "ghosty cards show <id> 5210000009922 --limit 50", "ghosty cards archive <id> --integration prueba --dry-run", "ghosty cards archive <id> --column Perdido --before 30d --yes"],
2379
2406
  notes: ["show prints each message with who wrote it (customer, agent, operator), its attachment and whether gs keeps a copy of it.", "Archiving is reversible: when the customer writes again the card comes back. At least one filter is required."],
2380
2407
  learn: `${REF}#tablero`
2381
2408
  };
2409
+ HELPS.board = HELPS.cards;
2382
2410
  HELPS.conversations = HELPS.chat;
2383
2411
  HELPS.usage = { ...HELPS.me, summary: "Your plan and how much you've used this week and month.", usage: ["ghosty usage", "ghosty me usage"], examples: ["ghosty usage", "ghosty usage --json"], learn: `${REF}#uso` };
2384
2412
  function helpText(cmd) {
@@ -2414,28 +2442,84 @@ function newer(a, b) {
2414
2442
  }
2415
2443
  async function latestVersion() {
2416
2444
  if (process.env.GHOSTY_NO_UPDATE_CHECK || process.env.CI) return null;
2417
- let cache = {};
2445
+ let cache2 = {};
2418
2446
  try {
2419
- cache = JSON.parse(await readFile9(FILE, "utf8"));
2447
+ cache2 = JSON.parse(await readFile9(FILE, "utf8"));
2420
2448
  } catch {
2421
2449
  }
2422
- if (!cache.checkedAt || Date.now() - cache.checkedAt > DAY) {
2450
+ if (!cache2.checkedAt || Date.now() - cache2.checkedAt > DAY) {
2423
2451
  try {
2424
2452
  const r = await fetch("https://registry.npmjs.org/@ghostystudio/cli/latest", { signal: AbortSignal.timeout(1500) });
2425
2453
  const latest = (await r.json()).version;
2426
- if (latest) cache = { latest, checkedAt: Date.now() };
2454
+ if (latest) cache2 = { latest, checkedAt: Date.now() };
2427
2455
  await mkdir4(dirname3(FILE), { recursive: true });
2428
- await writeFile4(FILE, JSON.stringify(cache));
2456
+ await writeFile4(FILE, JSON.stringify(cache2));
2429
2457
  } catch {
2430
2458
  }
2431
2459
  }
2432
- return cache.latest ?? null;
2460
+ return cache2.latest ?? null;
2433
2461
  }
2434
2462
  async function updateNotice(current) {
2435
2463
  const latest = await latestVersion();
2436
2464
  return latest && newer(latest, current) ? `Update available: ${current} \u2192 ${latest}. Run: ${UPGRADE}` : null;
2437
2465
  }
2438
2466
 
2467
+ // src/resolve.ts
2468
+ var looksLikeId = (s) => /^c[a-z0-9]{20,}$/.test(s) || /^[0-9a-f]{24}$/.test(s);
2469
+ function pickAgent(list, ref, workspace) {
2470
+ const want = ref.trim().toLowerCase();
2471
+ let hits = list.filter((a) => a.nombre.trim().toLowerCase() === want);
2472
+ if (hits.length > 1 && workspace) {
2473
+ const here = hits.filter((a) => a.espacio?.nombre === workspace);
2474
+ if (here.length) hits = here;
2475
+ }
2476
+ if (hits.length === 1) return hits[0].id;
2477
+ if (!hits.length) {
2478
+ const near = list.filter((a) => a.nombre.toLowerCase().includes(want)).slice(0, 5);
2479
+ throw new CliError(`No agent named "${ref}".`, EXIT.USAGE, near.length ? `Did you mean: ${near.map((a) => `${a.nombre} (${a.id})`).join(", ")}?` : "List them: ghosty agents ls");
2480
+ }
2481
+ const where = (a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "?";
2482
+ throw new CliError(`${hits.length} agents are named "${ref}".`, EXIT.USAGE, `Use the id:
2483
+ ${hits.map((a) => ` ${a.id} ${a.motor ?? ""} ${where(a)}`).join("\n")}`);
2484
+ }
2485
+ var cache = null;
2486
+ function listAgents() {
2487
+ cache ??= api("/api/v2/me/agents?client=cli").then((r) => r.agentes);
2488
+ return cache;
2489
+ }
2490
+ async function resolveAgent(ref, workspace) {
2491
+ if (!ref || looksLikeId(ref)) return ref;
2492
+ return pickAgent(await listAgents(), ref, workspace);
2493
+ }
2494
+
2495
+ // src/aliases.ts
2496
+ function normalizeArgs(pos, v) {
2497
+ const notes = [];
2498
+ const [cmd, sub] = pos;
2499
+ if (cmd === "cards") pos[0] = "board";
2500
+ else if (cmd === "board") notes.push("`ghosty board` is now `ghosty cards` (same subcommands).");
2501
+ if (cmd === "skills") {
2502
+ if (sub === "enable") pos[1] = "on";
2503
+ else if (sub === "disable") pos[1] = "off";
2504
+ else if (sub === "on" || sub === "off") notes.push(`\`skills ${sub}\` is now \`skills ${sub === "on" ? "enable" : "disable"}\`.`);
2505
+ }
2506
+ if (cmd === "agents" && sub === "demo") {
2507
+ if (pos[2] === "enable" || pos[2] === "disable") {
2508
+ v[pos[2] === "enable" ? "on" : "off"] = true;
2509
+ pos.splice(2, 1);
2510
+ } else if (v.on || v.off) notes.push(`\`agents demo <agent> --${v.on ? "on" : "off"}\` is now \`agents demo ${v.on ? "enable" : "disable"} <agent>\`.`);
2511
+ if (v.expires !== void 0) v.vence = v.expires;
2512
+ else if (v.vence !== void 0) notes.push("`--vence` is now `--expires`.");
2513
+ }
2514
+ if (cmd === "channels" && pos[2] === "groups") {
2515
+ if (pos[3] === "enable" || pos[3] === "disable") {
2516
+ v[pos[3] === "enable" ? "on" : "off"] = true;
2517
+ pos[3] = "set";
2518
+ } else if (pos[3] === "set" && (v.on || v.off)) notes.push(`\`groups set \u2026 --${v.on ? "on" : "off"}\` is now \`groups ${v.on ? "enable" : "disable"} <agent> <group>\` (set keeps --role).`);
2519
+ }
2520
+ return notes;
2521
+ }
2522
+
2439
2523
  // src/index.ts
2440
2524
  var HELP = `${bold("Usage:")} ghosty <command> [options]
2441
2525
 
@@ -2444,6 +2528,7 @@ ${bold("Session")}
2444
2528
  --device Sign in from another device with a link + code (auto over SSH/CI)
2445
2529
  logout Sign out and revoke this device's session
2446
2530
  whoami Show who you are signed in as
2531
+ use [workspace] Default workspace for boards, agents create and agents ls [--clear]
2447
2532
  usage Your plan and how much you've used this week and month
2448
2533
 
2449
2534
  ${bold("Agents")}
@@ -2466,12 +2551,13 @@ ${bold("Agents")}
2466
2551
  agents rm <id> Delete an agent (owner only; asks you to type its name, or --yes)
2467
2552
  agents restart <id> Restart an ACP agent (needsRestart) so it loads new skills
2468
2553
  agents box <id> The machine serving it: is it on the new image? [--check PATH]\u2026 [--contains PATH=TEXT]\u2026 [--recycle]
2554
+ agents demo enable|disable <id> Turn its public demo link on or off
2469
2555
  agents demo <id> Public demo link /p/<slug>: anyone tries it without an account, "Keep it" clones it to theirs
2470
- [--slug NAME] [--on | --off] [--vence "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXT] [--chips "a|b|c"] [--starter TEXT] [--sponsor on|off] [--rotate]
2556
+ [--slug NAME] [--expires "YYYY-MM-DDTHH:MM"|no] [--turns N] [--per-ip N] [--welcome TEXT] [--chips "a|b|c"] [--starter TEXT] [--sponsor on|off] [--rotate]
2471
2557
 
2472
2558
  ${bold("Configure an agent")}
2473
2559
  files ls|get|put|rm <id> [path] Files in the agent's machine [--file LOCAL] [--out LOCAL]
2474
- skills on|off <id> <slug> Turn one of its skills (or one of Ghosty's) on or off
2560
+ skills enable|disable <id> <slug> Turn one of its skills (or one of Ghosty's) on or off
2475
2561
  skills house <id> Ghosty's skills in bulk [--only a,b | --enable a,b | --disable a,b] [--dry-run]
2476
2562
  skills ls|get|add|rm <id> [slug] Skills (add from catalog, --file SKILL.md or --dir FOLDER; get lists, --out DIR saves; rm keeps a copy)
2477
2563
  skills templates <id> pdf-doc templates it sees: its own and Ghosty's, and which folder wins
@@ -2483,11 +2569,11 @@ ${bold("Configure an agent")}
2483
2569
  channels wa import <id> --from-file F | --from-dir DIR --own-number yes|no Move a personal WhatsApp session here, no QR [--dry-run] [--replace] [--no-connect]
2484
2570
  channels wa pair <id> --phone N Pair a personal WhatsApp by code (no QR)
2485
2571
  channels wa status|pause <id> Personal WhatsApp: how it is / pause it (keeps the session)
2486
- channels wa groups ls|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--on|--off] [--dry-run]
2572
+ channels wa groups ls|enable|disable|set|create <id> [group] Personal WhatsApp groups it answers, and as whom [--role equipo|cliente] [--dry-run]
2487
2573
  boards ls|create|assign|move-cards|rm A workspace's boards: create (--columns-from), where an agent sends cards, move cards
2488
- board ls <id> Recent cards on its sales board [--q TEXT] [--limit 20]
2489
- board show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
2490
- board archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
2574
+ cards ls <id> Recent cards on its sales board [--q TEXT] [--limit 20]
2575
+ cards show <id> <card> A card's thread: who wrote, text, attachments and whether gs kept a copy (folio, phone or id)
2576
+ cards archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
2491
2577
 
2492
2578
  ${bold("Talk to an agent")}
2493
2579
  try <id> "message" One test turn, prints the full answer [--session] [--reset]
@@ -2513,6 +2599,7 @@ ${bold("Your account")}
2513
2599
  docs [topic] Open the docs
2514
2600
 
2515
2601
  ${bold("Global options")}
2602
+ <agent> Everywhere: an agent's NAME (case-insensitive) or its id
2516
2603
  --json Machine-readable output (stdout is JSON only)
2517
2604
  --token <token> Use this token (or env GHOSTY_TOKEN). A gat_\u2026 agent token reaches only that agent.
2518
2605
  --profile <name> Use a separate saved session (or env GHOSTY_PROFILE), e.g. one per account
@@ -2523,7 +2610,7 @@ ${bold("Global options")}
2523
2610
  ${bold("Exit codes")} 0 ok \xB7 1 API error \xB7 2 usage error \xB7 3 not signed in
2524
2611
  ${dim("Note: `ghosty` may collide with another binary on your PATH (e.g. the Ghosty TUI). Check with `which -a ghosty`; run `npx @ghostystudio/cli` to be sure.")}
2525
2612
  ${dim("Docs: https://www.ghosty.studio/docs/cli")}`;
2526
- var COMMANDS = ["login", "logout", "whoami", "usage", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
2613
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
2527
2614
  var AGENTS_SUBS = ["ls", "create", "get", "set", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
2528
2615
  function unknown(what, input, known, helpCmd) {
2529
2616
  const s = suggest(input, known);
@@ -2533,6 +2620,45 @@ function showHelp() {
2533
2620
  if (process.stdout.isTTY && !process.env.NO_COLOR && !globals.json) process.stdout.write(BANNER + "\n\n");
2534
2621
  process.stdout.write(HELP + "\n");
2535
2622
  }
2623
+ function agentArgIndex(pos) {
2624
+ const [cmd, sub] = pos;
2625
+ switch (cmd) {
2626
+ case "agents":
2627
+ if (sub === "docs" || sub === "documents") return 3;
2628
+ return sub && !["ls", "list", "create", "new"].includes(sub) ? 2 : null;
2629
+ case "files":
2630
+ case "skills":
2631
+ case "mcp":
2632
+ case "dbs":
2633
+ case "db":
2634
+ case "turns":
2635
+ case "board":
2636
+ case "conversations":
2637
+ case "schedule":
2638
+ return 2;
2639
+ case "try":
2640
+ case "chat":
2641
+ return 1;
2642
+ case "channels":
2643
+ return pos[2] === "groups" ? 4 : 3;
2644
+ case "boards":
2645
+ return sub === "assign" ? 2 : null;
2646
+ default:
2647
+ return null;
2648
+ }
2649
+ }
2650
+ async function useCmd(slug, v) {
2651
+ const cfg = await readConfig();
2652
+ if (v.clear) {
2653
+ delete cfg.workspace;
2654
+ await writeConfig(cfg);
2655
+ return out({ workspace: null }, () => `${lavender("\u2713")} No default workspace.`);
2656
+ }
2657
+ if (!slug) return out({ workspace: cfg.workspace ?? null }, () => cfg.workspace ? `Workspace: ${bold(cfg.workspace)}` : dim("No default workspace. Set one: ghosty use <workspace>"));
2658
+ await api(`/api/v2/workspaces/${encodeURIComponent(slug)}/boards`);
2659
+ await writeConfig({ ...cfg, workspace: slug });
2660
+ return out({ workspace: slug }, () => `${lavender("\u2713")} Using ${bold(slug)}: boards, agents create and agents ls default to it. ${dim("(ghosty use --clear to stop)")}`);
2661
+ }
2536
2662
  async function main() {
2537
2663
  const { values: v, positionals: pos } = parseArgs({
2538
2664
  allowPositionals: true,
@@ -2614,6 +2740,8 @@ async function main() {
2614
2740
  "columns-from": { type: "string" },
2615
2741
  to: { type: "string" },
2616
2742
  all: { type: "boolean" },
2743
+ clear: { type: "boolean" },
2744
+ expires: { type: "string" },
2617
2745
  q: { type: "string" },
2618
2746
  "prompt-out": { type: "string" },
2619
2747
  toolsets: { type: "string" },
@@ -2654,7 +2782,17 @@ async function main() {
2654
2782
  const tokens = splitTokens(pos, v);
2655
2783
  globals.token = tokens.session;
2656
2784
  globals.profile = v.profile;
2657
- if (v.version) return out({ version: "0.16.1" }, () => "0.16.1");
2785
+ if (v.version) return out({ version: "0.18.0" }, () => "0.18.0");
2786
+ for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
2787
+ const context = (await readConfig()).workspace;
2788
+ if (!explicitToken()?.startsWith("gat_")) {
2789
+ const at = agentArgIndex(pos);
2790
+ if (at !== null && pos[at]) pos[at] = await resolveAgent(pos[at], context);
2791
+ for (const f of ["like", "prompt-base"]) {
2792
+ const val = v[f];
2793
+ if (typeof val === "string" && val !== "none") v[f] = await resolveAgent(val, context);
2794
+ }
2795
+ }
2658
2796
  const [cmd, sub, a1, a2] = pos;
2659
2797
  if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
2660
2798
  const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
@@ -2680,9 +2818,18 @@ async function main() {
2680
2818
  const me = await api("/api/v2/me");
2681
2819
  return out(me, () => `${bold(me.email)}${dim(` \xB7 ${new URL(process.env.GHOSTY_URL || "https://www.ghosty.studio").host}`)}`);
2682
2820
  }
2821
+ case "use":
2822
+ return useCmd(sub, v);
2683
2823
  case "agents":
2684
- if (sub === "ls" || sub === "list" || !sub) return agentsLs();
2685
- if (sub === "create" || sub === "new") return agentsCreate(v);
2824
+ if (sub === "ls" || sub === "list" || !sub) return agentsLs(v.all ? void 0 : context);
2825
+ if (sub === "create" || sub === "new") {
2826
+ if (v.workspace === "none") delete v.workspace;
2827
+ else if (!v.workspace && context) {
2828
+ v.workspace = context;
2829
+ if (!globals.json) info(dim(`workspace ${context} (from ghosty use; --workspace none for a personal agent)`));
2830
+ }
2831
+ return agentsCreate(v);
2832
+ }
2686
2833
  if (sub === "get") return agentsGet(a1, v.fields, v["prompt-out"]);
2687
2834
  if (sub === "share" || sub === "unshare" || sub === "shares") return agentsShare(sub, a1, a2, v);
2688
2835
  if (sub === "set") return agentsSet(a1, v);
@@ -2707,7 +2854,7 @@ async function main() {
2707
2854
  case "board":
2708
2855
  return boardCmd(sub, a1, v, a2);
2709
2856
  case "boards":
2710
- return boardsCmd(sub, a1, a2, v);
2857
+ return boardsCmd(sub, a1, a2, v, context);
2711
2858
  case "channels":
2712
2859
  if (a1 === "groups") return groupsCmd(pos.slice(3).filter((x) => x !== void 0), v);
2713
2860
  return channelsCmd(sub, a1, a2, pos.slice(4).join(" ") || void 0, v);
@@ -2742,7 +2889,7 @@ async function main() {
2742
2889
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
2743
2890
  }
2744
2891
  }
2745
- var notice = updateNotice("0.16.1").catch(() => null);
2892
+ var notice = updateNotice("0.18.0").catch(() => null);
2746
2893
  main().then(
2747
2894
  async () => {
2748
2895
  const n = await notice;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.16.1",
3
+ "version": "0.18.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": {
@@ -16,7 +16,7 @@
16
16
  "scripts": {
17
17
  "build": "esbuild src/index.ts --bundle --platform=node --format=esm --target=node22 --outfile=dist/index.js --banner:js='#!/usr/bin/env node' --define:__VERSION__=\\\"$npm_package_version\\\"",
18
18
  "prepack": "npm run build",
19
- "test": "for t in flags prompt api rows wa-auth; do esbuild src/$t.test.ts --bundle --platform=node --format=esm --outfile=/tmp/ghosty-cli-$t.test.mjs --log-level=error || exit 1; done && node --test /tmp/ghosty-cli-*.test.mjs"
19
+ "test": "for t in flags prompt api rows wa-auth resolve aliases; do esbuild src/$t.test.ts --bundle --platform=node --format=esm --outfile=/tmp/ghosty-cli-$t.test.mjs --log-level=error || exit 1; done && node --test /tmp/ghosty-cli-*.test.mjs"
20
20
  },
21
21
  "keywords": [
22
22
  "ghosty",