@ghostystudio/cli 0.84.0 → 0.86.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 +9 -0
- package/dist/index.js +477 -309
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -973,10 +973,10 @@ async function readConfig() {
|
|
|
973
973
|
}
|
|
974
974
|
}
|
|
975
975
|
async function writeConfig(c) {
|
|
976
|
-
const
|
|
977
|
-
await mkdir(dirname(
|
|
978
|
-
await writeFile(
|
|
979
|
-
await chmod(
|
|
976
|
+
const path2 = configPath();
|
|
977
|
+
await mkdir(dirname(path2), { recursive: true });
|
|
978
|
+
await writeFile(path2, JSON.stringify(c, null, 2), { mode: 384 });
|
|
979
|
+
await chmod(path2, 384);
|
|
980
980
|
}
|
|
981
981
|
async function baseUrl() {
|
|
982
982
|
const url = process.env.GHOSTY_URL || (await readConfig()).baseUrl || "https://www.ghosty.studio";
|
|
@@ -1166,16 +1166,16 @@ async function currentToken() {
|
|
|
1166
1166
|
if (cfg.expiresAt !== void 0 && cfg.expiresAt - Date.now() < 6e4) cfg = await refresh(cfg) ?? cfg;
|
|
1167
1167
|
return cfg.accessToken;
|
|
1168
1168
|
}
|
|
1169
|
-
async function postLong(
|
|
1169
|
+
async function postLong(path2, body, timeoutMs = 9e5) {
|
|
1170
1170
|
const base = await baseUrl();
|
|
1171
1171
|
const token = await currentToken();
|
|
1172
1172
|
const httpsRequest = base.startsWith("https:") ? (await import("node:https")).request : (await import("node:http")).request;
|
|
1173
1173
|
const data = JSON.stringify(body);
|
|
1174
1174
|
return new Promise((resolve3) => {
|
|
1175
|
-
const req = httpsRequest(`${base}${
|
|
1175
|
+
const req = httpsRequest(`${base}${path2}`, {
|
|
1176
1176
|
method: "POST",
|
|
1177
1177
|
timeout: timeoutMs,
|
|
1178
|
-
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.
|
|
1178
|
+
headers: { authorization: `Bearer ${token}`, "user-agent": `ghosty-cli/${"0.86.0"}`, "content-type": "application/json", "content-length": Buffer.byteLength(data) }
|
|
1179
1179
|
}, (res) => {
|
|
1180
1180
|
let buf = "";
|
|
1181
1181
|
res.on("data", (c) => buf += c);
|
|
@@ -1200,8 +1200,8 @@ function retryAfterSeconds(h, now = Date.now()) {
|
|
|
1200
1200
|
const t2 = Date.parse(h);
|
|
1201
1201
|
return Number.isNaN(t2) ? null : Math.max(0, Math.round((t2 - now) / 1e3));
|
|
1202
1202
|
}
|
|
1203
|
-
async function api(
|
|
1204
|
-
const res = await request(
|
|
1203
|
+
async function api(path2, opts = {}) {
|
|
1204
|
+
const res = await request(path2, opts);
|
|
1205
1205
|
const text = await res.text();
|
|
1206
1206
|
if (!text) return void 0;
|
|
1207
1207
|
try {
|
|
@@ -1210,14 +1210,14 @@ async function api(path, opts = {}) {
|
|
|
1210
1210
|
return text;
|
|
1211
1211
|
}
|
|
1212
1212
|
}
|
|
1213
|
-
async function request(
|
|
1213
|
+
async function request(path2, opts = {}, retried = false) {
|
|
1214
1214
|
const base = await baseUrl();
|
|
1215
1215
|
const token = await currentToken();
|
|
1216
|
-
const res = await fetch(`${base}${
|
|
1216
|
+
const res = await fetch(`${base}${path2}`, {
|
|
1217
1217
|
method: opts.method ?? (opts.body !== void 0 || opts.raw !== void 0 ? "POST" : "GET"),
|
|
1218
1218
|
headers: {
|
|
1219
1219
|
authorization: `Bearer ${token}`,
|
|
1220
|
-
"user-agent": `ghosty-cli/${"0.
|
|
1220
|
+
"user-agent": `ghosty-cli/${"0.86.0"}`,
|
|
1221
1221
|
"accept-language": lang(),
|
|
1222
1222
|
...opts.body !== void 0 ? { "content-type": "application/json" } : {},
|
|
1223
1223
|
...opts.headers
|
|
@@ -1227,7 +1227,7 @@ async function request(path, opts = {}, retried = false) {
|
|
|
1227
1227
|
});
|
|
1228
1228
|
if (res.status === 401 && !retried && !explicitToken()) {
|
|
1229
1229
|
const cfg = await refresh(await readConfig());
|
|
1230
|
-
if (cfg) return request(
|
|
1230
|
+
if (cfg) return request(path2, opts, true);
|
|
1231
1231
|
}
|
|
1232
1232
|
if (!res.ok) throw await toError(res);
|
|
1233
1233
|
return res;
|
|
@@ -1264,7 +1264,7 @@ var mteams_exports = {};
|
|
|
1264
1264
|
__export(mteams_exports, {
|
|
1265
1265
|
mteamsCmd: () => mteamsCmd
|
|
1266
1266
|
});
|
|
1267
|
-
import { writeFile as
|
|
1267
|
+
import { writeFile as writeFile9 } from "node:fs/promises";
|
|
1268
1268
|
function list(bots) {
|
|
1269
1269
|
return bots.length ? table(
|
|
1270
1270
|
bots.map((b) => ({
|
|
@@ -1281,18 +1281,18 @@ function list(bots) {
|
|
|
1281
1281
|
}
|
|
1282
1282
|
async function mteamsCmd(sub, id, botId, v) {
|
|
1283
1283
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), EX);
|
|
1284
|
-
const
|
|
1284
|
+
const path2 = `/api/v2/agents/${enc10(id)}/mteams`;
|
|
1285
1285
|
switch (sub) {
|
|
1286
1286
|
case void 0:
|
|
1287
1287
|
case "ls":
|
|
1288
1288
|
case "list": {
|
|
1289
|
-
const r = await api(
|
|
1289
|
+
const r = await api(path2);
|
|
1290
1290
|
return out(r, () => list(r.bots));
|
|
1291
1291
|
}
|
|
1292
1292
|
case "connect": {
|
|
1293
1293
|
const secret = v.secret ?? process.env.GHOSTY_MTEAMS_SECRET;
|
|
1294
1294
|
if (!v["app-id"] || !v.tenant || !secret) throw usage(t("Missing --app-id, --tenant or the secret (--secret or GHOSTY_MTEAMS_SECRET).", "Faltan --app-id, --tenant o el secreto (--secret o GHOSTY_MTEAMS_SECRET)."), EX);
|
|
1295
|
-
const r = await api(
|
|
1295
|
+
const r = await api(path2, { method: "POST", body: { appId: v["app-id"], tenantId: v.tenant, secret, botName: v.name } });
|
|
1296
1296
|
const b = r.bots.find((x) => x.appId === v["app-id"]);
|
|
1297
1297
|
return out(
|
|
1298
1298
|
r,
|
|
@@ -1303,36 +1303,36 @@ ${dim(`${t("Next", "Sigue")}: ghosty channels mteams zip ${id} ${b?.id ?? "<bot-
|
|
|
1303
1303
|
}
|
|
1304
1304
|
case "zip": {
|
|
1305
1305
|
if (!botId) throw usage(t("Missing bot id.", "Falta el id del bot."), EX);
|
|
1306
|
-
const res = await request(`${
|
|
1306
|
+
const res = await request(`${path2}/${enc10(botId)}/zip`);
|
|
1307
1307
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
1308
1308
|
const file = v.out ?? "ghosty-teams.zip";
|
|
1309
|
-
await
|
|
1309
|
+
await writeFile9(file, bytes);
|
|
1310
1310
|
return out({ saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${t(`Saved ${file}: upload it in Teams admin \u2192 Manage apps.`, `Se guard\xF3 ${file}: s\xFAbelo en Teams admin \u2192 Administrar apps.`)}`);
|
|
1311
1311
|
}
|
|
1312
1312
|
case "on":
|
|
1313
1313
|
case "off": {
|
|
1314
1314
|
if (!botId) throw usage(t("Missing bot id.", "Falta el id del bot."), EX);
|
|
1315
|
-
const r = await api(
|
|
1315
|
+
const r = await api(path2, { method: "PATCH", body: { botId, enabled: sub === "on" } });
|
|
1316
1316
|
return out(r, () => list(r.bots));
|
|
1317
1317
|
}
|
|
1318
1318
|
case "rm":
|
|
1319
1319
|
case "disconnect": {
|
|
1320
1320
|
if (!botId) throw usage(t("Missing bot id.", "Falta el id del bot."), EX);
|
|
1321
|
-
const r = await api(
|
|
1321
|
+
const r = await api(path2, { method: "DELETE", body: { botId } });
|
|
1322
1322
|
return out(r, () => list(r.bots));
|
|
1323
1323
|
}
|
|
1324
1324
|
default:
|
|
1325
1325
|
throw usage(t(`Unknown subcommand: ${sub}.`, `Subcomando desconocido: ${sub}.`), EX);
|
|
1326
1326
|
}
|
|
1327
1327
|
}
|
|
1328
|
-
var
|
|
1328
|
+
var enc10, EX;
|
|
1329
1329
|
var init_mteams = __esm({
|
|
1330
1330
|
"src/commands/mteams.ts"() {
|
|
1331
1331
|
"use strict";
|
|
1332
1332
|
init_api();
|
|
1333
1333
|
init_util();
|
|
1334
1334
|
init_i18n();
|
|
1335
|
-
|
|
1335
|
+
enc10 = encodeURIComponent;
|
|
1336
1336
|
EX = "ghosty channels mteams ls <agent-id> | connect <agent-id> --app-id \u2026 --tenant \u2026 [--secret \u2026] [--name \u2026] | zip <agent-id> <bot-id> [--out F.zip] | on|off|rm <agent-id> <bot-id>";
|
|
1337
1337
|
}
|
|
1338
1338
|
});
|
|
@@ -1414,17 +1414,17 @@ var require_visit = __commonJS({
|
|
|
1414
1414
|
visit.BREAK = BREAK;
|
|
1415
1415
|
visit.SKIP = SKIP2;
|
|
1416
1416
|
visit.REMOVE = REMOVE;
|
|
1417
|
-
function visit_(key, node, visitor,
|
|
1418
|
-
const ctrl = callVisitor(key, node, visitor,
|
|
1417
|
+
function visit_(key, node, visitor, path2) {
|
|
1418
|
+
const ctrl = callVisitor(key, node, visitor, path2);
|
|
1419
1419
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
1420
|
-
replaceNode(key,
|
|
1421
|
-
return visit_(key, ctrl, visitor,
|
|
1420
|
+
replaceNode(key, path2, ctrl);
|
|
1421
|
+
return visit_(key, ctrl, visitor, path2);
|
|
1422
1422
|
}
|
|
1423
1423
|
if (typeof ctrl !== "symbol") {
|
|
1424
1424
|
if (identity.isCollection(node)) {
|
|
1425
|
-
|
|
1425
|
+
path2 = Object.freeze(path2.concat(node));
|
|
1426
1426
|
for (let i = 0; i < node.items.length; ++i) {
|
|
1427
|
-
const ci = visit_(i, node.items[i], visitor,
|
|
1427
|
+
const ci = visit_(i, node.items[i], visitor, path2);
|
|
1428
1428
|
if (typeof ci === "number")
|
|
1429
1429
|
i = ci - 1;
|
|
1430
1430
|
else if (ci === BREAK)
|
|
@@ -1435,13 +1435,13 @@ var require_visit = __commonJS({
|
|
|
1435
1435
|
}
|
|
1436
1436
|
}
|
|
1437
1437
|
} else if (identity.isPair(node)) {
|
|
1438
|
-
|
|
1439
|
-
const ck = visit_("key", node.key, visitor,
|
|
1438
|
+
path2 = Object.freeze(path2.concat(node));
|
|
1439
|
+
const ck = visit_("key", node.key, visitor, path2);
|
|
1440
1440
|
if (ck === BREAK)
|
|
1441
1441
|
return BREAK;
|
|
1442
1442
|
else if (ck === REMOVE)
|
|
1443
1443
|
node.key = null;
|
|
1444
|
-
const cv = visit_("value", node.value, visitor,
|
|
1444
|
+
const cv = visit_("value", node.value, visitor, path2);
|
|
1445
1445
|
if (cv === BREAK)
|
|
1446
1446
|
return BREAK;
|
|
1447
1447
|
else if (cv === REMOVE)
|
|
@@ -1462,17 +1462,17 @@ var require_visit = __commonJS({
|
|
|
1462
1462
|
visitAsync.BREAK = BREAK;
|
|
1463
1463
|
visitAsync.SKIP = SKIP2;
|
|
1464
1464
|
visitAsync.REMOVE = REMOVE;
|
|
1465
|
-
async function visitAsync_(key, node, visitor,
|
|
1466
|
-
const ctrl = await callVisitor(key, node, visitor,
|
|
1465
|
+
async function visitAsync_(key, node, visitor, path2) {
|
|
1466
|
+
const ctrl = await callVisitor(key, node, visitor, path2);
|
|
1467
1467
|
if (identity.isNode(ctrl) || identity.isPair(ctrl)) {
|
|
1468
|
-
replaceNode(key,
|
|
1469
|
-
return visitAsync_(key, ctrl, visitor,
|
|
1468
|
+
replaceNode(key, path2, ctrl);
|
|
1469
|
+
return visitAsync_(key, ctrl, visitor, path2);
|
|
1470
1470
|
}
|
|
1471
1471
|
if (typeof ctrl !== "symbol") {
|
|
1472
1472
|
if (identity.isCollection(node)) {
|
|
1473
|
-
|
|
1473
|
+
path2 = Object.freeze(path2.concat(node));
|
|
1474
1474
|
for (let i = 0; i < node.items.length; ++i) {
|
|
1475
|
-
const ci = await visitAsync_(i, node.items[i], visitor,
|
|
1475
|
+
const ci = await visitAsync_(i, node.items[i], visitor, path2);
|
|
1476
1476
|
if (typeof ci === "number")
|
|
1477
1477
|
i = ci - 1;
|
|
1478
1478
|
else if (ci === BREAK)
|
|
@@ -1483,13 +1483,13 @@ var require_visit = __commonJS({
|
|
|
1483
1483
|
}
|
|
1484
1484
|
}
|
|
1485
1485
|
} else if (identity.isPair(node)) {
|
|
1486
|
-
|
|
1487
|
-
const ck = await visitAsync_("key", node.key, visitor,
|
|
1486
|
+
path2 = Object.freeze(path2.concat(node));
|
|
1487
|
+
const ck = await visitAsync_("key", node.key, visitor, path2);
|
|
1488
1488
|
if (ck === BREAK)
|
|
1489
1489
|
return BREAK;
|
|
1490
1490
|
else if (ck === REMOVE)
|
|
1491
1491
|
node.key = null;
|
|
1492
|
-
const cv = await visitAsync_("value", node.value, visitor,
|
|
1492
|
+
const cv = await visitAsync_("value", node.value, visitor, path2);
|
|
1493
1493
|
if (cv === BREAK)
|
|
1494
1494
|
return BREAK;
|
|
1495
1495
|
else if (cv === REMOVE)
|
|
@@ -1516,23 +1516,23 @@ var require_visit = __commonJS({
|
|
|
1516
1516
|
}
|
|
1517
1517
|
return visitor;
|
|
1518
1518
|
}
|
|
1519
|
-
function callVisitor(key, node, visitor,
|
|
1519
|
+
function callVisitor(key, node, visitor, path2) {
|
|
1520
1520
|
if (typeof visitor === "function")
|
|
1521
|
-
return visitor(key, node,
|
|
1521
|
+
return visitor(key, node, path2);
|
|
1522
1522
|
if (identity.isMap(node))
|
|
1523
|
-
return visitor.Map?.(key, node,
|
|
1523
|
+
return visitor.Map?.(key, node, path2);
|
|
1524
1524
|
if (identity.isSeq(node))
|
|
1525
|
-
return visitor.Seq?.(key, node,
|
|
1525
|
+
return visitor.Seq?.(key, node, path2);
|
|
1526
1526
|
if (identity.isPair(node))
|
|
1527
|
-
return visitor.Pair?.(key, node,
|
|
1527
|
+
return visitor.Pair?.(key, node, path2);
|
|
1528
1528
|
if (identity.isScalar(node))
|
|
1529
|
-
return visitor.Scalar?.(key, node,
|
|
1529
|
+
return visitor.Scalar?.(key, node, path2);
|
|
1530
1530
|
if (identity.isAlias(node))
|
|
1531
|
-
return visitor.Alias?.(key, node,
|
|
1531
|
+
return visitor.Alias?.(key, node, path2);
|
|
1532
1532
|
return void 0;
|
|
1533
1533
|
}
|
|
1534
|
-
function replaceNode(key,
|
|
1535
|
-
const parent =
|
|
1534
|
+
function replaceNode(key, path2, node) {
|
|
1535
|
+
const parent = path2[path2.length - 1];
|
|
1536
1536
|
if (identity.isCollection(parent)) {
|
|
1537
1537
|
parent.items[key] = node;
|
|
1538
1538
|
} else if (identity.isPair(parent)) {
|
|
@@ -2144,10 +2144,10 @@ var require_Collection = __commonJS({
|
|
|
2144
2144
|
var createNode = require_createNode();
|
|
2145
2145
|
var identity = require_identity();
|
|
2146
2146
|
var Node = require_Node();
|
|
2147
|
-
function collectionFromPath(schema,
|
|
2147
|
+
function collectionFromPath(schema, path2, value) {
|
|
2148
2148
|
let v = value;
|
|
2149
|
-
for (let i =
|
|
2150
|
-
const k =
|
|
2149
|
+
for (let i = path2.length - 1; i >= 0; --i) {
|
|
2150
|
+
const k = path2[i];
|
|
2151
2151
|
if (typeof k === "number" && Number.isInteger(k) && k >= 0) {
|
|
2152
2152
|
const a = [];
|
|
2153
2153
|
a[k] = v;
|
|
@@ -2166,7 +2166,7 @@ var require_Collection = __commonJS({
|
|
|
2166
2166
|
sourceObjects: /* @__PURE__ */ new Map()
|
|
2167
2167
|
});
|
|
2168
2168
|
}
|
|
2169
|
-
var isEmptyPath = (
|
|
2169
|
+
var isEmptyPath = (path2) => path2 == null || typeof path2 === "object" && !!path2[Symbol.iterator]().next().done;
|
|
2170
2170
|
var Collection = class extends Node.NodeBase {
|
|
2171
2171
|
constructor(type, schema) {
|
|
2172
2172
|
super(type);
|
|
@@ -2196,11 +2196,11 @@ var require_Collection = __commonJS({
|
|
|
2196
2196
|
* be a Pair instance or a `{ key, value }` object, which may not have a key
|
|
2197
2197
|
* that already exists in the map.
|
|
2198
2198
|
*/
|
|
2199
|
-
addIn(
|
|
2200
|
-
if (isEmptyPath(
|
|
2199
|
+
addIn(path2, value) {
|
|
2200
|
+
if (isEmptyPath(path2))
|
|
2201
2201
|
this.add(value);
|
|
2202
2202
|
else {
|
|
2203
|
-
const [key, ...rest] =
|
|
2203
|
+
const [key, ...rest] = path2;
|
|
2204
2204
|
const node = this.get(key, true);
|
|
2205
2205
|
if (identity.isCollection(node))
|
|
2206
2206
|
node.addIn(rest, value);
|
|
@@ -2214,8 +2214,8 @@ var require_Collection = __commonJS({
|
|
|
2214
2214
|
* Removes a value from the collection.
|
|
2215
2215
|
* @returns `true` if the item was found and removed.
|
|
2216
2216
|
*/
|
|
2217
|
-
deleteIn(
|
|
2218
|
-
const [key, ...rest] =
|
|
2217
|
+
deleteIn(path2) {
|
|
2218
|
+
const [key, ...rest] = path2;
|
|
2219
2219
|
if (rest.length === 0)
|
|
2220
2220
|
return this.delete(key);
|
|
2221
2221
|
const node = this.get(key, true);
|
|
@@ -2229,8 +2229,8 @@ var require_Collection = __commonJS({
|
|
|
2229
2229
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
2230
2230
|
* `true` (collections are always returned intact).
|
|
2231
2231
|
*/
|
|
2232
|
-
getIn(
|
|
2233
|
-
const [key, ...rest] =
|
|
2232
|
+
getIn(path2, keepScalar) {
|
|
2233
|
+
const [key, ...rest] = path2;
|
|
2234
2234
|
const node = this.get(key, true);
|
|
2235
2235
|
if (rest.length === 0)
|
|
2236
2236
|
return !keepScalar && identity.isScalar(node) ? node.value : node;
|
|
@@ -2248,8 +2248,8 @@ var require_Collection = __commonJS({
|
|
|
2248
2248
|
/**
|
|
2249
2249
|
* Checks if the collection includes a value with the key `key`.
|
|
2250
2250
|
*/
|
|
2251
|
-
hasIn(
|
|
2252
|
-
const [key, ...rest] =
|
|
2251
|
+
hasIn(path2) {
|
|
2252
|
+
const [key, ...rest] = path2;
|
|
2253
2253
|
if (rest.length === 0)
|
|
2254
2254
|
return this.has(key);
|
|
2255
2255
|
const node = this.get(key, true);
|
|
@@ -2259,8 +2259,8 @@ var require_Collection = __commonJS({
|
|
|
2259
2259
|
* Sets a value in this collection. For `!!set`, `value` needs to be a
|
|
2260
2260
|
* boolean to add/remove the item from the set.
|
|
2261
2261
|
*/
|
|
2262
|
-
setIn(
|
|
2263
|
-
const [key, ...rest] =
|
|
2262
|
+
setIn(path2, value) {
|
|
2263
|
+
const [key, ...rest] = path2;
|
|
2264
2264
|
if (rest.length === 0) {
|
|
2265
2265
|
this.set(key, value);
|
|
2266
2266
|
} else {
|
|
@@ -4775,9 +4775,9 @@ var require_Document = __commonJS({
|
|
|
4775
4775
|
this.contents.add(value);
|
|
4776
4776
|
}
|
|
4777
4777
|
/** Adds a value to the document. */
|
|
4778
|
-
addIn(
|
|
4778
|
+
addIn(path2, value) {
|
|
4779
4779
|
if (assertCollection(this.contents))
|
|
4780
|
-
this.contents.addIn(
|
|
4780
|
+
this.contents.addIn(path2, value);
|
|
4781
4781
|
}
|
|
4782
4782
|
/**
|
|
4783
4783
|
* Create a new `Alias` node, ensuring that the target `node` has the required anchor.
|
|
@@ -4852,14 +4852,14 @@ var require_Document = __commonJS({
|
|
|
4852
4852
|
* Removes a value from the document.
|
|
4853
4853
|
* @returns `true` if the item was found and removed.
|
|
4854
4854
|
*/
|
|
4855
|
-
deleteIn(
|
|
4856
|
-
if (Collection.isEmptyPath(
|
|
4855
|
+
deleteIn(path2) {
|
|
4856
|
+
if (Collection.isEmptyPath(path2)) {
|
|
4857
4857
|
if (this.contents == null)
|
|
4858
4858
|
return false;
|
|
4859
4859
|
this.contents = null;
|
|
4860
4860
|
return true;
|
|
4861
4861
|
}
|
|
4862
|
-
return assertCollection(this.contents) ? this.contents.deleteIn(
|
|
4862
|
+
return assertCollection(this.contents) ? this.contents.deleteIn(path2) : false;
|
|
4863
4863
|
}
|
|
4864
4864
|
/**
|
|
4865
4865
|
* Returns item at `key`, or `undefined` if not found. By default unwraps
|
|
@@ -4874,10 +4874,10 @@ var require_Document = __commonJS({
|
|
|
4874
4874
|
* scalar values from their surrounding node; to disable set `keepScalar` to
|
|
4875
4875
|
* `true` (collections are always returned intact).
|
|
4876
4876
|
*/
|
|
4877
|
-
getIn(
|
|
4878
|
-
if (Collection.isEmptyPath(
|
|
4877
|
+
getIn(path2, keepScalar) {
|
|
4878
|
+
if (Collection.isEmptyPath(path2))
|
|
4879
4879
|
return !keepScalar && identity.isScalar(this.contents) ? this.contents.value : this.contents;
|
|
4880
|
-
return identity.isCollection(this.contents) ? this.contents.getIn(
|
|
4880
|
+
return identity.isCollection(this.contents) ? this.contents.getIn(path2, keepScalar) : void 0;
|
|
4881
4881
|
}
|
|
4882
4882
|
/**
|
|
4883
4883
|
* Checks if the document includes a value with the key `key`.
|
|
@@ -4888,10 +4888,10 @@ var require_Document = __commonJS({
|
|
|
4888
4888
|
/**
|
|
4889
4889
|
* Checks if the document includes a value at `path`.
|
|
4890
4890
|
*/
|
|
4891
|
-
hasIn(
|
|
4892
|
-
if (Collection.isEmptyPath(
|
|
4891
|
+
hasIn(path2) {
|
|
4892
|
+
if (Collection.isEmptyPath(path2))
|
|
4893
4893
|
return this.contents !== void 0;
|
|
4894
|
-
return identity.isCollection(this.contents) ? this.contents.hasIn(
|
|
4894
|
+
return identity.isCollection(this.contents) ? this.contents.hasIn(path2) : false;
|
|
4895
4895
|
}
|
|
4896
4896
|
/**
|
|
4897
4897
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
@@ -4908,13 +4908,13 @@ var require_Document = __commonJS({
|
|
|
4908
4908
|
* Sets a value in this document. For `!!set`, `value` needs to be a
|
|
4909
4909
|
* boolean to add/remove the item from the set.
|
|
4910
4910
|
*/
|
|
4911
|
-
setIn(
|
|
4912
|
-
if (Collection.isEmptyPath(
|
|
4911
|
+
setIn(path2, value) {
|
|
4912
|
+
if (Collection.isEmptyPath(path2)) {
|
|
4913
4913
|
this.contents = value;
|
|
4914
4914
|
} else if (this.contents == null) {
|
|
4915
|
-
this.contents = Collection.collectionFromPath(this.schema, Array.from(
|
|
4915
|
+
this.contents = Collection.collectionFromPath(this.schema, Array.from(path2), value);
|
|
4916
4916
|
} else if (assertCollection(this.contents)) {
|
|
4917
|
-
this.contents.setIn(
|
|
4917
|
+
this.contents.setIn(path2, value);
|
|
4918
4918
|
}
|
|
4919
4919
|
}
|
|
4920
4920
|
/**
|
|
@@ -6875,9 +6875,9 @@ var require_cst_visit = __commonJS({
|
|
|
6875
6875
|
visit.BREAK = BREAK;
|
|
6876
6876
|
visit.SKIP = SKIP2;
|
|
6877
6877
|
visit.REMOVE = REMOVE;
|
|
6878
|
-
visit.itemAtPath = (cst,
|
|
6878
|
+
visit.itemAtPath = (cst, path2) => {
|
|
6879
6879
|
let item = cst;
|
|
6880
|
-
for (const [field, index] of
|
|
6880
|
+
for (const [field, index] of path2) {
|
|
6881
6881
|
const tok = item?.[field];
|
|
6882
6882
|
if (tok && "items" in tok) {
|
|
6883
6883
|
item = tok.items[index];
|
|
@@ -6886,23 +6886,23 @@ var require_cst_visit = __commonJS({
|
|
|
6886
6886
|
}
|
|
6887
6887
|
return item;
|
|
6888
6888
|
};
|
|
6889
|
-
visit.parentCollection = (cst,
|
|
6890
|
-
const parent = visit.itemAtPath(cst,
|
|
6891
|
-
const field =
|
|
6889
|
+
visit.parentCollection = (cst, path2) => {
|
|
6890
|
+
const parent = visit.itemAtPath(cst, path2.slice(0, -1));
|
|
6891
|
+
const field = path2[path2.length - 1][0];
|
|
6892
6892
|
const coll = parent?.[field];
|
|
6893
6893
|
if (coll && "items" in coll)
|
|
6894
6894
|
return coll;
|
|
6895
6895
|
throw new Error("Parent collection not found");
|
|
6896
6896
|
};
|
|
6897
|
-
function _visit(
|
|
6898
|
-
let ctrl = visitor(item,
|
|
6897
|
+
function _visit(path2, item, visitor) {
|
|
6898
|
+
let ctrl = visitor(item, path2);
|
|
6899
6899
|
if (typeof ctrl === "symbol")
|
|
6900
6900
|
return ctrl;
|
|
6901
6901
|
for (const field of ["key", "value"]) {
|
|
6902
6902
|
const token = item[field];
|
|
6903
6903
|
if (token && "items" in token) {
|
|
6904
6904
|
for (let i = 0; i < token.items.length; ++i) {
|
|
6905
|
-
const ci = _visit(Object.freeze(
|
|
6905
|
+
const ci = _visit(Object.freeze(path2.concat([[field, i]])), token.items[i], visitor);
|
|
6906
6906
|
if (typeof ci === "number")
|
|
6907
6907
|
i = ci - 1;
|
|
6908
6908
|
else if (ci === BREAK)
|
|
@@ -6913,10 +6913,10 @@ var require_cst_visit = __commonJS({
|
|
|
6913
6913
|
}
|
|
6914
6914
|
}
|
|
6915
6915
|
if (typeof ctrl === "function" && field === "key")
|
|
6916
|
-
ctrl = ctrl(item,
|
|
6916
|
+
ctrl = ctrl(item, path2);
|
|
6917
6917
|
}
|
|
6918
6918
|
}
|
|
6919
|
-
return typeof ctrl === "function" ? ctrl(item,
|
|
6919
|
+
return typeof ctrl === "function" ? ctrl(item, path2) : ctrl;
|
|
6920
6920
|
}
|
|
6921
6921
|
exports.visit = visit;
|
|
6922
6922
|
}
|
|
@@ -9074,23 +9074,23 @@ ${t("Change it?", "\xBFCambiarlo?")}`, { yes: v.yes, retry });
|
|
|
9074
9074
|
if (sub === "renew") {
|
|
9075
9075
|
slug = needSpace(slug ?? context, ex);
|
|
9076
9076
|
if (!v.until) throw usage(t("Missing --until YYYY-MM-DD.", "Falta --until AAAA-MM-DD."), `ghosty spaces renew ${slug} --until 2026-11-01`);
|
|
9077
|
-
const
|
|
9078
|
-
const plan = await api(
|
|
9077
|
+
const path2 = wsPath(slug, "/renew");
|
|
9078
|
+
const plan = await api(path2, { method: "POST", body: { until: v.until, dryRun: true } });
|
|
9079
9079
|
const line2 = t(`${plan.workspace} (${plan.plan}): paid until ${plan.from ?? "\u2014"} \u2192 ${plan.to}`, `${plan.workspace} (${plan.plan}): pagado hasta ${plan.from ?? "\u2014"} \u2192 ${plan.to}`);
|
|
9080
9080
|
if (v["dry-run"]) return out(plan, () => `${line2}
|
|
9081
9081
|
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
9082
9082
|
requireYesIfHeadless(v.yes, `ghosty spaces renew ${slug} --until ${v.until}`);
|
|
9083
9083
|
await confirm(`${line2}
|
|
9084
9084
|
${t("Renew?", "\xBFRenovar?")}`, { yes: v.yes, retry: `ghosty spaces renew ${slug} --until ${v.until}` });
|
|
9085
|
-
const r = await api(
|
|
9085
|
+
const r = await api(path2, { method: "POST", body: { until: v.until, ...v.note ? { note: v.note } : {} } });
|
|
9086
9086
|
return out(r, () => `${lavender("\u2713")} ${line2} ${dim(t("(logged in the workspace's audit)", "(queda en la bit\xE1cora del espacio)"))}`);
|
|
9087
9087
|
}
|
|
9088
9088
|
if (sub === "plan") {
|
|
9089
9089
|
slug = needSpace(slug ?? context, "ghosty spaces plan <workspace> pro --courtesy --dry-run");
|
|
9090
9090
|
if (!arg) throw usage(t("Which plan?", "\xBFQu\xE9 plan?"), `ghosty spaces plan ${slug} trial|pro|empresarial [--courtesy] [--until YYYY-MM-DD] --dry-run`);
|
|
9091
|
-
const
|
|
9091
|
+
const path2 = wsPath(slug, "/plan");
|
|
9092
9092
|
const body = { plan: arg, ...v.courtesy ? { courtesy: true } : {}, ...v.until ? { until: v.until } : {}, ...v.note ? { note: v.note } : {} };
|
|
9093
|
-
const plan = await api(
|
|
9093
|
+
const plan = await api(path2, { method: "POST", body: { ...body, dryRun: true } });
|
|
9094
9094
|
const text = planChangeText(plan);
|
|
9095
9095
|
if (!plan.changed && !v.courtesy && !v.note) return out(plan, () => dim(t(`Nothing to do: ${plan.workspace} is already ${plan.before.plan}.`, `Nada que hacer: ${plan.workspace} ya es ${plan.before.plan}.`)));
|
|
9096
9096
|
if (v["dry-run"]) return out(plan, () => `${text}
|
|
@@ -9099,15 +9099,15 @@ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
|
9099
9099
|
requireYesIfHeadless(v.yes, retry);
|
|
9100
9100
|
await confirm(`${text}
|
|
9101
9101
|
${t("Change the plan?", "\xBFCambiar el plan?")}`, { yes: v.yes, retry });
|
|
9102
|
-
const r = await api(
|
|
9102
|
+
const r = await api(path2, { method: "POST", body });
|
|
9103
9103
|
return out(r, () => `${lavender("\u2713")} ${planChangeText(r)}
|
|
9104
9104
|
${dim(t("(logged in the workspace's audit)", "(queda en la bit\xE1cora del espacio)"))}`);
|
|
9105
9105
|
}
|
|
9106
9106
|
if (sub === "rename") {
|
|
9107
9107
|
slug = needSpace(slug, ex);
|
|
9108
9108
|
if (!arg) throw usage(t("Missing the new name.", "Falta el nombre nuevo."), `ghosty spaces rename ${slug} <new> --dry-run`);
|
|
9109
|
-
const
|
|
9110
|
-
const plan = await api(
|
|
9109
|
+
const path2 = wsPath(slug, "/rename");
|
|
9110
|
+
const plan = await api(path2, { method: "POST", body: { to: arg, dryRun: true } });
|
|
9111
9111
|
const text = `${plan.from} \u2192 ${bold(plan.to)}
|
|
9112
9112
|
${plan.warnings.map((w) => dim(` \xB7 ${serverText(w)}`)).join("\n")}`;
|
|
9113
9113
|
if (v["dry-run"]) return out(plan, () => `${text}
|
|
@@ -9115,14 +9115,14 @@ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
|
9115
9115
|
requireYesIfHeadless(v.yes, `ghosty spaces rename ${slug} ${arg}`);
|
|
9116
9116
|
await confirm(`${text}
|
|
9117
9117
|
${t("Rename?", "\xBFRenombrar?")}`, { yes: v.yes, expect: plan.from, retry: `ghosty spaces rename ${slug} ${arg}` });
|
|
9118
|
-
const r = await api(
|
|
9118
|
+
const r = await api(path2, { method: "POST", body: { to: arg } });
|
|
9119
9119
|
return out(r, () => `${lavender("\u2713")} ${t(`Renamed: ${r.from} \u2192 ${r.to}. Undo: ghosty spaces rename ${r.to} ${r.from}`, `Renombrado: ${r.from} \u2192 ${r.to}. Deshacer: ghosty spaces rename ${r.to} ${r.from}`)}`);
|
|
9120
9120
|
}
|
|
9121
9121
|
if (sub === "archive" || sub === "restore") {
|
|
9122
9122
|
slug = needSpace(slug, `ghosty spaces ${sub} <workspace> --dry-run`);
|
|
9123
9123
|
const archive = sub === "archive";
|
|
9124
|
-
const
|
|
9125
|
-
const plan = await api(
|
|
9124
|
+
const path2 = wsPath(slug, "/archive");
|
|
9125
|
+
const plan = await api(path2, { method: "POST", body: { archive, dryRun: true } });
|
|
9126
9126
|
const line2 = `${plan.slug}: ${plan.from} \u2192 ${bold(plan.to)} ${dim(t(`(${plan.agents} agent(s); they and their data stay as they are)`, `(${plan.agents} agente(s); ellos y sus datos se quedan igual)`))}`;
|
|
9127
9127
|
if (!plan.changed) return out(plan, () => dim(t(`Nothing to do: ${plan.slug} is already ${plan.from}.`, `Nada que hacer: ${plan.slug} ya est\xE1 ${plan.from}.`)));
|
|
9128
9128
|
if (v["dry-run"]) return out(plan, () => `${line2}
|
|
@@ -9131,7 +9131,7 @@ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
|
9131
9131
|
requireYesIfHeadless(v.yes, retry);
|
|
9132
9132
|
await confirm(`${line2}
|
|
9133
9133
|
${archive ? t("Archive it?", "\xBFArchivarlo?") : t("Restore it?", "\xBFRestaurarlo?")}`, { yes: v.yes, retry, ...archive ? { expect: plan.slug } : {} });
|
|
9134
|
-
const r = await api(
|
|
9134
|
+
const r = await api(path2, { method: "POST", body: { archive } });
|
|
9135
9135
|
return out(r, () => archive ? `${lavender("\u2713")} ${t(`Archived: ${r.slug}. Undo: ghosty spaces restore ${r.slug}`, `Archivado: ${r.slug}. Deshacer: ghosty spaces restore ${r.slug}`)}` : `${lavender("\u2713")} ${t(`Restored: ${r.slug}.`, `Restaurado: ${r.slug}.`)}`);
|
|
9136
9136
|
}
|
|
9137
9137
|
if (sub === "transfer") {
|
|
@@ -9139,8 +9139,8 @@ ${archive ? t("Archive it?", "\xBFArchivarlo?") : t("Restore it?", "\xBFRestaura
|
|
|
9139
9139
|
const toEmail = v.to ?? arg;
|
|
9140
9140
|
if (!toEmail) throw usage(t("Missing --to EMAIL.", "Falta --to CORREO."), `ghosty spaces transfer ${slug} --to new@owner.com --keep-as admin --copy-keys --dry-run`);
|
|
9141
9141
|
const body = { to: toEmail, ...v["keep-as"] ? { keepAs: v["keep-as"] } : {}, ...v["copy-keys"] ? { copyKeys: true } : {} };
|
|
9142
|
-
const
|
|
9143
|
-
const plan = await api(
|
|
9142
|
+
const path2 = wsPath(slug, "/transfer");
|
|
9143
|
+
const plan = await api(path2, { method: "POST", body: { ...body, dryRun: true } });
|
|
9144
9144
|
const text = [
|
|
9145
9145
|
`${plan.workspace}: ${plan.from ?? "\u2014"} \u2192 ${bold(plan.to)} ${dim(t(`(previous owner stays as ${plan.keepAs})`, `(el anterior queda como ${plan.keepAs})`))}`,
|
|
9146
9146
|
`${t("agents", "agentes")}: ${plan.agents.join(", ") || "\u2014"}`,
|
|
@@ -9153,7 +9153,7 @@ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
|
9153
9153
|
requireYesIfHeadless(v.yes, retry);
|
|
9154
9154
|
await confirm(`${text}
|
|
9155
9155
|
${t("Transfer it? Its agents' boxes are recycled (a turn in flight finishes first).", "\xBFTraspasarlo? Se reciclan las cajas de sus agentes (un turno en vuelo termina antes).")}`, { yes: v.yes, expect: plan.workspace, retry });
|
|
9156
|
-
const r = await api(
|
|
9156
|
+
const r = await api(path2, { method: "POST", body });
|
|
9157
9157
|
return out(r, () => `${lavender("\u2713")} ${t(`${r.workspace} now belongs to ${r.to}; ${r.from ?? "\u2014"} stays as ${r.keepAs}. ${r.recycled} agent(s) recycled.`, `${r.workspace} ya es de ${r.to}; ${r.from ?? "\u2014"} queda como ${r.keepAs}. ${r.recycled} agente(s) reciclado(s).`)}${r.warnings.length ? `
|
|
9158
9158
|
${r.warnings.map((w) => red(` \xB7 ${serverText(w)}`)).join("\n")}` : ""}`);
|
|
9159
9159
|
}
|
|
@@ -10046,10 +10046,10 @@ async function tryTurn(id, text, v) {
|
|
|
10046
10046
|
out(r, () => (r.text ?? "") + (r.error ? `
|
|
10047
10047
|
${dim(`(${t("warning", "aviso")}: ${serverText(r.error)})`)}` : ""));
|
|
10048
10048
|
}
|
|
10049
|
-
async function filesCmd(sub, id,
|
|
10049
|
+
async function filesCmd(sub, id, path2, v) {
|
|
10050
10050
|
const ex = "ghosty files ls <agent-id> | get <agent-id> <path> | put <agent-id> <path> --file <local> | rm <agent-id> <path>";
|
|
10051
10051
|
id = need(id, "agent id", ex);
|
|
10052
|
-
const p = (
|
|
10052
|
+
const p = (path2 ?? "").replace(/^\/+/, "");
|
|
10053
10053
|
switch (sub) {
|
|
10054
10054
|
case "ls": {
|
|
10055
10055
|
const r = await api(agentPath(id, `/files/${p}`));
|
|
@@ -10060,7 +10060,7 @@ ${dim(r.note)}` : ""}`);
|
|
|
10060
10060
|
return;
|
|
10061
10061
|
}
|
|
10062
10062
|
case "get": {
|
|
10063
|
-
need(
|
|
10063
|
+
need(path2, "path", ex);
|
|
10064
10064
|
const res = await request(agentPath(id, `/files/${p}`));
|
|
10065
10065
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
10066
10066
|
if (v.out) {
|
|
@@ -10070,14 +10070,14 @@ ${dim(r.note)}` : ""}`);
|
|
|
10070
10070
|
return;
|
|
10071
10071
|
}
|
|
10072
10072
|
case "put": {
|
|
10073
|
-
need(
|
|
10073
|
+
need(path2, "path", ex);
|
|
10074
10074
|
const data = v.file ? await readFile3(v.file) : await readStdin();
|
|
10075
10075
|
const r = await api(agentPath(id, `/files/${p}`), { method: "PUT", raw: new Uint8Array(data), headers: { "content-type": "application/octet-stream" } });
|
|
10076
10076
|
out(r ?? { path: p, bytes: data.length }, () => `${lavender("\u2713")} ${t("Wrote", "Escrito")} ${p} (${data.length} bytes).`);
|
|
10077
10077
|
return;
|
|
10078
10078
|
}
|
|
10079
10079
|
case "rm": {
|
|
10080
|
-
need(
|
|
10080
|
+
need(path2, "path", ex);
|
|
10081
10081
|
await confirm(t(`Delete ${p} from the agent's machine?`, `\xBFBorrar ${p} de la m\xE1quina del agente?`), { yes: v.yes, retry: `ghosty files rm ${id} ${p}` });
|
|
10082
10082
|
out(await api(agentPath(id, `/files/${p}`), { method: "DELETE" }) ?? { deleted: p }, () => `${lavender("\u2713")} ${t("Deleted", "Borrado")} ${p}`);
|
|
10083
10083
|
return;
|
|
@@ -10125,10 +10125,10 @@ async function skillsCmd(sub, id, slug, v) {
|
|
|
10125
10125
|
case "ls":
|
|
10126
10126
|
case "list": {
|
|
10127
10127
|
const r = await api(agentPath(id, "/skills"));
|
|
10128
|
-
const
|
|
10128
|
+
const short5 = (d) => (d ?? "").replace(/\s+/g, " ").slice(0, 70);
|
|
10129
10129
|
const rows = [
|
|
10130
|
-
...r.skills.map((s2) => ({ slug: s2.slug, source: "own", on: "on", description:
|
|
10131
|
-
...(r.house ?? []).map((s2) => ({ slug: s2.slug, source: "ghosty", on: s2.enabled ? "on" : "off", description:
|
|
10130
|
+
...r.skills.map((s2) => ({ slug: s2.slug, source: "own", on: "on", description: short5(s2.description) })),
|
|
10131
|
+
...(r.house ?? []).map((s2) => ({ slug: s2.slug, source: "ghosty", on: s2.enabled ? "on" : "off", description: short5(s2.description) }))
|
|
10132
10132
|
];
|
|
10133
10133
|
const more = r.community.filter((c) => !c.installed).length;
|
|
10134
10134
|
out(r, () => `${table(rows, ["slug", "source", "on", "description"])}
|
|
@@ -10417,8 +10417,8 @@ async function writeEmbedComponent(e, baseUrl2, file, force) {
|
|
|
10417
10417
|
if (!e.token) throw usage(t("Turn the widget on first: ghosty agents embed enable <id> --domains yoursite.com", "Prende primero el widget: ghosty agents embed enable <id> --domains tusitio.com"));
|
|
10418
10418
|
const ext = file.toLowerCase().match(/\.(tsx|jsx)$/)?.[1];
|
|
10419
10419
|
if (!ext) throw usage(t("--out must end in .tsx or .jsx", "--out debe terminar en .tsx o .jsx"));
|
|
10420
|
-
const
|
|
10421
|
-
const exists = await readFile3(
|
|
10420
|
+
const path2 = resolve(file);
|
|
10421
|
+
const exists = await readFile3(path2).then(() => true, () => false);
|
|
10422
10422
|
if (exists && !force) throw usage(t(`${file} already exists; add --force to overwrite it`, `${file} ya existe; agrega --force para pisarlo`));
|
|
10423
10423
|
const src = reactComponentSource(
|
|
10424
10424
|
{
|
|
@@ -10436,9 +10436,9 @@ async function writeEmbedComponent(e, baseUrl2, file, force) {
|
|
|
10436
10436
|
},
|
|
10437
10437
|
ext === "tsx"
|
|
10438
10438
|
);
|
|
10439
|
-
await mkdir3(dirname2(
|
|
10440
|
-
await writeFile3(
|
|
10441
|
-
return relative(process.cwd(),
|
|
10439
|
+
await mkdir3(dirname2(path2), { recursive: true });
|
|
10440
|
+
await writeFile3(path2, src);
|
|
10441
|
+
return relative(process.cwd(), path2) || path2;
|
|
10442
10442
|
}
|
|
10443
10443
|
async function agentsBox(id, v) {
|
|
10444
10444
|
id = need(id, "agent id", "ghosty agents box <agent-id> [--check PATH] [--contains PATH=TEXT] [--recycle | --start]");
|
|
@@ -10832,8 +10832,8 @@ ${dim(t(`${files.length} file(s), ${(total / 1e6).toFixed(1)} MB \u2192 ${dir}/
|
|
|
10832
10832
|
let name = safeName(f.name);
|
|
10833
10833
|
if (used.has(join4(sub, name).toLowerCase())) name = `${basename2(name, extname(name))}-${f.id.slice(-6)}${extname(name)}`;
|
|
10834
10834
|
used.add(join4(sub, name).toLowerCase());
|
|
10835
|
-
const
|
|
10836
|
-
const prev = await stat(
|
|
10835
|
+
const path2 = join4(sub, name);
|
|
10836
|
+
const prev = await stat(path2).catch(() => null);
|
|
10837
10837
|
if (prev && prev.size === f.size) {
|
|
10838
10838
|
skipped++;
|
|
10839
10839
|
continue;
|
|
@@ -10844,7 +10844,7 @@ ${dim(t(`${files.length} file(s), ${(total / 1e6).toFixed(1)} MB \u2192 ${dir}/
|
|
|
10844
10844
|
const res = await fetch(url);
|
|
10845
10845
|
if (!res.ok) throw new Error(`${res.status}`);
|
|
10846
10846
|
await mkdir4(sub, { recursive: true });
|
|
10847
|
-
await writeFile4(
|
|
10847
|
+
await writeFile4(path2, Buffer.from(await res.arrayBuffer()));
|
|
10848
10848
|
saved++;
|
|
10849
10849
|
} catch (e) {
|
|
10850
10850
|
failed.push({ id: f.id, name: f.name, error: e.message });
|
|
@@ -10962,16 +10962,16 @@ function parseTeamsTarget(raw) {
|
|
|
10962
10962
|
}
|
|
10963
10963
|
async function teamsSchedule(sub, agent, arg, v) {
|
|
10964
10964
|
const target = parseTeamsTarget(v.teams);
|
|
10965
|
-
const
|
|
10965
|
+
const path2 = `/api/v2/agents/${enc5(agent)}/teams-schedule`;
|
|
10966
10966
|
const when = (epoch) => new Date(epoch * 1e3).toLocaleString("es-MX", { timeZone: "America/Mexico_City", dateStyle: "medium", timeStyle: "short" });
|
|
10967
10967
|
if (sub === "ls" || sub === "list") {
|
|
10968
|
-
const r2 = await api(`${
|
|
10968
|
+
const r2 = await api(`${path2}?workspace=${enc5(target.workspace)}`);
|
|
10969
10969
|
const rows = r2.scheduled.map((x) => ({ id: x.id, due: when(x.dueAt), where: `#${x.channel}${x.parentId ? `/${x.parentId}` : ""}`, by: x.by, text: short(x.text) }));
|
|
10970
10970
|
return out(r2, () => rows.length ? table(rows, ["id", "due", "where", "by", "text"]) : dim(t("No scheduled turns in Teams.", "No hay turnos programados en Teams.")));
|
|
10971
10971
|
}
|
|
10972
10972
|
if (sub === "rm" || sub === "cancel") {
|
|
10973
10973
|
if (!arg) throw usage(t("Missing schedule id.", "Falta el id del turno."), `ghosty schedule rm <agent-id> <id> --teams ${target.workspace}`);
|
|
10974
|
-
const r2 = await api(
|
|
10974
|
+
const r2 = await api(path2, { method: "DELETE", body: { workspace: target.workspace, id: arg } });
|
|
10975
10975
|
return out(r2, () => `${lavender("\u2713")} ${t("Canceled.", "Cancelado.")}`);
|
|
10976
10976
|
}
|
|
10977
10977
|
if (sub !== "add") throw usage(t(`Unknown schedule subcommand: ${sub ?? "(none)"}.`, `Subcomando de schedule desconocido: ${sub ?? "(ninguno)"}.`), "ghosty schedule add|ls|rm <agent-id> \u2026 --teams workspace#room[/thread]");
|
|
@@ -10981,7 +10981,7 @@ async function teamsSchedule(sub, agent, arg, v) {
|
|
|
10981
10981
|
if (v.at && v.in) throw usage(t("Use --at or --in, not both.", "Usa --at o --in, no ambos."));
|
|
10982
10982
|
if (v.every) throw usage(t("--every is not supported with --teams yet.", "--every todav\xEDa no va con --teams."));
|
|
10983
10983
|
const dueAt = v.in ? new Date(Date.now() + minutesOf(v.in, "--in") * 6e4).toISOString() : isoOf(v.at, "--at");
|
|
10984
|
-
const r = await api(
|
|
10984
|
+
const r = await api(path2, { method: "POST", body: { workspace: target.workspace, channel: target.channel, ...target.parentId ? { parentId: target.parentId } : {}, text: arg, dueAt } });
|
|
10985
10985
|
out(r, () => `${lavender("\u2713")} ${t(`@${r.handle} will answer in #${r.channel}${r.parentId ? ` (thread ${r.parentId})` : ""} on ${when(r.dueAt)}.`, `@${r.handle} contestar\xE1 en #${r.channel}${r.parentId ? ` (hilo ${r.parentId})` : ""} el ${when(r.dueAt)}.`)} ${dim(`id ${r.id} \xB7 ${t("cancel", "cancelar")}: ghosty schedule rm ${agent} ${r.id} --teams ${target.workspace}`)}`);
|
|
10986
10986
|
}
|
|
10987
10987
|
async function scheduleCmd(sub, agent, arg, v) {
|
|
@@ -11172,7 +11172,7 @@ ${dim("The same gpk_/gps_ pair signs POST /api/v2/fleet-agents/<id>/message-stre
|
|
|
11172
11172
|
${dim("Docs: https://www.ghosty.studio/docs/cli/partners")}`,
|
|
11173
11173
|
`${bold("Uso:")} ghosty partner <comando> [opciones]
|
|
11174
11174
|
|
|
11175
|
-
${bold("Espacio de partner")} ${dim("(t\xFA, con sesi\xF3n como due\
|
|
11175
|
+
${bold("Espacio de partner")} ${dim("(t\xFA, con sesi\xF3n como due\xF1o del espacio)")}
|
|
11176
11176
|
partner ls Tus espacios de partner
|
|
11177
11177
|
partner enable <workspace> Convierte uno de tus espacios en espacio de partner
|
|
11178
11178
|
partner credentials ls Credenciales de servidor (sin secretos)
|
|
@@ -11223,35 +11223,35 @@ function partnerCreds() {
|
|
|
11223
11223
|
const secret = process.env.GHOSTY_PARTNER_KEY_SECRET || process.env.GHOSTY_PARTNER_SECRET;
|
|
11224
11224
|
return key && secret ? { key, secret } : null;
|
|
11225
11225
|
}
|
|
11226
|
-
async function call(
|
|
11226
|
+
async function call(path2, v, init = {}) {
|
|
11227
11227
|
const method = init.method ?? (init.body !== void 0 ? "POST" : "GET");
|
|
11228
11228
|
const raw = init.body !== void 0 ? JSON.stringify(init.body) : "";
|
|
11229
11229
|
const creds = init.ownerOnly ? null : partnerCreds();
|
|
11230
11230
|
if (creds) {
|
|
11231
11231
|
const ts = String(Math.floor(Date.now() / 1e3));
|
|
11232
11232
|
const sig = createHmac("sha256", creds.secret).update(`${ts}.${creds.key}.${raw}`).digest("hex");
|
|
11233
|
-
const res = await fetch(`${await baseUrl()}${
|
|
11233
|
+
const res = await fetch(`${await baseUrl()}${path2}`, {
|
|
11234
11234
|
method,
|
|
11235
11235
|
headers: {
|
|
11236
11236
|
"x-ghosty-key": creds.key,
|
|
11237
11237
|
"x-ghosty-ts": ts,
|
|
11238
11238
|
"x-ghosty-sig": sig,
|
|
11239
11239
|
"content-type": "application/json",
|
|
11240
|
-
"user-agent": `ghosty-cli/${"0.
|
|
11240
|
+
"user-agent": `ghosty-cli/${"0.86.0"}`
|
|
11241
11241
|
},
|
|
11242
11242
|
body: method === "GET" ? void 0 : raw
|
|
11243
11243
|
});
|
|
11244
11244
|
if (!res.ok) throw await toError(res);
|
|
11245
11245
|
return res;
|
|
11246
11246
|
}
|
|
11247
|
-
return request(
|
|
11247
|
+
return request(path2, {
|
|
11248
11248
|
method,
|
|
11249
11249
|
...init.body !== void 0 ? { body: init.body } : {},
|
|
11250
11250
|
headers: v.workspace ? { "x-ghosty-workspace": v.workspace } : {}
|
|
11251
11251
|
});
|
|
11252
11252
|
}
|
|
11253
|
-
async function json(
|
|
11254
|
-
const res = await call(
|
|
11253
|
+
async function json(path2, v, init = {}) {
|
|
11254
|
+
const res = await call(path2, v, init);
|
|
11255
11255
|
const text = await res.text();
|
|
11256
11256
|
return text ? JSON.parse(text) : void 0;
|
|
11257
11257
|
}
|
|
@@ -11335,8 +11335,8 @@ async function tenantsCmd(sub, id, v) {
|
|
|
11335
11335
|
return out(r, () => r.tenants.length ? table(r.tenants.map((tn) => ({ externalId: tn.externalId, config: JSON.stringify(tn.config ?? {}), updated: fmt(tn.updatedAt) })), ["externalId", "config", "updated"]) : dim(t("No tenants yet: each one appears on its first turn, or create it with ghosty partner tenants set <externalId> --config '{\u2026}'", "Todav\xEDa no hay tenants: cada uno aparece en su primer turno, o cr\xE9alo con ghosty partner tenants set <externalId> --config '{\u2026}'")));
|
|
11336
11336
|
}
|
|
11337
11337
|
if (!id) throw usage(t("Missing externalId.", "Falta el externalId."), `${t("Example", "Ejemplo")}: ghosty partner tenants ${sub} org_123`);
|
|
11338
|
-
const
|
|
11339
|
-
if (sub === "get") return out(await json(
|
|
11338
|
+
const path2 = `/api/v2/partner/tenants/${enc7(id)}`;
|
|
11339
|
+
if (sub === "get") return out(await json(path2, v));
|
|
11340
11340
|
if (sub === "set" || sub === "apply") {
|
|
11341
11341
|
if (!v.config) throw usage(t("Missing --config.", "Falta --config."), `${t("Example", "Ejemplo")}: --config '{"name":"Spa","timezone":"America/Mexico_City"}'`);
|
|
11342
11342
|
let config;
|
|
@@ -11345,11 +11345,11 @@ async function tenantsCmd(sub, id, v) {
|
|
|
11345
11345
|
} catch {
|
|
11346
11346
|
throw usage(t("--config must be JSON.", "--config debe ser JSON."));
|
|
11347
11347
|
}
|
|
11348
|
-
const r = await json(
|
|
11348
|
+
const r = await json(path2, v, { method: "PUT", body: { config } });
|
|
11349
11349
|
return out(r, () => `${lavender("\u2713")} ${r.created ? t("Created", "Creado") : r.changed ? t("Updated", "Actualizado") : t("No changes", "Sin cambios")} ${id}`);
|
|
11350
11350
|
}
|
|
11351
11351
|
if (sub === "rm" || sub === "delete") {
|
|
11352
|
-
const r = await json(
|
|
11352
|
+
const r = await json(path2, v, { method: "DELETE" });
|
|
11353
11353
|
return out(r, () => r.deleted ? `${lavender("\u2713")} ${t(`Deleted ${id} (${r.conversations} conversations)`, `${id} borrado (${r.conversations} conversaciones)`)}` : dim(t(`${id} did not exist.`, `${id} no exist\xEDa.`)));
|
|
11354
11354
|
}
|
|
11355
11355
|
throw usage(t(`Unknown tenants subcommand: ${sub}.`, `Subcomando de tenants desconocido: ${sub}.`), t("Try", "Prueba") + ": ghosty partner tenants ls | get | set | rm");
|
|
@@ -11571,9 +11571,110 @@ async function imageCmd(sub, a1, a2, v) {
|
|
|
11571
11571
|
);
|
|
11572
11572
|
}
|
|
11573
11573
|
|
|
11574
|
+
// src/commands/tts.ts
|
|
11575
|
+
init_api();
|
|
11576
|
+
init_util();
|
|
11577
|
+
init_i18n();
|
|
11578
|
+
import { writeFile as writeFile7 } from "node:fs/promises";
|
|
11579
|
+
var MAX_CHARS = 400;
|
|
11580
|
+
var VOICES = ["em_santa", "em_alex", "ef_dora"];
|
|
11581
|
+
async function ttsCmd(text, v) {
|
|
11582
|
+
const ex = 'ghosty tts "Hola, gracias por escribir." [--voice em_santa|em_alex|ef_dora] [--out voz.wav]';
|
|
11583
|
+
const said = text?.trim();
|
|
11584
|
+
if (!said) throw usage(t("Missing the text to say.", "Falta el texto a decir."), ex);
|
|
11585
|
+
if (said.length > MAX_CHARS) throw usage(t(`Too long: ${said.length} characters (max ${MAX_CHARS}). Split it into sentences.`, `Demasiado largo: ${said.length} caracteres (m\xE1x. ${MAX_CHARS}). P\xE1rtelo en frases.`), ex);
|
|
11586
|
+
const voice = v.voice ?? "em_santa";
|
|
11587
|
+
if (!VOICES.includes(voice)) throw usage(t(`Unknown voice: ${voice}.`, `Voz desconocida: ${voice}.`), `${t("Voices", "Voces")}: ${VOICES.join(", ")}`);
|
|
11588
|
+
const res = await request("/api/v2/me/tts", { body: { text: said, voice } });
|
|
11589
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
11590
|
+
const file = v.out ?? "voz.wav";
|
|
11591
|
+
await writeFile7(file, bytes);
|
|
11592
|
+
const durationMs = Number(res.headers.get("x-duration-ms") ?? 0);
|
|
11593
|
+
return out({ saved: file, voice, bytes: bytes.length, durationMs }, () => `${lavender("\u2713")} ${file} (${(durationMs / 1e3).toFixed(1)} s) ${dim(t(`\xB7 play it: afplay ${file}`, `\xB7 esc\xFAchalo: afplay ${file}`))}`);
|
|
11594
|
+
}
|
|
11595
|
+
|
|
11596
|
+
// src/commands/watch.ts
|
|
11597
|
+
init_api();
|
|
11598
|
+
init_util();
|
|
11599
|
+
init_i18n();
|
|
11600
|
+
var enc8 = encodeURIComponent;
|
|
11601
|
+
var path = (agent, rest = "") => `/api/v2/me/agents/${enc8(agent)}/watches${rest}`;
|
|
11602
|
+
var short2 = (s2, n = 50) => s2.length > n ? s2.slice(0, n - 1) + "\u2026" : s2;
|
|
11603
|
+
async function watchCmd(sub, agent, arg, v) {
|
|
11604
|
+
if (!agent) throw usage(t("Missing agent id.", "Falta el id del agente."), "ghosty watch ls <agent-id>");
|
|
11605
|
+
if (sub === "ls" || sub === "list" || !sub) {
|
|
11606
|
+
const r = await api(path(agent));
|
|
11607
|
+
const rows = r.watches.map((w) => ({
|
|
11608
|
+
id: w.id,
|
|
11609
|
+
page: short2(w.label ?? w.url),
|
|
11610
|
+
every: `${w.everyHours}h`,
|
|
11611
|
+
state: w.state + (w.failures ? ` (${w.failures}\u2717)` : ""),
|
|
11612
|
+
changed: w.lastChangedAt ?? "",
|
|
11613
|
+
checked: w.lastCheckedAt ?? "",
|
|
11614
|
+
conversation: w.sessionId
|
|
11615
|
+
}));
|
|
11616
|
+
return out(r, () => rows.length ? table(rows, ["id", "page", "every", "state", "changed", "checked", "conversation"]) : dim(t("No watched pages.", "No hay p\xE1ginas vigiladas.")));
|
|
11617
|
+
}
|
|
11618
|
+
if (sub === "add") {
|
|
11619
|
+
if (!arg) throw usage(t("Missing the URL.", "Falta la URL."), 'ghosty watch add <agent-id> https://competencia.mx/precios --every 24h --focus "Precios"');
|
|
11620
|
+
const body = {
|
|
11621
|
+
url: arg,
|
|
11622
|
+
...v.every ? { everyHours: minutesOf(v.every, "--every") / 60 } : {},
|
|
11623
|
+
...v.focus ? { focus: v.focus } : {},
|
|
11624
|
+
...v.instruction ? { instruction: v.instruction } : {},
|
|
11625
|
+
...v.label ? { label: v.label } : {},
|
|
11626
|
+
...v.days ? { days: Number(v.days) } : {}
|
|
11627
|
+
};
|
|
11628
|
+
let sid = v.conversation;
|
|
11629
|
+
const created = !sid;
|
|
11630
|
+
if (!sid) {
|
|
11631
|
+
sid = (await api(convPath(agent), { method: "POST", body: {} })).id;
|
|
11632
|
+
if (!globals.json) info(dim(t(`new conversation ${sid}`, `conversaci\xF3n nueva ${sid}`)));
|
|
11633
|
+
}
|
|
11634
|
+
let r;
|
|
11635
|
+
try {
|
|
11636
|
+
r = await api(path(agent), { body: { ...body, sessionId: sid } });
|
|
11637
|
+
} catch (e) {
|
|
11638
|
+
if (created) await api(convPath(agent, `/${enc8(sid)}`), { method: "DELETE" }).catch(() => {
|
|
11639
|
+
});
|
|
11640
|
+
throw e;
|
|
11641
|
+
}
|
|
11642
|
+
if (v.title && created) await api(convPath(agent, `/${enc8(sid)}`), { method: "PATCH", body: { title: v.title } });
|
|
11643
|
+
const w = r.watch;
|
|
11644
|
+
return out(
|
|
11645
|
+
r,
|
|
11646
|
+
() => `${lavender("\u2713")} ${t(`Watching ${w.url} every ${w.everyHours} h until ${w.expiresAt}`, `Vigilando ${w.url} cada ${w.everyHours} h hasta ${w.expiresAt}`)}
|
|
11647
|
+
${dim(`id ${w.id} \xB7 ${t("conversation", "conversaci\xF3n")} ${w.sessionId} \xB7 ${t("try it", "pru\xE9bala")}: ghosty watch check ${agent} ${w.id} --dry-run`)}`
|
|
11648
|
+
);
|
|
11649
|
+
}
|
|
11650
|
+
if (!arg) throw usage(t("Missing the watch id.", "Falta el id de la vigilancia."), t("See them with", "M\xEDralas con") + `: ghosty watch ls ${agent}`);
|
|
11651
|
+
if (sub === "rm" || sub === "remove") {
|
|
11652
|
+
const r = await api(path(agent, `/${enc8(arg)}`), { method: "DELETE" });
|
|
11653
|
+
return out(r, () => `${lavender("\u2713")} ${t("Stopped watching", "Ya no se vigila")} ${arg}`);
|
|
11654
|
+
}
|
|
11655
|
+
if (sub === "check") {
|
|
11656
|
+
const dry = !!v["dry-run"];
|
|
11657
|
+
const r = await api(path(agent, `/${enc8(arg)}`), { body: { action: "check", ...dry ? { dryRun: true } : {} } });
|
|
11658
|
+
if (!dry) return out(r, () => `${lavender("\u2713")} ${t("It will be checked in the next sweep (\u2264 30 s); if it changed, the agent gets a turn.", "Se revisa en el siguiente barrido (\u2264 30 s); si cambi\xF3, el agente recibe un turno.")}`);
|
|
11659
|
+
const label = {
|
|
11660
|
+
baseline: t("no snapshot yet: the first check takes it", "a\xFAn sin foto: la primera revisi\xF3n la toma"),
|
|
11661
|
+
same: t("unchanged", "sin cambios"),
|
|
11662
|
+
changed: t("CHANGED (in dry-run nothing is saved or sent)", "CAMBI\xD3 (en dry-run no se guarda ni se avisa)"),
|
|
11663
|
+
failed: t("could not read the page", "no se pudo leer la p\xE1gina"),
|
|
11664
|
+
skipped: t("skipped (daily web cap)", "saltada (tope diario de web)")
|
|
11665
|
+
};
|
|
11666
|
+
return out(r, () => [`${lavender("\u2022")} ${label[r.status ?? ""] ?? r.status}${r.chars != null ? dim(` \xB7 ${r.chars} ${t("chars", "caracteres")}`) : ""}`, r.error ? dim(r.error) : "", r.diff ?? ""].filter(Boolean).join("\n"));
|
|
11667
|
+
}
|
|
11668
|
+
if (sub === "resume") {
|
|
11669
|
+
const r = await api(path(agent, `/${enc8(arg)}`), { body: { action: "resume", ...v.days ? { days: Number(v.days) } : {} } });
|
|
11670
|
+
return out(r, () => `${lavender("\u2713")} ${t("Resumed", "Reanudada")} ${arg}`);
|
|
11671
|
+
}
|
|
11672
|
+
throw usage(t(`Unknown watch subcommand: ${sub}.`, `Subcomando de watch desconocido: ${sub}.`), "ghosty watch ls|add|rm|check|resume <agent-id> \u2026");
|
|
11673
|
+
}
|
|
11674
|
+
|
|
11574
11675
|
// src/commands/data.ts
|
|
11575
11676
|
init_api();
|
|
11576
|
-
import { readdir as readdir2, readFile as readFile6, writeFile as
|
|
11677
|
+
import { readdir as readdir2, readFile as readFile6, writeFile as writeFile8 } from "node:fs/promises";
|
|
11577
11678
|
import { basename as basename3, join as join5 } from "node:path";
|
|
11578
11679
|
|
|
11579
11680
|
// src/rows.ts
|
|
@@ -11597,8 +11698,8 @@ var describeRowScope = (scope) => Object.entries(scope ?? {}).map(([t2, c]) => `
|
|
|
11597
11698
|
// src/commands/data.ts
|
|
11598
11699
|
init_util();
|
|
11599
11700
|
init_i18n();
|
|
11600
|
-
var
|
|
11601
|
-
var agentPath2 = (id, rest = "") => `/api/v2/agents/${
|
|
11701
|
+
var enc9 = encodeURIComponent;
|
|
11702
|
+
var agentPath2 = (id, rest = "") => `/api/v2/agents/${enc9(id)}${rest}`;
|
|
11602
11703
|
var need2 = (v, what, example) => {
|
|
11603
11704
|
if (!v) throw usage(t(`Missing ${what}.`, `Falta ${what}.`), `${t("Example", "Ejemplo")}: ${example}`);
|
|
11604
11705
|
return v;
|
|
@@ -11653,15 +11754,15 @@ async function dbsImport(id, db, v) {
|
|
|
11653
11754
|
const easybitsKey = process.env.EASYBITS_API_KEY;
|
|
11654
11755
|
if (!easybitsKey) throw usage(t("EASYBITS_API_KEY is not set.", "EASYBITS_API_KEY no est\xE1 definida."), t("Export your EasyBits API key (https://www.easybits.cloud/dash/developer) and run again.", "Exporta tu API key de EasyBits (https://www.easybits.cloud/dash/developer) y vuelve a correrlo."));
|
|
11655
11756
|
const body = { from: "easybits", sourceDb: m[1], easybitsKey, mode: v.append ? "append" : "replace", ...v.tables ? { tables: csv(v.tables) } : {}, ...v["rehost-images"] ? { rehost: csv(v["rehost-images"]) } : {} };
|
|
11656
|
-
const
|
|
11657
|
-
const dry = await api(
|
|
11757
|
+
const path2 = agentPath2(id, `/dbs/${enc9(db)}/import`);
|
|
11758
|
+
const dry = await api(path2, { body: { ...body, dryRun: true } });
|
|
11658
11759
|
if (v["dry-run"]) return out({ ...dry, shrinking: shrinking(dry.plan).map((p) => p.table) }, () => `${dry.db} \u2190 easybits:${m[1]}
|
|
11659
11760
|
${planText(dry.plan)}${shrinkWarnings(dry.plan)}${importWarnings(dry.warnings)}${gsOnlyWarnings(dry.gsOnly)}
|
|
11660
11761
|
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
11661
11762
|
const destructive = dry.plan.some((p) => p.action === "replace" && p.targetRows > 0);
|
|
11662
11763
|
if (destructive) await confirm(`${planText(dry.plan)}${shrinkWarnings(dry.plan)}${importWarnings(dry.warnings)}${gsOnlyWarnings(dry.gsOnly)}
|
|
11663
11764
|
${t(`Replace the rows above in ${dry.db}?`, `\xBFReemplazar las filas de arriba en ${dry.db}?`)}`, { yes: v.yes, ...shrinking(dry.plan).length ? { expect: dry.db } : {}, retry: `ghosty dbs import ${id} ${db} --from ${v.from}${v.tables ? ` --tables ${v.tables}` : ""}` });
|
|
11664
|
-
const r = await api(
|
|
11765
|
+
const r = await api(path2, { body });
|
|
11665
11766
|
const img = r.rehosted ? `
|
|
11666
11767
|
${t(`${r.rehosted.copied} image link(s) now permanent`, `${r.rehosted.copied} liga(s) de imagen ya permanentes`)}${r.rehosted.failed.length ? `, ${red(String(r.rehosted.failed.length))} ${t("kept as they were", "se quedaron como estaban")}:
|
|
11667
11768
|
${r.rehosted.failed.map((f) => ` ${f.url} ${dim(f.error)}`).join("\n")}` : ""}` : "";
|
|
@@ -11672,8 +11773,8 @@ async function dbsRehost(id, db, v) {
|
|
|
11672
11773
|
const ex = "ghosty dbs rehost <agent-id> <db> --table catalogo --column imagen_url [--dry-run]";
|
|
11673
11774
|
const table2 = need2(v.table, "--table", ex);
|
|
11674
11775
|
const column = need2(v.column, "--column", ex);
|
|
11675
|
-
const
|
|
11676
|
-
const dry = await api(
|
|
11776
|
+
const path2 = agentPath2(id, `/dbs/${enc9(db)}/rehost`);
|
|
11777
|
+
const dry = await api(path2, { body: { table: table2, column, dryRun: true } });
|
|
11677
11778
|
const head = `${dry.db} \xB7 ${table2}.${column}: ${t(`${dry.distinct} distinct link(s), ${dry.alreadyOurs} already permanent, ${dry.toCopy} to copy`, `${dry.distinct} liga(s) distintas, ${dry.alreadyOurs} ya permanentes, ${dry.toCopy} por copiar`)}`;
|
|
11678
11779
|
if (v["dry-run"] || !dry.toCopy) return out(dry, () => `${head}
|
|
11679
11780
|
${dim(v["dry-run"] ? t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada.") : t("Nothing to do.", "No hay nada que hacer."))}`);
|
|
@@ -11681,7 +11782,7 @@ ${dim(v["dry-run"] ? t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3
|
|
|
11681
11782
|
const total = { copied: 0, reused: 0, failed: [], warnings: [] };
|
|
11682
11783
|
let after;
|
|
11683
11784
|
for (; ; ) {
|
|
11684
|
-
const r = await api(
|
|
11785
|
+
const r = await api(path2, { body: { table: table2, column, ...after ? { after } : {} } });
|
|
11685
11786
|
total.copied += r.copied ?? 0;
|
|
11686
11787
|
total.reused += r.reused ?? 0;
|
|
11687
11788
|
total.failed.push(...r.failed ?? []);
|
|
@@ -11702,7 +11803,7 @@ ${total.failed.map((f) => ` ${f.url} ${dim(f.error)}`).join("\n")}` : ""
|
|
|
11702
11803
|
);
|
|
11703
11804
|
}
|
|
11704
11805
|
async function dbsRename(id, db, name, dryRun) {
|
|
11705
|
-
const r = await api(agentPath2(id, `/dbs/${
|
|
11806
|
+
const r = await api(agentPath2(id, `/dbs/${enc9(db)}/rename`), { body: { name, dryRun } });
|
|
11706
11807
|
const refs = r.references.flatMap((x) => [
|
|
11707
11808
|
` ${bold(x.agent)} ${x.where}${x.derived ? dim(t(" (derived: follows its base)", " (derivado: sigue a su base)")) : ""}`,
|
|
11708
11809
|
...x.refs.slice(0, 3).map((l) => dim(` ${x.where === "prompt" || x.where.startsWith("skill:") ? `${l.line}: ` : ""}${l.text}`)),
|
|
@@ -11719,8 +11820,8 @@ ${dim(t("Dry run: nothing changed. Without --dry-run those mentions are rewritte
|
|
|
11719
11820
|
return out(r, () => [`${lavender("\u2713")} ${head}. ${t(`${r.rewritten.length} mention(s) rewritten.`, `${r.rewritten.length} menci\xF3n(es) reescritas.`)}`, ...failed, dim(`${t("Undo", "Deshacer")}: ghosty dbs rename ${id} ${r.to} ${r.from}`)].join("\n"));
|
|
11720
11821
|
}
|
|
11721
11822
|
async function dbsRm(id, db, v) {
|
|
11722
|
-
const
|
|
11723
|
-
const dry = await api(
|
|
11823
|
+
const path2 = agentPath2(id, `/dbs/${enc9(db)}/delete`);
|
|
11824
|
+
const dry = await api(path2, { body: { dryRun: true } });
|
|
11724
11825
|
const rows = dry.tables.reduce((n, x) => n + x.rows, 0);
|
|
11725
11826
|
const plan = [
|
|
11726
11827
|
`${bold(dry.name)}: ${t(`${dry.tables.length} table(s), ${rows} row(s)`, `${dry.tables.length} tabla(s), ${rows} fila(s)`)}`,
|
|
@@ -11730,7 +11831,7 @@ async function dbsRm(id, db, v) {
|
|
|
11730
11831
|
${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
11731
11832
|
await confirm(`${plan}
|
|
11732
11833
|
${t(`Delete ${dry.name} for good? This cannot be undone.`, `\xBFBorrar ${dry.name} para siempre? No se puede deshacer.`)}`, { yes: v.yes, expect: dry.name, retry: `ghosty dbs rm ${id} ${db}` });
|
|
11733
|
-
const r = await api(
|
|
11834
|
+
const r = await api(path2, { body: {} });
|
|
11734
11835
|
return out(r, () => `${lavender("\u2713")} ${t(`${r.name} deleted.`, `${r.name} borrada.`)}`);
|
|
11735
11836
|
}
|
|
11736
11837
|
async function dbsExport(id, db, v) {
|
|
@@ -11738,11 +11839,11 @@ async function dbsExport(id, db, v) {
|
|
|
11738
11839
|
if (format !== "sqlite" && format !== "csv") throw usage(t("--format takes sqlite or csv.", "--format acepta sqlite o csv."), `ghosty dbs export ${id} ${db} --format csv --table <table>`);
|
|
11739
11840
|
if (format === "csv" && !v.table) throw usage(t("A CSV is one table: add --table.", "Un CSV es de una tabla: agrega --table."), `ghosty dbs export ${id} ${db} --table <table> (ghosty dbs tables ${id} ${db})`);
|
|
11740
11841
|
const q = new URLSearchParams({ format, ...v.table ? { table: v.table } : {} });
|
|
11741
|
-
const res = await request(agentPath2(id, `/dbs/${
|
|
11842
|
+
const res = await request(agentPath2(id, `/dbs/${enc9(db)}/export?${q}`));
|
|
11742
11843
|
const suggested = /filename="([^"]+)"/.exec(res.headers.get("content-disposition") ?? "")?.[1] ?? (format === "csv" ? `${db}-${v.table}.csv` : `${db}.sqlite`);
|
|
11743
11844
|
const file = v.out ?? suggested;
|
|
11744
11845
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
11745
|
-
await
|
|
11846
|
+
await writeFile8(file, bytes);
|
|
11746
11847
|
const countsHeader = res.headers.get("x-gs-row-counts");
|
|
11747
11848
|
const counts = countsHeader ? JSON.parse(Buffer.from(countsHeader, "base64").toString("utf8")) : {};
|
|
11748
11849
|
const rows = Object.entries(counts).map(([tb, n]) => ({ table: tb, rows: n }));
|
|
@@ -11768,7 +11869,7 @@ ${dim(`${t("Delete one", "Borrar una")}: ghosty dbs rm ${id} <db> --dry-run`)}`
|
|
|
11768
11869
|
}
|
|
11769
11870
|
case "tables": {
|
|
11770
11871
|
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs tables <agent-id> <db>");
|
|
11771
|
-
const r = await api(agentPath2(id, `/dbs/${
|
|
11872
|
+
const r = await api(agentPath2(id, `/dbs/${enc9(db)}`));
|
|
11772
11873
|
const ext = new Set(r.externalWriteTables);
|
|
11773
11874
|
const own = r.externalRowScope ?? {};
|
|
11774
11875
|
const rows = r.tables.map((tb) => ({ table: tb.name, rows: tb.rows, "customer writes": ext.has(tb.name) ? "yes" : "", "customer sees": own[tb.name] ? `own rows (${own[tb.name].join(", ")})` : "", columns: tb.columns.map((c) => `${c.name}${c.pk ? "*" : ""}`).join(", ") }));
|
|
@@ -11787,7 +11888,7 @@ ${table(rows, ["table", "rows", "customer writes", "customer sees", "columns"])}
|
|
|
11787
11888
|
case "sql": {
|
|
11788
11889
|
db = need2(db, t("database (id or name)", "la base (id o nombre)"), 'ghosty dbs query <agent-id> <db> "SELECT \u2026"');
|
|
11789
11890
|
const sql = need2(rest.join(" ").trim() || void 0, t("SQL", "el SQL"), 'ghosty dbs query <agent-id> <db> "SELECT * FROM t LIMIT 10"');
|
|
11790
|
-
const r = await api(agentPath2(id, `/dbs/${
|
|
11891
|
+
const r = await api(agentPath2(id, `/dbs/${enc9(db)}/query`), { body: { sql, args: v.arg ?? [], write: v.write === true } }).catch((e) => {
|
|
11791
11892
|
if (e instanceof CliError && !v.write && /lectura|SELECT|escribir/i.test(e.message)) throw new CliError(e.message, EXIT.API, t("To write on purpose, add --write.", "Para escribir a prop\xF3sito, agrega --write."));
|
|
11792
11893
|
throw e;
|
|
11793
11894
|
});
|
|
@@ -11804,13 +11905,13 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
|
|
|
11804
11905
|
if (v.table) q.set("table", v.table);
|
|
11805
11906
|
if (v.since) q.set("since", v.since);
|
|
11806
11907
|
if (v.limit) q.set("limit", v.limit);
|
|
11807
|
-
const r = await api(agentPath2(id, `/dbs/${
|
|
11908
|
+
const r = await api(agentPath2(id, `/dbs/${enc9(db)}/origin${q.size ? `?${q}` : ""}`));
|
|
11808
11909
|
const rows = r.writes.map((w) => ({ when: w.created_at, agent: w.agent_id.slice(0, 10), channel: w.canal ?? "", what: `${w.op} ${w.table_name}${w.row_id != null ? ` #${w.row_id}` : ""}`, customer: w.phone10 ?? "", why: w.reason ?? "" }));
|
|
11809
11910
|
return out(r, () => rows.length ? table(rows, ["when", "agent", "channel", "what", "customer", "why"]) : dim(t("No agent writes recorded yet in this database.", "Todav\xEDa no hay escrituras de agentes registradas en esta base.")));
|
|
11810
11911
|
}
|
|
11811
11912
|
case "checkpoints": {
|
|
11812
11913
|
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs checkpoints <agent-id> <db>");
|
|
11813
|
-
const r = await api(agentPath2(id, `/dbs/${
|
|
11914
|
+
const r = await api(agentPath2(id, `/dbs/${enc9(db)}/checkpoints`));
|
|
11814
11915
|
const rows = r.checkpoints.map((c) => ({ id: c.id, when: c.at, rows: Object.values(c.counts).reduce((a, b) => a + b, 0), why: c.reason }));
|
|
11815
11916
|
return out(r, () => rows.length ? table(rows, ["id", "when", "rows", "why"]) : dim(t("No checkpoints yet: one is taken before the first write of each agent turn.", "Todav\xEDa no hay checkpoints: se toma uno antes de la primera escritura de cada turno de un agente.")));
|
|
11816
11917
|
}
|
|
@@ -11818,7 +11919,7 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
|
|
|
11818
11919
|
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs restore <agent-id> <db> <checkpoint-id> [--yes]");
|
|
11819
11920
|
const ckpt = need2(rest[0], t("checkpoint id (from dbs checkpoints)", "el id del checkpoint (de dbs checkpoints)"), `ghosty dbs restore ${id} ${db} <checkpoint-id>`);
|
|
11820
11921
|
await confirm(t(`Return every table of ${db} to checkpoint ${ckpt}? A new checkpoint is taken first, so you can undo it.`, `\xBFRegresar todas las tablas de ${db} al checkpoint ${ckpt}? Antes se toma otro, as\xED que se puede deshacer.`), { yes: v.yes, retry: `ghosty dbs restore ${id} ${db} ${ckpt}` });
|
|
11821
|
-
const r = await api(agentPath2(id, `/dbs/${
|
|
11922
|
+
const r = await api(agentPath2(id, `/dbs/${enc9(db)}/checkpoints`), { method: "POST", body: { checkpointId: ckpt } });
|
|
11822
11923
|
return out(r, () => `${lavender("\u2713")} ${t(`${r.db} returned to ${r.restoredTo.at}.`, `${r.db} regresada a ${r.restoredTo.at}.`)} ${dim(r.undo ? t(`Undo: ghosty dbs restore ${id} ${db} ${r.undo.id}`, `Para deshacer: ghosty dbs restore ${id} ${db} ${r.undo.id}`) : r.warning ?? "")}`);
|
|
11823
11924
|
}
|
|
11824
11925
|
case "grant": {
|
|
@@ -11833,7 +11934,7 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
|
|
|
11833
11934
|
...v["external-write"] !== void 0 ? { externalWriteTables: csv(v["external-write"]) } : {},
|
|
11834
11935
|
...rowScope !== void 0 ? { externalRowScope: rowScope } : {}
|
|
11835
11936
|
};
|
|
11836
|
-
const r = await api(agentPath2(id, `/dbs/${
|
|
11937
|
+
const r = await api(agentPath2(id, `/dbs/${enc9(db)}`), { method: "PUT", body });
|
|
11837
11938
|
const own = describeRowScope(r.externalRowScope);
|
|
11838
11939
|
return out(r, () => `${lavender("\u2713")} ${r.name} ${t("granted.", "concedida.")} ${dim(t(`From customer channels it writes to: ${r.externalWriteTables.join(", ") || "nothing (read only)"}.${own ? ` Each customer sees only their own rows of: ${own}.` : ""}`, `Desde canales de clientes escribe en: ${r.externalWriteTables.join(", ") || "nada (s\xF3lo lectura)"}.${own ? ` Cada cliente ve s\xF3lo sus propias filas de: ${own}.` : ""}`))}`);
|
|
11839
11940
|
}
|
|
@@ -11862,7 +11963,7 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
|
|
|
11862
11963
|
}
|
|
11863
11964
|
case "revoke": {
|
|
11864
11965
|
db = need2(db, t("database (id or name)", "la base (id o nombre)"), "ghosty dbs revoke <agent-id> <db>");
|
|
11865
|
-
const r = await api(agentPath2(id, `/dbs/${
|
|
11966
|
+
const r = await api(agentPath2(id, `/dbs/${enc9(db)}`), { method: "DELETE" });
|
|
11866
11967
|
return out(r, () => `${lavender("\u2713")} ${r.name} ${t("revoked.", "revocada.")} ${dim(`${t("Undo", "Deshacer")}: ghosty dbs grant ${id} ${r.name}`)}`);
|
|
11867
11968
|
}
|
|
11868
11969
|
default:
|
|
@@ -11870,9 +11971,9 @@ ${dim(`${t(`${rows.length} row(s)`, `${rows.length} fila(s)`)} \xB7 ${Math.round
|
|
|
11870
11971
|
}
|
|
11871
11972
|
}
|
|
11872
11973
|
async function docsDedupe(v) {
|
|
11873
|
-
const
|
|
11974
|
+
const path2 = "/api/v2/me/documents/dedupe";
|
|
11874
11975
|
const pair = v.keep || v.drop ? { keep: v.keep, drop: v.drop } : {};
|
|
11875
|
-
const plan = await api(
|
|
11976
|
+
const plan = await api(path2, { method: "POST", body: { ...pair, dryRun: true } });
|
|
11876
11977
|
const name = (d) => typeof d === "string" ? d : `\xAB${d.name}\xBB ${dim(`${d.id.slice(0, 10)} \xB7 ${d.agents} ${t("agent(s)", "agente(s)")}`)}`;
|
|
11877
11978
|
const text = [
|
|
11878
11979
|
...plan.merges.map((m) => `${t("keep", "queda")} ${name(m.keep)}
|
|
@@ -11886,7 +11987,7 @@ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
|
11886
11987
|
requireYesIfHeadless(v.yes, retry);
|
|
11887
11988
|
await confirm(`${text}
|
|
11888
11989
|
${t("Merge? The extra copies are deleted and their agents keep access through the one that stays.", "\xBFUnir? Las copias sobrantes se borran y sus agentes siguen leyendo por la que queda.")}`, { yes: v.yes, retry });
|
|
11889
|
-
const r = await api(
|
|
11990
|
+
const r = await api(path2, { method: "POST", body: pair });
|
|
11890
11991
|
out(r, () => `${lavender("\u2713")} ${t(`${r.merged} duplicate(s) merged \xB7 ${r.grantsMoved} permission(s) moved.`, `${r.merged} duplicado(s) unido(s) \xB7 ${r.grantsMoved} permiso(s) movido(s).`)}`);
|
|
11891
11992
|
}
|
|
11892
11993
|
async function agentDocsCmd(sub, id, docs, v) {
|
|
@@ -11916,7 +12017,7 @@ async function agentDocsCmd(sub, id, docs, v) {
|
|
|
11916
12017
|
wanted = docs.map((ref) => ({ ref, body }));
|
|
11917
12018
|
}
|
|
11918
12019
|
const done = [];
|
|
11919
|
-
for (const w of wanted) done.push(await api(agentPath2(id, `/documents/${
|
|
12020
|
+
for (const w of wanted) done.push(await api(agentPath2(id, `/documents/${enc9(w.ref)}`), { method: "PUT", body: w.body }));
|
|
11920
12021
|
return out(done.length === 1 ? done[0] : { granted: done }, () => done.map((r) => `${lavender("\u2713")} ${r.name}: ${r.mode === "write" ? t("reads and replaces it", "lo lee y lo reemplaza") : t("reads it", "lo lee")}${r.deliverable ? t(", and can send it", ", y puede mandarlo") : t(", never sends it", ", nunca lo manda")}.`).join("\n"));
|
|
11921
12022
|
}
|
|
11922
12023
|
case "add": {
|
|
@@ -11934,7 +12035,7 @@ async function agentDocsCmd(sub, id, docs, v) {
|
|
|
11934
12035
|
}
|
|
11935
12036
|
case "revoke": {
|
|
11936
12037
|
doc = need2(doc, t("document (id or exact name)", "el documento (id o nombre exacto)"), 'ghosty agents docs revoke <agent-id> "Cat\xE1logo.pdf"');
|
|
11937
|
-
const r = await api(agentPath2(id, `/documents/${
|
|
12038
|
+
const r = await api(agentPath2(id, `/documents/${enc9(doc)}`), { method: "DELETE" });
|
|
11938
12039
|
return out(r, () => `${lavender("\u2713")} ${r.name} ${t("revoked.", "revocado.")} ${dim(`${t("Undo", "Deshacer")}: ghosty agents docs grant ${id} "${r.name}"`)}`);
|
|
11939
12040
|
}
|
|
11940
12041
|
case "replace": {
|
|
@@ -11945,13 +12046,13 @@ async function agentDocsCmd(sub, id, docs, v) {
|
|
|
11945
12046
|
...v.url ? { url: v.url } : { contentBase64: (await readFile6(v.file)).toString("base64"), ...mimeField(v.file) },
|
|
11946
12047
|
...v.note ? { note: v.note } : {}
|
|
11947
12048
|
};
|
|
11948
|
-
const r = await api(agentPath2(id, `/documents/${
|
|
12049
|
+
const r = await api(agentPath2(id, `/documents/${enc9(doc)}/versions`), { method: "POST", body });
|
|
11949
12050
|
return out(r, () => `${lavender("\u2713")} ${r.name}: ${t("version", "versi\xF3n")} ${r.version?.n ?? "?"} ${dim(`(${r.size} bytes)`)}. ${dim(`${t("Undo", "Deshacer")}: ghosty agents docs restore ${id} "${r.name}" ${(r.version?.n ?? 2) - 1}`)}`);
|
|
11950
12051
|
}
|
|
11951
12052
|
case "history":
|
|
11952
12053
|
case "versions": {
|
|
11953
12054
|
doc = need2(doc, t("document (id or exact name)", "el documento (id o nombre exacto)"), 'ghosty agents docs history <agent-id> "Cat\xE1logo.pdf"');
|
|
11954
|
-
const r = await api(agentPath2(id, `/documents/${
|
|
12055
|
+
const r = await api(agentPath2(id, `/documents/${enc9(doc)}/versions`));
|
|
11955
12056
|
const rows = r.versions.map((x) => ({ v: `${x.n}${x.current ? "*" : ""}`, when: x.createdAt.slice(0, 16).replace("T", " "), by: x.agent ?? x.via ?? x.source, size: String(x.size), note: x.note ?? "" }));
|
|
11956
12057
|
const used = r.usedBy.map((u) => `${u.agent} (${u.mode === "write" ? t("updates it", "lo actualiza") + (u.writableFrom.length ? `: ${u.writableFrom.join(", ")}` : "") : t("reads it", "lo lee")})`).join(" \xB7 ");
|
|
11957
12058
|
return out(r, () => `${r.document.name}
|
|
@@ -11963,7 +12064,7 @@ ${dim(t("Used by", "Lo usan"))}: ${used}` : ""}`);
|
|
|
11963
12064
|
doc = need2(doc, t("document (id or exact name)", "el documento (id o nombre exacto)"), ex);
|
|
11964
12065
|
const n = Number(need2(docs[1], t("version number", "el n\xFAmero de versi\xF3n"), ex));
|
|
11965
12066
|
if (!Number.isInteger(n) || n < 1) throw usage(t("The version is a whole number (see docs history).", "La versi\xF3n es un n\xFAmero entero (mira docs history)."), ex);
|
|
11966
|
-
const r = await api(agentPath2(id, `/documents/${
|
|
12067
|
+
const r = await api(agentPath2(id, `/documents/${enc9(doc)}/versions/${n}/restore`), { method: "POST", body: v.note ? { note: v.note } : {} });
|
|
11967
12068
|
return out(r, () => `${lavender("\u2713")} ${r.name}: ${t(`restored from v${n} as version`, `restaurado de la v${n} como versi\xF3n`)} ${r.version?.n ?? "?"}.`);
|
|
11968
12069
|
}
|
|
11969
12070
|
default:
|
|
@@ -12001,7 +12102,7 @@ ${dim(tally)}` : dim(t(`Every customer message${v.since ? ` since ${v.since}` :
|
|
|
12001
12102
|
}
|
|
12002
12103
|
async function turnsJobs(id) {
|
|
12003
12104
|
id = need2(id, t("agent id", "el id del agente"), "ghosty turns jobs <agent-id>");
|
|
12004
|
-
const r = await api(`/api/v2/me/agents/${
|
|
12105
|
+
const r = await api(`/api/v2/me/agents/${enc9(id)}/tasks`);
|
|
12005
12106
|
const rows = jobRows(r.tasks);
|
|
12006
12107
|
out(r, () => rows.length ? `${table(rows, ["id", "kind", "what", "state", "minutes", "delivered", "conversation", "note"])}
|
|
12007
12108
|
${dim(t(`${r.running} running \xB7 stop them: ghosty turns cancel ${id} <conversation>`, `${r.running} corriendo \xB7 detenerlos: ghosty turns cancel ${id} <conversaci\xF3n>`))}` : dim(t("Nothing in the background (last 24 h).", "Nada en segundo plano (\xFAltimas 24 h).")));
|
|
@@ -12023,7 +12124,7 @@ async function turnsCancel(id, sid) {
|
|
|
12023
12124
|
const ex = "ghosty turns cancel <agent-id> <conversation-id>";
|
|
12024
12125
|
id = need2(id, t("agent id", "el id del agente"), ex);
|
|
12025
12126
|
sid = need2(sid, t("conversation id", "el id de la conversaci\xF3n"), ex);
|
|
12026
|
-
const r = await api(`/api/v2/me/agents/${
|
|
12127
|
+
const r = await api(`/api/v2/me/agents/${enc9(id)}/conversations/${enc9(sid)}/cancel`, { method: "POST", body: {} });
|
|
12027
12128
|
const b = r.background ?? { videos: 0, subagents: 0, wakes: 0 };
|
|
12028
12129
|
const bg = b.videos + b.subagents + b.wakes;
|
|
12029
12130
|
out(
|
|
@@ -12082,7 +12183,7 @@ async function turnsGrep(id, text, v) {
|
|
|
12082
12183
|
out(r, () => rows.length ? `${table(rows, ["at", "who", "turn", "state", "session", "text"])}
|
|
12083
12184
|
${dim(`${t("open one", "abre uno")}: ghosty turns show ${id} <turn>`)}` : dim(t(`Nothing says \xAB${text}\xBB${v.since ? ` since ${v.since}` : ""}.`, `Nada dice \xAB${text}\xBB${v.since ? ` desde ${v.since}` : ""}.`)));
|
|
12084
12185
|
}
|
|
12085
|
-
function mimeField(
|
|
12186
|
+
function mimeField(path2) {
|
|
12086
12187
|
const types = {
|
|
12087
12188
|
pdf: "application/pdf",
|
|
12088
12189
|
png: "image/png",
|
|
@@ -12106,12 +12207,12 @@ function mimeField(path) {
|
|
|
12106
12207
|
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
12107
12208
|
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
12108
12209
|
};
|
|
12109
|
-
const mime = types[
|
|
12210
|
+
const mime = types[path2.split(".").pop()?.toLowerCase() ?? ""];
|
|
12110
12211
|
return mime ? { mime } : {};
|
|
12111
12212
|
}
|
|
12112
12213
|
async function turnShow(id, turnId, full = false) {
|
|
12113
12214
|
id = need2(id, t("agent id", "el id del agente"), "ghosty turns show <agent-id> [turn-id|last] [--full]");
|
|
12114
|
-
const r = await api(agentPath2(id, `/turns/${
|
|
12215
|
+
const r = await api(agentPath2(id, `/turns/${enc9(turnId ?? "last")}`));
|
|
12115
12216
|
out(r, () => {
|
|
12116
12217
|
const tu = r.turn;
|
|
12117
12218
|
const clip = (s2) => full || s2.length <= 600 ? s2 : `${s2.slice(0, 600)}\u2026 ${dim(t(`(+${s2.length - 600} chars, --full)`, `(+${s2.length - 600} caracteres, --full)`))}`;
|
|
@@ -12178,7 +12279,7 @@ async function dbsPhotosCmd(verb, id, db, v) {
|
|
|
12178
12279
|
const dir = need2(v.dir, "--dir", ex);
|
|
12179
12280
|
let column = v.column;
|
|
12180
12281
|
if (!column) {
|
|
12181
|
-
const r = await api(agentPath2(id, `/dbs/${
|
|
12282
|
+
const r = await api(agentPath2(id, `/dbs/${enc9(db)}`));
|
|
12182
12283
|
const tb = r.tables.find((x) => x.name === tableName);
|
|
12183
12284
|
if (!tb) throw usage(t(`${db} has no table ${tableName}.`, `${db} no tiene la tabla ${tableName}.`));
|
|
12184
12285
|
column = tb.columns.find((c) => c.name === "imagen_url")?.name;
|
|
@@ -12186,12 +12287,12 @@ async function dbsPhotosCmd(verb, id, db, v) {
|
|
|
12186
12287
|
}
|
|
12187
12288
|
const files = photoFiles(await readdir2(dir));
|
|
12188
12289
|
if (!files.length) throw usage(t(`No .jpg/.png/.webp files in ${dir}.`, `No hay archivos .jpg/.png/.webp en ${dir}.`), t("Name each photo after its key: SKU-123.jpg", "Nombra cada foto con su clave: SKU-123.jpg"));
|
|
12189
|
-
const
|
|
12290
|
+
const path2 = agentPath2(id, `/dbs/${enc9(db)}/photos`);
|
|
12190
12291
|
const results = [];
|
|
12191
12292
|
for (let i = 0; i < files.length; i += 8) {
|
|
12192
12293
|
const batch = files.slice(i, i + 8);
|
|
12193
12294
|
const items = await Promise.all(batch.map(async (f) => ({ key: f.key, filename: f.file, contentBase64: (await readFile6(join5(dir, f.file))).toString("base64") })));
|
|
12194
|
-
const r = await api(
|
|
12295
|
+
const r = await api(path2, { body: { table: tableName, keyColumn, column, items, ...v["dry-run"] ? { dryRun: true } : {} } });
|
|
12195
12296
|
results.push(...r.items);
|
|
12196
12297
|
if (!globals.json && files.length > 8) info(dim(` ${Math.min(i + 8, files.length)}/${files.length}`));
|
|
12197
12298
|
}
|
|
@@ -12463,7 +12564,7 @@ async function readMultiFileAuth(dir) {
|
|
|
12463
12564
|
init_api();
|
|
12464
12565
|
init_util();
|
|
12465
12566
|
init_i18n();
|
|
12466
|
-
var
|
|
12567
|
+
var enc11 = encodeURIComponent;
|
|
12467
12568
|
var digits = (s2) => s2.replace(/\D/g, "");
|
|
12468
12569
|
function find2(nums, ref) {
|
|
12469
12570
|
const d = digits(ref);
|
|
@@ -12489,9 +12590,9 @@ async function channelsCmd(kind, sub, id, ref, v) {
|
|
|
12489
12590
|
if (sub === "integrations") return integrationsCmd(id, v.email);
|
|
12490
12591
|
if (sub === "import" || sub === "status" || sub === "pause" || sub === "pair") return waCmd2(sub, id, v);
|
|
12491
12592
|
if (sub === "enable" && !v["dry-run"]) requireYesIfHeadless(v.yes, `ghosty channels whatsapp enable ${id} ${ref ?? "<number>"}`);
|
|
12492
|
-
const
|
|
12593
|
+
const path2 = `/api/v2/agents/${enc11(id)}/whatsapp`;
|
|
12493
12594
|
const listing = !sub || sub === "ls" || sub === "list";
|
|
12494
|
-
const cur = await api(listing ? `${
|
|
12595
|
+
const cur = await api(listing ? `${path2}?formmy=1` : path2);
|
|
12495
12596
|
if (listing) {
|
|
12496
12597
|
const fwd = (n2) => !n2.formmy ? dim("\u2014") : "error" in n2.formmy ? dim("?") : n2.formmy.forwards ? t("yes", "s\xED") : lavender("NO");
|
|
12497
12598
|
const rows = cur.numbers.map((n2) => ({
|
|
@@ -12515,7 +12616,7 @@ ${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
|
12515
12616
|
if (on) await confirm(`${bold(plan)}
|
|
12516
12617
|
${t("Real customers will get answers from this agent.", "Clientes reales recibir\xE1n respuestas de este agente.")}`, { yes: v.yes, expect: digits(n.label).slice(-4) || n.id, retry: `ghosty channels whatsapp enable ${id} ${ref}` });
|
|
12517
12618
|
const wanted = cur.numbers.filter((x) => x.id === n.id ? on : x.mine).map((x) => x.id);
|
|
12518
|
-
const r = await api(
|
|
12619
|
+
const r = await api(path2, { method: "PUT", body: { numbers: wanted } });
|
|
12519
12620
|
return out(r, () => `${lavender("\u2713")} ${plan.slice(2)}`);
|
|
12520
12621
|
}
|
|
12521
12622
|
function findGroup(groups, ref) {
|
|
@@ -12533,18 +12634,18 @@ async function groupsCmd(args, v) {
|
|
|
12533
12634
|
if (rawSub === "disable") v = { ...v, off: true };
|
|
12534
12635
|
const ex = 'ghosty channels wa groups ls <agent-id> | set <agent-id> <group> [--role equipo|cliente] [--wake mention|all] [--on|--off] [--name "New name"] | create <agent-id> "Name" | inject <agent-id> <group> --text "\u2026" [--sender NAME]';
|
|
12535
12636
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), `${t("Example", "Ejemplo")}: ${ex}`);
|
|
12536
|
-
const
|
|
12637
|
+
const path2 = `/api/v2/agents/${enc11(id)}/wa/groups`;
|
|
12537
12638
|
if (v.role !== void 0 && v.role !== "equipo" && v.role !== "cliente") throw usage(t("--role takes equipo or cliente.", "--role acepta equipo o cliente."));
|
|
12538
12639
|
if (v.wake !== void 0 && v.wake !== "mention" && v.wake !== "all") throw usage(t("--wake takes mention or all.", "--wake acepta mention o all."));
|
|
12539
12640
|
if (!sub || sub === "ls" || sub === "list") {
|
|
12540
|
-
const r2 = await api(`${
|
|
12641
|
+
const r2 = await api(`${path2}${v.cached ? "?live=0" : ""}`);
|
|
12541
12642
|
const rows = r2.groups.map((g2) => ({ name: g2.name, on: g2.enabled ? lavender("on") : dim("off"), role: g2.role, wake: g2.wake ?? "all", jid: g2.jid }));
|
|
12542
12643
|
return out(r2, () => rows.length ? table(rows, ["name", "on", "role", "wake", "jid"]) : dim(t('No groups. Pair its personal WhatsApp in the web, or: ghosty channels wa groups create <agent-id> "Name"', 'Sin grupos. Parea su WhatsApp personal en la web, o: ghosty channels wa groups create <agent-id> "Nombre"')));
|
|
12543
12644
|
}
|
|
12544
12645
|
if (sub === "create") {
|
|
12545
12646
|
const name2 = rest.join(" ").trim();
|
|
12546
12647
|
if (!name2) throw usage(t("Missing group name.", "Falta el nombre del grupo."), t("Example", "Ejemplo") + `: ghosty channels wa groups create ${id} "Equipo ventas" --role equipo`);
|
|
12547
|
-
const r2 = await api(
|
|
12648
|
+
const r2 = await api(path2, { method: "POST", body: { create: name2, role: v.role ?? "equipo" } });
|
|
12548
12649
|
return out(r2, () => `${lavender("\u2713")} ${t("Created", "Creado")} ${bold(name2)} (${v.role ?? "equipo"}, on)
|
|
12549
12650
|
${t("invite", "invitaci\xF3n")}: ${r2.inviteUrl}`);
|
|
12550
12651
|
}
|
|
@@ -12552,15 +12653,15 @@ async function groupsCmd(args, v) {
|
|
|
12552
12653
|
const ref2 = rest.join(" ").trim();
|
|
12553
12654
|
const text = v.text?.trim();
|
|
12554
12655
|
if (!ref2 || !text) throw usage(t("Missing group or --text.", "Falta el grupo o --text."), t("Example", "Ejemplo") + `: ghosty channels wa groups inject ${id} "Equipo" --text "lo que dec\xEDa el audio" --sender Brenda`);
|
|
12555
|
-
const g2 = findGroup((await api(`${
|
|
12556
|
-
const r2 = await api(`/api/v2/agents/${
|
|
12656
|
+
const g2 = findGroup((await api(`${path2}?live=0`)).groups, ref2);
|
|
12657
|
+
const r2 = await api(`/api/v2/agents/${enc11(id)}/wa/inject`, { method: "POST", body: { jid: g2.jid, text, ...v.sender ? { senderName: v.sender } : {} } });
|
|
12557
12658
|
return out(r2, () => `${lavender("\u2713")} ${t(`Injected into ${g2.name}: the agent answers there.`, `Reinyectado en ${g2.name}: el agente contesta ah\xED.`)}`);
|
|
12558
12659
|
}
|
|
12559
12660
|
if (sub !== "set") throw usage(t(`Unknown groups subcommand: ${sub}.`, `Subcomando de groups desconocido: ${sub}.`), ex);
|
|
12560
12661
|
const ref = rest.join(" ").trim();
|
|
12561
12662
|
if (!ref) throw usage(t("Missing group (name or jid).", "Falta el grupo (nombre o jid)."), t("Example", "Ejemplo") + `: ghosty channels wa groups set ${id} "Pia-cotizadora" --role equipo --on`);
|
|
12562
12663
|
if (v.on && v.off) throw usage(t("Use --on or --off, not both.", "Usa --on o --off, no los dos."));
|
|
12563
|
-
const cur = await api(`${
|
|
12664
|
+
const cur = await api(`${path2}?live=0`);
|
|
12564
12665
|
const g = findGroup(cur.groups, ref);
|
|
12565
12666
|
const enabled = v.on ? true : v.off ? false : g.enabled;
|
|
12566
12667
|
const role = v.role ?? g.role;
|
|
@@ -12573,11 +12674,11 @@ async function groupsCmd(args, v) {
|
|
|
12573
12674
|
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
12574
12675
|
if (enabled && !g.enabled) await confirm(`${bold(plan)}
|
|
12575
12676
|
${t(`The agent will start answering in this group as ${role}.`, `El agente empezar\xE1 a contestar en este grupo como ${role}.`)}`, { yes: v.yes, retry: `ghosty channels wa groups set ${id} "${ref}"${v.role ? ` --role ${v.role}` : ""} --on` });
|
|
12576
|
-
const r = await api(
|
|
12677
|
+
const r = await api(path2, { method: "POST", body: { jid: g.jid, enabled, role, ...wake ? { wake } : {}, ...name ? { name } : {} } });
|
|
12577
12678
|
const now = r.groups.find((x) => x.jid === g.jid);
|
|
12578
12679
|
return out({ group: now ?? null, plan: [plan] }, () => `${lavender("\u2713")} ${now?.name ?? g.name}: ${now?.enabled ? "on" : "off"} ${t("as", "como")} ${now?.role ?? role} \xB7 wake ${now?.wake ?? wake ?? "all"}`);
|
|
12579
12680
|
}
|
|
12580
|
-
var EB_EXPORT = (agentId) => `${(process.env.EASYBITS_URL ?? "https://www.easybits.cloud").replace(/\/+$/, "")}/api/v2/fleet-agents/${
|
|
12681
|
+
var EB_EXPORT = (agentId) => `${(process.env.EASYBITS_URL ?? "https://www.easybits.cloud").replace(/\/+$/, "")}/api/v2/fleet-agents/${enc11(agentId)}/baileys/export`;
|
|
12581
12682
|
async function easybitsSession(agentId, keys) {
|
|
12582
12683
|
const key = process.env.EASYBITS_API_KEY;
|
|
12583
12684
|
if (!key) throw usage(t("EASYBITS_API_KEY is not set.", "EASYBITS_API_KEY no est\xE1 definida."), t("Export the EasyBits API key of the agent's owner and run again.", "Exporta la API key de EasyBits del due\xF1o del agente y vuelve a correrlo."));
|
|
@@ -12604,7 +12705,7 @@ function originText(o) {
|
|
|
12604
12705
|
}
|
|
12605
12706
|
async function waCmd2(sub, id, v) {
|
|
12606
12707
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), t("Example", "Ejemplo") + `: ghosty channels wa ${sub} <agent-id>`);
|
|
12607
|
-
const base = `/api/v2/agents/${
|
|
12708
|
+
const base = `/api/v2/agents/${enc11(id)}/wa`;
|
|
12608
12709
|
if (sub === "status") {
|
|
12609
12710
|
const s2 = await api(base);
|
|
12610
12711
|
return out(s2, () => `${s2.status === "connected" ? lavender(t("\u25CF connected", "\u25CF conectado")) : dim(`\u25CB ${s2.status}`)} ${s2.phone ? `+${s2.phone}` : dim(t("(no number)", "(sin n\xFAmero)"))}${s2.live ? "" : dim(t(" \xB7 no socket in gs", " \xB7 sin socket en gs"))}${s2.hasCreds ? "" : dim(t(" \xB7 no saved session", " \xB7 sin sesi\xF3n guardada"))}${s2.reason ? `
|
|
@@ -12668,19 +12769,19 @@ async function linkCmd(sub, id, v) {
|
|
|
12668
12769
|
const board = v.board === void 0 ? void 0 : /^(agent|agente)$/i.test(v.board) ? null : v.board;
|
|
12669
12770
|
const boardFlag = v.board !== void 0 ? ` --board ${JSON.stringify(v.board)}` : "";
|
|
12670
12771
|
if (sub === "link" && (adsOnly !== void 0 || board !== void 0) && v.answer === void 0) {
|
|
12671
|
-
const
|
|
12672
|
-
const r2 = await api(
|
|
12772
|
+
const path3 = `/api/v2/agents/${enc11(id)}/whatsapp/link`;
|
|
12773
|
+
const r2 = await api(path3, { method: "POST", body: { integrationId: v.integration, ...adsOnly !== void 0 ? { adsOnly } : {}, ...board !== void 0 ? { board } : {}, ...v["dry-run"] ? { dryRun: true } : {} } });
|
|
12673
12774
|
return out(r2, () => `${r2.plan.map((x) => serverText(x)).join("\n")}${v["dry-run"] ? `
|
|
12674
12775
|
${dim(applyHint(`ghosty channels whatsapp link ${id} --integration ${v.integration}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${boardFlag}`))}` : ""}`);
|
|
12675
12776
|
}
|
|
12676
12777
|
if (sub === "link" && v.answer !== "on" && v.answer !== "off") throw usage(t("Missing --answer on|off.", "Falta --answer on|off."), t("on = this agent answers real customers \xB7 off = it only records (add --forward so gs still sees the conversations)", "on = este agente contesta a clientes reales \xB7 off = s\xF3lo registra (agrega --forward para que gs siga viendo las conversaciones)"));
|
|
12677
|
-
const
|
|
12778
|
+
const path2 = `/api/v2/agents/${enc11(id)}/whatsapp/link`;
|
|
12678
12779
|
if (v["partner-agent"] && !v["partner-tenant"]) throw usage(t("--partner-agent needs --partner-tenant <externalId>.", "--partner-agent necesita --partner-tenant <externalId>."));
|
|
12679
12780
|
const partner = v["partner-tenant"] ? { partnerExternalId: v["partner-tenant"], ...v["partner-agent"] ? { partnerAgentSlug: v["partner-agent"] } : {} } : {};
|
|
12680
12781
|
const body = { integrationId: v.integration, ...sub === "unlink" ? { unlink: true } : { answer: v.answer === "on", ...v.forward ? { forward: true } : {}, ...adsOnly !== void 0 ? { adsOnly } : {}, ...board !== void 0 ? { board } : {}, ...partner } };
|
|
12681
12782
|
const partnerFlags = v["partner-tenant"] ? ` --partner-tenant ${v["partner-tenant"]}${v["partner-agent"] ? ` --partner-agent ${v["partner-agent"]}` : ""}` : "";
|
|
12682
12783
|
const cmd = `ghosty channels whatsapp ${sub} ${id} --integration ${v.integration}${sub === "link" ? ` --answer ${v.answer}${v.forward ? " --forward" : ""}${v["ads-only"] ? ` --ads-only ${v["ads-only"]}` : ""}${boardFlag}${partnerFlags}` : ""}`;
|
|
12683
|
-
const plan = await api(
|
|
12784
|
+
const plan = await api(path2, { method: "POST", body: { ...body, dryRun: true } });
|
|
12684
12785
|
const text = `${plan.plan.map((x) => serverText(x)).join("\n")}
|
|
12685
12786
|
${dim(`Formmy: ${plan.formmy.before.url ?? "null"} \u2192 ${plan.formmy.after.url ?? "null"} (${t("as", "como")} ${plan.formmy.by})`)}`;
|
|
12686
12787
|
if (v["dry-run"]) return out(plan, () => `${text}
|
|
@@ -12689,7 +12790,7 @@ ${dim(applyHint(cmd))}`);
|
|
|
12689
12790
|
const digits4 = (plan.number.phoneNumberId ?? v.integration).slice(-4);
|
|
12690
12791
|
await confirm(`${bold(text)}
|
|
12691
12792
|
${sub === "link" ? t("From now on this number's messages reach this agent in gs.", "Desde ahora los mensajes de este n\xFAmero le llegan a este agente en gs.") : t("This number goes back to where it was before.", "Este n\xFAmero regresa a donde estaba antes.")}`, { yes: v.yes, expect: digits4, retry: cmd });
|
|
12692
|
-
const r = await api(
|
|
12793
|
+
const r = await api(path2, { method: "POST", body });
|
|
12693
12794
|
return out(r, () => `${lavender("\u2713")} ${sub === "link" ? t(`Linked (link ${r.linkId}). Check: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m`, `Enlazado (enlace ${r.linkId}). Revisa: ghosty channels whatsapp ls ${id} \xB7 ghosty turns ls ${id} --since 10m`) : t("Unlinked: Formmy points where it did before.", "Desenlazado: Formmy apunta a donde apuntaba antes.")}`);
|
|
12694
12795
|
}
|
|
12695
12796
|
function bareMediaFlag(argv) {
|
|
@@ -12721,7 +12822,7 @@ async function pageWithRetry(fetchPage, where, waitMs = (n) => 5e3 * n, tries =
|
|
|
12721
12822
|
}
|
|
12722
12823
|
var lastCell = (l) => l ? `${l.agentName ?? l.agentId} ${dim(l.at.slice(0, 16).replace("T", " "))}` : dim("\u2014");
|
|
12723
12824
|
async function linksCmd(id, kind, orphans) {
|
|
12724
|
-
const r = await api(`/api/v2/agents/${
|
|
12825
|
+
const r = await api(`/api/v2/agents/${enc11(id)}/channels/links?kind=${kind}${orphans ? "&orphans=1" : ""}`);
|
|
12725
12826
|
const now = (l) => l.answersNow ? l.answersNow.agentName ?? l.answersNow.agentId : l.recordsOnly ? t("nobody (records only)", "nadie (s\xF3lo registra)") : l.via === "easybits" ? "EasyBits" : l.via === "partner" ? "partner" : lavender(t("nobody", "nadie"));
|
|
12726
12827
|
const rows = r.links.map((l) => ({ [kind === "waba" ? "number" : "page"]: l.label, "answers now": now(l), "last answered by": lastCell(l.lastAnswer), turns: l.lastAnswer?.turns ?? 0, id: l.id }));
|
|
12727
12828
|
const first = r.links.find((l) => l.nobody && l.lastAnswer);
|
|
@@ -12733,7 +12834,7 @@ ${dim(`${t("Give it back", "Regresarlo")}: ${kind === "waba" ? `ghosty channels
|
|
|
12733
12834
|
);
|
|
12734
12835
|
}
|
|
12735
12836
|
async function integrationsCmd(id, email) {
|
|
12736
|
-
const r = await api(`/api/v2/agents/${
|
|
12837
|
+
const r = await api(`/api/v2/agents/${enc11(id)}/whatsapp/integrations${email ? `?email=${enc11(email)}` : ""}`);
|
|
12737
12838
|
const who2 = { gs: "gs", easybits: "EasyBits", droplet: "droplet", formmy: "Formmy", other: t("other", "otro") };
|
|
12738
12839
|
const rows = r.integrations.map((i) => ({
|
|
12739
12840
|
number: i.phoneNumber ? `+${i.phoneNumber}` : dim("?"),
|
|
@@ -12754,11 +12855,11 @@ async function historyCmd(id, v) {
|
|
|
12754
12855
|
if (!v.integration) throw usage(t("Missing --integration.", "Falta --integration."), t("Example", "Ejemplo") + `: ghosty channels whatsapp history ${id} --integration <formmy-integration-id> --days 7 --dry-run`);
|
|
12755
12856
|
const days = Number(v.days ?? 7);
|
|
12756
12857
|
if (!Number.isInteger(days) || days < 1 || days > 30) throw usage(t("--days takes 1 to 30.", "--days acepta de 1 a 30."));
|
|
12757
|
-
const
|
|
12858
|
+
const path2 = `/api/v2/agents/${enc11(id)}/whatsapp/history`;
|
|
12758
12859
|
const media = v.media !== void 0;
|
|
12759
12860
|
const filters = { ...v["only-ads"] ? { onlyAds: true } : {}, ...v.phones ? { phones: v.phones.split(",").map((x) => x.trim()).filter(Boolean) } : {}, ...v.column ? { column: v.column } : {} };
|
|
12760
12861
|
const cmd = `ghosty channels whatsapp history ${id} --integration ${v.integration} --days ${days}${v["only-ads"] ? " --only-ads" : ""}${v.phones ? ` --phones ${v.phones}` : ""}${v.column ? ` --column "${v.column}"` : ""}`;
|
|
12761
|
-
const c = await api(
|
|
12862
|
+
const c = await api(path2, { method: "POST", body: { integrationId: v.integration, days, dryRun: true, media, ...v.list ? { list: true } : {}, ...filters } });
|
|
12762
12863
|
const files = (p) => (p.mediaCopied ?? 0) + (p.mediaFailed ?? 0) ? ` \xB7 ${t(`${p.mediaCopied ?? 0} file(s) copied`, `${p.mediaCopied ?? 0} archivo(s) copiado(s)`)}${p.mediaFailed ? lavender(t(`, ${p.mediaFailed} failed (retry: --media)`, `, ${p.mediaFailed} fallaron (reintenta: --media)`)) : ""}` : "";
|
|
12763
12864
|
if (media) {
|
|
12764
12865
|
const missing = c.mediaMissing ?? 0;
|
|
@@ -12773,7 +12874,7 @@ ${dim(v["dry-run"] ? t("Dry run: nothing copied.", "Simulacro: no se copi\xF3 na
|
|
|
12773
12874
|
let done2 = 0;
|
|
12774
12875
|
while (cursor2 !== null) {
|
|
12775
12876
|
const at = cursor2;
|
|
12776
|
-
const p = await pageWithRetry(() => api(
|
|
12877
|
+
const p = await pageWithRetry(() => api(path2, { method: "POST", body: { integrationId: v.integration, days, cursor: at, media: true } }), `${done2}/${c.conversations}`);
|
|
12777
12878
|
sum2.mediaCopied += p.mediaCopied ?? 0;
|
|
12778
12879
|
sum2.mediaFailed += p.mediaFailed ?? 0;
|
|
12779
12880
|
done2 += p.page;
|
|
@@ -12805,7 +12906,7 @@ ${t(`Cards appear on the team's board (no "new lead" alerts).`, "Las tarjetas ap
|
|
|
12805
12906
|
let done = 0;
|
|
12806
12907
|
while (cursor !== null) {
|
|
12807
12908
|
const at = cursor;
|
|
12808
|
-
const p = await pageWithRetry(() => api(
|
|
12909
|
+
const p = await pageWithRetry(() => api(path2, { method: "POST", body: { integrationId: v.integration, days, cursor: at, ...filters } }), `${done}/${c.conversations}`);
|
|
12809
12910
|
for (const k of Object.keys(sum)) sum[k] += p[k] ?? 0;
|
|
12810
12911
|
done += p.page;
|
|
12811
12912
|
if (!globals.json) info(dim(` ${done}/${p.total} ${t("conversations", "conversaciones")}`));
|
|
@@ -12955,8 +13056,8 @@ async function boardCmd(sub, id, v, ref) {
|
|
|
12955
13056
|
if (!Object.keys(filter).length) throw usage(t("Add at least one filter: --integration, --column or --before.", "Agrega al menos un filtro: --integration, --column o --before."), t("A filter-less archive would empty the board.", "Archivar sin filtro vaciar\xEDa el tablero."));
|
|
12956
13057
|
const retry = `ghosty board archive ${id} ${Object.entries(filter).map(([k, x]) => `--${k} ${JSON.stringify(x)}`).join(" ")}`;
|
|
12957
13058
|
if (!v["dry-run"]) requireYesIfHeadless(v.yes, retry);
|
|
12958
|
-
const
|
|
12959
|
-
const plan = await api(
|
|
13059
|
+
const path2 = `/api/v2/agents/${encodeURIComponent(id)}/board/archive`;
|
|
13060
|
+
const plan = await api(path2, { body: { ...filter, dryRun: true } });
|
|
12960
13061
|
const sample = () => table(plan.sample.map((c) => ({ name: c.name ?? "", phone: c.phone, column: c.estado_label ?? "" })), ["name", "phone", "column"]);
|
|
12961
13062
|
if (v["dry-run"] || plan.matched === 0) {
|
|
12962
13063
|
return out(plan, () => plan.matched ? `${sample()}
|
|
@@ -12964,7 +13065,7 @@ ${dim(t(`Would archive ${plan.matched} card(s) on \xAB${plan.board}\xBB. Dry run
|
|
|
12964
13065
|
}
|
|
12965
13066
|
await confirm(`${sample()}
|
|
12966
13067
|
${t(`Archive ${plan.matched} card(s) on \xAB${plan.board}\xBB? (reversible: a new message brings a card back)`, `\xBFArchivar ${plan.matched} tarjeta(s) en \xAB${plan.board}\xBB? (reversible: un mensaje nuevo regresa la tarjeta)`)}`, { yes: v.yes, retry });
|
|
12967
|
-
const r = await api(
|
|
13068
|
+
const r = await api(path2, { body: filter });
|
|
12968
13069
|
out(r, () => `${lavender("\u2713")} ${t(`Archived ${r.archived} card(s) on \xAB${r.board}\xBB.`, `Se archivaron ${r.archived} tarjeta(s) en \xAB${r.board}\xBB.`)}`);
|
|
12969
13070
|
}
|
|
12970
13071
|
|
|
@@ -12972,7 +13073,7 @@ ${t(`Archive ${plan.matched} card(s) on \xAB${plan.board}\xBB? (reversible: a ne
|
|
|
12972
13073
|
init_api();
|
|
12973
13074
|
init_util();
|
|
12974
13075
|
init_i18n();
|
|
12975
|
-
var
|
|
13076
|
+
var enc12 = encodeURIComponent;
|
|
12976
13077
|
async function boardsCmd(sub, a1, a2, v, context) {
|
|
12977
13078
|
if (context && sub !== "assign") {
|
|
12978
13079
|
if ((sub === "ls" || sub === "list" || !sub || sub === "move-cards") && !a1) a1 = context;
|
|
@@ -12983,13 +13084,13 @@ async function boardsCmd(sub, a1, a2, v, context) {
|
|
|
12983
13084
|
case "ls":
|
|
12984
13085
|
case "list": {
|
|
12985
13086
|
if (!a1) throw usage(t("Missing workspace.", "Falta el espacio."), t("Example", "Ejemplo") + ": ghosty boards ls <workspace-slug>");
|
|
12986
|
-
const r = await api(`/api/v2/workspaces/${
|
|
13087
|
+
const r = await api(`/api/v2/workspaces/${enc12(a1)}/boards`);
|
|
12987
13088
|
const rows = r.boards.map((b) => ({ name: b.main ? `${b.name} (${t("main", "principal")})` : b.name, cards: b.cards, agents: b.agents.map((x) => x.name ?? x.id).join(", "), columns: b.columns.length, id: b.id }));
|
|
12988
13089
|
return out(r, () => table(rows, ["name", "cards", "agents", "columns", "id"]));
|
|
12989
13090
|
}
|
|
12990
13091
|
case "create": {
|
|
12991
13092
|
if (!a1 || !a2) throw usage(t("Missing workspace or name.", "Falta el espacio o el nombre."), t("Example", "Ejemplo") + ': ghosty boards create <workspace> "Siiqtec - Ixmiquilpan" --columns-from main');
|
|
12992
|
-
const r = await api(`/api/v2/workspaces/${
|
|
13093
|
+
const r = await api(`/api/v2/workspaces/${enc12(a1)}/boards`, { method: "POST", body: { name: a2, ...v["columns-from"] ? { columnsFrom: v["columns-from"] } : {} } });
|
|
12993
13094
|
return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.name}\xBB created${r.columnsFrom ? ` with the ${r.columns} column(s) of \xAB${r.columnsFrom}\xBB` : ""}.`, `Tablero \xAB${r.name}\xBB creado${r.columnsFrom ? ` con las ${r.columns} columna(s) de \xAB${r.columnsFrom}\xBB` : ""}.`)} ${dim(`${t("Send an agent's cards there", "Manda ah\xED las tarjetas de un agente")}: ghosty boards assign <agent-id> "${r.name}"`)}${r.warning ? `
|
|
12994
13095
|
${lavender(`\u26A0\uFE0F ${serverText(r.warning)}`)}` : ""}`);
|
|
12995
13096
|
}
|
|
@@ -12997,22 +13098,22 @@ ${lavender(`\u26A0\uFE0F ${serverText(r.warning)}`)}` : ""}`);
|
|
|
12997
13098
|
if (!a1 || !a2) throw usage(t("Missing workspace or board.", "Falta el espacio o el tablero."), t("Example", "Ejemplo") + ': ghosty boards rm <workspace> "Prueba"');
|
|
12998
13099
|
requireYesIfHeadless(v.yes, `ghosty boards rm ${a1} ${JSON.stringify(a2)}`);
|
|
12999
13100
|
await confirm(t(`Delete the board \xAB${a2}\xBB of ${a1}? (only an empty one; its agents go back to the main board)`, `\xBFBorrar el tablero \xAB${a2}\xBB de ${a1}? (s\xF3lo si est\xE1 vac\xEDo; sus agentes regresan al tablero principal)`), { yes: v.yes, retry: `ghosty boards rm ${a1} ${JSON.stringify(a2)}` });
|
|
13000
|
-
const r = await api(`/api/v2/workspaces/${
|
|
13101
|
+
const r = await api(`/api/v2/workspaces/${enc12(a1)}/boards`, { method: "DELETE", body: { board: a2 } });
|
|
13001
13102
|
return out(r, () => `${lavender("\u2713")} ${t(`Board \xAB${r.deleted}\xBB deleted.`, `Tablero \xAB${r.deleted}\xBB borrado.`)}`);
|
|
13002
13103
|
}
|
|
13003
13104
|
case "assign": {
|
|
13004
13105
|
if (!a1 || !a2) throw usage(t("Missing agent or board.", "Falta el agente o el tablero."), t("Example", "Ejemplo") + ': ghosty boards assign <agent-id> "Siiqtec - Ixmiquilpan" (' + t("or main", "o main") + ")");
|
|
13005
|
-
const r = await api(`/api/v2/agents/${
|
|
13106
|
+
const r = await api(`/api/v2/agents/${enc12(a1)}`, { method: "PATCH", body: { board: a2 } });
|
|
13006
13107
|
return out(r, () => `${lavender("\u2713")} ${t(`New cards of ${a1} go to \xAB${r.board}\xBB.`, `Las tarjetas nuevas de ${a1} van a \xAB${r.board}\xBB.`)} ${dim(`${t("Existing ones stay where they are", "Las que ya existen se quedan donde est\xE1n")}: ghosty boards move-cards ${r.workspace} --from \u2026 --to "${r.board}" \u2026`)}`);
|
|
13007
13108
|
}
|
|
13008
13109
|
case "move-cards": {
|
|
13009
13110
|
if (!a1 || !v.from || !v.to) throw usage(t("Missing workspace, --from or --to.", "Falta el espacio, --from o --to."), t("Example", "Ejemplo") + ': ghosty boards move-cards <workspace> --from main --to "Siiqtec - Ixmiquilpan" --integration 6a3b02ad\u2026 --dry-run');
|
|
13010
13111
|
if (!v.integration && !v.phone && !v.all) throw usage(t("Add a filter: --integration ID, --phone N or --all.", "Agrega un filtro: --integration ID, --phone N o --all."), t("A filter-less move would empty the board.", "Mover sin filtro vaciar\xEDa el tablero."));
|
|
13011
|
-
const
|
|
13112
|
+
const path2 = `/api/v2/workspaces/${enc12(a1)}/boards/move-cards`;
|
|
13012
13113
|
const body = { from: v.from, to: v.to, ...v.integration ? { integration: v.integration } : {}, ...v.phone ? { phone: v.phone } : {}, ...v.all ? { all: true } : {} };
|
|
13013
13114
|
const retry = `ghosty boards move-cards ${a1} --from ${JSON.stringify(v.from)} --to ${JSON.stringify(v.to)}${v.integration ? ` --integration ${v.integration}` : ""}${v.phone ? ` --phone ${v.phone}` : ""}${v.all ? " --all" : ""}`;
|
|
13014
13115
|
if (!v["dry-run"]) requireYesIfHeadless(v.yes, retry);
|
|
13015
|
-
const plan = await api(
|
|
13116
|
+
const plan = await api(path2, { method: "POST", body: { ...body, dryRun: true } });
|
|
13016
13117
|
const sample = table(plan.sample.map((c) => ({ folio: c.folio ? `#${c.folio}` : "", name: c.name ?? "", phone: c.phone })), ["folio", "name", "phone"]);
|
|
13017
13118
|
const text = `${sample}
|
|
13018
13119
|
${t(`${plan.moved} of ${plan.matched} card(s)`, `${plan.moved} de ${plan.matched} tarjeta(s)`)} \xAB${plan.from}\xBB \u2192 \xAB${plan.to}\xBB${plan.skipped.length ? dim(t(` \xB7 ${plan.skipped.length} already in \xAB${plan.to}\xBB, skipped`, ` \xB7 ${plan.skipped.length} ya en \xAB${plan.to}\xBB, se omiten`)) : ""}`;
|
|
@@ -13020,7 +13121,7 @@ ${t(`${plan.moved} of ${plan.matched} card(s)`, `${plan.moved} de ${plan.matched
|
|
|
13020
13121
|
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
13021
13122
|
await confirm(`${text}
|
|
13022
13123
|
${t("Move them with their threads and orders? (they get a new folio there)", "\xBFMoverlas con sus hilos y pedidos? (all\xE1 reciben folio nuevo)")}`, { yes: v.yes, retry });
|
|
13023
|
-
const r = await api(
|
|
13124
|
+
const r = await api(path2, { method: "POST", body });
|
|
13024
13125
|
return out(r, () => `${lavender("\u2713")} ${t(`Moved ${r.moved} card(s) (${r.messages} message(s), ${r.orders} order(s)) to \xAB${r.to}\xBB.`, `Se movieron ${r.moved} tarjeta(s) (${r.messages} mensaje(s), ${r.orders} pedido(s)) a \xAB${r.to}\xBB.`)}${r.skipped.length ? dim(t(` Skipped ${r.skipped.length}.`, ` Se omitieron ${r.skipped.length}.`)) : ""}`);
|
|
13025
13126
|
}
|
|
13026
13127
|
// Quién ve un tablero: lista «sólo estas personas» o todo el espacio (feedback del revisor de Meta).
|
|
@@ -13031,7 +13132,7 @@ ${t("Move them with their threads and orders? (they get a new folio there)", "\x
|
|
|
13031
13132
|
const add = split(v.add);
|
|
13032
13133
|
const remove = split(v.remove);
|
|
13033
13134
|
const edit = add.length || remove.length || v.everyone;
|
|
13034
|
-
const r = edit ? await api(`/api/v2/workspaces/${
|
|
13135
|
+
const r = edit ? await api(`/api/v2/workspaces/${enc12(a1)}/boards/access`, { method: "POST", body: { board, add, remove, ...v.everyone ? { everyone: true } : {}, ...v.restrict ? { restrict: true } : {}, ...v.until ? { until: v.until } : {} } }) : await api(`/api/v2/workspaces/${enc12(a1)}/boards/access?board=${enc12(board)}`);
|
|
13035
13136
|
return out(r, () => {
|
|
13036
13137
|
const head = `\xAB${r.board}\xBB (${r.workspace}): ${r.everyone ? t("the whole workspace sees it", "lo ve todo el espacio") : t("only these people (plus owner and staff)", "s\xF3lo estas personas (m\xE1s due\xF1o y staff)")}`;
|
|
13037
13138
|
const people = r.everyone ? "" : `
|
|
@@ -13332,6 +13433,18 @@ var HELPS_ES = {
|
|
|
13332
13433
|
replace: [['"what to do"', '"qu\xE9 hacer"'], ["# answers in that thread as if mentioned", "# contesta en ese hilo como si lo mencionaran"]],
|
|
13333
13434
|
notes: ["--teams: el agente tiene que estar activo en el Teams de ese espacio y t\xFA ser miembro. El id del hilo es el del mensaje con que empieza.", "--conversation waba:\u2026 | wa:<jid> | messenger:\u2026: la respuesta sale por ese canal (WABA s\xF3lo dentro de 24 h; WhatsApp personal si el n\xFAmero est\xE1 conectado). \xABOK\xBB nunca se manda."]
|
|
13334
13435
|
},
|
|
13436
|
+
watch: {
|
|
13437
|
+
summary: "Vigila una p\xE1gina web y que el agente te diga algo S\xD3LO cuando cambie. La plataforma la baja sin turno del modelo (no gasta saldo); si cambi\xF3, el agente recibe un turno en esa conversaci\xF3n con lo que cambi\xF3 y te avisa por su canal.",
|
|
13438
|
+
replace: [['"heading or line"', '"encabezado o l\xEDnea"'], ['"what matters"', '"qu\xE9 importa"']],
|
|
13439
|
+
flags: [
|
|
13440
|
+
["--every 24h", "cada cu\xE1nto revisarla: 1h m\xEDnimo, 7d m\xE1ximo (default 24h)"],
|
|
13441
|
+
["--focus TEXT", "compara s\xF3lo esa parte: el texto de un encabezado (hasta el siguiente de su nivel) o de una l\xEDnea"],
|
|
13442
|
+
["--instruction TEXT", "lo que te importa (\xABav\xEDsame si baja de $500\xBB); el agente lo lee cuando la p\xE1gina cambia y contesta OK (silencio) si el cambio no importa"],
|
|
13443
|
+
["--days N", "add: vence a los N d\xEDas (default 90, m\xE1x. 365) \xB7 resume: renueva una vencida por N d\xEDas"],
|
|
13444
|
+
["--dry-run", "check: baja y compara ya contra la \xFAltima foto y ense\xF1a el diff; no guarda ni avisa (cuesta una lectura web, no saldo)"]
|
|
13445
|
+
],
|
|
13446
|
+
notes: ["Al comparar se ignoran fechas, horas, \xABhace 5 minutos\xBB, scripts, im\xE1genes y URLs de las ligas: una p\xE1gina que s\xF3lo cambia su reloj no avisa.", "Topes: 10 por agente; por cuenta seg\xFAn el plan (Gratis 2, Pro 10, Power 25, Max 50, espacio de equipo 25). Tras 5 lecturas fallidas seguidas se pausa y te llega un push, una vez.", "Sin --conversation abre una nueva, donde caen los avisos. --conversation wa:<jid> | waba:\u2026 tambi\xE9n vale: el aviso sale por ese canal, como un turno programado.", "El agente tiene lo mismo como tools: watch_add, watch_list, watch_remove."]
|
|
13447
|
+
},
|
|
13335
13448
|
teams: {
|
|
13336
13449
|
summary: "Escribe en un room o hilo de Teams a tu nombre; el @agente que menciones contesta, como en el composer.",
|
|
13337
13450
|
replace: [['"message"', '"mensaje"'], ["# new thread", "# hilo nuevo"], ["make Deploy wait for ci with needs", "que el Deploy espere al ci con needs"], ["how is it going?", "\xBFen qu\xE9 va?"]],
|
|
@@ -13341,6 +13454,16 @@ var HELPS_ES = {
|
|
|
13341
13454
|
summary: "Ghosty para Chrome: cerrar la sesi\xF3n del navegador.",
|
|
13342
13455
|
notes: ["Revoca todos los tokens de Ghosty para Chrome que tengan tus agentes de terminal (Claude Code, CLI, @ghostystudio/browser-mcp) y desconecta la extensi\xF3n. Para volver, copia un comando nuevo del panel de la extensi\xF3n."]
|
|
13343
13456
|
},
|
|
13457
|
+
tts: {
|
|
13458
|
+
summary: "Oye la voz de la casa decir un texto (Kokoro, espa\xF1ol de M\xE9xico, con el l\xE9xico de pronunciaci\xF3n de Ghosty): la misma de las notas de voz. Guarda un .wav.",
|
|
13459
|
+
usage: ['ghosty tts "texto" [--voice em_santa|em_alex|ef_dora] [--out ARCHIVO]'],
|
|
13460
|
+
flags: [
|
|
13461
|
+
["--voice V", "em_santa (hombre, la de la casa, default), em_alex (hombre) o ef_dora (mujer)"],
|
|
13462
|
+
["--out FILE", "d\xF3nde guardar el .wav (default voz.wav)"]
|
|
13463
|
+
],
|
|
13464
|
+
examples: ['ghosty tts "Hola, gracias por escribir. Te aviso por WhatsApp."', 'ghosty tts "Tu cita qued\xF3 el jueves a las cinco." --voice ef_dora --out cita.wav && afplay cita.wav'],
|
|
13465
|
+
notes: ["Hasta 400 caracteres y 60 frases por minuto. Las marcas (WhatsApp, Claude, GitHub, ghosty.studio\u2026) se escriben normal: el l\xE9xico corrige c\xF3mo suenan."]
|
|
13466
|
+
},
|
|
13344
13467
|
image: {
|
|
13345
13468
|
summary: "Im\xE1genes con IA (Gemini \xABNano Banana Pro\xBB) para los negocios de tu espacio de partner: generar, editar una que ya existe o componer con referencias; cobradas del saldo del espacio.",
|
|
13346
13469
|
replace: [
|
|
@@ -13367,7 +13490,7 @@ var HELPS_ES = {
|
|
|
13367
13490
|
notes: [
|
|
13368
13491
|
"Para cambiar una imagen que ya existe usa edit (no generate): sale la misma imagen con s\xF3lo ese cambio.",
|
|
13369
13492
|
`Cada imagen se cobra aparte del saldo del espacio de partner: USD 0.25 a 1K/2K y USD 0.45 a 4K; las variantes se cobran una por una y las fallidas no se cobran. Sin saldo: 402. gs no pone topes propios; un negocio puede tener el suyo: tenants set <id> --config '{"imageDailyCap":20}'.`,
|
|
13370
|
-
"Auth: tu login (due\
|
|
13493
|
+
"Auth: tu login (due\xF1o del espacio; --workspace si tienes varios) o la credencial del servidor del partner en el env (GHOSTY_PARTNER_KEY + GHOSTY_PARTNER_KEY_SECRET), que entonces exige --tenant."
|
|
13371
13494
|
]
|
|
13372
13495
|
},
|
|
13373
13496
|
me: {
|
|
@@ -14142,6 +14265,20 @@ var HELPS = {
|
|
|
14142
14265
|
notes: ["--teams: the agent must be active in that workspace's Teams and you must be a member. The thread id is the id of the message that starts it.", "--conversation waba:\u2026 | wa:<jid> | messenger:\u2026: the answer goes out through that channel (WABA only within 24 h; personal WhatsApp if the number is connected). \xABOK\xBB is never sent."],
|
|
14143
14266
|
learn: `${REF}#programar-turnos`
|
|
14144
14267
|
},
|
|
14268
|
+
watch: {
|
|
14269
|
+
summary: "Watch a web page and hear from the agent ONLY when it changes. The platform fetches it without a model turn (no balance spent); on a change, the agent gets a turn in that conversation with what changed and tells you through its channel.",
|
|
14270
|
+
usage: ['ghosty watch add <agent-id> <url> [--every 24h] [--focus "heading or line"] [--instruction "what matters"] [--label L] [--days 90] [--conversation ID] [--title T]', "ghosty watch ls <agent-id>", "ghosty watch check <agent-id> <watch-id> [--dry-run]", "ghosty watch rm <agent-id> <watch-id>", "ghosty watch resume <agent-id> <watch-id> [--days N]"],
|
|
14271
|
+
flags: [
|
|
14272
|
+
["--every 24h", "how often to check it: 1h minimum, 7d maximum (default 24h)"],
|
|
14273
|
+
["--focus TEXT", "compare only that part: the text of a heading (until the next one of its level) or of a line"],
|
|
14274
|
+
["--instruction TEXT", "what matters to you (\xABtell me if it drops below $500\xBB); the agent reads it when the page changes and answers OK (silence) if the change doesn't matter"],
|
|
14275
|
+
["--days N", "add: expires after N days (default 90, max 365) \xB7 resume: renew an expired one for N days"],
|
|
14276
|
+
["--dry-run", "check: fetch and compare now against the last snapshot, show the diff; saves nothing and notifies nobody (costs one web read, no balance)"]
|
|
14277
|
+
],
|
|
14278
|
+
examples: ['ghosty watch add <id> https://competencia.mx/precios --every 12h --focus "Planes" --instruction "av\xEDsame si bajan el Pro"', "ghosty watch check <id> <watch-id> --dry-run"],
|
|
14279
|
+
notes: ["Dates, times, \xAB5 minutes ago\xBB, scripts, images and link URLs are ignored when comparing: a page that only changes its clock doesn't notify.", "Caps: 10 per agent; per account by plan (Free 2, Pro 10, Power 25, Max 50, team space 25). After 5 failed reads in a row it pauses and pushes you once.", "No --conversation = a new conversation, where the notices land. --conversation wa:<jid> | waba:\u2026 also works: the notice goes out through that channel, like a scheduled turn.", "The agent has the same as tools: watch_add, watch_list, watch_remove."],
|
|
14280
|
+
learn: `${REF}#vigilar-paginas`
|
|
14281
|
+
},
|
|
14145
14282
|
teams: {
|
|
14146
14283
|
summary: "Write in a Teams room or thread as yourself; any @agent you mention answers, like in the composer.",
|
|
14147
14284
|
usage: ['ghosty teams post <workspace>#<room> "message"', 'ghosty teams post <workspace>#<room>/<thread-id> "message" [--wait [--timeout 600]]'],
|
|
@@ -14156,6 +14293,16 @@ var HELPS = {
|
|
|
14156
14293
|
notes: ["Revokes every Ghosty for Chrome token your terminal agents hold (Claude Code, CLI, @ghostystudio/browser-mcp) and disconnects the extension. Copy a new command from the extension panel to reconnect."],
|
|
14157
14294
|
learn: `${REF}#navegador`
|
|
14158
14295
|
},
|
|
14296
|
+
tts: {
|
|
14297
|
+
summary: "Hear the house voice say a text (Kokoro, Mexican Spanish, with Ghosty's pronunciation lexicon): the same voice as the voice notes. Saves a .wav.",
|
|
14298
|
+
usage: ['ghosty tts "text" [--voice em_santa|em_alex|ef_dora] [--out FILE]'],
|
|
14299
|
+
flags: [
|
|
14300
|
+
["--voice V", "em_santa (man, the house voice, default), em_alex (man) or ef_dora (woman)"],
|
|
14301
|
+
["--out FILE", "where to save the .wav (default voz.wav)"]
|
|
14302
|
+
],
|
|
14303
|
+
examples: ['ghosty tts "Hola, gracias por escribir. Te aviso por WhatsApp."', 'ghosty tts "Tu cita qued\xF3 el jueves a las cinco." --voice ef_dora --out cita.wav && afplay cita.wav'],
|
|
14304
|
+
notes: ["Up to 400 characters and 60 sentences per minute. Brand names (WhatsApp, Claude, GitHub, ghosty.studio\u2026) are written normally: the lexicon fixes how they sound."]
|
|
14305
|
+
},
|
|
14159
14306
|
image: {
|
|
14160
14307
|
summary: "AI images (Gemini \xABNano Banana Pro\xBB) for your partner businesses: generate, edit an existing one, or compose from references; charged to the space's balance.",
|
|
14161
14308
|
usage: [
|
|
@@ -14755,7 +14902,7 @@ function helpText(cmd) {
|
|
|
14755
14902
|
|
|
14756
14903
|
// src/update.ts
|
|
14757
14904
|
init_i18n();
|
|
14758
|
-
import { mkdir as mkdir5, readFile as readFile9, writeFile as
|
|
14905
|
+
import { mkdir as mkdir5, readFile as readFile9, writeFile as writeFile10 } from "node:fs/promises";
|
|
14759
14906
|
import { homedir as homedir3 } from "node:os";
|
|
14760
14907
|
import { dirname as dirname3, join as join7 } from "node:path";
|
|
14761
14908
|
var FILE = join7(process.env.XDG_CONFIG_HOME || join7(homedir3(), ".config"), "ghosty", "update.json");
|
|
@@ -14781,7 +14928,7 @@ async function latestVersion(current2) {
|
|
|
14781
14928
|
const latest = (await r.json()).version;
|
|
14782
14929
|
if (latest) cache2 = { latest, checkedAt: Date.now() };
|
|
14783
14930
|
await mkdir5(dirname3(FILE), { recursive: true });
|
|
14784
|
-
await
|
|
14931
|
+
await writeFile10(FILE, JSON.stringify(cache2));
|
|
14785
14932
|
} catch {
|
|
14786
14933
|
}
|
|
14787
14934
|
}
|
|
@@ -14842,6 +14989,7 @@ function agentArgIndex(pos) {
|
|
|
14842
14989
|
case "board":
|
|
14843
14990
|
case "conversations":
|
|
14844
14991
|
case "schedule":
|
|
14992
|
+
case "watch":
|
|
14845
14993
|
return 2;
|
|
14846
14994
|
case "try":
|
|
14847
14995
|
case "chat":
|
|
@@ -14861,7 +15009,7 @@ function agentArgIndex(pos) {
|
|
|
14861
15009
|
}
|
|
14862
15010
|
|
|
14863
15011
|
// src/completion.ts
|
|
14864
|
-
import { mkdir as mkdir6, readFile as readFile10, writeFile as
|
|
15012
|
+
import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile11 } from "node:fs/promises";
|
|
14865
15013
|
import { homedir as homedir4 } from "node:os";
|
|
14866
15014
|
import { join as join8 } from "node:path";
|
|
14867
15015
|
var GLOBAL_FLAGS = ["--json", "--token", "--profile", "--yes", "--help", "--version", "--lang", "--dry-run"];
|
|
@@ -14913,7 +15061,7 @@ async function names() {
|
|
|
14913
15061
|
const workspaces = [...new Set(list2.map((a) => a.espacio?.tipo === "workspace" ? a.espacio.nombre : void 0).filter((x) => !!x))];
|
|
14914
15062
|
await mkdir6(join8(cacheFile(), ".."), { recursive: true }).catch(() => {
|
|
14915
15063
|
});
|
|
14916
|
-
await
|
|
15064
|
+
await writeFile11(cacheFile(), JSON.stringify({ at: Date.now(), agents, workspaces })).catch(() => {
|
|
14917
15065
|
});
|
|
14918
15066
|
return { agents, workspaces };
|
|
14919
15067
|
}
|
|
@@ -15000,24 +15148,24 @@ init_i18n();
|
|
|
15000
15148
|
// src/commands/spec.ts
|
|
15001
15149
|
var import_yaml = __toESM(require_dist(), 1);
|
|
15002
15150
|
init_api();
|
|
15003
|
-
import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as
|
|
15151
|
+
import { mkdir as mkdir7, readdir as readdir4, readFile as readFile11, stat as stat2, writeFile as writeFile12 } from "node:fs/promises";
|
|
15004
15152
|
import { join as join9, resolve as resolve2 } from "node:path";
|
|
15005
15153
|
init_util();
|
|
15006
15154
|
init_i18n();
|
|
15007
|
-
var
|
|
15155
|
+
var enc13 = encodeURIComponent;
|
|
15008
15156
|
async function agentsExport(id, v) {
|
|
15009
15157
|
if (!id) throw usage(t("Missing agent.", "Falta el agente."), t("Example", "Ejemplo") + ": ghosty agents export pia-0 > pia.yaml \xB7 ghosty agents export pia-0 --out ./pia");
|
|
15010
|
-
const yaml = await (await request(`/api/v2/agents/${
|
|
15158
|
+
const yaml = await (await request(`/api/v2/agents/${enc13(id)}/export`)).text();
|
|
15011
15159
|
if (!v.out) {
|
|
15012
15160
|
if (globals.json) return out((0, import_yaml.parse)(yaml));
|
|
15013
15161
|
return void process.stdout.write(yaml);
|
|
15014
15162
|
}
|
|
15015
15163
|
const dir = resolve2(v.out);
|
|
15016
15164
|
await mkdir7(dir, { recursive: true });
|
|
15017
|
-
await
|
|
15165
|
+
await writeFile12(join9(dir, "agent.yaml"), yaml);
|
|
15018
15166
|
const own = (0, import_yaml.parse)(yaml)["x-ghosty"]?.skills?.own ?? [];
|
|
15019
15167
|
for (const slug of own) {
|
|
15020
|
-
const sk = await api(`/api/v2/agents/${
|
|
15168
|
+
const sk = await api(`/api/v2/agents/${enc13(id)}/skills/${enc13(slug)}`);
|
|
15021
15169
|
await writeSkill(join9(dir, "skills", slug), sk);
|
|
15022
15170
|
}
|
|
15023
15171
|
out({ saved: dir, skills: own }, () => `${lavender("\u2713")} ${dir}/agent.yaml${own.length ? ` + skills/ (${own.join(", ")})` : ""}
|
|
@@ -15076,8 +15224,8 @@ ${dim(t("Then every setting of the file is applied. Dry run: nothing created.",
|
|
|
15076
15224
|
}
|
|
15077
15225
|
const spec = JSON.stringify(v.create ? { ...file, title: void 0 } : file);
|
|
15078
15226
|
const body = { spec, ...files ? { files } : {}, ...v.prune ? { prune: true } : {}, ...v["no-notify"] ? { notify: false } : {} };
|
|
15079
|
-
const
|
|
15080
|
-
const plan = await api(
|
|
15227
|
+
const path2 = `/api/v2/agents/${enc13(id)}/apply`;
|
|
15228
|
+
const plan = await api(path2, { method: "POST", body: { ...body, dryRun: true } });
|
|
15081
15229
|
const actionable = plan.plan.filter((c) => c.op !== "!");
|
|
15082
15230
|
const text2 = plan.plan.map(line).join("\n") || dim(t("No changes: the agent already matches the file.", "Sin cambios: el agente ya coincide con el archivo."));
|
|
15083
15231
|
if (v["dry-run"] || !actionable.length) return out(plan, () => `${bold(id)}
|
|
@@ -15087,7 +15235,7 @@ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}` : "
|
|
|
15087
15235
|
if (!v.create) requireYesIfHeadless(v.yes, retry);
|
|
15088
15236
|
if (!v.create) await confirm(`${text2}
|
|
15089
15237
|
${t(`Apply ${actionable.length} change(s) to ${id}?`, `\xBFAplicar ${actionable.length} cambio(s) a ${id}?`)}`, { yes: v.yes, retry });
|
|
15090
|
-
const r = await api(
|
|
15238
|
+
const r = await api(path2, { method: "POST", body });
|
|
15091
15239
|
out(r, () => `${lavender("\u2713")} ${t(`${r.applied?.length ?? 0} change(s) applied to ${id}.`, `${r.applied?.length ?? 0} cambio(s) aplicados a ${id}.`)}${r.failed ? `
|
|
15092
15240
|
${red(t(`Stopped at \xAB${r.failed.what}\xBB: ${serverText(r.failed.error)}`, `Se detuvo en \xAB${r.failed.what}\xBB: ${r.failed.error}`))}` : ""}`);
|
|
15093
15241
|
}
|
|
@@ -15100,8 +15248,8 @@ async function planCmd(sub, slug, v, context) {
|
|
|
15100
15248
|
if (sub !== "apply") throw usage(t(`Unknown plan subcommand: ${sub ?? "(none)"}.`, `Subcomando desconocido de plan: ${sub ?? "(ninguno)"}.`), "ghosty plan apply <workspace> [--dry-run]");
|
|
15101
15249
|
slug ??= context;
|
|
15102
15250
|
if (!slug) throw usage(t("Missing workspace.", "Falta el espacio."), "ghosty plan apply <workspace> [--dry-run] (or ghosty use <workspace>)");
|
|
15103
|
-
const
|
|
15104
|
-
const plan = await api(
|
|
15251
|
+
const path2 = `/api/v2/workspaces/${encodeURIComponent(slug)}/plan/apply`;
|
|
15252
|
+
const plan = await api(path2, { method: "POST", body: { dryRun: true } });
|
|
15105
15253
|
const money2 = (n) => `$${n.toLocaleString("es-MX")} MXN`;
|
|
15106
15254
|
const text = [
|
|
15107
15255
|
`${bold(plan.workspace)} ${dim(plan.scheduledFor ? t(`scheduled for ${plan.scheduledFor.slice(0, 10)}`, `programado para el ${plan.scheduledFor.slice(0, 10)}`) : "")}`,
|
|
@@ -15113,7 +15261,7 @@ ${dim(t("Dry run: nothing applied.", "Simulaci\xF3n: no se aplic\xF3 nada."))}`)
|
|
|
15113
15261
|
requireYesIfHeadless(v.yes, `ghosty plan apply ${slug}`);
|
|
15114
15262
|
await confirm(`${text}
|
|
15115
15263
|
${t("Apply this plan now?", "\xBFAplicar este plan ya?")}`, { yes: v.yes, retry: `ghosty plan apply ${slug}` });
|
|
15116
|
-
const r = await api(
|
|
15264
|
+
const r = await api(path2, { method: "POST", body: {} });
|
|
15117
15265
|
out(r, () => `${lavender("\u2713")} ${r.applied ? t(`Plan applied to ${r.workspace}.`, `Plan aplicado a ${r.workspace}.`) : t("Nothing applied.", "No se aplic\xF3 nada.")}`);
|
|
15118
15266
|
}
|
|
15119
15267
|
async function boxesUsage(slug) {
|
|
@@ -15137,17 +15285,17 @@ ${dim(t(`${r.totals.activeTurns} turn(s) running`, `${r.totals.activeTurns} turn
|
|
|
15137
15285
|
init_api();
|
|
15138
15286
|
init_auth();
|
|
15139
15287
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
15140
|
-
import { mkdir as mkdir8, readFile as readFile12, writeFile as
|
|
15288
|
+
import { mkdir as mkdir8, readFile as readFile12, writeFile as writeFile13 } from "node:fs/promises";
|
|
15141
15289
|
import { homedir as homedir5 } from "node:os";
|
|
15142
15290
|
import { dirname as dirname4, join as join10 } from "node:path";
|
|
15143
15291
|
init_i18n();
|
|
15144
15292
|
init_util();
|
|
15145
|
-
var
|
|
15293
|
+
var enc14 = encodeURIComponent;
|
|
15146
15294
|
var hora = (iso) => new Date(iso).toLocaleString("es-MX", { timeZone: "America/Mexico_City", hour12: false, dateStyle: "short", timeStyle: "short" });
|
|
15147
15295
|
async function history(agent, v) {
|
|
15148
15296
|
if (!agent) throw usage(t("Missing agent.", "Falta el agente."), "ghosty demos history <agent> [--since 2d] [--session web:\u2026] [--timeline] [--full]");
|
|
15149
15297
|
const q = new URLSearchParams({ since: v.since ?? "7d", ...v.session ? { session: v.session } : {}, ...v.timeline ? { timeline: "1" } : {}, ...v.full ? { full: "1" } : {} });
|
|
15150
|
-
const r = await api(`/api/v2/agents/${
|
|
15298
|
+
const r = await api(`/api/v2/agents/${enc14(agent)}/demo/conversations?${q}`);
|
|
15151
15299
|
out(r, () => {
|
|
15152
15300
|
const lines = [`${bold(r.agent.name ?? r.agent.id)} ${dim(`/p/${r.agent.slug ?? "\u2014"} \xB7 ${t("since", "desde")} ${hora(r.since)} \xB7 ${t("clones", "clones")}: ${r.clones.length}`)}`];
|
|
15153
15301
|
for (const c of r.clones) lines.push(dim(` ${t("clone", "clon")} ${c.id} (${hora(c.createdAt)})${c.sponsored ? t(" \xB7 on your key", " \xB7 con tu llave") : ""}`));
|
|
@@ -15176,21 +15324,21 @@ function anonCookieOf(setCookie) {
|
|
|
15176
15324
|
async function visitorCookie(base, slug, fresh) {
|
|
15177
15325
|
const jar = await readJar();
|
|
15178
15326
|
if (jar[slug] && !fresh) return jar[slug];
|
|
15179
|
-
const res = await fetch(`${base}/p/${
|
|
15327
|
+
const res = await fetch(`${base}/p/${enc14(slug)}`, { redirect: "manual" });
|
|
15180
15328
|
if (res.status === 404) throw new CliError(t(`No demo \xAB${slug}\xBB (the link is name-code, e.g. guardias-k3x9q2).`, `No hay demo \xAB${slug}\xBB (la liga es nombre-c\xF3digo, p. ej. guardias-k3x9q2).`), EXIT.API);
|
|
15181
15329
|
const cookie = anonCookieOf(res.headers.get("set-cookie"));
|
|
15182
15330
|
if (!cookie) throw new CliError(t("The demo didn't hand out a visitor cookie.", "El demo no entreg\xF3 cookie de visitante."), EXIT.API);
|
|
15183
15331
|
jar[slug] = cookie;
|
|
15184
15332
|
await mkdir8(dirname4(jarPath()), { recursive: true });
|
|
15185
|
-
await
|
|
15333
|
+
await writeFile13(jarPath(), JSON.stringify(jar, null, 2), { mode: 384 });
|
|
15186
15334
|
return cookie;
|
|
15187
15335
|
}
|
|
15188
15336
|
async function visitorTurn(base, slug, cookie, text) {
|
|
15189
15337
|
const turnId = randomUUID2();
|
|
15190
15338
|
const ctrl = new AbortController();
|
|
15191
|
-
const ev = await fetch(`${base}/p/${
|
|
15339
|
+
const ev = await fetch(`${base}/p/${enc14(slug)}/events`, { headers: { cookie, accept: "text/event-stream" }, signal: ctrl.signal });
|
|
15192
15340
|
if (!ev.ok) throw await toError(ev);
|
|
15193
|
-
const sent = await fetch(`${base}/p/${
|
|
15341
|
+
const sent = await fetch(`${base}/p/${enc14(slug)}/messages`, { method: "POST", headers: { cookie, "content-type": "application/json" }, body: JSON.stringify({ content: text, turnId }) });
|
|
15194
15342
|
if (!sent.ok) {
|
|
15195
15343
|
ctrl.abort();
|
|
15196
15344
|
const body = await sent.text();
|
|
@@ -15220,7 +15368,7 @@ async function visitorTurn(base, slug, cookie, text) {
|
|
|
15220
15368
|
async function recycle(agent) {
|
|
15221
15369
|
for (let i = 0; i < 10; i++) {
|
|
15222
15370
|
try {
|
|
15223
|
-
await api(`/api/v2/agents/${
|
|
15371
|
+
await api(`/api/v2/agents/${enc14(agent)}/box`, { method: "POST", body: { recycle: true } });
|
|
15224
15372
|
return;
|
|
15225
15373
|
} catch (e) {
|
|
15226
15374
|
if (!(e instanceof CliError) || !/turno|turn/i.test(e.message)) throw e;
|
|
@@ -15276,10 +15424,10 @@ async function collectSiteFiles(target) {
|
|
|
15276
15424
|
const full = join11(dir, e.name);
|
|
15277
15425
|
if (e.isDirectory()) await walk(full);
|
|
15278
15426
|
else if (e.isFile()) {
|
|
15279
|
-
const
|
|
15280
|
-
const ext =
|
|
15427
|
+
const path2 = relative2(target, full).split(sep).join("/");
|
|
15428
|
+
const ext = path2.split(".").pop()?.toLowerCase() ?? "";
|
|
15281
15429
|
const buf = await readFile13(full);
|
|
15282
|
-
files.push(TEXT_EXT.has(ext) ? { path, content: buf.toString("utf8"), encoding: "text" } : { path, content: buf.toString("base64"), encoding: "base64" });
|
|
15430
|
+
files.push(TEXT_EXT.has(ext) ? { path: path2, content: buf.toString("utf8"), encoding: "text" } : { path: path2, content: buf.toString("base64"), encoding: "base64" });
|
|
15283
15431
|
}
|
|
15284
15432
|
}
|
|
15285
15433
|
};
|
|
@@ -15400,9 +15548,9 @@ async function domainCmd(action, a2, a3) {
|
|
|
15400
15548
|
const acts = /* @__PURE__ */ new Set(["ls", "list", "add", "verify", "rm", "remove"]);
|
|
15401
15549
|
const [act, slug, host] = action && !acts.has(action) ? ["ls", action, a2] : [action ?? "ls", a2, a3];
|
|
15402
15550
|
if (!slug) throw usage(t("Missing the app (slug, id or URL).", "Falta la app (slug, id o URL)."), ex);
|
|
15403
|
-
const
|
|
15551
|
+
const path2 = `/api/v2/sites/${encodeURIComponent(slug)}/domain`;
|
|
15404
15552
|
if (act === "ls" || act === "list") {
|
|
15405
|
-
const r2 = await api(
|
|
15553
|
+
const r2 = await api(path2);
|
|
15406
15554
|
return out(
|
|
15407
15555
|
r2,
|
|
15408
15556
|
() => r2.domains.length ? `${bold(r2.url)}
|
|
@@ -15411,18 +15559,18 @@ ${table(r2.domains.map((d) => ({ domain: d.host, state: d.verified ? t("verified
|
|
|
15411
15559
|
}
|
|
15412
15560
|
if (!host) throw usage(t("Missing the domain.", "Falta el dominio."), ex);
|
|
15413
15561
|
if (act === "rm" || act === "remove") {
|
|
15414
|
-
const r2 = await api(
|
|
15562
|
+
const r2 = await api(path2, { method: "DELETE", body: { domain: host } });
|
|
15415
15563
|
return out(r2, () => `${lavender("\u2713")} ${t("Removed", "Quitado")} ${bold(r2.host)}`);
|
|
15416
15564
|
}
|
|
15417
15565
|
if (act !== "add" && act !== "verify") throw usage(t(`Unknown domain action: ${act}.`, `Acci\xF3n de dominio desconocida: ${act}.`), ex);
|
|
15418
|
-
const r = await api(
|
|
15566
|
+
const r = await api(path2, { method: "POST", body: { domain: host, ...act === "verify" ? { verify: true } : {} } });
|
|
15419
15567
|
return out(r, () => `${r.verified ? lavender("\u2713") : dim("\u2026")} ${bold(r.host)}
|
|
15420
15568
|
${domainLine(r, t("en", "es") === "es" ? "es" : "en").replace("<slug>", slug)}`);
|
|
15421
15569
|
}
|
|
15422
15570
|
|
|
15423
15571
|
// src/commands/docs.ts
|
|
15424
15572
|
init_api();
|
|
15425
|
-
import { writeFile as
|
|
15573
|
+
import { writeFile as writeFile14 } from "node:fs/promises";
|
|
15426
15574
|
|
|
15427
15575
|
// ../docs/src/core/doc-blocks.ts
|
|
15428
15576
|
var DOC_ENVELOPE_VERSION = 1;
|
|
@@ -15498,8 +15646,8 @@ function blockTextMapped(b) {
|
|
|
15498
15646
|
// src/commands/docs.ts
|
|
15499
15647
|
init_util();
|
|
15500
15648
|
init_i18n();
|
|
15501
|
-
var
|
|
15502
|
-
var docPath = (id, rest = "") => `/api/v2/me/docs/${
|
|
15649
|
+
var enc15 = encodeURIComponent;
|
|
15650
|
+
var docPath = (id, rest = "") => `/api/v2/me/docs/${enc15(id)}${rest}`;
|
|
15503
15651
|
function docRef(ref) {
|
|
15504
15652
|
const m = /\/docs\/([^/?#\s]+)/.exec(ref);
|
|
15505
15653
|
return m && /^https?:\/\//.test(ref) ? decodeURIComponent(m[1]) : ref.trim();
|
|
@@ -15585,7 +15733,7 @@ ${t("Ids: ghosty me docs ls", "Ids: ghosty me docs ls")}`);
|
|
|
15585
15733
|
}
|
|
15586
15734
|
const named = /filename\*=UTF-8''([^;]+)/i.exec(res.headers.get("content-disposition") ?? "")?.[1];
|
|
15587
15735
|
const file = v.out ?? (named ? decodeURIComponent(named) : fileName(id, format));
|
|
15588
|
-
await
|
|
15736
|
+
await writeFile14(file, bytes);
|
|
15589
15737
|
return out({ id, format, saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${file} (${bytes.length} bytes)`);
|
|
15590
15738
|
}
|
|
15591
15739
|
case "check": {
|
|
@@ -15611,7 +15759,7 @@ ${t("Ids: ghosty me docs ls", "Ids: ghosty me docs ls")}`);
|
|
|
15611
15759
|
if (res.status === 204) return out({ id, block, segment, segments, saved: null }, () => dim(t("That block has no text to read.", "Ese bloque no tiene texto que leer.")));
|
|
15612
15760
|
const bytes = Buffer.from(await res.arrayBuffer());
|
|
15613
15761
|
const file = v.out ?? `${id}-${block}${segment !== null ? `-${segment}` : ""}.${audioExt(res.headers.get("content-type"))}`;
|
|
15614
|
-
await
|
|
15762
|
+
await writeFile14(file, bytes);
|
|
15615
15763
|
return out({ id, block, segment, segments, saved: file, bytes: bytes.length }, () => `${lavender("\u2713")} ${file} (${bytes.length} bytes) ${dim(t(`\xB7 the block has ${segments} sentence(s): --segment 0\u2026${Math.max(segments - 1, 0)}`, `\xB7 el bloque tiene ${segments} frase(s): --segment 0\u2026${Math.max(segments - 1, 0)}`))}`);
|
|
15616
15764
|
}
|
|
15617
15765
|
case "restore": {
|
|
@@ -15630,8 +15778,8 @@ init_api();
|
|
|
15630
15778
|
init_auth();
|
|
15631
15779
|
init_util();
|
|
15632
15780
|
init_i18n();
|
|
15633
|
-
var
|
|
15634
|
-
var agentPath3 = (id, rest = "") => `/api/v2/agents/${
|
|
15781
|
+
var enc16 = encodeURIComponent;
|
|
15782
|
+
var agentPath3 = (id, rest = "") => `/api/v2/agents/${enc16(id)}${rest}`;
|
|
15635
15783
|
async function agentsToken(sub, id, v) {
|
|
15636
15784
|
const ex = "ghosty agents token rotate|revoke <agent-id>";
|
|
15637
15785
|
if (sub !== "rotate" && sub !== "revoke") throw usage(t(`Unknown agents token subcommand: ${sub ?? "(none)"}.`, `Subcomando de agents token desconocido: ${sub ?? "(ninguno)"}.`), ex);
|
|
@@ -15653,21 +15801,21 @@ async function agentsToken(sub, id, v) {
|
|
|
15653
15801
|
const r = await api(agentPath3(id, "/token/revoke"), { method: "POST", body: {} });
|
|
15654
15802
|
return out(r, () => `${lavender("\u2713")} ${r.hadToken ? t("Token revoked: nothing gets in with it.", "Token revocado: ya no entra nada con \xE9l.") : t("It had no token; the channel stays closed.", "No ten\xEDa token; el canal sigue cerrado.")} ${dim(t(`New one: ghosty agents token rotate ${id}`, `Uno nuevo: ghosty agents token rotate ${id}`))}`);
|
|
15655
15803
|
}
|
|
15656
|
-
var
|
|
15804
|
+
var short3 = (s2, n = 70) => s2.length > n ? s2.slice(0, n - 1) + "\u2026" : s2;
|
|
15657
15805
|
function memoryRows(r) {
|
|
15658
15806
|
return {
|
|
15659
|
-
notes: r.notes.map((n) => ({ id: n.id, scope: n.scope || t("all", "todo"), rule:
|
|
15660
|
-
promos: r.promos.map((p) => ({ id: p.id, title:
|
|
15807
|
+
notes: r.notes.map((n) => ({ id: n.id, scope: n.scope || t("all", "todo"), rule: short3(n.content), by: n.source === "agent" ? t("agent", "agente") : t("team", "equipo") })),
|
|
15808
|
+
promos: r.promos.map((p) => ({ id: p.id, title: short3(p.title, 40), when: p.validFrom || p.validUntil ? `${p.validFrom ?? "\u2026"} \u2192 ${p.validUntil ?? "\u2026"}` : t("always", "siempre"), state: !p.active ? t("off", "apagada") : p.live ? t("live today", "vigente hoy") : t("on, not today", "prendida, hoy no") }))
|
|
15661
15809
|
};
|
|
15662
15810
|
}
|
|
15663
15811
|
async function agentsMemory(sub, id, args, v) {
|
|
15664
15812
|
const ex = 'ghosty agents memory ls <agent-id> | add <agent-id> "rule" [--scope wa] | edit <agent-id> <rule-id> "rule" | add <agent-id> --promo --title T --details D [--from YYYY-MM-DD] [--until YYYY-MM-DD] | rm <agent-id> <id> | enable|disable <agent-id> <promo-id>';
|
|
15665
15813
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), ex);
|
|
15666
|
-
const
|
|
15814
|
+
const path2 = agentPath3(id, "/memory");
|
|
15667
15815
|
switch (sub ?? "ls") {
|
|
15668
15816
|
case "ls":
|
|
15669
15817
|
case "list": {
|
|
15670
|
-
const r = await api(
|
|
15818
|
+
const r = await api(path2);
|
|
15671
15819
|
const rows = memoryRows(r);
|
|
15672
15820
|
return out(
|
|
15673
15821
|
r,
|
|
@@ -15680,33 +15828,33 @@ async function agentsMemory(sub, id, args, v) {
|
|
|
15680
15828
|
case "add": {
|
|
15681
15829
|
if (v.promo) {
|
|
15682
15830
|
if (!v.title || !v.details) throw usage(t("A promo needs --title and --details.", "Una promo necesita --title y --details."), ex);
|
|
15683
|
-
const r2 = await api(
|
|
15831
|
+
const r2 = await api(path2, { method: "POST", body: { promo: { title: v.title, details: v.details, ...v.from ? { validFrom: v.from } : {}, ...v.until ? { validUntil: v.until } : {} } } });
|
|
15684
15832
|
return out(r2, () => `${lavender("\u2713")} ${t("Promo saved", "Promo guardada")} ${r2.promo.id}${r2.warning ? dim(` (${serverText(r2.warning)})`) : ""}`);
|
|
15685
15833
|
}
|
|
15686
15834
|
const content = args.join(" ").trim();
|
|
15687
15835
|
if (!content) throw usage(t("Missing the rule.", "Falta la regla."), ex);
|
|
15688
|
-
const r = await api(
|
|
15836
|
+
const r = await api(path2, { method: "POST", body: { content, ...v.scope ? { scope: v.scope } : {} } });
|
|
15689
15837
|
return out(r, () => `${lavender("\u2713")} ${t("Saved", "Guardada")} ${r.note.id} ${dim(r.note.scope ? t(`(only ${r.note.scope})`, `(s\xF3lo ${r.note.scope})`) : t("(whole agent)", "(todo el agente)"))}`);
|
|
15690
15838
|
}
|
|
15691
15839
|
case "edit": {
|
|
15692
15840
|
const [ref, ...rest] = args;
|
|
15693
15841
|
const content = rest.join(" ").trim();
|
|
15694
15842
|
if (!ref || !content) throw usage(t("Missing the rule id or its new text.", "Falta el id de la regla o su texto nuevo."), `ghosty agents memory edit ${id} <rule-id> "\u2026"`);
|
|
15695
|
-
const r = await api(
|
|
15843
|
+
const r = await api(path2, { method: "POST", body: { content, replaces: ref, ...v.scope !== void 0 ? { scope: v.scope } : {} } });
|
|
15696
15844
|
return out(r, () => `${lavender("\u2713")} ${t("Updated", "Actualizada")} ${r.note.id}`);
|
|
15697
15845
|
}
|
|
15698
15846
|
case "rm":
|
|
15699
15847
|
case "forget": {
|
|
15700
15848
|
const ref = args[0];
|
|
15701
15849
|
if (!ref) throw usage(t("Missing the rule or promo id.", "Falta el id de la regla o la promo."), `ghosty agents memory rm ${id} <id> (ids: ghosty agents memory ls ${id})`);
|
|
15702
|
-
const r = await api(
|
|
15850
|
+
const r = await api(path2, { method: "DELETE", body: { id: ref } });
|
|
15703
15851
|
return out(r, () => `${lavender("\u2713")} ${r.kind === "promo" ? t("Promo deleted", "Promo borrada") : t("Rule forgotten", "Regla olvidada")} ${r.deleted}`);
|
|
15704
15852
|
}
|
|
15705
15853
|
case "enable":
|
|
15706
15854
|
case "disable": {
|
|
15707
15855
|
const ref = args[0];
|
|
15708
15856
|
if (!ref) throw usage(t("Missing the promo id.", "Falta el id de la promo."), `ghosty agents memory ${sub} ${id} <promo-id>`);
|
|
15709
|
-
const r = await api(
|
|
15857
|
+
const r = await api(path2, { method: "POST", body: { promo: { id: ref, active: sub === "enable" } } });
|
|
15710
15858
|
return out(r, () => `${lavender("\u2713")} ${r.promo.title}: ${r.promo.active ? t("on", "prendida") : t("off (kept)", "apagada (se conserva)")}`);
|
|
15711
15859
|
}
|
|
15712
15860
|
default:
|
|
@@ -15722,8 +15870,8 @@ async function agentsTransfer(id, v) {
|
|
|
15722
15870
|
if (!id) throw usage(t("Missing agent id.", "Falta el id del agente."), ex);
|
|
15723
15871
|
if (!v.to && !v.workspace) throw usage(t("Say where: --to EMAIL (another person) or --workspace SLUG|none (one of your workspaces).", "Di a d\xF3nde: --to CORREO (otra persona) o --workspace SLUG|none (uno de tus espacios)."), ex);
|
|
15724
15872
|
const body = { ...v.to ? { to: v.to } : {}, ...v.workspace ? { workspace: v.workspace } : {}, ...v.force ? { force: true } : {} };
|
|
15725
|
-
const
|
|
15726
|
-
const plan = await api(
|
|
15873
|
+
const path2 = agentPath3(id, "/transfer");
|
|
15874
|
+
const plan = await api(path2, { method: "POST", body: { ...body, dryRun: true } });
|
|
15727
15875
|
const text = transferText(plan);
|
|
15728
15876
|
const retry = `ghosty agents transfer ${id}${v.to ? ` --to ${v.to}` : ""}${v.workspace ? ` --workspace ${v.workspace}` : ""}${v.force ? " --force" : ""}`;
|
|
15729
15877
|
if (v["dry-run"]) return out(plan, () => `${text}
|
|
@@ -15732,13 +15880,13 @@ ${dim(applyHint(retry))}`);
|
|
|
15732
15880
|
requireYesIfHeadless(v.yes, retry);
|
|
15733
15881
|
await confirm(`${text}
|
|
15734
15882
|
${v.to ? t("The agent, its box and its memory go to that person. You lose it.", "El agente, su caja y su memoria pasan a esa persona. T\xFA lo pierdes.") : t("Move it?", "\xBFMoverlo?")}`, { yes: v.yes, ...v.to ? { expect: v.to } : {}, retry });
|
|
15735
|
-
const r = await api(
|
|
15883
|
+
const r = await api(path2, { method: "POST", body });
|
|
15736
15884
|
if (r.kind === "move" && r.failed.length) return out(r, () => `${transferText(r)}
|
|
15737
15885
|
${t(`Moved, but these steps failed: ${r.failed.join(", ")}. Check the agent (ghosty agents doctor ${id}).`, `Se movi\xF3, pero fallaron estos pasos: ${r.failed.join(", ")}. Revisa el agente (ghosty agents doctor ${id}).`)}`);
|
|
15738
15886
|
return out(r, () => `${lavender("\u2713")} ${r.kind === "transfer" ? t(`Handed over to ${r.to}.`, `Cedido a ${r.to}.`) : t(`Moved to ${r.to}.`, `Movido a ${r.to}.`)}`);
|
|
15739
15887
|
}
|
|
15740
|
-
async function openPage(
|
|
15741
|
-
const url = `${await baseUrl()}${
|
|
15888
|
+
async function openPage(path2, what, v) {
|
|
15889
|
+
const url = `${await baseUrl()}${path2}`;
|
|
15742
15890
|
if (globals.json) return out({ url });
|
|
15743
15891
|
info(`${lavender("\u203A")} ${what}: ${url}`);
|
|
15744
15892
|
if (!v["no-open"]) await openUrl(url);
|
|
@@ -15785,15 +15933,15 @@ import { randomBytes as randomBytes2 } from "node:crypto";
|
|
|
15785
15933
|
import { existsSync, readFileSync } from "node:fs";
|
|
15786
15934
|
init_util();
|
|
15787
15935
|
init_i18n();
|
|
15788
|
-
var
|
|
15936
|
+
var enc17 = encodeURIComponent;
|
|
15789
15937
|
var money = (n) => n === null || n === void 0 ? "\u2014" : `$${n.toFixed(2)}`;
|
|
15790
15938
|
var mins = (n) => n === null || n === void 0 ? "\u2014" : n >= 120 ? `${(n / 60).toFixed(1)} h` : `${n} min`;
|
|
15791
15939
|
var hhmm = (iso) => new Date(iso).toLocaleTimeString("es-MX", { hour: "2-digit", minute: "2-digit", hour12: false });
|
|
15792
|
-
var
|
|
15940
|
+
var short4 = (s2) => (s2 ?? "").slice(0, 16).replace("T", " ");
|
|
15793
15941
|
async function roomsCmd(ws, verb, repo, v) {
|
|
15794
|
-
const
|
|
15942
|
+
const path2 = `/api/v2/workspaces/${enc17(ws)}/factory/rooms`;
|
|
15795
15943
|
if (!verb || verb === "ls" || verb === "list") {
|
|
15796
|
-
const r2 = await api(
|
|
15944
|
+
const r2 = await api(path2);
|
|
15797
15945
|
const rows = r2.rooms.filter((x) => x.repos.length || v.full).map((x) => ({ room: `#${x.slug}`, repos: x.repos.join(", ") || dim("\u2014"), factory: x.factory ? lavender(t("yes", "s\xED")) : dim("\u2014"), private: x.private ? t("yes", "s\xED") : "" }));
|
|
15798
15946
|
return out(
|
|
15799
15947
|
r2,
|
|
@@ -15805,7 +15953,7 @@ async function roomsCmd(ws, verb, repo, v) {
|
|
|
15805
15953
|
);
|
|
15806
15954
|
}
|
|
15807
15955
|
if (verb === "repos") {
|
|
15808
|
-
const r2 = await api(`${
|
|
15956
|
+
const r2 = await api(`${path2}?repos=1`);
|
|
15809
15957
|
return out(r2, () => {
|
|
15810
15958
|
if (!r2.connected) return red(t(`GitHub isn't connected: ${r2.detail ?? "connect it with ghosty connectors connect github"}`, `GitHub no est\xE1 conectado: ${r2.detail ?? "con\xE9ctalo con ghosty connectors connect github"}`));
|
|
15811
15959
|
if (!r2.installed) return red(t(`The Ghosty GitHub App isn't installed: ${r2.installUrl ?? ""}`, `La GitHub App de Ghosty no est\xE1 instalada: ${r2.installUrl ?? ""}`));
|
|
@@ -15823,10 +15971,10 @@ async function roomsCmd(ws, verb, repo, v) {
|
|
|
15823
15971
|
const retry = `ghosty factory rooms disconnect ${repo} --room ${v.room} --workspace ${ws}`;
|
|
15824
15972
|
requireYesIfHeadless(v.yes, retry);
|
|
15825
15973
|
await confirm(t(`Disconnect ${repo} from #${v.room}?`, `\xBFDesconectar ${repo} de #${v.room}?`), { yes: v.yes, retry });
|
|
15826
|
-
const r2 = await api(
|
|
15974
|
+
const r2 = await api(path2, { body: { action: "disconnect", repo, room: v.room } });
|
|
15827
15975
|
return out(r2, () => `${lavender("\u2713")} ${t(`${repo} is no longer connected to #${r2.room}.`, `${repo} ya no est\xE1 conectado a #${r2.room}.`)}`);
|
|
15828
15976
|
}
|
|
15829
|
-
const r = await api(
|
|
15977
|
+
const r = await api(path2, { body: { action: "connect", repo, room: v.room, ...v.create ? { create: true } : {}, ...v.private ? { private: true } : {} } });
|
|
15830
15978
|
const rd = r.readiness;
|
|
15831
15979
|
return out(
|
|
15832
15980
|
r,
|
|
@@ -15841,9 +15989,9 @@ async function roomsCmd(ws, verb, repo, v) {
|
|
|
15841
15989
|
);
|
|
15842
15990
|
}
|
|
15843
15991
|
async function sprintsCmd(ws, verb, ref, v) {
|
|
15844
|
-
const
|
|
15992
|
+
const path2 = `/api/v2/workspaces/${enc17(ws)}/factory/sprints`;
|
|
15845
15993
|
if (!verb || verb === "ls" || verb === "list") {
|
|
15846
|
-
const r2 = await api(
|
|
15994
|
+
const r2 = await api(path2);
|
|
15847
15995
|
const rows = r2.sprints.map((x) => ({ sprint: `#${x.id}`, title: x.title.slice(0, 60), status: x.status, room: x.room ? `#${x.room}` : "", merged: `${x.merged}/${x.total}` }));
|
|
15848
15996
|
return out(r2, () => rows.length ? table(rows, ["sprint", "title", "status", "room", "merged"]) : dim(t("No sprints yet.", "Todav\xEDa no hay sprints.")));
|
|
15849
15997
|
}
|
|
@@ -15855,7 +16003,7 @@ async function sprintsCmd(ws, verb, ref, v) {
|
|
|
15855
16003
|
requireYesIfHeadless(v.yes, retry);
|
|
15856
16004
|
await confirm(t(`Approve sprint #${id}? Every included ticket starts, one at a time.`, `\xBFAprobar el sprint #${id}? Arrancan todos los tickets incluidos, uno a la vez.`), { yes: v.yes, retry });
|
|
15857
16005
|
}
|
|
15858
|
-
const r = verb === "approve" ? await api(`${
|
|
16006
|
+
const r = verb === "approve" ? await api(`${path2}/${id}`, { body: { action: "approve" } }) : await api(`${path2}/${id}`);
|
|
15859
16007
|
return out(
|
|
15860
16008
|
r,
|
|
15861
16009
|
() => [
|
|
@@ -15881,12 +16029,12 @@ function dotenvKeys(text) {
|
|
|
15881
16029
|
return text.split("\n").map((l) => l.trim().replace(/^export\s+/, "")).filter((l) => l && !l.startsWith("#") && l.indexOf("=") > 0).map((l) => l.slice(0, l.indexOf("=")).trim());
|
|
15882
16030
|
}
|
|
15883
16031
|
async function envCmd(ws, verb, ref, file, v) {
|
|
15884
|
-
const
|
|
16032
|
+
const path2 = `/api/v2/workspaces/${enc17(ws)}/factory/env`;
|
|
15885
16033
|
const repo = ref && ref.includes("/") ? ref : v.repo ?? repoHere();
|
|
15886
16034
|
if (ref && !ref.includes("/") && !file) file = ref;
|
|
15887
16035
|
if (!repo) throw usage(t("Which repo?", "\xBFQu\xE9 repo?"), `ghosty factory env ${verb ?? "ls"} OWNER/REPO --workspace ${ws}`);
|
|
15888
16036
|
if (!verb || verb === "ls" || verb === "list") {
|
|
15889
|
-
const r2 = await api(`${
|
|
16037
|
+
const r2 = await api(`${path2}?repo=${enc17(repo)}`);
|
|
15890
16038
|
const risk = new Map(r2.risks.map((x) => [x.key, x.risk]));
|
|
15891
16039
|
return out(
|
|
15892
16040
|
r2,
|
|
@@ -15925,7 +16073,7 @@ ${k}=${randomBytes2(32).toString("hex")}`;
|
|
|
15925
16073
|
v.replace ? t(`REPLACE the whole preview .env of ${repo} with ${keys.length} variables (${keys.join(", ")})?`, `\xBFREEMPLAZAR todo el .env de la preview de ${repo} con ${keys.length} variables (${keys.join(", ")})?`) : t(`Save ${keys.length} variables for the preview of ${repo} (${keys.join(", ")})? The others stay.`, `\xBFGuardar ${keys.length} variables para la preview de ${repo} (${keys.join(", ")})? Las dem\xE1s se quedan.`),
|
|
15926
16074
|
{ yes: v.yes, retry }
|
|
15927
16075
|
);
|
|
15928
|
-
const r = await api(
|
|
16076
|
+
const r = await api(path2, {
|
|
15929
16077
|
body: { repo, dotenv, ...v.replace ? { replace: true } : {} }
|
|
15930
16078
|
});
|
|
15931
16079
|
return out(
|
|
@@ -16026,7 +16174,7 @@ async function factoryCmd(sub, verb, ref, title, v, context) {
|
|
|
16026
16174
|
if (sub === "rooms") return roomsCmd(ws, verb, ref, v);
|
|
16027
16175
|
if (sub === "sprints") return sprintsCmd(ws, verb, ref, v);
|
|
16028
16176
|
if (sub === "env") return envCmd(ws, verb, ref, title, v);
|
|
16029
|
-
const base = `/api/v2/workspaces/${
|
|
16177
|
+
const base = `/api/v2/workspaces/${enc17(ws)}/factory/runs`;
|
|
16030
16178
|
if (sub === "run") {
|
|
16031
16179
|
const text = verb?.trim();
|
|
16032
16180
|
if (!text) throw usage(t("Missing the request.", "Falta el pedido."), `ghosty factory run "\u2026" --repo owner/name [--room R] [--wait] --workspace ${ws}`);
|
|
@@ -16043,13 +16191,13 @@ ${dim(t(`Wait for the plan: ghosty factory runs watch --thread ${r.threadId} --w
|
|
|
16043
16191
|
if (!verb || verb === "ls" || verb === "list") {
|
|
16044
16192
|
const qs = new URLSearchParams({ ...v.room ? { room: v.room } : {}, ...v.status ? { status: v.status } : {}, ...v.limit ? { limit: v.limit } : {} });
|
|
16045
16193
|
const r = await api(`${base}?${qs}`);
|
|
16046
|
-
const rows = r.runs.map((x) => ({ run: `#${x.id}`, title: x.title.slice(0, 60), status: x.status, room: x.room ? `#${x.room}` : "", pr: x.pr ? x.pr.replace(/^https:\/\/github\.com\//, "") : "", time: mins(x.durationMin), usd: money(x.usd), updated:
|
|
16194
|
+
const rows = r.runs.map((x) => ({ run: `#${x.id}`, title: x.title.slice(0, 60), status: x.status, room: x.room ? `#${x.room}` : "", pr: x.pr ? x.pr.replace(/^https:\/\/github\.com\//, "") : "", time: mins(x.durationMin), usd: money(x.usd), updated: short4(x.updatedAt) }));
|
|
16047
16195
|
return out(r, () => rows.length ? `${table(rows, ["run", "title", "status", "room", "pr", "time", "usd", "updated"])}
|
|
16048
16196
|
${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r.workspace}`)}` : dim(t(`No factory runs in ${r.workspace}.`, `No hay pedidos de la f\xE1brica en ${r.workspace}.`)));
|
|
16049
16197
|
}
|
|
16050
16198
|
if (verb === "stats") {
|
|
16051
16199
|
const qs = new URLSearchParams({ since: v.since ?? "30d", ...v.repo ? { repo: v.repo } : {} });
|
|
16052
|
-
const { stats: s2, workspace } = await api(`/api/v2/workspaces/${
|
|
16200
|
+
const { stats: s2, workspace } = await api(`/api/v2/workspaces/${enc17(ws)}/factory/stats?${qs}`);
|
|
16053
16201
|
return out({ workspace, stats: s2 }, () => {
|
|
16054
16202
|
if (!s2.runs) return dim(t(`No factory runs in ${workspace} in the last ${s2.sinceDays} days.`, `No hay pedidos de la f\xE1brica en ${workspace} en los \xFAltimos ${s2.sinceDays} d\xEDas.`));
|
|
16055
16203
|
return [
|
|
@@ -16069,9 +16217,9 @@ ${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r
|
|
|
16069
16217
|
return waitResult(ws, await waitRun(base, ws, ref ? { runId: Number(ref.replace(/^#/, "")) } : { thread }, timeoutS), timeoutS);
|
|
16070
16218
|
}
|
|
16071
16219
|
if (!ref) throw usage(t("Missing run.", "Falta el pedido."), `ghosty factory runs ${verb} <run> --workspace ${ws}`);
|
|
16072
|
-
const
|
|
16220
|
+
const path2 = `${base}/${enc17(ref.replace(/^#/, ""))}`;
|
|
16073
16221
|
if (verb === "show" && v.timing) {
|
|
16074
|
-
const { timing: tm, workspace } = await api(`${
|
|
16222
|
+
const { timing: tm, workspace } = await api(`${path2}/timing`);
|
|
16075
16223
|
return out({ workspace, timing: tm }, () => {
|
|
16076
16224
|
const c = tm.clock;
|
|
16077
16225
|
const head = [
|
|
@@ -16102,14 +16250,14 @@ ${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r
|
|
|
16102
16250
|
});
|
|
16103
16251
|
}
|
|
16104
16252
|
if (verb === "why") {
|
|
16105
|
-
const { why: w, workspace } = await api(`${
|
|
16253
|
+
const { why: w, workspace } = await api(`${path2}/why`, v.refresh ? { body: { refresh: true } } : void 0);
|
|
16106
16254
|
return out({ workspace, why: w }, () => {
|
|
16107
16255
|
const gap = w.incomplete?.length ? red(t(`Incomplete: no recorded turn for @${w.incomplete.join(", @")} (lost, e.g. a deploy while it closed); numbers don't add up.`, `Incompleto: no qued\xF3 registrado el turno de @${w.incomplete.join(", @")} (se perdi\xF3, p. ej. un deploy al cerrarlo); los n\xFAmeros no cuadran.`)) : null;
|
|
16108
16256
|
if (gap) return `${bold(`#${w.runId}`)} ${gap}`;
|
|
16109
16257
|
if (w.healthy) return `${lavender("\u2713")} #${w.runId} ${t("was healthy: no stuck patterns, no long @build, no loops, no red CI.", "fue sano: sin atascos, sin @build largo, sin vueltas ni CI rojo.")}`;
|
|
16110
16258
|
const impact = { high: t("high", "alto"), medium: t("medium", "medio"), low: t("low", "bajo") }[w.impact] ?? w.impact;
|
|
16111
16259
|
return [
|
|
16112
|
-
`${bold(`#${w.runId}`)} ${red(w.category)} ${dim(`\xB7 ${t("impact", "impacto")} ${impact} \xB7 ${w.source === "model" ? w.model ?? "model" : t("rules", "reglas")} \xB7 ${
|
|
16260
|
+
`${bold(`#${w.runId}`)} ${red(w.category)} ${dim(`\xB7 ${t("impact", "impacto")} ${impact} \xB7 ${w.source === "model" ? w.model ?? "model" : t("rules", "reglas")} \xB7 ${short4(w.createdAt)}`)}`,
|
|
16113
16261
|
...w.acceptance?.length ? [bold(red(t("Unmet criterion, approved anyway", "Criterio incumplido y aprobado"))), ...w.acceptance.map((g) => ` ${g.at ? hhmm(g.at) : "\u2014"} ${red(g.rule)} \xAB${g.criterion}\xBB
|
|
16114
16262
|
${dim(g.evidence)}`), ""] : [],
|
|
16115
16263
|
w.summary,
|
|
@@ -16122,7 +16270,7 @@ ${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r
|
|
|
16122
16270
|
}
|
|
16123
16271
|
if (verb === "advise") {
|
|
16124
16272
|
const id = Number(ref.replace(/^#/, ""));
|
|
16125
|
-
const { advice: a, workspace } = await api(`${
|
|
16273
|
+
const { advice: a, workspace } = await api(`${path2}/advise`, { body: {} });
|
|
16126
16274
|
const note = a.buildNote.map((p, i) => `${i + 1}. ${p.point}`).join(" ");
|
|
16127
16275
|
const sh = (x) => `'${x.replace(/'/g, `'\\''`)}'`;
|
|
16128
16276
|
const apply = a.decision === "fix" ? `ghosty factory runs approve ${id} --note ${sh(note)}${a.checkScope ? ` --check-scope ${sh(a.checkScope)}` : ""} --workspace ${workspace}` : a.decision === "replan" ? `ghosty factory runs changes ${id} --note ${sh(note)} --workspace ${workspace}` : a.decision === "recheck" ? `ghosty factory runs recheck ${id} --workspace ${workspace}` : null;
|
|
@@ -16148,21 +16296,21 @@ ${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r
|
|
|
16148
16296
|
});
|
|
16149
16297
|
}
|
|
16150
16298
|
if (verb === "show") {
|
|
16151
|
-
const { run: r } = await api(
|
|
16299
|
+
const { run: r } = await api(path2);
|
|
16152
16300
|
const lines = [
|
|
16153
16301
|
`${bold(`#${r.id} ${r.title}`)} ${dim(`\xB7 ${r.status}${r.kind ? ` (${r.kind})` : ""} \xB7 ${r.room ? `#${r.room}` : ""}`)}`,
|
|
16154
16302
|
`${t("repo", "repo")}: ${r.repo ?? "\u2014"}${r.branch ? ` (${r.branch})` : ""} PR: ${r.pr ?? "\u2014"}`,
|
|
16155
16303
|
`preview: ${r.preview ?? "\u2014"}${r.previewState ? dim(` (${r.previewState})`) : ""}`,
|
|
16156
16304
|
`${t("plan", "plan")}: v${r.planVersion}${r.plan ? ` ${r.plan.decision ?? t("pending", "pendiente")}${r.plan.decidedBy ? ` ${t("by", "por")} ${r.plan.decidedBy}` : ""}` : ""} \xB7 ${t("loops", "vueltas")}: ${r.loops} \xB7 ${t("requested by", "pidi\xF3")} ${r.requestedBy}`,
|
|
16157
16305
|
...r.plans.map((p) => dim(` v${p.version} ${p.decision ?? "\u2014"}: ${p.title ?? ""}`)),
|
|
16158
|
-
`${t("running", "trabajando")}: ${r.running.length ? r.running.map((x) => `@${x.role} ${dim(`(${x.turn}, ${
|
|
16306
|
+
`${t("running", "trabajando")}: ${r.running.length ? r.running.map((x) => `@${x.role} ${dim(`(${x.turn}, ${short4(x.since)})`)}`).join(", ") : dim(t("nobody", "nadie"))}`,
|
|
16159
16307
|
...r.escalation ? [
|
|
16160
16308
|
bold(red(r.escalation.blocked ? t("@check stopped it: needs something @build can't do", "@check lo fren\xF3: falta algo que @build no puede dar") : t(`@check stopped it after ${r.escalation.loops} loops`, `@check lo fren\xF3 tras ${r.escalation.loops} vueltas`))),
|
|
16161
16309
|
r.escalation.findings,
|
|
16162
16310
|
dim(t(`What to do: ghosty factory runs advise ${r.id} --workspace ${ws}`, `Qu\xE9 hacer: ghosty factory runs advise ${r.id} --workspace ${ws}`))
|
|
16163
16311
|
] : [],
|
|
16164
16312
|
bold(t("Latest events", "\xDAltimos eventos")),
|
|
16165
|
-
...r.events.map((e) => ` ${dim(
|
|
16313
|
+
...r.events.map((e) => ` ${dim(short4(e.at))} ${e.type}${e.actor ? ` ${dim(e.actor)}` : ""}`),
|
|
16166
16314
|
...v.full && r.plan ? ["", bold(t("Current plan", "Plan vigente")), r.plan.markdown] : [dim(t("--full prints the current plan.", "--full imprime el plan vigente."))]
|
|
16167
16315
|
];
|
|
16168
16316
|
return out(r, () => lines.join("\n"));
|
|
@@ -16171,12 +16319,12 @@ ${dim(`${t("Detail", "Detalle")}: ghosty factory runs show <run> --workspace ${r
|
|
|
16171
16319
|
if (!title?.trim()) throw usage(t("Missing the new title.", "Falta el t\xEDtulo nuevo."), `ghosty factory runs rename ${ref} "T\xEDtulo" --workspace ${ws}`);
|
|
16172
16320
|
const retry = `ghosty factory runs rename ${ref} ${JSON.stringify(title)} --workspace ${ws}`;
|
|
16173
16321
|
if (!v["dry-run"]) requireYesIfHeadless(v.yes, retry);
|
|
16174
|
-
const plan = await api(
|
|
16322
|
+
const plan = await api(path2, { body: { title, dryRun: true } });
|
|
16175
16323
|
const line2 = `#${ref.replace(/^#/, "")}: \xAB${plan.from}\xBB \u2192 \xAB${plan.to}\xBB`;
|
|
16176
16324
|
if (v["dry-run"]) return out({ ...plan, dryRun: true }, () => `${line2}
|
|
16177
16325
|
${dim(t("Dry run: nothing changed.", "Simulacro: no se cambi\xF3 nada."))}`);
|
|
16178
16326
|
await confirm(t(`Rename ${line2}?`, `\xBFRenombrar ${line2}?`), { yes: v.yes, retry });
|
|
16179
|
-
const r = await api(
|
|
16327
|
+
const r = await api(path2, { body: { title } });
|
|
16180
16328
|
return out(r, () => `${lavender("\u2713")} ${line2} ${dim(t("(logged; the room card picks it up on its next refresh)", "(queda en bit\xE1cora; la tarjeta del room lo toma en su siguiente refresco)"))}`);
|
|
16181
16329
|
}
|
|
16182
16330
|
if (verb === "approve" || verb === "changes" || verb === "recheck" || verb === "merge" || verb === "card") {
|
|
@@ -16378,7 +16526,7 @@ init_api();
|
|
|
16378
16526
|
import { readFile as readFile14 } from "node:fs/promises";
|
|
16379
16527
|
init_i18n();
|
|
16380
16528
|
init_util();
|
|
16381
|
-
var
|
|
16529
|
+
var enc18 = encodeURIComponent;
|
|
16382
16530
|
var cdmx = (iso) => new Date(Date.parse(iso) - 6 * 36e5).toISOString().slice(5, 16).replace("T", " ");
|
|
16383
16531
|
async function supportThreads(email, v) {
|
|
16384
16532
|
const q = new URLSearchParams({ user: email, since: v.since ?? "30d", ...v.surface ? { surface: v.surface } : {} });
|
|
@@ -16398,7 +16546,7 @@ ${table(rows, ["last", "agent", "conversation", "title", "kind", "listed", "turn
|
|
|
16398
16546
|
${dim(t("Read one: ghosty conversations show <agent> <conversation> --why \xB7 times in CDMX \xB7 listed = in their app list", "Leer uno: ghosty conversations show <agente> <conversaci\xF3n> --why \xB7 horas CDMX \xB7 listed = sale en su lista de la app"))}`);
|
|
16399
16547
|
}
|
|
16400
16548
|
async function supportThread(agent, sid, v) {
|
|
16401
|
-
const r = await api(`/api/v2/support/threads/${
|
|
16549
|
+
const r = await api(`/api/v2/support/threads/${enc18(agent)}/${enc18(sid)}?tail=${enc18(v.limit ?? "40")}${v.tools ? "&tools=1" : ""}`);
|
|
16402
16550
|
out(r, () => {
|
|
16403
16551
|
if (v.tools && r.tools) {
|
|
16404
16552
|
const x = r.tools;
|
|
@@ -16431,9 +16579,9 @@ async function cleanupConversations(action, agent, v) {
|
|
|
16431
16579
|
if (!agent) throw usage(t("Missing agent.", "Falta el agente."), ex);
|
|
16432
16580
|
if (v.kind !== "test" && v.kind !== "channel" && v.kind !== "personal") throw usage(t("--kind takes test, channel or personal.", "--kind acepta test, channel o personal."), ex);
|
|
16433
16581
|
if (action === "rm" && v.kind === "channel") throw usage(t("Customer conversations (channel) are archived, never deleted: their memory keeps serving them.", "Los hilos de clientes (channel) se archivan, nunca se borran: su memoria los sigue atendiendo."), ex);
|
|
16434
|
-
const
|
|
16582
|
+
const path2 = `/api/v2/agents/${enc18(agent)}/conversations/cleanup`;
|
|
16435
16583
|
const body = { kind: v.kind, action, ...v.before ? { before: v.before } : {} };
|
|
16436
|
-
const plan = await api(
|
|
16584
|
+
const plan = await api(path2, { method: "POST", body: { ...body, dryRun: true } });
|
|
16437
16585
|
const verb = action === "rm" ? t("delete (rows + agent memory)", "borrar (filas + memoria del agente)") : t("archive (hidden; memory stays)", "archivar (se ocultan; la memoria se queda)");
|
|
16438
16586
|
const line2 = t(`${plan.threads} ${v.kind} conversation(s), ${plan.messages} message(s) to ${verb}`, `${plan.threads} conversaci\xF3n(es) ${v.kind}, ${plan.messages} mensaje(s) para ${verb}`);
|
|
16439
16587
|
if (v["dry-run"] || !plan.threads) return out(plan, () => `${line2}${plan.sample.length ? `
|
|
@@ -16442,11 +16590,11 @@ ${dim(t("Dry run: nothing changed.", "Simulaci\xF3n: no cambi\xF3 nada."))}`);
|
|
|
16442
16590
|
requireYesIfHeadless(v.yes, `ghosty conversations ${action} ${agent} --kind ${v.kind}${v.before ? ` --before ${v.before}` : ""}`);
|
|
16443
16591
|
await confirm(`${line2}
|
|
16444
16592
|
${t("Go ahead?", "\xBFSigo?")}`, { yes: v.yes, retry: `ghosty conversations ${action} ${agent} --kind ${v.kind}` });
|
|
16445
|
-
const r = await api(
|
|
16593
|
+
const r = await api(path2, { method: "POST", body });
|
|
16446
16594
|
out(r, () => `${lavender("\u2713")} ${t(`${r.threads} conversation(s) ${action === "rm" ? "deleted" : "archived"}.`, `${r.threads} conversaci\xF3n(es) ${action === "rm" ? "borradas" : "archivadas"}.`)}`);
|
|
16447
16595
|
}
|
|
16448
16596
|
async function turnStats(v) {
|
|
16449
|
-
const r = await api(`/api/v2/support/turn-stats?since=${
|
|
16597
|
+
const r = await api(`/api/v2/support/turn-stats?since=${enc18(v.since ?? "14d")}${v.all ? "&all=1" : ""}`);
|
|
16450
16598
|
out(
|
|
16451
16599
|
r,
|
|
16452
16600
|
() => [
|
|
@@ -16466,7 +16614,7 @@ async function fleetBoxes() {
|
|
|
16466
16614
|
${table(rows, ["template", "boxes", "on", "asleep", "ram", "vcpu", "ram on"])}`);
|
|
16467
16615
|
}
|
|
16468
16616
|
async function appAgentsOf(email, client) {
|
|
16469
|
-
const r = await api(`/api/v2/support/app-agents?user=${
|
|
16617
|
+
const r = await api(`/api/v2/support/app-agents?user=${enc18(email)}${client && client !== "all" ? `&client=${enc18(client)}` : ""}`);
|
|
16470
16618
|
const rows = r.agentes.map((a) => ({ id: a.id, name: a.nombre, engine: a.motor ?? "", model: a.modelo ?? "", tone: a.tono ?? t("(by position)", "(por posici\xF3n)"), space: a.espacio?.tipo === "workspace" ? a.espacio.nombre : a.espacio?.tipo ?? "", last: a.ultimaActividad ? cdmx(a.ultimaActividad) : "" }));
|
|
16471
16619
|
const phones = (r.devices ?? []).map((d) => `${d.platform} ${d.appVersion ?? ""}${d.appBuild ? ` (build ${d.appBuild})` : " (build ?)"} \xB7 ${t("seen", "visto")} ${cdmx(d.lastSeenAt)}`.replace(/\s+/g, " "));
|
|
16472
16620
|
out(r, () => `${bold(r.user)} ${dim(r.client ? t(`as the ${r.client} app sees it`, `como lo ve la app de ${r.client}`) : t("all apps", "todas las apps"))}
|
|
@@ -16477,7 +16625,7 @@ ${dim(t("No phones registered for push.", "Sin tel\xE9fonos registrados para avi
|
|
|
16477
16625
|
}
|
|
16478
16626
|
async function pushLog(email, v) {
|
|
16479
16627
|
if (!email) throw usage(t("Missing --user.", "Falta --user."), "ghosty push log --user ana@empresa.com [--since 2d]");
|
|
16480
|
-
const r = await api(`/api/v2/support/push-log?user=${
|
|
16628
|
+
const r = await api(`/api/v2/support/push-log?user=${enc18(email)}&since=${enc18(v.since ?? "2d")}${v.limit ? `&limit=${enc18(v.limit)}` : ""}`);
|
|
16481
16629
|
const rows = r.pushes.map((p) => ({ at: cdmx(p.at), kind: p.kind, to: p.platform, status: p.status === "sent" ? p.status : red(p.status), thread: p.threadKind ? `${p.threadId?.slice(0, 18)} (${p.threadKind})` : (p.threadId ?? "").slice(0, 24), title: (p.title ?? "").slice(0, 40), detail: p.detail ?? "" }));
|
|
16482
16630
|
out(r, () => rows.length ? `${bold(r.user)}
|
|
16483
16631
|
${table(rows, ["at", "kind", "to", "status", "thread", "title", "detail"])}` : dim(t(`No pushes for ${r.user} in that range (logged since 2026-10-01).`, `Sin avisos para ${r.user} en ese rango (se registran desde el 1-oct).`)));
|
|
@@ -16515,24 +16663,24 @@ ${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en t
|
|
|
16515
16663
|
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
16516
16664
|
const b = await fields();
|
|
16517
16665
|
if (!Object.keys(b).length) throw usage(t("Nothing to change.", "Nada que cambiar."), ex);
|
|
16518
|
-
const r = await api(`${base}/${
|
|
16666
|
+
const r = await api(`${base}/${enc18(id)}`, { method: "PATCH", body: b });
|
|
16519
16667
|
return out(r, () => `${lavender("\u2713")} ${line2(r)}`);
|
|
16520
16668
|
}
|
|
16521
16669
|
case "test": {
|
|
16522
16670
|
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
16523
|
-
const r = await api(`${base}/${
|
|
16671
|
+
const r = await api(`${base}/${enc18(id)}`, { method: "POST", body: { push: "test" } });
|
|
16524
16672
|
return out(r, () => `${lavender("\u2713")} ${t(`Sent to your phone (${r.enviados} device(s)).`, `Enviada a tu tel\xE9fono (${r.enviados} dispositivo(s)).`)}`);
|
|
16525
16673
|
}
|
|
16526
16674
|
case "send":
|
|
16527
16675
|
case "schedule": {
|
|
16528
16676
|
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
16529
16677
|
if (v.at !== void 0) {
|
|
16530
|
-
const r2 = await api(`${base}/${
|
|
16678
|
+
const r2 = await api(`${base}/${enc18(id)}`, { method: "PATCH", body: { schedule: v.at || null } });
|
|
16531
16679
|
return out(r2, () => `${lavender("\u2713")} ${line2(r2)}`);
|
|
16532
16680
|
}
|
|
16533
16681
|
requireYesIfHeadless(v.yes, `ghosty novedades send ${id}`);
|
|
16534
16682
|
await confirm(t("Push this announcement to every recipient now?", "\xBFMandar ya el push de esta novedad a todos sus destinatarios?"), { yes: v.yes, retry: `ghosty novedades send ${id}` });
|
|
16535
|
-
const r = await api(`${base}/${
|
|
16683
|
+
const r = await api(`${base}/${enc18(id)}`, { method: "POST", body: { push: "all" } });
|
|
16536
16684
|
return out(r, () => `${lavender("\u2713")} ${t(`Pushed to ${r.enviados} device(s).`, `Enviada a ${r.enviados} dispositivo(s).`)}`);
|
|
16537
16685
|
}
|
|
16538
16686
|
case "rm":
|
|
@@ -16540,7 +16688,7 @@ ${dim(t(`Try it on your phone: ghosty novedades test ${r.id}`, `Pru\xE9bala en t
|
|
|
16540
16688
|
if (!id) throw usage(t("Missing id.", "Falta el id."), ex);
|
|
16541
16689
|
requireYesIfHeadless(v.yes, `ghosty novedades rm ${id}`);
|
|
16542
16690
|
await confirm(t(`Delete announcement ${id}?`, `\xBFBorrar la novedad ${id}?`), { yes: v.yes, retry: `ghosty novedades rm ${id}` });
|
|
16543
|
-
return out(await api(`${base}/${
|
|
16691
|
+
return out(await api(`${base}/${enc18(id)}`, { method: "DELETE" }), () => `${lavender("\u2713")} ${t("Deleted.", "Borrada.")}`);
|
|
16544
16692
|
}
|
|
16545
16693
|
default:
|
|
16546
16694
|
throw usage(t(`Unknown novedades subcommand: ${sub}.`, `Subcomando de novedades desconocido: ${sub}.`), ex);
|
|
@@ -16609,7 +16757,7 @@ ${dim(r.case.id)}`);
|
|
|
16609
16757
|
}
|
|
16610
16758
|
if (!id) throw usage(t("Missing the case id.", "Falta el id del caso."), "ghosty support ls");
|
|
16611
16759
|
if (sub === "show") {
|
|
16612
|
-
const r = v.all ? await api(`/api/v2/support/cases/${
|
|
16760
|
+
const r = v.all ? await api(`/api/v2/support/cases/${enc18(id)}`) : await api(`/api/v2/me/support/${enc18(id)}`);
|
|
16613
16761
|
const c = r.case;
|
|
16614
16762
|
return out(r, () => {
|
|
16615
16763
|
const who2 = (a) => a === "staff" ? "Ghosty" : a === "agent" ? t("agent", "agente") : a === "customer" ? t("customer", "cliente") : a;
|
|
@@ -16630,18 +16778,18 @@ ${m.body}${m.attachments.length ? dim(`
|
|
|
16630
16778
|
}
|
|
16631
16779
|
if (sub === "reply") {
|
|
16632
16780
|
if (!v.body && !v.resolve) throw usage(t("Missing --body.", "Falta --body."), 'ghosty support reply <id> --body "\u2026" [--resolve]');
|
|
16633
|
-
const r = v.all || v.resolve ? await api(`/api/v2/support/cases/${
|
|
16781
|
+
const r = v.all || v.resolve ? await api(`/api/v2/support/cases/${enc18(id)}`, { method: "POST", body: { intent: "reply", body: v.body ?? "", resolve: Boolean(v.resolve) } }) : await api(`/api/v2/me/support/${enc18(id)}`, { method: "POST", body: { body: v.body } });
|
|
16634
16782
|
return out(r, () => `${lavender("\u2713")} ${r.case.code} \xB7 ${STATUS(r.case.status)}`);
|
|
16635
16783
|
}
|
|
16636
16784
|
if (sub === "link") {
|
|
16637
16785
|
const ref = v.ref;
|
|
16638
16786
|
if (!ref) throw usage(t("Missing the issue or PR.", "Falta el issue o PR."), "ghosty support link <id> owner/repo#12 (or the issue/PR URL)");
|
|
16639
|
-
const r = await api(`/api/v2/support/cases/${
|
|
16787
|
+
const r = await api(`/api/v2/support/cases/${enc18(id)}`, { method: "POST", body: { intent: "link", ref } });
|
|
16640
16788
|
return out(r, () => `${lavender("\u2713")} ${r.case.code} \u2192 ${bold(r.ref)} ${dim(`(${r.github.title})`)}${r.alsoWaiting ? `
|
|
16641
16789
|
${dim(t(`${r.alsoWaiting} more cases wait for the same fix: all get "fixed" when it closes.`, `Otros ${r.alsoWaiting} casos esperan el mismo arreglo: a todos les llega \xABya qued\xF3\xBB al cerrarse.`))}` : ""}`);
|
|
16642
16790
|
}
|
|
16643
16791
|
if (sub === "resolve" || sub === "reopen") {
|
|
16644
|
-
const r = await api(`/api/v2/support/cases/${
|
|
16792
|
+
const r = await api(`/api/v2/support/cases/${enc18(id)}`, { method: "POST", body: { intent: sub } });
|
|
16645
16793
|
return out(r, () => `${lavender("\u2713")} ${r.case.code} \xB7 ${STATUS(r.case.status)}`);
|
|
16646
16794
|
}
|
|
16647
16795
|
throw usage(t(`Unknown support subcommand: ${sub}.`, `Subcomando de support desconocido: ${sub}.`), "ghosty support ls | open | show | reply | resolve | reopen | link");
|
|
@@ -16823,6 +16971,12 @@ ${bold("Schedule turns")} ${dim("(each run lands in a conversation and notifies
|
|
|
16823
16971
|
schedule rm <id> <schedule-id> Cancel a pending turn
|
|
16824
16972
|
schedule add|ls|rm <id> \u2026 --teams WS#room[/thread] The same in a Teams room or thread: it answers there as if mentioned
|
|
16825
16973
|
|
|
16974
|
+
${bold("Watch pages")} ${dim("(checked without a model turn; the agent only gets a turn when the page changed)")}
|
|
16975
|
+
watch ls <id> Watched pages: state, last change, conversation
|
|
16976
|
+
watch add <id> <url> Watch a page [--every 24h (\u2265 1h)] [--focus "heading or line"] [--instruction "what matters"] [--label L] [--days 90] [--conversation ID] [--title T]
|
|
16977
|
+
watch check <id> <watch-id> Check it in the next sweep [--dry-run: fetch and compare now, save nothing, notify nobody]
|
|
16978
|
+
watch rm|resume <id> <watch-id> Stop watching \xB7 resume a paused one (or renew an expired one) [--days N]
|
|
16979
|
+
|
|
16826
16980
|
${bold("Teams")}
|
|
16827
16981
|
teams post WS#room[/thread] "msg" Write there as you; any @agent it mentions answers (no thread = new thread) [--wait [--timeout 600]: print their answers]
|
|
16828
16982
|
|
|
@@ -16833,6 +16987,7 @@ ${bold("Partners")} ${dim("(give an agent to each of YOUR customers \u2014 see:
|
|
|
16833
16987
|
partner ls | enable | credentials | agent | tenants | audit | try
|
|
16834
16988
|
image generate "prompt" An AI image for your businesses, charged to the space's balance [--ref URL (repeat, keeps the character)] [--style-ref URL] [--scene URL] [--text "-50 %"] [--direction D]\u2026 [--variants N] [--size 1K|2K|4K] [--aspect 16:9] [--tenant ID] [--out FILE]
|
|
16835
16989
|
image edit URL "change" Edit an existing image: only that change, the rest stays [--ref URL] [--text T] [--variants N] [--size] [--aspect] [--tenant ID] [--out FILE]
|
|
16990
|
+
tts "text" Hear the house voice (Mexican Spanish, em_santa) say it, to a .wav [--voice em_santa|em_alex|ef_dora] [--out FILE]
|
|
16836
16991
|
|
|
16837
16992
|
${bold("Your account")}
|
|
16838
16993
|
me files ls|get|export|upload|rm Files in your account [--q TEXT] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out FILE]
|
|
@@ -17031,6 +17186,12 @@ ${bold("Turnos programados")} ${dim("(cada corrida cae en una conversaci\xF3n y
|
|
|
17031
17186
|
schedule rm <id> <schedule-id> Cancela un turno pendiente
|
|
17032
17187
|
schedule add|ls|rm <id> \u2026 --teams ESP#room[/hilo] Lo mismo en un room o hilo de Teams: contesta ah\xED como si lo mencionaran
|
|
17033
17188
|
|
|
17189
|
+
${bold("Vigilar p\xE1ginas")} ${dim("(se revisan sin turno del modelo; el agente s\xF3lo recibe turno cuando la p\xE1gina cambi\xF3)")}
|
|
17190
|
+
watch ls <id> P\xE1ginas vigiladas: estado, \xFAltimo cambio, conversaci\xF3n
|
|
17191
|
+
watch add <id> <url> Vigila una p\xE1gina [--every 24h (\u2265 1h)] [--focus "encabezado o l\xEDnea"] [--instruction "qu\xE9 importa"] [--label L] [--days 90] [--conversation ID] [--title T]
|
|
17192
|
+
watch check <id> <watch-id> Rev\xEDsala en el siguiente barrido [--dry-run: baja y compara ya, sin guardar ni avisar]
|
|
17193
|
+
watch rm|resume <id> <watch-id> Deja de vigilarla \xB7 reanuda una pausada (o renueva una vencida) [--days N]
|
|
17194
|
+
|
|
17034
17195
|
${bold("Teams")}
|
|
17035
17196
|
teams post ESP#room[/hilo] "texto" Escribe ah\xED a tu nombre; el @agente que menciones contesta (sin hilo = hilo nuevo) [--wait [--timeout 600]: imprime sus respuestas]
|
|
17036
17197
|
|
|
@@ -17041,6 +17202,7 @@ ${bold("Partners")} ${dim("(dale un agente a cada uno de TUS clientes \u2014 ve
|
|
|
17041
17202
|
partner ls | enable | credentials | agent | tenants | audit | try
|
|
17042
17203
|
image generate "prompt" Una imagen con IA para tus negocios, cobrada del saldo del espacio [--ref URL (se repite, conserva al personaje)] [--style-ref URL] [--scene URL] [--text "-50 %"] [--direction D]\u2026 [--variants N] [--size 1K|2K|4K] [--aspect 16:9] [--tenant ID] [--out ARCHIVO]
|
|
17043
17204
|
image edit URL "cambio" Edita una imagen que ya existe: s\xF3lo ese cambio, lo dem\xE1s se queda [--ref URL] [--text T] [--variants N] [--size] [--aspect] [--tenant ID] [--out ARCHIVO]
|
|
17205
|
+
tts "texto" Oye la voz de la casa (espa\xF1ol de M\xE9xico, em_santa) decirlo, en un .wav [--voice em_santa|em_alex|ef_dora] [--out ARCHIVO]
|
|
17044
17206
|
|
|
17045
17207
|
${bold("Tu cuenta")}
|
|
17046
17208
|
me files ls|get|export|upload|rm Archivos de tu cuenta [--q TEXTO] [--kind image|video|audio|document|other] [--cursor C] \xB7 get <id> [--out ARCHIVO]
|
|
@@ -17066,7 +17228,7 @@ ${bold("Opciones globales")}
|
|
|
17066
17228
|
${bold("C\xF3digos de salida")} 0 bien \xB7 1 error de la API \xB7 2 uso incorrecto \xB7 3 sin sesi\xF3n
|
|
17067
17229
|
${dim("Nota: `ghosty` puede chocar con otro binario en tu PATH (p. ej. la TUI de Ghosty). Rev\xEDsalo con `which -a ghosty`; corre `npx @ghostystudio/cli` para ir a la segura.")}
|
|
17068
17230
|
${dim("Documentaci\xF3n: https://www.ghosty.studio/docs/cli")}`;
|
|
17069
|
-
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "coupons", "ads", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "teams", "partner", "image", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
|
|
17231
|
+
var COMMANDS = ["login", "logout", "whoami", "usage", "use", "apply", "plan", "engines", "bags", "spaces", "workspaces", "staff", "settings", "coupons", "ads", "audit", "factory", "demos", "sites", "push", "novedades", "completion", "cards", "agents", "files", "skills", "mcp", "dbs", "turns", "try", "chat", "conversations", "schedule", "watch", "teams", "partner", "image", "tts", "me", "channels", "board", "boards", "access", "credentials", "connectors", "browser", "support", "docs", "help"];
|
|
17070
17232
|
var AGENTS_SUBS = ["ls", "create", "get", "show", "set", "export", "doctor", "docs", "share", "unshare", "shares", "teams", "rm", "restart", "demo", "embed", "box", "token", "memory", "transfer"];
|
|
17071
17233
|
function unknown(what, input, known, helpCmd) {
|
|
17072
17234
|
const s2 = suggest(input, known);
|
|
@@ -17131,6 +17293,8 @@ async function main() {
|
|
|
17131
17293
|
at: { type: "string" },
|
|
17132
17294
|
in: { type: "string" },
|
|
17133
17295
|
every: { type: "string" },
|
|
17296
|
+
focus: { type: "string" },
|
|
17297
|
+
instruction: { type: "string" },
|
|
17134
17298
|
until: { type: "string" },
|
|
17135
17299
|
credit: { type: "string" },
|
|
17136
17300
|
eligibility: { type: "string" },
|
|
@@ -17381,7 +17545,7 @@ async function main() {
|
|
|
17381
17545
|
const tokens = splitTokens(pos, v);
|
|
17382
17546
|
globals.token = tokens.session;
|
|
17383
17547
|
globals.profile = v.profile;
|
|
17384
|
-
if (v.version) return out({ version: "0.
|
|
17548
|
+
if (v.version) return out({ version: "0.86.0" }, () => "0.86.0");
|
|
17385
17549
|
for (const n of normalizeArgs(pos, v)) if (!globals.json) info(dim(n));
|
|
17386
17550
|
const context = (await readConfig()).workspace;
|
|
17387
17551
|
if (!explicitToken()?.startsWith("gat_")) {
|
|
@@ -17516,6 +17680,8 @@ async function main() {
|
|
|
17516
17680
|
throw usage(t(`Unknown conversations subcommand: ${sub ?? "(none)"}.`, `Subcomando de conversations desconocido: ${sub ?? "(ninguno)"}.`), t("Try: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>", "Prueba: ghosty conversations ls <agent-id> \xB7 ghosty conversations show <agent-id> <conversation-id>"));
|
|
17517
17681
|
case "schedule":
|
|
17518
17682
|
return scheduleCmd(sub, a1, a2, v);
|
|
17683
|
+
case "watch":
|
|
17684
|
+
return watchCmd(sub, a1, a2, v);
|
|
17519
17685
|
case "teams":
|
|
17520
17686
|
return teamsCmd(sub, a1, a2, v);
|
|
17521
17687
|
case "browser":
|
|
@@ -17524,6 +17690,8 @@ async function main() {
|
|
|
17524
17690
|
return partnerCmd(sub, a1, a2, { ...v, token: tokens.turn }, pos[4]);
|
|
17525
17691
|
case "image":
|
|
17526
17692
|
return imageCmd(sub, a1, a2, v);
|
|
17693
|
+
case "tts":
|
|
17694
|
+
return ttsCmd(sub, v);
|
|
17527
17695
|
case "usage":
|
|
17528
17696
|
if (v.images) return imageUsage(v);
|
|
17529
17697
|
if (v.boxes && v.by === "template") return fleetBoxes();
|
|
@@ -17580,7 +17748,7 @@ async function main() {
|
|
|
17580
17748
|
throw unknown(cmd, cmd, COMMANDS, "ghosty --help");
|
|
17581
17749
|
}
|
|
17582
17750
|
}
|
|
17583
|
-
var notice = updateNotice("0.
|
|
17751
|
+
var notice = updateNotice("0.86.0").catch(() => null);
|
|
17584
17752
|
main().then(
|
|
17585
17753
|
async () => {
|
|
17586
17754
|
const n = await notice;
|