@ghostystudio/cli 0.10.1 → 0.11.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.
- package/README.md +6 -1
- package/dist/index.js +61 -16
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ ghosty agents set <id> --voice elevenlabs:ID --voice-replies auto --auto-stage o
|
|
|
31
31
|
ghosty files ls|get|put|rm <id> [path] [--file LOCAL] [--out LOCAL]
|
|
32
32
|
ghosty skills ls|get|add|rm <id> [slug] [--file SKILL.md | --dir FOLDER] [--out DIR] # get without --out only lists
|
|
33
33
|
ghosty skills templates <id> # pdf-doc templates: its own and Ghosty's, and which folder wins
|
|
34
|
-
ghosty dbs ls|tables|query|import|grant|revoke <id> [db] [--write] [--arg V]… [--external-write t1,t2] [--from easybits:ID --tables a,b --append --dry-run]
|
|
34
|
+
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]
|
|
35
35
|
ghosty turns ls <id> [--errors | --status running|done|stopped|error] [--since 1h] [--limit N]
|
|
36
36
|
ghosty turns show <id> [turn-id|last] # where a turn's time went: each tool with its seconds, output tokens
|
|
37
37
|
ghosty channels whatsapp ls|enable|disable <id> [number] [--dry-run]
|
|
@@ -43,6 +43,7 @@ ghosty try <id> "message" --waba --session PHONE [--media FILE] [--history FILE]
|
|
|
43
43
|
ghosty try <id> --replay FILE --out DIR [--max 15] [--concurrency 8] [--resume]
|
|
44
44
|
ghosty chat <id> ["message"] [--conversation ID]
|
|
45
45
|
ghosty conversations ls <id>
|
|
46
|
+
ghosty conversations show <id> <conversation-id> [--limit 20] # what was said
|
|
46
47
|
|
|
47
48
|
ghosty schedule add <id> "what to do" --in 30m | --at ISO [--every 12h --until ISO] [--conversation ID] [--title T]
|
|
48
49
|
ghosty schedule ls <id>
|
|
@@ -74,6 +75,10 @@ Exit codes: `0` ok · `1` API error · `2` usage error · `3` not signed in.
|
|
|
74
75
|
- `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.
|
|
75
76
|
- `agents doctor` speaks English like the rest of the CLI; `channels whatsapp ls` returns a full URL.
|
|
76
77
|
|
|
78
|
+
### Changes in 0.11.0
|
|
79
|
+
|
|
80
|
+
- New: `conversations show` (what was said in a conversation; `--limit 0` for all).
|
|
81
|
+
|
|
77
82
|
### Changes in 0.10.0
|
|
78
83
|
|
|
79
84
|
- Tells you when a new version is out (checks npm once a day, on stderr; `GHOSTY_NO_UPDATE_CHECK=1` or `CI` turns it off). An unknown option on an old version says to update.
|
package/dist/index.js
CHANGED
|
@@ -282,7 +282,7 @@ async function postLong(path, body, timeoutMs = 9e5) {
|
|
|
282
282
|
const req = httpsRequest(`${base}${path}`, {
|
|
283
283
|
method: "POST",
|
|
284
284
|
timeout: timeoutMs,
|
|
285
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
285
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.11.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
286
286
|
}, (res) => {
|
|
287
287
|
let buf = "";
|
|
288
288
|
res.on("data", (c) => buf += c);
|
|
@@ -324,7 +324,7 @@ async function request(path, opts = {}, retried = false) {
|
|
|
324
324
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
325
325
|
headers: {
|
|
326
326
|
authorization: `Bearer ${token}`,
|
|
327
|
-
"user-agent": `ghosty-cli/${"0.
|
|
327
|
+
"user-agent": `ghosty-cli/${"0.11.0"}`,
|
|
328
328
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
329
329
|
...opts.headers
|
|
330
330
|
},
|
|
@@ -851,6 +851,17 @@ async function conversationsLs(agent) {
|
|
|
851
851
|
const rows = r.conversaciones.map((c) => ({ id: c.id, title: c.titulo, updated: c.actualizada }));
|
|
852
852
|
out(rows, () => table(rows, ["id", "title", "updated"]));
|
|
853
853
|
}
|
|
854
|
+
async function conversationsShow(agent, sid, v) {
|
|
855
|
+
if (!agent || !sid) throw usage("Missing agent id or conversation id.", "Example: ghosty conversations show <agent-id> <conversation-id> [--limit 20]");
|
|
856
|
+
const tail = v.limit ?? "20";
|
|
857
|
+
const r = await api(convPath(agent, `/${enc3(sid)}?tail=${enc3(tail)}`));
|
|
858
|
+
out(r, () => {
|
|
859
|
+
const lines = r.messages.map((m) => `${bold(m.role === "user" ? "User" : m.role === "assistant" ? "Agent" : m.role)} ${m.text.trim()}`);
|
|
860
|
+
const head = r.saltados ? [dim(`(${r.saltados} earlier messages not shown \xB7 --limit 0 for all)`)] : [];
|
|
861
|
+
const tailNote = r.enCurso ? [dim("(a turn is running: its answer is not here yet)")] : [];
|
|
862
|
+
return [...head, ...lines, ...tailNote].join("\n\n") || dim("(empty)");
|
|
863
|
+
});
|
|
864
|
+
}
|
|
854
865
|
async function turn(agent, sid, text, first) {
|
|
855
866
|
const turnId = randomUUID();
|
|
856
867
|
const ctrl = new AbortController();
|
|
@@ -1125,7 +1136,7 @@ async function call(path, v, init = {}) {
|
|
|
1125
1136
|
"x-ghosty-ts": ts,
|
|
1126
1137
|
"x-ghosty-sig": sig,
|
|
1127
1138
|
"content-type": "application/json",
|
|
1128
|
-
"user-agent": `ghosty-cli/${"0.
|
|
1139
|
+
"user-agent": `ghosty-cli/${"0.11.0"}`
|
|
1129
1140
|
},
|
|
1130
1141
|
body: method === "GET" ? void 0 : raw
|
|
1131
1142
|
});
|
|
@@ -1290,6 +1301,24 @@ async function partnerCmd(sub, a1, a2, v) {
|
|
|
1290
1301
|
}
|
|
1291
1302
|
}
|
|
1292
1303
|
|
|
1304
|
+
// src/rows.ts
|
|
1305
|
+
var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1306
|
+
function parseRowScope(raw) {
|
|
1307
|
+
const s = raw.trim();
|
|
1308
|
+
if (!s) return null;
|
|
1309
|
+
const out2 = {};
|
|
1310
|
+
for (const part of s.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
1311
|
+
const [table2, cols = ""] = part.split(":").map((x) => x.trim());
|
|
1312
|
+
const list = cols.split("+").map((x) => x.trim()).filter(Boolean);
|
|
1313
|
+
if (!IDENT.test(table2 ?? "") || !list.length || !list.every((c) => IDENT.test(c))) {
|
|
1314
|
+
throw new Error(`\xAB${part}\xBB no es tabla:columna(+columna); ej. clientes:telefono+tel_secundario`);
|
|
1315
|
+
}
|
|
1316
|
+
out2[table2] = list;
|
|
1317
|
+
}
|
|
1318
|
+
return out2;
|
|
1319
|
+
}
|
|
1320
|
+
var describeRowScope = (scope) => Object.entries(scope ?? {}).map(([t, c]) => `${t} (${c.join(", ")})`).join("; ");
|
|
1321
|
+
|
|
1293
1322
|
// src/commands/data.ts
|
|
1294
1323
|
var enc6 = encodeURIComponent;
|
|
1295
1324
|
var agentPath2 = (id, rest = "") => `/api/v2/agents/${enc6(id)}${rest}`;
|
|
@@ -1327,16 +1356,17 @@ async function dbsCmd(sub, id, db, rest, v) {
|
|
|
1327
1356
|
case "ls":
|
|
1328
1357
|
case "list": {
|
|
1329
1358
|
const r = await api(agentPath2(id, "/dbs"));
|
|
1330
|
-
const rows = r.dbs.map((d) => ({ name: d.name, access: d.granted ? "granted" : "\u2014", "writes from customers": d.granted ? d.externalWriteTables.join(", ") || "(none)" : "", id: d.id }));
|
|
1331
|
-
return out(r.dbs, () => rows.length ? table(rows, ["name", "access", "writes from customers", "id"]) : dim("This account has no databases yet: create one at /app/dbs."));
|
|
1359
|
+
const rows = r.dbs.map((d) => ({ name: d.name, access: d.granted ? "granted" : "\u2014", "writes from customers": d.granted ? d.externalWriteTables.join(", ") || "(none)" : "", "own rows": d.granted ? Object.keys(d.externalRowScope ?? {}).join(", ") : "", id: d.id }));
|
|
1360
|
+
return out(r.dbs, () => rows.length ? table(rows, ["name", "access", "writes from customers", "own rows", "id"]) : dim("This account has no databases yet: create one at /app/dbs."));
|
|
1332
1361
|
}
|
|
1333
1362
|
case "tables": {
|
|
1334
1363
|
db = need2(db, "database (id or name)", "ghosty dbs tables <agent-id> <db>");
|
|
1335
1364
|
const r = await api(agentPath2(id, `/dbs/${enc6(db)}`));
|
|
1336
1365
|
const ext = new Set(r.externalWriteTables);
|
|
1337
|
-
const
|
|
1366
|
+
const own = r.externalRowScope ?? {};
|
|
1367
|
+
const rows = r.tables.map((t) => ({ table: t.name, rows: t.rows, "customer writes": ext.has(t.name) ? "yes" : "", "customer sees": own[t.name] ? `own rows (${own[t.name].join(", ")})` : "", columns: t.columns.map((c) => `${c.name}${c.pk ? "*" : ""}`).join(", ") }));
|
|
1338
1368
|
return out(r, () => `${bold(r.name)} ${r.granted ? lavender("granted") : dim("not granted")}
|
|
1339
|
-
${table(rows, ["table", "rows", "customer writes", "columns"])}`);
|
|
1369
|
+
${table(rows, ["table", "rows", "customer writes", "customer sees", "columns"])}`);
|
|
1340
1370
|
}
|
|
1341
1371
|
case "query":
|
|
1342
1372
|
case "sql": {
|
|
@@ -1354,10 +1384,20 @@ ${dim(`${rows.length} row(s) \xB7 ${Math.round(r.ms)} ms`)}`;
|
|
|
1354
1384
|
});
|
|
1355
1385
|
}
|
|
1356
1386
|
case "grant": {
|
|
1357
|
-
db = need2(db, "database (id or name)", "ghosty dbs grant <agent-id> <db> [--external-write t1,t2]");
|
|
1358
|
-
|
|
1387
|
+
db = need2(db, "database (id or name)", "ghosty dbs grant <agent-id> <db> [--external-write t1,t2] [--external-rows t:col+col]");
|
|
1388
|
+
let rowScope;
|
|
1389
|
+
try {
|
|
1390
|
+
rowScope = v["external-rows"] !== void 0 ? parseRowScope(v["external-rows"]) : void 0;
|
|
1391
|
+
} catch (e) {
|
|
1392
|
+
throw usage(e.message, `ghosty dbs grant ${id} ${db} --external-rows clientes:telefono+tel_secundario`);
|
|
1393
|
+
}
|
|
1394
|
+
const body = {
|
|
1395
|
+
...v["external-write"] !== void 0 ? { externalWriteTables: csv(v["external-write"]) } : {},
|
|
1396
|
+
...rowScope !== void 0 ? { externalRowScope: rowScope } : {}
|
|
1397
|
+
};
|
|
1359
1398
|
const r = await api(agentPath2(id, `/dbs/${enc6(db)}`), { method: "PUT", body });
|
|
1360
|
-
|
|
1399
|
+
const own = describeRowScope(r.externalRowScope);
|
|
1400
|
+
return out(r, () => `${lavender("\u2713")} ${r.name} granted. ${dim(`From customer channels it writes to: ${r.externalWriteTables.join(", ") || "nothing (read only)"}.${own ? ` Each customer sees only their own rows of: ${own}.` : ""}`)}`);
|
|
1361
1401
|
}
|
|
1362
1402
|
case "import": {
|
|
1363
1403
|
db = need2(db, "database (id or name)", "ghosty dbs import <agent-id> <db> --from easybits:<db-id>");
|
|
@@ -1675,13 +1715,14 @@ var HELPS = {
|
|
|
1675
1715
|
"ghosty dbs tables <agent-id> <db>",
|
|
1676
1716
|
'ghosty dbs query <agent-id> <db> "SQL" [--arg V]\u2026 [--write]',
|
|
1677
1717
|
"ghosty dbs import <agent-id> <db> --from easybits:<db-id> [--tables a,b] [--append] [--dry-run] [--yes]",
|
|
1678
|
-
"ghosty dbs grant <agent-id> <db> [--external-write t1,t2]",
|
|
1718
|
+
"ghosty dbs grant <agent-id> <db> [--external-write t1,t2] [--external-rows t:col+col,\u2026]",
|
|
1679
1719
|
"ghosty dbs revoke <agent-id> <db>"
|
|
1680
1720
|
],
|
|
1681
1721
|
flags: [
|
|
1682
1722
|
["--arg V", "query: value for a ? placeholder (repeatable)"],
|
|
1683
1723
|
["--write", "query: allow INSERT/UPDATE/DELETE/DDL (default: read only)"],
|
|
1684
1724
|
["--external-write t1,t2", 'grant: tables it may write from WhatsApp/Messenger ("" = none)'],
|
|
1725
|
+
["--external-rows t:col+col", 'grant: from WhatsApp/Messenger each customer sees only THEIR rows of t, matched by phone column(s) ("" = no filter)'],
|
|
1685
1726
|
["--from easybits:ID", "import: source database in EasyBits (key from $EASYBITS_API_KEY)"],
|
|
1686
1727
|
["--tables a,b", "import: only these tables (default: all)"],
|
|
1687
1728
|
["--append", "import: add rows instead of replacing them (replace is the idempotent default)"],
|
|
@@ -1692,6 +1733,7 @@ var HELPS = {
|
|
|
1692
1733
|
"ghosty dbs tables <id> totequim",
|
|
1693
1734
|
'ghosty dbs query <id> totequim "SELECT count(*) AS n FROM productos"',
|
|
1694
1735
|
"ghosty dbs grant <id> totequim --external-write cotizaciones,folio",
|
|
1736
|
+
"ghosty dbs grant <id> totequim --external-rows clientes:telefono+tel_secundario,cotizaciones:cliente_tel",
|
|
1695
1737
|
"ghosty dbs import <id> totequim --from easybits:6a10c84c\u2026 --dry-run # reads $EASYBITS_API_KEY"
|
|
1696
1738
|
],
|
|
1697
1739
|
notes: ["<db> is the database id or its name. From owner channels the agent reads and writes everything granted."],
|
|
@@ -1755,7 +1797,7 @@ var HELPS = {
|
|
|
1755
1797
|
},
|
|
1756
1798
|
chat: {
|
|
1757
1799
|
summary: "Stream a conversation; with no message it is interactive.",
|
|
1758
|
-
usage: ['ghosty chat <agent-id> ["message"] [--conversation ID]', "ghosty conversations ls <agent-id>"],
|
|
1800
|
+
usage: ['ghosty chat <agent-id> ["message"] [--conversation ID]', "ghosty conversations ls <agent-id>", "ghosty conversations show <agent-id> <conversation-id> [--limit 20]"],
|
|
1759
1801
|
examples: ['ghosty chat <id> "hola"', "ghosty chat <id>"],
|
|
1760
1802
|
learn: `${REF}#conversaciones`
|
|
1761
1803
|
},
|
|
@@ -1899,7 +1941,7 @@ ${bold("Configure an agent")}
|
|
|
1899
1941
|
files ls|get|put|rm <id> [path] Files in the agent's machine [--file LOCAL] [--out LOCAL]
|
|
1900
1942
|
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)
|
|
1901
1943
|
skills templates <id> pdf-doc templates it sees: its own and Ghosty's, and which folder wins
|
|
1902
|
-
dbs ls|tables|query|import|grant|revoke <id> [db] Databases it uses [--write] [--external-write t1,t2] [--from easybits:ID --dry-run]
|
|
1944
|
+
dbs ls|tables|query|import|grant|revoke <id> [db] Databases it uses [--write] [--external-write t1,t2] [--external-rows t:col] [--from easybits:ID --dry-run]
|
|
1903
1945
|
mcp get|set <id> MCP servers [--file servers.json]
|
|
1904
1946
|
channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
|
|
1905
1947
|
board archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
@@ -1910,6 +1952,7 @@ ${bold("Talk to an agent")}
|
|
|
1910
1952
|
--replay FILE --out DIR Replay real conversations; report written locally [--max 15] [--concurrency 8] [--resume]
|
|
1911
1953
|
chat <id> ["message"] Stream a conversation (no message = interactive) [--conversation ID]
|
|
1912
1954
|
conversations ls <id> List conversations
|
|
1955
|
+
conversations show <id> <conversation> What was said [--limit 20] (0 = all)
|
|
1913
1956
|
turns ls <id> Recent turns and why they failed [--errors] [--since 1h] [--limit N]
|
|
1914
1957
|
turns show <id> [turn] Where a turn's time went: each tool with its seconds, output tokens (default: last)
|
|
1915
1958
|
|
|
@@ -2008,6 +2051,7 @@ async function main() {
|
|
|
2008
2051
|
arg: { type: "string", multiple: true },
|
|
2009
2052
|
write: { type: "boolean" },
|
|
2010
2053
|
"external-write": { type: "string" },
|
|
2054
|
+
"external-rows": { type: "string" },
|
|
2011
2055
|
"no-deliver": { type: "boolean" },
|
|
2012
2056
|
deliver: { type: "boolean" },
|
|
2013
2057
|
errors: { type: "boolean" },
|
|
@@ -2035,7 +2079,7 @@ async function main() {
|
|
|
2035
2079
|
const tokens = splitTokens(pos, v);
|
|
2036
2080
|
globals.token = tokens.session;
|
|
2037
2081
|
globals.profile = v.profile;
|
|
2038
|
-
if (v.version) return out({ version: "0.
|
|
2082
|
+
if (v.version) return out({ version: "0.11.0" }, () => "0.11.0");
|
|
2039
2083
|
const [cmd, sub, a1, a2] = pos;
|
|
2040
2084
|
if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
|
|
2041
2085
|
const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
|
|
@@ -2097,7 +2141,8 @@ async function main() {
|
|
|
2097
2141
|
return chat(sub, a1, v);
|
|
2098
2142
|
case "conversations":
|
|
2099
2143
|
if (sub === "ls" || sub === "list") return conversationsLs(a1);
|
|
2100
|
-
|
|
2144
|
+
if (sub === "show") return conversationsShow(a1, a2, v);
|
|
2145
|
+
throw usage(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, "Try: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>");
|
|
2101
2146
|
case "schedule":
|
|
2102
2147
|
return scheduleCmd(sub, a1, a2, v);
|
|
2103
2148
|
case "partner":
|
|
@@ -2118,7 +2163,7 @@ async function main() {
|
|
|
2118
2163
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
2119
2164
|
}
|
|
2120
2165
|
}
|
|
2121
|
-
var notice = updateNotice("0.
|
|
2166
|
+
var notice = updateNotice("0.11.0").catch(() => null);
|
|
2122
2167
|
main().then(
|
|
2123
2168
|
async () => {
|
|
2124
2169
|
const n = await notice;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghostystudio/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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; 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; 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",
|