@ghostystudio/cli 0.6.0 → 0.7.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.
Files changed (2) hide show
  1. package/dist/index.js +312 -13
  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.6.0"}`,
293
+ "user-agent": `ghosty-cli/${"0.7.1"}`,
279
294
  ...opts.body !== void 0 ? { "content-type": "application/json" } : {},
280
295
  ...opts.headers
281
296
  },
@@ -439,9 +454,18 @@ async function skillsCmd(sub, id, slug, v) {
439
454
  const ex = "ghosty skills ls <agent-id> | add <agent-id> <slug> [--file SKILL.md] | rm <agent-id> <slug>";
440
455
  id = need(id, "agent id", ex);
441
456
  switch (sub) {
442
- case "ls":
443
- 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)`)}`);
444
467
  return;
468
+ }
445
469
  case "add": {
446
470
  slug = need(slug, "skill slug", ex);
447
471
  const body = v.file ? { markdown: await readFile2(v.file, "utf8") } : { fromCatalog: true };
@@ -470,6 +494,34 @@ async function mcpCmd(sub, id, v) {
470
494
  }
471
495
  throw usage(`Unknown mcp subcommand: ${sub ?? "(none)"}.`, ex);
472
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
+ ...d.lastError ? [red(`last failure ${d.lastError.at}: ${d.lastError.message ?? d.lastError.error}`)] : []
522
+ ].join("\n")
523
+ );
524
+ }
473
525
 
474
526
  // src/commands/chat.ts
475
527
  import { randomUUID } from "node:crypto";
@@ -738,6 +790,225 @@ ${dim(`conversation ${p.sessionId}`)}`
738
790
  throw usage(`Unknown schedule subcommand: ${sub ?? "(none)"}.`, "Try: ghosty schedule ls | add | rm");
739
791
  }
740
792
 
793
+ // src/commands/partner.ts
794
+ import { createHmac } from "node:crypto";
795
+ import { readFile as readFile4 } from "node:fs/promises";
796
+ var PARTNER_HELP = `${bold("Usage:")} ghosty partner <command> [options]
797
+
798
+ ${bold("Partner space")} ${dim("(you, signed in as the space owner)")}
799
+ partner ls Your partner spaces
800
+ partner enable <workspace> Turn one of your spaces into a partner space
801
+ partner credentials ls Server credentials (no secrets)
802
+ partner credentials create [--label L] Issue one \u2014 the secret is shown ONCE
803
+ partner credentials rotate <keyId> [--grace MIN] New secret, same keyId (old one valid MIN, default 60)
804
+ partner credentials revoke <keyId> Revoke it (idempotent)
805
+ partner audit Who did what, when
806
+
807
+ ${bold("Shared agent (solo-MCP)")}
808
+ partner agent get The agent template and its version
809
+ partner agent apply --file agent.json Create or update it (idempotent)
810
+
811
+ ${bold("Tenants")} ${dim("(your customers, by externalId \u2014 config only, no box)")}
812
+ partner tenants ls
813
+ partner tenants get <externalId>
814
+ partner tenants set <externalId> --config '{"name":"\u2026","timezone":"America/Mexico_City"}'
815
+ partner tenants rm <externalId> Deletes its config AND its memory
816
+
817
+ ${bold("Try it")}
818
+ partner try <externalId> "message" One turn [--group END_USER_ID] [--turn-token SHORT_LIVED_TOKEN]
819
+ (the turn token goes to YOUR MCP as \${turn.token}; --token also works here)
820
+
821
+ ${bold("Options")}
822
+ --workspace SLUG Which partner space (only if you own more than one)
823
+ --json Machine-readable output
824
+
825
+ ${bold("Server credential mode")} GHOSTY_PARTNER_KEY=gpk_\u2026 GHOSTY_PARTNER_SECRET=gps_\u2026 ghosty partner agent get
826
+ ${dim("Docs: https://www.ghosty.studio/docs/cli")}`;
827
+ var enc4 = encodeURIComponent;
828
+ function partnerCreds() {
829
+ const key = process.env.GHOSTY_PARTNER_KEY;
830
+ const secret = process.env.GHOSTY_PARTNER_SECRET;
831
+ return key && secret ? { key, secret } : null;
832
+ }
833
+ async function call(path, v, init = {}) {
834
+ const method = init.method ?? (init.body !== void 0 ? "POST" : "GET");
835
+ const raw = init.body !== void 0 ? JSON.stringify(init.body) : "";
836
+ const creds = init.ownerOnly ? null : partnerCreds();
837
+ if (creds) {
838
+ const ts = String(Math.floor(Date.now() / 1e3));
839
+ const sig = createHmac("sha256", creds.secret).update(`${ts}.${creds.key}.${raw}`).digest("hex");
840
+ const res = await fetch(`${await baseUrl()}${path}`, {
841
+ method,
842
+ headers: {
843
+ "x-ghosty-key": creds.key,
844
+ "x-ghosty-ts": ts,
845
+ "x-ghosty-sig": sig,
846
+ "content-type": "application/json",
847
+ "user-agent": `ghosty-cli/${"0.7.1"}`
848
+ },
849
+ body: method === "GET" ? void 0 : raw
850
+ });
851
+ if (!res.ok) throw await toError(res);
852
+ return res;
853
+ }
854
+ return request(path, {
855
+ method,
856
+ ...init.body !== void 0 ? { body: init.body } : {},
857
+ headers: v.workspace ? { "x-ghosty-workspace": v.workspace } : {}
858
+ });
859
+ }
860
+ async function json(path, v, init = {}) {
861
+ const res = await call(path, v, init);
862
+ const t = await res.text();
863
+ return t ? JSON.parse(t) : void 0;
864
+ }
865
+ var fmt = (d) => d ? d.replace("T", " ").slice(0, 16) : "\u2014";
866
+ function showSecret(r) {
867
+ out(
868
+ r,
869
+ () => [
870
+ `${lavender("\u2713")} Save these now \u2014 the secret is not shown again:`,
871
+ ` GHOSTY_PARTNER_KEY=${r.keyId}`,
872
+ ` GHOSTY_PARTNER_SECRET=${r.secret}`
873
+ ].join("\n")
874
+ );
875
+ }
876
+ async function credentialsCmd(sub, id, v) {
877
+ const owner = { ownerOnly: true };
878
+ if (!sub || sub === "ls" || sub === "list") {
879
+ const r = await json("/api/v2/partner/credentials", v, owner);
880
+ return out(
881
+ r,
882
+ () => table(
883
+ r.credentials.map((c) => ({ keyId: c.keyId, label: c.label, status: c.status, lastUsed: fmt(c.lastUsedAt), created: fmt(c.createdAt) })),
884
+ ["keyId", "label", "status", "lastUsed", "created"]
885
+ )
886
+ );
887
+ }
888
+ if (sub === "create" || sub === "new") {
889
+ return showSecret(await json("/api/v2/partner/credentials", v, { ...owner, method: "POST", body: { label: v.label } }));
890
+ }
891
+ if (!id) throw usage("Missing keyId.", `Example: ghosty partner credentials ${sub} gpk_\u2026`);
892
+ if (sub === "rotate") {
893
+ const grace = v.grace !== void 0 ? Number(v.grace) : void 0;
894
+ if (grace !== void 0 && (!Number.isFinite(grace) || grace < 0)) throw usage("--grace must be minutes \u2265 0.");
895
+ return showSecret(await json(`/api/v2/partner/credentials/${enc4(id)}/rotate`, v, { ...owner, method: "POST", body: { graceMinutes: grace } }));
896
+ }
897
+ if (sub === "revoke" || sub === "rm") {
898
+ const r = await json(`/api/v2/partner/credentials/${enc4(id)}`, v, { ...owner, method: "DELETE" });
899
+ return out(r, () => `${lavender("\u2713")} Revoked ${r.keyId}.`);
900
+ }
901
+ throw usage(`Unknown credentials subcommand: ${sub}.`, "Try: ghosty partner credentials ls | create | rotate | revoke");
902
+ }
903
+ async function agentCmd(sub, v) {
904
+ if (!sub || sub === "get") {
905
+ const r = await json("/api/v2/partner/agent", v);
906
+ return out(r, () => {
907
+ const a = r.agent;
908
+ return [
909
+ `${bold(a.name)} ${dim(`v${a.version} \xB7 solo-MCP \xB7 ${a.engine}/${a.model}`)}`,
910
+ `MCP: ${a.mcp.map((m) => `${m.name} (${m.url})`).join(", ") || "none"}`,
911
+ dim(a.prompt.slice(0, 300))
912
+ ].join("\n");
913
+ });
914
+ }
915
+ if (sub === "apply" || sub === "set") {
916
+ if (!v.file) throw usage("Missing --file.", "Example: ghosty partner agent apply --file agent.json");
917
+ const body = JSON.parse(await readFile4(v.file, "utf8"));
918
+ const r = await json("/api/v2/partner/agent", v, { method: "PUT", body });
919
+ return out(r, () => `${lavender("\u2713")} ${r.created ? "Created" : r.changed ? "Updated" : "No changes"} \xB7 v${r.agent.version}`);
920
+ }
921
+ throw usage(`Unknown agent subcommand: ${sub}.`, "Try: ghosty partner agent get | apply --file agent.json");
922
+ }
923
+ async function tenantsCmd(sub, id, v) {
924
+ if (!sub || sub === "ls" || sub === "list") {
925
+ const r = await json("/api/v2/partner/tenants", v);
926
+ return out(r, () => table(r.tenants.map((t) => ({ externalId: t.externalId, config: JSON.stringify(t.config ?? {}), updated: fmt(t.updatedAt) })), ["externalId", "config", "updated"]));
927
+ }
928
+ if (!id) throw usage("Missing externalId.", `Example: ghosty partner tenants ${sub} org_123`);
929
+ const path = `/api/v2/partner/tenants/${enc4(id)}`;
930
+ if (sub === "get") return out(await json(path, v));
931
+ if (sub === "set" || sub === "apply") {
932
+ if (!v.config) throw usage("Missing --config.", `Example: --config '{"name":"Spa","timezone":"America/Mexico_City"}'`);
933
+ let config;
934
+ try {
935
+ config = JSON.parse(v.config);
936
+ } catch {
937
+ throw usage("--config must be JSON.");
938
+ }
939
+ const r = await json(path, v, { method: "PUT", body: { config } });
940
+ return out(r, () => `${lavender("\u2713")} ${r.created ? "Created" : r.changed ? "Updated" : "No changes"} ${id}`);
941
+ }
942
+ if (sub === "rm" || sub === "delete") {
943
+ const r = await json(path, v, { method: "DELETE" });
944
+ return out(r, () => r.deleted ? `${lavender("\u2713")} Deleted ${id} (${r.conversations} conversations)` : dim(`${id} did not exist.`));
945
+ }
946
+ throw usage(`Unknown tenants subcommand: ${sub}.`, "Try: ghosty partner tenants ls | get | set | rm");
947
+ }
948
+ async function tryCmd(externalId, text, v) {
949
+ if (!externalId || !text) throw usage("Missing externalId or message.", 'Example: ghosty partner try org_123 "\xBFqu\xE9 citas tengo hoy?"');
950
+ const res = await call("/api/v2/partner/turns", v, {
951
+ method: "POST",
952
+ body: { tenant: { externalId }, groupId: v.group ?? "cli", text, ...v.token ? { token: v.token } : {} }
953
+ });
954
+ const dec = new TextDecoder();
955
+ let buf = "";
956
+ let full = "";
957
+ for await (const part of res.body) {
958
+ buf += dec.decode(part, { stream: true });
959
+ let i;
960
+ while ((i = buf.indexOf("\n\n")) >= 0) {
961
+ const block = buf.slice(0, i);
962
+ buf = buf.slice(i + 2);
963
+ const line = block.split("\n").find((l) => l.startsWith("data:"));
964
+ if (!line) continue;
965
+ const ev = JSON.parse(line.slice(5));
966
+ if (globals.json) {
967
+ process.stdout.write(JSON.stringify(ev) + "\n");
968
+ continue;
969
+ }
970
+ if (ev.type === "chunk" && ev.value) {
971
+ full += ev.value;
972
+ process.stdout.write(ev.value);
973
+ } else if (ev.type === "tool" && ev.phase === "start") info(dim(` \u21B3 ${ev.name}`));
974
+ else if (ev.type === "error") info(`Error: ${ev.message}`);
975
+ }
976
+ }
977
+ if (!globals.json && full) process.stdout.write("\n");
978
+ }
979
+ async function partnerCmd(sub, a1, a2, v) {
980
+ switch (sub) {
981
+ case void 0:
982
+ case "help":
983
+ return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
984
+ case "ls":
985
+ case "list": {
986
+ const r = await json("/api/v2/partner", v, { ownerOnly: true });
987
+ 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"]));
988
+ }
989
+ case "enable": {
990
+ if (!a1) throw usage("Missing workspace.", "Example: ghosty partner enable denik");
991
+ const r = await json("/api/v2/partner", v, { ownerOnly: true, method: "POST", body: { workspace: a1 } });
992
+ return out(r, () => `${lavender("\u2713")} ${r.partner.workspace} is a partner space.`);
993
+ }
994
+ case "credentials":
995
+ case "creds":
996
+ return credentialsCmd(a1, a2, v);
997
+ case "agent":
998
+ return agentCmd(a1, v);
999
+ case "tenants":
1000
+ return tenantsCmd(a1, a2, v);
1001
+ case "audit": {
1002
+ const r = await json("/api/v2/partner/audit", v, { ownerOnly: true });
1003
+ 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"]));
1004
+ }
1005
+ case "try":
1006
+ return tryCmd(a1, a2, v);
1007
+ default:
1008
+ throw usage(`Unknown partner subcommand: ${sub}.`, "Run: ghosty partner --help");
1009
+ }
1010
+ }
1011
+
741
1012
  // src/index.ts
742
1013
  var HELP = `${bold("Usage:")} ghosty <command> [options]
743
1014
 
@@ -756,6 +1027,8 @@ ${bold("Agents")}
756
1027
  [--connect google-drive,\u2026] [--disconnect \u2026] give/remove access to your connectors
757
1028
  agents rm <id> Delete an agent (owner only)
758
1029
  agents restart <id> Restart an ACP agent (needsRestart) so it loads new skills
1030
+ agents demo <id> Public demo link /p/<slug>: anyone tries it without an account, "Keep it" clones it to theirs
1031
+ [--slug NAME] [--on | --off] [--turns N] [--per-ip N] [--welcome TEXT] [--chips "a|b|c"] [--sponsor on|off]
759
1032
 
760
1033
  ${bold("Configure an agent")}
761
1034
  files ls|get|put|rm <id> [path] Files in the agent's machine [--file LOCAL] [--out LOCAL]
@@ -772,6 +1045,9 @@ ${bold("Schedule turns")} ${dim("(each run lands in a conversation and notifies
772
1045
  schedule add <id> "what to do" Schedule a turn --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]
773
1046
  schedule rm <id> <schedule-id> Cancel a pending turn
774
1047
 
1048
+ ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see: ghosty partner --help)")}
1049
+ partner ls | enable | credentials | agent | tenants | audit | try
1050
+
775
1051
  ${bold("Your account")}
776
1052
  me files ls|upload|rm Files in your account [--kind image|video|audio|document|other]
777
1053
  docs [topic] Open the docs
@@ -779,10 +1055,12 @@ ${bold("Your account")}
779
1055
  ${bold("Global options")}
780
1056
  --json Machine-readable output (stdout is JSON only)
781
1057
  --token <token> Use this token (or env GHOSTY_TOKEN). A gat_\u2026 agent token reaches only that agent.
1058
+ --profile <name> Use a separate saved session (or env GHOSTY_PROFILE), e.g. one per account
782
1059
  -h, --help Show help
783
1060
  -v, --version Show version
784
1061
 
785
1062
  ${bold("Exit codes")} 0 ok \xB7 1 API error \xB7 2 usage error \xB7 3 not signed in
1063
+ ${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.")}
786
1064
  ${dim("Docs: https://www.ghosty.studio/docs/cli")}`;
787
1065
  function showHelp() {
788
1066
  if (process.stdout.isTTY && !process.env.NO_COLOR && !globals.json) process.stdout.write(BANNER + "\n\n");
@@ -818,13 +1096,31 @@ async function main() {
818
1096
  in: { type: "string" },
819
1097
  every: { type: "string" },
820
1098
  until: { type: "string" },
821
- title: { type: "string" }
1099
+ title: { type: "string" },
1100
+ profile: { type: "string" },
1101
+ workspace: { type: "string" },
1102
+ label: { type: "string" },
1103
+ grace: { type: "string" },
1104
+ group: { type: "string" },
1105
+ config: { type: "string" },
1106
+ "turn-token": { type: "string" },
1107
+ slug: { type: "string" },
1108
+ on: { type: "boolean" },
1109
+ off: { type: "boolean" },
1110
+ turns: { type: "string" },
1111
+ "per-ip": { type: "string" },
1112
+ welcome: { type: "string" },
1113
+ chips: { type: "string" },
1114
+ sponsor: { type: "string" }
822
1115
  }
823
1116
  });
824
1117
  globals.json = Boolean(v.json);
825
- globals.token = v.token;
826
- if (v.version) return out({ version: "0.6.0" }, () => "0.6.0");
1118
+ const tokens = splitTokens(pos, v);
1119
+ globals.token = tokens.session;
1120
+ globals.profile = v.profile;
1121
+ if (v.version) return out({ version: "0.7.1" }, () => "0.7.1");
827
1122
  const [cmd, sub, a1, a2] = pos;
1123
+ if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
828
1124
  if (!cmd || v.help || cmd === "help") return showHelp();
829
1125
  switch (cmd) {
830
1126
  case "login": {
@@ -852,7 +1148,8 @@ async function main() {
852
1148
  if (sub === "set") return agentsSet(a1, v);
853
1149
  if (sub === "rm" || sub === "delete") return agentsRm(a1);
854
1150
  if (sub === "restart") return agentsRestart(a1);
855
- throw usage(`Unknown agents subcommand: ${sub}.`, "Try: ghosty agents ls | create | get | set | rm | restart");
1151
+ if (sub === "demo") return agentsDemo(a1, v);
1152
+ throw usage(`Unknown agents subcommand: ${sub}.`, "Try: ghosty agents ls | create | get | set | rm | restart | demo");
856
1153
  case "files":
857
1154
  return filesCmd(sub, a1, a2, v);
858
1155
  case "skills":
@@ -868,6 +1165,8 @@ async function main() {
868
1165
  throw usage(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, "Try: ghosty conversations ls <agent-id>");
869
1166
  case "schedule":
870
1167
  return scheduleCmd(sub, a1, a2, v);
1168
+ case "partner":
1169
+ return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn });
871
1170
  case "usage":
872
1171
  return meUsage();
873
1172
  case "me":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghostystudio/cli",
3
- "version": "0.6.0",
3
+ "version": "0.7.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": {
@@ -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",