@ghostystudio/cli 0.16.1 → 0.17.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 +123 -19
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -87,6 +87,11 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
87
87
  - `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
88
  - `agents doctor` speaks English like the rest of the CLI; `channels whatsapp ls` returns a full URL.
89
89
 
90
+ ### Changes in 0.17.0
91
+
92
+ - 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.
93
+ - `ghosty use <workspace>`: default workspace for `boards`, `agents create` and `agents ls` (`--all`, `--workspace none`, `use --clear`).
94
+
90
95
  ### Changes in 0.16.1
91
96
 
92
97
  - `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.17.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.17.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.17.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": {
@@ -2339,6 +2347,14 @@ HELPS.channels = {
2339
2347
  ],
2340
2348
  learn: `${REF}#canales-de-whatsapp`
2341
2349
  };
2350
+ HELPS.use = {
2351
+ 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.",
2352
+ usage: ["ghosty use # which one", "ghosty use <workspace>", "ghosty use --clear"],
2353
+ flags: [["--clear", "no default workspace"]],
2354
+ examples: ["ghosty use siiqtec", "ghosty boards ls", "ghosty agents create --name tania-mkt --like pia-0 --board new", "ghosty agents ls --all"],
2355
+ 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."],
2356
+ learn: `${REF}#sesion`
2357
+ };
2342
2358
  HELPS.boards = {
2343
2359
  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
2360
  usage: [
@@ -2414,28 +2430,56 @@ function newer(a, b) {
2414
2430
  }
2415
2431
  async function latestVersion() {
2416
2432
  if (process.env.GHOSTY_NO_UPDATE_CHECK || process.env.CI) return null;
2417
- let cache = {};
2433
+ let cache2 = {};
2418
2434
  try {
2419
- cache = JSON.parse(await readFile9(FILE, "utf8"));
2435
+ cache2 = JSON.parse(await readFile9(FILE, "utf8"));
2420
2436
  } catch {
2421
2437
  }
2422
- if (!cache.checkedAt || Date.now() - cache.checkedAt > DAY) {
2438
+ if (!cache2.checkedAt || Date.now() - cache2.checkedAt > DAY) {
2423
2439
  try {
2424
2440
  const r = await fetch("https://registry.npmjs.org/@ghostystudio/cli/latest", { signal: AbortSignal.timeout(1500) });
2425
2441
  const latest = (await r.json()).version;
2426
- if (latest) cache = { latest, checkedAt: Date.now() };
2442
+ if (latest) cache2 = { latest, checkedAt: Date.now() };
2427
2443
  await mkdir4(dirname3(FILE), { recursive: true });
2428
- await writeFile4(FILE, JSON.stringify(cache));
2444
+ await writeFile4(FILE, JSON.stringify(cache2));
2429
2445
  } catch {
2430
2446
  }
2431
2447
  }
2432
- return cache.latest ?? null;
2448
+ return cache2.latest ?? null;
2433
2449
  }
2434
2450
  async function updateNotice(current) {
2435
2451
  const latest = await latestVersion();
2436
2452
  return latest && newer(latest, current) ? `Update available: ${current} \u2192 ${latest}. Run: ${UPGRADE}` : null;
2437
2453
  }
2438
2454
 
2455
+ // src/resolve.ts
2456
+ var looksLikeId = (s) => /^c[a-z0-9]{20,}$/.test(s) || /^[0-9a-f]{24}$/.test(s);
2457
+ function pickAgent(list, ref, workspace) {
2458
+ const want = ref.trim().toLowerCase();
2459
+ let hits = list.filter((a) => a.nombre.trim().toLowerCase() === want);
2460
+ if (hits.length > 1 && workspace) {
2461
+ const here = hits.filter((a) => a.espacio?.nombre === workspace);
2462
+ if (here.length) hits = here;
2463
+ }
2464
+ if (hits.length === 1) return hits[0].id;
2465
+ if (!hits.length) {
2466
+ const near = list.filter((a) => a.nombre.toLowerCase().includes(want)).slice(0, 5);
2467
+ 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");
2468
+ }
2469
+ const where = (a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "?";
2470
+ throw new CliError(`${hits.length} agents are named "${ref}".`, EXIT.USAGE, `Use the id:
2471
+ ${hits.map((a) => ` ${a.id} ${a.motor ?? ""} ${where(a)}`).join("\n")}`);
2472
+ }
2473
+ var cache = null;
2474
+ function listAgents() {
2475
+ cache ??= api("/api/v2/me/agents?client=cli").then((r) => r.agentes);
2476
+ return cache;
2477
+ }
2478
+ async function resolveAgent(ref, workspace) {
2479
+ if (!ref || looksLikeId(ref)) return ref;
2480
+ return pickAgent(await listAgents(), ref, workspace);
2481
+ }
2482
+
2439
2483
  // src/index.ts
2440
2484
  var HELP = `${bold("Usage:")} ghosty <command> [options]
2441
2485
 
@@ -2444,6 +2488,7 @@ ${bold("Session")}
2444
2488
  --device Sign in from another device with a link + code (auto over SSH/CI)
2445
2489
  logout Sign out and revoke this device's session
2446
2490
  whoami Show who you are signed in as
2491
+ use [workspace] Default workspace for boards, agents create and agents ls [--clear]
2447
2492
  usage Your plan and how much you've used this week and month
2448
2493
 
2449
2494
  ${bold("Agents")}
@@ -2513,6 +2558,7 @@ ${bold("Your account")}
2513
2558
  docs [topic] Open the docs
2514
2559
 
2515
2560
  ${bold("Global options")}
2561
+ <agent> Everywhere: an agent's NAME (case-insensitive) or its id
2516
2562
  --json Machine-readable output (stdout is JSON only)
2517
2563
  --token <token> Use this token (or env GHOSTY_TOKEN). A gat_\u2026 agent token reaches only that agent.
2518
2564
  --profile <name> Use a separate saved session (or env GHOSTY_PROFILE), e.g. one per account
@@ -2523,7 +2569,7 @@ ${bold("Global options")}
2523
2569
  ${bold("Exit codes")} 0 ok \xB7 1 API error \xB7 2 usage error \xB7 3 not signed in
2524
2570
  ${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
2571
  ${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"];
2572
+ var COMMANDS = ["login", "logout", "whoami", "usage", "use", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "partner", "me", "channels", "board", "boards", "credentials", "docs", "help"];
2527
2573
  var AGENTS_SUBS = ["ls", "create", "get", "set", "doctor", "docs", "share", "unshare", "shares", "rm", "restart", "demo", "box"];
2528
2574
  function unknown(what, input, known, helpCmd) {
2529
2575
  const s = suggest(input, known);
@@ -2533,6 +2579,45 @@ function showHelp() {
2533
2579
  if (process.stdout.isTTY && !process.env.NO_COLOR && !globals.json) process.stdout.write(BANNER + "\n\n");
2534
2580
  process.stdout.write(HELP + "\n");
2535
2581
  }
2582
+ function agentArgIndex(pos) {
2583
+ const [cmd, sub] = pos;
2584
+ switch (cmd) {
2585
+ case "agents":
2586
+ if (sub === "docs" || sub === "documents") return 3;
2587
+ return sub && !["ls", "list", "create", "new"].includes(sub) ? 2 : null;
2588
+ case "files":
2589
+ case "skills":
2590
+ case "mcp":
2591
+ case "dbs":
2592
+ case "db":
2593
+ case "turns":
2594
+ case "board":
2595
+ case "conversations":
2596
+ case "schedule":
2597
+ return 2;
2598
+ case "try":
2599
+ case "chat":
2600
+ return 1;
2601
+ case "channels":
2602
+ return pos[2] === "groups" ? 4 : 3;
2603
+ case "boards":
2604
+ return sub === "assign" ? 2 : null;
2605
+ default:
2606
+ return null;
2607
+ }
2608
+ }
2609
+ async function useCmd(slug, v) {
2610
+ const cfg = await readConfig();
2611
+ if (v.clear) {
2612
+ delete cfg.workspace;
2613
+ await writeConfig(cfg);
2614
+ return out({ workspace: null }, () => `${lavender("\u2713")} No default workspace.`);
2615
+ }
2616
+ if (!slug) return out({ workspace: cfg.workspace ?? null }, () => cfg.workspace ? `Workspace: ${bold(cfg.workspace)}` : dim("No default workspace. Set one: ghosty use <workspace>"));
2617
+ await api(`/api/v2/workspaces/${encodeURIComponent(slug)}/boards`);
2618
+ await writeConfig({ ...cfg, workspace: slug });
2619
+ return out({ workspace: slug }, () => `${lavender("\u2713")} Using ${bold(slug)}: boards, agents create and agents ls default to it. ${dim("(ghosty use --clear to stop)")}`);
2620
+ }
2536
2621
  async function main() {
2537
2622
  const { values: v, positionals: pos } = parseArgs({
2538
2623
  allowPositionals: true,
@@ -2614,6 +2699,7 @@ async function main() {
2614
2699
  "columns-from": { type: "string" },
2615
2700
  to: { type: "string" },
2616
2701
  all: { type: "boolean" },
2702
+ clear: { type: "boolean" },
2617
2703
  q: { type: "string" },
2618
2704
  "prompt-out": { type: "string" },
2619
2705
  toolsets: { type: "string" },
@@ -2654,7 +2740,16 @@ async function main() {
2654
2740
  const tokens = splitTokens(pos, v);
2655
2741
  globals.token = tokens.session;
2656
2742
  globals.profile = v.profile;
2657
- if (v.version) return out({ version: "0.16.1" }, () => "0.16.1");
2743
+ if (v.version) return out({ version: "0.17.0" }, () => "0.17.0");
2744
+ const context = (await readConfig()).workspace;
2745
+ if (!explicitToken()?.startsWith("gat_")) {
2746
+ const at = agentArgIndex(pos);
2747
+ if (at !== null && pos[at]) pos[at] = await resolveAgent(pos[at], context);
2748
+ for (const f of ["like", "prompt-base"]) {
2749
+ const val = v[f];
2750
+ if (typeof val === "string" && val !== "none") v[f] = await resolveAgent(val, context);
2751
+ }
2752
+ }
2658
2753
  const [cmd, sub, a1, a2] = pos;
2659
2754
  if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
2660
2755
  const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
@@ -2680,9 +2775,18 @@ async function main() {
2680
2775
  const me = await api("/api/v2/me");
2681
2776
  return out(me, () => `${bold(me.email)}${dim(` \xB7 ${new URL(process.env.GHOSTY_URL || "https://www.ghosty.studio").host}`)}`);
2682
2777
  }
2778
+ case "use":
2779
+ return useCmd(sub, v);
2683
2780
  case "agents":
2684
- if (sub === "ls" || sub === "list" || !sub) return agentsLs();
2685
- if (sub === "create" || sub === "new") return agentsCreate(v);
2781
+ if (sub === "ls" || sub === "list" || !sub) return agentsLs(v.all ? void 0 : context);
2782
+ if (sub === "create" || sub === "new") {
2783
+ if (v.workspace === "none") delete v.workspace;
2784
+ else if (!v.workspace && context) {
2785
+ v.workspace = context;
2786
+ if (!globals.json) info(dim(`workspace ${context} (from ghosty use; --workspace none for a personal agent)`));
2787
+ }
2788
+ return agentsCreate(v);
2789
+ }
2686
2790
  if (sub === "get") return agentsGet(a1, v.fields, v["prompt-out"]);
2687
2791
  if (sub === "share" || sub === "unshare" || sub === "shares") return agentsShare(sub, a1, a2, v);
2688
2792
  if (sub === "set") return agentsSet(a1, v);
@@ -2707,7 +2811,7 @@ async function main() {
2707
2811
  case "board":
2708
2812
  return boardCmd(sub, a1, v, a2);
2709
2813
  case "boards":
2710
- return boardsCmd(sub, a1, a2, v);
2814
+ return boardsCmd(sub, a1, a2, v, context);
2711
2815
  case "channels":
2712
2816
  if (a1 === "groups") return groupsCmd(pos.slice(3).filter((x) => x !== void 0), v);
2713
2817
  return channelsCmd(sub, a1, a2, pos.slice(4).join(" ") || void 0, v);
@@ -2742,7 +2846,7 @@ async function main() {
2742
2846
  throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
2743
2847
  }
2744
2848
  }
2745
- var notice = updateNotice("0.16.1").catch(() => null);
2849
+ var notice = updateNotice("0.17.0").catch(() => null);
2746
2850
  main().then(
2747
2851
  async () => {
2748
2852
  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.17.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; 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",