@gravity-rail/cli 0.7.2 → 0.7.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account-verify-phone-TDQNZ5XN.js +41 -0
- package/dist/account-verify-phone-TDQNZ5XN.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-KTTHOL3B.js +758 -0
- package/dist/chunk-KTTHOL3B.js.map +1 -0
- package/dist/{errors-SMY7LZJB.js → errors-VK4QO3QZ.js} +2 -2
- package/dist/gr.js +1725 -467
- package/dist/gr.js.map +1 -1
- package/dist/lib.d.ts +4 -1
- package/dist/lib.js +1077 -1127
- package/dist/lib.js.map +1 -1
- package/package.json +3 -7
- 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-KTTHOL3B.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) {
|
|
@@ -1653,6 +1098,19 @@ 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
|
+
function normalizeChannel(channel) {
|
|
1106
|
+
const aliases = {
|
|
1107
|
+
phone: "phone-voice",
|
|
1108
|
+
voice: "phone-voice",
|
|
1109
|
+
sms: "phone-sms",
|
|
1110
|
+
web: "web-chat"
|
|
1111
|
+
};
|
|
1112
|
+
return aliases[channel] ?? channel;
|
|
1113
|
+
}
|
|
1656
1114
|
var agentsDomain = {
|
|
1657
1115
|
name: "agents",
|
|
1658
1116
|
description: "Manage agents",
|
|
@@ -1669,7 +1127,37 @@ var agentsDomain = {
|
|
|
1669
1127
|
unarchiveMethod: "unarchiveAgent",
|
|
1670
1128
|
columns: COLUMNS,
|
|
1671
1129
|
paginationSupport: "full"
|
|
1672
|
-
})
|
|
1130
|
+
}),
|
|
1131
|
+
{
|
|
1132
|
+
path: ["agents", "resolve-tools"],
|
|
1133
|
+
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.",
|
|
1134
|
+
requiresWorkspace: true,
|
|
1135
|
+
args: [
|
|
1136
|
+
{ name: "id", description: "Agent ID", required: true },
|
|
1137
|
+
{
|
|
1138
|
+
name: "channel",
|
|
1139
|
+
description: "Channel to simulate. Canonical: phone-voice, web-voice, phone-sms, web-chat. Aliases: phone|voice -> phone-voice, sms -> phone-sms, web -> web-chat.",
|
|
1140
|
+
required: true
|
|
1141
|
+
},
|
|
1142
|
+
{
|
|
1143
|
+
name: "task-id",
|
|
1144
|
+
description: "Task ID whose workflow abilities to resolve",
|
|
1145
|
+
required: true
|
|
1146
|
+
}
|
|
1147
|
+
],
|
|
1148
|
+
handler: async ({ ctx, workspace, args }) => {
|
|
1149
|
+
const result2 = await requireAuth(ctx).apiClient.resolveAgentTools(
|
|
1150
|
+
workspace,
|
|
1151
|
+
requireNumber(args, "id"),
|
|
1152
|
+
{
|
|
1153
|
+
channel: normalizeChannel(String(args.channel)),
|
|
1154
|
+
taskId: requireNumber(args, "task-id")
|
|
1155
|
+
}
|
|
1156
|
+
);
|
|
1157
|
+
const tools = result2.tools ?? result2;
|
|
1158
|
+
outputData(Array.isArray(tools) ? tools : [tools], ctx, TOOL_COLUMNS);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1673
1161
|
]
|
|
1674
1162
|
};
|
|
1675
1163
|
|
|
@@ -3421,7 +2909,7 @@ var MESSAGE_COLUMNS4 = [
|
|
|
3421
2909
|
COL.created()
|
|
3422
2910
|
];
|
|
3423
2911
|
var TASK_COLUMNS = [COL.id(), COL.name(), COL.status()];
|
|
3424
|
-
var
|
|
2912
|
+
var TOOL_COLUMNS2 = [
|
|
3425
2913
|
COL.name(),
|
|
3426
2914
|
{ header: "DESCRIPTION", getValue: (item) => (item.description ?? "").slice(0, 60) }
|
|
3427
2915
|
];
|
|
@@ -3529,7 +3017,7 @@ var assignmentsDomain = {
|
|
|
3529
3017
|
requireNumber(args, "assignment-id")
|
|
3530
3018
|
);
|
|
3531
3019
|
const tools = result2.tools ?? result2;
|
|
3532
|
-
outputData(Array.isArray(tools) ? tools : [tools], ctx,
|
|
3020
|
+
outputData(Array.isArray(tools) ? tools : [tools], ctx, TOOL_COLUMNS2);
|
|
3533
3021
|
}
|
|
3534
3022
|
}
|
|
3535
3023
|
]
|
|
@@ -5938,6 +5426,7 @@ var accessGrantsDomain = {
|
|
|
5938
5426
|
{
|
|
5939
5427
|
path: ["access-grants", "resolve"],
|
|
5940
5428
|
description: "Resolve an access grant",
|
|
5429
|
+
mutates: true,
|
|
5941
5430
|
requiresWorkspace: true,
|
|
5942
5431
|
args: [{ name: "id", description: "Access grant ID", required: true }],
|
|
5943
5432
|
handler: async ({ ctx, workspace, args, data }) => {
|
|
@@ -6485,119 +5974,6 @@ var billingPortalDomain = {
|
|
|
6485
5974
|
commands
|
|
6486
5975
|
};
|
|
6487
5976
|
|
|
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
5977
|
// src/domains/concierge.ts
|
|
6602
5978
|
var CHAT_COLUMNS = [
|
|
6603
5979
|
COL.id(),
|
|
@@ -6642,7 +6018,7 @@ var conciergeDomain = {
|
|
|
6642
6018
|
|
|
6643
6019
|
// src/domains/custom-toolkits.ts
|
|
6644
6020
|
var COLUMNS17 = [COL.id(), COL.name(), COL.description(), COL.enabled()];
|
|
6645
|
-
var
|
|
6021
|
+
var TOOL_COLUMNS3 = [COL.id(), COL.name(), COL.description()];
|
|
6646
6022
|
var customToolkitsDomain = {
|
|
6647
6023
|
name: "custom-toolkits",
|
|
6648
6024
|
description: "Manage custom toolkits and their tools",
|
|
@@ -6700,7 +6076,7 @@ var customToolkitsDomain = {
|
|
|
6700
6076
|
toolkitId,
|
|
6701
6077
|
data
|
|
6702
6078
|
);
|
|
6703
|
-
outputSingle(result2, ctx,
|
|
6079
|
+
outputSingle(result2, ctx, TOOL_COLUMNS3);
|
|
6704
6080
|
}
|
|
6705
6081
|
},
|
|
6706
6082
|
{
|
|
@@ -6722,7 +6098,7 @@ var customToolkitsDomain = {
|
|
|
6722
6098
|
toolId,
|
|
6723
6099
|
data
|
|
6724
6100
|
);
|
|
6725
|
-
outputSingle(result2, ctx,
|
|
6101
|
+
outputSingle(result2, ctx, TOOL_COLUMNS3);
|
|
6726
6102
|
}
|
|
6727
6103
|
},
|
|
6728
6104
|
{
|
|
@@ -7935,6 +7311,50 @@ function parseCallerRole(value) {
|
|
|
7935
7311
|
}
|
|
7936
7312
|
return VALID_CALLER_ROLES.has(value) ? value : void 0;
|
|
7937
7313
|
}
|
|
7314
|
+
var PERMISSION_USE = 1;
|
|
7315
|
+
function evaluateWorkflowMemberRoleAccess(memberRoleId, memberRoleAccess) {
|
|
7316
|
+
if (memberRoleAccess === void 0) {
|
|
7317
|
+
return { outcome: "skip", detail: "workflow memberRoleAccess not available" };
|
|
7318
|
+
}
|
|
7319
|
+
if (memberRoleAccess.length === 0) {
|
|
7320
|
+
return { outcome: "allow", detail: "no workflow role restrictions configured" };
|
|
7321
|
+
}
|
|
7322
|
+
if (memberRoleId === void 0) {
|
|
7323
|
+
return {
|
|
7324
|
+
outcome: "skip",
|
|
7325
|
+
detail: "caller member role unknown \u2014 cannot evaluate role gate"
|
|
7326
|
+
};
|
|
7327
|
+
}
|
|
7328
|
+
const hasUse = memberRoleAccess.some(
|
|
7329
|
+
(entry) => entry.memberRoleId === memberRoleId && (entry.accessMode & PERMISSION_USE) === PERMISSION_USE
|
|
7330
|
+
);
|
|
7331
|
+
if (hasUse) {
|
|
7332
|
+
return { outcome: "allow", detail: `memberRoleId=${memberRoleId} has USE on workflow` };
|
|
7333
|
+
}
|
|
7334
|
+
return {
|
|
7335
|
+
outcome: "deny",
|
|
7336
|
+
detail: `memberRoleId=${memberRoleId} lacks USE in workflow memberRoleAccess`
|
|
7337
|
+
};
|
|
7338
|
+
}
|
|
7339
|
+
function parseWorkflowMemberRoleAccess(workflow) {
|
|
7340
|
+
const raw = workflow.memberRoleAccess;
|
|
7341
|
+
if (!Array.isArray(raw)) {
|
|
7342
|
+
return [];
|
|
7343
|
+
}
|
|
7344
|
+
const entries = [];
|
|
7345
|
+
for (const item of raw) {
|
|
7346
|
+
if (!item || typeof item !== "object") {
|
|
7347
|
+
continue;
|
|
7348
|
+
}
|
|
7349
|
+
const row = item;
|
|
7350
|
+
const memberRoleId = typeof row.memberRoleId === "number" ? row.memberRoleId : Number(row.memberRoleId);
|
|
7351
|
+
const accessMode = typeof row.accessMode === "number" ? row.accessMode : Number(row.accessMode);
|
|
7352
|
+
if (Number.isFinite(memberRoleId) && Number.isFinite(accessMode)) {
|
|
7353
|
+
entries.push({ memberRoleId, accessMode });
|
|
7354
|
+
}
|
|
7355
|
+
}
|
|
7356
|
+
return entries;
|
|
7357
|
+
}
|
|
7938
7358
|
function inferCallerRole(phone, opts) {
|
|
7939
7359
|
if (opts.asRole) {
|
|
7940
7360
|
return opts.asRole;
|
|
@@ -8061,6 +7481,32 @@ function simulateInboundRouting(phone, opts = {}) {
|
|
|
8061
7481
|
return finish("no_inbound_workflow", false, true, steps, recommendations, callerRole);
|
|
8062
7482
|
}
|
|
8063
7483
|
steps.push({ step: "inbound_workflow", outcome: "ok", detail: `workflowId=${workflowId}` });
|
|
7484
|
+
const callerMemberRoleId = callerRole === "member" ? opts.matchedMemberRoleId : callerRole === "anonymous" ? phone.anonymousMemberRoleId ?? void 0 : void 0;
|
|
7485
|
+
const roleAccess = evaluateWorkflowMemberRoleAccess(
|
|
7486
|
+
callerMemberRoleId,
|
|
7487
|
+
opts.workflowMemberRoleAccess
|
|
7488
|
+
);
|
|
7489
|
+
if (roleAccess.outcome === "deny") {
|
|
7490
|
+
steps.push({
|
|
7491
|
+
step: "workflow_member_role_access",
|
|
7492
|
+
outcome: "reject",
|
|
7493
|
+
detail: roleAccess.detail
|
|
7494
|
+
});
|
|
7495
|
+
recommendations.push(
|
|
7496
|
+
"Grant the caller's member role USE access on the inbound workflow (workflow memberRoleAccess or `gr member-roles update` workflowAccess)"
|
|
7497
|
+
);
|
|
7498
|
+
return finish("workflow_member_role_denied", false, true, steps, recommendations, callerRole);
|
|
7499
|
+
}
|
|
7500
|
+
steps.push({
|
|
7501
|
+
step: "workflow_member_role_access",
|
|
7502
|
+
outcome: roleAccess.outcome === "skip" ? "skipped" : "ok",
|
|
7503
|
+
detail: roleAccess.detail
|
|
7504
|
+
});
|
|
7505
|
+
if (roleAccess.outcome === "skip" && callerMemberRoleId === void 0 && opts.workflowMemberRoleAccess !== void 0 && opts.workflowMemberRoleAccess.length > 0) {
|
|
7506
|
+
recommendations.push(
|
|
7507
|
+
"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"
|
|
7508
|
+
);
|
|
7509
|
+
}
|
|
8064
7510
|
steps.push({ step: "chat_create", outcome: "create_phone_voice_chat" });
|
|
8065
7511
|
steps.push({ step: "assistant", outcome: "connect_media_stream" });
|
|
8066
7512
|
return finish("ai_voice_chat", true, true, steps, recommendations, callerRole);
|
|
@@ -8077,6 +7523,7 @@ function finish(code, createsChat, createsPhoneCall, steps, recommendations, cal
|
|
|
8077
7523
|
voicemail_record: "Record voicemail",
|
|
8078
7524
|
play_message_hangup: "Play off-hours message, hang up",
|
|
8079
7525
|
no_inbound_workflow: "Play unavailable message (no inbound workflow)",
|
|
7526
|
+
workflow_member_role_denied: "Reject \u2014 caller member role lacks workflow USE (memberRoleAccess)",
|
|
8080
7527
|
ai_voice_chat: "Create chat + connect AI voice"
|
|
8081
7528
|
};
|
|
8082
7529
|
return {
|
|
@@ -10488,7 +9935,9 @@ function normalizeCallerE164(phone) {
|
|
|
10488
9935
|
var MEMBER_SEARCH_PAGE_SIZE = 100;
|
|
10489
9936
|
async function resolveMemberByCallerPhone(client, workspace, callerE164) {
|
|
10490
9937
|
const normalized = normalizeCallerE164(callerE164);
|
|
10491
|
-
const
|
|
9938
|
+
const digits = callerE164.replace(/\D/g, "");
|
|
9939
|
+
const nsn = digits.length === 11 && digits.startsWith("1") ? digits.slice(1) : digits;
|
|
9940
|
+
const candidates = [normalized, callerE164.trim(), digits, nsn].filter(
|
|
10492
9941
|
(v, i, arr) => Boolean(v) && arr.indexOf(v) === i
|
|
10493
9942
|
);
|
|
10494
9943
|
const matchInRows = (rows) => {
|
|
@@ -10533,6 +9982,25 @@ async function resolveMemberByCallerPhone(client, workspace, callerE164) {
|
|
|
10533
9982
|
return void 0;
|
|
10534
9983
|
}
|
|
10535
9984
|
|
|
9985
|
+
// src/lib/phone-numbers/workflow-role-access.ts
|
|
9986
|
+
async function loadInboundWorkflowMemberRoleAccess(client, workspace, defaultWorkflowId) {
|
|
9987
|
+
const workflowId = typeof defaultWorkflowId === "number" ? defaultWorkflowId : defaultWorkflowId === null || defaultWorkflowId === void 0 ? void 0 : Number(defaultWorkflowId);
|
|
9988
|
+
if (workflowId === void 0 || !Number.isFinite(workflowId)) {
|
|
9989
|
+
return void 0;
|
|
9990
|
+
}
|
|
9991
|
+
try {
|
|
9992
|
+
const workflow = await client.getWorkflow(workspace, workflowId);
|
|
9993
|
+
return parseWorkflowMemberRoleAccess(workflow);
|
|
9994
|
+
} catch (err) {
|
|
9995
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
9996
|
+
process.stderr.write(
|
|
9997
|
+
`[diagnose] Could not fetch workflow ${workflowId} memberRoleAccess \u2014 role gate will be skipped (${reason})
|
|
9998
|
+
`
|
|
9999
|
+
);
|
|
10000
|
+
return void 0;
|
|
10001
|
+
}
|
|
10002
|
+
}
|
|
10003
|
+
|
|
10536
10004
|
// src/domains/phone-numbers.ts
|
|
10537
10005
|
function asPhoneRecord(value) {
|
|
10538
10006
|
return value;
|
|
@@ -10591,9 +10059,8 @@ var phoneNumbersDomain = {
|
|
|
10591
10059
|
const client = requireAuth(ctx).apiClient;
|
|
10592
10060
|
const id = requireNumber(args, "id");
|
|
10593
10061
|
const phone = asPhoneRecord(await client.getPhoneNumber(workspace, id));
|
|
10594
|
-
if (args.raw) {
|
|
10595
|
-
|
|
10596
|
-
`);
|
|
10062
|
+
if (args.raw || ctx.outputFormat !== "table") {
|
|
10063
|
+
outputSingle(phone, ctx);
|
|
10597
10064
|
return;
|
|
10598
10065
|
}
|
|
10599
10066
|
const outboundUsers = await findOutboundPhoneUsers(client, workspace, phone);
|
|
@@ -10767,21 +10234,34 @@ Setup complete: id=${phone.id} uuid=${phone.uuid}
|
|
|
10767
10234
|
const asRole = parseCallerRole(args["as-role"]);
|
|
10768
10235
|
let hasMemberMatch = asRole === "member";
|
|
10769
10236
|
let matchedMemberId;
|
|
10237
|
+
let matchedMemberRoleId;
|
|
10770
10238
|
let matchedMemberPhoneType;
|
|
10771
10239
|
if (!asRole && typeof args.as === "string") {
|
|
10772
10240
|
const match = await resolveMemberByCallerPhone(client, workspace, args.as);
|
|
10773
10241
|
if (match) {
|
|
10774
10242
|
hasMemberMatch = true;
|
|
10775
10243
|
matchedMemberId = match.memberId;
|
|
10244
|
+
matchedMemberRoleId = match.memberRoleId;
|
|
10776
10245
|
matchedMemberPhoneType = match.phoneType;
|
|
10777
10246
|
}
|
|
10778
10247
|
}
|
|
10248
|
+
const workflowMemberRoleAccess = await loadInboundWorkflowMemberRoleAccess(
|
|
10249
|
+
client,
|
|
10250
|
+
workspace,
|
|
10251
|
+
phone.defaultWorkflowId
|
|
10252
|
+
);
|
|
10779
10253
|
const sim = simulateInboundRouting(phone, {
|
|
10780
10254
|
asRole,
|
|
10781
10255
|
hasMemberMatch,
|
|
10782
10256
|
matchedMemberId,
|
|
10783
|
-
|
|
10257
|
+
matchedMemberRoleId,
|
|
10258
|
+
matchedMemberPhoneType,
|
|
10259
|
+
workflowMemberRoleAccess
|
|
10784
10260
|
});
|
|
10261
|
+
if (ctx.outputFormat !== "table") {
|
|
10262
|
+
outputSingle({ phoneNumberId: id, simulation: sim }, ctx);
|
|
10263
|
+
return;
|
|
10264
|
+
}
|
|
10785
10265
|
process.stdout.write(`${formatRoutingSimulation(sim, phone)}
|
|
10786
10266
|
`);
|
|
10787
10267
|
}
|
|
@@ -10803,10 +10283,28 @@ Setup complete: id=${phone.id} uuid=${phone.uuid}
|
|
|
10803
10283
|
const id = requireNumber(args, "id");
|
|
10804
10284
|
const role = requireCallerRole(args, "as-role");
|
|
10805
10285
|
const phone = asPhoneRecord(await client.getPhoneNumber(workspace, id));
|
|
10286
|
+
const workflowMemberRoleAccess = await loadInboundWorkflowMemberRoleAccess(
|
|
10287
|
+
client,
|
|
10288
|
+
workspace,
|
|
10289
|
+
phone.defaultWorkflowId
|
|
10290
|
+
);
|
|
10806
10291
|
const sim = simulateInboundRouting(phone, {
|
|
10807
10292
|
asRole: role,
|
|
10808
|
-
hasMemberMatch: role === "member"
|
|
10293
|
+
hasMemberMatch: role === "member",
|
|
10294
|
+
workflowMemberRoleAccess
|
|
10809
10295
|
});
|
|
10296
|
+
if (ctx.outputFormat !== "table") {
|
|
10297
|
+
outputSingle(
|
|
10298
|
+
{
|
|
10299
|
+
phoneNumberId: id,
|
|
10300
|
+
callerRole: role,
|
|
10301
|
+
finalBehavior: sim.finalBehavior,
|
|
10302
|
+
simulation: sim
|
|
10303
|
+
},
|
|
10304
|
+
ctx
|
|
10305
|
+
);
|
|
10306
|
+
return;
|
|
10307
|
+
}
|
|
10810
10308
|
process.stdout.write(
|
|
10811
10309
|
`If a caller in role "${role}" dials this line right now:
|
|
10812
10310
|
\u2192 ${sim.finalBehavior}
|
|
@@ -10832,17 +10330,26 @@ ${formatRoutingSimulation(sim, phone)}
|
|
|
10832
10330
|
const asRole = parseCallerRole(args["as-role"]);
|
|
10833
10331
|
let hasMemberMatch = asRole === "member";
|
|
10834
10332
|
let matchedMemberId;
|
|
10333
|
+
let matchedMemberRoleId;
|
|
10835
10334
|
if (!asRole && typeof args.as === "string") {
|
|
10836
10335
|
const match = await resolveMemberByCallerPhone(client, workspace, args.as);
|
|
10837
10336
|
if (match) {
|
|
10838
10337
|
hasMemberMatch = true;
|
|
10839
10338
|
matchedMemberId = match.memberId;
|
|
10339
|
+
matchedMemberRoleId = match.memberRoleId;
|
|
10840
10340
|
}
|
|
10841
10341
|
}
|
|
10342
|
+
const workflowMemberRoleAccess = await loadInboundWorkflowMemberRoleAccess(
|
|
10343
|
+
client,
|
|
10344
|
+
workspace,
|
|
10345
|
+
phone.defaultWorkflowId
|
|
10346
|
+
);
|
|
10842
10347
|
const sim = simulateInboundRouting(phone, {
|
|
10843
10348
|
asRole,
|
|
10844
10349
|
hasMemberMatch,
|
|
10845
|
-
matchedMemberId
|
|
10350
|
+
matchedMemberId,
|
|
10351
|
+
matchedMemberRoleId,
|
|
10352
|
+
workflowMemberRoleAccess
|
|
10846
10353
|
});
|
|
10847
10354
|
process.stdout.write(
|
|
10848
10355
|
`${JSON.stringify({ phoneNumberId: id, simulation: sim }, null, 2)}
|
|
@@ -10886,11 +10393,15 @@ ${formatRoutingSimulation(sim, phone)}
|
|
|
10886
10393
|
{ inbound: true }
|
|
10887
10394
|
);
|
|
10888
10395
|
const inbound = result2.items ?? [];
|
|
10889
|
-
|
|
10396
|
+
if (ctx.outputFormat === "table") {
|
|
10397
|
+
process.stdout.write(`Inbound calls (most recent page): ${inbound.length} shown
|
|
10890
10398
|
`);
|
|
10891
|
-
|
|
10892
|
-
|
|
10893
|
-
|
|
10399
|
+
process.stdout.write(
|
|
10400
|
+
"Tip: gr phone-numbers diagnose --id <id> --as +1\u2026 simulates routing without a live call.\n"
|
|
10401
|
+
);
|
|
10402
|
+
} else {
|
|
10403
|
+
stderr(`Inbound calls (most recent page): ${inbound.length} shown`);
|
|
10404
|
+
}
|
|
10894
10405
|
outputData(inbound, ctx);
|
|
10895
10406
|
}
|
|
10896
10407
|
},
|
|
@@ -13169,7 +12680,7 @@ function registerPublicDomains(registry) {
|
|
|
13169
12680
|
}
|
|
13170
12681
|
|
|
13171
12682
|
// src/commands/auth-browser.ts
|
|
13172
|
-
import { execFile
|
|
12683
|
+
import { execFile } from "child_process";
|
|
13173
12684
|
async function authBrowserCommand(ctx, args) {
|
|
13174
12685
|
const noOpen = Boolean(args["no-open"]);
|
|
13175
12686
|
const account = await ctx.apiClient.getUserAccount();
|
|
@@ -13205,7 +12716,7 @@ function openUrl(url) {
|
|
|
13205
12716
|
command = "xdg-open";
|
|
13206
12717
|
args = [url];
|
|
13207
12718
|
}
|
|
13208
|
-
|
|
12719
|
+
execFile(command, args, (err) => {
|
|
13209
12720
|
if (err) {
|
|
13210
12721
|
process.stderr.write("Warning: Could not open browser automatically\n");
|
|
13211
12722
|
}
|
|
@@ -13418,7 +12929,7 @@ async function resolveDirectoryTrust(opts) {
|
|
|
13418
12929
|
return "trusted";
|
|
13419
12930
|
}
|
|
13420
12931
|
async function requireDirectoryTrust(opts) {
|
|
13421
|
-
const { CLIError: CLIError2 } = await import("./errors-
|
|
12932
|
+
const { CLIError: CLIError2 } = await import("./errors-VK4QO3QZ.js");
|
|
13422
12933
|
const dir = path3.resolve(opts.trustDir ?? opts.cwd ?? process.cwd());
|
|
13423
12934
|
const status = await resolveDirectoryTrust(opts);
|
|
13424
12935
|
if (status === "trusted") return;
|
|
@@ -13541,13 +13052,19 @@ async function runConciergeAguiLoop(client, initialMessages, onEvent, opts) {
|
|
|
13541
13052
|
if (!hitlPayload) {
|
|
13542
13053
|
break;
|
|
13543
13054
|
}
|
|
13544
|
-
|
|
13545
|
-
|
|
13546
|
-
|
|
13547
|
-
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13055
|
+
opts.onHitlPromptStart?.();
|
|
13056
|
+
let next;
|
|
13057
|
+
try {
|
|
13058
|
+
next = await handleCliHitlInterrupt(hitlPayload, {
|
|
13059
|
+
allowWrite: opts.allowWrite,
|
|
13060
|
+
autoApprove: opts.autoApprove,
|
|
13061
|
+
trustCwd: opts.trustCwd,
|
|
13062
|
+
trustDir: opts.trustDir,
|
|
13063
|
+
nonInteractive: opts.nonInteractive
|
|
13064
|
+
});
|
|
13065
|
+
} finally {
|
|
13066
|
+
opts.onHitlPromptEnd?.();
|
|
13067
|
+
}
|
|
13551
13068
|
if (!next) {
|
|
13552
13069
|
process.stderr.write(
|
|
13553
13070
|
" [HITL] Unsupported interrupt in CLI \u2014 open app.gravityrail.com/setup to continue.\n"
|
|
@@ -13560,9 +13077,6 @@ async function runConciergeAguiLoop(client, initialMessages, onEvent, opts) {
|
|
|
13560
13077
|
return { streamText, hasToolCalls };
|
|
13561
13078
|
}
|
|
13562
13079
|
|
|
13563
|
-
// src/lib/chat-session.ts
|
|
13564
|
-
import * as readline4 from "readline";
|
|
13565
|
-
|
|
13566
13080
|
// src/lib/realtime-client.ts
|
|
13567
13081
|
import { EventEmitter } from "events";
|
|
13568
13082
|
var RealtimeClient = class extends EventEmitter {
|
|
@@ -13822,6 +13336,19 @@ function isToolCall(item) {
|
|
|
13822
13336
|
function isToolResult(item) {
|
|
13823
13337
|
return "kind" in item && item.kind === "tool_result";
|
|
13824
13338
|
}
|
|
13339
|
+
function toolResultLooksFailed(content) {
|
|
13340
|
+
const trimmed = content.trim();
|
|
13341
|
+
if (!trimmed) return false;
|
|
13342
|
+
if (/^error[:\s]/i.test(trimmed)) return true;
|
|
13343
|
+
try {
|
|
13344
|
+
const parsed = JSON.parse(trimmed);
|
|
13345
|
+
if (parsed.ok === false) return true;
|
|
13346
|
+
if (typeof parsed.error === "string" && parsed.error.length > 0) return true;
|
|
13347
|
+
if (typeof parsed.status === "string" && parsed.status.toLowerCase() === "error") return true;
|
|
13348
|
+
} catch {
|
|
13349
|
+
}
|
|
13350
|
+
return false;
|
|
13351
|
+
}
|
|
13825
13352
|
function getTruncationLabel(reason, truncatedAtMs) {
|
|
13826
13353
|
const timeStr = truncatedAtMs ? ` at ${(truncatedAtMs / 1e3).toFixed(1)}s` : "";
|
|
13827
13354
|
switch (reason) {
|
|
@@ -13855,6 +13382,8 @@ var CLEAR_EOL = `${CSI}K`;
|
|
|
13855
13382
|
var RESET = `${CSI}0m`;
|
|
13856
13383
|
var MOUSE_ON = `${CSI}?1000h${CSI}?1006h`;
|
|
13857
13384
|
var MOUSE_OFF = `${CSI}?1000l${CSI}?1006l`;
|
|
13385
|
+
var BRACKETED_PASTE_ON = `${CSI}?2004h`;
|
|
13386
|
+
var BRACKETED_PASTE_OFF = `${CSI}?2004l`;
|
|
13858
13387
|
var BLUE = `${CSI}34m`;
|
|
13859
13388
|
var GREEN = `${CSI}32m`;
|
|
13860
13389
|
var GRAY = `${CSI}90m`;
|
|
@@ -13863,6 +13392,11 @@ var ORANGE = `${CSI}38;5;208m`;
|
|
|
13863
13392
|
var LIGHT_BLUE = `${CSI}38;5;117m`;
|
|
13864
13393
|
var DIM = `${CSI}2m`;
|
|
13865
13394
|
var DIM_ITALIC_GRAY = `${CSI}2;3;90m`;
|
|
13395
|
+
var RED = `${CSI}31m`;
|
|
13396
|
+
var CYAN = `${CSI}36m`;
|
|
13397
|
+
var MAX_INPUT_ROWS = 6;
|
|
13398
|
+
var INPUT_LABEL = "You: ";
|
|
13399
|
+
var INPUT_LABEL_WIDTH = 5;
|
|
13866
13400
|
function moveTo(row, col) {
|
|
13867
13401
|
return `${CSI}${row};${col}H`;
|
|
13868
13402
|
}
|
|
@@ -13887,6 +13421,11 @@ var ChatTUI = class {
|
|
|
13887
13421
|
// Escape sequence accumulator for raw stdin parsing
|
|
13888
13422
|
escBuf = "";
|
|
13889
13423
|
escTimer = null;
|
|
13424
|
+
// Run / inference status (title bar)
|
|
13425
|
+
runState = "idle";
|
|
13426
|
+
activeToolName = "";
|
|
13427
|
+
pausedForPrompt = false;
|
|
13428
|
+
turnInProgress = false;
|
|
13890
13429
|
constructor(options) {
|
|
13891
13430
|
this.options = options;
|
|
13892
13431
|
this.debugPanelVisible = !!options.debug;
|
|
@@ -13898,8 +13437,8 @@ var ChatTUI = class {
|
|
|
13898
13437
|
// ─── Lifecycle ──────────────────────────────────────────────────────
|
|
13899
13438
|
start() {
|
|
13900
13439
|
this.updateDimensions();
|
|
13901
|
-
process.stdout.write(ALT_SCREEN_ON + MOUSE_ON);
|
|
13902
|
-
|
|
13440
|
+
process.stdout.write(ALT_SCREEN_ON + MOUSE_ON + BRACKETED_PASTE_ON);
|
|
13441
|
+
this.applyScrollRegion();
|
|
13903
13442
|
process.stdin.setEncoding("utf8");
|
|
13904
13443
|
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
13905
13444
|
process.stdin.on("data", this.onData);
|
|
@@ -13942,6 +13481,7 @@ var ChatTUI = class {
|
|
|
13942
13481
|
streamStart() {
|
|
13943
13482
|
this.isStreaming = true;
|
|
13944
13483
|
this.streamContent = "";
|
|
13484
|
+
this.runState = "streaming";
|
|
13945
13485
|
this.scrollOffset = 0;
|
|
13946
13486
|
this.dirty();
|
|
13947
13487
|
}
|
|
@@ -13957,8 +13497,42 @@ var ChatTUI = class {
|
|
|
13957
13497
|
this.streamContent = "";
|
|
13958
13498
|
this.dirty();
|
|
13959
13499
|
}
|
|
13500
|
+
runStarted() {
|
|
13501
|
+
this.turnInProgress = true;
|
|
13502
|
+
this.runState = "thinking";
|
|
13503
|
+
this.activeToolName = "";
|
|
13504
|
+
this.dirty();
|
|
13505
|
+
}
|
|
13506
|
+
runFinished() {
|
|
13507
|
+
this.turnInProgress = false;
|
|
13508
|
+
this.runState = "idle";
|
|
13509
|
+
this.activeToolName = "";
|
|
13510
|
+
this.dirty();
|
|
13511
|
+
}
|
|
13512
|
+
/** Temporarily leave alt-screen for an external prompt (HITL). */
|
|
13513
|
+
pauseForPrompt() {
|
|
13514
|
+
if (!this.started || this.pausedForPrompt) return;
|
|
13515
|
+
this.pausedForPrompt = true;
|
|
13516
|
+
process.stdin.removeListener("data", this.onData);
|
|
13517
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
13518
|
+
process.stdout.write(
|
|
13519
|
+
`${MOUSE_OFF}${BRACKETED_PASTE_OFF}${CSI}r${ALT_SCREEN_OFF}${SHOW_CURSOR}`
|
|
13520
|
+
);
|
|
13521
|
+
}
|
|
13522
|
+
/** Re-enter alt-screen after an external prompt. */
|
|
13523
|
+
resumeAfterPrompt() {
|
|
13524
|
+
if (!this.started || !this.pausedForPrompt) return;
|
|
13525
|
+
this.pausedForPrompt = false;
|
|
13526
|
+
process.stdout.write(ALT_SCREEN_ON + MOUSE_ON + BRACKETED_PASTE_ON);
|
|
13527
|
+
this.applyScrollRegion();
|
|
13528
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
13529
|
+
process.stdin.on("data", this.onData);
|
|
13530
|
+
this.render();
|
|
13531
|
+
}
|
|
13960
13532
|
toolCallStart(id, name) {
|
|
13961
13533
|
this.items.push({ kind: "tool_call", id, name, args: "", done: false });
|
|
13534
|
+
this.runState = "tool_running";
|
|
13535
|
+
this.activeToolName = name;
|
|
13962
13536
|
this.dirty();
|
|
13963
13537
|
}
|
|
13964
13538
|
toolCallArgs(id, delta) {
|
|
@@ -14011,7 +13585,12 @@ var ChatTUI = class {
|
|
|
14011
13585
|
this.dirty();
|
|
14012
13586
|
}
|
|
14013
13587
|
toolCallResult(toolCallId, toolName, content) {
|
|
14014
|
-
|
|
13588
|
+
const failed = toolResultLooksFailed(content);
|
|
13589
|
+
this.items.push({ kind: "tool_result", toolCallId, toolName, content, failed });
|
|
13590
|
+
if (!this.isStreaming) {
|
|
13591
|
+
this.runState = "thinking";
|
|
13592
|
+
this.activeToolName = "";
|
|
13593
|
+
}
|
|
14015
13594
|
this.dirty();
|
|
14016
13595
|
}
|
|
14017
13596
|
/** Find the tool name for a given tool call ID */
|
|
@@ -14064,7 +13643,9 @@ var ChatTUI = class {
|
|
|
14064
13643
|
this.scheduleRender();
|
|
14065
13644
|
}
|
|
14066
13645
|
restoreTerminal = () => {
|
|
14067
|
-
process.stdout.write(
|
|
13646
|
+
process.stdout.write(
|
|
13647
|
+
`${MOUSE_OFF}${BRACKETED_PASTE_OFF}${CSI}r${ALT_SCREEN_OFF}${SHOW_CURSOR}`
|
|
13648
|
+
);
|
|
14068
13649
|
};
|
|
14069
13650
|
updateDimensions() {
|
|
14070
13651
|
this.rows = process.stdout.rows || 24;
|
|
@@ -14080,11 +13661,32 @@ var ChatTUI = class {
|
|
|
14080
13661
|
if (!this.debugVisible) return 0;
|
|
14081
13662
|
return this.cols - this.chatWidth() - 1;
|
|
14082
13663
|
}
|
|
13664
|
+
titleRows() {
|
|
13665
|
+
return this.options.title || this.options.subtitle ? 1 : 0;
|
|
13666
|
+
}
|
|
13667
|
+
inputRows() {
|
|
13668
|
+
const lines = this.inputBuffer.split("\n").length;
|
|
13669
|
+
return Math.min(MAX_INPUT_ROWS, Math.max(1, lines));
|
|
13670
|
+
}
|
|
13671
|
+
footerRows() {
|
|
13672
|
+
return 1 + this.inputRows();
|
|
13673
|
+
}
|
|
13674
|
+
messageAreaTop() {
|
|
13675
|
+
return 1 + this.titleRows();
|
|
13676
|
+
}
|
|
13677
|
+
messageAreaBottom() {
|
|
13678
|
+
return this.rows - this.footerRows();
|
|
13679
|
+
}
|
|
13680
|
+
applyScrollRegion() {
|
|
13681
|
+
const top = this.messageAreaTop();
|
|
13682
|
+
const bottom = Math.max(top, this.messageAreaBottom());
|
|
13683
|
+
process.stdout.write(`${CSI}${top};${bottom}r`);
|
|
13684
|
+
}
|
|
14083
13685
|
scrollHeight() {
|
|
14084
|
-
return Math.max(1, this.
|
|
13686
|
+
return Math.max(1, this.messageAreaBottom() - this.messageAreaTop() + 1);
|
|
14085
13687
|
}
|
|
14086
13688
|
pageSize() {
|
|
14087
|
-
return Math.max(1, this.
|
|
13689
|
+
return Math.max(1, this.scrollHeight() - 2);
|
|
14088
13690
|
}
|
|
14089
13691
|
// ─── Scroll ───────────────────────────────────────────────────────
|
|
14090
13692
|
scrollUp(lines) {
|
|
@@ -14150,6 +13752,11 @@ var ChatTUI = class {
|
|
|
14150
13752
|
}
|
|
14151
13753
|
/** Handle a parsed CSI sequence: params is everything between \x1B[ and the final char */
|
|
14152
13754
|
handleCSI(params, code) {
|
|
13755
|
+
if (params === "201~") {
|
|
13756
|
+
this.scrollToBottom();
|
|
13757
|
+
this.dirty();
|
|
13758
|
+
return;
|
|
13759
|
+
}
|
|
14153
13760
|
if (params.startsWith("<") && (code === "M" || code === "m")) {
|
|
14154
13761
|
if (code === "m") return;
|
|
14155
13762
|
const parts = params.slice(1).split(";");
|
|
@@ -14231,22 +13838,31 @@ var ChatTUI = class {
|
|
|
14231
13838
|
this.handleEnter();
|
|
14232
13839
|
return;
|
|
14233
13840
|
}
|
|
13841
|
+
if (code === 10) {
|
|
13842
|
+
this.inputBuffer += "\n";
|
|
13843
|
+
this.dirty();
|
|
13844
|
+
return;
|
|
13845
|
+
}
|
|
14234
13846
|
if (code === 127 || code === 8) {
|
|
14235
13847
|
if (this.inputBuffer.length > 0) {
|
|
14236
13848
|
this.inputBuffer = this.inputBuffer.slice(0, -1);
|
|
14237
|
-
this.
|
|
13849
|
+
this.dirty();
|
|
14238
13850
|
}
|
|
14239
13851
|
return;
|
|
14240
13852
|
}
|
|
14241
13853
|
if (code < 32) return;
|
|
14242
13854
|
this.inputBuffer += ch;
|
|
14243
|
-
this.
|
|
13855
|
+
this.dirty();
|
|
14244
13856
|
}
|
|
14245
13857
|
handleEnter() {
|
|
13858
|
+
if (this.turnInProgress) {
|
|
13859
|
+
this.dirty();
|
|
13860
|
+
return;
|
|
13861
|
+
}
|
|
14246
13862
|
const text = this.inputBuffer.trim();
|
|
14247
13863
|
this.inputBuffer = "";
|
|
14248
13864
|
if (!text) {
|
|
14249
|
-
this.
|
|
13865
|
+
this.dirty();
|
|
14250
13866
|
return;
|
|
14251
13867
|
}
|
|
14252
13868
|
if (text.toLowerCase() === "exit" || text.toLowerCase() === "quit") {
|
|
@@ -14263,7 +13879,7 @@ var ChatTUI = class {
|
|
|
14263
13879
|
// ─── Events ───────────────────────────────────────────────────────
|
|
14264
13880
|
onResize = () => {
|
|
14265
13881
|
this.updateDimensions();
|
|
14266
|
-
|
|
13882
|
+
this.applyScrollRegion();
|
|
14267
13883
|
this.displayLinesCache = null;
|
|
14268
13884
|
this.debugLinesCache = null;
|
|
14269
13885
|
this.render();
|
|
@@ -14305,19 +13921,24 @@ var ChatTUI = class {
|
|
|
14305
13921
|
visible = displayLines.slice(start, end);
|
|
14306
13922
|
}
|
|
14307
13923
|
const buf = [HIDE_CURSOR];
|
|
13924
|
+
const msgTop = this.messageAreaTop();
|
|
13925
|
+
if (this.titleRows()) {
|
|
13926
|
+
buf.push(moveTo(1, 1), this.buildTitleBar(), CLEAR_EOL);
|
|
13927
|
+
}
|
|
14308
13928
|
if (this.debugVisible) {
|
|
14309
|
-
this.renderSplitScreen(buf, visible, sh);
|
|
13929
|
+
this.renderSplitScreen(buf, visible, sh, msgTop);
|
|
14310
13930
|
} else {
|
|
14311
13931
|
for (let i = 0; i < sh; i++) {
|
|
14312
|
-
buf.push(moveTo(
|
|
13932
|
+
buf.push(moveTo(msgTop + i, 1), visible[i] || "", CLEAR_EOL);
|
|
14313
13933
|
}
|
|
14314
13934
|
}
|
|
14315
|
-
|
|
14316
|
-
buf.push(moveTo(
|
|
13935
|
+
const sepRow = this.rows - this.inputRows();
|
|
13936
|
+
buf.push(moveTo(sepRow, 1), this.buildSeparator(), CLEAR_EOL);
|
|
13937
|
+
this.renderInputArea(buf, sepRow + 1);
|
|
14317
13938
|
buf.push(SHOW_CURSOR);
|
|
14318
13939
|
process.stdout.write(buf.join(""));
|
|
14319
13940
|
}
|
|
14320
|
-
renderSplitScreen(buf, chatVisible, sh) {
|
|
13941
|
+
renderSplitScreen(buf, chatVisible, sh, msgTop = 1) {
|
|
14321
13942
|
const cw = this.chatWidth();
|
|
14322
13943
|
const dw = this.debugWidth();
|
|
14323
13944
|
const divCol = cw + 1;
|
|
@@ -14333,21 +13954,70 @@ var ChatTUI = class {
|
|
|
14333
13954
|
const chatLine = truncateAnsi(chatVisible[i] || "", cw);
|
|
14334
13955
|
const debugLine = truncateAnsi(debugVisible[i] || "", dw);
|
|
14335
13956
|
buf.push(
|
|
14336
|
-
moveTo(
|
|
13957
|
+
moveTo(msgTop + i, 1),
|
|
14337
13958
|
chatLine,
|
|
14338
13959
|
CLEAR_EOL,
|
|
14339
13960
|
// clear to end handles rest, but we need to position divider
|
|
14340
|
-
moveTo(
|
|
13961
|
+
moveTo(msgTop + i, divCol),
|
|
14341
13962
|
`${DIM}\u2502${RESET}`,
|
|
14342
13963
|
debugLine
|
|
14343
13964
|
);
|
|
14344
13965
|
buf.push(CLEAR_EOL);
|
|
14345
13966
|
}
|
|
14346
13967
|
}
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
|
|
14350
|
-
);
|
|
13968
|
+
renderInputArea(buf, startRow) {
|
|
13969
|
+
const inputRows = this.inputRows();
|
|
13970
|
+
const width = this.chatWidth();
|
|
13971
|
+
const contentWidth = Math.max(1, width - INPUT_LABEL_WIDTH);
|
|
13972
|
+
const inputLines = this.wrapInputLines(this.inputBuffer, contentWidth, inputRows);
|
|
13973
|
+
for (let i = 0; i < inputRows; i++) {
|
|
13974
|
+
const row = startRow + i;
|
|
13975
|
+
const line = inputLines[i] ?? "";
|
|
13976
|
+
const prefix = i === 0 ? `${BLUE}${INPUT_LABEL}${RESET} ` : " ".repeat(INPUT_LABEL_WIDTH);
|
|
13977
|
+
buf.push(moveTo(row, 1), prefix, line, CLEAR_EOL);
|
|
13978
|
+
}
|
|
13979
|
+
}
|
|
13980
|
+
wrapInputLines(text, maxWidth, maxRows) {
|
|
13981
|
+
const rawLines = text.split("\n");
|
|
13982
|
+
const wrapped = [];
|
|
13983
|
+
for (const raw of rawLines) {
|
|
13984
|
+
for (const wl of this.wrapText(raw, maxWidth)) {
|
|
13985
|
+
wrapped.push(wl);
|
|
13986
|
+
}
|
|
13987
|
+
}
|
|
13988
|
+
if (!wrapped.length) wrapped.push("");
|
|
13989
|
+
if (wrapped.length > maxRows) {
|
|
13990
|
+
const trimmed = wrapped.slice(wrapped.length - maxRows);
|
|
13991
|
+
trimmed[0] = "\u2026" + trimmed[0].slice(1);
|
|
13992
|
+
return trimmed;
|
|
13993
|
+
}
|
|
13994
|
+
return wrapped;
|
|
13995
|
+
}
|
|
13996
|
+
buildTitleBar() {
|
|
13997
|
+
const parts = [];
|
|
13998
|
+
if (this.options.title) {
|
|
13999
|
+
parts.push(`${CYAN}${this.options.title}${RESET}`);
|
|
14000
|
+
}
|
|
14001
|
+
if (this.options.subtitle) {
|
|
14002
|
+
parts.push(`${DIM}${this.options.subtitle}${RESET}`);
|
|
14003
|
+
}
|
|
14004
|
+
let status = "";
|
|
14005
|
+
switch (this.runState) {
|
|
14006
|
+
case "thinking":
|
|
14007
|
+
status = `${YELLOW}Thinking\u2026${RESET}`;
|
|
14008
|
+
break;
|
|
14009
|
+
case "tool_running":
|
|
14010
|
+
status = `${YELLOW}Running ${this.activeToolName}\u2026${RESET}`;
|
|
14011
|
+
break;
|
|
14012
|
+
case "streaming":
|
|
14013
|
+
status = `${GREEN}Writing\u2026${RESET}`;
|
|
14014
|
+
break;
|
|
14015
|
+
default:
|
|
14016
|
+
break;
|
|
14017
|
+
}
|
|
14018
|
+
if (status) parts.push(status);
|
|
14019
|
+
const line = parts.join(` ${DIM}|${RESET} `);
|
|
14020
|
+
return truncateAnsi(line, this.cols);
|
|
14351
14021
|
}
|
|
14352
14022
|
// ─── Separator ────────────────────────────────────────────────────
|
|
14353
14023
|
buildSeparator() {
|
|
@@ -14362,6 +14032,7 @@ var ChatTUI = class {
|
|
|
14362
14032
|
if (this.options.debug) {
|
|
14363
14033
|
hints.push(`^D ${this.debugPanelVisible ? "hide" : "show"} debug`);
|
|
14364
14034
|
}
|
|
14035
|
+
hints.push("Enter send");
|
|
14365
14036
|
const lineWidth = Math.min(this.cols, 60);
|
|
14366
14037
|
if (!hints.length) {
|
|
14367
14038
|
return `${GRAY}${"\u2500".repeat(lineWidth)}${RESET}`;
|
|
@@ -14392,8 +14063,9 @@ var ChatTUI = class {
|
|
|
14392
14063
|
return lines;
|
|
14393
14064
|
}
|
|
14394
14065
|
appendToolCallLines(lines, tc, width) {
|
|
14395
|
-
const status = tc.done ? "
|
|
14396
|
-
const
|
|
14066
|
+
const status = tc.done ? "\u2713" : "\u2026";
|
|
14067
|
+
const verb = tc.done ? "Called" : "Calling";
|
|
14068
|
+
const summary = ` ${tc.done ? GREEN : YELLOW}${status}${RESET} ${GRAY}${verb} ${tc.name}${RESET}`;
|
|
14397
14069
|
if (!this.expandDetails) {
|
|
14398
14070
|
lines.push(summary);
|
|
14399
14071
|
return;
|
|
@@ -14414,14 +14086,15 @@ var ChatTUI = class {
|
|
|
14414
14086
|
}
|
|
14415
14087
|
}
|
|
14416
14088
|
appendToolResultLines(lines, tr, width) {
|
|
14089
|
+
const mark = tr.failed ? `${RED}\u2717${RESET}` : `${GREEN}\u2713${RESET}`;
|
|
14417
14090
|
if (!this.expandDetails) {
|
|
14418
|
-
const maxLen = width -
|
|
14091
|
+
const maxLen = width - 12;
|
|
14419
14092
|
const preview = tr.content.replace(/\n/g, " ").slice(0, maxLen);
|
|
14420
14093
|
const truncated = preview.length < tr.content.length ? "..." : "";
|
|
14421
|
-
lines.push(` ${
|
|
14094
|
+
lines.push(` ${mark} ${GRAY}${tr.toolName}: ${preview}${truncated}${RESET}`);
|
|
14422
14095
|
return;
|
|
14423
14096
|
}
|
|
14424
|
-
lines.push(` ${
|
|
14097
|
+
lines.push(` ${mark} ${GRAY}${tr.toolName} result:${RESET}`);
|
|
14425
14098
|
let formatted = tr.content;
|
|
14426
14099
|
try {
|
|
14427
14100
|
formatted = JSON.stringify(JSON.parse(tr.content), null, 2);
|
|
@@ -14473,7 +14146,12 @@ var ChatTUI = class {
|
|
|
14473
14146
|
const labelWidth = 12;
|
|
14474
14147
|
const indent = " ".repeat(labelWidth);
|
|
14475
14148
|
if (!this.streamContent) {
|
|
14476
|
-
|
|
14149
|
+
if (this.runState === "thinking" || this.runState === "tool_running") {
|
|
14150
|
+
const hint = this.runState === "tool_running" && this.activeToolName ? `Running ${this.activeToolName}\u2026` : "Thinking\u2026";
|
|
14151
|
+
lines.push(`${label}${YELLOW}${hint}${RESET}`);
|
|
14152
|
+
} else {
|
|
14153
|
+
lines.push(label);
|
|
14154
|
+
}
|
|
14477
14155
|
return;
|
|
14478
14156
|
}
|
|
14479
14157
|
let first = true;
|
|
@@ -14591,15 +14269,68 @@ function buildDebugSummary(_direction, msg) {
|
|
|
14591
14269
|
return type;
|
|
14592
14270
|
}
|
|
14593
14271
|
|
|
14272
|
+
// src/lib/agui-tui-events.ts
|
|
14273
|
+
function handleAguiEvent(tui, event) {
|
|
14274
|
+
const type = event.type?.toLowerCase() ?? "";
|
|
14275
|
+
switch (type) {
|
|
14276
|
+
case "run_started":
|
|
14277
|
+
tui.runStarted();
|
|
14278
|
+
break;
|
|
14279
|
+
case "run_finished":
|
|
14280
|
+
tui.runFinished();
|
|
14281
|
+
break;
|
|
14282
|
+
case "text_message_start":
|
|
14283
|
+
tui.streamStart();
|
|
14284
|
+
break;
|
|
14285
|
+
case "text_message_content": {
|
|
14286
|
+
const delta = getTextDelta(event);
|
|
14287
|
+
if (delta) tui.streamDelta(delta);
|
|
14288
|
+
break;
|
|
14289
|
+
}
|
|
14290
|
+
case "text_message_end":
|
|
14291
|
+
tui.streamEnd();
|
|
14292
|
+
break;
|
|
14293
|
+
case "tool_call_start":
|
|
14294
|
+
tui.toolCallStart(
|
|
14295
|
+
event.toolCallId ?? "",
|
|
14296
|
+
event.toolCallName ?? event.name ?? "tool"
|
|
14297
|
+
);
|
|
14298
|
+
break;
|
|
14299
|
+
case "tool_call_args":
|
|
14300
|
+
if (event.toolCallId && event.delta) {
|
|
14301
|
+
tui.toolCallArgs(event.toolCallId, event.delta);
|
|
14302
|
+
}
|
|
14303
|
+
break;
|
|
14304
|
+
case "tool_call_end":
|
|
14305
|
+
if (event.toolCallId) tui.toolCallEnd(event.toolCallId);
|
|
14306
|
+
break;
|
|
14307
|
+
case "tool_call_result": {
|
|
14308
|
+
const toolCallId = event.toolCallId ?? "";
|
|
14309
|
+
const toolName = tui.findToolCallName(toolCallId);
|
|
14310
|
+
const content = typeof event.content === "string" ? event.content : typeof event.result === "string" ? event.result : JSON.stringify(event.result ?? event.content ?? "");
|
|
14311
|
+
tui.toolCallResult(toolCallId, toolName, content);
|
|
14312
|
+
break;
|
|
14313
|
+
}
|
|
14314
|
+
case "run_error": {
|
|
14315
|
+
const errorMsg = getErrorMessage(event);
|
|
14316
|
+
if (errorMsg) tui.showError(errorMsg);
|
|
14317
|
+
tui.runFinished();
|
|
14318
|
+
break;
|
|
14319
|
+
}
|
|
14320
|
+
default:
|
|
14321
|
+
break;
|
|
14322
|
+
}
|
|
14323
|
+
}
|
|
14324
|
+
|
|
14594
14325
|
// src/lib/audio.ts
|
|
14595
|
-
import { spawn, execSync
|
|
14326
|
+
import { spawn, execSync, spawnSync } from "child_process";
|
|
14596
14327
|
import { EventEmitter as EventEmitter2 } from "events";
|
|
14597
14328
|
import * as readline3 from "readline";
|
|
14598
14329
|
var SAMPLE_RATE = 24e3;
|
|
14599
14330
|
var MIC_CHUNK_BYTES = 4800;
|
|
14600
14331
|
function checkSoxAvailable() {
|
|
14601
14332
|
try {
|
|
14602
|
-
|
|
14333
|
+
execSync("which sox", { stdio: "pipe" });
|
|
14603
14334
|
return true;
|
|
14604
14335
|
} catch {
|
|
14605
14336
|
return false;
|
|
@@ -15015,99 +14746,81 @@ async function interactiveChatSession(session, debug, autoApprove, allowWrite) {
|
|
|
15015
14746
|
model: session.model,
|
|
15016
14747
|
appContext: session.appContext
|
|
15017
14748
|
});
|
|
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
14749
|
const existingMessages = await session.loadMessages();
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
15030
|
-
|
|
15031
|
-
|
|
15032
|
-
|
|
14750
|
+
const history = buildHistory(existingMessages);
|
|
14751
|
+
const conversationMessages = buildConversationMessages(existingMessages);
|
|
14752
|
+
let tui;
|
|
14753
|
+
const sendTurn = async (text) => {
|
|
14754
|
+
conversationMessages.push({ role: "user", content: text });
|
|
14755
|
+
let assistantContent = "";
|
|
14756
|
+
try {
|
|
14757
|
+
tui.runStarted();
|
|
14758
|
+
const { streamText } = await runConciergeAguiLoop(
|
|
14759
|
+
aguiClient,
|
|
14760
|
+
conversationMessages,
|
|
14761
|
+
(event) => {
|
|
14762
|
+
handleAguiEvent(tui, event);
|
|
14763
|
+
const type = event.type?.toLowerCase() ?? "";
|
|
14764
|
+
if (type === "text_message_content" && event.delta) {
|
|
14765
|
+
assistantContent += event.delta;
|
|
14766
|
+
}
|
|
14767
|
+
const errorMsg = getErrorMessage(event);
|
|
14768
|
+
if (errorMsg) tui.showError(errorMsg);
|
|
14769
|
+
},
|
|
14770
|
+
{
|
|
14771
|
+
autoApprove,
|
|
14772
|
+
allowWrite: allowWrite || !!session.allowWrite,
|
|
14773
|
+
debug,
|
|
14774
|
+
trustCwd: session.trustCwd,
|
|
14775
|
+
trustDir: session.trustDir,
|
|
14776
|
+
onHitlPromptStart: () => tui.pauseForPrompt(),
|
|
14777
|
+
onHitlPromptEnd: () => tui.resumeAfterPrompt()
|
|
14778
|
+
}
|
|
14779
|
+
);
|
|
14780
|
+
if (streamText && !assistantContent) {
|
|
14781
|
+
assistantContent = streamText;
|
|
14782
|
+
}
|
|
14783
|
+
if (assistantContent) {
|
|
14784
|
+
conversationMessages.push({ role: "assistant", content: assistantContent });
|
|
15033
14785
|
}
|
|
14786
|
+
} catch (error) {
|
|
14787
|
+
tui.showError(error instanceof Error ? error.message : String(error));
|
|
14788
|
+
} finally {
|
|
14789
|
+
tui.runFinished();
|
|
15034
14790
|
}
|
|
15035
|
-
|
|
15036
|
-
|
|
15037
|
-
|
|
15038
|
-
|
|
14791
|
+
};
|
|
14792
|
+
await new Promise((resolve) => {
|
|
14793
|
+
tui = new ChatTUI({
|
|
14794
|
+
title: session.label,
|
|
14795
|
+
subtitle: session.chatTitle,
|
|
14796
|
+
onSend: (text) => {
|
|
14797
|
+
void sendTurn(text);
|
|
14798
|
+
},
|
|
14799
|
+
onExit: () => {
|
|
14800
|
+
tui.stop();
|
|
14801
|
+
resolve();
|
|
14802
|
+
},
|
|
14803
|
+
debug,
|
|
14804
|
+
env: session.ctx.env
|
|
14805
|
+
});
|
|
14806
|
+
tui.loadHistory(history);
|
|
14807
|
+
tui.start();
|
|
14808
|
+
});
|
|
14809
|
+
}
|
|
14810
|
+
function buildConversationMessages(messages) {
|
|
14811
|
+
const out = [];
|
|
14812
|
+
for (const msg of messages) {
|
|
15039
14813
|
const text = typeof msg.content === "string" ? msg.content : "";
|
|
15040
|
-
if (text
|
|
15041
|
-
|
|
14814
|
+
if (text.trim() && (msg.role === "user" || msg.role === "assistant")) {
|
|
14815
|
+
out.push({ role: msg.role, content: text });
|
|
15042
14816
|
}
|
|
15043
14817
|
}
|
|
15044
|
-
|
|
15045
|
-
return new Promise((resolve) => {
|
|
15046
|
-
const prompt = () => {
|
|
15047
|
-
rl.question("\x1B[34mYou:\x1B[0m ", async (input) => {
|
|
15048
|
-
const trimmed = input.trim();
|
|
15049
|
-
if (!trimmed) {
|
|
15050
|
-
prompt();
|
|
15051
|
-
return;
|
|
15052
|
-
}
|
|
15053
|
-
if (trimmed.toLowerCase() === "exit" || trimmed.toLowerCase() === "quit") {
|
|
15054
|
-
console.log("\nGoodbye!");
|
|
15055
|
-
rl.close();
|
|
15056
|
-
return;
|
|
15057
|
-
}
|
|
15058
|
-
conversationMessages.push({ role: "user", content: trimmed });
|
|
15059
|
-
process.stdout.write("\n\x1B[32mAssistant:\x1B[0m ");
|
|
15060
|
-
let assistantContent = "";
|
|
15061
|
-
try {
|
|
15062
|
-
const { streamText } = await runConciergeAguiLoop(
|
|
15063
|
-
aguiClient,
|
|
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
|
-
});
|
|
14818
|
+
return out;
|
|
15107
14819
|
}
|
|
15108
|
-
function buildHistory(messages) {
|
|
14820
|
+
function buildHistory(messages, limit) {
|
|
15109
14821
|
const history = [];
|
|
15110
|
-
|
|
14822
|
+
const slice = limit ? messages.slice(-limit) : messages;
|
|
14823
|
+
for (const msg of slice) {
|
|
15111
14824
|
const text = typeof msg.content === "string" ? msg.content : "";
|
|
15112
14825
|
if (msg.role === "user" || msg.role === "assistant") {
|
|
15113
14826
|
if (text.trim()) {
|
|
@@ -15179,7 +14892,7 @@ async function realtimeChatSession(session, vad, debug) {
|
|
|
15179
14892
|
throw new CLIError(`Failed to connect: ${err instanceof Error ? err.message : err}`);
|
|
15180
14893
|
}
|
|
15181
14894
|
const existingMessages = await session.loadMessages();
|
|
15182
|
-
const history = buildHistory(existingMessages);
|
|
14895
|
+
const history = buildHistory(existingMessages, 30);
|
|
15183
14896
|
const tui = new ChatTUI({
|
|
15184
14897
|
onSend: (text) => {
|
|
15185
14898
|
client.sendTextMessage(text);
|
|
@@ -15244,7 +14957,7 @@ Connecting to ${session.label.toLowerCase()}...
|
|
|
15244
14957
|
throw new CLIError(`Failed to connect: ${err instanceof Error ? err.message : err}`);
|
|
15245
14958
|
}
|
|
15246
14959
|
const existingMessages = await session.loadMessages();
|
|
15247
|
-
const history = buildHistory(existingMessages);
|
|
14960
|
+
const history = buildHistory(existingMessages, 30);
|
|
15248
14961
|
const mic = new MicCapture((pcm16) => {
|
|
15249
14962
|
if (!client.isConnected()) return;
|
|
15250
14963
|
client.sendAudio(pcm16);
|
|
@@ -15339,57 +15052,210 @@ async function listAudioDevices() {
|
|
|
15339
15052
|
}
|
|
15340
15053
|
}
|
|
15341
15054
|
|
|
15342
|
-
// src/
|
|
15343
|
-
|
|
15344
|
-
|
|
15345
|
-
|
|
15346
|
-
|
|
15347
|
-
|
|
15348
|
-
|
|
15349
|
-
|
|
15350
|
-
|
|
15351
|
-
|
|
15352
|
-
|
|
15353
|
-
|
|
15354
|
-
);
|
|
15355
|
-
return;
|
|
15356
|
-
}
|
|
15357
|
-
if (args.sms) {
|
|
15358
|
-
const result2 = await originateConciergeSms(ctx);
|
|
15359
|
-
process.stderr.write(
|
|
15360
|
-
`Concierge SMS started. messageSid=${result2.messageSid} conversationId=${result2.conversationId}
|
|
15361
|
-
`
|
|
15362
|
-
);
|
|
15363
|
-
return;
|
|
15055
|
+
// src/lib/chat-history.ts
|
|
15056
|
+
var PAGE_SIZE = 100;
|
|
15057
|
+
var HARD_CAP = 2e3;
|
|
15058
|
+
async function loadAllChatMessages(ctx, workspaceUuid, chatId) {
|
|
15059
|
+
const items = [];
|
|
15060
|
+
let offset = 0;
|
|
15061
|
+
while (items.length < HARD_CAP) {
|
|
15062
|
+
const batch = await ctx.apiClient.getChatMessages(workspaceUuid, chatId, PAGE_SIZE, offset).catch(() => []);
|
|
15063
|
+
if (!batch.length) break;
|
|
15064
|
+
items.push(...batch);
|
|
15065
|
+
if (batch.length < PAGE_SIZE) break;
|
|
15066
|
+
offset += batch.length;
|
|
15364
15067
|
}
|
|
15365
|
-
|
|
15366
|
-
"Use `gr concierge chat --call`, `gr concierge chat --sms`, or `gr concierge chat` for interactive mode."
|
|
15367
|
-
);
|
|
15068
|
+
return items;
|
|
15368
15069
|
}
|
|
15369
|
-
|
|
15070
|
+
|
|
15071
|
+
// src/commands/manage.ts
|
|
15072
|
+
async function manageCommand(hctx) {
|
|
15370
15073
|
const ctx = requireAuth(hctx.ctx);
|
|
15371
|
-
const { args } = hctx;
|
|
15372
|
-
|
|
15373
|
-
if (args.call && args.sms) {
|
|
15374
|
-
throw new CLIError("--call and --sms are mutually exclusive");
|
|
15375
|
-
}
|
|
15376
|
-
if (args.voice || args.realtime) {
|
|
15377
|
-
throw new CLIError("--call/--sms cannot be combined with --voice or --realtime");
|
|
15378
|
-
}
|
|
15379
|
-
await conciergeOutboundCommand(hctx);
|
|
15380
|
-
return;
|
|
15381
|
-
}
|
|
15074
|
+
const { workspace, args } = hctx;
|
|
15075
|
+
const asMember = typeof args["as-member"] === "string" ? args["as-member"] : void 0;
|
|
15382
15076
|
const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
|
|
15383
15077
|
const message = typeof args.message === "string" ? args.message : void 0;
|
|
15384
|
-
const chat = await
|
|
15385
|
-
const session =
|
|
15386
|
-
|
|
15387
|
-
|
|
15388
|
-
|
|
15389
|
-
|
|
15390
|
-
|
|
15391
|
-
|
|
15392
|
-
|
|
15078
|
+
const chat = await getOrCreateChat(ctx, workspace, chatIdArg, asMember, "manager", !!message);
|
|
15079
|
+
const session = buildWorkspaceSession(ctx, workspace, chat, "Workspace Manager", {
|
|
15080
|
+
appContext: buildCliConciergeAppContext(),
|
|
15081
|
+
allowWrite: true,
|
|
15082
|
+
trustCwd: !!args["trust-cwd"],
|
|
15083
|
+
trustDir: typeof args["trust-dir"] === "string" ? args["trust-dir"] : void 0
|
|
15084
|
+
});
|
|
15085
|
+
process.stderr.write(`Chat #${session.chatId}
|
|
15086
|
+
`);
|
|
15087
|
+
await runChatSession(session, args);
|
|
15088
|
+
process.stderr.write(`Chat #${session.chatId}
|
|
15089
|
+
`);
|
|
15090
|
+
}
|
|
15091
|
+
async function chatCommand(hctx) {
|
|
15092
|
+
const ctx = requireAuth(hctx.ctx);
|
|
15093
|
+
const { workspace, args } = hctx;
|
|
15094
|
+
const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
|
|
15095
|
+
const workflowId = typeof args.workflow === "string" ? args.workflow : void 0;
|
|
15096
|
+
const memberId = typeof args.member === "string" ? args.member : void 0;
|
|
15097
|
+
let chat;
|
|
15098
|
+
if (chatIdArg) {
|
|
15099
|
+
const id = Number(chatIdArg);
|
|
15100
|
+
if (isNaN(id)) throw new CLIError("--chat-id must be a number");
|
|
15101
|
+
chat = await ctx.apiClient.getChat(workspace, id);
|
|
15102
|
+
} else if (workflowId) {
|
|
15103
|
+
chat = await createAssignmentChat(ctx.apiClient, workspace, Number(workflowId), memberId);
|
|
15104
|
+
} else if (memberId) {
|
|
15105
|
+
chat = await createMemberChat(ctx.apiClient, workspace, memberId);
|
|
15106
|
+
} else {
|
|
15107
|
+
throw new CLIError(
|
|
15108
|
+
'Specify --workflow <id>, --member <id>, or --chat-id <id>.\nUse "gr manage" for workspace manager chats.'
|
|
15109
|
+
);
|
|
15110
|
+
}
|
|
15111
|
+
const session = buildWorkspaceSession(ctx, workspace, chat, "Chat");
|
|
15112
|
+
await runChatSession(session, args);
|
|
15113
|
+
}
|
|
15114
|
+
function buildWorkspaceSession(ctx, workspace, chat, label, extra) {
|
|
15115
|
+
const wsBase = ctx.envConfig.apiUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
|
|
15116
|
+
return {
|
|
15117
|
+
ctx,
|
|
15118
|
+
workspaceUuid: workspace,
|
|
15119
|
+
chatId: chat.id,
|
|
15120
|
+
chatUuid: chat.uuid,
|
|
15121
|
+
chatTitle: chat.title || chat.uuid,
|
|
15122
|
+
aguiEndpointUrl: `${ctx.envConfig.apiUrl}/api/v2/w/${workspace}/web-chat/${chat.id}/chat/agui`,
|
|
15123
|
+
wsUrl: `${wsBase}/api/v2/w/${workspace}/realtime/stream-ws`,
|
|
15124
|
+
loadMessages: () => loadAllChatMessages(ctx, workspace, chat.id),
|
|
15125
|
+
label,
|
|
15126
|
+
model: "manager",
|
|
15127
|
+
...extra
|
|
15128
|
+
};
|
|
15129
|
+
}
|
|
15130
|
+
async function getOrCreateChat(ctx, workspace, chatIdArg, asMemberId, chatType, skipSelection = false) {
|
|
15131
|
+
if (chatIdArg) {
|
|
15132
|
+
const chatId = Number(chatIdArg);
|
|
15133
|
+
if (isNaN(chatId)) throw new CLIError("--chat-id must be a number");
|
|
15134
|
+
return ctx.apiClient.getChat(workspace, chatId);
|
|
15135
|
+
}
|
|
15136
|
+
if (skipSelection) {
|
|
15137
|
+
return createManagerChat(ctx.apiClient, workspace, asMemberId);
|
|
15138
|
+
}
|
|
15139
|
+
const chatsResponse = await ctx.apiClient.getChats(workspace, {
|
|
15140
|
+
chatType,
|
|
15141
|
+
sortBy: "updatedAt",
|
|
15142
|
+
sortOrder: "desc",
|
|
15143
|
+
pageSize: 20
|
|
15144
|
+
});
|
|
15145
|
+
const chats = chatsResponse.items || [];
|
|
15146
|
+
const { item: selectedChat, isNew } = await selectFromList(chats, {
|
|
15147
|
+
title: "Select a chat or create a new one",
|
|
15148
|
+
labelFn: (chat) => chat.title || `Chat ${chat.uuid.slice(0, 8)}...`,
|
|
15149
|
+
detailFn: (chat) => {
|
|
15150
|
+
const time = formatTimestamp(chat.updatedAt);
|
|
15151
|
+
const msgCount = chat.messageCount || 0;
|
|
15152
|
+
return `${msgCount} msg${msgCount !== 1 ? "s" : ""} - ${time}`;
|
|
15153
|
+
},
|
|
15154
|
+
emptyMessage: "No existing chats.",
|
|
15155
|
+
allowNew: true,
|
|
15156
|
+
newLabel: "+ Start new chat"
|
|
15157
|
+
});
|
|
15158
|
+
if (!selectedChat && !isNew) {
|
|
15159
|
+
throw new CLIError("No chat selected.");
|
|
15160
|
+
}
|
|
15161
|
+
if (isNew) {
|
|
15162
|
+
return createManagerChat(ctx.apiClient, workspace, asMemberId);
|
|
15163
|
+
}
|
|
15164
|
+
return selectedChat;
|
|
15165
|
+
}
|
|
15166
|
+
async function createManagerChat(apiClient, workspace, asMemberId) {
|
|
15167
|
+
const data = {
|
|
15168
|
+
chatType: "manager",
|
|
15169
|
+
channel: "web-chat"
|
|
15170
|
+
};
|
|
15171
|
+
if (asMemberId) {
|
|
15172
|
+
data.memberId = Number(asMemberId);
|
|
15173
|
+
}
|
|
15174
|
+
const chat = await apiClient.createChat(workspace, data);
|
|
15175
|
+
process.stderr.write(`Created chat #${chat.id}
|
|
15176
|
+
`);
|
|
15177
|
+
return chat;
|
|
15178
|
+
}
|
|
15179
|
+
async function createAssignmentChat(apiClient, workspace, workflowId, asMemberId) {
|
|
15180
|
+
if (isNaN(workflowId)) throw new CLIError("--workflow must be a number");
|
|
15181
|
+
const data = {
|
|
15182
|
+
chatType: "assignment",
|
|
15183
|
+
channel: "web-chat",
|
|
15184
|
+
workflowId
|
|
15185
|
+
};
|
|
15186
|
+
if (asMemberId) {
|
|
15187
|
+
data.memberId = Number(asMemberId);
|
|
15188
|
+
}
|
|
15189
|
+
const chat = await apiClient.createChat(workspace, data);
|
|
15190
|
+
process.stderr.write(`Created assignment chat #${chat.id} (workflow ${workflowId})
|
|
15191
|
+
`);
|
|
15192
|
+
return chat;
|
|
15193
|
+
}
|
|
15194
|
+
async function createMemberChat(apiClient, workspace, memberId) {
|
|
15195
|
+
const id = Number(memberId);
|
|
15196
|
+
if (isNaN(id)) throw new CLIError("--member must be a number");
|
|
15197
|
+
const data = {
|
|
15198
|
+
chatType: "member",
|
|
15199
|
+
channel: "web-chat",
|
|
15200
|
+
memberId: id
|
|
15201
|
+
};
|
|
15202
|
+
const chat = await apiClient.createChat(workspace, data);
|
|
15203
|
+
process.stderr.write(`Created DM chat #${chat.id} (member ${memberId})
|
|
15204
|
+
`);
|
|
15205
|
+
return chat;
|
|
15206
|
+
}
|
|
15207
|
+
|
|
15208
|
+
// src/commands/concierge.ts
|
|
15209
|
+
async function conciergeOutboundCommand(hctx) {
|
|
15210
|
+
const ctx = requireAuth(hctx.ctx);
|
|
15211
|
+
const { args } = hctx;
|
|
15212
|
+
if (args.call && args.sms) {
|
|
15213
|
+
throw new CLIError("--call and --sms are mutually exclusive");
|
|
15214
|
+
}
|
|
15215
|
+
if (args.call) {
|
|
15216
|
+
const result2 = await originateConciergeCall(ctx);
|
|
15217
|
+
process.stderr.write(
|
|
15218
|
+
`Concierge call started. callSid=${result2.callSid} startedAt=${result2.startedAt}
|
|
15219
|
+
`
|
|
15220
|
+
);
|
|
15221
|
+
return;
|
|
15222
|
+
}
|
|
15223
|
+
if (args.sms) {
|
|
15224
|
+
const result2 = await originateConciergeSms(ctx);
|
|
15225
|
+
process.stderr.write(
|
|
15226
|
+
`Concierge SMS started. messageSid=${result2.messageSid} conversationId=${result2.conversationId}
|
|
15227
|
+
`
|
|
15228
|
+
);
|
|
15229
|
+
return;
|
|
15230
|
+
}
|
|
15231
|
+
throw new CLIError(
|
|
15232
|
+
"Use `gr concierge chat --call`, `gr concierge chat --sms`, or `gr concierge chat` for interactive mode."
|
|
15233
|
+
);
|
|
15234
|
+
}
|
|
15235
|
+
async function conciergeCommand(hctx) {
|
|
15236
|
+
const ctx = requireAuth(hctx.ctx);
|
|
15237
|
+
const { args } = hctx;
|
|
15238
|
+
if (args.call || args.sms) {
|
|
15239
|
+
if (args.call && args.sms) {
|
|
15240
|
+
throw new CLIError("--call and --sms are mutually exclusive");
|
|
15241
|
+
}
|
|
15242
|
+
if (args.voice || args.realtime) {
|
|
15243
|
+
throw new CLIError("--call/--sms cannot be combined with --voice or --realtime");
|
|
15244
|
+
}
|
|
15245
|
+
await conciergeOutboundCommand(hctx);
|
|
15246
|
+
return;
|
|
15247
|
+
}
|
|
15248
|
+
const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
|
|
15249
|
+
const message = typeof args.message === "string" ? args.message : void 0;
|
|
15250
|
+
const chat = await getOrCreateConciergeChat(ctx, chatIdArg, !!message);
|
|
15251
|
+
const session = buildConciergeSession(ctx, chat, args);
|
|
15252
|
+
await runChatSession(session, args);
|
|
15253
|
+
}
|
|
15254
|
+
function buildConciergeSession(ctx, chat, args) {
|
|
15255
|
+
const wsBase = ctx.envConfig.apiUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
|
|
15256
|
+
return {
|
|
15257
|
+
ctx,
|
|
15258
|
+
workspaceUuid: "",
|
|
15393
15259
|
chatId: chat.id,
|
|
15394
15260
|
chatUuid: chat.uuid,
|
|
15395
15261
|
chatTitle: chat.title || chat.uuid,
|
|
@@ -15438,6 +15304,64 @@ async function getOrCreateConciergeChat(ctx, chatIdArg, skipSelection) {
|
|
|
15438
15304
|
return selectedChat;
|
|
15439
15305
|
}
|
|
15440
15306
|
|
|
15307
|
+
// src/commands/dev.ts
|
|
15308
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync as readFileSync2 } from "fs";
|
|
15309
|
+
import { execSync as execSync2 } from "child_process";
|
|
15310
|
+
import { join } from "path";
|
|
15311
|
+
async function devImportLocalCommand(ctx, workspace) {
|
|
15312
|
+
stderr(`Exporting workspace ${workspace}...`);
|
|
15313
|
+
const exportData = await ctx.apiClient.exportWorkspace(workspace);
|
|
15314
|
+
stderr("Export successful!");
|
|
15315
|
+
stderr(` Workflows: ${(exportData.workflows || []).length}`);
|
|
15316
|
+
stderr(` Tasks: ${(exportData.tasks || []).length}`);
|
|
15317
|
+
stderr(` Data Types: ${(exportData.dataTypes || []).length}`);
|
|
15318
|
+
stderr(` Sites: ${(exportData.sites || []).length}`);
|
|
15319
|
+
const slug = workspace.slice(0, 8);
|
|
15320
|
+
const importCacheDir = join(ctx.credentialsManager.getCacheDir(), "imports");
|
|
15321
|
+
if (!existsSync(importCacheDir)) {
|
|
15322
|
+
mkdirSync(importCacheDir, { recursive: true });
|
|
15323
|
+
}
|
|
15324
|
+
const filename = `ws-${slug}-${workspace}.json`;
|
|
15325
|
+
const filepath = join(importCacheDir, filename);
|
|
15326
|
+
writeFileSync(filepath, JSON.stringify(exportData, null, 2));
|
|
15327
|
+
stderr(`Saved to: ${filepath}`);
|
|
15328
|
+
stderr("Running dev:restore...");
|
|
15329
|
+
const rootDir = findProjectRoot();
|
|
15330
|
+
const relativePath = filepath.replace(rootDir + "/", "");
|
|
15331
|
+
try {
|
|
15332
|
+
const result2 = execSync2(`yarn dev:restore ${relativePath}`, {
|
|
15333
|
+
cwd: rootDir,
|
|
15334
|
+
stdio: "pipe",
|
|
15335
|
+
encoding: "utf-8"
|
|
15336
|
+
});
|
|
15337
|
+
process.stderr.write(result2);
|
|
15338
|
+
const failed = result2.includes("Failed/Skipped: ") && !result2.includes("Failed/Skipped: 0") || result2.includes("Successfully restored: 0");
|
|
15339
|
+
if (failed) throw new CLIError("Import failed");
|
|
15340
|
+
} catch (error) {
|
|
15341
|
+
if (error instanceof CLIError) throw error;
|
|
15342
|
+
const execError = error;
|
|
15343
|
+
if (execError.stdout) process.stderr.write(execError.stdout);
|
|
15344
|
+
if (execError.stderr) process.stderr.write(execError.stderr);
|
|
15345
|
+
throw new CLIError("dev:restore failed");
|
|
15346
|
+
}
|
|
15347
|
+
stderr(`Import complete: ${workspace}`);
|
|
15348
|
+
}
|
|
15349
|
+
function findProjectRoot() {
|
|
15350
|
+
let dir = process.cwd();
|
|
15351
|
+
while (dir !== "/") {
|
|
15352
|
+
const pkgPath = join(dir, "package.json");
|
|
15353
|
+
if (existsSync(pkgPath)) {
|
|
15354
|
+
try {
|
|
15355
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
15356
|
+
if (pkg.workspaces) return dir;
|
|
15357
|
+
} catch {
|
|
15358
|
+
}
|
|
15359
|
+
}
|
|
15360
|
+
dir = join(dir, "..");
|
|
15361
|
+
}
|
|
15362
|
+
return process.cwd();
|
|
15363
|
+
}
|
|
15364
|
+
|
|
15441
15365
|
// src/commands/get-started.ts
|
|
15442
15366
|
var WEB_SETUP = "https://app.gravityrail.com/setup";
|
|
15443
15367
|
var WEB_GET_STARTED = "https://app.gravityrail.com/get-started";
|
|
@@ -15537,73 +15461,15 @@ async function getStartedCommand(hctx) {
|
|
|
15537
15461
|
process.stdout.write(LLM_GUIDE);
|
|
15538
15462
|
}
|
|
15539
15463
|
|
|
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
15464
|
// src/commands/login.ts
|
|
15599
|
-
import { GravityRailClient
|
|
15465
|
+
import { GravityRailClient } from "@gravity-rail/sdk";
|
|
15600
15466
|
async function loginCommand(ctx) {
|
|
15601
15467
|
const { accessToken } = await performOAuthFlow({
|
|
15602
15468
|
env: ctx.env,
|
|
15603
15469
|
envConfig: ctx.envConfig,
|
|
15604
15470
|
credentialsManager: ctx.credentialsManager
|
|
15605
15471
|
});
|
|
15606
|
-
const apiClient = new
|
|
15472
|
+
const apiClient = new GravityRailClient(accessToken, ctx.envConfig.apiUrl);
|
|
15607
15473
|
const account = await apiClient.getUserAccount();
|
|
15608
15474
|
stderr(`Logged in as ${account.name} (${account.email})`);
|
|
15609
15475
|
}
|
|
@@ -15612,143 +15478,6 @@ async function logoutCommand(ctx) {
|
|
|
15612
15478
|
stderr(`Credentials cleared for ${ctx.env}`);
|
|
15613
15479
|
}
|
|
15614
15480
|
|
|
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
15481
|
// src/commands/organizations.ts
|
|
15753
15482
|
var COLUMNS34 = [
|
|
15754
15483
|
{ header: "NAME", getValue: (o) => o.organizationName || "", maxWidth: 35 },
|
|
@@ -15776,6 +15505,226 @@ async function whoamiCommand(ctx) {
|
|
|
15776
15505
|
);
|
|
15777
15506
|
outputSingle(account, ctx, columns);
|
|
15778
15507
|
}
|
|
15508
|
+
|
|
15509
|
+
// src/top-level-commands.ts
|
|
15510
|
+
var topLevelCommands = [
|
|
15511
|
+
{
|
|
15512
|
+
path: ["login"],
|
|
15513
|
+
description: "Authenticate via OAuth",
|
|
15514
|
+
requiresAuth: false,
|
|
15515
|
+
handler: async ({ ctx }) => {
|
|
15516
|
+
await loginCommand(ctx);
|
|
15517
|
+
}
|
|
15518
|
+
},
|
|
15519
|
+
{
|
|
15520
|
+
path: ["logout"],
|
|
15521
|
+
description: "Clear cached credentials",
|
|
15522
|
+
requiresAuth: false,
|
|
15523
|
+
handler: async ({ ctx }) => {
|
|
15524
|
+
await logoutCommand(ctx);
|
|
15525
|
+
}
|
|
15526
|
+
},
|
|
15527
|
+
{
|
|
15528
|
+
path: ["auth", "browser"],
|
|
15529
|
+
description: "Open an authenticated browser session using CLI credentials",
|
|
15530
|
+
args: [
|
|
15531
|
+
{ name: "no-open", description: "Print the URL instead of opening browser", isFlag: true }
|
|
15532
|
+
],
|
|
15533
|
+
handler: async ({ ctx, args }) => {
|
|
15534
|
+
await authBrowserCommand(requireAuth(ctx), args);
|
|
15535
|
+
}
|
|
15536
|
+
},
|
|
15537
|
+
{
|
|
15538
|
+
path: ["manage"],
|
|
15539
|
+
description: "Interactive workspace manager chat (text, realtime, or voice)",
|
|
15540
|
+
requiresWorkspace: true,
|
|
15541
|
+
mutates: true,
|
|
15542
|
+
args: [
|
|
15543
|
+
{ name: "realtime", description: "Use WebSocket realtime connection", isFlag: true },
|
|
15544
|
+
{ name: "voice", description: "Enable voice mode (microphone + speaker)", isFlag: true },
|
|
15545
|
+
{ name: "message", alias: "m", description: "Single-turn: send one message and exit" },
|
|
15546
|
+
{ name: "as-member", description: "Chat as a specific member (by numeric ID)" },
|
|
15547
|
+
{ name: "chat-id", description: "Resume an existing chat by numeric ID" },
|
|
15548
|
+
{ name: "vad", description: "Voice activity detection: server (default) or client" },
|
|
15549
|
+
{ name: "list-devices", description: "List audio devices and exit", isFlag: true },
|
|
15550
|
+
{ name: "input-device", description: "Microphone device name or ID" },
|
|
15551
|
+
{ name: "output-device", description: "Speaker device name or ID" },
|
|
15552
|
+
{ name: "debug", description: "Enable debug logging", isFlag: true },
|
|
15553
|
+
{
|
|
15554
|
+
name: "auto-approve",
|
|
15555
|
+
description: "Auto-approve all tool calls (skip HITL)",
|
|
15556
|
+
isFlag: true
|
|
15557
|
+
},
|
|
15558
|
+
{
|
|
15559
|
+
name: "trust-cwd",
|
|
15560
|
+
description: "Trust files in the current working directory (non-interactive)",
|
|
15561
|
+
isFlag: true
|
|
15562
|
+
},
|
|
15563
|
+
{
|
|
15564
|
+
name: "trust-dir",
|
|
15565
|
+
description: "Trust files in this directory (non-interactive; absolute path)"
|
|
15566
|
+
}
|
|
15567
|
+
],
|
|
15568
|
+
handler: async (hctx) => {
|
|
15569
|
+
await manageCommand(hctx);
|
|
15570
|
+
}
|
|
15571
|
+
},
|
|
15572
|
+
{
|
|
15573
|
+
path: ["chat"],
|
|
15574
|
+
description: "Chat with a workflow, member, or existing chat (text, realtime, or voice)",
|
|
15575
|
+
requiresWorkspace: true,
|
|
15576
|
+
mutates: true,
|
|
15577
|
+
args: [
|
|
15578
|
+
{ name: "workflow", description: "Start assignment chat with workflow (by numeric ID)" },
|
|
15579
|
+
{ name: "member", description: "Start DM with a member (by numeric ID)" },
|
|
15580
|
+
{ name: "chat-id", description: "Resume an existing chat by numeric ID" },
|
|
15581
|
+
{ name: "realtime", description: "Use WebSocket realtime connection", isFlag: true },
|
|
15582
|
+
{ name: "voice", description: "Enable voice mode (microphone + speaker)", isFlag: true },
|
|
15583
|
+
{ name: "message", alias: "m", description: "Single-turn: send one message and exit" },
|
|
15584
|
+
{ name: "vad", description: "Voice activity detection: server (default) or client" },
|
|
15585
|
+
{ name: "list-devices", description: "List audio devices and exit", isFlag: true },
|
|
15586
|
+
{ name: "input-device", description: "Microphone device name or ID" },
|
|
15587
|
+
{ name: "output-device", description: "Speaker device name or ID" },
|
|
15588
|
+
{ name: "debug", description: "Enable debug logging", isFlag: true },
|
|
15589
|
+
{
|
|
15590
|
+
name: "auto-approve",
|
|
15591
|
+
description: "Auto-approve all tool calls (skip HITL)",
|
|
15592
|
+
isFlag: true
|
|
15593
|
+
}
|
|
15594
|
+
],
|
|
15595
|
+
handler: async (hctx) => {
|
|
15596
|
+
await chatCommand(hctx);
|
|
15597
|
+
}
|
|
15598
|
+
},
|
|
15599
|
+
{
|
|
15600
|
+
path: ["get-started"],
|
|
15601
|
+
description: "Bootstrap instructions for new users and LLM agents",
|
|
15602
|
+
requiresAuth: false,
|
|
15603
|
+
args: [
|
|
15604
|
+
{
|
|
15605
|
+
name: "format",
|
|
15606
|
+
description: "Output format: llm (agent-readable) or default (human)"
|
|
15607
|
+
},
|
|
15608
|
+
{
|
|
15609
|
+
name: "agent",
|
|
15610
|
+
description: "Alias for --format llm",
|
|
15611
|
+
isFlag: true
|
|
15612
|
+
}
|
|
15613
|
+
],
|
|
15614
|
+
handler: async (hctx) => {
|
|
15615
|
+
await getStartedCommand(hctx);
|
|
15616
|
+
}
|
|
15617
|
+
},
|
|
15618
|
+
{
|
|
15619
|
+
path: ["setup"],
|
|
15620
|
+
description: "Alias for get-started \u2014 bootstrap instructions",
|
|
15621
|
+
requiresAuth: false,
|
|
15622
|
+
args: [
|
|
15623
|
+
{ name: "format", description: "Output format: llm or default" },
|
|
15624
|
+
{ name: "agent", description: "Alias for --format llm", isFlag: true }
|
|
15625
|
+
],
|
|
15626
|
+
handler: async (hctx) => {
|
|
15627
|
+
await getStartedCommand(hctx);
|
|
15628
|
+
}
|
|
15629
|
+
},
|
|
15630
|
+
{
|
|
15631
|
+
path: ["concierge"],
|
|
15632
|
+
description: "Call or SMS your verified phone via the Concierge (server-side)",
|
|
15633
|
+
requiresWorkspace: false,
|
|
15634
|
+
mutates: true,
|
|
15635
|
+
args: [
|
|
15636
|
+
{ name: "call", description: "Ring your verified account phone", isFlag: true },
|
|
15637
|
+
{ name: "sms", description: "Start an SMS thread on your verified phone", isFlag: true }
|
|
15638
|
+
],
|
|
15639
|
+
handler: async (hctx) => {
|
|
15640
|
+
await conciergeOutboundCommand(hctx);
|
|
15641
|
+
}
|
|
15642
|
+
},
|
|
15643
|
+
{
|
|
15644
|
+
path: ["account", "verify-phone"],
|
|
15645
|
+
description: "Verify your account phone for concierge chat --call / --sms",
|
|
15646
|
+
requiresWorkspace: false,
|
|
15647
|
+
mutates: true,
|
|
15648
|
+
args: [{ name: "phone", description: "E.164 phone number (optional; prompts if omitted)" }],
|
|
15649
|
+
handler: async (hctx) => {
|
|
15650
|
+
const { accountVerifyPhoneCommand } = await import("./account-verify-phone-TDQNZ5XN.js");
|
|
15651
|
+
await accountVerifyPhoneCommand(hctx);
|
|
15652
|
+
}
|
|
15653
|
+
},
|
|
15654
|
+
{
|
|
15655
|
+
path: ["concierge", "chat"],
|
|
15656
|
+
description: "Interactive concierge chat (text, realtime, or voice)",
|
|
15657
|
+
requiresWorkspace: false,
|
|
15658
|
+
mutates: true,
|
|
15659
|
+
args: [
|
|
15660
|
+
{ name: "realtime", description: "Use WebSocket realtime connection", isFlag: true },
|
|
15661
|
+
{ name: "voice", description: "Enable voice mode (microphone + speaker)", isFlag: true },
|
|
15662
|
+
{
|
|
15663
|
+
name: "call",
|
|
15664
|
+
description: "Ring your verified account phone (server-side)",
|
|
15665
|
+
isFlag: true
|
|
15666
|
+
},
|
|
15667
|
+
{
|
|
15668
|
+
name: "sms",
|
|
15669
|
+
description: "SMS your verified account phone (server-side)",
|
|
15670
|
+
isFlag: true
|
|
15671
|
+
},
|
|
15672
|
+
{ name: "message", alias: "m", description: "Single-turn: send one message and exit" },
|
|
15673
|
+
{ name: "chat-id", description: "Resume an existing chat by numeric ID" },
|
|
15674
|
+
{ name: "vad", description: "Voice activity detection: server (default) or client" },
|
|
15675
|
+
{ name: "list-devices", description: "List audio devices and exit", isFlag: true },
|
|
15676
|
+
{ name: "input-device", description: "Microphone device name or ID" },
|
|
15677
|
+
{ name: "output-device", description: "Speaker device name or ID" },
|
|
15678
|
+
{ name: "debug", description: "Enable debug logging", isFlag: true },
|
|
15679
|
+
{
|
|
15680
|
+
name: "auto-approve",
|
|
15681
|
+
description: "Auto-approve all tool calls (skip HITL)",
|
|
15682
|
+
isFlag: true
|
|
15683
|
+
},
|
|
15684
|
+
{
|
|
15685
|
+
name: "trust-cwd",
|
|
15686
|
+
description: "Trust files in the current working directory (non-interactive)",
|
|
15687
|
+
isFlag: true
|
|
15688
|
+
},
|
|
15689
|
+
{
|
|
15690
|
+
name: "trust-dir",
|
|
15691
|
+
description: "Trust files in this directory (non-interactive; absolute path)"
|
|
15692
|
+
}
|
|
15693
|
+
],
|
|
15694
|
+
handler: async (hctx) => {
|
|
15695
|
+
await conciergeCommand(hctx);
|
|
15696
|
+
}
|
|
15697
|
+
},
|
|
15698
|
+
{
|
|
15699
|
+
path: ["dev", "import-local"],
|
|
15700
|
+
description: "Import remote workspace locally",
|
|
15701
|
+
requiresWorkspace: true,
|
|
15702
|
+
handler: async ({ ctx, workspace }) => {
|
|
15703
|
+
await devImportLocalCommand(requireAuth(ctx), workspace);
|
|
15704
|
+
}
|
|
15705
|
+
},
|
|
15706
|
+
{
|
|
15707
|
+
path: ["whoami"],
|
|
15708
|
+
description: "Show current user info",
|
|
15709
|
+
handler: async ({ ctx }) => {
|
|
15710
|
+
await whoamiCommand(requireAuth(ctx));
|
|
15711
|
+
}
|
|
15712
|
+
},
|
|
15713
|
+
{
|
|
15714
|
+
path: ["organizations", "list"],
|
|
15715
|
+
description: "List organizations",
|
|
15716
|
+
handler: async ({ ctx }) => {
|
|
15717
|
+
await organizationsListCommand(requireAuth(ctx));
|
|
15718
|
+
}
|
|
15719
|
+
},
|
|
15720
|
+
{
|
|
15721
|
+
path: ["orgs", "list"],
|
|
15722
|
+
description: "List organizations",
|
|
15723
|
+
handler: async ({ ctx }) => {
|
|
15724
|
+
await organizationsListCommand(requireAuth(ctx));
|
|
15725
|
+
}
|
|
15726
|
+
}
|
|
15727
|
+
];
|
|
15779
15728
|
export {
|
|
15780
15729
|
AuthRequiredError,
|
|
15781
15730
|
CLIError,
|
|
@@ -15834,6 +15783,7 @@ export {
|
|
|
15834
15783
|
simpleCrudCommands,
|
|
15835
15784
|
startAccountPhoneVerify,
|
|
15836
15785
|
stderr,
|
|
15786
|
+
topLevelCommands,
|
|
15837
15787
|
unwrapItems,
|
|
15838
15788
|
waitForOutboundPhoneCallOutcome,
|
|
15839
15789
|
whoamiCommand
|