@ghostystudio/cli 0.10.1 → 0.10.2
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 +1 -1
- package/dist/index.js +46 -14
- 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]
|
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.10.
|
|
285
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.10.2"}`, "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.10.
|
|
327
|
+
"user-agent": `ghosty-cli/${"0.10.2"}`,
|
|
328
328
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
329
329
|
...opts.headers
|
|
330
330
|
},
|
|
@@ -1125,7 +1125,7 @@ async function call(path, v, init = {}) {
|
|
|
1125
1125
|
"x-ghosty-ts": ts,
|
|
1126
1126
|
"x-ghosty-sig": sig,
|
|
1127
1127
|
"content-type": "application/json",
|
|
1128
|
-
"user-agent": `ghosty-cli/${"0.10.
|
|
1128
|
+
"user-agent": `ghosty-cli/${"0.10.2"}`
|
|
1129
1129
|
},
|
|
1130
1130
|
body: method === "GET" ? void 0 : raw
|
|
1131
1131
|
});
|
|
@@ -1290,6 +1290,24 @@ async function partnerCmd(sub, a1, a2, v) {
|
|
|
1290
1290
|
}
|
|
1291
1291
|
}
|
|
1292
1292
|
|
|
1293
|
+
// src/rows.ts
|
|
1294
|
+
var IDENT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1295
|
+
function parseRowScope(raw) {
|
|
1296
|
+
const s = raw.trim();
|
|
1297
|
+
if (!s) return null;
|
|
1298
|
+
const out2 = {};
|
|
1299
|
+
for (const part of s.split(",").map((x) => x.trim()).filter(Boolean)) {
|
|
1300
|
+
const [table2, cols = ""] = part.split(":").map((x) => x.trim());
|
|
1301
|
+
const list = cols.split("+").map((x) => x.trim()).filter(Boolean);
|
|
1302
|
+
if (!IDENT.test(table2 ?? "") || !list.length || !list.every((c) => IDENT.test(c))) {
|
|
1303
|
+
throw new Error(`\xAB${part}\xBB no es tabla:columna(+columna); ej. clientes:telefono+tel_secundario`);
|
|
1304
|
+
}
|
|
1305
|
+
out2[table2] = list;
|
|
1306
|
+
}
|
|
1307
|
+
return out2;
|
|
1308
|
+
}
|
|
1309
|
+
var describeRowScope = (scope) => Object.entries(scope ?? {}).map(([t, c]) => `${t} (${c.join(", ")})`).join("; ");
|
|
1310
|
+
|
|
1293
1311
|
// src/commands/data.ts
|
|
1294
1312
|
var enc6 = encodeURIComponent;
|
|
1295
1313
|
var agentPath2 = (id, rest = "") => `/api/v2/agents/${enc6(id)}${rest}`;
|
|
@@ -1327,16 +1345,17 @@ async function dbsCmd(sub, id, db, rest, v) {
|
|
|
1327
1345
|
case "ls":
|
|
1328
1346
|
case "list": {
|
|
1329
1347
|
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."));
|
|
1348
|
+
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 }));
|
|
1349
|
+
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
1350
|
}
|
|
1333
1351
|
case "tables": {
|
|
1334
1352
|
db = need2(db, "database (id or name)", "ghosty dbs tables <agent-id> <db>");
|
|
1335
1353
|
const r = await api(agentPath2(id, `/dbs/${enc6(db)}`));
|
|
1336
1354
|
const ext = new Set(r.externalWriteTables);
|
|
1337
|
-
const
|
|
1355
|
+
const own = r.externalRowScope ?? {};
|
|
1356
|
+
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
1357
|
return out(r, () => `${bold(r.name)} ${r.granted ? lavender("granted") : dim("not granted")}
|
|
1339
|
-
${table(rows, ["table", "rows", "customer writes", "columns"])}`);
|
|
1358
|
+
${table(rows, ["table", "rows", "customer writes", "customer sees", "columns"])}`);
|
|
1340
1359
|
}
|
|
1341
1360
|
case "query":
|
|
1342
1361
|
case "sql": {
|
|
@@ -1354,10 +1373,20 @@ ${dim(`${rows.length} row(s) \xB7 ${Math.round(r.ms)} ms`)}`;
|
|
|
1354
1373
|
});
|
|
1355
1374
|
}
|
|
1356
1375
|
case "grant": {
|
|
1357
|
-
db = need2(db, "database (id or name)", "ghosty dbs grant <agent-id> <db> [--external-write t1,t2]");
|
|
1358
|
-
|
|
1376
|
+
db = need2(db, "database (id or name)", "ghosty dbs grant <agent-id> <db> [--external-write t1,t2] [--external-rows t:col+col]");
|
|
1377
|
+
let rowScope;
|
|
1378
|
+
try {
|
|
1379
|
+
rowScope = v["external-rows"] !== void 0 ? parseRowScope(v["external-rows"]) : void 0;
|
|
1380
|
+
} catch (e) {
|
|
1381
|
+
throw usage(e.message, `ghosty dbs grant ${id} ${db} --external-rows clientes:telefono+tel_secundario`);
|
|
1382
|
+
}
|
|
1383
|
+
const body = {
|
|
1384
|
+
...v["external-write"] !== void 0 ? { externalWriteTables: csv(v["external-write"]) } : {},
|
|
1385
|
+
...rowScope !== void 0 ? { externalRowScope: rowScope } : {}
|
|
1386
|
+
};
|
|
1359
1387
|
const r = await api(agentPath2(id, `/dbs/${enc6(db)}`), { method: "PUT", body });
|
|
1360
|
-
|
|
1388
|
+
const own = describeRowScope(r.externalRowScope);
|
|
1389
|
+
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
1390
|
}
|
|
1362
1391
|
case "import": {
|
|
1363
1392
|
db = need2(db, "database (id or name)", "ghosty dbs import <agent-id> <db> --from easybits:<db-id>");
|
|
@@ -1675,13 +1704,14 @@ var HELPS = {
|
|
|
1675
1704
|
"ghosty dbs tables <agent-id> <db>",
|
|
1676
1705
|
'ghosty dbs query <agent-id> <db> "SQL" [--arg V]\u2026 [--write]',
|
|
1677
1706
|
"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]",
|
|
1707
|
+
"ghosty dbs grant <agent-id> <db> [--external-write t1,t2] [--external-rows t:col+col,\u2026]",
|
|
1679
1708
|
"ghosty dbs revoke <agent-id> <db>"
|
|
1680
1709
|
],
|
|
1681
1710
|
flags: [
|
|
1682
1711
|
["--arg V", "query: value for a ? placeholder (repeatable)"],
|
|
1683
1712
|
["--write", "query: allow INSERT/UPDATE/DELETE/DDL (default: read only)"],
|
|
1684
1713
|
["--external-write t1,t2", 'grant: tables it may write from WhatsApp/Messenger ("" = none)'],
|
|
1714
|
+
["--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
1715
|
["--from easybits:ID", "import: source database in EasyBits (key from $EASYBITS_API_KEY)"],
|
|
1686
1716
|
["--tables a,b", "import: only these tables (default: all)"],
|
|
1687
1717
|
["--append", "import: add rows instead of replacing them (replace is the idempotent default)"],
|
|
@@ -1692,6 +1722,7 @@ var HELPS = {
|
|
|
1692
1722
|
"ghosty dbs tables <id> totequim",
|
|
1693
1723
|
'ghosty dbs query <id> totequim "SELECT count(*) AS n FROM productos"',
|
|
1694
1724
|
"ghosty dbs grant <id> totequim --external-write cotizaciones,folio",
|
|
1725
|
+
"ghosty dbs grant <id> totequim --external-rows clientes:telefono+tel_secundario,cotizaciones:cliente_tel",
|
|
1695
1726
|
"ghosty dbs import <id> totequim --from easybits:6a10c84c\u2026 --dry-run # reads $EASYBITS_API_KEY"
|
|
1696
1727
|
],
|
|
1697
1728
|
notes: ["<db> is the database id or its name. From owner channels the agent reads and writes everything granted."],
|
|
@@ -1899,7 +1930,7 @@ ${bold("Configure an agent")}
|
|
|
1899
1930
|
files ls|get|put|rm <id> [path] Files in the agent's machine [--file LOCAL] [--out LOCAL]
|
|
1900
1931
|
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
1932
|
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]
|
|
1933
|
+
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
1934
|
mcp get|set <id> MCP servers [--file servers.json]
|
|
1904
1935
|
channels whatsapp ls|enable|disable <id> [number] Which WhatsApp Business numbers it answers [--dry-run]
|
|
1905
1936
|
board archive <id> Archive cards in bulk (reversible) --integration X | --column X | --before 30d [--dry-run]
|
|
@@ -2008,6 +2039,7 @@ async function main() {
|
|
|
2008
2039
|
arg: { type: "string", multiple: true },
|
|
2009
2040
|
write: { type: "boolean" },
|
|
2010
2041
|
"external-write": { type: "string" },
|
|
2042
|
+
"external-rows": { type: "string" },
|
|
2011
2043
|
"no-deliver": { type: "boolean" },
|
|
2012
2044
|
deliver: { type: "boolean" },
|
|
2013
2045
|
errors: { type: "boolean" },
|
|
@@ -2035,7 +2067,7 @@ async function main() {
|
|
|
2035
2067
|
const tokens = splitTokens(pos, v);
|
|
2036
2068
|
globals.token = tokens.session;
|
|
2037
2069
|
globals.profile = v.profile;
|
|
2038
|
-
if (v.version) return out({ version: "0.10.
|
|
2070
|
+
if (v.version) return out({ version: "0.10.2" }, () => "0.10.2");
|
|
2039
2071
|
const [cmd, sub, a1, a2] = pos;
|
|
2040
2072
|
if (cmd === "partner" && (v.help || !sub || sub === "help")) return out({ help: PARTNER_HELP }, () => PARTNER_HELP);
|
|
2041
2073
|
const topic = cmd === "help" ? sub : v.help ? cmd : void 0;
|
|
@@ -2118,7 +2150,7 @@ async function main() {
|
|
|
2118
2150
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
2119
2151
|
}
|
|
2120
2152
|
}
|
|
2121
|
-
var notice = updateNotice("0.10.
|
|
2153
|
+
var notice = updateNotice("0.10.2").catch(() => null);
|
|
2122
2154
|
main().then(
|
|
2123
2155
|
async () => {
|
|
2124
2156
|
const n = await notice;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghostystudio/cli",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
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",
|