@gravity-rail/cli 0.7.2 → 0.8.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 +27 -0
- package/dist/account-verify-phone-TC73QIIE.js +41 -0
- package/dist/account-verify-phone-TC73QIIE.js.map +1 -0
- package/dist/{chunk-OPWCTOHP.js → chunk-7VZ4JLAJ.js} +11 -1
- package/dist/chunk-7VZ4JLAJ.js.map +1 -0
- package/dist/chunk-L5YCPUAM.js +761 -0
- package/dist/chunk-L5YCPUAM.js.map +1 -0
- package/dist/{errors-SMY7LZJB.js → errors-VK4QO3QZ.js} +2 -2
- package/dist/gr.js +2699 -668
- package/dist/gr.js.map +1 -1
- package/dist/lib.d.ts +5 -2
- package/dist/lib.js +1869 -1290
- package/dist/lib.js.map +1 -1
- package/package.json +5 -8
- package/dist/chunk-OPWCTOHP.js.map +0 -1
- package/dist/register-special-commands.d.ts +0 -4
- package/dist/register-special-commands.js +0 -3856
- package/dist/register-special-commands.js.map +0 -1
- /package/dist/{errors-SMY7LZJB.js.map → errors-VK4QO3QZ.js.map} +0 -0
package/dist/lib.js
CHANGED
|
@@ -1,157 +1,48 @@
|
|
|
1
|
+
import {
|
|
2
|
+
COL,
|
|
3
|
+
LIST_PAGINATION_ARGS,
|
|
4
|
+
confirmAccountPhoneVerify,
|
|
5
|
+
createAuthenticatedContext,
|
|
6
|
+
createConciergeChat,
|
|
7
|
+
createContext,
|
|
8
|
+
createPublicAuthenticatedContext,
|
|
9
|
+
createPublicContext,
|
|
10
|
+
detectOutputFormat,
|
|
11
|
+
extractPaginationMeta,
|
|
12
|
+
fetchAllCursorPages,
|
|
13
|
+
fetchAllPages,
|
|
14
|
+
formatPaginationWarning,
|
|
15
|
+
getConciergeChat,
|
|
16
|
+
getConciergeChatMessages,
|
|
17
|
+
isCursorPaginatedResponse,
|
|
18
|
+
isListResult,
|
|
19
|
+
isPagePaginatedResponse,
|
|
20
|
+
isPaginatedResponse,
|
|
21
|
+
listConciergeChats,
|
|
22
|
+
listResult,
|
|
23
|
+
originateConciergeCall,
|
|
24
|
+
originateConciergeSms,
|
|
25
|
+
outputData,
|
|
26
|
+
outputSingle,
|
|
27
|
+
parsePaginationArgs,
|
|
28
|
+
requireAuth,
|
|
29
|
+
startAccountPhoneVerify,
|
|
30
|
+
stderr,
|
|
31
|
+
unwrapItems
|
|
32
|
+
} from "./chunk-L5YCPUAM.js";
|
|
1
33
|
import {
|
|
2
34
|
AGUIClient,
|
|
3
|
-
CredentialsManager,
|
|
4
|
-
clearScreen,
|
|
5
35
|
formatTimestamp,
|
|
6
|
-
getEnvironmentConfig,
|
|
7
36
|
getErrorMessage,
|
|
8
37
|
getTextDelta,
|
|
9
38
|
performOAuthFlow,
|
|
10
|
-
printMessage,
|
|
11
|
-
printTable,
|
|
12
|
-
refreshAccessToken,
|
|
13
39
|
selectFromList
|
|
14
40
|
} from "./chunk-H62EC2CF.js";
|
|
15
41
|
import {
|
|
16
42
|
AuthRequiredError,
|
|
17
43
|
CLIError,
|
|
18
44
|
handleError
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
|
|
21
|
-
// src/lib/pagination.ts
|
|
22
|
-
function isListResult(data) {
|
|
23
|
-
if (!data || typeof data !== "object" || Array.isArray(data)) return false;
|
|
24
|
-
const obj = data;
|
|
25
|
-
return Array.isArray(obj.items) && ("supportsAll" in obj || "meta" in obj && (obj.meta === void 0 || typeof obj.meta === "object"));
|
|
26
|
-
}
|
|
27
|
-
function isPagePaginatedResponse(data) {
|
|
28
|
-
if (!data || typeof data !== "object") return false;
|
|
29
|
-
const obj = data;
|
|
30
|
-
if (!Array.isArray(obj.items) || typeof obj.page !== "number") return false;
|
|
31
|
-
if (typeof obj.totalPages === "number") return true;
|
|
32
|
-
if (typeof obj.total === "number" && typeof obj.pageSize === "number") return true;
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
function isCursorPaginatedResponse(data) {
|
|
36
|
-
if (!data || typeof data !== "object") return false;
|
|
37
|
-
const obj = data;
|
|
38
|
-
return Array.isArray(obj.items) && typeof obj.hasMore === "boolean";
|
|
39
|
-
}
|
|
40
|
-
function isPaginatedResponse(data) {
|
|
41
|
-
return isPagePaginatedResponse(data) || isCursorPaginatedResponse(data);
|
|
42
|
-
}
|
|
43
|
-
function extractPaginationMeta(data) {
|
|
44
|
-
if (isPagePaginatedResponse(data)) {
|
|
45
|
-
const totalPages = data.totalPages ?? (data.pageSize ? Math.ceil(data.total / data.pageSize) : void 0);
|
|
46
|
-
return {
|
|
47
|
-
total: data.total,
|
|
48
|
-
page: data.page,
|
|
49
|
-
totalPages
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
if (isCursorPaginatedResponse(data)) {
|
|
53
|
-
return {
|
|
54
|
-
hasMore: data.hasMore,
|
|
55
|
-
nextCursor: data.nextCursor
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
return void 0;
|
|
59
|
-
}
|
|
60
|
-
function formatPaginationWarning(meta, supportsAll = true) {
|
|
61
|
-
if (!meta) return void 0;
|
|
62
|
-
const allHint = supportsAll ? " Use --all to fetch all pages." : "";
|
|
63
|
-
if (meta.totalPages !== void 0 && meta.totalPages > 1) {
|
|
64
|
-
const pageNum = meta.page ?? 1;
|
|
65
|
-
return `Showing page ${pageNum} of ${meta.totalPages} (${meta.total} total items).${allHint}`;
|
|
66
|
-
}
|
|
67
|
-
if (meta.hasMore === true) {
|
|
68
|
-
return `More results available.${allHint}`;
|
|
69
|
-
}
|
|
70
|
-
return void 0;
|
|
71
|
-
}
|
|
72
|
-
function unwrapItems(data) {
|
|
73
|
-
if (Array.isArray(data)) return data;
|
|
74
|
-
if (data && typeof data === "object" && "items" in data && Array.isArray(data.items)) {
|
|
75
|
-
return data.items;
|
|
76
|
-
}
|
|
77
|
-
return [data];
|
|
78
|
-
}
|
|
79
|
-
var LIST_PAGINATION_ARGS = [
|
|
80
|
-
{ name: "page", description: "Page number" },
|
|
81
|
-
{ name: "page-size", description: "Page size" },
|
|
82
|
-
{ name: "all", description: "Fetch all pages and return combined results", isFlag: true }
|
|
83
|
-
];
|
|
84
|
-
function parsePaginationArgs(args) {
|
|
85
|
-
const result2 = {};
|
|
86
|
-
if (args.all === true) {
|
|
87
|
-
result2.all = true;
|
|
88
|
-
}
|
|
89
|
-
if (args.page !== void 0) {
|
|
90
|
-
const num = Number(args.page);
|
|
91
|
-
if (isNaN(num) || num < 1) throw new CLIError("--page must be a positive integer");
|
|
92
|
-
result2.page = num;
|
|
93
|
-
}
|
|
94
|
-
if (args["page-size"] !== void 0) {
|
|
95
|
-
const num = Number(args["page-size"]);
|
|
96
|
-
if (isNaN(num) || num < 1) throw new CLIError("--page-size must be a positive integer");
|
|
97
|
-
if (num > 100) throw new CLIError("--page-size must be at most 100 (API limit)");
|
|
98
|
-
result2.pageSize = num;
|
|
99
|
-
}
|
|
100
|
-
return result2;
|
|
101
|
-
}
|
|
102
|
-
var MAX_PAGE_SIZE = 100;
|
|
103
|
-
var MAX_PAGES = 200;
|
|
104
|
-
async function fetchAllPages(fetcher) {
|
|
105
|
-
const allItems = [];
|
|
106
|
-
let page = 1;
|
|
107
|
-
while (page <= MAX_PAGES) {
|
|
108
|
-
const result2 = await fetcher({ page, pageSize: MAX_PAGE_SIZE });
|
|
109
|
-
const items = unwrapItems(result2);
|
|
110
|
-
allItems.push(...items);
|
|
111
|
-
const meta = extractPaginationMeta(result2);
|
|
112
|
-
if (meta?.totalPages !== void 0) {
|
|
113
|
-
if (page >= meta.totalPages) break;
|
|
114
|
-
} else if (!isPagePaginatedResponse(result2)) {
|
|
115
|
-
if (items.length < MAX_PAGE_SIZE) break;
|
|
116
|
-
}
|
|
117
|
-
if (page === MAX_PAGES) {
|
|
118
|
-
throw new CLIError(
|
|
119
|
-
`Reached maximum page limit (${MAX_PAGES}). Results may be incomplete.`
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
page++;
|
|
123
|
-
}
|
|
124
|
-
return allItems;
|
|
125
|
-
}
|
|
126
|
-
async function fetchAllCursorPages(fetcher) {
|
|
127
|
-
const allItems = [];
|
|
128
|
-
let cursor;
|
|
129
|
-
let pages = 0;
|
|
130
|
-
while (pages < MAX_PAGES) {
|
|
131
|
-
const result2 = await fetcher({ cursor });
|
|
132
|
-
const items = unwrapItems(result2);
|
|
133
|
-
allItems.push(...items);
|
|
134
|
-
pages++;
|
|
135
|
-
if (isCursorPaginatedResponse(result2) && result2.hasMore && result2.nextCursor) {
|
|
136
|
-
cursor = result2.nextCursor;
|
|
137
|
-
} else {
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
if (pages === MAX_PAGES) {
|
|
141
|
-
throw new CLIError(
|
|
142
|
-
`Reached maximum page limit (${MAX_PAGES}). Results may be incomplete.`
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return allItems;
|
|
147
|
-
}
|
|
148
|
-
function listResult(data, supportsAll = false) {
|
|
149
|
-
return {
|
|
150
|
-
items: unwrapItems(data),
|
|
151
|
-
meta: extractPaginationMeta(data),
|
|
152
|
-
supportsAll
|
|
153
|
-
};
|
|
154
|
-
}
|
|
45
|
+
} from "./chunk-7VZ4JLAJ.js";
|
|
155
46
|
|
|
156
47
|
// src/lib/args.ts
|
|
157
48
|
var PAGINATION_ARGS = [
|
|
@@ -229,475 +120,8 @@ function findDef(defs, arg) {
|
|
|
229
120
|
return void 0;
|
|
230
121
|
}
|
|
231
122
|
|
|
232
|
-
// src/lib/context.ts
|
|
233
|
-
import { GravityRailClient } from "@gravity-rail/sdk";
|
|
234
|
-
import { homedir } from "os";
|
|
235
|
-
|
|
236
|
-
// src/lib/output.ts
|
|
237
|
-
function detectOutputFormat(explicit) {
|
|
238
|
-
if (explicit) return explicit;
|
|
239
|
-
return process.stdout.isTTY ? "table" : "json";
|
|
240
|
-
}
|
|
241
|
-
function outputData(data, opts, columns, paginationMeta, supportsAll) {
|
|
242
|
-
let items;
|
|
243
|
-
let meta;
|
|
244
|
-
let allSupported;
|
|
245
|
-
if (isListResult(data)) {
|
|
246
|
-
items = data.items;
|
|
247
|
-
meta = data.meta;
|
|
248
|
-
allSupported = data.supportsAll ?? false;
|
|
249
|
-
} else {
|
|
250
|
-
items = unwrapItems(data);
|
|
251
|
-
meta = paginationMeta ?? extractPaginationMeta(data);
|
|
252
|
-
allSupported = supportsAll ?? false;
|
|
253
|
-
}
|
|
254
|
-
const warning = formatPaginationWarning(meta, allSupported);
|
|
255
|
-
if (warning) {
|
|
256
|
-
process.stderr.write(warning + "\n");
|
|
257
|
-
}
|
|
258
|
-
switch (opts.outputFormat) {
|
|
259
|
-
case "json":
|
|
260
|
-
process.stdout.write(JSON.stringify(items, null, 2) + "\n");
|
|
261
|
-
break;
|
|
262
|
-
case "jsonl":
|
|
263
|
-
for (const item of items) {
|
|
264
|
-
process.stdout.write(JSON.stringify(item) + "\n");
|
|
265
|
-
}
|
|
266
|
-
break;
|
|
267
|
-
case "table":
|
|
268
|
-
if (columns) {
|
|
269
|
-
printTable(items, columns);
|
|
270
|
-
} else {
|
|
271
|
-
process.stdout.write(JSON.stringify(items, null, 2) + "\n");
|
|
272
|
-
}
|
|
273
|
-
break;
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
function outputSingle(data, opts, columns) {
|
|
277
|
-
switch (opts.outputFormat) {
|
|
278
|
-
case "json":
|
|
279
|
-
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
280
|
-
break;
|
|
281
|
-
case "jsonl":
|
|
282
|
-
process.stdout.write(JSON.stringify(data) + "\n");
|
|
283
|
-
break;
|
|
284
|
-
case "table":
|
|
285
|
-
if (columns) {
|
|
286
|
-
printTable([data], columns);
|
|
287
|
-
} else {
|
|
288
|
-
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
289
|
-
}
|
|
290
|
-
break;
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
var COL = {
|
|
294
|
-
id: () => ({ header: "ID", getValue: (item) => String(item.id ?? "") }),
|
|
295
|
-
uuid: () => ({ header: "UUID", getValue: (item) => item.uuid ?? "" }),
|
|
296
|
-
name: () => ({ header: "NAME", getValue: (item) => item.name ?? "" }),
|
|
297
|
-
status: () => ({ header: "STATUS", getValue: (item) => item.status ?? "" }),
|
|
298
|
-
created: () => ({
|
|
299
|
-
header: "CREATED",
|
|
300
|
-
getValue: (item) => item.created_at ?? ""
|
|
301
|
-
}),
|
|
302
|
-
updated: () => ({
|
|
303
|
-
header: "UPDATED",
|
|
304
|
-
getValue: (item) => item.updated_at ?? ""
|
|
305
|
-
}),
|
|
306
|
-
description: () => ({
|
|
307
|
-
header: "DESCRIPTION",
|
|
308
|
-
getValue: (item) => item.description ?? ""
|
|
309
|
-
}),
|
|
310
|
-
enabled: () => ({
|
|
311
|
-
header: "ENABLED",
|
|
312
|
-
getValue: (item) => String(item.enabled ?? "")
|
|
313
|
-
})
|
|
314
|
-
};
|
|
315
|
-
function stderr(msg) {
|
|
316
|
-
process.stderr.write(msg + "\n");
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
// src/lib/realm.ts
|
|
320
|
-
import { execSync } from "child_process";
|
|
321
|
-
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "fs";
|
|
322
|
-
import { dirname, join } from "path";
|
|
323
|
-
function findDevRealmsFile() {
|
|
324
|
-
let dir = process.cwd();
|
|
325
|
-
while (dir !== dirname(dir)) {
|
|
326
|
-
const candidate = join(dir, "dev-realms.json");
|
|
327
|
-
if (existsSync(candidate)) return candidate;
|
|
328
|
-
dir = dirname(dir);
|
|
329
|
-
}
|
|
330
|
-
throw new Error("Cannot find dev-realms.json in any parent directory");
|
|
331
|
-
}
|
|
332
|
-
var DEV_CLOUD_CACHE_DIR = ".dev-cloud";
|
|
333
|
-
var CACHE_MAX_AGE_MS = 60 * 60 * 1e3;
|
|
334
|
-
function findRepoRoot() {
|
|
335
|
-
let dir = process.cwd();
|
|
336
|
-
while (dir !== dirname(dir)) {
|
|
337
|
-
if (existsSync(join(dir, ".git"))) {
|
|
338
|
-
return dir;
|
|
339
|
-
}
|
|
340
|
-
dir = dirname(dir);
|
|
341
|
-
}
|
|
342
|
-
return void 0;
|
|
343
|
-
}
|
|
344
|
-
function getDevCloudCacheDir(namespace) {
|
|
345
|
-
const repoRoot = findRepoRoot();
|
|
346
|
-
const base = repoRoot ?? process.cwd();
|
|
347
|
-
return join(base, DEV_CLOUD_CACHE_DIR, namespace);
|
|
348
|
-
}
|
|
349
|
-
function fetchDevCloudRealms(namespace) {
|
|
350
|
-
const k8sNamespace = `dev-cloud-${namespace}`;
|
|
351
|
-
const cmd = `kubectl exec -n ${k8sNamespace} deploy/env -c devbox -- cat /workspace/dev-realms.json`;
|
|
352
|
-
try {
|
|
353
|
-
return execSync(cmd, { encoding: "utf-8", timeout: 3e4 });
|
|
354
|
-
} catch (err) {
|
|
355
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
356
|
-
throw new Error(
|
|
357
|
-
`Failed to fetch dev-realms.json from dev-cloud pod "${namespace}": ${message}`
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
function loadDevCloudRealmFile(namespace) {
|
|
362
|
-
const cacheDir = getDevCloudCacheDir(namespace);
|
|
363
|
-
const cachePath = join(cacheDir, "dev-realms.json");
|
|
364
|
-
if (existsSync(cachePath)) {
|
|
365
|
-
const stat = statSync(cachePath);
|
|
366
|
-
const age = Date.now() - stat.mtimeMs;
|
|
367
|
-
if (age < CACHE_MAX_AGE_MS) {
|
|
368
|
-
return cachePath;
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
const content = fetchDevCloudRealms(namespace);
|
|
372
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
373
|
-
writeFileSync(cachePath, content, "utf-8");
|
|
374
|
-
return cachePath;
|
|
375
|
-
}
|
|
376
|
-
function loadRealmCredentials(realm, devRealmsPath) {
|
|
377
|
-
const filePath = devRealmsPath ?? findDevRealmsFile();
|
|
378
|
-
let data;
|
|
379
|
-
try {
|
|
380
|
-
data = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
381
|
-
} catch {
|
|
382
|
-
throw new Error(`Cannot read dev-realms.json at ${filePath}`);
|
|
383
|
-
}
|
|
384
|
-
const parts = realm.split("/");
|
|
385
|
-
if (parts.length === 1) {
|
|
386
|
-
const accounts = data._accounts;
|
|
387
|
-
const account = accounts?.[parts[0]];
|
|
388
|
-
if (!account?.token) {
|
|
389
|
-
throw new Error(`Realm account "${parts[0]}" not found in dev-realms.json`);
|
|
390
|
-
}
|
|
391
|
-
return { token: account.token };
|
|
392
|
-
} else {
|
|
393
|
-
const [wsName, memberName] = parts;
|
|
394
|
-
const ws = data[wsName];
|
|
395
|
-
if (!ws) {
|
|
396
|
-
throw new Error(`Realm workspace "${wsName}" not found in dev-realms.json`);
|
|
397
|
-
}
|
|
398
|
-
const member = ws.members?.[memberName];
|
|
399
|
-
if (!member?.token) {
|
|
400
|
-
throw new Error(`Realm member "${memberName}" not found in workspace "${wsName}"`);
|
|
401
|
-
}
|
|
402
|
-
return { token: member.token, workspaceUuid: ws.workspaceUuid };
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// src/lib/org-auth.ts
|
|
407
|
-
import { execFile } from "child_process";
|
|
408
|
-
var ORG_AUTH_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
409
|
-
function extractOrgTokenFromSetCookies(setCookieValues, preferredCookieName) {
|
|
410
|
-
let fallback;
|
|
411
|
-
for (const value of setCookieValues) {
|
|
412
|
-
const nameValue = value.split(";")[0]?.trim() ?? "";
|
|
413
|
-
const eqIdx = nameValue.indexOf("=");
|
|
414
|
-
if (eqIdx === -1) continue;
|
|
415
|
-
const name = nameValue.slice(0, eqIdx).trim();
|
|
416
|
-
const tokenValue = nameValue.slice(eqIdx + 1).trim() || void 0;
|
|
417
|
-
if (!tokenValue) continue;
|
|
418
|
-
if (name === preferredCookieName) return tokenValue;
|
|
419
|
-
if (!fallback && (name.startsWith("orgToken") || name.startsWith("orgSession"))) {
|
|
420
|
-
fallback = tokenValue;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return fallback;
|
|
424
|
-
}
|
|
425
|
-
async function tryDirectJoin(apiUrl, accessToken, orgAuthInfo) {
|
|
426
|
-
const response = await fetch(`${apiUrl}/api/v2/o/${orgAuthInfo.orgUuid}/join`, {
|
|
427
|
-
method: "POST",
|
|
428
|
-
headers: {
|
|
429
|
-
"Content-Type": "application/json",
|
|
430
|
-
Authorization: `Bearer ${accessToken}`
|
|
431
|
-
},
|
|
432
|
-
body: "{}"
|
|
433
|
-
});
|
|
434
|
-
if (response.status === 400) {
|
|
435
|
-
return null;
|
|
436
|
-
}
|
|
437
|
-
if (!response.ok) {
|
|
438
|
-
throw new Error(`Org join failed (HTTP ${response.status})`);
|
|
439
|
-
}
|
|
440
|
-
const rawHeaders = response.headers;
|
|
441
|
-
const setCookies = rawHeaders.getSetCookie?.() ?? (rawHeaders.get("set-cookie") ? [rawHeaders.get("set-cookie")] : []);
|
|
442
|
-
if (setCookies.length === 0) {
|
|
443
|
-
throw new Error("Org join succeeded but no Set-Cookie header returned");
|
|
444
|
-
}
|
|
445
|
-
const orgToken = extractOrgTokenFromSetCookies(setCookies, orgAuthInfo.cookieName);
|
|
446
|
-
if (!orgToken) {
|
|
447
|
-
throw new Error("Org join succeeded but org token cookie not found in Set-Cookie");
|
|
448
|
-
}
|
|
449
|
-
return orgToken;
|
|
450
|
-
}
|
|
451
|
-
function isAllowedAuthUrl(url, apiUrl) {
|
|
452
|
-
try {
|
|
453
|
-
const parsed = new URL(url);
|
|
454
|
-
const api = new URL(apiUrl);
|
|
455
|
-
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return false;
|
|
456
|
-
if (parsed.hostname === api.hostname) return true;
|
|
457
|
-
const lastTwo = (host) => {
|
|
458
|
-
const parts = host.split(".").filter(Boolean);
|
|
459
|
-
return parts.length >= 2 ? parts.slice(-2).join(".") : host;
|
|
460
|
-
};
|
|
461
|
-
const isIp = /^[0-9.]+$|^\[.*\]$/.test(parsed.hostname);
|
|
462
|
-
if (isIp) return false;
|
|
463
|
-
return lastTwo(parsed.hostname) === lastTwo(api.hostname);
|
|
464
|
-
} catch {
|
|
465
|
-
return false;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
function openBrowser(url) {
|
|
469
|
-
let command;
|
|
470
|
-
let args;
|
|
471
|
-
if (process.platform === "darwin") {
|
|
472
|
-
command = "open";
|
|
473
|
-
args = [url];
|
|
474
|
-
} else if (process.platform === "win32") {
|
|
475
|
-
command = "cmd.exe";
|
|
476
|
-
args = ["/c", "start", "", url];
|
|
477
|
-
} else {
|
|
478
|
-
command = "xdg-open";
|
|
479
|
-
args = [url];
|
|
480
|
-
}
|
|
481
|
-
execFile(command, args, (_err) => {
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
|
-
function registerOrgAuthHandler(apiClient, apiUrl, accessToken) {
|
|
485
|
-
apiClient.setOrgAuthHandler(async (orgAuthInfo) => {
|
|
486
|
-
try {
|
|
487
|
-
const orgToken = await tryDirectJoin(apiUrl, accessToken, orgAuthInfo);
|
|
488
|
-
if (orgToken !== null) {
|
|
489
|
-
apiClient.setOrgToken(orgToken, orgAuthInfo.orgUuid);
|
|
490
|
-
return;
|
|
491
|
-
}
|
|
492
|
-
} catch (err) {
|
|
493
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
494
|
-
stderr(`Warning: org direct-join failed (${msg}); trying browser auth`);
|
|
495
|
-
}
|
|
496
|
-
if (!isAllowedAuthUrl(orgAuthInfo.authUrl, apiUrl)) {
|
|
497
|
-
throw new Error(
|
|
498
|
-
`Refusing to open organization auth URL: scheme/host does not match the configured API host. Re-run with the expected --api-url, or contact support.`
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
stderr(`
|
|
502
|
-
This workspace requires organization SSO authentication.`);
|
|
503
|
-
stderr(`Opening browser to complete authentication...`);
|
|
504
|
-
stderr(` ${orgAuthInfo.authUrl}
|
|
505
|
-
`);
|
|
506
|
-
stderr(`If the browser does not open, visit the URL above manually.`);
|
|
507
|
-
stderr(
|
|
508
|
-
`After signing in, re-run the command \u2014 the CLI cannot receive the SSO token directly.`
|
|
509
|
-
);
|
|
510
|
-
openBrowser(orgAuthInfo.authUrl);
|
|
511
|
-
await new Promise((_, reject) => {
|
|
512
|
-
const t = setTimeout(
|
|
513
|
-
() => reject(
|
|
514
|
-
new Error(
|
|
515
|
-
"Organization authentication timed out.\nPlease complete authentication in the browser, then re-run the command."
|
|
516
|
-
)
|
|
517
|
-
),
|
|
518
|
-
ORG_AUTH_TIMEOUT_MS
|
|
519
|
-
);
|
|
520
|
-
t.unref?.();
|
|
521
|
-
});
|
|
522
|
-
});
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
// src/lib/pinned-fetch.ts
|
|
526
|
-
import { Agent, fetch as undiciFetch } from "undici";
|
|
527
|
-
function createPinnedFetch(apiUrl, apiIp) {
|
|
528
|
-
if (!apiIp) return fetch;
|
|
529
|
-
let parsed;
|
|
530
|
-
try {
|
|
531
|
-
parsed = new URL(apiUrl);
|
|
532
|
-
} catch {
|
|
533
|
-
return fetch;
|
|
534
|
-
}
|
|
535
|
-
const hostname = parsed.hostname;
|
|
536
|
-
const dispatcher = new Agent({
|
|
537
|
-
connect: {
|
|
538
|
-
servername: hostname
|
|
539
|
-
}
|
|
540
|
-
});
|
|
541
|
-
return async (input, init) => {
|
|
542
|
-
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
543
|
-
let reqUrl;
|
|
544
|
-
try {
|
|
545
|
-
reqUrl = new URL(url, apiUrl);
|
|
546
|
-
} catch {
|
|
547
|
-
return fetch(input, init);
|
|
548
|
-
}
|
|
549
|
-
const headers2 = new Headers(init?.headers);
|
|
550
|
-
if (reqUrl.hostname === hostname) {
|
|
551
|
-
headers2.set("Host", hostname);
|
|
552
|
-
reqUrl = new URL(reqUrl.href);
|
|
553
|
-
reqUrl.hostname = apiIp;
|
|
554
|
-
}
|
|
555
|
-
return undiciFetch(reqUrl, {
|
|
556
|
-
...init,
|
|
557
|
-
headers: headers2,
|
|
558
|
-
dispatcher
|
|
559
|
-
});
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
function withAuditHeaders(fetchFn, headers2) {
|
|
563
|
-
const { chatId, sessionId, toolCallId } = headers2;
|
|
564
|
-
if (chatId === void 0 && !sessionId && !toolCallId) {
|
|
565
|
-
return fetchFn;
|
|
566
|
-
}
|
|
567
|
-
return async (input, init) => {
|
|
568
|
-
const reqHeaders = new Headers(init?.headers);
|
|
569
|
-
if (chatId !== void 0) {
|
|
570
|
-
reqHeaders.set("X-GR-Chat-Id", String(chatId));
|
|
571
|
-
}
|
|
572
|
-
if (sessionId) {
|
|
573
|
-
reqHeaders.set("X-GR-Session-Id", sessionId);
|
|
574
|
-
}
|
|
575
|
-
if (toolCallId) {
|
|
576
|
-
reqHeaders.set("X-GR-Tool-Call-Id", toolCallId);
|
|
577
|
-
}
|
|
578
|
-
return fetchFn(input, { ...init, headers: reqHeaders });
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
function createSandboxFetchFromEnv(apiUrl) {
|
|
582
|
-
const apiIp = process.env.GRAVITY_RAIL_API_IP;
|
|
583
|
-
let fetchFn = createPinnedFetch(apiUrl, apiIp);
|
|
584
|
-
const chatIdRaw = process.env.GRAVITY_RAIL_CHAT_ID;
|
|
585
|
-
const chatId = chatIdRaw ? Number(chatIdRaw) : void 0;
|
|
586
|
-
return withAuditHeaders(fetchFn, {
|
|
587
|
-
chatId: chatId !== void 0 && Number.isFinite(chatId) ? chatId : void 0,
|
|
588
|
-
sessionId: process.env.GRAVITY_RAIL_SESSION_ID,
|
|
589
|
-
toolCallId: process.env.GRAVITY_RAIL_TOOL_CALL_ID
|
|
590
|
-
});
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
// src/lib/context.ts
|
|
594
|
-
function makeApiClient(accessToken, apiUrl) {
|
|
595
|
-
const fetchFn = createSandboxFetchFromEnv(apiUrl);
|
|
596
|
-
const apiClient = new GravityRailClient(accessToken, apiUrl, fetchFn);
|
|
597
|
-
registerOrgAuthHandler(apiClient, apiUrl, accessToken);
|
|
598
|
-
return apiClient;
|
|
599
|
-
}
|
|
600
|
-
async function tryTokenRefresh(ctx, creds) {
|
|
601
|
-
if (!creds.refreshToken) return null;
|
|
602
|
-
const refreshed = await refreshAccessToken(
|
|
603
|
-
ctx.envConfig,
|
|
604
|
-
creds,
|
|
605
|
-
ctx.credentialsManager,
|
|
606
|
-
ctx.env
|
|
607
|
-
);
|
|
608
|
-
if (!refreshed?.accessToken) return null;
|
|
609
|
-
const apiClient = makeApiClient(refreshed.accessToken, ctx.envConfig.apiUrl);
|
|
610
|
-
return { ...ctx, accessToken: refreshed.accessToken, apiClient };
|
|
611
|
-
}
|
|
612
|
-
function createContext(opts) {
|
|
613
|
-
const baseConfig = getEnvironmentConfig(opts.env);
|
|
614
|
-
const apiUrl = opts.apiUrl || process.env.GRAVITY_RAIL_API_URL || baseConfig.apiUrl;
|
|
615
|
-
const envConfig = apiUrl === baseConfig.apiUrl ? baseConfig : { ...baseConfig, apiUrl };
|
|
616
|
-
const credentialsManager = new CredentialsManager(homedir(), envConfig.apiUrl);
|
|
617
|
-
return {
|
|
618
|
-
env: opts.env,
|
|
619
|
-
envConfig,
|
|
620
|
-
outputFormat: detectOutputFormat(opts.outputFormat),
|
|
621
|
-
verbose: opts.verbose ?? false,
|
|
622
|
-
allowWrite: opts.allowWrite ?? false,
|
|
623
|
-
credentialsManager
|
|
624
|
-
};
|
|
625
|
-
}
|
|
626
|
-
async function createAuthenticatedContext(opts) {
|
|
627
|
-
const ctx = createContext(opts);
|
|
628
|
-
if (opts.realm) {
|
|
629
|
-
let devRealmsPath;
|
|
630
|
-
if (opts.env.startsWith("dev-cloud:")) {
|
|
631
|
-
const namespace = opts.env.slice("dev-cloud:".length);
|
|
632
|
-
devRealmsPath = loadDevCloudRealmFile(namespace);
|
|
633
|
-
}
|
|
634
|
-
const { token, workspaceUuid } = loadRealmCredentials(opts.realm, devRealmsPath);
|
|
635
|
-
const apiClient = makeApiClient(token, ctx.envConfig.apiUrl);
|
|
636
|
-
return { ...ctx, accessToken: token, apiClient, realmWorkspace: workspaceUuid };
|
|
637
|
-
}
|
|
638
|
-
const apiKey = process.env.GRAVITY_RAIL_API_KEY;
|
|
639
|
-
if (apiKey) {
|
|
640
|
-
const apiClient = makeApiClient(apiKey, ctx.envConfig.apiUrl);
|
|
641
|
-
return { ...ctx, accessToken: apiKey, apiClient };
|
|
642
|
-
}
|
|
643
|
-
const creds = ctx.credentialsManager.load(ctx.env);
|
|
644
|
-
if (creds?.accessToken) {
|
|
645
|
-
const buffer = 5 * 60 * 1e3;
|
|
646
|
-
const notExpired = !creds.expiresAt || new Date(creds.expiresAt) > new Date(Date.now() + buffer);
|
|
647
|
-
if (notExpired) {
|
|
648
|
-
const apiClient = makeApiClient(creds.accessToken, ctx.envConfig.apiUrl);
|
|
649
|
-
return { ...ctx, accessToken: creds.accessToken, apiClient };
|
|
650
|
-
}
|
|
651
|
-
const refreshed = await tryTokenRefresh(ctx, creds);
|
|
652
|
-
if (refreshed) return refreshed;
|
|
653
|
-
}
|
|
654
|
-
throw new AuthRequiredError();
|
|
655
|
-
}
|
|
656
|
-
function requireAuth(ctx) {
|
|
657
|
-
if (!("apiClient" in ctx)) {
|
|
658
|
-
throw new Error("Command handler accessed apiClient without declaring requiresAuth");
|
|
659
|
-
}
|
|
660
|
-
return ctx;
|
|
661
|
-
}
|
|
662
|
-
function createPublicContext(opts) {
|
|
663
|
-
const env = opts.env ?? "prod";
|
|
664
|
-
const envConfig = opts.apiUrl ? {
|
|
665
|
-
apiUrl: opts.apiUrl,
|
|
666
|
-
frontendUrl: opts.frontendUrl || process.env.GRAVITY_RAIL_FRONTEND_URL || "https://app.gravityrail.com"
|
|
667
|
-
} : getEnvironmentConfig(env);
|
|
668
|
-
const credentialsManager = new CredentialsManager(homedir(), envConfig.apiUrl);
|
|
669
|
-
return {
|
|
670
|
-
env,
|
|
671
|
-
envConfig,
|
|
672
|
-
outputFormat: detectOutputFormat(opts.outputFormat),
|
|
673
|
-
verbose: opts.verbose ?? false,
|
|
674
|
-
allowWrite: opts.allowWrite ?? false,
|
|
675
|
-
credentialsManager
|
|
676
|
-
};
|
|
677
|
-
}
|
|
678
|
-
async function createPublicAuthenticatedContext(opts) {
|
|
679
|
-
const ctx = createPublicContext(opts);
|
|
680
|
-
const apiKey = process.env.GRAVITY_RAIL_API_KEY;
|
|
681
|
-
if (apiKey) {
|
|
682
|
-
const apiClient = makeApiClient(apiKey, ctx.envConfig.apiUrl);
|
|
683
|
-
return { ...ctx, accessToken: apiKey, apiClient };
|
|
684
|
-
}
|
|
685
|
-
const creds = ctx.credentialsManager.load(ctx.env);
|
|
686
|
-
if (creds?.accessToken) {
|
|
687
|
-
const buffer = 5 * 60 * 1e3;
|
|
688
|
-
const notExpired = !creds.expiresAt || new Date(creds.expiresAt) > new Date(Date.now() + buffer);
|
|
689
|
-
if (notExpired) {
|
|
690
|
-
const apiClient = makeApiClient(creds.accessToken, ctx.envConfig.apiUrl);
|
|
691
|
-
return { ...ctx, accessToken: creds.accessToken, apiClient };
|
|
692
|
-
}
|
|
693
|
-
const refreshed = await tryTokenRefresh(ctx, creds);
|
|
694
|
-
if (refreshed) return refreshed;
|
|
695
|
-
}
|
|
696
|
-
throw new AuthRequiredError();
|
|
697
|
-
}
|
|
698
|
-
|
|
699
123
|
// src/lib/input.ts
|
|
700
|
-
import { readFileSync
|
|
124
|
+
import { readFileSync } from "fs";
|
|
701
125
|
var JSON_BODY_ACTIONS = /* @__PURE__ */ new Set([
|
|
702
126
|
"create",
|
|
703
127
|
"update",
|
|
@@ -744,7 +168,7 @@ async function readJsonInput(args) {
|
|
|
744
168
|
}
|
|
745
169
|
if (filePath) {
|
|
746
170
|
try {
|
|
747
|
-
const content =
|
|
171
|
+
const content = readFileSync(filePath, "utf-8");
|
|
748
172
|
return JSON.parse(content);
|
|
749
173
|
} catch (err) {
|
|
750
174
|
if (err instanceof SyntaxError) {
|
|
@@ -1344,6 +768,11 @@ var CommandRegistry = class {
|
|
|
1344
768
|
const key = cmd.path.join(" ");
|
|
1345
769
|
this.commands.set(key, cmd);
|
|
1346
770
|
}
|
|
771
|
+
registerCommands(commands3) {
|
|
772
|
+
for (const cmd of commands3) {
|
|
773
|
+
this.registerCommand(cmd);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
1347
776
|
/** Resolve command words to a CommandDef using longest-match.
|
|
1348
777
|
* Supports {param} path segments that match any word and extract as pathParams. */
|
|
1349
778
|
resolve(words) {
|
|
@@ -1376,6 +805,7 @@ var CommandRegistry = class {
|
|
|
1376
805
|
}
|
|
1377
806
|
/** Generate help text. If prefix given, show commands under that prefix. */
|
|
1378
807
|
getHelp(prefix) {
|
|
808
|
+
const inSandbox = process.env.GRAVITY_RAIL_SANDBOX === "1";
|
|
1379
809
|
if (!prefix || prefix.length === 0) {
|
|
1380
810
|
return this.getTopLevelHelp();
|
|
1381
811
|
}
|
|
@@ -1412,18 +842,21 @@ var CommandRegistry = class {
|
|
|
1412
842
|
`, "Commands:"];
|
|
1413
843
|
for (const cmd of matching.sort((a, b) => a.path.join(" ").localeCompare(b.path.join(" ")))) {
|
|
1414
844
|
const subPath = cmd.path.slice(prefix.length).join(" ") || cmd.path[cmd.path.length - 1];
|
|
1415
|
-
|
|
845
|
+
const description = inSandbox ? sandboxHelpText(cmd.description) : cmd.description;
|
|
846
|
+
lines.push(` ${subPath.padEnd(28)} ${description}`);
|
|
1416
847
|
}
|
|
1417
848
|
return lines.join("\n") + "\n";
|
|
1418
849
|
}
|
|
1419
850
|
getCommandHelp(cmd) {
|
|
1420
851
|
const cmdStr = cmd.path.join(" ");
|
|
1421
|
-
const
|
|
852
|
+
const inSandbox = process.env.GRAVITY_RAIL_SANDBOX === "1";
|
|
853
|
+
const description = inSandbox ? sandboxHelpText(cmd.description) : cmd.description;
|
|
854
|
+
const lines = [`${description}
|
|
1422
855
|
`, `Usage: gr ${cmdStr} [options]`];
|
|
1423
|
-
if (cmd.requiresWorkspace) {
|
|
856
|
+
if (cmd.requiresWorkspace && !inSandbox) {
|
|
1424
857
|
lines.push(" Requires: -w <workspace-uuid> (or --realm <workspace>/<member> in dev)");
|
|
1425
858
|
}
|
|
1426
|
-
if (cmd.mutates) {
|
|
859
|
+
if (cmd.mutates && !inSandbox) {
|
|
1427
860
|
lines.push(" Requires: --allow-write");
|
|
1428
861
|
}
|
|
1429
862
|
const action = cmd.path[cmd.path.length - 1] ?? "";
|
|
@@ -1445,7 +878,7 @@ var CommandRegistry = class {
|
|
|
1445
878
|
if (cmd.examples && cmd.examples.length > 0) {
|
|
1446
879
|
lines.push("", "Examples:");
|
|
1447
880
|
for (const ex of cmd.examples) {
|
|
1448
|
-
lines.push(` ${ex}`);
|
|
881
|
+
lines.push(` ${inSandbox ? sandboxExample(ex) : ex}`);
|
|
1449
882
|
}
|
|
1450
883
|
}
|
|
1451
884
|
return lines.join("\n") + "\n";
|
|
@@ -1460,38 +893,50 @@ var CommandRegistry = class {
|
|
|
1460
893
|
}
|
|
1461
894
|
getTopLevelHelp() {
|
|
1462
895
|
const lines = ["Usage: gr <command> [options]\n", "Commands:"];
|
|
896
|
+
const inSandbox = process.env.GRAVITY_RAIL_SANDBOX === "1";
|
|
1463
897
|
const seen = /* @__PURE__ */ new Set();
|
|
1464
898
|
for (const domain of this.domains) {
|
|
1465
899
|
if (!seen.has(domain.name)) {
|
|
1466
900
|
seen.add(domain.name);
|
|
1467
|
-
|
|
901
|
+
const description = inSandbox ? sandboxHelpText(domain.description) : domain.description;
|
|
902
|
+
lines.push(` ${domain.name.padEnd(28)} ${description}`);
|
|
1468
903
|
}
|
|
1469
904
|
}
|
|
1470
905
|
for (const [, cmd] of this.commands) {
|
|
1471
906
|
const topWord = cmd.path[0];
|
|
1472
907
|
if (!seen.has(topWord) && cmd.path.length === 1) {
|
|
1473
908
|
seen.add(topWord);
|
|
1474
|
-
|
|
909
|
+
const description = inSandbox ? sandboxHelpText(cmd.description) : cmd.description;
|
|
910
|
+
lines.push(` ${topWord.padEnd(28)} ${description}`);
|
|
1475
911
|
}
|
|
1476
912
|
}
|
|
1477
|
-
|
|
913
|
+
const globalOptions = [
|
|
1478
914
|
"",
|
|
1479
915
|
"Global Options:",
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
916
|
+
...!inSandbox ? [
|
|
917
|
+
" --env <env> Environment: dev, staging, prod (default: dev)",
|
|
918
|
+
" --realm <ws>/<member> Dev auth shortcut (dev only; sets -w automatically)",
|
|
919
|
+
" --api-url <url> API base URL (or GRAVITY_RAIL_API_URL)",
|
|
920
|
+
" -w, --workspace <uuid> Workspace UUID"
|
|
921
|
+
] : [],
|
|
1484
922
|
" -d, --data <json> JSON body (create/update and other write commands)",
|
|
1485
923
|
" -f, --file <path> JSON body from file",
|
|
1486
|
-
" --allow-write Required for create/update/delete/archive",
|
|
924
|
+
...!inSandbox ? [" --allow-write Required for create/update/delete/archive"] : [],
|
|
1487
925
|
" -o, --output-format <fmt> Output format: json, jsonl, table (default: auto)",
|
|
1488
926
|
" -v, --verbose Verbose output to stderr",
|
|
1489
927
|
" --help Show help",
|
|
1490
928
|
" --version Show version"
|
|
1491
|
-
|
|
929
|
+
];
|
|
930
|
+
lines.push(...globalOptions);
|
|
1492
931
|
return lines.join("\n") + "\n";
|
|
1493
932
|
}
|
|
1494
933
|
};
|
|
934
|
+
function sandboxHelpText(text) {
|
|
935
|
+
return text.replace(/\byarn gr\b/g, "gr").replace(/\s--allow-write\b/g, "").replace(/\s--realm\s+(?:"[^"]+"|'[^']+'|<[^>]+>|\S+)/g, "").replace(/\s-w\s+(?:"[^"]+"|'[^']+'|<[^>]+>|\S+)/g, "");
|
|
936
|
+
}
|
|
937
|
+
function sandboxExample(example) {
|
|
938
|
+
return sandboxHelpText(example);
|
|
939
|
+
}
|
|
1495
940
|
|
|
1496
941
|
// src/lib/types.ts
|
|
1497
942
|
function requireString(args, key) {
|
|
@@ -1514,8 +959,8 @@ function requireNumberFrom(args, ...keys) {
|
|
|
1514
959
|
if (!isNaN(num)) return num;
|
|
1515
960
|
}
|
|
1516
961
|
}
|
|
1517
|
-
const
|
|
1518
|
-
throw new Error(`Missing required argument: ${
|
|
962
|
+
const hint2 = keys.map((k) => `--${k}`).join(" or ");
|
|
963
|
+
throw new Error(`Missing required argument: ${hint2}`);
|
|
1519
964
|
}
|
|
1520
965
|
|
|
1521
966
|
// src/domains/agent-tasks.ts
|
|
@@ -1653,6 +1098,42 @@ var agentTasksDomain = {
|
|
|
1653
1098
|
|
|
1654
1099
|
// src/domains/agents.ts
|
|
1655
1100
|
var COLUMNS = [COL.id(), COL.uuid(), COL.name(), COL.status(), COL.created()];
|
|
1101
|
+
var TOOL_COLUMNS = [
|
|
1102
|
+
COL.name(),
|
|
1103
|
+
{ header: "DESCRIPTION", getValue: (item) => (item.description ?? "").slice(0, 60) }
|
|
1104
|
+
];
|
|
1105
|
+
var ABILITY_COLUMNS = [
|
|
1106
|
+
COL.uuid(),
|
|
1107
|
+
{ header: "ABILITY-TYPE", getValue: (item) => item.abilityType ?? "" },
|
|
1108
|
+
COL.enabled(),
|
|
1109
|
+
{ header: "CEL-CONDITION", getValue: (item) => item.celCondition ?? "" }
|
|
1110
|
+
];
|
|
1111
|
+
function requireAbilityScope(args) {
|
|
1112
|
+
const hasWorkflow = typeof args["workflow-id"] === "string";
|
|
1113
|
+
const hasTask = typeof args["task-id"] === "string";
|
|
1114
|
+
if (hasWorkflow === hasTask) {
|
|
1115
|
+
throw new CLIError(
|
|
1116
|
+
"Specify exactly one of --workflow-id or --task-id. Abilities are scoped to a workflow or task, not to the agent row itself."
|
|
1117
|
+
);
|
|
1118
|
+
}
|
|
1119
|
+
if (hasTask) {
|
|
1120
|
+
const taskId = Number(args["task-id"]);
|
|
1121
|
+
if (isNaN(taskId)) throw new CLIError("--task-id must be a number");
|
|
1122
|
+
return { taskId };
|
|
1123
|
+
}
|
|
1124
|
+
const workflowId = Number(args["workflow-id"]);
|
|
1125
|
+
if (isNaN(workflowId)) throw new CLIError("--workflow-id must be a number");
|
|
1126
|
+
return { workflowId };
|
|
1127
|
+
}
|
|
1128
|
+
function normalizeChannel(channel) {
|
|
1129
|
+
const aliases = {
|
|
1130
|
+
phone: "phone-voice",
|
|
1131
|
+
voice: "phone-voice",
|
|
1132
|
+
sms: "phone-sms",
|
|
1133
|
+
web: "web-chat"
|
|
1134
|
+
};
|
|
1135
|
+
return aliases[channel] ?? channel;
|
|
1136
|
+
}
|
|
1656
1137
|
var agentsDomain = {
|
|
1657
1138
|
name: "agents",
|
|
1658
1139
|
description: "Manage agents",
|
|
@@ -1668,52 +1149,207 @@ var agentsDomain = {
|
|
|
1668
1149
|
archiveMethod: "archiveAgent",
|
|
1669
1150
|
unarchiveMethod: "unarchiveAgent",
|
|
1670
1151
|
columns: COLUMNS,
|
|
1671
|
-
paginationSupport: "full"
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1152
|
+
paginationSupport: "full",
|
|
1153
|
+
updateHelp: "Update an agent. Body schema (JSON via --data/--file/stdin), all fields optional:\n name string display name\n description string short summary shown in listings\n systemPrompt string base instructions for the agent\n model string AI model slug (see `gr ai-models list`)\n temperature number sampling temperature (0-2)\n avatarFileId number workspace file ID for the avatar\n config object AgentConfig overrides (advanced)\nAbilities are NOT set here \u2014 they live on a workflow/task scope; use `gr agents abilities` to manage them.",
|
|
1154
|
+
updateExamples: [
|
|
1155
|
+
`gr agents update --id 42 --data '{"name":"Scheduler","temperature":0.4}'`,
|
|
1156
|
+
"gr agents update --id 42 --file agent.json"
|
|
1157
|
+
]
|
|
1158
|
+
}),
|
|
1159
|
+
{
|
|
1160
|
+
path: ["agents", "resolve-tools"],
|
|
1161
|
+
description: "Show resolved runtime tools for an agent in a channel context. Channel accepts canonical values (phone-voice, web-voice, phone-sms, web-chat) or shorthand aliases: phone -> phone-voice, voice -> phone-voice, sms -> phone-sms, web -> web-chat. Unknown values are passed through unchanged. When neither --task-id nor --task-uuid is supplied the agent's workflow start task is used.",
|
|
1162
|
+
requiresWorkspace: true,
|
|
1163
|
+
args: [
|
|
1164
|
+
{ name: "id", description: "Agent ID", required: true },
|
|
1165
|
+
{
|
|
1166
|
+
name: "channel",
|
|
1167
|
+
description: "Channel to simulate. Canonical: phone-voice, web-voice, phone-sms, web-chat. Aliases: phone|voice -> phone-voice, sms -> phone-sms, web -> web-chat.",
|
|
1168
|
+
required: true
|
|
1169
|
+
},
|
|
1170
|
+
{
|
|
1171
|
+
name: "task-id",
|
|
1172
|
+
description: "Numeric task ID whose workflow abilities to resolve. Omit to use the workflow start task (or supply --task-uuid instead).",
|
|
1173
|
+
required: false
|
|
1174
|
+
},
|
|
1175
|
+
{
|
|
1176
|
+
name: "task-uuid",
|
|
1177
|
+
description: "Task UUID as an alternative to --task-id. Omit to use the workflow start task.",
|
|
1178
|
+
required: false
|
|
1179
|
+
}
|
|
1180
|
+
],
|
|
1181
|
+
handler: async ({ ctx, workspace, args }) => {
|
|
1182
|
+
const rawTaskId = args["task-id"];
|
|
1183
|
+
const rawTaskUuid = args["task-uuid"];
|
|
1184
|
+
if (rawTaskId !== void 0 && rawTaskUuid !== void 0) {
|
|
1185
|
+
throw new Error("Provide at most one of --task-id or --task-uuid, not both");
|
|
1186
|
+
}
|
|
1187
|
+
const options = {
|
|
1188
|
+
channel: normalizeChannel(String(args.channel))
|
|
1189
|
+
};
|
|
1190
|
+
if (typeof rawTaskId === "string") {
|
|
1191
|
+
const n = Number(rawTaskId);
|
|
1192
|
+
if (isNaN(n)) throw new Error("Argument --task-id must be a number");
|
|
1193
|
+
options.taskId = n;
|
|
1194
|
+
}
|
|
1195
|
+
if (typeof rawTaskUuid === "string") {
|
|
1196
|
+
options.taskUuid = rawTaskUuid;
|
|
1197
|
+
}
|
|
1198
|
+
const result2 = await requireAuth(ctx).apiClient.resolveAgentTools(
|
|
1199
|
+
workspace,
|
|
1200
|
+
requireNumber(args, "id"),
|
|
1201
|
+
options
|
|
1202
|
+
);
|
|
1203
|
+
const tools = result2.tools ?? result2;
|
|
1204
|
+
outputData(Array.isArray(tools) ? tools : [tools], ctx, TOOL_COLUMNS);
|
|
1205
|
+
}
|
|
1206
|
+
},
|
|
1207
|
+
{
|
|
1208
|
+
path: ["agents", "abilities", "list"],
|
|
1209
|
+
description: "Show the abilities effective for an agent on a task (read-only). Requires --task-id because abilities are resolved through the task's workflow, not stored on the agent row.",
|
|
1210
|
+
requiresWorkspace: true,
|
|
1211
|
+
columns: ABILITY_COLUMNS,
|
|
1212
|
+
args: [
|
|
1213
|
+
{ name: "id", description: "Agent ID", required: true },
|
|
1214
|
+
{
|
|
1215
|
+
name: "task-id",
|
|
1216
|
+
description: "Task ID whose workflow abilities to list",
|
|
1217
|
+
required: true
|
|
1218
|
+
}
|
|
1219
|
+
],
|
|
1220
|
+
handler: async ({ ctx, workspace, args }) => {
|
|
1221
|
+
const result2 = await requireAuth(ctx).apiClient.listAgentAbilities(
|
|
1222
|
+
workspace,
|
|
1223
|
+
requireNumber(args, "id"),
|
|
1224
|
+
{ taskId: requireNumber(args, "task-id") }
|
|
1225
|
+
);
|
|
1226
|
+
outputData(result2, ctx, ABILITY_COLUMNS);
|
|
1227
|
+
}
|
|
1228
|
+
},
|
|
1229
|
+
{
|
|
1230
|
+
path: ["agents", "abilities", "enable"],
|
|
1231
|
+
description: "Enable (create) an ability on a workflow or task scope. Requires exactly one of --workflow-id or --task-id \u2014 abilities are scoped to a workflow/task, never to the agent row. Provide initial --config JSON inline or via --data/--file.",
|
|
1232
|
+
mutates: true,
|
|
1233
|
+
requiresWorkspace: true,
|
|
1234
|
+
args: [
|
|
1235
|
+
{ name: "ability-type", description: "Ability type slug (e.g. 'communication:send_sms')", required: true },
|
|
1236
|
+
{ name: "workflow-id", description: "Workflow scope (mutually exclusive with --task-id)" },
|
|
1237
|
+
{ name: "task-id", description: "Task scope (mutually exclusive with --workflow-id)" }
|
|
1238
|
+
],
|
|
1239
|
+
examples: [
|
|
1240
|
+
"gr agents abilities enable --workflow-id 7 --ability-type tools:web_search",
|
|
1241
|
+
`gr agents abilities enable --task-id 12 --ability-type integration:data_access --data '{"dataTypeUuid":"..."}'`
|
|
1242
|
+
],
|
|
1243
|
+
handler: async ({ ctx, workspace, args, data }) => {
|
|
1244
|
+
const abilityType = args["ability-type"];
|
|
1245
|
+
if (typeof abilityType !== "string") {
|
|
1246
|
+
throw new CLIError("--ability-type is required");
|
|
1247
|
+
}
|
|
1248
|
+
const scope = requireAbilityScope(args);
|
|
1249
|
+
const config = data && typeof data === "object" ? data : {};
|
|
1250
|
+
const body = {
|
|
1251
|
+
abilityType,
|
|
1252
|
+
config,
|
|
1253
|
+
...scope
|
|
1254
|
+
};
|
|
1255
|
+
const result2 = await requireAuth(ctx).apiClient.createAbility(workspace, body);
|
|
1256
|
+
outputSingle(result2, ctx, ABILITY_COLUMNS);
|
|
1257
|
+
}
|
|
1258
|
+
},
|
|
1259
|
+
{
|
|
1260
|
+
path: ["agents", "abilities", "disable"],
|
|
1261
|
+
description: "Disable an ability instance by UUID (sets enabled=false, keeps config). Use `gr agents abilities list` to find the UUID.",
|
|
1262
|
+
mutates: true,
|
|
1263
|
+
requiresWorkspace: true,
|
|
1264
|
+
args: [{ name: "uuid", description: "Ability instance UUID", required: true }],
|
|
1265
|
+
handler: async ({ ctx, workspace, args }) => {
|
|
1266
|
+
const uuid = args.uuid;
|
|
1267
|
+
if (typeof uuid !== "string") throw new CLIError("--uuid is required");
|
|
1268
|
+
const result2 = await requireAuth(ctx).apiClient.updateAbility(workspace, uuid, {
|
|
1269
|
+
enabled: false
|
|
1270
|
+
});
|
|
1271
|
+
outputSingle(result2, ctx, ABILITY_COLUMNS);
|
|
1272
|
+
}
|
|
1273
|
+
},
|
|
1274
|
+
{
|
|
1275
|
+
path: ["agents", "abilities", "configure"],
|
|
1276
|
+
description: "Update an ability instance by UUID. Pass --data/--file JSON to merge into config, and/or --enabled true|false to toggle. config is merged with the existing config server-side.",
|
|
1277
|
+
mutates: true,
|
|
1278
|
+
requiresWorkspace: true,
|
|
1279
|
+
args: [
|
|
1280
|
+
{ name: "uuid", description: "Ability instance UUID", required: true },
|
|
1281
|
+
{ name: "enabled", description: "Set enabled state (true|false)" }
|
|
1282
|
+
],
|
|
1283
|
+
examples: [
|
|
1284
|
+
`gr agents abilities configure --uuid <uuid> --data '{"canForward":true}'`,
|
|
1285
|
+
"gr agents abilities configure --uuid <uuid> --enabled true"
|
|
1286
|
+
],
|
|
1287
|
+
handler: async ({ ctx, workspace, args, data }) => {
|
|
1288
|
+
const uuid = args.uuid;
|
|
1289
|
+
if (typeof uuid !== "string") throw new CLIError("--uuid is required");
|
|
1290
|
+
const body = {};
|
|
1291
|
+
if (data && typeof data === "object") {
|
|
1292
|
+
body.config = data;
|
|
1293
|
+
}
|
|
1294
|
+
if (typeof args.enabled === "string") {
|
|
1295
|
+
if (args.enabled !== "true" && args.enabled !== "false") {
|
|
1296
|
+
throw new CLIError("--enabled must be true or false");
|
|
1297
|
+
}
|
|
1298
|
+
body.enabled = args.enabled === "true";
|
|
1299
|
+
}
|
|
1300
|
+
if (Object.keys(body).length === 0) {
|
|
1301
|
+
throw new CLIError(
|
|
1302
|
+
"Nothing to update: provide --data config and/or --enabled"
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
const result2 = await requireAuth(ctx).apiClient.updateAbility(workspace, uuid, body);
|
|
1306
|
+
outputSingle(result2, ctx, ABILITY_COLUMNS);
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
]
|
|
1310
|
+
};
|
|
1311
|
+
|
|
1312
|
+
// src/domains/chats.ts
|
|
1313
|
+
var COLUMNS2 = [
|
|
1314
|
+
COL.id(),
|
|
1315
|
+
COL.uuid(),
|
|
1316
|
+
{ header: "TYPE", getValue: (item) => item.chatType ?? item.chat_type ?? "" },
|
|
1317
|
+
{ header: "MEMBER", getValue: (item) => item.member_name ?? String(item.member_id ?? "") },
|
|
1318
|
+
COL.status(),
|
|
1319
|
+
{ header: "CHANNEL", getValue: (item) => item.channel ?? "" },
|
|
1320
|
+
COL.created()
|
|
1321
|
+
];
|
|
1322
|
+
var LABEL_COLUMNS = [
|
|
1323
|
+
COL.id(),
|
|
1324
|
+
COL.uuid(),
|
|
1325
|
+
COL.name(),
|
|
1326
|
+
{ header: "COLOR", getValue: (item) => item.color ?? "" }
|
|
1327
|
+
];
|
|
1328
|
+
var FILTER_COLUMNS = [COL.id(), COL.uuid(), COL.name()];
|
|
1329
|
+
var MESSAGE_COLUMNS2 = [
|
|
1330
|
+
COL.id(),
|
|
1331
|
+
{ header: "ROLE", getValue: (item) => item.role ?? "" },
|
|
1332
|
+
{
|
|
1333
|
+
header: "CONTENT",
|
|
1334
|
+
getValue: (item) => String(item.content ?? "").slice(0, 80),
|
|
1335
|
+
maxWidth: 80
|
|
1336
|
+
},
|
|
1337
|
+
{
|
|
1338
|
+
header: "TOOL_CALLS",
|
|
1339
|
+
getValue: (item) => Array.isArray(item.tool_calls) && item.tool_calls.length > 0 ? item.tool_calls.map((t) => t.function?.name ?? "?").join(",") : ""
|
|
1340
|
+
},
|
|
1341
|
+
COL.created()
|
|
1342
|
+
];
|
|
1343
|
+
var chatsDomain = {
|
|
1344
|
+
name: "chats",
|
|
1345
|
+
description: "Manage chats",
|
|
1346
|
+
commands: [
|
|
1347
|
+
...crudCommands({
|
|
1348
|
+
prefix: ["chats"],
|
|
1349
|
+
entityName: "chat",
|
|
1350
|
+
listMethod: "getChats",
|
|
1351
|
+
getMethod: "getChat",
|
|
1352
|
+
createMethod: "createChat",
|
|
1717
1353
|
updateMethod: "updateChat",
|
|
1718
1354
|
deleteMethod: "deleteChat",
|
|
1719
1355
|
archiveMethod: "archiveChat",
|
|
@@ -1894,21 +1530,47 @@ var chatsDomain = {
|
|
|
1894
1530
|
// Inspect (aggregated debug view)
|
|
1895
1531
|
{
|
|
1896
1532
|
path: ["chats", "inspect"],
|
|
1897
|
-
description: "Inspect a chat with messages, assignment state, and data records",
|
|
1898
|
-
requiresWorkspace:
|
|
1533
|
+
description: "Inspect a chat with messages, assignment state, and data records. Omit -w for concierge (account-level) chats.",
|
|
1534
|
+
requiresWorkspace: false,
|
|
1899
1535
|
args: [{ name: "id", description: "Chat ID", required: true }],
|
|
1900
1536
|
handler: async ({ ctx, workspace, args }) => {
|
|
1901
1537
|
const chatId = requireNumber(args, "id");
|
|
1902
|
-
const
|
|
1903
|
-
requireAuth(ctx).apiClient.getChat(workspace, chatId),
|
|
1904
|
-
requireAuth(ctx).apiClient.getChatMessages(workspace, chatId),
|
|
1905
|
-
requireAuth(ctx).apiClient.getWorkspace(workspace)
|
|
1906
|
-
]);
|
|
1907
|
-
const isHipaa = ws.features?.includes("hipaa_compliance");
|
|
1538
|
+
const authed = requireAuth(ctx);
|
|
1908
1539
|
const truncate2 = (content) => {
|
|
1909
1540
|
const text = typeof content === "string" ? content : JSON.stringify(content);
|
|
1910
1541
|
return text.slice(0, 200);
|
|
1911
1542
|
};
|
|
1543
|
+
if (!workspace) {
|
|
1544
|
+
const [chat2, messages2] = await Promise.all([
|
|
1545
|
+
getConciergeChat(authed, chatId),
|
|
1546
|
+
getConciergeChatMessages(authed, chatId)
|
|
1547
|
+
]);
|
|
1548
|
+
outputSingle(
|
|
1549
|
+
{
|
|
1550
|
+
chat: {
|
|
1551
|
+
id: chat2.id,
|
|
1552
|
+
uuid: chat2.uuid,
|
|
1553
|
+
title: chat2.title,
|
|
1554
|
+
chatType: chat2.chatType,
|
|
1555
|
+
threadId: chat2.threadId,
|
|
1556
|
+
createdAt: chat2.createdAt
|
|
1557
|
+
},
|
|
1558
|
+
messages: messages2.map((m) => ({
|
|
1559
|
+
id: m.id,
|
|
1560
|
+
role: m.role,
|
|
1561
|
+
content: truncate2(m.content)
|
|
1562
|
+
}))
|
|
1563
|
+
},
|
|
1564
|
+
ctx
|
|
1565
|
+
);
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
const [chat, messages, ws] = await Promise.all([
|
|
1569
|
+
authed.apiClient.getChat(workspace, chatId),
|
|
1570
|
+
authed.apiClient.getChatMessages(workspace, chatId),
|
|
1571
|
+
authed.apiClient.getWorkspace(workspace)
|
|
1572
|
+
]);
|
|
1573
|
+
const isHipaa = ws.features?.includes("hipaa_compliance");
|
|
1912
1574
|
const result2 = {
|
|
1913
1575
|
chat: {
|
|
1914
1576
|
id: chat.id,
|
|
@@ -1928,10 +1590,7 @@ var chatsDomain = {
|
|
|
1928
1590
|
};
|
|
1929
1591
|
const assignmentId = chat.assignmentId;
|
|
1930
1592
|
if (assignmentId) {
|
|
1931
|
-
const assignment = await
|
|
1932
|
-
workspace,
|
|
1933
|
-
assignmentId
|
|
1934
|
-
);
|
|
1593
|
+
const assignment = await authed.apiClient.getAssignment(workspace, assignmentId);
|
|
1935
1594
|
result2.assignment = {
|
|
1936
1595
|
id: assignment.id,
|
|
1937
1596
|
status: assignment.status,
|
|
@@ -2186,10 +1845,7 @@ var memberRolesDomain = {
|
|
|
2186
1845
|
updateMethod: "updateMemberRole",
|
|
2187
1846
|
deleteMethod: "deleteMemberRole",
|
|
2188
1847
|
columns: ROLE_COLUMNS2,
|
|
2189
|
-
createExamples: [
|
|
2190
|
-
"gr member-roles list -w $WID",
|
|
2191
|
-
"gr member-roles get -w $WID --id 2"
|
|
2192
|
-
]
|
|
1848
|
+
createExamples: ["gr member-roles list -w $WID", "gr member-roles get -w $WID --id 2"]
|
|
2193
1849
|
})
|
|
2194
1850
|
};
|
|
2195
1851
|
|
|
@@ -2198,10 +1854,13 @@ var COLUMNS4 = [
|
|
|
2198
1854
|
COL.id(),
|
|
2199
1855
|
COL.uuid(),
|
|
2200
1856
|
COL.name(),
|
|
2201
|
-
|
|
1857
|
+
// TaskResponse has no `status` field — it has `archived`. Surface that
|
|
1858
|
+
// directly so active vs archived is visible in the default table (relevant
|
|
1859
|
+
// once --include-archived mixes both into the listing).
|
|
1860
|
+
{ header: "STATUS", getValue: (item) => item.archived ? "archived" : "active" },
|
|
2202
1861
|
{
|
|
2203
1862
|
header: "WORKFLOW",
|
|
2204
|
-
getValue: (item) =>
|
|
1863
|
+
getValue: (item) => String(item.workflowId ?? "")
|
|
2205
1864
|
},
|
|
2206
1865
|
COL.created()
|
|
2207
1866
|
];
|
|
@@ -2219,7 +1878,21 @@ var tasksDomain = {
|
|
|
2219
1878
|
deleteMethod: "deleteTask",
|
|
2220
1879
|
archiveMethod: "archiveTask",
|
|
2221
1880
|
unarchiveMethod: "unarchiveTask",
|
|
2222
|
-
columns: COLUMNS4
|
|
1881
|
+
columns: COLUMNS4,
|
|
1882
|
+
// `tasks list` is workspace-wide by default. Filter to one workflow
|
|
1883
|
+
// with --workflow-id, and opt into archived tasks with
|
|
1884
|
+
// --include-archived (active-only by default, matching the backend).
|
|
1885
|
+
listArgs: [
|
|
1886
|
+
{
|
|
1887
|
+
name: "workflow-id",
|
|
1888
|
+
description: "Filter tasks to a single workflow by ID"
|
|
1889
|
+
},
|
|
1890
|
+
{
|
|
1891
|
+
name: "include-archived",
|
|
1892
|
+
description: "Include archived tasks in the listing",
|
|
1893
|
+
isFlag: true
|
|
1894
|
+
}
|
|
1895
|
+
]
|
|
2223
1896
|
}),
|
|
2224
1897
|
{
|
|
2225
1898
|
path: ["tasks", "clone"],
|
|
@@ -2274,9 +1947,18 @@ var COLUMNS5 = [
|
|
|
2274
1947
|
COL.id(),
|
|
2275
1948
|
COL.uuid(),
|
|
2276
1949
|
COL.name(),
|
|
2277
|
-
|
|
1950
|
+
// taskCount is the total (active + archived). Show the active count as the
|
|
1951
|
+
// headline number — matching `tasks list` — and call out archived tasks
|
|
1952
|
+
// separately so the table never overstates how many tasks actually run.
|
|
1953
|
+
{ header: "TASKS", getValue: (item) => formatTaskCount(item) },
|
|
2278
1954
|
COL.created()
|
|
2279
1955
|
];
|
|
1956
|
+
function formatTaskCount(item) {
|
|
1957
|
+
const total = Number(item.taskCount ?? 0);
|
|
1958
|
+
const archived = Number(item.archivedTaskCount ?? 0);
|
|
1959
|
+
const active = total - archived;
|
|
1960
|
+
return archived > 0 ? `${active} (+${archived} archived)` : String(active);
|
|
1961
|
+
}
|
|
2280
1962
|
var NOTE_COLUMNS = [
|
|
2281
1963
|
COL.id(),
|
|
2282
1964
|
{ header: "TITLE", getValue: (item) => item.title ?? "" },
|
|
@@ -2377,13 +2059,21 @@ var workflowsDomain = {
|
|
|
2377
2059
|
// Diagram
|
|
2378
2060
|
{
|
|
2379
2061
|
path: ["workflows", "diagram"],
|
|
2380
|
-
description: "Get workflow diagram",
|
|
2062
|
+
description: "Get workflow diagram (active tasks only by default)",
|
|
2381
2063
|
requiresWorkspace: true,
|
|
2382
|
-
args: [
|
|
2064
|
+
args: [
|
|
2065
|
+
{ name: "id", description: "Workflow ID", required: true },
|
|
2066
|
+
{
|
|
2067
|
+
name: "include-archived",
|
|
2068
|
+
description: "Render archived tasks (distinctly styled) too",
|
|
2069
|
+
isFlag: true
|
|
2070
|
+
}
|
|
2071
|
+
],
|
|
2383
2072
|
handler: async ({ ctx, workspace, args }) => {
|
|
2384
2073
|
const result2 = await requireAuth(ctx).apiClient.getWorkflowDiagram(
|
|
2385
2074
|
workspace,
|
|
2386
|
-
requireNumber(args, "id")
|
|
2075
|
+
requireNumber(args, "id"),
|
|
2076
|
+
{ includeArchived: args["include-archived"] === true }
|
|
2387
2077
|
);
|
|
2388
2078
|
outputSingle(result2, ctx);
|
|
2389
2079
|
}
|
|
@@ -2900,28 +2590,64 @@ var workspacesDomain = {
|
|
|
2900
2590
|
outputSingle(result2, ctx, COLUMNS7);
|
|
2901
2591
|
}
|
|
2902
2592
|
},
|
|
2903
|
-
//
|
|
2593
|
+
// Provision a workspace under an organization's active subscription.
|
|
2904
2594
|
{
|
|
2905
2595
|
path: ["workspaces", "create"],
|
|
2906
|
-
description: "
|
|
2596
|
+
description: "Provision a workspace in an organization (requires an active subscription)",
|
|
2907
2597
|
mutates: true,
|
|
2908
2598
|
requiresWorkspace: false,
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2599
|
+
args: [
|
|
2600
|
+
{
|
|
2601
|
+
name: "org",
|
|
2602
|
+
aliases: ["org-uuid"],
|
|
2603
|
+
description: "Organization UUID",
|
|
2604
|
+
required: true
|
|
2605
|
+
},
|
|
2606
|
+
{ name: "product", description: "Workspace plan product ID", required: true },
|
|
2607
|
+
{ name: "name", description: "Workspace name", required: true },
|
|
2608
|
+
{ name: "period", description: "Billing period: 'month' or 'year'", default: "month" },
|
|
2609
|
+
{ name: "slug", description: "Workspace slug (optional)" },
|
|
2610
|
+
{ name: "template", description: "Template workspace UUID to clone (optional)" }
|
|
2611
|
+
],
|
|
2612
|
+
examples: ['gr workspaces create --org <org-uuid> --product 14 --name "Front Desk"'],
|
|
2613
|
+
handler: async ({ ctx, args }) => {
|
|
2614
|
+
const orgUuid = args.org ?? args["org-uuid"];
|
|
2615
|
+
const name = args.name;
|
|
2616
|
+
const productRaw = args.product;
|
|
2617
|
+
if (!orgUuid) throw new CLIError("Missing required argument: --org <org-uuid>");
|
|
2618
|
+
if (!name) throw new CLIError("Missing required argument: --name");
|
|
2619
|
+
if (!productRaw) {
|
|
2913
2620
|
throw new CLIError(
|
|
2914
|
-
|
|
2621
|
+
"Missing required argument: --product\nList workspace plans with: gr billing products --type workspace"
|
|
2915
2622
|
);
|
|
2916
2623
|
}
|
|
2624
|
+
const payload = {
|
|
2625
|
+
name,
|
|
2626
|
+
productId: Number(productRaw),
|
|
2627
|
+
planPeriod: args.period || "month"
|
|
2628
|
+
};
|
|
2629
|
+
if (args.slug) payload.slug = args.slug;
|
|
2630
|
+
if (args.template) payload.templateWorkspaceUuid = args.template;
|
|
2917
2631
|
try {
|
|
2918
|
-
const result2 = await requireAuth(ctx).apiClient.
|
|
2632
|
+
const result2 = await requireAuth(ctx).apiClient.createOrganizationWorkspace(
|
|
2633
|
+
orgUuid,
|
|
2634
|
+
payload
|
|
2635
|
+
);
|
|
2919
2636
|
outputSingle(result2, ctx, COLUMNS7);
|
|
2637
|
+
if ((ctx.outputFormat ?? "table") === "table") {
|
|
2638
|
+
const wsUuid = result2?.uuid ?? "<workspace-uuid>";
|
|
2639
|
+
process.stdout.write(
|
|
2640
|
+
`
|
|
2641
|
+
Provisioning started. Watch it come up with:
|
|
2642
|
+
gr workspaces get -w ${wsUuid}
|
|
2643
|
+
`
|
|
2644
|
+
);
|
|
2645
|
+
}
|
|
2920
2646
|
} catch (err) {
|
|
2921
2647
|
const statusCode = err?.statusCode;
|
|
2922
|
-
if (statusCode ===
|
|
2648
|
+
if (statusCode === 400) {
|
|
2923
2649
|
throw new CLIError(
|
|
2924
|
-
|
|
2650
|
+
"Could not create the workspace. The organization needs an active, paid subscription first.\n gr billing products --type org # pick an org plan\n gr billing checkout --product <id> # pay up front\n gr billing status # confirm it is active"
|
|
2925
2651
|
);
|
|
2926
2652
|
}
|
|
2927
2653
|
throw err;
|
|
@@ -3421,7 +3147,7 @@ var MESSAGE_COLUMNS4 = [
|
|
|
3421
3147
|
COL.created()
|
|
3422
3148
|
];
|
|
3423
3149
|
var TASK_COLUMNS = [COL.id(), COL.name(), COL.status()];
|
|
3424
|
-
var
|
|
3150
|
+
var TOOL_COLUMNS2 = [
|
|
3425
3151
|
COL.name(),
|
|
3426
3152
|
{ header: "DESCRIPTION", getValue: (item) => (item.description ?? "").slice(0, 60) }
|
|
3427
3153
|
];
|
|
@@ -3529,7 +3255,7 @@ var assignmentsDomain = {
|
|
|
3529
3255
|
requireNumber(args, "assignment-id")
|
|
3530
3256
|
);
|
|
3531
3257
|
const tools = result2.tools ?? result2;
|
|
3532
|
-
outputData(Array.isArray(tools) ? tools : [tools], ctx,
|
|
3258
|
+
outputData(Array.isArray(tools) ? tools : [tools], ctx, TOOL_COLUMNS2);
|
|
3533
3259
|
}
|
|
3534
3260
|
}
|
|
3535
3261
|
]
|
|
@@ -3975,35 +3701,24 @@ Common optional
|
|
|
3975
3701
|
Scope (set exactly one when objectType requires it)
|
|
3976
3702
|
routineId, workflowId, taskId, dataTypeId, journeyStepId, calendarId, ...
|
|
3977
3703
|
|
|
3978
|
-
chat:create actionParams (outbound voice/SMS)
|
|
3979
|
-
{"
|
|
3980
|
-
"
|
|
3704
|
+
chat:create actionParams (outbound voice/SMS \u2014 starts a workflow chat)
|
|
3705
|
+
{"workflowUUID":"<workflow-uuid>","channel":"phone-voice"|"phone-sms",
|
|
3706
|
+
"phoneNumberUUID":"<uuid>","inboxUUID":"<optional-uuid>","assistantEnabled":<bool>}
|
|
3707
|
+
Keys are UUID strings: workflowUUID (not workflowId), phoneNumberUUID (not
|
|
3708
|
+
phoneNumberId), inboxUUID (not inboxId). Unknown keys are rejected.
|
|
3709
|
+
|
|
3710
|
+
sms:send actionParams (send an SMS, or start an SMS workflow chat)
|
|
3711
|
+
One-off text: {"phoneNumberUUID":"<from-number-uuid>","messageTemplate":"Hi {{name}} \u2026"}
|
|
3712
|
+
Workflow chat: {"phoneNumberUUID":"<from-number-uuid>","workflowUUID":"<workflow-uuid>",
|
|
3713
|
+
"initialUserMessage":"<optional seed>"}
|
|
3714
|
+
phoneNumberUUID (the from-number) is required. The message body is messageTemplate
|
|
3715
|
+
(supports {{record/member}} template vars) \u2014 there is no "body"/"message" key.
|
|
3716
|
+
Provide messageTemplate for a one-off text, OR workflowUUID to start a chat.
|
|
3981
3717
|
|
|
3982
3718
|
member:label actionParams
|
|
3983
3719
|
{"addLabelIds":["<uuid>"],"removeLabelIds":["<uuid>"]}
|
|
3984
3720
|
`;
|
|
3985
3721
|
|
|
3986
|
-
// src/lib/routines/field-help.ts
|
|
3987
|
-
var ROUTINE_FIELD_REFERENCE = `
|
|
3988
|
-
Field reference for --data JSON (create/update):
|
|
3989
|
-
|
|
3990
|
-
Core
|
|
3991
|
-
name (string) Display name
|
|
3992
|
-
workflowId (number) Workflow run when the routine fires
|
|
3993
|
-
targetMemberId (number) Member who receives the scheduled outreach
|
|
3994
|
-
|
|
3995
|
-
Schedule
|
|
3996
|
-
scheduleEnabled (bool) Master on/off for the cron trigger
|
|
3997
|
-
scheduleMode (string) "CRON" (only mode today)
|
|
3998
|
-
cronString (string) Standard cron, e.g. "0 7 * * *"
|
|
3999
|
-
timezone (string, optional) IANA name, e.g. "America/Los_Angeles"
|
|
4000
|
-
Defaults to workspace timezone, else UTC
|
|
4001
|
-
|
|
4002
|
-
Visibility / lifecycle
|
|
4003
|
-
archived (bool) Archive flag on update
|
|
4004
|
-
event rules Use gr events rules create \u2014 not inline here
|
|
4005
|
-
`;
|
|
4006
|
-
|
|
4007
3722
|
// src/domains/events.ts
|
|
4008
3723
|
var RULE_COLUMNS = [
|
|
4009
3724
|
COL.id(),
|
|
@@ -4011,15 +3726,6 @@ var RULE_COLUMNS = [
|
|
|
4011
3726
|
{ header: "EVENT", getValue: (item) => item.eventType ?? "" },
|
|
4012
3727
|
{ header: "ACTIVE", getValue: (item) => String(item.active ?? "") }
|
|
4013
3728
|
];
|
|
4014
|
-
var ROUTINE_COLUMNS = [
|
|
4015
|
-
COL.id(),
|
|
4016
|
-
COL.name(),
|
|
4017
|
-
{ header: "MODE", getValue: (item) => item.scheduleMode ?? "" },
|
|
4018
|
-
{ header: "CRON", getValue: (item) => item.cronString ?? "" },
|
|
4019
|
-
{ header: "TZ", getValue: (item) => item.timezone ?? "UTC" },
|
|
4020
|
-
{ header: "ENABLED", getValue: (item) => String(item.scheduleEnabled ?? "") },
|
|
4021
|
-
{ header: "NEXT RUN", getValue: (item) => item.nextRun ?? "" }
|
|
4022
|
-
];
|
|
4023
3729
|
var eventRulesCrudNoList = crudCommands({
|
|
4024
3730
|
prefix: ["events", "rules"],
|
|
4025
3731
|
entityName: "event rule",
|
|
@@ -4043,8 +3749,8 @@ ${EVENT_RULE_FIELD_REFERENCE}`,
|
|
|
4043
3749
|
`gr events rules create -w $WID --allow-write --data '{"name":"AM voice check-in",`,
|
|
4044
3750
|
' "objectType":"routine","eventType":"routine:triggered","actionType":"chat:create",',
|
|
4045
3751
|
' "routineId":<routine-id>,',
|
|
4046
|
-
' "actionParams":{"
|
|
4047
|
-
` "
|
|
3752
|
+
' "actionParams":{"workflowUUID":"<workflow-uuid>","channel":"phone-voice",',
|
|
3753
|
+
` "phoneNumberUUID":"<workspace-phone-uuid>"}}'`,
|
|
4048
3754
|
"",
|
|
4049
3755
|
' PM SMS variant: use "channel":"phone-sms" and a second rule on the same routineId.',
|
|
4050
3756
|
" Prereq: org phone assigned + gr phone-numbers link; workflow published with start task.",
|
|
@@ -4182,77 +3888,9 @@ var eventsDomain = {
|
|
|
4182
3888
|
const result2 = await requireAuth(ctx).apiClient.searchEventRules(workspace, opts);
|
|
4183
3889
|
outputData(result2, ctx, RULE_COLUMNS);
|
|
4184
3890
|
}
|
|
4185
|
-
}
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
prefix: ["routines"],
|
|
4189
|
-
entityName: "routine",
|
|
4190
|
-
listMethod: "getRoutines",
|
|
4191
|
-
getMethod: "getRoutine",
|
|
4192
|
-
createMethod: "createRoutine",
|
|
4193
|
-
updateMethod: "updateRoutine",
|
|
4194
|
-
deleteMethod: "deleteRoutine",
|
|
4195
|
-
paginationSupport: "full",
|
|
4196
|
-
columns: ROUTINE_COLUMNS,
|
|
4197
|
-
createHelp: `Create a routine
|
|
4198
|
-
|
|
4199
|
-
${ROUTINE_FIELD_REFERENCE}`,
|
|
4200
|
-
updateHelp: `Update a routine
|
|
4201
|
-
|
|
4202
|
-
${ROUTINE_FIELD_REFERENCE}`,
|
|
4203
|
-
createExamples: [
|
|
4204
|
-
"Daily 7am check-in in the member\u2019s timezone (cron is interpreted in `timezone`):",
|
|
4205
|
-
`gr routines create -w $WID --allow-write --data '{"name":"AM check-in",`,
|
|
4206
|
-
' "scheduleEnabled":true,"scheduleMode":"CRON","cronString":"0 7 * * *",',
|
|
4207
|
-
` "timezone":"America/Los_Angeles","targetMemberId":1,"workflowId":2}'`,
|
|
4208
|
-
" (timezone is an IANA name; omit it to default to the workspace timezone, else UTC.",
|
|
4209
|
-
' Without it, "0 7 * * *" fires at 07:00 UTC, not 7am local.)'
|
|
4210
|
-
]
|
|
4211
|
-
}),
|
|
4212
|
-
{
|
|
4213
|
-
path: ["routines", "run"],
|
|
4214
|
-
description: "Run a routine now",
|
|
4215
|
-
mutates: true,
|
|
4216
|
-
requiresWorkspace: true,
|
|
4217
|
-
args: [{ name: "id", description: "Routine ID", required: true }],
|
|
4218
|
-
handler: async ({ ctx, workspace, args, data }) => {
|
|
4219
|
-
const result2 = await requireAuth(ctx).apiClient.runRoutine(
|
|
4220
|
-
workspace,
|
|
4221
|
-
requireNumber(args, "id"),
|
|
4222
|
-
data ?? {}
|
|
4223
|
-
);
|
|
4224
|
-
outputSingle(result2, ctx);
|
|
4225
|
-
}
|
|
4226
|
-
},
|
|
4227
|
-
{
|
|
4228
|
-
path: ["routines", "runs", "list"],
|
|
4229
|
-
description: "List runs for a routine",
|
|
4230
|
-
requiresWorkspace: true,
|
|
4231
|
-
args: [
|
|
4232
|
-
{
|
|
4233
|
-
name: "id",
|
|
4234
|
-
description: "Routine ID (aligns with routines get/run/update/delete)"
|
|
4235
|
-
},
|
|
4236
|
-
{ name: "status", description: "Filter by run status" },
|
|
4237
|
-
{ name: "page", description: "Page number (1-indexed)" },
|
|
4238
|
-
{ name: "page-size", description: "Page size (max 100)" }
|
|
4239
|
-
],
|
|
4240
|
-
handler: async ({ ctx, workspace, args }) => {
|
|
4241
|
-
const routineId = args.id ? requireNumber(args, "id") : void 0;
|
|
4242
|
-
const page = args.page ? requireNumber(args, "page") : void 0;
|
|
4243
|
-
const pageSize = args["page-size"] ? requireNumber(args, "page-size") : void 0;
|
|
4244
|
-
const status = args.status ?? void 0;
|
|
4245
|
-
const result2 = await requireAuth(ctx).apiClient.getRoutineRuns(workspace, {
|
|
4246
|
-
...routineId !== void 0 ? { routineId } : {},
|
|
4247
|
-
...page !== void 0 ? { page } : {},
|
|
4248
|
-
...pageSize !== void 0 ? { pageSize } : {},
|
|
4249
|
-
...status ? { status } : {}
|
|
4250
|
-
});
|
|
4251
|
-
outputData(result2, ctx);
|
|
4252
|
-
}
|
|
4253
|
-
}
|
|
4254
|
-
]
|
|
4255
|
-
};
|
|
3891
|
+
}
|
|
3892
|
+
]
|
|
3893
|
+
};
|
|
4256
3894
|
|
|
4257
3895
|
// src/domains/experiments.ts
|
|
4258
3896
|
var EXPERIMENT_COLUMNS = [
|
|
@@ -5866,6 +5504,178 @@ var sitesDomain = {
|
|
|
5866
5504
|
]
|
|
5867
5505
|
};
|
|
5868
5506
|
|
|
5507
|
+
// src/domains/abilities.ts
|
|
5508
|
+
import { randomUUID } from "crypto";
|
|
5509
|
+
var ATTACHED_COLUMNS = [
|
|
5510
|
+
{ header: "UUID", getValue: (item) => item.uuid ?? "" },
|
|
5511
|
+
{ header: "TYPE", getValue: (item) => item.abilityType ?? "" },
|
|
5512
|
+
{ header: "ENABLED", getValue: (item) => item.enabled === false ? "" : "yes" },
|
|
5513
|
+
{ header: "CEL", getValue: (item) => item.celCondition ?? "" }
|
|
5514
|
+
];
|
|
5515
|
+
function toAbilityData(a) {
|
|
5516
|
+
return {
|
|
5517
|
+
uuid: a.uuid,
|
|
5518
|
+
abilityType: a.abilityType,
|
|
5519
|
+
config: a.config ?? {},
|
|
5520
|
+
enabled: a.enabled ?? true,
|
|
5521
|
+
...a.celCondition ? { celCondition: a.celCondition } : {}
|
|
5522
|
+
};
|
|
5523
|
+
}
|
|
5524
|
+
var TYPE_COLUMNS = [
|
|
5525
|
+
{ header: "SLUG", getValue: (item) => item.slug ?? "" },
|
|
5526
|
+
{ header: "TITLE", getValue: (item) => item.title ?? "" },
|
|
5527
|
+
{ header: "CATEGORY", getValue: (item) => item.category ?? "" },
|
|
5528
|
+
{
|
|
5529
|
+
header: "CHANNELS",
|
|
5530
|
+
// null channels means "all channels" — show that explicitly so an empty
|
|
5531
|
+
// cell never reads as "no channels".
|
|
5532
|
+
getValue: (item) => Array.isArray(item.channels) ? item.channels.join(",") : "all"
|
|
5533
|
+
},
|
|
5534
|
+
{ header: "SINGLETON", getValue: (item) => item.singleton ? "yes" : "" },
|
|
5535
|
+
{ header: "FEATURE-GATE", getValue: (item) => item.requiredFeature ?? "" }
|
|
5536
|
+
];
|
|
5537
|
+
var abilitiesDomain = {
|
|
5538
|
+
name: "abilities",
|
|
5539
|
+
description: "Inspect ability types and attach/detach them on a task",
|
|
5540
|
+
commands: [
|
|
5541
|
+
{
|
|
5542
|
+
path: ["abilities", "list"],
|
|
5543
|
+
description: "List builtin ability types with their channel constraints and param specs. Table shows SLUG, TITLE, CATEGORY, CHANNELS (all = every channel), SINGLETON, FEATURE-GATE; use -o json for full specs including the params JSON Schema.",
|
|
5544
|
+
requiresWorkspace: true,
|
|
5545
|
+
columns: TYPE_COLUMNS,
|
|
5546
|
+
args: [
|
|
5547
|
+
{
|
|
5548
|
+
name: "include-system",
|
|
5549
|
+
description: "Also list auto-injected system abilities (hidden by default)",
|
|
5550
|
+
isFlag: true
|
|
5551
|
+
}
|
|
5552
|
+
],
|
|
5553
|
+
handler: async (hctx) => {
|
|
5554
|
+
const includeSystem = hctx.args["include-system"] === true;
|
|
5555
|
+
const result2 = await requireAuth(hctx.ctx).apiClient.getAbilityTypes(
|
|
5556
|
+
hctx.workspace,
|
|
5557
|
+
includeSystem ? { includeSystem: true } : {}
|
|
5558
|
+
);
|
|
5559
|
+
outputData(result2.types ?? [], hctx.ctx, TYPE_COLUMNS);
|
|
5560
|
+
}
|
|
5561
|
+
},
|
|
5562
|
+
{
|
|
5563
|
+
path: ["abilities", "get"],
|
|
5564
|
+
description: "Show the full spec for one ability type: title, description, category, channels, singleton/system flags, required feature, and the JSON Schema of its params model.",
|
|
5565
|
+
requiresWorkspace: true,
|
|
5566
|
+
args: [
|
|
5567
|
+
{
|
|
5568
|
+
name: "name",
|
|
5569
|
+
aliases: ["slug"],
|
|
5570
|
+
description: "Ability type slug (e.g. 'communication:phone_call_tools')",
|
|
5571
|
+
required: true
|
|
5572
|
+
}
|
|
5573
|
+
],
|
|
5574
|
+
handler: async (hctx) => {
|
|
5575
|
+
const slug = hctx.args.name ?? hctx.args.slug;
|
|
5576
|
+
if (!slug) throw new CLIError("--name is required");
|
|
5577
|
+
const result2 = await requireAuth(hctx.ctx).apiClient.getAbilityType(
|
|
5578
|
+
hctx.workspace,
|
|
5579
|
+
slug
|
|
5580
|
+
);
|
|
5581
|
+
outputSingle(result2, hctx.ctx);
|
|
5582
|
+
}
|
|
5583
|
+
},
|
|
5584
|
+
{
|
|
5585
|
+
path: ["abilities", "attached"],
|
|
5586
|
+
description: "List a task's task-specific abilities (excludes workflow-inherited).",
|
|
5587
|
+
requiresWorkspace: true,
|
|
5588
|
+
columns: ATTACHED_COLUMNS,
|
|
5589
|
+
args: [{ name: "task", description: "Task ID", required: true }],
|
|
5590
|
+
handler: async (hctx) => {
|
|
5591
|
+
const task = await requireAuth(hctx.ctx).apiClient.getTask(
|
|
5592
|
+
hctx.workspace,
|
|
5593
|
+
requireNumber(hctx.args, "task")
|
|
5594
|
+
);
|
|
5595
|
+
outputData(task.abilities ?? [], hctx.ctx, ATTACHED_COLUMNS);
|
|
5596
|
+
}
|
|
5597
|
+
},
|
|
5598
|
+
{
|
|
5599
|
+
path: ["abilities", "attach"],
|
|
5600
|
+
description: "Attach an ability to a task. Reads the task, appends the new ability (generating its uuid), and saves \u2014 so existing abilities aren't lost. Find slugs via 'gr abilities list' and the config schema via 'gr abilities get'.",
|
|
5601
|
+
mutates: true,
|
|
5602
|
+
requiresWorkspace: true,
|
|
5603
|
+
args: [
|
|
5604
|
+
{ name: "task", description: "Task ID to attach to", required: true },
|
|
5605
|
+
{
|
|
5606
|
+
name: "type",
|
|
5607
|
+
aliases: ["ability-type", "slug"],
|
|
5608
|
+
description: "Ability type slug, e.g. 'integration:data_access'",
|
|
5609
|
+
required: true
|
|
5610
|
+
},
|
|
5611
|
+
{
|
|
5612
|
+
name: "config",
|
|
5613
|
+
description: `Ability config as a JSON object, e.g. '{"pauseOnEscalate":true}'`
|
|
5614
|
+
},
|
|
5615
|
+
{ name: "cel", description: "Optional CEL activation condition" },
|
|
5616
|
+
{
|
|
5617
|
+
name: "disabled",
|
|
5618
|
+
description: "Attach in a disabled state (default: enabled)",
|
|
5619
|
+
isFlag: true
|
|
5620
|
+
}
|
|
5621
|
+
],
|
|
5622
|
+
handler: async (hctx) => {
|
|
5623
|
+
const api = requireAuth(hctx.ctx).apiClient;
|
|
5624
|
+
const taskId = requireNumber(hctx.args, "task");
|
|
5625
|
+
const abilityType = hctx.args.type ?? hctx.args["ability-type"] ?? hctx.args.slug;
|
|
5626
|
+
if (!abilityType) throw new CLIError("--type is required (an ability type slug)");
|
|
5627
|
+
let config = {};
|
|
5628
|
+
if (hctx.args.config) {
|
|
5629
|
+
try {
|
|
5630
|
+
config = JSON.parse(hctx.args.config);
|
|
5631
|
+
} catch {
|
|
5632
|
+
throw new CLIError("--config must be a valid JSON object");
|
|
5633
|
+
}
|
|
5634
|
+
}
|
|
5635
|
+
const task = await api.getTask(hctx.workspace, taskId);
|
|
5636
|
+
const existing = (task.abilities ?? []).map(toAbilityData);
|
|
5637
|
+
const next = {
|
|
5638
|
+
uuid: randomUUID(),
|
|
5639
|
+
abilityType,
|
|
5640
|
+
config,
|
|
5641
|
+
enabled: hctx.args.disabled !== true,
|
|
5642
|
+
...hctx.args.cel ? { celCondition: hctx.args.cel } : {}
|
|
5643
|
+
};
|
|
5644
|
+
const updated = await api.updateTask(hctx.workspace, taskId, {
|
|
5645
|
+
abilities: [...existing, next]
|
|
5646
|
+
});
|
|
5647
|
+
outputData(updated.abilities ?? [], hctx.ctx, ATTACHED_COLUMNS);
|
|
5648
|
+
}
|
|
5649
|
+
},
|
|
5650
|
+
{
|
|
5651
|
+
path: ["abilities", "detach"],
|
|
5652
|
+
description: "Detach an ability from a task by its uuid (from `gr abilities attached`). Re-saves the remaining task-specific abilities so the rest are preserved.",
|
|
5653
|
+
mutates: true,
|
|
5654
|
+
requiresWorkspace: true,
|
|
5655
|
+
args: [
|
|
5656
|
+
{ name: "task", description: "Task ID", required: true },
|
|
5657
|
+
{ name: "uuid", description: "UUID of the attached ability to remove", required: true }
|
|
5658
|
+
],
|
|
5659
|
+
handler: async (hctx) => {
|
|
5660
|
+
const api = requireAuth(hctx.ctx).apiClient;
|
|
5661
|
+
const taskId = requireNumber(hctx.args, "task");
|
|
5662
|
+
const uuid = hctx.args.uuid;
|
|
5663
|
+
if (!uuid) throw new CLIError("--uuid is required");
|
|
5664
|
+
const task = await api.getTask(hctx.workspace, taskId);
|
|
5665
|
+
const existing = task.abilities ?? [];
|
|
5666
|
+
const remaining = existing.filter((a) => a.uuid !== uuid).map(toAbilityData);
|
|
5667
|
+
if (remaining.length === existing.length) {
|
|
5668
|
+
throw new CLIError(`No attached ability with uuid '${uuid}' on task ${taskId}`);
|
|
5669
|
+
}
|
|
5670
|
+
const updated = await api.updateTask(hctx.workspace, taskId, {
|
|
5671
|
+
abilities: remaining
|
|
5672
|
+
});
|
|
5673
|
+
outputData(updated.abilities ?? [], hctx.ctx, ATTACHED_COLUMNS);
|
|
5674
|
+
}
|
|
5675
|
+
}
|
|
5676
|
+
]
|
|
5677
|
+
};
|
|
5678
|
+
|
|
5869
5679
|
// src/domains/access-grants.ts
|
|
5870
5680
|
var COLUMNS14 = [
|
|
5871
5681
|
COL.id(),
|
|
@@ -5938,6 +5748,7 @@ var accessGrantsDomain = {
|
|
|
5938
5748
|
{
|
|
5939
5749
|
path: ["access-grants", "resolve"],
|
|
5940
5750
|
description: "Resolve an access grant",
|
|
5751
|
+
mutates: true,
|
|
5941
5752
|
requiresWorkspace: true,
|
|
5942
5753
|
args: [{ name: "id", description: "Access grant ID", required: true }],
|
|
5943
5754
|
handler: async ({ ctx, workspace, args, data }) => {
|
|
@@ -6386,6 +6197,149 @@ var authManageDomain = {
|
|
|
6386
6197
|
]
|
|
6387
6198
|
};
|
|
6388
6199
|
|
|
6200
|
+
// src/domains/billing.ts
|
|
6201
|
+
function priceSummary(product) {
|
|
6202
|
+
const plans = product.plans ?? {};
|
|
6203
|
+
const abbr = { month: "mo", year: "yr", quarter: "qtr" };
|
|
6204
|
+
const parts = [];
|
|
6205
|
+
for (const [period, plan] of Object.entries(plans)) {
|
|
6206
|
+
const cents = plan?.costPerPeriod ?? 0;
|
|
6207
|
+
if (!plan?.stripePriceId) continue;
|
|
6208
|
+
parts.push(`$${(cents / 100).toFixed(0)}/${abbr[period] ?? period}`);
|
|
6209
|
+
}
|
|
6210
|
+
return parts.join(", ") || "\u2014";
|
|
6211
|
+
}
|
|
6212
|
+
var PRODUCT_COLUMNS2 = [
|
|
6213
|
+
{ header: "ID", getValue: (p) => String(p.id ?? "") },
|
|
6214
|
+
{ header: "NAME", getValue: (p) => p.name ?? "" },
|
|
6215
|
+
{ header: "TYPE", getValue: (p) => String(p.productType ?? "") },
|
|
6216
|
+
{ header: "PRICE", getValue: (p) => priceSummary(p) },
|
|
6217
|
+
{
|
|
6218
|
+
header: "TRIAL",
|
|
6219
|
+
getValue: (p) => {
|
|
6220
|
+
const days = p.trialPeriodDays ?? 0;
|
|
6221
|
+
return days > 0 ? `${days}d` : "\u2014";
|
|
6222
|
+
}
|
|
6223
|
+
}
|
|
6224
|
+
];
|
|
6225
|
+
var SUBSCRIPTION_COLUMNS = [
|
|
6226
|
+
COL.id(),
|
|
6227
|
+
COL.status(),
|
|
6228
|
+
{ header: "PLAN", getValue: (s) => String(s.plan_name ?? s.productName ?? s.plan ?? "") },
|
|
6229
|
+
{ header: "PERIOD", getValue: (s) => String(s.planPeriod ?? s.plan_period ?? "") },
|
|
6230
|
+
{
|
|
6231
|
+
header: "ORG",
|
|
6232
|
+
getValue: (s) => String(s.organizationUuid ?? s.customerOrganizationUuid ?? "")
|
|
6233
|
+
}
|
|
6234
|
+
];
|
|
6235
|
+
function isTable(ctx) {
|
|
6236
|
+
return (ctx.outputFormat ?? "table") === "table";
|
|
6237
|
+
}
|
|
6238
|
+
function hint(ctx, lines) {
|
|
6239
|
+
if (!isTable(ctx)) return;
|
|
6240
|
+
process.stdout.write("\n" + lines.join("\n") + "\n");
|
|
6241
|
+
}
|
|
6242
|
+
function productType(args) {
|
|
6243
|
+
const t = args.type ?? args["product-type"];
|
|
6244
|
+
if (t === "org" || t === "workspace") return t;
|
|
6245
|
+
return void 0;
|
|
6246
|
+
}
|
|
6247
|
+
var billingDomain = {
|
|
6248
|
+
name: "billing",
|
|
6249
|
+
description: "Browse plans, start checkout, and check subscription status",
|
|
6250
|
+
commands: [
|
|
6251
|
+
{
|
|
6252
|
+
path: ["billing", "products"],
|
|
6253
|
+
description: "List self-service plans you can purchase",
|
|
6254
|
+
requiresWorkspace: false,
|
|
6255
|
+
args: [
|
|
6256
|
+
{
|
|
6257
|
+
name: "type",
|
|
6258
|
+
description: "Filter by plan type: 'org' or 'workspace'"
|
|
6259
|
+
}
|
|
6260
|
+
],
|
|
6261
|
+
examples: ["gr billing products --type org"],
|
|
6262
|
+
handler: async ({ ctx, args }) => {
|
|
6263
|
+
const products = await requireAuth(ctx).apiClient.listSelfServeProducts(
|
|
6264
|
+
productType(args)
|
|
6265
|
+
);
|
|
6266
|
+
outputData(products, ctx, PRODUCT_COLUMNS2);
|
|
6267
|
+
if (products.length === 0) {
|
|
6268
|
+
hint(ctx, ["No purchasable plans found. (Free plans are not sold via the CLI.)"]);
|
|
6269
|
+
} else {
|
|
6270
|
+
hint(ctx, [
|
|
6271
|
+
"Next \u2014 buy an organization plan to get started:",
|
|
6272
|
+
" gr billing checkout --product <id> [--period month|year]"
|
|
6273
|
+
]);
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
6276
|
+
},
|
|
6277
|
+
{
|
|
6278
|
+
path: ["billing", "checkout"],
|
|
6279
|
+
description: "Get a Stripe payment link to purchase an organization plan",
|
|
6280
|
+
mutates: true,
|
|
6281
|
+
requiresWorkspace: false,
|
|
6282
|
+
args: [
|
|
6283
|
+
{ name: "product", description: "Product ID of the org plan to buy", required: true },
|
|
6284
|
+
{ name: "period", description: "Billing period: 'month' or 'year'", default: "month" }
|
|
6285
|
+
],
|
|
6286
|
+
examples: ["gr billing checkout --product 12 --period month"],
|
|
6287
|
+
handler: async ({ ctx, args }) => {
|
|
6288
|
+
const productId = Number(requireString(args, "product"));
|
|
6289
|
+
const period = args.period || "month";
|
|
6290
|
+
const result2 = await requireAuth(ctx).apiClient.createCheckoutSession(
|
|
6291
|
+
productId,
|
|
6292
|
+
period
|
|
6293
|
+
);
|
|
6294
|
+
if (isTable(ctx)) {
|
|
6295
|
+
process.stdout.write(
|
|
6296
|
+
`
|
|
6297
|
+
\u{1F4B3} Open this link in a browser to complete payment:
|
|
6298
|
+
|
|
6299
|
+
${result2.checkoutUrl}
|
|
6300
|
+
|
|
6301
|
+
Once payment succeeds, your organization and subscription are created automatically.
|
|
6302
|
+
Then check it landed:
|
|
6303
|
+
gr billing status
|
|
6304
|
+
`
|
|
6305
|
+
);
|
|
6306
|
+
} else {
|
|
6307
|
+
outputSingle(result2, ctx);
|
|
6308
|
+
}
|
|
6309
|
+
}
|
|
6310
|
+
},
|
|
6311
|
+
{
|
|
6312
|
+
path: ["billing", "status"],
|
|
6313
|
+
description: "Show your subscriptions and what to do next",
|
|
6314
|
+
requiresWorkspace: false,
|
|
6315
|
+
examples: ["gr billing status"],
|
|
6316
|
+
handler: async ({ ctx }) => {
|
|
6317
|
+
const subs = await requireAuth(ctx).apiClient.getSubscriptions();
|
|
6318
|
+
outputData(subs, ctx, SUBSCRIPTION_COLUMNS);
|
|
6319
|
+
const active = subs.find(
|
|
6320
|
+
(s) => ["active", "trialing"].includes(String(s.status ?? s.subscriptionStatus ?? ""))
|
|
6321
|
+
);
|
|
6322
|
+
if (!active) {
|
|
6323
|
+
hint(ctx, [
|
|
6324
|
+
"No active subscription yet. To get started:",
|
|
6325
|
+
" gr billing products --type org # pick a plan",
|
|
6326
|
+
" gr billing checkout --product <id> # pay up front"
|
|
6327
|
+
]);
|
|
6328
|
+
return;
|
|
6329
|
+
}
|
|
6330
|
+
const orgUuid = String(
|
|
6331
|
+
active.organizationUuid ?? active.customerOrganizationUuid ?? "<org-uuid>"
|
|
6332
|
+
);
|
|
6333
|
+
hint(ctx, [
|
|
6334
|
+
"You have an active subscription. Add a workspace:",
|
|
6335
|
+
" gr billing products --type workspace # pick a workspace plan",
|
|
6336
|
+
` gr workspaces create --org ${orgUuid} --product <id> --name "My Workspace"`
|
|
6337
|
+
]);
|
|
6338
|
+
}
|
|
6339
|
+
}
|
|
6340
|
+
]
|
|
6341
|
+
};
|
|
6342
|
+
|
|
6389
6343
|
// src/domains/billing-portal.ts
|
|
6390
6344
|
var INVOICE_COLUMNS = [
|
|
6391
6345
|
{ header: "ID", getValue: (i) => String(i.id) },
|
|
@@ -6485,119 +6439,6 @@ var billingPortalDomain = {
|
|
|
6485
6439
|
commands
|
|
6486
6440
|
};
|
|
6487
6441
|
|
|
6488
|
-
// src/lib/concierge-api.ts
|
|
6489
|
-
function baseUrl2(ctx) {
|
|
6490
|
-
return `${ctx.envConfig.apiUrl}/api/v2/concierge`;
|
|
6491
|
-
}
|
|
6492
|
-
function authHeaders(ctx) {
|
|
6493
|
-
return {
|
|
6494
|
-
Authorization: `Bearer ${ctx.accessToken}`,
|
|
6495
|
-
"Content-Type": "application/json"
|
|
6496
|
-
};
|
|
6497
|
-
}
|
|
6498
|
-
function toCamelChat(raw) {
|
|
6499
|
-
return {
|
|
6500
|
-
id: raw.id,
|
|
6501
|
-
uuid: raw.uuid,
|
|
6502
|
-
chatType: raw.chat_type,
|
|
6503
|
-
title: raw.title,
|
|
6504
|
-
threadId: raw.thread_id,
|
|
6505
|
-
createdAt: raw.created_at,
|
|
6506
|
-
updatedAt: raw.updated_at
|
|
6507
|
-
};
|
|
6508
|
-
}
|
|
6509
|
-
function toCamelMessage(raw) {
|
|
6510
|
-
return {
|
|
6511
|
-
id: raw.id,
|
|
6512
|
-
role: raw.role,
|
|
6513
|
-
content: raw.content ?? "",
|
|
6514
|
-
createdAt: raw.createdAt ?? raw.created_at ?? null,
|
|
6515
|
-
toolCalls: raw.toolCalls ?? raw.tool_calls ?? null,
|
|
6516
|
-
toolCallId: raw.toolCallId ?? raw.tool_call_id ?? null
|
|
6517
|
-
};
|
|
6518
|
-
}
|
|
6519
|
-
async function listConciergeChats(ctx) {
|
|
6520
|
-
const res = await fetch(`${baseUrl2(ctx)}/chats`, { headers: authHeaders(ctx) });
|
|
6521
|
-
if (!res.ok) throw new Error(`Failed to list concierge chats: ${res.status}`);
|
|
6522
|
-
const data = await res.json();
|
|
6523
|
-
return data.map(toCamelChat);
|
|
6524
|
-
}
|
|
6525
|
-
async function createConciergeChat(ctx, title) {
|
|
6526
|
-
const res = await fetch(`${baseUrl2(ctx)}/chats`, {
|
|
6527
|
-
method: "POST",
|
|
6528
|
-
headers: authHeaders(ctx),
|
|
6529
|
-
body: JSON.stringify({ title: title ?? null })
|
|
6530
|
-
});
|
|
6531
|
-
if (!res.ok) throw new Error(`Failed to create concierge chat: ${res.status}`);
|
|
6532
|
-
return toCamelChat(await res.json());
|
|
6533
|
-
}
|
|
6534
|
-
async function getConciergeChat(ctx, chatId) {
|
|
6535
|
-
const res = await fetch(`${baseUrl2(ctx)}/chats/${chatId}`, { headers: authHeaders(ctx) });
|
|
6536
|
-
if (!res.ok) throw new Error(`Failed to get concierge chat ${chatId}: ${res.status}`);
|
|
6537
|
-
return toCamelChat(await res.json());
|
|
6538
|
-
}
|
|
6539
|
-
async function originateConciergeCall(ctx, kind = "concierge_setup") {
|
|
6540
|
-
const res = await fetch(`${baseUrl2(ctx)}/calls`, {
|
|
6541
|
-
method: "POST",
|
|
6542
|
-
headers: authHeaders(ctx),
|
|
6543
|
-
body: JSON.stringify({ kind })
|
|
6544
|
-
});
|
|
6545
|
-
if (res.status === 412) {
|
|
6546
|
-
throw new Error("Your account phone is not verified. Run `gr account verify-phone` first.");
|
|
6547
|
-
}
|
|
6548
|
-
if (!res.ok) {
|
|
6549
|
-
const detail = await res.text();
|
|
6550
|
-
throw new Error(`Failed to start concierge call: ${res.status} ${detail}`);
|
|
6551
|
-
}
|
|
6552
|
-
return await res.json();
|
|
6553
|
-
}
|
|
6554
|
-
async function originateConciergeSms(ctx, kind = "concierge_setup") {
|
|
6555
|
-
const res = await fetch(`${baseUrl2(ctx)}/sms`, {
|
|
6556
|
-
method: "POST",
|
|
6557
|
-
headers: authHeaders(ctx),
|
|
6558
|
-
body: JSON.stringify({ kind })
|
|
6559
|
-
});
|
|
6560
|
-
if (res.status === 412) {
|
|
6561
|
-
throw new Error("Your account phone is not verified. Run `gr account verify-phone` first.");
|
|
6562
|
-
}
|
|
6563
|
-
if (!res.ok) {
|
|
6564
|
-
const detail = await res.text();
|
|
6565
|
-
throw new Error(`Failed to start concierge SMS: ${res.status} ${detail}`);
|
|
6566
|
-
}
|
|
6567
|
-
return await res.json();
|
|
6568
|
-
}
|
|
6569
|
-
async function startAccountPhoneVerify(ctx, phone) {
|
|
6570
|
-
const res = await fetch(`${ctx.envConfig.apiUrl}/api/v2/account/phone/verify/start`, {
|
|
6571
|
-
method: "POST",
|
|
6572
|
-
headers: authHeaders(ctx),
|
|
6573
|
-
body: JSON.stringify({ phone })
|
|
6574
|
-
});
|
|
6575
|
-
if (!res.ok) {
|
|
6576
|
-
const detail = await res.text();
|
|
6577
|
-
throw new Error(`Failed to send verification code: ${res.status} ${detail}`);
|
|
6578
|
-
}
|
|
6579
|
-
}
|
|
6580
|
-
async function confirmAccountPhoneVerify(ctx, code) {
|
|
6581
|
-
const res = await fetch(`${ctx.envConfig.apiUrl}/api/v2/account/phone/verify/confirm`, {
|
|
6582
|
-
method: "POST",
|
|
6583
|
-
headers: authHeaders(ctx),
|
|
6584
|
-
body: JSON.stringify({ code })
|
|
6585
|
-
});
|
|
6586
|
-
if (!res.ok) {
|
|
6587
|
-
const detail = await res.text();
|
|
6588
|
-
throw new Error(`Failed to confirm verification code: ${res.status} ${detail}`);
|
|
6589
|
-
}
|
|
6590
|
-
}
|
|
6591
|
-
async function getConciergeChatMessages(ctx, chatId) {
|
|
6592
|
-
const res = await fetch(`${baseUrl2(ctx)}/chats/${chatId}/messages`, {
|
|
6593
|
-
headers: authHeaders(ctx)
|
|
6594
|
-
});
|
|
6595
|
-
if (!res.ok)
|
|
6596
|
-
throw new Error(`Failed to get concierge messages for chat ${chatId}: ${res.status}`);
|
|
6597
|
-
const data = await res.json();
|
|
6598
|
-
return data.map(toCamelMessage);
|
|
6599
|
-
}
|
|
6600
|
-
|
|
6601
6442
|
// src/domains/concierge.ts
|
|
6602
6443
|
var CHAT_COLUMNS = [
|
|
6603
6444
|
COL.id(),
|
|
@@ -6642,7 +6483,7 @@ var conciergeDomain = {
|
|
|
6642
6483
|
|
|
6643
6484
|
// src/domains/custom-toolkits.ts
|
|
6644
6485
|
var COLUMNS17 = [COL.id(), COL.name(), COL.description(), COL.enabled()];
|
|
6645
|
-
var
|
|
6486
|
+
var TOOL_COLUMNS3 = [COL.id(), COL.name(), COL.description()];
|
|
6646
6487
|
var customToolkitsDomain = {
|
|
6647
6488
|
name: "custom-toolkits",
|
|
6648
6489
|
description: "Manage custom toolkits and their tools",
|
|
@@ -6700,7 +6541,7 @@ var customToolkitsDomain = {
|
|
|
6700
6541
|
toolkitId,
|
|
6701
6542
|
data
|
|
6702
6543
|
);
|
|
6703
|
-
outputSingle(result2, ctx,
|
|
6544
|
+
outputSingle(result2, ctx, TOOL_COLUMNS3);
|
|
6704
6545
|
}
|
|
6705
6546
|
},
|
|
6706
6547
|
{
|
|
@@ -6722,7 +6563,7 @@ var customToolkitsDomain = {
|
|
|
6722
6563
|
toolId,
|
|
6723
6564
|
data
|
|
6724
6565
|
);
|
|
6725
|
-
outputSingle(result2, ctx,
|
|
6566
|
+
outputSingle(result2, ctx, TOOL_COLUMNS3);
|
|
6726
6567
|
}
|
|
6727
6568
|
},
|
|
6728
6569
|
{
|
|
@@ -7935,6 +7776,50 @@ function parseCallerRole(value) {
|
|
|
7935
7776
|
}
|
|
7936
7777
|
return VALID_CALLER_ROLES.has(value) ? value : void 0;
|
|
7937
7778
|
}
|
|
7779
|
+
var PERMISSION_USE = 1;
|
|
7780
|
+
function evaluateWorkflowMemberRoleAccess(memberRoleId, memberRoleAccess) {
|
|
7781
|
+
if (memberRoleAccess === void 0) {
|
|
7782
|
+
return { outcome: "skip", detail: "workflow memberRoleAccess not available" };
|
|
7783
|
+
}
|
|
7784
|
+
if (memberRoleAccess.length === 0) {
|
|
7785
|
+
return { outcome: "allow", detail: "no workflow role restrictions configured" };
|
|
7786
|
+
}
|
|
7787
|
+
if (memberRoleId === void 0) {
|
|
7788
|
+
return {
|
|
7789
|
+
outcome: "skip",
|
|
7790
|
+
detail: "caller member role unknown \u2014 cannot evaluate role gate"
|
|
7791
|
+
};
|
|
7792
|
+
}
|
|
7793
|
+
const hasUse = memberRoleAccess.some(
|
|
7794
|
+
(entry) => entry.memberRoleId === memberRoleId && (entry.accessMode & PERMISSION_USE) === PERMISSION_USE
|
|
7795
|
+
);
|
|
7796
|
+
if (hasUse) {
|
|
7797
|
+
return { outcome: "allow", detail: `memberRoleId=${memberRoleId} has USE on workflow` };
|
|
7798
|
+
}
|
|
7799
|
+
return {
|
|
7800
|
+
outcome: "deny",
|
|
7801
|
+
detail: `memberRoleId=${memberRoleId} lacks USE in workflow memberRoleAccess`
|
|
7802
|
+
};
|
|
7803
|
+
}
|
|
7804
|
+
function parseWorkflowMemberRoleAccess(workflow) {
|
|
7805
|
+
const raw = workflow.memberRoleAccess;
|
|
7806
|
+
if (!Array.isArray(raw)) {
|
|
7807
|
+
return [];
|
|
7808
|
+
}
|
|
7809
|
+
const entries = [];
|
|
7810
|
+
for (const item of raw) {
|
|
7811
|
+
if (!item || typeof item !== "object") {
|
|
7812
|
+
continue;
|
|
7813
|
+
}
|
|
7814
|
+
const row = item;
|
|
7815
|
+
const memberRoleId = typeof row.memberRoleId === "number" ? row.memberRoleId : Number(row.memberRoleId);
|
|
7816
|
+
const accessMode = typeof row.accessMode === "number" ? row.accessMode : Number(row.accessMode);
|
|
7817
|
+
if (Number.isFinite(memberRoleId) && Number.isFinite(accessMode)) {
|
|
7818
|
+
entries.push({ memberRoleId, accessMode });
|
|
7819
|
+
}
|
|
7820
|
+
}
|
|
7821
|
+
return entries;
|
|
7822
|
+
}
|
|
7938
7823
|
function inferCallerRole(phone, opts) {
|
|
7939
7824
|
if (opts.asRole) {
|
|
7940
7825
|
return opts.asRole;
|
|
@@ -8061,6 +7946,32 @@ function simulateInboundRouting(phone, opts = {}) {
|
|
|
8061
7946
|
return finish("no_inbound_workflow", false, true, steps, recommendations, callerRole);
|
|
8062
7947
|
}
|
|
8063
7948
|
steps.push({ step: "inbound_workflow", outcome: "ok", detail: `workflowId=${workflowId}` });
|
|
7949
|
+
const callerMemberRoleId = callerRole === "member" ? opts.matchedMemberRoleId : callerRole === "anonymous" ? phone.anonymousMemberRoleId ?? void 0 : void 0;
|
|
7950
|
+
const roleAccess = evaluateWorkflowMemberRoleAccess(
|
|
7951
|
+
callerMemberRoleId,
|
|
7952
|
+
opts.workflowMemberRoleAccess
|
|
7953
|
+
);
|
|
7954
|
+
if (roleAccess.outcome === "deny") {
|
|
7955
|
+
steps.push({
|
|
7956
|
+
step: "workflow_member_role_access",
|
|
7957
|
+
outcome: "reject",
|
|
7958
|
+
detail: roleAccess.detail
|
|
7959
|
+
});
|
|
7960
|
+
recommendations.push(
|
|
7961
|
+
"Grant the caller's member role USE access on the inbound workflow (workflow memberRoleAccess or `gr member-roles update` workflowAccess)"
|
|
7962
|
+
);
|
|
7963
|
+
return finish("workflow_member_role_denied", false, true, steps, recommendations, callerRole);
|
|
7964
|
+
}
|
|
7965
|
+
steps.push({
|
|
7966
|
+
step: "workflow_member_role_access",
|
|
7967
|
+
outcome: roleAccess.outcome === "skip" ? "skipped" : "ok",
|
|
7968
|
+
detail: roleAccess.detail
|
|
7969
|
+
});
|
|
7970
|
+
if (roleAccess.outcome === "skip" && callerMemberRoleId === void 0 && opts.workflowMemberRoleAccess !== void 0 && opts.workflowMemberRoleAccess.length > 0) {
|
|
7971
|
+
recommendations.push(
|
|
7972
|
+
"Workflow has member role restrictions but caller memberRoleId was not resolved \u2014 re-run with --as +E.164 (caller phone) so the role gate can be evaluated against real data"
|
|
7973
|
+
);
|
|
7974
|
+
}
|
|
8064
7975
|
steps.push({ step: "chat_create", outcome: "create_phone_voice_chat" });
|
|
8065
7976
|
steps.push({ step: "assistant", outcome: "connect_media_stream" });
|
|
8066
7977
|
return finish("ai_voice_chat", true, true, steps, recommendations, callerRole);
|
|
@@ -8077,6 +7988,7 @@ function finish(code, createsChat, createsPhoneCall, steps, recommendations, cal
|
|
|
8077
7988
|
voicemail_record: "Record voicemail",
|
|
8078
7989
|
play_message_hangup: "Play off-hours message, hang up",
|
|
8079
7990
|
no_inbound_workflow: "Play unavailable message (no inbound workflow)",
|
|
7991
|
+
workflow_member_role_denied: "Reject \u2014 caller member role lacks workflow USE (memberRoleAccess)",
|
|
8080
7992
|
ai_voice_chat: "Create chat + connect AI voice"
|
|
8081
7993
|
};
|
|
8082
7994
|
return {
|
|
@@ -8128,9 +8040,22 @@ function formatPhoneNumberBehaviorMatrix(phone, outboundUsers, warnings = collec
|
|
|
8128
8040
|
lines.push(`UUID: ${phone.uuid} | Mapped: ${phone.isMapped ? "yes" : "no"}`);
|
|
8129
8041
|
lines.push("");
|
|
8130
8042
|
lines.push("## Channels");
|
|
8131
|
-
lines.push(
|
|
8043
|
+
lines.push(
|
|
8044
|
+
` Voice: ${phone.enableVoice ? "on" : "off"} SMS: ${phone.enableSms ? "on" : "off"} WhatsApp: ${phone.enableWhatsApp ? "on" : "off"}`
|
|
8045
|
+
);
|
|
8132
8046
|
lines.push(` Assistant: ${phone.assistantEnabled ? "on" : "off"}`);
|
|
8133
8047
|
lines.push("");
|
|
8048
|
+
if (phone.enableVoice) {
|
|
8049
|
+
lines.push("## Channel defaults (auto-injected on voice)");
|
|
8050
|
+
lines.push(
|
|
8051
|
+
" Voice channels auto-add the hang_up_call tool (system:voice_channel);"
|
|
8052
|
+
);
|
|
8053
|
+
lines.push(
|
|
8054
|
+
" it is not listed by `gr agents abilities list`. See `gr abilities get"
|
|
8055
|
+
);
|
|
8056
|
+
lines.push(" --name communication:phone_call_tools` for the voice ability spec.");
|
|
8057
|
+
lines.push("");
|
|
8058
|
+
}
|
|
8134
8059
|
lines.push("## Inbound route");
|
|
8135
8060
|
lines.push(
|
|
8136
8061
|
` defaultWorkflowId (inbound): ${phone.defaultWorkflowId ?? "null"} restartDelay: ${phone.restartDelay ?? "null"}s`
|
|
@@ -8211,9 +8136,7 @@ async function findOutboundPhoneUsers(client, workspace, phone) {
|
|
|
8211
8136
|
}
|
|
8212
8137
|
page += 1;
|
|
8213
8138
|
if (page > 50) {
|
|
8214
|
-
process.stderr.write(
|
|
8215
|
-
"Warning: event-rules lookup truncated at 5000 results (50 pages)\n"
|
|
8216
|
-
);
|
|
8139
|
+
process.stderr.write("Warning: event-rules lookup truncated at 5000 results (50 pages)\n");
|
|
8217
8140
|
break;
|
|
8218
8141
|
}
|
|
8219
8142
|
}
|
|
@@ -10488,7 +10411,9 @@ function normalizeCallerE164(phone) {
|
|
|
10488
10411
|
var MEMBER_SEARCH_PAGE_SIZE = 100;
|
|
10489
10412
|
async function resolveMemberByCallerPhone(client, workspace, callerE164) {
|
|
10490
10413
|
const normalized = normalizeCallerE164(callerE164);
|
|
10491
|
-
const
|
|
10414
|
+
const digits = callerE164.replace(/\D/g, "");
|
|
10415
|
+
const nsn = digits.length === 11 && digits.startsWith("1") ? digits.slice(1) : digits;
|
|
10416
|
+
const candidates = [normalized, callerE164.trim(), digits, nsn].filter(
|
|
10492
10417
|
(v, i, arr) => Boolean(v) && arr.indexOf(v) === i
|
|
10493
10418
|
);
|
|
10494
10419
|
const matchInRows = (rows) => {
|
|
@@ -10533,6 +10458,25 @@ async function resolveMemberByCallerPhone(client, workspace, callerE164) {
|
|
|
10533
10458
|
return void 0;
|
|
10534
10459
|
}
|
|
10535
10460
|
|
|
10461
|
+
// src/lib/phone-numbers/workflow-role-access.ts
|
|
10462
|
+
async function loadInboundWorkflowMemberRoleAccess(client, workspace, defaultWorkflowId) {
|
|
10463
|
+
const workflowId = typeof defaultWorkflowId === "number" ? defaultWorkflowId : defaultWorkflowId === null || defaultWorkflowId === void 0 ? void 0 : Number(defaultWorkflowId);
|
|
10464
|
+
if (workflowId === void 0 || !Number.isFinite(workflowId)) {
|
|
10465
|
+
return void 0;
|
|
10466
|
+
}
|
|
10467
|
+
try {
|
|
10468
|
+
const workflow = await client.getWorkflow(workspace, workflowId);
|
|
10469
|
+
return parseWorkflowMemberRoleAccess(workflow);
|
|
10470
|
+
} catch (err) {
|
|
10471
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
10472
|
+
process.stderr.write(
|
|
10473
|
+
`[diagnose] Could not fetch workflow ${workflowId} memberRoleAccess \u2014 role gate will be skipped (${reason})
|
|
10474
|
+
`
|
|
10475
|
+
);
|
|
10476
|
+
return void 0;
|
|
10477
|
+
}
|
|
10478
|
+
}
|
|
10479
|
+
|
|
10536
10480
|
// src/domains/phone-numbers.ts
|
|
10537
10481
|
function asPhoneRecord(value) {
|
|
10538
10482
|
return value;
|
|
@@ -10591,9 +10535,8 @@ var phoneNumbersDomain = {
|
|
|
10591
10535
|
const client = requireAuth(ctx).apiClient;
|
|
10592
10536
|
const id = requireNumber(args, "id");
|
|
10593
10537
|
const phone = asPhoneRecord(await client.getPhoneNumber(workspace, id));
|
|
10594
|
-
if (args.raw) {
|
|
10595
|
-
|
|
10596
|
-
`);
|
|
10538
|
+
if (args.raw || ctx.outputFormat !== "table") {
|
|
10539
|
+
outputSingle(phone, ctx);
|
|
10597
10540
|
return;
|
|
10598
10541
|
}
|
|
10599
10542
|
const outboundUsers = await findOutboundPhoneUsers(client, workspace, phone);
|
|
@@ -10767,21 +10710,34 @@ Setup complete: id=${phone.id} uuid=${phone.uuid}
|
|
|
10767
10710
|
const asRole = parseCallerRole(args["as-role"]);
|
|
10768
10711
|
let hasMemberMatch = asRole === "member";
|
|
10769
10712
|
let matchedMemberId;
|
|
10713
|
+
let matchedMemberRoleId;
|
|
10770
10714
|
let matchedMemberPhoneType;
|
|
10771
10715
|
if (!asRole && typeof args.as === "string") {
|
|
10772
10716
|
const match = await resolveMemberByCallerPhone(client, workspace, args.as);
|
|
10773
10717
|
if (match) {
|
|
10774
10718
|
hasMemberMatch = true;
|
|
10775
10719
|
matchedMemberId = match.memberId;
|
|
10720
|
+
matchedMemberRoleId = match.memberRoleId;
|
|
10776
10721
|
matchedMemberPhoneType = match.phoneType;
|
|
10777
10722
|
}
|
|
10778
10723
|
}
|
|
10724
|
+
const workflowMemberRoleAccess = await loadInboundWorkflowMemberRoleAccess(
|
|
10725
|
+
client,
|
|
10726
|
+
workspace,
|
|
10727
|
+
phone.defaultWorkflowId
|
|
10728
|
+
);
|
|
10779
10729
|
const sim = simulateInboundRouting(phone, {
|
|
10780
10730
|
asRole,
|
|
10781
10731
|
hasMemberMatch,
|
|
10782
10732
|
matchedMemberId,
|
|
10783
|
-
|
|
10733
|
+
matchedMemberRoleId,
|
|
10734
|
+
matchedMemberPhoneType,
|
|
10735
|
+
workflowMemberRoleAccess
|
|
10784
10736
|
});
|
|
10737
|
+
if (ctx.outputFormat !== "table") {
|
|
10738
|
+
outputSingle({ phoneNumberId: id, simulation: sim }, ctx);
|
|
10739
|
+
return;
|
|
10740
|
+
}
|
|
10785
10741
|
process.stdout.write(`${formatRoutingSimulation(sim, phone)}
|
|
10786
10742
|
`);
|
|
10787
10743
|
}
|
|
@@ -10803,10 +10759,28 @@ Setup complete: id=${phone.id} uuid=${phone.uuid}
|
|
|
10803
10759
|
const id = requireNumber(args, "id");
|
|
10804
10760
|
const role = requireCallerRole(args, "as-role");
|
|
10805
10761
|
const phone = asPhoneRecord(await client.getPhoneNumber(workspace, id));
|
|
10762
|
+
const workflowMemberRoleAccess = await loadInboundWorkflowMemberRoleAccess(
|
|
10763
|
+
client,
|
|
10764
|
+
workspace,
|
|
10765
|
+
phone.defaultWorkflowId
|
|
10766
|
+
);
|
|
10806
10767
|
const sim = simulateInboundRouting(phone, {
|
|
10807
10768
|
asRole: role,
|
|
10808
|
-
hasMemberMatch: role === "member"
|
|
10769
|
+
hasMemberMatch: role === "member",
|
|
10770
|
+
workflowMemberRoleAccess
|
|
10809
10771
|
});
|
|
10772
|
+
if (ctx.outputFormat !== "table") {
|
|
10773
|
+
outputSingle(
|
|
10774
|
+
{
|
|
10775
|
+
phoneNumberId: id,
|
|
10776
|
+
callerRole: role,
|
|
10777
|
+
finalBehavior: sim.finalBehavior,
|
|
10778
|
+
simulation: sim
|
|
10779
|
+
},
|
|
10780
|
+
ctx
|
|
10781
|
+
);
|
|
10782
|
+
return;
|
|
10783
|
+
}
|
|
10810
10784
|
process.stdout.write(
|
|
10811
10785
|
`If a caller in role "${role}" dials this line right now:
|
|
10812
10786
|
\u2192 ${sim.finalBehavior}
|
|
@@ -10832,17 +10806,26 @@ ${formatRoutingSimulation(sim, phone)}
|
|
|
10832
10806
|
const asRole = parseCallerRole(args["as-role"]);
|
|
10833
10807
|
let hasMemberMatch = asRole === "member";
|
|
10834
10808
|
let matchedMemberId;
|
|
10809
|
+
let matchedMemberRoleId;
|
|
10835
10810
|
if (!asRole && typeof args.as === "string") {
|
|
10836
10811
|
const match = await resolveMemberByCallerPhone(client, workspace, args.as);
|
|
10837
10812
|
if (match) {
|
|
10838
10813
|
hasMemberMatch = true;
|
|
10839
10814
|
matchedMemberId = match.memberId;
|
|
10815
|
+
matchedMemberRoleId = match.memberRoleId;
|
|
10840
10816
|
}
|
|
10841
10817
|
}
|
|
10818
|
+
const workflowMemberRoleAccess = await loadInboundWorkflowMemberRoleAccess(
|
|
10819
|
+
client,
|
|
10820
|
+
workspace,
|
|
10821
|
+
phone.defaultWorkflowId
|
|
10822
|
+
);
|
|
10842
10823
|
const sim = simulateInboundRouting(phone, {
|
|
10843
10824
|
asRole,
|
|
10844
10825
|
hasMemberMatch,
|
|
10845
|
-
matchedMemberId
|
|
10826
|
+
matchedMemberId,
|
|
10827
|
+
matchedMemberRoleId,
|
|
10828
|
+
workflowMemberRoleAccess
|
|
10846
10829
|
});
|
|
10847
10830
|
process.stdout.write(
|
|
10848
10831
|
`${JSON.stringify({ phoneNumberId: id, simulation: sim }, null, 2)}
|
|
@@ -10886,11 +10869,15 @@ ${formatRoutingSimulation(sim, phone)}
|
|
|
10886
10869
|
{ inbound: true }
|
|
10887
10870
|
);
|
|
10888
10871
|
const inbound = result2.items ?? [];
|
|
10889
|
-
|
|
10872
|
+
if (ctx.outputFormat === "table") {
|
|
10873
|
+
process.stdout.write(`Inbound calls (most recent page): ${inbound.length} shown
|
|
10890
10874
|
`);
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
10875
|
+
process.stdout.write(
|
|
10876
|
+
"Tip: gr phone-numbers diagnose --id <id> --as +1\u2026 simulates routing without a live call.\n"
|
|
10877
|
+
);
|
|
10878
|
+
} else {
|
|
10879
|
+
stderr(`Inbound calls (most recent page): ${inbound.length} shown`);
|
|
10880
|
+
}
|
|
10894
10881
|
outputData(inbound, ctx);
|
|
10895
10882
|
}
|
|
10896
10883
|
},
|
|
@@ -11264,6 +11251,115 @@ var reportsDomain = {
|
|
|
11264
11251
|
]
|
|
11265
11252
|
};
|
|
11266
11253
|
|
|
11254
|
+
// src/lib/routines/field-help.ts
|
|
11255
|
+
var ROUTINE_FIELD_REFERENCE = `
|
|
11256
|
+
Field reference for --data JSON (create/update):
|
|
11257
|
+
|
|
11258
|
+
Core
|
|
11259
|
+
name (string) Display name
|
|
11260
|
+
workflowId (number) Workflow run when the routine fires
|
|
11261
|
+
targetMemberId (number) Member who receives the scheduled outreach
|
|
11262
|
+
|
|
11263
|
+
Schedule
|
|
11264
|
+
scheduleEnabled (bool) Master on/off for the cron trigger
|
|
11265
|
+
scheduleMode (string) "CRON" (only mode today)
|
|
11266
|
+
cronString (string) Standard cron, e.g. "0 7 * * *"
|
|
11267
|
+
timezone (string, optional) IANA name, e.g. "America/Los_Angeles"
|
|
11268
|
+
Defaults to workspace timezone, else UTC
|
|
11269
|
+
|
|
11270
|
+
Visibility / lifecycle
|
|
11271
|
+
archived (bool) Archive flag on update
|
|
11272
|
+
event rules Use gr events rules create \u2014 not inline here
|
|
11273
|
+
`;
|
|
11274
|
+
|
|
11275
|
+
// src/domains/routines.ts
|
|
11276
|
+
var ROUTINE_COLUMNS = [
|
|
11277
|
+
COL.id(),
|
|
11278
|
+
COL.name(),
|
|
11279
|
+
{ header: "MODE", getValue: (item) => item.scheduleMode ?? "" },
|
|
11280
|
+
{ header: "CRON", getValue: (item) => item.cronString ?? "" },
|
|
11281
|
+
{ header: "TZ", getValue: (item) => item.timezone ?? "UTC" },
|
|
11282
|
+
{ header: "ENABLED", getValue: (item) => String(item.scheduleEnabled ?? "") },
|
|
11283
|
+
{ header: "NEXT RUN", getValue: (item) => item.nextRun ?? "" }
|
|
11284
|
+
];
|
|
11285
|
+
var routinesDomain = {
|
|
11286
|
+
name: "routines",
|
|
11287
|
+
description: "Schedule recurring outbound (e.g. a daily SMS/voice check-in via cron)",
|
|
11288
|
+
commands: [
|
|
11289
|
+
...crudCommands({
|
|
11290
|
+
prefix: ["routines"],
|
|
11291
|
+
entityName: "routine",
|
|
11292
|
+
listMethod: "getRoutines",
|
|
11293
|
+
getMethod: "getRoutine",
|
|
11294
|
+
createMethod: "createRoutine",
|
|
11295
|
+
updateMethod: "updateRoutine",
|
|
11296
|
+
deleteMethod: "deleteRoutine",
|
|
11297
|
+
paginationSupport: "full",
|
|
11298
|
+
columns: ROUTINE_COLUMNS,
|
|
11299
|
+
createHelp: `Create a routine
|
|
11300
|
+
|
|
11301
|
+
${ROUTINE_FIELD_REFERENCE}`,
|
|
11302
|
+
updateHelp: `Update a routine
|
|
11303
|
+
|
|
11304
|
+
${ROUTINE_FIELD_REFERENCE}`,
|
|
11305
|
+
createExamples: [
|
|
11306
|
+
"Daily 7am check-in in the member\u2019s timezone (cron is interpreted in `timezone`):",
|
|
11307
|
+
`gr routines create -w $WID --allow-write --data '{"name":"AM check-in",`,
|
|
11308
|
+
' "scheduleEnabled":true,"scheduleMode":"CRON","cronString":"0 7 * * *",',
|
|
11309
|
+
` "timezone":"America/Los_Angeles","targetMemberId":1,"workflowId":2}'`,
|
|
11310
|
+
" (timezone is an IANA name; omit it to default to the workspace timezone, else UTC.",
|
|
11311
|
+
' Without it, "0 7 * * *" fires at 07:00 UTC, not 7am local.)',
|
|
11312
|
+
"",
|
|
11313
|
+
"To actually send via SMS/voice, pair the routine with an event rule:",
|
|
11314
|
+
' gr events rules create \u2014 objectType "routine", eventType "routine:triggered",',
|
|
11315
|
+
' actionType "chat:create". See `gr events --help`.'
|
|
11316
|
+
]
|
|
11317
|
+
}),
|
|
11318
|
+
{
|
|
11319
|
+
path: ["routines", "run"],
|
|
11320
|
+
description: "Run a routine now",
|
|
11321
|
+
mutates: true,
|
|
11322
|
+
requiresWorkspace: true,
|
|
11323
|
+
args: [{ name: "id", description: "Routine ID", required: true }],
|
|
11324
|
+
handler: async ({ ctx, workspace, args, data }) => {
|
|
11325
|
+
const result2 = await requireAuth(ctx).apiClient.runRoutine(
|
|
11326
|
+
workspace,
|
|
11327
|
+
requireNumber(args, "id"),
|
|
11328
|
+
data ?? {}
|
|
11329
|
+
);
|
|
11330
|
+
outputSingle(result2, ctx);
|
|
11331
|
+
}
|
|
11332
|
+
},
|
|
11333
|
+
{
|
|
11334
|
+
path: ["routines", "runs", "list"],
|
|
11335
|
+
description: "List runs for a routine",
|
|
11336
|
+
requiresWorkspace: true,
|
|
11337
|
+
args: [
|
|
11338
|
+
{
|
|
11339
|
+
name: "id",
|
|
11340
|
+
description: "Routine ID (aligns with routines get/run/update/delete)"
|
|
11341
|
+
},
|
|
11342
|
+
{ name: "status", description: "Filter by run status" },
|
|
11343
|
+
{ name: "page", description: "Page number (1-indexed)" },
|
|
11344
|
+
{ name: "page-size", description: "Page size (max 100)" }
|
|
11345
|
+
],
|
|
11346
|
+
handler: async ({ ctx, workspace, args }) => {
|
|
11347
|
+
const routineId = args.id ? requireNumber(args, "id") : void 0;
|
|
11348
|
+
const page = args.page ? requireNumber(args, "page") : void 0;
|
|
11349
|
+
const pageSize = args["page-size"] ? requireNumber(args, "page-size") : void 0;
|
|
11350
|
+
const status = args.status ?? void 0;
|
|
11351
|
+
const result2 = await requireAuth(ctx).apiClient.getRoutineRuns(workspace, {
|
|
11352
|
+
...routineId !== void 0 ? { routineId } : {},
|
|
11353
|
+
...page !== void 0 ? { page } : {},
|
|
11354
|
+
...pageSize !== void 0 ? { pageSize } : {},
|
|
11355
|
+
...status ? { status } : {}
|
|
11356
|
+
});
|
|
11357
|
+
outputData(result2, ctx);
|
|
11358
|
+
}
|
|
11359
|
+
}
|
|
11360
|
+
]
|
|
11361
|
+
};
|
|
11362
|
+
|
|
11267
11363
|
// src/domains/subscriptions.ts
|
|
11268
11364
|
var COLUMNS27 = [
|
|
11269
11365
|
COL.id(),
|
|
@@ -13126,11 +13222,13 @@ function registerPublicDomains(registry) {
|
|
|
13126
13222
|
registry.register(sitesDomain);
|
|
13127
13223
|
registry.register(filesDomain);
|
|
13128
13224
|
registry.register(eventsDomain);
|
|
13225
|
+
registry.register(routinesDomain);
|
|
13129
13226
|
registry.register(experimentsDomain);
|
|
13130
13227
|
registry.register(calendarsDomain);
|
|
13131
13228
|
registry.register(qualificationsDomain);
|
|
13132
13229
|
registry.register(personasDomain);
|
|
13133
13230
|
registry.register(journeysDomain);
|
|
13231
|
+
registry.register(abilitiesDomain);
|
|
13134
13232
|
registry.register(phoneNumbersDomain);
|
|
13135
13233
|
registry.register(inboxesDomain);
|
|
13136
13234
|
registry.register(operatorGroupsDomain);
|
|
@@ -13143,6 +13241,7 @@ function registerPublicDomains(registry) {
|
|
|
13143
13241
|
registry.register(mcpServersDomain);
|
|
13144
13242
|
registry.register(apiKeysDomain);
|
|
13145
13243
|
registry.register(subscriptionsDomain);
|
|
13244
|
+
registry.register(billingDomain);
|
|
13146
13245
|
registry.register(supportDomain);
|
|
13147
13246
|
registry.register(reportsDomain);
|
|
13148
13247
|
registry.register(aiModelsDomain);
|
|
@@ -13169,7 +13268,7 @@ function registerPublicDomains(registry) {
|
|
|
13169
13268
|
}
|
|
13170
13269
|
|
|
13171
13270
|
// src/commands/auth-browser.ts
|
|
13172
|
-
import { execFile
|
|
13271
|
+
import { execFile } from "child_process";
|
|
13173
13272
|
async function authBrowserCommand(ctx, args) {
|
|
13174
13273
|
const noOpen = Boolean(args["no-open"]);
|
|
13175
13274
|
const account = await ctx.apiClient.getUserAccount();
|
|
@@ -13205,7 +13304,7 @@ function openUrl(url) {
|
|
|
13205
13304
|
command = "xdg-open";
|
|
13206
13305
|
args = [url];
|
|
13207
13306
|
}
|
|
13208
|
-
|
|
13307
|
+
execFile(command, args, (err) => {
|
|
13209
13308
|
if (err) {
|
|
13210
13309
|
process.stderr.write("Warning: Could not open browser automatically\n");
|
|
13211
13310
|
}
|
|
@@ -13418,7 +13517,7 @@ async function resolveDirectoryTrust(opts) {
|
|
|
13418
13517
|
return "trusted";
|
|
13419
13518
|
}
|
|
13420
13519
|
async function requireDirectoryTrust(opts) {
|
|
13421
|
-
const { CLIError: CLIError2 } = await import("./errors-
|
|
13520
|
+
const { CLIError: CLIError2 } = await import("./errors-VK4QO3QZ.js");
|
|
13422
13521
|
const dir = path3.resolve(opts.trustDir ?? opts.cwd ?? process.cwd());
|
|
13423
13522
|
const status = await resolveDirectoryTrust(opts);
|
|
13424
13523
|
if (status === "trusted") return;
|
|
@@ -13541,27 +13640,42 @@ async function runConciergeAguiLoop(client, initialMessages, onEvent, opts) {
|
|
|
13541
13640
|
if (!hitlPayload) {
|
|
13542
13641
|
break;
|
|
13543
13642
|
}
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13643
|
+
opts.onHitlPromptStart?.();
|
|
13644
|
+
let next;
|
|
13645
|
+
try {
|
|
13646
|
+
next = await handleCliHitlInterrupt(hitlPayload, {
|
|
13647
|
+
allowWrite: opts.allowWrite,
|
|
13648
|
+
autoApprove: opts.autoApprove,
|
|
13649
|
+
trustCwd: opts.trustCwd,
|
|
13650
|
+
trustDir: opts.trustDir,
|
|
13651
|
+
nonInteractive: opts.nonInteractive
|
|
13652
|
+
});
|
|
13653
|
+
} finally {
|
|
13654
|
+
opts.onHitlPromptEnd?.();
|
|
13655
|
+
}
|
|
13551
13656
|
if (!next) {
|
|
13552
13657
|
process.stderr.write(
|
|
13553
13658
|
" [HITL] Unsupported interrupt in CLI \u2014 open app.gravityrail.com/setup to continue.\n"
|
|
13554
13659
|
);
|
|
13555
|
-
|
|
13660
|
+
return {
|
|
13661
|
+
streamText,
|
|
13662
|
+
hasToolCalls,
|
|
13663
|
+
unsupportedHitl: normalizeUnsupportedHitl(hitlPayload)
|
|
13664
|
+
};
|
|
13556
13665
|
}
|
|
13557
13666
|
resume = next;
|
|
13558
13667
|
streamText = "";
|
|
13559
13668
|
}
|
|
13560
13669
|
return { streamText, hasToolCalls };
|
|
13561
13670
|
}
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13671
|
+
function normalizeUnsupportedHitl(raw) {
|
|
13672
|
+
const payload = raw && typeof raw === "object" ? raw : {};
|
|
13673
|
+
return {
|
|
13674
|
+
type: typeof payload.type === "string" ? payload.type : void 0,
|
|
13675
|
+
interruptId: typeof payload.interrupt_id === "string" ? payload.interrupt_id : void 0,
|
|
13676
|
+
payload
|
|
13677
|
+
};
|
|
13678
|
+
}
|
|
13565
13679
|
|
|
13566
13680
|
// src/lib/realtime-client.ts
|
|
13567
13681
|
import { EventEmitter } from "events";
|
|
@@ -13822,6 +13936,19 @@ function isToolCall(item) {
|
|
|
13822
13936
|
function isToolResult(item) {
|
|
13823
13937
|
return "kind" in item && item.kind === "tool_result";
|
|
13824
13938
|
}
|
|
13939
|
+
function toolResultLooksFailed(content) {
|
|
13940
|
+
const trimmed = content.trim();
|
|
13941
|
+
if (!trimmed) return false;
|
|
13942
|
+
if (/^error[:\s]/i.test(trimmed)) return true;
|
|
13943
|
+
try {
|
|
13944
|
+
const parsed = JSON.parse(trimmed);
|
|
13945
|
+
if (parsed.ok === false) return true;
|
|
13946
|
+
if (typeof parsed.error === "string" && parsed.error.length > 0) return true;
|
|
13947
|
+
if (typeof parsed.status === "string" && parsed.status.toLowerCase() === "error") return true;
|
|
13948
|
+
} catch {
|
|
13949
|
+
}
|
|
13950
|
+
return false;
|
|
13951
|
+
}
|
|
13825
13952
|
function getTruncationLabel(reason, truncatedAtMs) {
|
|
13826
13953
|
const timeStr = truncatedAtMs ? ` at ${(truncatedAtMs / 1e3).toFixed(1)}s` : "";
|
|
13827
13954
|
switch (reason) {
|
|
@@ -13855,6 +13982,8 @@ var CLEAR_EOL = `${CSI}K`;
|
|
|
13855
13982
|
var RESET = `${CSI}0m`;
|
|
13856
13983
|
var MOUSE_ON = `${CSI}?1000h${CSI}?1006h`;
|
|
13857
13984
|
var MOUSE_OFF = `${CSI}?1000l${CSI}?1006l`;
|
|
13985
|
+
var BRACKETED_PASTE_ON = `${CSI}?2004h`;
|
|
13986
|
+
var BRACKETED_PASTE_OFF = `${CSI}?2004l`;
|
|
13858
13987
|
var BLUE = `${CSI}34m`;
|
|
13859
13988
|
var GREEN = `${CSI}32m`;
|
|
13860
13989
|
var GRAY = `${CSI}90m`;
|
|
@@ -13863,6 +13992,11 @@ var ORANGE = `${CSI}38;5;208m`;
|
|
|
13863
13992
|
var LIGHT_BLUE = `${CSI}38;5;117m`;
|
|
13864
13993
|
var DIM = `${CSI}2m`;
|
|
13865
13994
|
var DIM_ITALIC_GRAY = `${CSI}2;3;90m`;
|
|
13995
|
+
var RED = `${CSI}31m`;
|
|
13996
|
+
var CYAN = `${CSI}36m`;
|
|
13997
|
+
var MAX_INPUT_ROWS = 6;
|
|
13998
|
+
var INPUT_LABEL = "You: ";
|
|
13999
|
+
var INPUT_LABEL_WIDTH = 5;
|
|
13866
14000
|
function moveTo(row, col) {
|
|
13867
14001
|
return `${CSI}${row};${col}H`;
|
|
13868
14002
|
}
|
|
@@ -13887,6 +14021,11 @@ var ChatTUI = class {
|
|
|
13887
14021
|
// Escape sequence accumulator for raw stdin parsing
|
|
13888
14022
|
escBuf = "";
|
|
13889
14023
|
escTimer = null;
|
|
14024
|
+
// Run / inference status (title bar)
|
|
14025
|
+
runState = "idle";
|
|
14026
|
+
activeToolName = "";
|
|
14027
|
+
pausedForPrompt = false;
|
|
14028
|
+
turnInProgress = false;
|
|
13890
14029
|
constructor(options) {
|
|
13891
14030
|
this.options = options;
|
|
13892
14031
|
this.debugPanelVisible = !!options.debug;
|
|
@@ -13898,8 +14037,8 @@ var ChatTUI = class {
|
|
|
13898
14037
|
// ─── Lifecycle ──────────────────────────────────────────────────────
|
|
13899
14038
|
start() {
|
|
13900
14039
|
this.updateDimensions();
|
|
13901
|
-
process.stdout.write(ALT_SCREEN_ON + MOUSE_ON);
|
|
13902
|
-
|
|
14040
|
+
process.stdout.write(ALT_SCREEN_ON + MOUSE_ON + BRACKETED_PASTE_ON);
|
|
14041
|
+
this.applyScrollRegion();
|
|
13903
14042
|
process.stdin.setEncoding("utf8");
|
|
13904
14043
|
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
13905
14044
|
process.stdin.on("data", this.onData);
|
|
@@ -13942,6 +14081,7 @@ var ChatTUI = class {
|
|
|
13942
14081
|
streamStart() {
|
|
13943
14082
|
this.isStreaming = true;
|
|
13944
14083
|
this.streamContent = "";
|
|
14084
|
+
this.runState = "streaming";
|
|
13945
14085
|
this.scrollOffset = 0;
|
|
13946
14086
|
this.dirty();
|
|
13947
14087
|
}
|
|
@@ -13957,8 +14097,42 @@ var ChatTUI = class {
|
|
|
13957
14097
|
this.streamContent = "";
|
|
13958
14098
|
this.dirty();
|
|
13959
14099
|
}
|
|
14100
|
+
runStarted() {
|
|
14101
|
+
this.turnInProgress = true;
|
|
14102
|
+
this.runState = "thinking";
|
|
14103
|
+
this.activeToolName = "";
|
|
14104
|
+
this.dirty();
|
|
14105
|
+
}
|
|
14106
|
+
runFinished() {
|
|
14107
|
+
this.turnInProgress = false;
|
|
14108
|
+
this.runState = "idle";
|
|
14109
|
+
this.activeToolName = "";
|
|
14110
|
+
this.dirty();
|
|
14111
|
+
}
|
|
14112
|
+
/** Temporarily leave alt-screen for an external prompt (HITL). */
|
|
14113
|
+
pauseForPrompt() {
|
|
14114
|
+
if (!this.started || this.pausedForPrompt) return;
|
|
14115
|
+
this.pausedForPrompt = true;
|
|
14116
|
+
process.stdin.removeListener("data", this.onData);
|
|
14117
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
14118
|
+
process.stdout.write(
|
|
14119
|
+
`${MOUSE_OFF}${BRACKETED_PASTE_OFF}${CSI}r${ALT_SCREEN_OFF}${SHOW_CURSOR}`
|
|
14120
|
+
);
|
|
14121
|
+
}
|
|
14122
|
+
/** Re-enter alt-screen after an external prompt. */
|
|
14123
|
+
resumeAfterPrompt() {
|
|
14124
|
+
if (!this.started || !this.pausedForPrompt) return;
|
|
14125
|
+
this.pausedForPrompt = false;
|
|
14126
|
+
process.stdout.write(ALT_SCREEN_ON + MOUSE_ON + BRACKETED_PASTE_ON);
|
|
14127
|
+
this.applyScrollRegion();
|
|
14128
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
14129
|
+
process.stdin.on("data", this.onData);
|
|
14130
|
+
this.render();
|
|
14131
|
+
}
|
|
13960
14132
|
toolCallStart(id, name) {
|
|
13961
14133
|
this.items.push({ kind: "tool_call", id, name, args: "", done: false });
|
|
14134
|
+
this.runState = "tool_running";
|
|
14135
|
+
this.activeToolName = name;
|
|
13962
14136
|
this.dirty();
|
|
13963
14137
|
}
|
|
13964
14138
|
toolCallArgs(id, delta) {
|
|
@@ -14011,7 +14185,12 @@ var ChatTUI = class {
|
|
|
14011
14185
|
this.dirty();
|
|
14012
14186
|
}
|
|
14013
14187
|
toolCallResult(toolCallId, toolName, content) {
|
|
14014
|
-
|
|
14188
|
+
const failed = toolResultLooksFailed(content);
|
|
14189
|
+
this.items.push({ kind: "tool_result", toolCallId, toolName, content, failed });
|
|
14190
|
+
if (!this.isStreaming) {
|
|
14191
|
+
this.runState = "thinking";
|
|
14192
|
+
this.activeToolName = "";
|
|
14193
|
+
}
|
|
14015
14194
|
this.dirty();
|
|
14016
14195
|
}
|
|
14017
14196
|
/** Find the tool name for a given tool call ID */
|
|
@@ -14064,7 +14243,9 @@ var ChatTUI = class {
|
|
|
14064
14243
|
this.scheduleRender();
|
|
14065
14244
|
}
|
|
14066
14245
|
restoreTerminal = () => {
|
|
14067
|
-
process.stdout.write(
|
|
14246
|
+
process.stdout.write(
|
|
14247
|
+
`${MOUSE_OFF}${BRACKETED_PASTE_OFF}${CSI}r${ALT_SCREEN_OFF}${SHOW_CURSOR}`
|
|
14248
|
+
);
|
|
14068
14249
|
};
|
|
14069
14250
|
updateDimensions() {
|
|
14070
14251
|
this.rows = process.stdout.rows || 24;
|
|
@@ -14080,11 +14261,32 @@ var ChatTUI = class {
|
|
|
14080
14261
|
if (!this.debugVisible) return 0;
|
|
14081
14262
|
return this.cols - this.chatWidth() - 1;
|
|
14082
14263
|
}
|
|
14264
|
+
titleRows() {
|
|
14265
|
+
return this.options.title || this.options.subtitle ? 1 : 0;
|
|
14266
|
+
}
|
|
14267
|
+
inputRows() {
|
|
14268
|
+
const lines = this.inputBuffer.split("\n").length;
|
|
14269
|
+
return Math.min(MAX_INPUT_ROWS, Math.max(1, lines));
|
|
14270
|
+
}
|
|
14271
|
+
footerRows() {
|
|
14272
|
+
return 1 + this.inputRows();
|
|
14273
|
+
}
|
|
14274
|
+
messageAreaTop() {
|
|
14275
|
+
return 1 + this.titleRows();
|
|
14276
|
+
}
|
|
14277
|
+
messageAreaBottom() {
|
|
14278
|
+
return this.rows - this.footerRows();
|
|
14279
|
+
}
|
|
14280
|
+
applyScrollRegion() {
|
|
14281
|
+
const top = this.messageAreaTop();
|
|
14282
|
+
const bottom = Math.max(top, this.messageAreaBottom());
|
|
14283
|
+
process.stdout.write(`${CSI}${top};${bottom}r`);
|
|
14284
|
+
}
|
|
14083
14285
|
scrollHeight() {
|
|
14084
|
-
return Math.max(1, this.
|
|
14286
|
+
return Math.max(1, this.messageAreaBottom() - this.messageAreaTop() + 1);
|
|
14085
14287
|
}
|
|
14086
14288
|
pageSize() {
|
|
14087
|
-
return Math.max(1, this.
|
|
14289
|
+
return Math.max(1, this.scrollHeight() - 2);
|
|
14088
14290
|
}
|
|
14089
14291
|
// ─── Scroll ───────────────────────────────────────────────────────
|
|
14090
14292
|
scrollUp(lines) {
|
|
@@ -14150,6 +14352,11 @@ var ChatTUI = class {
|
|
|
14150
14352
|
}
|
|
14151
14353
|
/** Handle a parsed CSI sequence: params is everything between \x1B[ and the final char */
|
|
14152
14354
|
handleCSI(params, code) {
|
|
14355
|
+
if (params === "201~") {
|
|
14356
|
+
this.scrollToBottom();
|
|
14357
|
+
this.dirty();
|
|
14358
|
+
return;
|
|
14359
|
+
}
|
|
14153
14360
|
if (params.startsWith("<") && (code === "M" || code === "m")) {
|
|
14154
14361
|
if (code === "m") return;
|
|
14155
14362
|
const parts = params.slice(1).split(";");
|
|
@@ -14231,22 +14438,31 @@ var ChatTUI = class {
|
|
|
14231
14438
|
this.handleEnter();
|
|
14232
14439
|
return;
|
|
14233
14440
|
}
|
|
14441
|
+
if (code === 10) {
|
|
14442
|
+
this.inputBuffer += "\n";
|
|
14443
|
+
this.dirty();
|
|
14444
|
+
return;
|
|
14445
|
+
}
|
|
14234
14446
|
if (code === 127 || code === 8) {
|
|
14235
14447
|
if (this.inputBuffer.length > 0) {
|
|
14236
14448
|
this.inputBuffer = this.inputBuffer.slice(0, -1);
|
|
14237
|
-
this.
|
|
14449
|
+
this.dirty();
|
|
14238
14450
|
}
|
|
14239
14451
|
return;
|
|
14240
14452
|
}
|
|
14241
14453
|
if (code < 32) return;
|
|
14242
14454
|
this.inputBuffer += ch;
|
|
14243
|
-
this.
|
|
14455
|
+
this.dirty();
|
|
14244
14456
|
}
|
|
14245
14457
|
handleEnter() {
|
|
14458
|
+
if (this.turnInProgress) {
|
|
14459
|
+
this.dirty();
|
|
14460
|
+
return;
|
|
14461
|
+
}
|
|
14246
14462
|
const text = this.inputBuffer.trim();
|
|
14247
14463
|
this.inputBuffer = "";
|
|
14248
14464
|
if (!text) {
|
|
14249
|
-
this.
|
|
14465
|
+
this.dirty();
|
|
14250
14466
|
return;
|
|
14251
14467
|
}
|
|
14252
14468
|
if (text.toLowerCase() === "exit" || text.toLowerCase() === "quit") {
|
|
@@ -14263,7 +14479,7 @@ var ChatTUI = class {
|
|
|
14263
14479
|
// ─── Events ───────────────────────────────────────────────────────
|
|
14264
14480
|
onResize = () => {
|
|
14265
14481
|
this.updateDimensions();
|
|
14266
|
-
|
|
14482
|
+
this.applyScrollRegion();
|
|
14267
14483
|
this.displayLinesCache = null;
|
|
14268
14484
|
this.debugLinesCache = null;
|
|
14269
14485
|
this.render();
|
|
@@ -14305,19 +14521,24 @@ var ChatTUI = class {
|
|
|
14305
14521
|
visible = displayLines.slice(start, end);
|
|
14306
14522
|
}
|
|
14307
14523
|
const buf = [HIDE_CURSOR];
|
|
14524
|
+
const msgTop = this.messageAreaTop();
|
|
14525
|
+
if (this.titleRows()) {
|
|
14526
|
+
buf.push(moveTo(1, 1), this.buildTitleBar(), CLEAR_EOL);
|
|
14527
|
+
}
|
|
14308
14528
|
if (this.debugVisible) {
|
|
14309
|
-
this.renderSplitScreen(buf, visible, sh);
|
|
14529
|
+
this.renderSplitScreen(buf, visible, sh, msgTop);
|
|
14310
14530
|
} else {
|
|
14311
14531
|
for (let i = 0; i < sh; i++) {
|
|
14312
|
-
buf.push(moveTo(
|
|
14532
|
+
buf.push(moveTo(msgTop + i, 1), visible[i] || "", CLEAR_EOL);
|
|
14313
14533
|
}
|
|
14314
14534
|
}
|
|
14315
|
-
|
|
14316
|
-
buf.push(moveTo(
|
|
14535
|
+
const sepRow = this.rows - this.inputRows();
|
|
14536
|
+
buf.push(moveTo(sepRow, 1), this.buildSeparator(), CLEAR_EOL);
|
|
14537
|
+
this.renderInputArea(buf, sepRow + 1);
|
|
14317
14538
|
buf.push(SHOW_CURSOR);
|
|
14318
14539
|
process.stdout.write(buf.join(""));
|
|
14319
14540
|
}
|
|
14320
|
-
renderSplitScreen(buf, chatVisible, sh) {
|
|
14541
|
+
renderSplitScreen(buf, chatVisible, sh, msgTop = 1) {
|
|
14321
14542
|
const cw = this.chatWidth();
|
|
14322
14543
|
const dw = this.debugWidth();
|
|
14323
14544
|
const divCol = cw + 1;
|
|
@@ -14333,21 +14554,70 @@ var ChatTUI = class {
|
|
|
14333
14554
|
const chatLine = truncateAnsi(chatVisible[i] || "", cw);
|
|
14334
14555
|
const debugLine = truncateAnsi(debugVisible[i] || "", dw);
|
|
14335
14556
|
buf.push(
|
|
14336
|
-
moveTo(
|
|
14557
|
+
moveTo(msgTop + i, 1),
|
|
14337
14558
|
chatLine,
|
|
14338
14559
|
CLEAR_EOL,
|
|
14339
14560
|
// clear to end handles rest, but we need to position divider
|
|
14340
|
-
moveTo(
|
|
14561
|
+
moveTo(msgTop + i, divCol),
|
|
14341
14562
|
`${DIM}\u2502${RESET}`,
|
|
14342
14563
|
debugLine
|
|
14343
14564
|
);
|
|
14344
14565
|
buf.push(CLEAR_EOL);
|
|
14345
14566
|
}
|
|
14346
14567
|
}
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
|
|
14350
|
-
);
|
|
14568
|
+
renderInputArea(buf, startRow) {
|
|
14569
|
+
const inputRows = this.inputRows();
|
|
14570
|
+
const width = this.chatWidth();
|
|
14571
|
+
const contentWidth = Math.max(1, width - INPUT_LABEL_WIDTH);
|
|
14572
|
+
const inputLines = this.wrapInputLines(this.inputBuffer, contentWidth, inputRows);
|
|
14573
|
+
for (let i = 0; i < inputRows; i++) {
|
|
14574
|
+
const row = startRow + i;
|
|
14575
|
+
const line = inputLines[i] ?? "";
|
|
14576
|
+
const prefix = i === 0 ? `${BLUE}${INPUT_LABEL}${RESET} ` : " ".repeat(INPUT_LABEL_WIDTH);
|
|
14577
|
+
buf.push(moveTo(row, 1), prefix, line, CLEAR_EOL);
|
|
14578
|
+
}
|
|
14579
|
+
}
|
|
14580
|
+
wrapInputLines(text, maxWidth, maxRows) {
|
|
14581
|
+
const rawLines = text.split("\n");
|
|
14582
|
+
const wrapped = [];
|
|
14583
|
+
for (const raw of rawLines) {
|
|
14584
|
+
for (const wl of this.wrapText(raw, maxWidth)) {
|
|
14585
|
+
wrapped.push(wl);
|
|
14586
|
+
}
|
|
14587
|
+
}
|
|
14588
|
+
if (!wrapped.length) wrapped.push("");
|
|
14589
|
+
if (wrapped.length > maxRows) {
|
|
14590
|
+
const trimmed = wrapped.slice(wrapped.length - maxRows);
|
|
14591
|
+
trimmed[0] = "\u2026" + trimmed[0].slice(1);
|
|
14592
|
+
return trimmed;
|
|
14593
|
+
}
|
|
14594
|
+
return wrapped;
|
|
14595
|
+
}
|
|
14596
|
+
buildTitleBar() {
|
|
14597
|
+
const parts = [];
|
|
14598
|
+
if (this.options.title) {
|
|
14599
|
+
parts.push(`${CYAN}${this.options.title}${RESET}`);
|
|
14600
|
+
}
|
|
14601
|
+
if (this.options.subtitle) {
|
|
14602
|
+
parts.push(`${DIM}${this.options.subtitle}${RESET}`);
|
|
14603
|
+
}
|
|
14604
|
+
let status = "";
|
|
14605
|
+
switch (this.runState) {
|
|
14606
|
+
case "thinking":
|
|
14607
|
+
status = `${YELLOW}Thinking\u2026${RESET}`;
|
|
14608
|
+
break;
|
|
14609
|
+
case "tool_running":
|
|
14610
|
+
status = `${YELLOW}Running ${this.activeToolName}\u2026${RESET}`;
|
|
14611
|
+
break;
|
|
14612
|
+
case "streaming":
|
|
14613
|
+
status = `${GREEN}Writing\u2026${RESET}`;
|
|
14614
|
+
break;
|
|
14615
|
+
default:
|
|
14616
|
+
break;
|
|
14617
|
+
}
|
|
14618
|
+
if (status) parts.push(status);
|
|
14619
|
+
const line = parts.join(` ${DIM}|${RESET} `);
|
|
14620
|
+
return truncateAnsi(line, this.cols);
|
|
14351
14621
|
}
|
|
14352
14622
|
// ─── Separator ────────────────────────────────────────────────────
|
|
14353
14623
|
buildSeparator() {
|
|
@@ -14362,6 +14632,7 @@ var ChatTUI = class {
|
|
|
14362
14632
|
if (this.options.debug) {
|
|
14363
14633
|
hints.push(`^D ${this.debugPanelVisible ? "hide" : "show"} debug`);
|
|
14364
14634
|
}
|
|
14635
|
+
hints.push("Enter send");
|
|
14365
14636
|
const lineWidth = Math.min(this.cols, 60);
|
|
14366
14637
|
if (!hints.length) {
|
|
14367
14638
|
return `${GRAY}${"\u2500".repeat(lineWidth)}${RESET}`;
|
|
@@ -14392,8 +14663,9 @@ var ChatTUI = class {
|
|
|
14392
14663
|
return lines;
|
|
14393
14664
|
}
|
|
14394
14665
|
appendToolCallLines(lines, tc, width) {
|
|
14395
|
-
const status = tc.done ? "
|
|
14396
|
-
const
|
|
14666
|
+
const status = tc.done ? "\u2713" : "\u2026";
|
|
14667
|
+
const verb = tc.done ? "Called" : "Calling";
|
|
14668
|
+
const summary = ` ${tc.done ? GREEN : YELLOW}${status}${RESET} ${GRAY}${verb} ${tc.name}${RESET}`;
|
|
14397
14669
|
if (!this.expandDetails) {
|
|
14398
14670
|
lines.push(summary);
|
|
14399
14671
|
return;
|
|
@@ -14414,14 +14686,15 @@ var ChatTUI = class {
|
|
|
14414
14686
|
}
|
|
14415
14687
|
}
|
|
14416
14688
|
appendToolResultLines(lines, tr, width) {
|
|
14689
|
+
const mark = tr.failed ? `${RED}\u2717${RESET}` : `${GREEN}\u2713${RESET}`;
|
|
14417
14690
|
if (!this.expandDetails) {
|
|
14418
|
-
const maxLen = width -
|
|
14691
|
+
const maxLen = width - 12;
|
|
14419
14692
|
const preview = tr.content.replace(/\n/g, " ").slice(0, maxLen);
|
|
14420
14693
|
const truncated = preview.length < tr.content.length ? "..." : "";
|
|
14421
|
-
lines.push(` ${
|
|
14694
|
+
lines.push(` ${mark} ${GRAY}${tr.toolName}: ${preview}${truncated}${RESET}`);
|
|
14422
14695
|
return;
|
|
14423
14696
|
}
|
|
14424
|
-
lines.push(` ${
|
|
14697
|
+
lines.push(` ${mark} ${GRAY}${tr.toolName} result:${RESET}`);
|
|
14425
14698
|
let formatted = tr.content;
|
|
14426
14699
|
try {
|
|
14427
14700
|
formatted = JSON.stringify(JSON.parse(tr.content), null, 2);
|
|
@@ -14473,7 +14746,12 @@ var ChatTUI = class {
|
|
|
14473
14746
|
const labelWidth = 12;
|
|
14474
14747
|
const indent = " ".repeat(labelWidth);
|
|
14475
14748
|
if (!this.streamContent) {
|
|
14476
|
-
|
|
14749
|
+
if (this.runState === "thinking" || this.runState === "tool_running") {
|
|
14750
|
+
const hint2 = this.runState === "tool_running" && this.activeToolName ? `Running ${this.activeToolName}\u2026` : "Thinking\u2026";
|
|
14751
|
+
lines.push(`${label}${YELLOW}${hint2}${RESET}`);
|
|
14752
|
+
} else {
|
|
14753
|
+
lines.push(label);
|
|
14754
|
+
}
|
|
14477
14755
|
return;
|
|
14478
14756
|
}
|
|
14479
14757
|
let first = true;
|
|
@@ -14591,15 +14869,68 @@ function buildDebugSummary(_direction, msg) {
|
|
|
14591
14869
|
return type;
|
|
14592
14870
|
}
|
|
14593
14871
|
|
|
14872
|
+
// src/lib/agui-tui-events.ts
|
|
14873
|
+
function handleAguiEvent(tui, event) {
|
|
14874
|
+
const type = event.type?.toLowerCase() ?? "";
|
|
14875
|
+
switch (type) {
|
|
14876
|
+
case "run_started":
|
|
14877
|
+
tui.runStarted();
|
|
14878
|
+
break;
|
|
14879
|
+
case "run_finished":
|
|
14880
|
+
tui.runFinished();
|
|
14881
|
+
break;
|
|
14882
|
+
case "text_message_start":
|
|
14883
|
+
tui.streamStart();
|
|
14884
|
+
break;
|
|
14885
|
+
case "text_message_content": {
|
|
14886
|
+
const delta = getTextDelta(event);
|
|
14887
|
+
if (delta) tui.streamDelta(delta);
|
|
14888
|
+
break;
|
|
14889
|
+
}
|
|
14890
|
+
case "text_message_end":
|
|
14891
|
+
tui.streamEnd();
|
|
14892
|
+
break;
|
|
14893
|
+
case "tool_call_start":
|
|
14894
|
+
tui.toolCallStart(
|
|
14895
|
+
event.toolCallId ?? "",
|
|
14896
|
+
event.toolCallName ?? event.name ?? "tool"
|
|
14897
|
+
);
|
|
14898
|
+
break;
|
|
14899
|
+
case "tool_call_args":
|
|
14900
|
+
if (event.toolCallId && event.delta) {
|
|
14901
|
+
tui.toolCallArgs(event.toolCallId, event.delta);
|
|
14902
|
+
}
|
|
14903
|
+
break;
|
|
14904
|
+
case "tool_call_end":
|
|
14905
|
+
if (event.toolCallId) tui.toolCallEnd(event.toolCallId);
|
|
14906
|
+
break;
|
|
14907
|
+
case "tool_call_result": {
|
|
14908
|
+
const toolCallId = event.toolCallId ?? "";
|
|
14909
|
+
const toolName = tui.findToolCallName(toolCallId);
|
|
14910
|
+
const content = typeof event.content === "string" ? event.content : typeof event.result === "string" ? event.result : JSON.stringify(event.result ?? event.content ?? "");
|
|
14911
|
+
tui.toolCallResult(toolCallId, toolName, content);
|
|
14912
|
+
break;
|
|
14913
|
+
}
|
|
14914
|
+
case "run_error": {
|
|
14915
|
+
const errorMsg = getErrorMessage(event);
|
|
14916
|
+
if (errorMsg) tui.showError(errorMsg);
|
|
14917
|
+
tui.runFinished();
|
|
14918
|
+
break;
|
|
14919
|
+
}
|
|
14920
|
+
default:
|
|
14921
|
+
break;
|
|
14922
|
+
}
|
|
14923
|
+
}
|
|
14924
|
+
|
|
14594
14925
|
// src/lib/audio.ts
|
|
14595
|
-
import { spawn, execSync
|
|
14926
|
+
import { spawn, execSync, spawnSync } from "child_process";
|
|
14596
14927
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
14597
14928
|
import * as readline3 from "readline";
|
|
14598
14929
|
var SAMPLE_RATE = 24e3;
|
|
14599
14930
|
var MIC_CHUNK_BYTES = 4800;
|
|
14600
14931
|
function checkSoxAvailable() {
|
|
14601
14932
|
try {
|
|
14602
|
-
|
|
14933
|
+
execSync("which sox", { stdio: "pipe" });
|
|
14603
14934
|
return true;
|
|
14604
14935
|
} catch {
|
|
14605
14936
|
return false;
|
|
@@ -14910,6 +15241,7 @@ var AudioPlayer = class extends EventEmitter2 {
|
|
|
14910
15241
|
};
|
|
14911
15242
|
|
|
14912
15243
|
// src/lib/chat-session.ts
|
|
15244
|
+
var HITL_INTERRUPT_EXIT_CODE = 3;
|
|
14913
15245
|
async function runChatSession(session, args) {
|
|
14914
15246
|
const debug = !!args.debug;
|
|
14915
15247
|
const isVoice = !!args.voice;
|
|
@@ -14939,6 +15271,14 @@ async function runChatSession(session, args) {
|
|
|
14939
15271
|
function vadMode(args) {
|
|
14940
15272
|
return typeof args.vad === "string" ? args.vad : "server";
|
|
14941
15273
|
}
|
|
15274
|
+
function sanitizeInterruptForStdout(result2) {
|
|
15275
|
+
const url = typeof result2.payload.url === "string" ? result2.payload.url : void 0;
|
|
15276
|
+
return {
|
|
15277
|
+
type: result2.type,
|
|
15278
|
+
interrupt_id: result2.interruptId,
|
|
15279
|
+
url
|
|
15280
|
+
};
|
|
15281
|
+
}
|
|
14942
15282
|
async function singleTurnChatSession(session, message, debug, autoApprove = false, allowWrite = false) {
|
|
14943
15283
|
const aguiClient = new AGUIClient({
|
|
14944
15284
|
baseUrl: session.ctx.envConfig.apiUrl,
|
|
@@ -14958,7 +15298,7 @@ async function singleTurnChatSession(session, message, debug, autoApprove = fals
|
|
|
14958
15298
|
`);
|
|
14959
15299
|
}
|
|
14960
15300
|
};
|
|
14961
|
-
const { streamText, hasToolCalls } = await runConciergeAguiLoop(
|
|
15301
|
+
const { streamText, hasToolCalls, unsupportedHitl } = await runConciergeAguiLoop(
|
|
14962
15302
|
aguiClient,
|
|
14963
15303
|
[{ role: "user", content: message }],
|
|
14964
15304
|
onEvent,
|
|
@@ -14971,6 +15311,21 @@ async function singleTurnChatSession(session, message, debug, autoApprove = fals
|
|
|
14971
15311
|
nonInteractive: true
|
|
14972
15312
|
}
|
|
14973
15313
|
);
|
|
15314
|
+
if (unsupportedHitl) {
|
|
15315
|
+
const jsonPayload = JSON.stringify({
|
|
15316
|
+
error: "hitl_interrupt",
|
|
15317
|
+
message: "A human-in-the-loop interrupt requires out-of-band action before the operation can continue.",
|
|
15318
|
+
resume: {
|
|
15319
|
+
chatId: session.chatId,
|
|
15320
|
+
chatUuid: session.chatUuid,
|
|
15321
|
+
interruptId: unsupportedHitl.interruptId
|
|
15322
|
+
},
|
|
15323
|
+
interrupt: sanitizeInterruptForStdout(unsupportedHitl)
|
|
15324
|
+
});
|
|
15325
|
+
process.stdout.write(`${jsonPayload}
|
|
15326
|
+
`);
|
|
15327
|
+
throw new CLIError("HITL interrupt requires out-of-band handling", HITL_INTERRUPT_EXIT_CODE);
|
|
15328
|
+
}
|
|
14974
15329
|
if (streamText) {
|
|
14975
15330
|
process.stdout.write(`\x1B[32mAssistant:\x1B[0m ${streamText}
|
|
14976
15331
|
`);
|
|
@@ -15015,113 +15370,95 @@ async function interactiveChatSession(session, debug, autoApprove, allowWrite) {
|
|
|
15015
15370
|
model: session.model,
|
|
15016
15371
|
appContext: session.appContext
|
|
15017
15372
|
});
|
|
15018
|
-
clearScreen();
|
|
15019
|
-
console.log("=".repeat(60));
|
|
15020
|
-
console.log(session.label);
|
|
15021
|
-
console.log("=".repeat(60));
|
|
15022
|
-
console.log(`Chat: ${session.chatTitle}`);
|
|
15023
|
-
console.log("Type your message and press Enter. Type 'exit' or Ctrl+C to quit.");
|
|
15024
|
-
console.log("=".repeat(60));
|
|
15025
|
-
console.log("");
|
|
15026
15373
|
const existingMessages = await session.loadMessages();
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
15374
|
+
const history = buildHistory(existingMessages);
|
|
15375
|
+
const conversationMessages = buildConversationMessages(existingMessages);
|
|
15376
|
+
let tui;
|
|
15377
|
+
const sendTurn = async (text) => {
|
|
15378
|
+
conversationMessages.push({ role: "user", content: text });
|
|
15379
|
+
let assistantContent = "";
|
|
15380
|
+
try {
|
|
15381
|
+
tui.runStarted();
|
|
15382
|
+
const { streamText } = await runConciergeAguiLoop(
|
|
15383
|
+
aguiClient,
|
|
15384
|
+
conversationMessages,
|
|
15385
|
+
(event) => {
|
|
15386
|
+
handleAguiEvent(tui, event);
|
|
15387
|
+
const type = event.type?.toLowerCase() ?? "";
|
|
15388
|
+
if (type === "text_message_content" && event.delta) {
|
|
15389
|
+
assistantContent += event.delta;
|
|
15390
|
+
}
|
|
15391
|
+
const errorMsg = getErrorMessage(event);
|
|
15392
|
+
if (errorMsg) tui.showError(errorMsg);
|
|
15393
|
+
},
|
|
15394
|
+
{
|
|
15395
|
+
autoApprove,
|
|
15396
|
+
allowWrite: allowWrite || !!session.allowWrite,
|
|
15397
|
+
debug,
|
|
15398
|
+
trustCwd: session.trustCwd,
|
|
15399
|
+
trustDir: session.trustDir,
|
|
15400
|
+
onHitlPromptStart: () => tui.pauseForPrompt(),
|
|
15401
|
+
onHitlPromptEnd: () => tui.resumeAfterPrompt()
|
|
15402
|
+
}
|
|
15403
|
+
);
|
|
15404
|
+
if (streamText && !assistantContent) {
|
|
15405
|
+
assistantContent = streamText;
|
|
15033
15406
|
}
|
|
15407
|
+
if (assistantContent) {
|
|
15408
|
+
conversationMessages.push({ role: "assistant", content: assistantContent });
|
|
15409
|
+
}
|
|
15410
|
+
} catch (error) {
|
|
15411
|
+
tui.showError(error instanceof Error ? error.message : String(error));
|
|
15412
|
+
} finally {
|
|
15413
|
+
tui.runFinished();
|
|
15034
15414
|
}
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
15415
|
+
};
|
|
15416
|
+
await new Promise((resolve) => {
|
|
15417
|
+
tui = new ChatTUI({
|
|
15418
|
+
title: session.label,
|
|
15419
|
+
subtitle: session.chatTitle,
|
|
15420
|
+
onSend: (text) => {
|
|
15421
|
+
void sendTurn(text);
|
|
15422
|
+
},
|
|
15423
|
+
onExit: () => {
|
|
15424
|
+
tui.stop();
|
|
15425
|
+
resolve();
|
|
15426
|
+
},
|
|
15427
|
+
debug,
|
|
15428
|
+
env: session.ctx.env
|
|
15429
|
+
});
|
|
15430
|
+
tui.loadHistory(history);
|
|
15431
|
+
tui.start();
|
|
15432
|
+
});
|
|
15433
|
+
}
|
|
15434
|
+
function buildConversationMessages(messages) {
|
|
15435
|
+
const out = [];
|
|
15436
|
+
for (const msg of messages) {
|
|
15039
15437
|
const text = typeof msg.content === "string" ? msg.content : "";
|
|
15040
|
-
if (text
|
|
15041
|
-
|
|
15438
|
+
if (text.trim() && (msg.role === "user" || msg.role === "assistant")) {
|
|
15439
|
+
out.push({ role: msg.role, content: text });
|
|
15042
15440
|
}
|
|
15043
15441
|
}
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
|
|
15048
|
-
|
|
15049
|
-
|
|
15050
|
-
|
|
15051
|
-
|
|
15052
|
-
|
|
15053
|
-
|
|
15054
|
-
|
|
15055
|
-
|
|
15056
|
-
|
|
15057
|
-
|
|
15058
|
-
|
|
15059
|
-
|
|
15060
|
-
|
|
15061
|
-
|
|
15062
|
-
|
|
15063
|
-
|
|
15064
|
-
conversationMessages,
|
|
15065
|
-
(event) => {
|
|
15066
|
-
const delta = getTextDelta(event);
|
|
15067
|
-
if (delta) {
|
|
15068
|
-
process.stdout.write(delta);
|
|
15069
|
-
assistantContent += delta;
|
|
15070
|
-
}
|
|
15071
|
-
const errorMsg = getErrorMessage(event);
|
|
15072
|
-
if (errorMsg) console.log(`
|
|
15073
|
-
\x1B[31mError: ${errorMsg}\x1B[0m`);
|
|
15074
|
-
},
|
|
15075
|
-
{
|
|
15076
|
-
autoApprove,
|
|
15077
|
-
allowWrite: allowWrite || !!session.allowWrite,
|
|
15078
|
-
debug,
|
|
15079
|
-
trustCwd: session.trustCwd,
|
|
15080
|
-
trustDir: session.trustDir
|
|
15081
|
-
}
|
|
15082
|
-
);
|
|
15083
|
-
if (streamText && !assistantContent) {
|
|
15084
|
-
assistantContent = streamText;
|
|
15085
|
-
process.stdout.write(streamText);
|
|
15086
|
-
}
|
|
15087
|
-
if (assistantContent) {
|
|
15088
|
-
conversationMessages.push({ role: "assistant", content: assistantContent });
|
|
15089
|
-
}
|
|
15090
|
-
console.log("\n");
|
|
15091
|
-
} catch (error) {
|
|
15092
|
-
console.log(
|
|
15093
|
-
`
|
|
15094
|
-
\x1B[31mError: ${error instanceof Error ? error.message : error}\x1B[0m
|
|
15095
|
-
`
|
|
15096
|
-
);
|
|
15097
|
-
}
|
|
15098
|
-
prompt();
|
|
15099
|
-
});
|
|
15100
|
-
};
|
|
15101
|
-
rl.on("close", () => {
|
|
15102
|
-
console.log("\nGoodbye!");
|
|
15103
|
-
resolve();
|
|
15104
|
-
});
|
|
15105
|
-
prompt();
|
|
15106
|
-
});
|
|
15107
|
-
}
|
|
15108
|
-
function buildHistory(messages) {
|
|
15109
|
-
const history = [];
|
|
15110
|
-
for (const msg of messages.slice(-30)) {
|
|
15111
|
-
const text = typeof msg.content === "string" ? msg.content : "";
|
|
15112
|
-
if (msg.role === "user" || msg.role === "assistant") {
|
|
15113
|
-
if (text.trim()) {
|
|
15114
|
-
history.push({ role: msg.role, content: text });
|
|
15115
|
-
}
|
|
15116
|
-
if (msg.role === "assistant" && msg.tool_calls) {
|
|
15117
|
-
for (const tc of msg.tool_calls) {
|
|
15118
|
-
history.push({
|
|
15119
|
-
kind: "tool_call",
|
|
15120
|
-
id: tc.id,
|
|
15121
|
-
name: tc.function.name,
|
|
15122
|
-
args: tc.function.arguments || "",
|
|
15123
|
-
done: true
|
|
15124
|
-
});
|
|
15442
|
+
return out;
|
|
15443
|
+
}
|
|
15444
|
+
function buildHistory(messages, limit) {
|
|
15445
|
+
const history = [];
|
|
15446
|
+
const slice = limit ? messages.slice(-limit) : messages;
|
|
15447
|
+
for (const msg of slice) {
|
|
15448
|
+
const text = typeof msg.content === "string" ? msg.content : "";
|
|
15449
|
+
if (msg.role === "user" || msg.role === "assistant") {
|
|
15450
|
+
if (text.trim()) {
|
|
15451
|
+
history.push({ role: msg.role, content: text });
|
|
15452
|
+
}
|
|
15453
|
+
if (msg.role === "assistant" && msg.tool_calls) {
|
|
15454
|
+
for (const tc of msg.tool_calls) {
|
|
15455
|
+
history.push({
|
|
15456
|
+
kind: "tool_call",
|
|
15457
|
+
id: tc.id,
|
|
15458
|
+
name: tc.function.name,
|
|
15459
|
+
args: tc.function.arguments || "",
|
|
15460
|
+
done: true
|
|
15461
|
+
});
|
|
15125
15462
|
}
|
|
15126
15463
|
}
|
|
15127
15464
|
} else if (msg.role === "tool") {
|
|
@@ -15179,7 +15516,7 @@ async function realtimeChatSession(session, vad, debug) {
|
|
|
15179
15516
|
throw new CLIError(`Failed to connect: ${err instanceof Error ? err.message : err}`);
|
|
15180
15517
|
}
|
|
15181
15518
|
const existingMessages = await session.loadMessages();
|
|
15182
|
-
const history = buildHistory(existingMessages);
|
|
15519
|
+
const history = buildHistory(existingMessages, 30);
|
|
15183
15520
|
const tui = new ChatTUI({
|
|
15184
15521
|
onSend: (text) => {
|
|
15185
15522
|
client.sendTextMessage(text);
|
|
@@ -15244,7 +15581,7 @@ Connecting to ${session.label.toLowerCase()}...
|
|
|
15244
15581
|
throw new CLIError(`Failed to connect: ${err instanceof Error ? err.message : err}`);
|
|
15245
15582
|
}
|
|
15246
15583
|
const existingMessages = await session.loadMessages();
|
|
15247
|
-
const history = buildHistory(existingMessages);
|
|
15584
|
+
const history = buildHistory(existingMessages, 30);
|
|
15248
15585
|
const mic = new MicCapture((pcm16) => {
|
|
15249
15586
|
if (!client.isConnected()) return;
|
|
15250
15587
|
client.sendAudio(pcm16);
|
|
@@ -15339,15 +15676,169 @@ async function listAudioDevices() {
|
|
|
15339
15676
|
}
|
|
15340
15677
|
}
|
|
15341
15678
|
|
|
15679
|
+
// src/lib/chat-history.ts
|
|
15680
|
+
var PAGE_SIZE = 100;
|
|
15681
|
+
var HARD_CAP = 2e3;
|
|
15682
|
+
async function loadAllChatMessages(ctx, workspaceUuid, chatId) {
|
|
15683
|
+
const items = [];
|
|
15684
|
+
let offset = 0;
|
|
15685
|
+
while (items.length < HARD_CAP) {
|
|
15686
|
+
const batch = await ctx.apiClient.getChatMessages(workspaceUuid, chatId, PAGE_SIZE, offset).catch(() => []);
|
|
15687
|
+
if (!batch.length) break;
|
|
15688
|
+
items.push(...batch);
|
|
15689
|
+
if (batch.length < PAGE_SIZE) break;
|
|
15690
|
+
offset += batch.length;
|
|
15691
|
+
}
|
|
15692
|
+
return items;
|
|
15693
|
+
}
|
|
15694
|
+
|
|
15695
|
+
// src/commands/manage.ts
|
|
15696
|
+
async function manageCommand(hctx) {
|
|
15697
|
+
const ctx = requireAuth(hctx.ctx);
|
|
15698
|
+
const { workspace, args } = hctx;
|
|
15699
|
+
const asMember = typeof args["as-member"] === "string" ? args["as-member"] : void 0;
|
|
15700
|
+
const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
|
|
15701
|
+
const message = typeof args.message === "string" ? args.message : void 0;
|
|
15702
|
+
const chat = await getOrCreateChat(ctx, workspace, chatIdArg, asMember, "manager", !!message);
|
|
15703
|
+
const session = buildWorkspaceSession(ctx, workspace, chat, "Workspace Manager", {
|
|
15704
|
+
appContext: buildCliConciergeAppContext(),
|
|
15705
|
+
allowWrite: true,
|
|
15706
|
+
trustCwd: !!args["trust-cwd"],
|
|
15707
|
+
trustDir: typeof args["trust-dir"] === "string" ? args["trust-dir"] : void 0
|
|
15708
|
+
});
|
|
15709
|
+
process.stderr.write(`Chat #${session.chatId}
|
|
15710
|
+
`);
|
|
15711
|
+
await runChatSession(session, args);
|
|
15712
|
+
process.stderr.write(`Chat #${session.chatId}
|
|
15713
|
+
`);
|
|
15714
|
+
}
|
|
15715
|
+
async function chatCommand(hctx) {
|
|
15716
|
+
const ctx = requireAuth(hctx.ctx);
|
|
15717
|
+
const { workspace, args } = hctx;
|
|
15718
|
+
const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
|
|
15719
|
+
const workflowId = typeof args.workflow === "string" ? args.workflow : void 0;
|
|
15720
|
+
const memberId = typeof args.member === "string" ? args.member : void 0;
|
|
15721
|
+
let chat;
|
|
15722
|
+
if (chatIdArg) {
|
|
15723
|
+
const id = Number(chatIdArg);
|
|
15724
|
+
if (isNaN(id)) throw new CLIError("--chat-id must be a number");
|
|
15725
|
+
chat = await ctx.apiClient.getChat(workspace, id);
|
|
15726
|
+
} else if (workflowId) {
|
|
15727
|
+
chat = await createAssignmentChat(ctx.apiClient, workspace, Number(workflowId), memberId);
|
|
15728
|
+
} else if (memberId) {
|
|
15729
|
+
chat = await createMemberChat(ctx.apiClient, workspace, memberId);
|
|
15730
|
+
} else {
|
|
15731
|
+
throw new CLIError(
|
|
15732
|
+
'Specify --workflow <id>, --member <id>, or --chat-id <id>.\nUse "gr manage" for workspace manager chats.'
|
|
15733
|
+
);
|
|
15734
|
+
}
|
|
15735
|
+
const session = buildWorkspaceSession(ctx, workspace, chat, "Chat");
|
|
15736
|
+
await runChatSession(session, args);
|
|
15737
|
+
}
|
|
15738
|
+
function buildWorkspaceSession(ctx, workspace, chat, label, extra) {
|
|
15739
|
+
const wsBase = ctx.envConfig.apiUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
|
|
15740
|
+
return {
|
|
15741
|
+
ctx,
|
|
15742
|
+
workspaceUuid: workspace,
|
|
15743
|
+
chatId: chat.id,
|
|
15744
|
+
chatUuid: chat.uuid,
|
|
15745
|
+
chatTitle: chat.title || chat.uuid,
|
|
15746
|
+
aguiEndpointUrl: `${ctx.envConfig.apiUrl}/api/v2/w/${workspace}/web-chat/${chat.id}/chat/agui`,
|
|
15747
|
+
wsUrl: `${wsBase}/api/v2/w/${workspace}/realtime/stream-ws`,
|
|
15748
|
+
loadMessages: () => loadAllChatMessages(ctx, workspace, chat.id),
|
|
15749
|
+
label,
|
|
15750
|
+
model: "manager",
|
|
15751
|
+
...extra
|
|
15752
|
+
};
|
|
15753
|
+
}
|
|
15754
|
+
async function getOrCreateChat(ctx, workspace, chatIdArg, asMemberId, chatType, skipSelection = false) {
|
|
15755
|
+
if (chatIdArg) {
|
|
15756
|
+
const chatId = Number(chatIdArg);
|
|
15757
|
+
if (isNaN(chatId)) throw new CLIError("--chat-id must be a number");
|
|
15758
|
+
return ctx.apiClient.getChat(workspace, chatId);
|
|
15759
|
+
}
|
|
15760
|
+
if (skipSelection) {
|
|
15761
|
+
return createManagerChat(ctx.apiClient, workspace, asMemberId);
|
|
15762
|
+
}
|
|
15763
|
+
const chatsResponse = await ctx.apiClient.getChats(workspace, {
|
|
15764
|
+
chatType,
|
|
15765
|
+
sortBy: "updatedAt",
|
|
15766
|
+
sortOrder: "desc",
|
|
15767
|
+
pageSize: 20
|
|
15768
|
+
});
|
|
15769
|
+
const chats = chatsResponse.items || [];
|
|
15770
|
+
const { item: selectedChat, isNew } = await selectFromList(chats, {
|
|
15771
|
+
title: "Select a chat or create a new one",
|
|
15772
|
+
labelFn: (chat) => chat.title || `Chat ${chat.uuid.slice(0, 8)}...`,
|
|
15773
|
+
detailFn: (chat) => {
|
|
15774
|
+
const time = formatTimestamp(chat.updatedAt);
|
|
15775
|
+
const msgCount = chat.messageCount || 0;
|
|
15776
|
+
return `${msgCount} msg${msgCount !== 1 ? "s" : ""} - ${time}`;
|
|
15777
|
+
},
|
|
15778
|
+
emptyMessage: "No existing chats.",
|
|
15779
|
+
allowNew: true,
|
|
15780
|
+
newLabel: "+ Start new chat"
|
|
15781
|
+
});
|
|
15782
|
+
if (!selectedChat && !isNew) {
|
|
15783
|
+
throw new CLIError("No chat selected.");
|
|
15784
|
+
}
|
|
15785
|
+
if (isNew) {
|
|
15786
|
+
return createManagerChat(ctx.apiClient, workspace, asMemberId);
|
|
15787
|
+
}
|
|
15788
|
+
return selectedChat;
|
|
15789
|
+
}
|
|
15790
|
+
async function createManagerChat(apiClient, workspace, asMemberId) {
|
|
15791
|
+
const data = {
|
|
15792
|
+
chatType: "manager",
|
|
15793
|
+
channel: "web-chat"
|
|
15794
|
+
};
|
|
15795
|
+
if (asMemberId) {
|
|
15796
|
+
data.memberId = Number(asMemberId);
|
|
15797
|
+
}
|
|
15798
|
+
const chat = await apiClient.createChat(workspace, data);
|
|
15799
|
+
process.stderr.write(`Created chat #${chat.id}
|
|
15800
|
+
`);
|
|
15801
|
+
return chat;
|
|
15802
|
+
}
|
|
15803
|
+
async function createAssignmentChat(apiClient, workspace, workflowId, asMemberId) {
|
|
15804
|
+
if (isNaN(workflowId)) throw new CLIError("--workflow must be a number");
|
|
15805
|
+
const data = {
|
|
15806
|
+
chatType: "assignment",
|
|
15807
|
+
channel: "web-chat",
|
|
15808
|
+
workflowId
|
|
15809
|
+
};
|
|
15810
|
+
if (asMemberId) {
|
|
15811
|
+
data.memberId = Number(asMemberId);
|
|
15812
|
+
}
|
|
15813
|
+
const chat = await apiClient.createChat(workspace, data);
|
|
15814
|
+
process.stderr.write(`Created assignment chat #${chat.id} (workflow ${workflowId})
|
|
15815
|
+
`);
|
|
15816
|
+
return chat;
|
|
15817
|
+
}
|
|
15818
|
+
async function createMemberChat(apiClient, workspace, memberId) {
|
|
15819
|
+
const id = Number(memberId);
|
|
15820
|
+
if (isNaN(id)) throw new CLIError("--member must be a number");
|
|
15821
|
+
const data = {
|
|
15822
|
+
chatType: "member",
|
|
15823
|
+
channel: "web-chat",
|
|
15824
|
+
memberId: id
|
|
15825
|
+
};
|
|
15826
|
+
const chat = await apiClient.createChat(workspace, data);
|
|
15827
|
+
process.stderr.write(`Created DM chat #${chat.id} (member ${memberId})
|
|
15828
|
+
`);
|
|
15829
|
+
return chat;
|
|
15830
|
+
}
|
|
15831
|
+
|
|
15342
15832
|
// src/commands/concierge.ts
|
|
15343
15833
|
async function conciergeOutboundCommand(hctx) {
|
|
15344
15834
|
const ctx = requireAuth(hctx.ctx);
|
|
15345
15835
|
const { args } = hctx;
|
|
15836
|
+
const message = typeof args.message === "string" ? args.message : void 0;
|
|
15346
15837
|
if (args.call && args.sms) {
|
|
15347
15838
|
throw new CLIError("--call and --sms are mutually exclusive");
|
|
15348
15839
|
}
|
|
15349
15840
|
if (args.call) {
|
|
15350
|
-
const result2 = await originateConciergeCall(ctx);
|
|
15841
|
+
const result2 = await originateConciergeCall(ctx, "concierge_setup", message, "cli");
|
|
15351
15842
|
process.stderr.write(
|
|
15352
15843
|
`Concierge call started. callSid=${result2.callSid} startedAt=${result2.startedAt}
|
|
15353
15844
|
`
|
|
@@ -15438,6 +15929,64 @@ async function getOrCreateConciergeChat(ctx, chatIdArg, skipSelection) {
|
|
|
15438
15929
|
return selectedChat;
|
|
15439
15930
|
}
|
|
15440
15931
|
|
|
15932
|
+
// src/commands/dev.ts
|
|
15933
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync as readFileSync2 } from "fs";
|
|
15934
|
+
import { execSync as execSync2 } from "child_process";
|
|
15935
|
+
import { join } from "path";
|
|
15936
|
+
async function devImportLocalCommand(ctx, workspace) {
|
|
15937
|
+
stderr(`Exporting workspace ${workspace}...`);
|
|
15938
|
+
const exportData = await ctx.apiClient.exportWorkspace(workspace);
|
|
15939
|
+
stderr("Export successful!");
|
|
15940
|
+
stderr(` Workflows: ${(exportData.workflows || []).length}`);
|
|
15941
|
+
stderr(` Tasks: ${(exportData.tasks || []).length}`);
|
|
15942
|
+
stderr(` Data Types: ${(exportData.dataTypes || []).length}`);
|
|
15943
|
+
stderr(` Sites: ${(exportData.sites || []).length}`);
|
|
15944
|
+
const slug = workspace.slice(0, 8);
|
|
15945
|
+
const importCacheDir = join(ctx.credentialsManager.getCacheDir(), "imports");
|
|
15946
|
+
if (!existsSync(importCacheDir)) {
|
|
15947
|
+
mkdirSync(importCacheDir, { recursive: true });
|
|
15948
|
+
}
|
|
15949
|
+
const filename = `ws-${slug}-${workspace}.json`;
|
|
15950
|
+
const filepath = join(importCacheDir, filename);
|
|
15951
|
+
writeFileSync(filepath, JSON.stringify(exportData, null, 2));
|
|
15952
|
+
stderr(`Saved to: ${filepath}`);
|
|
15953
|
+
stderr("Running dev:restore...");
|
|
15954
|
+
const rootDir = findProjectRoot();
|
|
15955
|
+
const relativePath = filepath.replace(rootDir + "/", "");
|
|
15956
|
+
try {
|
|
15957
|
+
const result2 = execSync2(`yarn dev:restore ${relativePath}`, {
|
|
15958
|
+
cwd: rootDir,
|
|
15959
|
+
stdio: "pipe",
|
|
15960
|
+
encoding: "utf-8"
|
|
15961
|
+
});
|
|
15962
|
+
process.stderr.write(result2);
|
|
15963
|
+
const failed = result2.includes("Failed/Skipped: ") && !result2.includes("Failed/Skipped: 0") || result2.includes("Successfully restored: 0");
|
|
15964
|
+
if (failed) throw new CLIError("Import failed");
|
|
15965
|
+
} catch (error) {
|
|
15966
|
+
if (error instanceof CLIError) throw error;
|
|
15967
|
+
const execError = error;
|
|
15968
|
+
if (execError.stdout) process.stderr.write(execError.stdout);
|
|
15969
|
+
if (execError.stderr) process.stderr.write(execError.stderr);
|
|
15970
|
+
throw new CLIError("dev:restore failed");
|
|
15971
|
+
}
|
|
15972
|
+
stderr(`Import complete: ${workspace}`);
|
|
15973
|
+
}
|
|
15974
|
+
function findProjectRoot() {
|
|
15975
|
+
let dir = process.cwd();
|
|
15976
|
+
while (dir !== "/") {
|
|
15977
|
+
const pkgPath = join(dir, "package.json");
|
|
15978
|
+
if (existsSync(pkgPath)) {
|
|
15979
|
+
try {
|
|
15980
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
15981
|
+
if (pkg.workspaces) return dir;
|
|
15982
|
+
} catch {
|
|
15983
|
+
}
|
|
15984
|
+
}
|
|
15985
|
+
dir = join(dir, "..");
|
|
15986
|
+
}
|
|
15987
|
+
return process.cwd();
|
|
15988
|
+
}
|
|
15989
|
+
|
|
15441
15990
|
// src/commands/get-started.ts
|
|
15442
15991
|
var WEB_SETUP = "https://app.gravityrail.com/setup";
|
|
15443
15992
|
var WEB_GET_STARTED = "https://app.gravityrail.com/get-started";
|
|
@@ -15537,73 +16086,15 @@ async function getStartedCommand(hctx) {
|
|
|
15537
16086
|
process.stdout.write(LLM_GUIDE);
|
|
15538
16087
|
}
|
|
15539
16088
|
|
|
15540
|
-
// src/commands/dev.ts
|
|
15541
|
-
import { existsSync as existsSync2, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, readFileSync as readFileSync3 } from "fs";
|
|
15542
|
-
import { execSync as execSync3 } from "child_process";
|
|
15543
|
-
import { join as join2 } from "path";
|
|
15544
|
-
async function devImportLocalCommand(ctx, workspace) {
|
|
15545
|
-
stderr(`Exporting workspace ${workspace}...`);
|
|
15546
|
-
const exportData = await ctx.apiClient.exportWorkspace(workspace);
|
|
15547
|
-
stderr("Export successful!");
|
|
15548
|
-
stderr(` Workflows: ${(exportData.workflows || []).length}`);
|
|
15549
|
-
stderr(` Tasks: ${(exportData.tasks || []).length}`);
|
|
15550
|
-
stderr(` Data Types: ${(exportData.dataTypes || []).length}`);
|
|
15551
|
-
stderr(` Sites: ${(exportData.sites || []).length}`);
|
|
15552
|
-
const slug = workspace.slice(0, 8);
|
|
15553
|
-
const importCacheDir = join2(ctx.credentialsManager.getCacheDir(), "imports");
|
|
15554
|
-
if (!existsSync2(importCacheDir)) {
|
|
15555
|
-
mkdirSync2(importCacheDir, { recursive: true });
|
|
15556
|
-
}
|
|
15557
|
-
const filename = `ws-${slug}-${workspace}.json`;
|
|
15558
|
-
const filepath = join2(importCacheDir, filename);
|
|
15559
|
-
writeFileSync2(filepath, JSON.stringify(exportData, null, 2));
|
|
15560
|
-
stderr(`Saved to: ${filepath}`);
|
|
15561
|
-
stderr("Running dev:restore...");
|
|
15562
|
-
const rootDir = findProjectRoot();
|
|
15563
|
-
const relativePath = filepath.replace(rootDir + "/", "");
|
|
15564
|
-
try {
|
|
15565
|
-
const result2 = execSync3(`yarn dev:restore ${relativePath}`, {
|
|
15566
|
-
cwd: rootDir,
|
|
15567
|
-
stdio: "pipe",
|
|
15568
|
-
encoding: "utf-8"
|
|
15569
|
-
});
|
|
15570
|
-
process.stderr.write(result2);
|
|
15571
|
-
const failed = result2.includes("Failed/Skipped: ") && !result2.includes("Failed/Skipped: 0") || result2.includes("Successfully restored: 0");
|
|
15572
|
-
if (failed) throw new CLIError("Import failed");
|
|
15573
|
-
} catch (error) {
|
|
15574
|
-
if (error instanceof CLIError) throw error;
|
|
15575
|
-
const execError = error;
|
|
15576
|
-
if (execError.stdout) process.stderr.write(execError.stdout);
|
|
15577
|
-
if (execError.stderr) process.stderr.write(execError.stderr);
|
|
15578
|
-
throw new CLIError("dev:restore failed");
|
|
15579
|
-
}
|
|
15580
|
-
stderr(`Import complete: ${workspace}`);
|
|
15581
|
-
}
|
|
15582
|
-
function findProjectRoot() {
|
|
15583
|
-
let dir = process.cwd();
|
|
15584
|
-
while (dir !== "/") {
|
|
15585
|
-
const pkgPath = join2(dir, "package.json");
|
|
15586
|
-
if (existsSync2(pkgPath)) {
|
|
15587
|
-
try {
|
|
15588
|
-
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
15589
|
-
if (pkg.workspaces) return dir;
|
|
15590
|
-
} catch {
|
|
15591
|
-
}
|
|
15592
|
-
}
|
|
15593
|
-
dir = join2(dir, "..");
|
|
15594
|
-
}
|
|
15595
|
-
return process.cwd();
|
|
15596
|
-
}
|
|
15597
|
-
|
|
15598
16089
|
// src/commands/login.ts
|
|
15599
|
-
import { GravityRailClient
|
|
16090
|
+
import { GravityRailClient } from "@gravity-rail/sdk";
|
|
15600
16091
|
async function loginCommand(ctx) {
|
|
15601
16092
|
const { accessToken } = await performOAuthFlow({
|
|
15602
16093
|
env: ctx.env,
|
|
15603
16094
|
envConfig: ctx.envConfig,
|
|
15604
16095
|
credentialsManager: ctx.credentialsManager
|
|
15605
16096
|
});
|
|
15606
|
-
const apiClient = new
|
|
16097
|
+
const apiClient = new GravityRailClient(accessToken, ctx.envConfig.apiUrl);
|
|
15607
16098
|
const account = await apiClient.getUserAccount();
|
|
15608
16099
|
stderr(`Logged in as ${account.name} (${account.email})`);
|
|
15609
16100
|
}
|
|
@@ -15612,143 +16103,6 @@ async function logoutCommand(ctx) {
|
|
|
15612
16103
|
stderr(`Credentials cleared for ${ctx.env}`);
|
|
15613
16104
|
}
|
|
15614
16105
|
|
|
15615
|
-
// src/commands/manage.ts
|
|
15616
|
-
async function manageCommand(hctx) {
|
|
15617
|
-
const ctx = requireAuth(hctx.ctx);
|
|
15618
|
-
const { workspace, args } = hctx;
|
|
15619
|
-
const asMember = typeof args["as-member"] === "string" ? args["as-member"] : void 0;
|
|
15620
|
-
const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
|
|
15621
|
-
const message = typeof args.message === "string" ? args.message : void 0;
|
|
15622
|
-
const chat = await getOrCreateChat(ctx, workspace, chatIdArg, asMember, "manager", !!message);
|
|
15623
|
-
const session = buildWorkspaceSession(ctx, workspace, chat, "Workspace Manager", {
|
|
15624
|
-
appContext: buildCliConciergeAppContext(),
|
|
15625
|
-
allowWrite: true,
|
|
15626
|
-
trustCwd: !!args["trust-cwd"],
|
|
15627
|
-
trustDir: typeof args["trust-dir"] === "string" ? args["trust-dir"] : void 0
|
|
15628
|
-
});
|
|
15629
|
-
process.stderr.write(`Chat #${session.chatId}
|
|
15630
|
-
`);
|
|
15631
|
-
await runChatSession(session, args);
|
|
15632
|
-
process.stderr.write(`Chat #${session.chatId}
|
|
15633
|
-
`);
|
|
15634
|
-
}
|
|
15635
|
-
async function chatCommand(hctx) {
|
|
15636
|
-
const ctx = requireAuth(hctx.ctx);
|
|
15637
|
-
const { workspace, args } = hctx;
|
|
15638
|
-
const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
|
|
15639
|
-
const workflowId = typeof args.workflow === "string" ? args.workflow : void 0;
|
|
15640
|
-
const memberId = typeof args.member === "string" ? args.member : void 0;
|
|
15641
|
-
let chat;
|
|
15642
|
-
if (chatIdArg) {
|
|
15643
|
-
const id = Number(chatIdArg);
|
|
15644
|
-
if (isNaN(id)) throw new CLIError("--chat-id must be a number");
|
|
15645
|
-
chat = await ctx.apiClient.getChat(workspace, id);
|
|
15646
|
-
} else if (workflowId) {
|
|
15647
|
-
chat = await createAssignmentChat(ctx.apiClient, workspace, Number(workflowId), memberId);
|
|
15648
|
-
} else if (memberId) {
|
|
15649
|
-
chat = await createMemberChat(ctx.apiClient, workspace, memberId);
|
|
15650
|
-
} else {
|
|
15651
|
-
throw new CLIError(
|
|
15652
|
-
'Specify --workflow <id>, --member <id>, or --chat-id <id>.\nUse "gr manage" for workspace manager chats.'
|
|
15653
|
-
);
|
|
15654
|
-
}
|
|
15655
|
-
const session = buildWorkspaceSession(ctx, workspace, chat, "Chat");
|
|
15656
|
-
await runChatSession(session, args);
|
|
15657
|
-
}
|
|
15658
|
-
function buildWorkspaceSession(ctx, workspace, chat, label, extra) {
|
|
15659
|
-
const wsBase = ctx.envConfig.apiUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
|
|
15660
|
-
return {
|
|
15661
|
-
ctx,
|
|
15662
|
-
workspaceUuid: workspace,
|
|
15663
|
-
chatId: chat.id,
|
|
15664
|
-
chatUuid: chat.uuid,
|
|
15665
|
-
chatTitle: chat.title || chat.uuid,
|
|
15666
|
-
aguiEndpointUrl: `${ctx.envConfig.apiUrl}/api/v2/w/${workspace}/web-chat/${chat.id}/chat/agui`,
|
|
15667
|
-
wsUrl: `${wsBase}/api/v2/w/${workspace}/realtime/stream-ws`,
|
|
15668
|
-
loadMessages: () => ctx.apiClient.getChatMessages(workspace, chat.id, 50).catch(() => []),
|
|
15669
|
-
label,
|
|
15670
|
-
model: "manager",
|
|
15671
|
-
...extra
|
|
15672
|
-
};
|
|
15673
|
-
}
|
|
15674
|
-
async function getOrCreateChat(ctx, workspace, chatIdArg, asMemberId, chatType, skipSelection = false) {
|
|
15675
|
-
if (chatIdArg) {
|
|
15676
|
-
const chatId = Number(chatIdArg);
|
|
15677
|
-
if (isNaN(chatId)) throw new CLIError("--chat-id must be a number");
|
|
15678
|
-
return ctx.apiClient.getChat(workspace, chatId);
|
|
15679
|
-
}
|
|
15680
|
-
if (skipSelection) {
|
|
15681
|
-
return createManagerChat(ctx.apiClient, workspace, asMemberId);
|
|
15682
|
-
}
|
|
15683
|
-
const chatsResponse = await ctx.apiClient.getChats(workspace, {
|
|
15684
|
-
chatType,
|
|
15685
|
-
sortBy: "updatedAt",
|
|
15686
|
-
sortOrder: "desc",
|
|
15687
|
-
pageSize: 20
|
|
15688
|
-
});
|
|
15689
|
-
const chats = chatsResponse.items || [];
|
|
15690
|
-
const { item: selectedChat, isNew } = await selectFromList(chats, {
|
|
15691
|
-
title: "Select a chat or create a new one",
|
|
15692
|
-
labelFn: (chat) => chat.title || `Chat ${chat.uuid.slice(0, 8)}...`,
|
|
15693
|
-
detailFn: (chat) => {
|
|
15694
|
-
const time = formatTimestamp(chat.updatedAt);
|
|
15695
|
-
const msgCount = chat.messageCount || 0;
|
|
15696
|
-
return `${msgCount} msg${msgCount !== 1 ? "s" : ""} - ${time}`;
|
|
15697
|
-
},
|
|
15698
|
-
emptyMessage: "No existing chats.",
|
|
15699
|
-
allowNew: true,
|
|
15700
|
-
newLabel: "+ Start new chat"
|
|
15701
|
-
});
|
|
15702
|
-
if (!selectedChat && !isNew) {
|
|
15703
|
-
throw new CLIError("No chat selected.");
|
|
15704
|
-
}
|
|
15705
|
-
if (isNew) {
|
|
15706
|
-
return createManagerChat(ctx.apiClient, workspace, asMemberId);
|
|
15707
|
-
}
|
|
15708
|
-
return selectedChat;
|
|
15709
|
-
}
|
|
15710
|
-
async function createManagerChat(apiClient, workspace, asMemberId) {
|
|
15711
|
-
const data = {
|
|
15712
|
-
chatType: "manager",
|
|
15713
|
-
channel: "web-chat"
|
|
15714
|
-
};
|
|
15715
|
-
if (asMemberId) {
|
|
15716
|
-
data.memberId = Number(asMemberId);
|
|
15717
|
-
}
|
|
15718
|
-
const chat = await apiClient.createChat(workspace, data);
|
|
15719
|
-
process.stderr.write(`Created chat #${chat.id}
|
|
15720
|
-
`);
|
|
15721
|
-
return chat;
|
|
15722
|
-
}
|
|
15723
|
-
async function createAssignmentChat(apiClient, workspace, workflowId, asMemberId) {
|
|
15724
|
-
if (isNaN(workflowId)) throw new CLIError("--workflow must be a number");
|
|
15725
|
-
const data = {
|
|
15726
|
-
chatType: "assignment",
|
|
15727
|
-
channel: "web-chat",
|
|
15728
|
-
workflowId
|
|
15729
|
-
};
|
|
15730
|
-
if (asMemberId) {
|
|
15731
|
-
data.memberId = Number(asMemberId);
|
|
15732
|
-
}
|
|
15733
|
-
const chat = await apiClient.createChat(workspace, data);
|
|
15734
|
-
process.stderr.write(`Created assignment chat #${chat.id} (workflow ${workflowId})
|
|
15735
|
-
`);
|
|
15736
|
-
return chat;
|
|
15737
|
-
}
|
|
15738
|
-
async function createMemberChat(apiClient, workspace, memberId) {
|
|
15739
|
-
const id = Number(memberId);
|
|
15740
|
-
if (isNaN(id)) throw new CLIError("--member must be a number");
|
|
15741
|
-
const data = {
|
|
15742
|
-
chatType: "member",
|
|
15743
|
-
channel: "web-chat",
|
|
15744
|
-
memberId: id
|
|
15745
|
-
};
|
|
15746
|
-
const chat = await apiClient.createChat(workspace, data);
|
|
15747
|
-
process.stderr.write(`Created DM chat #${chat.id} (member ${memberId})
|
|
15748
|
-
`);
|
|
15749
|
-
return chat;
|
|
15750
|
-
}
|
|
15751
|
-
|
|
15752
16106
|
// src/commands/organizations.ts
|
|
15753
16107
|
var COLUMNS34 = [
|
|
15754
16108
|
{ header: "NAME", getValue: (o) => o.organizationName || "", maxWidth: 35 },
|
|
@@ -15776,6 +16130,230 @@ async function whoamiCommand(ctx) {
|
|
|
15776
16130
|
);
|
|
15777
16131
|
outputSingle(account, ctx, columns);
|
|
15778
16132
|
}
|
|
16133
|
+
|
|
16134
|
+
// src/top-level-commands.ts
|
|
16135
|
+
var topLevelCommands = [
|
|
16136
|
+
{
|
|
16137
|
+
path: ["login"],
|
|
16138
|
+
description: "Authenticate via OAuth",
|
|
16139
|
+
requiresAuth: false,
|
|
16140
|
+
handler: async ({ ctx }) => {
|
|
16141
|
+
await loginCommand(ctx);
|
|
16142
|
+
}
|
|
16143
|
+
},
|
|
16144
|
+
{
|
|
16145
|
+
path: ["logout"],
|
|
16146
|
+
description: "Clear cached credentials",
|
|
16147
|
+
requiresAuth: false,
|
|
16148
|
+
handler: async ({ ctx }) => {
|
|
16149
|
+
await logoutCommand(ctx);
|
|
16150
|
+
}
|
|
16151
|
+
},
|
|
16152
|
+
{
|
|
16153
|
+
path: ["auth", "browser"],
|
|
16154
|
+
description: "Open an authenticated browser session using CLI credentials",
|
|
16155
|
+
args: [
|
|
16156
|
+
{ name: "no-open", description: "Print the URL instead of opening browser", isFlag: true }
|
|
16157
|
+
],
|
|
16158
|
+
handler: async ({ ctx, args }) => {
|
|
16159
|
+
await authBrowserCommand(requireAuth(ctx), args);
|
|
16160
|
+
}
|
|
16161
|
+
},
|
|
16162
|
+
{
|
|
16163
|
+
path: ["manage"],
|
|
16164
|
+
description: "Interactive workspace manager chat (text, realtime, or voice)",
|
|
16165
|
+
requiresWorkspace: true,
|
|
16166
|
+
mutates: true,
|
|
16167
|
+
args: [
|
|
16168
|
+
{ name: "realtime", description: "Use WebSocket realtime connection", isFlag: true },
|
|
16169
|
+
{ name: "voice", description: "Enable voice mode (microphone + speaker)", isFlag: true },
|
|
16170
|
+
{ name: "message", alias: "m", description: "Single-turn: send one message and exit" },
|
|
16171
|
+
{ name: "as-member", description: "Chat as a specific member (by numeric ID)" },
|
|
16172
|
+
{ name: "chat-id", description: "Resume an existing chat by numeric ID" },
|
|
16173
|
+
{ name: "vad", description: "Voice activity detection: server (default) or client" },
|
|
16174
|
+
{ name: "list-devices", description: "List audio devices and exit", isFlag: true },
|
|
16175
|
+
{ name: "input-device", description: "Microphone device name or ID" },
|
|
16176
|
+
{ name: "output-device", description: "Speaker device name or ID" },
|
|
16177
|
+
{ name: "debug", description: "Enable debug logging", isFlag: true },
|
|
16178
|
+
{
|
|
16179
|
+
name: "auto-approve",
|
|
16180
|
+
description: "Auto-approve all tool calls (skip HITL)",
|
|
16181
|
+
isFlag: true
|
|
16182
|
+
},
|
|
16183
|
+
{
|
|
16184
|
+
name: "trust-cwd",
|
|
16185
|
+
description: "Trust files in the current working directory (non-interactive)",
|
|
16186
|
+
isFlag: true
|
|
16187
|
+
},
|
|
16188
|
+
{
|
|
16189
|
+
name: "trust-dir",
|
|
16190
|
+
description: "Trust files in this directory (non-interactive; absolute path)"
|
|
16191
|
+
}
|
|
16192
|
+
],
|
|
16193
|
+
handler: async (hctx) => {
|
|
16194
|
+
await manageCommand(hctx);
|
|
16195
|
+
}
|
|
16196
|
+
},
|
|
16197
|
+
{
|
|
16198
|
+
path: ["chat"],
|
|
16199
|
+
description: "Chat with a workflow, member, or existing chat (text, realtime, or voice)",
|
|
16200
|
+
requiresWorkspace: true,
|
|
16201
|
+
mutates: true,
|
|
16202
|
+
args: [
|
|
16203
|
+
{ name: "workflow", description: "Start assignment chat with workflow (by numeric ID)" },
|
|
16204
|
+
{ name: "member", description: "Start DM with a member (by numeric ID)" },
|
|
16205
|
+
{ name: "chat-id", description: "Resume an existing chat by numeric ID" },
|
|
16206
|
+
{ name: "realtime", description: "Use WebSocket realtime connection", isFlag: true },
|
|
16207
|
+
{ name: "voice", description: "Enable voice mode (microphone + speaker)", isFlag: true },
|
|
16208
|
+
{ name: "message", alias: "m", description: "Single-turn: send one message and exit" },
|
|
16209
|
+
{ name: "vad", description: "Voice activity detection: server (default) or client" },
|
|
16210
|
+
{ name: "list-devices", description: "List audio devices and exit", isFlag: true },
|
|
16211
|
+
{ name: "input-device", description: "Microphone device name or ID" },
|
|
16212
|
+
{ name: "output-device", description: "Speaker device name or ID" },
|
|
16213
|
+
{ name: "debug", description: "Enable debug logging", isFlag: true },
|
|
16214
|
+
{
|
|
16215
|
+
name: "auto-approve",
|
|
16216
|
+
description: "Auto-approve all tool calls (skip HITL)",
|
|
16217
|
+
isFlag: true
|
|
16218
|
+
}
|
|
16219
|
+
],
|
|
16220
|
+
handler: async (hctx) => {
|
|
16221
|
+
await chatCommand(hctx);
|
|
16222
|
+
}
|
|
16223
|
+
},
|
|
16224
|
+
{
|
|
16225
|
+
path: ["get-started"],
|
|
16226
|
+
description: "Bootstrap instructions for new users and LLM agents",
|
|
16227
|
+
requiresAuth: false,
|
|
16228
|
+
args: [
|
|
16229
|
+
{
|
|
16230
|
+
name: "format",
|
|
16231
|
+
description: "Output format: llm (agent-readable) or default (human)"
|
|
16232
|
+
},
|
|
16233
|
+
{
|
|
16234
|
+
name: "agent",
|
|
16235
|
+
description: "Alias for --format llm",
|
|
16236
|
+
isFlag: true
|
|
16237
|
+
}
|
|
16238
|
+
],
|
|
16239
|
+
handler: async (hctx) => {
|
|
16240
|
+
await getStartedCommand(hctx);
|
|
16241
|
+
}
|
|
16242
|
+
},
|
|
16243
|
+
{
|
|
16244
|
+
path: ["setup"],
|
|
16245
|
+
description: "Alias for get-started \u2014 bootstrap instructions",
|
|
16246
|
+
requiresAuth: false,
|
|
16247
|
+
args: [
|
|
16248
|
+
{ name: "format", description: "Output format: llm or default" },
|
|
16249
|
+
{ name: "agent", description: "Alias for --format llm", isFlag: true }
|
|
16250
|
+
],
|
|
16251
|
+
handler: async (hctx) => {
|
|
16252
|
+
await getStartedCommand(hctx);
|
|
16253
|
+
}
|
|
16254
|
+
},
|
|
16255
|
+
{
|
|
16256
|
+
path: ["concierge"],
|
|
16257
|
+
description: "Call or SMS your verified phone via the Concierge (server-side)",
|
|
16258
|
+
requiresWorkspace: false,
|
|
16259
|
+
mutates: true,
|
|
16260
|
+
args: [
|
|
16261
|
+
{ name: "call", description: "Ring your verified account phone", isFlag: true },
|
|
16262
|
+
{ name: "sms", description: "Start an SMS thread on your verified phone", isFlag: true }
|
|
16263
|
+
],
|
|
16264
|
+
handler: async (hctx) => {
|
|
16265
|
+
await conciergeOutboundCommand(hctx);
|
|
16266
|
+
}
|
|
16267
|
+
},
|
|
16268
|
+
{
|
|
16269
|
+
path: ["account", "verify-phone"],
|
|
16270
|
+
description: "Verify your account phone for concierge chat --call / --sms",
|
|
16271
|
+
requiresWorkspace: false,
|
|
16272
|
+
mutates: true,
|
|
16273
|
+
args: [{ name: "phone", description: "E.164 phone number (optional; prompts if omitted)" }],
|
|
16274
|
+
handler: async (hctx) => {
|
|
16275
|
+
const { accountVerifyPhoneCommand } = await import("./account-verify-phone-TC73QIIE.js");
|
|
16276
|
+
await accountVerifyPhoneCommand(hctx);
|
|
16277
|
+
}
|
|
16278
|
+
},
|
|
16279
|
+
{
|
|
16280
|
+
path: ["concierge", "chat"],
|
|
16281
|
+
description: "Interactive concierge chat (text, realtime, or voice)",
|
|
16282
|
+
requiresWorkspace: false,
|
|
16283
|
+
mutates: true,
|
|
16284
|
+
args: [
|
|
16285
|
+
{ name: "realtime", description: "Use WebSocket realtime connection", isFlag: true },
|
|
16286
|
+
{ name: "voice", description: "Enable voice mode (microphone + speaker)", isFlag: true },
|
|
16287
|
+
{
|
|
16288
|
+
name: "call",
|
|
16289
|
+
description: "Ring your verified account phone (server-side)",
|
|
16290
|
+
isFlag: true
|
|
16291
|
+
},
|
|
16292
|
+
{
|
|
16293
|
+
name: "sms",
|
|
16294
|
+
description: "SMS your verified account phone (server-side)",
|
|
16295
|
+
isFlag: true
|
|
16296
|
+
},
|
|
16297
|
+
{
|
|
16298
|
+
name: "message",
|
|
16299
|
+
alias: "m",
|
|
16300
|
+
description: 'Single-turn: send one message and exit. With --call: the "why I am calling" anchor for the opening self-introduction.'
|
|
16301
|
+
},
|
|
16302
|
+
{ name: "chat-id", description: "Resume an existing chat by numeric ID" },
|
|
16303
|
+
{ name: "vad", description: "Voice activity detection: server (default) or client" },
|
|
16304
|
+
{ name: "list-devices", description: "List audio devices and exit", isFlag: true },
|
|
16305
|
+
{ name: "input-device", description: "Microphone device name or ID" },
|
|
16306
|
+
{ name: "output-device", description: "Speaker device name or ID" },
|
|
16307
|
+
{ name: "debug", description: "Enable debug logging", isFlag: true },
|
|
16308
|
+
{
|
|
16309
|
+
name: "auto-approve",
|
|
16310
|
+
description: "Auto-approve all tool calls (skip HITL)",
|
|
16311
|
+
isFlag: true
|
|
16312
|
+
},
|
|
16313
|
+
{
|
|
16314
|
+
name: "trust-cwd",
|
|
16315
|
+
description: "Trust files in the current working directory (non-interactive)",
|
|
16316
|
+
isFlag: true
|
|
16317
|
+
},
|
|
16318
|
+
{
|
|
16319
|
+
name: "trust-dir",
|
|
16320
|
+
description: "Trust files in this directory (non-interactive; absolute path)"
|
|
16321
|
+
}
|
|
16322
|
+
],
|
|
16323
|
+
handler: async (hctx) => {
|
|
16324
|
+
await conciergeCommand(hctx);
|
|
16325
|
+
}
|
|
16326
|
+
},
|
|
16327
|
+
{
|
|
16328
|
+
path: ["dev", "import-local"],
|
|
16329
|
+
description: "Import remote workspace locally",
|
|
16330
|
+
requiresWorkspace: true,
|
|
16331
|
+
handler: async ({ ctx, workspace }) => {
|
|
16332
|
+
await devImportLocalCommand(requireAuth(ctx), workspace);
|
|
16333
|
+
}
|
|
16334
|
+
},
|
|
16335
|
+
{
|
|
16336
|
+
path: ["whoami"],
|
|
16337
|
+
description: "Show current user info",
|
|
16338
|
+
handler: async ({ ctx }) => {
|
|
16339
|
+
await whoamiCommand(requireAuth(ctx));
|
|
16340
|
+
}
|
|
16341
|
+
},
|
|
16342
|
+
{
|
|
16343
|
+
path: ["organizations", "list"],
|
|
16344
|
+
description: "List organizations",
|
|
16345
|
+
handler: async ({ ctx }) => {
|
|
16346
|
+
await organizationsListCommand(requireAuth(ctx));
|
|
16347
|
+
}
|
|
16348
|
+
},
|
|
16349
|
+
{
|
|
16350
|
+
path: ["orgs", "list"],
|
|
16351
|
+
description: "List organizations",
|
|
16352
|
+
handler: async ({ ctx }) => {
|
|
16353
|
+
await organizationsListCommand(requireAuth(ctx));
|
|
16354
|
+
}
|
|
16355
|
+
}
|
|
16356
|
+
];
|
|
15779
16357
|
export {
|
|
15780
16358
|
AuthRequiredError,
|
|
15781
16359
|
CLIError,
|
|
@@ -15834,6 +16412,7 @@ export {
|
|
|
15834
16412
|
simpleCrudCommands,
|
|
15835
16413
|
startAccountPhoneVerify,
|
|
15836
16414
|
stderr,
|
|
16415
|
+
topLevelCommands,
|
|
15837
16416
|
unwrapItems,
|
|
15838
16417
|
waitForOutboundPhoneCallOutcome,
|
|
15839
16418
|
whoamiCommand
|