@ghostystudio/cli 0.5.0 → 0.7.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 (2) hide show
  1. package/dist/index.js +337 -14
  2. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -1,5 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/flags.ts
4
+ function splitTokens(pos, v) {
5
+ const [cmd, sub] = pos;
6
+ const isTry = cmd === "partner" && sub === "try";
7
+ if (isTry) return { session: void 0, turn: v["turn-token"] ?? v.token };
8
+ return { session: v.token, turn: v["turn-token"] };
9
+ }
10
+
3
11
  // src/index.ts
4
12
  import { parseArgs } from "node:util";
5
13
 
@@ -69,20 +77,27 @@ async function openUrl(url) {
69
77
  var CLIENT_ID = "ghosty-cli";
70
78
  var SCOPES = "profile agents:read agents:write files";
71
79
  var LOGIN_TIMEOUT_MS = 5 * 6e4;
72
- var CONFIG_PATH = join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "ghosty", "config.json");
80
+ function configPath() {
81
+ const profile = globals.profile || process.env.GHOSTY_PROFILE || "";
82
+ if (profile && !/^[A-Za-z0-9_-]{1,40}$/.test(profile)) {
83
+ throw new CliError(`Invalid profile: ${profile}.`, EXIT.USAGE, "Use letters, digits, - or _");
84
+ }
85
+ return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "ghosty", profile ? `config.${profile}.json` : "config.json");
86
+ }
73
87
  async function readConfig() {
74
88
  try {
75
- return JSON.parse(await readFile(CONFIG_PATH, "utf8"));
89
+ return JSON.parse(await readFile(configPath(), "utf8"));
76
90
  } catch {
77
91
  return {};
78
92
  }
79
93
  }
80
94
  async function writeConfig(c) {
81
- await mkdir(dirname(CONFIG_PATH), { recursive: true });
82
- await writeFile(CONFIG_PATH, JSON.stringify(c, null, 2), { mode: 384 });
83
- await chmod(CONFIG_PATH, 384);
95
+ const path = configPath();
96
+ await mkdir(dirname(path), { recursive: true });
97
+ await writeFile(path, JSON.stringify(c, null, 2), { mode: 384 });
98
+ await chmod(path, 384);
84
99
  }
85
- var clearConfig = () => rm(CONFIG_PATH, { force: true });
100
+ var clearConfig = () => rm(configPath(), { force: true });
86
101
  async function baseUrl() {
87
102
  const url = process.env.GHOSTY_URL || (await readConfig()).baseUrl || "https://www.ghosty.studio";
88
103
  return url.replace(/\/+$/, "");
@@ -275,7 +290,7 @@ async function request(path, opts = {}, retried = false) {
275
290
  method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
276
291
  headers: {
277
292
  authorization: `Bearer ${token}`,
278
- "user-agent": `ghosty-cli/${"0.5.0"}`,
293
+ "user-agent": `ghosty-cli/${"0.7.0"}`,
279
294
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
280
295
  ...opts.headers
281
296
  },
@@ -364,7 +379,11 @@ async function agentsSet(id, v) {
364
379
  if (v["prompt-file"]) body.prompt = await readFile2(v["prompt-file"], "utf8");
365
380
  const env = parseEnv(v.env);
366
381
  if (env) body.env = env;
367
- if (!Object.keys(body).length) throw usage("Nothing to change.", "Use --name, --engine, --model, --prompt, --prompt-file or --env.");
382
+ const connectors = {};
383
+ for (const c of (v.connect ?? "").split(",").map((s) => s.trim()).filter(Boolean)) connectors[c] = {};
384
+ for (const c of (v.disconnect ?? "").split(",").map((s) => s.trim()).filter(Boolean)) connectors[c] = null;
385
+ if (Object.keys(connectors).length) body.connectors = connectors;
386
+ if (!Object.keys(body).length) throw usage("Nothing to change.", "Use --name, --engine, --model, --prompt, --prompt-file, --env, --connect or --disconnect.");
368
387
  const r = await api(agentPath(id), { method: "PATCH", body });
369
388
  out(r, () => `${lavender("\u2713")} Updated ${Object.keys(body).join(", ")}.`);
370
389
  }
@@ -435,9 +454,18 @@ async function skillsCmd(sub, id, slug, v) {
435
454
  const ex = "ghosty skills ls <agent-id> | add <agent-id> <slug> [--file SKILL.md] | rm <agent-id> <slug>";
436
455
  id = need(id, "agent id", ex);
437
456
  switch (sub) {
438
- case "ls":
439
- out(await api(agentPath(id, "/skills")));
457
+ case "ls": {
458
+ const r = await api(agentPath(id, "/skills"));
459
+ const short2 = (d) => (d ?? "").replace(/\s+/g, " ").slice(0, 70);
460
+ const rows = [
461
+ ...r.skills.map((s) => ({ slug: s.slug, source: "own", on: "on", description: short2(s.description) })),
462
+ ...(r.house ?? []).map((s) => ({ slug: s.slug, source: "ghosty", on: s.enabled ? "on" : "off", description: short2(s.description) }))
463
+ ];
464
+ const more = r.community.filter((c) => !c.installed).length;
465
+ out(r, () => `${table(rows, ["slug", "source", "on", "description"])}
466
+ ${dim(`${more} more in the community catalog: ghosty skills add <agent-id> <slug> (--json lists them)`)}`);
440
467
  return;
468
+ }
441
469
  case "add": {
442
470
  slug = need(slug, "skill slug", ex);
443
471
  const body = v.file ? { markdown: await readFile2(v.file, "utf8") } : { fromCatalog: true };
@@ -466,6 +494,33 @@ async function mcpCmd(sub, id, v) {
466
494
  }
467
495
  throw usage(`Unknown mcp subcommand: ${sub ?? "(none)"}.`, ex);
468
496
  }
497
+ async function agentsDemo(id, v) {
498
+ id = need(id, "agent id", "ghosty agents demo <agent-id> --slug guardias --on");
499
+ if (v.on && v.off) throw usage("Use --on or --off, not both.");
500
+ const body = {};
501
+ if (v.slug) body.slug = v.slug;
502
+ if (v.on) body.enabled = true;
503
+ if (v.off) body.enabled = false;
504
+ if (v.turns) body.maxTurns = Number(v.turns);
505
+ if (v["per-ip"]) body.perIpDay = Number(v["per-ip"]);
506
+ if (v.welcome !== void 0) body.welcome = v.welcome;
507
+ if (v.chips !== void 0) body.chips = v.chips.split("|").map((c) => c.trim()).filter(Boolean);
508
+ if (v.sponsor !== void 0) {
509
+ if (v.sponsor !== "on" && v.sponsor !== "off") throw usage("--sponsor takes on or off.");
510
+ body.sponsor = v.sponsor === "on";
511
+ }
512
+ const d = Object.keys(body).length ? await api(agentPath(id, "/demo"), { method: "PUT", body }) : await api(agentPath(id, "/demo"));
513
+ out(
514
+ d,
515
+ () => [
516
+ `${d.enabled ? lavender("\u25CF on") : dim("\u25CB off")} ${d.url ?? dim("(no link yet: --slug NAME)")}`,
517
+ dim(`limits: ${d.maxTurns} messages per visitor \xB7 ${d.perIpDay} per IP per day`),
518
+ d.welcome ? `welcome: ${d.welcome}` : dim("welcome: (default greeting)"),
519
+ d.chips.length ? `chips: ${d.chips.join(" | ")}` : dim("chips: (default)"),
520
+ `tried ${d.stats.pruebas} \xB7 messages ${d.stats.mensajes} \xB7 kept ${d.stats.clones} (${d.stats.patrocinados} on your key)`
521
+ ].join("\n")
522
+ );
523
+ }
469
524
 
470
525
  // src/commands/chat.ts
471
526
  import { randomUUID } from "node:crypto";
@@ -635,6 +690,20 @@ ${dim(`${(r.storage.usedBytes / 1e9).toFixed(2)} GB of ${r.storage.limitGb} GB u
635
690
  throw usage(`Unknown files subcommand: ${sub ?? "(none)"}.`, ex);
636
691
  }
637
692
  }
693
+ async function meUsage() {
694
+ const r = await api("/api/v2/me/usage");
695
+ const pct = (p) => `${Math.round(p * 100)}%`;
696
+ out(
697
+ r,
698
+ () => [
699
+ `Plan ${r.plan.name}${r.exhausted ? " \u2014 out of usage for now" : ""}`,
700
+ ` This week ${pct(r.week.pct).padStart(4)} used ${dim(`resets ${r.week.resetsAt.slice(0, 10)}`)}`,
701
+ ...r.month.pct !== null ? [` This month ${pct(r.month.pct).padStart(4)} used ${dim(`resets ${r.month.resetsAt.slice(0, 10)}`)}`] : [],
702
+ ` Top-ups ${r.creditMxn > 0 ? "available" : "none"}`,
703
+ dim("Change plan or top up: https://www.ghosty.studio/c/uso")
704
+ ].join("\n")
705
+ );
706
+ }
638
707
 
639
708
  // src/commands/schedule.ts
640
709
  var enc3 = encodeURIComponent;
@@ -720,6 +789,225 @@ ${dim(`conversation ${p.sessionId}`)}`
720
789
  throw usage(`Unknown schedule subcommand: ${sub ?? "(none)"}.`, "Try: ghosty schedule ls | add | rm");
721
790
  }
722
791
 
792
+ // src/commands/partner.ts
793
+ import { createHmac } from "node:crypto";
794
+ import { readFile as readFile4 } from "node:fs/promises";
795
+ var PARTNER_HELP = `${bold("Usage:")} ghosty partner <command> [options]
796
+
797
+ ${bold("Partner space")} ${dim("(you, signed in as the space owner)")}
798
+ partner ls Your partner spaces
799
+ partner enable <workspace> Turn one of your spaces into a partner space
800
+ partner credentials ls Server credentials (no secrets)
801
+ partner credentials create [--label L] Issue one \u2014 the secret is shown ONCE
802
+ partner credentials rotate <keyId> [--grace MIN] New secret, same keyId (old one valid MIN, default 60)
803
+ partner credentials revoke <keyId> Revoke it (idempotent)
804
+ partner audit Who did what, when
805
+
806
+ ${bold("Shared agent (solo-MCP)")}
807
+ partner agent get The agent template and its version
808
+ partner agent apply --file agent.json Create or update it (idempotent)
809
+
810
+ ${bold("Tenants")} ${dim("(your customers, by externalId \u2014 config only, no box)")}
811
+ partner tenants ls
812
+ partner tenants get <externalId>
813
+ partner tenants set <externalId> --config '{"name":"\u2026","timezone":"America/Mexico_City"}'
814
+ partner tenants rm <externalId> Deletes its config AND its memory
815
+
816
+ ${bold("Try it")}
817
+ partner try <externalId> "message" One turn [--group END_USER_ID] [--turn-token SHORT_LIVED_TOKEN]
818
+ (the turn token goes to YOUR MCP as \${turn.token}; --token also works here)
819
+
820
+ ${bold("Options")}
821
+ --workspace SLUG Which partner space (only if you own more than one)
822
+ --json Machine-readable output
823
+
824
+ ${bold("Server credential mode")} GHOSTY_PARTNER_KEY=gpk_\u2026 GHOSTY_PARTNER_SECRET=gps_\u2026 ghosty partner agent get
825
+ ${dim("Docs: https://www.ghosty.studio/docs/cli")}`;
826
+ var enc4 = encodeURIComponent;
827
+ function partnerCreds() {
828
+ const key = process.env.GHOSTY_PARTNER_KEY;
829
+ const secret = process.env.GHOSTY_PARTNER_SECRET;
830
+ return key && secret ? { key, secret } : null;
831
+ }
832
+ async function call(path, v, init = {}) {
833
+ const method = init.method ?? (init.body !== void 0 ? "POST" : "GET");
834
+ const raw = init.body !== void 0 ? JSON.stringify(init.body) : "";
835
+ const creds = init.ownerOnly ? null : partnerCreds();
836
+ if (creds) {
837
+ const ts = String(Math.floor(Date.now() / 1e3));
838
+ const sig = createHmac("sha256", creds.secret).update(`${ts}.${creds.key}.${raw}`).digest("hex");
839
+ const res = await fetch(`${await baseUrl()}${path}`, {
840
+ method,
841
+ headers: {
842
+ "x-ghosty-key": creds.key,
843
+ "x-ghosty-ts": ts,
844
+ "x-ghosty-sig": sig,
845
+ "content-type": "application/json",
846
+ "user-agent": `ghosty-cli/${"0.7.0"}`
847
+ },
848
+ body: method === "GET" ? void 0 : raw
849
+ });
850
+ if (!res.ok) throw await toError(res);
851
+ return res;
852
+ }
853
+ return request(path, {
854
+ method,
855
+ ...init.body !== void 0 ? { body: init.body } : {},
856
+ headers: v.workspace ? { "x-ghosty-workspace": v.workspace } : {}
857
+ });
858
+ }
859
+ async function json(path, v, init = {}) {
860
+ const res = await call(path, v, init);
861
+ const t = await res.text();
862
+ return t ? JSON.parse(t) : void 0;
863
+ }
864
+ var fmt = (d) => d ? d.replace("T", " ").slice(0, 16) : "\u2014";
865
+ function showSecret(r) {
866
+ out(
867
+ r,
868
+ () => [
869
+ `${lavender("\u2713")} Save these now \u2014 the secret is not shown again:`,
870
+ ` GHOSTY_PARTNER_KEY=${r.keyId}`,
871
+ ` GHOSTY_PARTNER_SECRET=${r.secret}`
872
+ ].join("\n")
873
+ );
874
+ }
875
+ async function credentialsCmd(sub, id, v) {
876
+ const owner = { ownerOnly: true };
877
+ if (!sub || sub === "ls" || sub === "list") {
878
+ const r = await json("/api/v2/partner/credentials", v, owner);
879
+ return out(
880
+ r,
881
+ () => table(
882
+ r.credentials.map((c) => ({ keyId: c.keyId, label: c.label, status: c.status, lastUsed: fmt(c.lastUsedAt), created: fmt(c.createdAt) })),
883
+ ["keyId", "label", "status", "lastUsed", "created"]
884
+ )
885
+ );
886
+ }
887
+ if (sub === "create" || sub === "new") {
888
+ return showSecret(await json("/api/v2/partner/credentials", v, { ...owner, method: "POST", body: { label: v.label } }));
889
+ }
890
+ if (!id) throw usage("Missing keyId.", `Example: ghosty partner credentials ${sub} gpk_\u2026`);
891
+ if (sub === "rotate") {
892
+ const grace = v.grace !== void 0 ? Number(v.grace) : void 0;
893
+ if (grace !== void 0 && (!Number.isFinite(grace) || grace < 0)) throw usage("--grace must be minutes \u2265 0.");
894
+ return showSecret(await json(`/api/v2/partner/credentials/${enc4(id)}/rotate`, v, { ...owner, method: "POST", body: { graceMinutes: grace } }));
895
+ }
896
+ if (sub === "revoke" || sub === "rm") {
897
+ const r = await json(`/api/v2/partner/credentials/${enc4(id)}`, v, { ...owner, method: "DELETE" });
898
+ return out(r, () => `${lavender("\u2713")} Revoked ${r.keyId}.`);
899
+ }
900
+ throw usage(`Unknown credentials subcommand: ${sub}.`, "Try: ghosty partner credentials ls | create | rotate | revoke");
901
+ }
902
+ async function agentCmd(sub, v) {
903
+ if (!sub || sub === "get") {
904
+ const r = await json("/api/v2/partner/agent", v);
905
+ return out(r, () => {
906
+ const a = r.agent;
907
+ return [
908
+ `${bold(a.name)} ${dim(`v${a.version} \xB7 solo-MCP \xB7 ${a.engine}/${a.model}`)}`,
909
+ `MCP: ${a.mcp.map((m) => `${m.name} (${m.url})`).join(", ") || "none"}`,
910
+ dim(a.prompt.slice(0, 300))
911
+ ].join("\n");
912
+ });
913
+ }
914
+ if (sub === "apply" || sub === "set") {
915
+ if (!v.file) throw usage("Missing --file.", "Example: ghosty partner agent apply --file agent.json");
916
+ const body = JSON.parse(await readFile4(v.file, "utf8"));
917
+ const r = await json("/api/v2/partner/agent", v, { method: "PUT", body });
918
+ return out(r, () => `${lavender("\u2713")} ${r.created ? "Created" : r.changed ? "Updated" : "No changes"} \xB7 v${r.agent.version}`);
919
+ }
920
+ throw usage(`Unknown agent subcommand: ${sub}.`, "Try: ghosty partner agent get | apply --file agent.json");
921
+ }
922
+ async function tenantsCmd(sub, id, v) {
923
+ if (!sub || sub === "ls" || sub === "list") {
924
+ const r = await json("/api/v2/partner/tenants", v);
925
+ return out(r, () => table(r.tenants.map((t) => ({ externalId: t.externalId, config: JSON.stringify(t.config ?? {}), updated: fmt(t.updatedAt) })), ["externalId", "config", "updated"]));
926
+ }
927
+ if (!id) throw usage("Missing externalId.", `Example: ghosty partner tenants ${sub} org_123`);
928
+ const path = `/api/v2/partner/tenants/${enc4(id)}`;
929
+ if (sub === "get") return out(await json(path, v));
930
+ if (sub === "set" || sub === "apply") {
931
+ if (!v.config) throw usage("Missing --config.", `Example: --config '{"name":"Spa","timezone":"America/Mexico_City"}'`);
932
+ let config;
933
+ try {
934
+ config = JSON.parse(v.config);
935
+ } catch {
936
+ throw usage("--config must be JSON.");
937
+ }
938
+ const r = await json(path, v, { method: "PUT", body: { config } });
939
+ return out(r, () => `${lavender("\u2713")} ${r.created ? "Created" : r.changed ? "Updated" : "No changes"} ${id}`);
940
+ }
941
+ if (sub === "rm" || sub === "delete") {
942
+ const r = await json(path, v, { method: "DELETE" });
943
+ return out(r, () => r.deleted ? `${lavender("\u2713")} Deleted ${id} (${r.conversations} conversations)` : dim(`${id} did not exist.`));
944
+ }
945
+ throw usage(`Unknown tenants subcommand: ${sub}.`, "Try: ghosty partner tenants ls | get | set | rm");
946
+ }
947
+ async function tryCmd(externalId, text, v) {
948
+ if (!externalId || !text) throw usage("Missing externalId or message.", 'Example: ghosty partner try org_123 "\xBFqu\xE9 citas tengo hoy?"');
949
+ const res = await call("/api/v2/partner/turns", v, {
950
+ method: "POST",
951
+ body: { tenant: { externalId }, groupId: v.group ?? "cli", text, ...v.token ? { token: v.token } : {} }
952
+ });
953
+ const dec = new TextDecoder();
954
+ let buf = "";
955
+ let full = "";
956
+ for await (const part of res.body) {
957
+ buf += dec.decode(part, { stream: true });
958
+ let i;
959
+ while ((i = buf.indexOf("\n\n")) >= 0) {
960
+ const block = buf.slice(0, i);
961
+ buf = buf.slice(i + 2);
962
+ const line = block.split("\n").find((l) => l.startsWith("data:"));
963
+ if (!line) continue;
964
+ const ev = JSON.parse(line.slice(5));
965
+ if (globals.json) {
966
+ process.stdout.write(JSON.stringify(ev) + "\n");
967
+ continue;
968
+ }
969
+ if (ev.type === "chunk" && ev.value) {
970
+ full += ev.value;
971
+ process.stdout.write(ev.value);
972
+ } else if (ev.type === "tool" && ev.phase === "start") info(dim(` \u21B3 ${ev.name}`));
973
+ else if (ev.type === "error") info(`Error: ${ev.message}`);
974
+ }
975
+ }
976
+ if (!globals.json && full) process.stdout.write("\n");
977
+ }
978
+ async function partnerCmd(sub, a1, a2, v) {
979
+ switch (sub) {
980
+ case void 0:
981
+ case "help":
982
+ return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
983
+ case "ls":
984
+ case "list": {
985
+ const r = await json("/api/v2/partner", v, { ownerOnly: true });
986
+ return out(r, () => table(r.partners.map((p) => ({ workspace: p.workspace, credentials: p.credentials, agent: p.agents ? "yes" : "no", status: p.disabledAt ? "off" : "on" })), ["workspace", "credentials", "agent", "status"]));
987
+ }
988
+ case "enable": {
989
+ if (!a1) throw usage("Missing workspace.", "Example: ghosty partner enable denik");
990
+ const r = await json("/api/v2/partner", v, { ownerOnly: true, method: "POST", body: { workspace: a1 } });
991
+ return out(r, () => `${lavender("\u2713")} ${r.partner.workspace} is a partner space.`);
992
+ }
993
+ case "credentials":
994
+ case "creds":
995
+ return credentialsCmd(a1, a2, v);
996
+ case "agent":
997
+ return agentCmd(a1, v);
998
+ case "tenants":
999
+ return tenantsCmd(a1, a2, v);
1000
+ case "audit": {
1001
+ const r = await json("/api/v2/partner/audit", v, { ownerOnly: true });
1002
+ return out(r, () => table(r.events.map((e) => ({ when: fmt(e.ts), action: e.action, externalId: e.externalId, actor: e.actor })), ["when", "action", "externalId", "actor"]));
1003
+ }
1004
+ case "try":
1005
+ return tryCmd(a1, a2, v);
1006
+ default:
1007
+ throw usage(`Unknown partner subcommand: ${sub}.`, "Run: ghosty partner --help");
1008
+ }
1009
+ }
1010
+
723
1011
  // src/index.ts
724
1012
  var HELP = `${bold("Usage:")} ghosty <command> [options]
725
1013
 
@@ -728,14 +1016,18 @@ ${bold("Session")}
728
1016
  --device Sign in from another device with a link + code (auto over SSH/CI)
729
1017
  logout Sign out and revoke this device's session
730
1018
  whoami Show who you are signed in as
1019
+ usage Your plan and how much you've used this week and month
731
1020
 
732
1021
  ${bold("Agents")}
733
1022
  agents ls List your agents
734
1023
  agents create Create an agent --name [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
735
1024
  agents get <id> Show an agent's config [--fields name,model,prompt]
736
1025
  agents set <id> Change identity or engine [--name] [--engine] [--model] [--prompt | --prompt-file FILE] [--env K=V,\u2026]
1026
+ [--connect google-drive,\u2026] [--disconnect \u2026] give/remove access to your connectors
737
1027
  agents rm <id> Delete an agent (owner only)
738
1028
  agents restart <id> Restart an ACP agent (needsRestart) so it loads new skills
1029
+ agents demo <id> Public demo link /p/<slug>: anyone tries it without an account, "Keep it" clones it to theirs
1030
+ [--slug NAME] [--on | --off] [--turns N] [--per-ip N] [--welcome TEXT] [--chips "a|b|c"] [--sponsor on|off]
739
1031
 
740
1032
  ${bold("Configure an agent")}
741
1033
  files ls|get|put|rm <id> [path] Files in the agent's machine [--file LOCAL] [--out LOCAL]
@@ -752,6 +1044,9 @@ ${bold("Schedule turns")} ${dim("(each run lands in a conversation and notifies
752
1044
  schedule add <id> "what to do" Schedule a turn --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]
753
1045
  schedule rm <id> <schedule-id> Cancel a pending turn
754
1046
 
1047
+ ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see: ghosty partner --help)")}
1048
+ partner ls | enable | credentials | agent | tenants | audit | try
1049
+
755
1050
  ${bold("Your account")}
756
1051
  me files ls|upload|rm Files in your account [--kind image|video|audio|document|other]
757
1052
  docs [topic] Open the docs
@@ -759,10 +1054,12 @@ ${bold("Your account")}
759
1054
  ${bold("Global options")}
760
1055
  --json Machine-readable output (stdout is JSON only)
761
1056
  --token <token> Use this token (or env GHOSTY_TOKEN). A gat_\u2026 agent token reaches only that agent.
1057
+ --profile <name> Use a separate saved session (or env GHOSTY_PROFILE), e.g. one per account
762
1058
  -h, --help Show help
763
1059
  -v, --version Show version
764
1060
 
765
1061
  ${bold("Exit codes")} 0 ok \xB7 1 API error \xB7 2 usage error \xB7 3 not signed in
1062
+ ${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.")}
766
1063
  ${dim("Docs: https://www.ghosty.studio/docs/cli")}`;
767
1064
  function showHelp() {
768
1065
  if (process.stdout.isTTY && !process.env.NO_COLOR && !globals.json) process.stdout.write(BANNER + "\n\n");
@@ -782,6 +1079,8 @@ async function main() {
782
1079
  name: { type: "string" },
783
1080
  model: { type: "string" },
784
1081
  engine: { type: "string" },
1082
+ connect: { type: "string" },
1083
+ disconnect: { type: "string" },
785
1084
  env: { type: "string" },
786
1085
  prompt: { type: "string" },
787
1086
  "prompt-file": { type: "string" },
@@ -796,13 +1095,31 @@ async function main() {
796
1095
  in: { type: "string" },
797
1096
  every: { type: "string" },
798
1097
  until: { type: "string" },
799
- title: { type: "string" }
1098
+ title: { type: "string" },
1099
+ profile: { type: "string" },
1100
+ workspace: { type: "string" },
1101
+ label: { type: "string" },
1102
+ grace: { type: "string" },
1103
+ group: { type: "string" },
1104
+ config: { type: "string" },
1105
+ "turn-token": { type: "string" },
1106
+ slug: { type: "string" },
1107
+ on: { type: "boolean" },
1108
+ off: { type: "boolean" },
1109
+ turns: { type: "string" },
1110
+ "per-ip": { type: "string" },
1111
+ welcome: { type: "string" },
1112
+ chips: { type: "string" },
1113
+ sponsor: { type: "string" }
800
1114
  }
801
1115
  });
802
1116
  globals.json = Boolean(v.json);
803
- globals.token = v.token;
804
- if (v.version) return out({ version: "0.5.0" }, () => "0.5.0");
1117
+ const tokens = splitTokens(pos, v);
1118
+ globals.token = tokens.session;
1119
+ globals.profile = v.profile;
1120
+ if (v.version) return out({ version: "0.7.0" }, () => "0.7.0");
805
1121
  const [cmd, sub, a1, a2] = pos;
1122
+ if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
806
1123
  if (!cmd || v.help || cmd === "help") return showHelp();
807
1124
  switch (cmd) {
808
1125
  case "login": {
@@ -830,7 +1147,8 @@ async function main() {
830
1147
  if (sub === "set") return agentsSet(a1, v);
831
1148
  if (sub === "rm" || sub === "delete") return agentsRm(a1);
832
1149
  if (sub === "restart") return agentsRestart(a1);
833
- throw usage(`Unknown agents subcommand: ${sub}.`, "Try: ghosty agents ls | create | get | set | rm | restart");
1150
+ if (sub === "demo") return agentsDemo(a1, v);
1151
+ throw usage(`Unknown agents subcommand: ${sub}.`, "Try: ghosty agents ls | create | get | set | rm | restart | demo");
834
1152
  case "files":
835
1153
  return filesCmd(sub, a1, a2, v);
836
1154
  case "skills":
@@ -846,8 +1164,13 @@ async function main() {
846
1164
  throw usage(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, "Try: ghosty conversations ls <agent-id>");
847
1165
  case "schedule":
848
1166
  return scheduleCmd(sub, a1, a2, v);
1167
+ case "partner":
1168
+ return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn });
1169
+ case "usage":
1170
+ return meUsage();
849
1171
  case "me":
850
1172
  if (sub === "files") return meFiles(a1, a2, v);
1173
+ if (sub === "usage") return meUsage();
851
1174
  throw usage(`Unknown me subcommand: ${sub ?? "(none)"}.`, "Try: ghosty me files ls");
852
1175
  case "docs": {
853
1176
  const url = `${await baseUrl()}/docs${sub ? `/${encodeURIComponent(sub)}` : "/cli"}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.5.0",
3
+ "version": "0.7.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": {
@@ -15,7 +15,8 @@
15
15
  },
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
- "prepack": "npm run build"
18
+ "prepack": "npm run build",
19
+ "test": "esbuild src/flags.test.ts --bundle --platform=node --format=esm --outfile=/tmp/ghosty-cli-test.mjs --log-level=error && node --test /tmp/ghosty-cli-test.mjs"
19
20
  },
20
21
  "keywords": [
21
22
  "ghosty",