@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/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-OPWCTOHP.js";
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 as readFileSync2 } from "fs";
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 = readFileSync2(filePath, "utf-8");
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
- lines.push(` ${subPath.padEnd(28)} ${cmd.description}`);
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 lines = [`${cmd.description}
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
- lines.push(` ${domain.name.padEnd(28)} ${domain.description}`);
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
- lines.push(` ${topWord.padEnd(28)} ${cmd.description}`);
909
+ const description = inSandbox ? sandboxHelpText(cmd.description) : cmd.description;
910
+ lines.push(` ${topWord.padEnd(28)} ${description}`);
1475
911
  }
1476
912
  }
1477
- lines.push(
913
+ const globalOptions = [
1478
914
  "",
1479
915
  "Global Options:",
1480
- " --env <env> Environment: dev, staging, prod (default: dev)",
1481
- " --realm <ws>/<member> Dev auth shortcut (dev only; sets -w automatically)",
1482
- " --api-url <url> API base URL (or GRAVITY_RAIL_API_URL)",
1483
- " -w, --workspace <uuid> Workspace UUID",
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 TOOL_COLUMNS = [
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, TOOL_COLUMNS);
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 TOOL_COLUMNS2 = [COL.id(), COL.name(), COL.description()];
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, TOOL_COLUMNS2);
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, TOOL_COLUMNS2);
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 candidates = [normalized, callerE164.trim()].filter(
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
- process.stdout.write(`${JSON.stringify(phone, null, 2)}
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
- matchedMemberPhoneType
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
- process.stdout.write(`Inbound calls (most recent page): ${inbound.length} shown
10396
+ if (ctx.outputFormat === "table") {
10397
+ process.stdout.write(`Inbound calls (most recent page): ${inbound.length} shown
10890
10398
  `);
10891
- process.stdout.write(
10892
- "Tip: gr phone-numbers diagnose --id <id> --as +1\u2026 simulates routing without a live call.\n"
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 as execFile2 } from "child_process";
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
- execFile2(command, args, (err) => {
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-SMY7LZJB.js");
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
- const next = await handleCliHitlInterrupt(hitlPayload, {
13545
- allowWrite: opts.allowWrite,
13546
- autoApprove: opts.autoApprove,
13547
- trustCwd: opts.trustCwd,
13548
- trustDir: opts.trustDir,
13549
- nonInteractive: opts.nonInteractive
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
- process.stdout.write(`${CSI}1;${Math.max(1, this.rows - 2)}r`);
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
- this.items.push({ kind: "tool_result", toolCallId, toolName, content });
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(`${MOUSE_OFF}${CSI}r${ALT_SCREEN_OFF}${SHOW_CURSOR}`);
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.rows - 2);
13686
+ return Math.max(1, this.messageAreaBottom() - this.messageAreaTop() + 1);
14085
13687
  }
14086
13688
  pageSize() {
14087
- return Math.max(1, this.rows - 4);
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.renderInputLine();
13849
+ this.dirty();
14238
13850
  }
14239
13851
  return;
14240
13852
  }
14241
13853
  if (code < 32) return;
14242
13854
  this.inputBuffer += ch;
14243
- this.renderInputLine();
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.renderInputLine();
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
- process.stdout.write(`${CSI}1;${Math.max(1, this.rows - 2)}r`);
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(i + 1, 1), visible[i] || "", CLEAR_EOL);
13932
+ buf.push(moveTo(msgTop + i, 1), visible[i] || "", CLEAR_EOL);
14313
13933
  }
14314
13934
  }
14315
- buf.push(moveTo(this.rows - 1, 1), this.buildSeparator(), CLEAR_EOL);
14316
- buf.push(moveTo(this.rows, 1), `${BLUE}You:${RESET} ${this.inputBuffer}${CLEAR_EOL}`);
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(i + 1, 1),
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(i + 1, divCol),
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
- renderInputLine() {
14348
- process.stdout.write(
14349
- `${moveTo(this.rows, 1)}${BLUE}You:${RESET} ${this.inputBuffer}${CLEAR_EOL}`
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 ? "Called" : "Calling";
14396
- const summary = ` ${GRAY}${status} ${tc.name}${tc.done ? "" : "..."}${RESET}`;
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 - 10;
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(` ${GRAY}\u2192 ${tr.toolName}: ${preview}${truncated}${RESET}`);
14094
+ lines.push(` ${mark} ${GRAY}${tr.toolName}: ${preview}${truncated}${RESET}`);
14422
14095
  return;
14423
14096
  }
14424
- lines.push(` ${GRAY}\u2192 ${tr.toolName} result:${RESET}`);
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
- lines.push(label);
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 as execSync2, spawnSync } from "child_process";
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
- execSync2("which sox", { stdio: "pipe" });
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
- if (existingMessages.length > 0) {
15028
- console.log("\x1B[90m--- Previous messages ---\x1B[0m\n");
15029
- for (const msg of existingMessages.slice(-10)) {
15030
- const text = typeof msg.content === "string" ? msg.content : "";
15031
- if (text && text.trim()) {
15032
- printMessage(text, msg.role);
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
- console.log("\x1B[90m--- End of history ---\x1B[0m\n");
15036
- }
15037
- const conversationMessages = [];
15038
- for (const msg of existingMessages.slice(-10)) {
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 && text.trim() && (msg.role === "user" || msg.role === "assistant")) {
15041
- conversationMessages.push({ role: msg.role, content: text });
14814
+ if (text.trim() && (msg.role === "user" || msg.role === "assistant")) {
14815
+ out.push({ role: msg.role, content: text });
15042
14816
  }
15043
14817
  }
15044
- const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
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
- for (const msg of messages.slice(-30)) {
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/commands/concierge.ts
15343
- async function conciergeOutboundCommand(hctx) {
15344
- const ctx = requireAuth(hctx.ctx);
15345
- const { args } = hctx;
15346
- if (args.call && args.sms) {
15347
- throw new CLIError("--call and --sms are mutually exclusive");
15348
- }
15349
- if (args.call) {
15350
- const result2 = await originateConciergeCall(ctx);
15351
- process.stderr.write(
15352
- `Concierge call started. callSid=${result2.callSid} startedAt=${result2.startedAt}
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
- throw new CLIError(
15366
- "Use `gr concierge chat --call`, `gr concierge chat --sms`, or `gr concierge chat` for interactive mode."
15367
- );
15068
+ return items;
15368
15069
  }
15369
- async function conciergeCommand(hctx) {
15070
+
15071
+ // src/commands/manage.ts
15072
+ async function manageCommand(hctx) {
15370
15073
  const ctx = requireAuth(hctx.ctx);
15371
- const { args } = hctx;
15372
- if (args.call || args.sms) {
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 getOrCreateConciergeChat(ctx, chatIdArg, !!message);
15385
- const session = buildConciergeSession(ctx, chat, args);
15386
- await runChatSession(session, args);
15387
- }
15388
- function buildConciergeSession(ctx, chat, args) {
15389
- const wsBase = ctx.envConfig.apiUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
15390
- return {
15391
- ctx,
15392
- workspaceUuid: "",
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 as GravityRailClient2 } from "@gravity-rail/sdk";
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 GravityRailClient2(accessToken, ctx.envConfig.apiUrl);
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