@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.
- package/dist/index.js +312 -13
- 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
|
-
|
|
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(
|
|
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
|
-
|
|
82
|
-
await
|
|
83
|
-
await
|
|
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(
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
826
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|