@gravity-rail/cli 0.7.0 → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,39 +1,1103 @@
1
- #!/usr/bin/env node
2
- import { AGUIClient, clearScreen, printMessage, requireAuth, originateConciergeCall, originateConciergeSms, getConciergeChatMessages, getConciergeChat, createConciergeChat, listConciergeChats, selectFromList, formatTimestamp, getTextDelta, getErrorMessage } from './chunk-3EO4COHB.js';
3
- import { CLIError } from './chunk-XRBVAFCB.js';
4
- import path3 from 'path';
5
- import * as readline from 'readline';
6
- import readline__default from 'readline';
7
- import fs, { createWriteStream } from 'fs';
8
- import os from 'os';
9
- import { EventEmitter } from 'events';
10
- import { execSync, spawn, spawnSync } from 'child_process';
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // src/cli-utils/config.ts
12
+ import { existsSync, readFileSync } from "fs";
13
+ import { dirname, join, resolve } from "path";
14
+ var CALLBACK_PORT, CALLBACK_PATH, REDIRECT_URI;
15
+ var init_config = __esm({
16
+ "src/cli-utils/config.ts"() {
17
+ "use strict";
18
+ CALLBACK_PORT = 31337;
19
+ CALLBACK_PATH = "/oauth/callback";
20
+ REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;
21
+ }
22
+ });
23
+
24
+ // src/cli-utils/auth.ts
25
+ import { createServer } from "http";
26
+ import { createHash, randomBytes } from "crypto";
27
+ import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync, chmodSync, unlinkSync } from "fs";
28
+ import { join as join2 } from "path";
29
+ import { exec } from "child_process";
30
+ function stderr(msg) {
31
+ process.stderr.write(msg + "\n");
32
+ }
33
+ function isOAuthError(body, errorCode) {
34
+ try {
35
+ const parsed = JSON.parse(body);
36
+ if (parsed.error === errorCode) return true;
37
+ } catch {
38
+ }
39
+ return body.includes(errorCode);
40
+ }
41
+ function validateTokenResponse(tokenData) {
42
+ if (!tokenData.access_token || typeof tokenData.access_token !== "string") {
43
+ throw new Error("Token response missing access_token");
44
+ }
45
+ if (tokenData.token_type && String(tokenData.token_type).toLowerCase() !== "bearer") {
46
+ throw new Error(`Unsupported token type: ${tokenData.token_type}`);
47
+ }
48
+ let expiresIn = 86400;
49
+ if (tokenData.expires_in != null) {
50
+ const raw = Number(tokenData.expires_in);
51
+ if (!Number.isFinite(raw) || raw < 60 || raw > 31536e3) {
52
+ stderr(` Warning: unusual expires_in value (${tokenData.expires_in}), using default`);
53
+ } else {
54
+ expiresIn = raw;
55
+ }
56
+ }
57
+ return {
58
+ accessToken: tokenData.access_token,
59
+ refreshToken: typeof tokenData.refresh_token === "string" ? tokenData.refresh_token : void 0,
60
+ expiresIn
61
+ };
62
+ }
63
+ function generatePKCE() {
64
+ const codeVerifier = randomBytes(64).toString("base64url").slice(0, 128);
65
+ const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
66
+ return { codeVerifier, codeChallenge };
67
+ }
68
+ function openBrowser(url) {
69
+ const platform = process.platform;
70
+ let cmd;
71
+ if (platform === "darwin") {
72
+ cmd = `open "${url}"`;
73
+ } else if (platform === "win32") {
74
+ cmd = `start "" "${url}"`;
75
+ } else {
76
+ cmd = `xdg-open "${url}"`;
77
+ }
78
+ exec(cmd, (err) => {
79
+ if (err) {
80
+ stderr(" Could not open browser automatically.");
81
+ }
82
+ });
83
+ }
84
+ function startCallbackServer() {
85
+ return new Promise((resolve2, reject) => {
86
+ let authCode = null;
87
+ let error = null;
88
+ let receivedState = null;
89
+ const server = createServer((req, res) => {
90
+ const url = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
91
+ if (url.pathname !== CALLBACK_PATH) {
92
+ res.writeHead(404);
93
+ res.end("Not found");
94
+ return;
95
+ }
96
+ if (url.searchParams.has("error")) {
97
+ error = url.searchParams.get("error");
98
+ const errorDesc = url.searchParams.get("error_description") || "Unknown error";
99
+ res.writeHead(200, { "Content-Type": "text/html" });
100
+ res.end(`
101
+ <html>
102
+ <head><title>Authorization Failed</title></head>
103
+ <body style="font-family: system-ui; text-align: center; padding: 50px;">
104
+ <h1 style="color: #dc2626;">Authorization Failed</h1>
105
+ <p>${errorDesc}</p>
106
+ <p>You can close this window.</p>
107
+ </body>
108
+ </html>
109
+ `);
110
+ server.close();
111
+ reject(new Error(`Authorization failed: ${error}`));
112
+ } else if (url.searchParams.has("code")) {
113
+ authCode = url.searchParams.get("code");
114
+ receivedState = url.searchParams.get("state");
115
+ res.writeHead(200, { "Content-Type": "text/html" });
116
+ res.end(`
117
+ <html>
118
+ <head><title>Authorization Successful</title></head>
119
+ <body style="font-family: system-ui; text-align: center; padding: 50px;">
120
+ <h1 style="color: #16a34a;">Authorization Successful!</h1>
121
+ <p>You can close this window and return to the terminal.</p>
122
+ <script>setTimeout(() => window.close(), 2000);</script>
123
+ </body>
124
+ </html>
125
+ `);
126
+ server.close();
127
+ resolve2({ code: authCode, state: receivedState });
128
+ } else {
129
+ res.writeHead(400);
130
+ res.end("Missing code parameter");
131
+ }
132
+ });
133
+ server.listen(CALLBACK_PORT, "localhost", () => {
134
+ });
135
+ const timeout = setTimeout(
136
+ () => {
137
+ if (!authCode && !error) {
138
+ server.close();
139
+ reject(new Error("Authorization timeout - no response received"));
140
+ }
141
+ },
142
+ 5 * 60 * 1e3
143
+ );
144
+ timeout.unref();
145
+ });
146
+ }
147
+ async function dynamicClientRegistration(envConfig, clientName = "Gravity CLI") {
148
+ stderr(" Registering OAuth client...");
149
+ const registrationUrl = `${envConfig.apiUrl}/api/v2/oauth/register`;
150
+ const registrationData = {
151
+ client_name: clientName,
152
+ redirect_uris: [REDIRECT_URI],
153
+ grant_types: ["authorization_code", "refresh_token"],
154
+ response_types: ["code"],
155
+ token_endpoint_auth_method: "client_secret_basic",
156
+ scope: "workspace:admin"
157
+ };
158
+ const response = await fetch(registrationUrl, {
159
+ method: "POST",
160
+ headers: { "Content-Type": "application/json" },
161
+ body: JSON.stringify(registrationData)
162
+ });
163
+ if (!response.ok) {
164
+ throw new Error(`Client registration failed: ${response.status} - ${await response.text()}`);
165
+ }
166
+ return await response.json();
167
+ }
168
+ async function performOAuthFlow(options) {
169
+ const { env, envConfig, credentialsManager, clientName = "Gravity CLI" } = options;
170
+ let hasRetried = false;
171
+ while (true) {
172
+ let creds = credentialsManager.load(env);
173
+ if (!creds || !creds.clientId) {
174
+ const registration = await dynamicClientRegistration(envConfig, clientName);
175
+ creds = {
176
+ clientId: registration.client_id,
177
+ clientSecret: registration.client_secret
178
+ };
179
+ credentialsManager.save(env, creds);
180
+ } else {
181
+ stderr(" Using cached client credentials");
182
+ }
183
+ const { codeVerifier, codeChallenge } = generatePKCE();
184
+ const state = randomBytes(32).toString("base64url");
185
+ const authParams = new URLSearchParams({
186
+ response_type: "code",
187
+ client_id: creds.clientId,
188
+ redirect_uri: REDIRECT_URI,
189
+ scope: "workspace:admin",
190
+ state,
191
+ code_challenge: codeChallenge,
192
+ code_challenge_method: "S256"
193
+ });
194
+ const authUrl = `${envConfig.apiUrl}/api/v2/oauth/authorize?${authParams}`;
195
+ const callbackPromise = startCallbackServer();
196
+ stderr("\n Opening browser for authorization...");
197
+ stderr(` If browser doesn't open, visit:
198
+ ${authUrl}
199
+ `);
200
+ openBrowser(authUrl);
201
+ stderr(" Waiting for authorization (timeout: 5 minutes)...");
202
+ let code;
203
+ let receivedState;
204
+ try {
205
+ ({ code, state: receivedState } = await callbackPromise);
206
+ } catch (err) {
207
+ const msg = err instanceof Error ? err.message : String(err);
208
+ if (!hasRetried && isOAuthError(msg, "invalid_client")) {
209
+ stderr(" Cached client credentials expired, re-registering...");
210
+ credentialsManager.clear(env);
211
+ hasRetried = true;
212
+ continue;
213
+ }
214
+ throw err;
215
+ }
216
+ if (receivedState !== state) {
217
+ throw new Error("State mismatch - possible CSRF attack");
218
+ }
219
+ stderr(" Authorization code received!");
220
+ stderr(" Exchanging code for tokens...");
221
+ const tokenUrl = `${envConfig.apiUrl}/api/v2/oauth/token`;
222
+ const authHeader = Buffer.from(`${creds.clientId}:${creds.clientSecret}`).toString("base64");
223
+ const tokenResponse = await fetch(tokenUrl, {
224
+ method: "POST",
225
+ headers: {
226
+ Authorization: `Basic ${authHeader}`,
227
+ "Content-Type": "application/x-www-form-urlencoded"
228
+ },
229
+ body: new URLSearchParams({
230
+ grant_type: "authorization_code",
231
+ code,
232
+ redirect_uri: REDIRECT_URI,
233
+ code_verifier: codeVerifier
234
+ })
235
+ });
236
+ if (!tokenResponse.ok) {
237
+ const body = await tokenResponse.text();
238
+ if (!hasRetried && isOAuthError(body, "invalid_client")) {
239
+ stderr(" Cached client credentials expired, re-registering...");
240
+ credentialsManager.clear(env);
241
+ hasRetried = true;
242
+ continue;
243
+ }
244
+ throw new Error(`Token exchange failed: ${tokenResponse.status} - ${body}`);
245
+ }
246
+ const tokenData = await tokenResponse.json();
247
+ const { accessToken, refreshToken, expiresIn } = validateTokenResponse(tokenData);
248
+ creds.accessToken = accessToken;
249
+ creds.refreshToken = refreshToken ?? creds.refreshToken;
250
+ creds.expiresAt = new Date(Date.now() + expiresIn * 1e3).toISOString();
251
+ credentialsManager.save(env, creds);
252
+ stderr(" Authentication successful!");
253
+ return { accessToken, credentials: creds };
254
+ }
255
+ }
256
+ var init_auth = __esm({
257
+ "src/cli-utils/auth.ts"() {
258
+ "use strict";
259
+ init_config();
260
+ }
261
+ });
262
+
263
+ // src/cli-utils/ui.ts
264
+ import * as readline from "readline";
265
+ async function selectFromList(items, options = {}) {
266
+ const {
267
+ title = "Select an item",
268
+ labelFn = (item) => String(item.name || item.title || item),
269
+ detailFn = null,
270
+ env = null,
271
+ emptyMessage = "No items found.",
272
+ allowNew = false,
273
+ newLabel = "Create new..."
274
+ } = options;
275
+ if ((!items || items.length === 0) && !allowNew) {
276
+ console.log(`
277
+ ${emptyMessage}`);
278
+ return { item: null, isNew: false };
279
+ }
280
+ const displayItems = allowNew ? [NEW_ITEM_MARKER, ...items] : items;
281
+ return new Promise((resolve2) => {
282
+ let selectedIndex = allowNew ? 1 : 0;
283
+ let filterText = "";
284
+ const getFilteredItems = () => {
285
+ if (!filterText) return displayItems;
286
+ const lower = filterText.toLowerCase();
287
+ return displayItems.filter((item) => {
288
+ if (item === NEW_ITEM_MARKER) return true;
289
+ const label = labelFn(item);
290
+ return label.toLowerCase().includes(lower);
291
+ });
292
+ };
293
+ const render = () => {
294
+ const filtered = getFilteredItems();
295
+ process.stdout.write("\x1B[2J\x1B[H");
296
+ console.log("=".repeat(60));
297
+ console.log(`${title} (\u2191/\u2193 to move, Enter to select, Esc to cancel)`);
298
+ console.log("=".repeat(60));
299
+ if (env) {
300
+ console.log(`Environment: ${env}`);
301
+ }
302
+ console.log(`Filter: ${filterText}${filterText ? "" : "(type to filter)"}`);
303
+ console.log("");
304
+ if (filtered.length === 0) {
305
+ console.log(` ${emptyMessage}`);
306
+ console.log("");
307
+ return;
308
+ }
309
+ const maxVisible = 15;
310
+ let startIdx = Math.max(0, selectedIndex - Math.floor(maxVisible / 2));
311
+ let endIdx = Math.min(filtered.length, startIdx + maxVisible);
312
+ if (endIdx - startIdx < maxVisible && startIdx > 0) {
313
+ startIdx = Math.max(0, endIdx - maxVisible);
314
+ }
315
+ if (startIdx > 0) {
316
+ console.log(` ... ${startIdx} more above ...`);
317
+ }
318
+ for (let i = startIdx; i < endIdx; i++) {
319
+ const item = filtered[i];
320
+ const prefix = i === selectedIndex ? "> " : " ";
321
+ let line;
322
+ if (item === NEW_ITEM_MARKER) {
323
+ line = `\x1B[32m${newLabel}\x1B[0m`;
324
+ } else {
325
+ const typedItem = item;
326
+ line = labelFn(typedItem);
327
+ if (detailFn) {
328
+ const detail = detailFn(typedItem);
329
+ if (detail) {
330
+ line = `${line} \x1B[90m${detail}\x1B[0m`;
331
+ }
332
+ }
333
+ }
334
+ if (i === selectedIndex) {
335
+ console.log(`\x1B[7m${prefix}${line}\x1B[0m`);
336
+ } else {
337
+ console.log(`${prefix}${line}`);
338
+ }
339
+ }
340
+ if (endIdx < filtered.length) {
341
+ console.log(` ... ${filtered.length - endIdx} more below ...`);
342
+ }
343
+ console.log("");
344
+ console.log(` ${filtered.length} item(s)`);
345
+ };
346
+ readline.emitKeypressEvents(process.stdin);
347
+ if (process.stdin.isTTY) {
348
+ process.stdin.setRawMode(true);
349
+ }
350
+ render();
351
+ const handleKeypress = (str, key) => {
352
+ const filtered = getFilteredItems();
353
+ if (key.name === "up") {
354
+ selectedIndex = Math.max(0, selectedIndex - 1);
355
+ render();
356
+ } else if (key.name === "down") {
357
+ selectedIndex = Math.min(filtered.length - 1, selectedIndex + 1);
358
+ render();
359
+ } else if (key.name === "return") {
360
+ cleanup();
361
+ if (filtered.length > 0 && selectedIndex < filtered.length) {
362
+ const selected = filtered[selectedIndex];
363
+ if (selected === NEW_ITEM_MARKER) {
364
+ resolve2({ item: null, isNew: true });
365
+ } else {
366
+ resolve2({ item: selected, isNew: false });
367
+ }
368
+ } else {
369
+ resolve2({ item: null, isNew: false });
370
+ }
371
+ } else if (key.name === "escape" || key.ctrl && key.name === "c") {
372
+ cleanup();
373
+ resolve2({ item: null, isNew: false });
374
+ } else if (key.name === "backspace") {
375
+ filterText = filterText.slice(0, -1);
376
+ selectedIndex = allowNew ? 0 : 0;
377
+ render();
378
+ } else if (str && str.length === 1 && !key.ctrl && !key.meta) {
379
+ filterText += str;
380
+ selectedIndex = allowNew ? 0 : 0;
381
+ render();
382
+ }
383
+ };
384
+ const cleanup = () => {
385
+ process.stdin.removeListener("keypress", handleKeypress);
386
+ if (process.stdin.isTTY) {
387
+ process.stdin.setRawMode(false);
388
+ }
389
+ process.stdin.pause();
390
+ process.stdout.write("\x1B[2J\x1B[H");
391
+ };
392
+ process.stdin.on("keypress", handleKeypress);
393
+ process.stdin.resume();
394
+ });
395
+ }
396
+ function printTable(items, columns, options = {}) {
397
+ if (!items || items.length === 0) {
398
+ console.log("\nNo items found.");
399
+ return;
400
+ }
401
+ if (options.title) {
402
+ console.log(`
403
+ === ${options.title} ===`);
404
+ }
405
+ if (options.env) {
406
+ console.log(`Environment: ${options.env}`);
407
+ }
408
+ console.log("");
409
+ const widths = columns.map((col) => {
410
+ const maxContent = Math.max(...items.map((item) => String(col.getValue(item) ?? "").length));
411
+ const width = Math.max(col.header.length, maxContent);
412
+ return col.maxWidth ? Math.min(width, col.maxWidth) : width;
413
+ });
414
+ const headerLine = columns.map((col, i) => col.header.padEnd(widths[i])).join(" ");
415
+ console.log(headerLine);
416
+ console.log(widths.map((w) => "-".repeat(w)).join(" "));
417
+ for (const item of items) {
418
+ const row = columns.map((col, i) => {
419
+ const value = String(col.getValue(item) ?? "");
420
+ const width = widths[i];
421
+ return value.slice(0, width).padEnd(width);
422
+ }).join(" ");
423
+ console.log(row);
424
+ }
425
+ console.log("");
426
+ console.log(`Total: ${items.length} item(s)`);
427
+ }
428
+ function formatTimestamp(isoString) {
429
+ if (!isoString) return "";
430
+ const date = new Date(isoString);
431
+ const now = /* @__PURE__ */ new Date();
432
+ const diff = now.getTime() - date.getTime();
433
+ if (diff < 6e4) return "just now";
434
+ if (diff < 36e5) return `${Math.floor(diff / 6e4)}m ago`;
435
+ if (diff < 864e5) return `${Math.floor(diff / 36e5)}h ago`;
436
+ if (diff < 6048e5) return `${Math.floor(diff / 864e5)}d ago`;
437
+ return date.toLocaleDateString();
438
+ }
439
+ function wrapText(text, width = 70) {
440
+ const words = text.split(" ");
441
+ const lines = [];
442
+ let currentLine = "";
443
+ for (const word of words) {
444
+ if (currentLine.length + word.length + 1 <= width) {
445
+ currentLine += (currentLine ? " " : "") + word;
446
+ } else {
447
+ if (currentLine) lines.push(currentLine);
448
+ currentLine = word;
449
+ }
450
+ }
451
+ if (currentLine) lines.push(currentLine);
452
+ return lines;
453
+ }
454
+ function printMessage(content, role, indent = " ") {
455
+ const roleColors = {
456
+ user: "\x1B[34m",
457
+ // Blue
458
+ assistant: "\x1B[32m",
459
+ // Green
460
+ system: "\x1B[90m"
461
+ // Gray
462
+ };
463
+ const resetColor = "\x1B[0m";
464
+ const color = roleColors[role] || resetColor;
465
+ const label = role === "user" ? "You" : role === "assistant" ? "Assistant" : "System";
466
+ console.log(`${indent}${color}[${label}]${resetColor}`);
467
+ const lines = content.split("\n");
468
+ for (const line of lines) {
469
+ const wrapped = wrapText(line, 70);
470
+ for (const wrappedLine of wrapped) {
471
+ console.log(`${indent} ${wrappedLine}`);
472
+ }
473
+ }
474
+ console.log("");
475
+ }
476
+ function clearScreen() {
477
+ process.stdout.write("\x1B[2J\x1B[H");
478
+ }
479
+ var NEW_ITEM_MARKER;
480
+ var init_ui = __esm({
481
+ "src/cli-utils/ui.ts"() {
482
+ "use strict";
483
+ NEW_ITEM_MARKER = Symbol("NEW_ITEM");
484
+ }
485
+ });
486
+
487
+ // src/cli-utils/agui.ts
488
+ function isTextContentEvent(event) {
489
+ const type = event.type.toLowerCase();
490
+ return type === "text_message_content";
491
+ }
492
+ function isErrorEvent(event) {
493
+ const type = event.type.toLowerCase();
494
+ return type === "run_error";
495
+ }
496
+ function getTextDelta(event) {
497
+ if (isTextContentEvent(event) && event.delta) {
498
+ return event.delta;
499
+ }
500
+ return null;
501
+ }
502
+ function getErrorMessage(event) {
503
+ if (isErrorEvent(event)) {
504
+ return event.message || event.code || "Unknown error";
505
+ }
506
+ return null;
507
+ }
508
+ var AGUIClient;
509
+ var init_agui = __esm({
510
+ "src/cli-utils/agui.ts"() {
511
+ "use strict";
512
+ AGUIClient = class {
513
+ endpointUrl;
514
+ baseUrl;
515
+ workspaceUuid;
516
+ chatId;
517
+ accessToken;
518
+ debug;
519
+ model;
520
+ appContext;
521
+ constructor(options) {
522
+ this.baseUrl = options.baseUrl;
523
+ this.workspaceUuid = options.workspaceUuid ?? "";
524
+ this.chatId = options.chatId ?? 0;
525
+ if (!options.endpointUrl && (!options.workspaceUuid || options.chatId === void 0)) {
526
+ throw new Error("AGUIClient requires workspaceUuid and chatId, or endpointUrl");
527
+ }
528
+ this.endpointUrl = options.endpointUrl ?? `${options.baseUrl}/api/v2/w/${options.workspaceUuid}/web-chat/${options.chatId}/chat/agui`;
529
+ this.accessToken = options.accessToken;
530
+ this.debug = options.debug ?? false;
531
+ this.model = "model" in options ? options.model : "manager";
532
+ this.appContext = options.appContext;
533
+ }
534
+ inferenceStatusUrl() {
535
+ if (!this.workspaceUuid || this.chatId <= 0) return null;
536
+ return `${this.baseUrl}/api/v2/w/${this.workspaceUuid}/chats/${this.chatId}/inference/status`;
537
+ }
538
+ async waitForInferenceInactive(maxWaitMs = 6e4) {
539
+ const url = this.inferenceStatusUrl();
540
+ if (!url) return false;
541
+ const deadline = Date.now() + maxWaitMs;
542
+ while (Date.now() < deadline) {
543
+ try {
544
+ const res = await fetch(url, {
545
+ headers: { Authorization: `Bearer ${this.accessToken}` }
546
+ });
547
+ if (res.ok) {
548
+ const body = await res.json();
549
+ if (!body.active) return true;
550
+ }
551
+ } catch (err) {
552
+ if (this.debug) {
553
+ console.log(
554
+ `[DEBUG] waitForInferenceInactive fetch failed: ${err instanceof Error ? err.message : String(err)}`
555
+ );
556
+ }
557
+ }
558
+ await new Promise((r) => setTimeout(r, 2e3));
559
+ }
560
+ return false;
561
+ }
562
+ /**
563
+ * Send messages and stream the response
564
+ */
565
+ async sendMessage(messages, onEvent, options) {
566
+ return this.streamRequest({ messages }, onEvent, options);
567
+ }
568
+ /**
569
+ * Stream an AG-UI request (initial messages or HITL resume).
570
+ */
571
+ async streamRequest(request, onEvent, options) {
572
+ const body = JSON.stringify({
573
+ ...this.model ? { model: this.model } : {},
574
+ messages: request.messages ?? [],
575
+ ...this.appContext ? { appContext: this.appContext } : {},
576
+ ...request.resumeValue !== void 0 ? { resumeValue: request.resumeValue } : {},
577
+ ...request.resumeInterruptId ? { resumeInterruptId: request.resumeInterruptId } : {},
578
+ ...options?.autoApprove ? { context: { autoApprove: true } } : {}
579
+ });
580
+ if (this.debug) {
581
+ console.log(`
582
+ [DEBUG] POST ${this.endpointUrl}`);
583
+ console.log(
584
+ `[DEBUG] Body: ${request.resumeValue !== void 0 ? "resume" : `${(request.messages ?? []).length} message(s)`}`
585
+ );
586
+ }
587
+ await this.postStream(body, onEvent, options);
588
+ }
589
+ async postStream(body, onEvent, options, conflictRetries = 0) {
590
+ const response = await fetch(this.endpointUrl, {
591
+ method: "POST",
592
+ headers: {
593
+ Authorization: `Bearer ${this.accessToken}`,
594
+ "Content-Type": "application/json",
595
+ Accept: "text/event-stream"
596
+ },
597
+ body
598
+ });
599
+ if (this.debug) {
600
+ console.log(`[DEBUG] Response status: ${response.status}`);
601
+ }
602
+ if (response.status === 409 && conflictRetries < 3) {
603
+ const ready = await this.waitForInferenceInactive();
604
+ if (ready) {
605
+ await this.postStream(body, onEvent, options, conflictRetries + 1);
606
+ return;
607
+ }
608
+ }
609
+ if (!response.ok) {
610
+ const errorText = await response.text().catch(() => "");
611
+ if (this.debug) {
612
+ console.log(`[DEBUG] Error response: ${errorText}`);
613
+ }
614
+ throw new Error(
615
+ `Chat request failed: ${response.status}${errorText ? `: ${errorText.slice(0, 200)}` : ""}`
616
+ );
617
+ }
618
+ const reader = response.body.getReader();
619
+ const decoder = new TextDecoder();
620
+ let buffer = "";
621
+ while (true) {
622
+ const { done, value } = await reader.read();
623
+ if (done) break;
624
+ buffer += decoder.decode(value, { stream: true });
625
+ const lines = buffer.split("\n");
626
+ buffer = lines.pop() || "";
627
+ for (const line of lines) {
628
+ if (line.startsWith("data: ")) {
629
+ const data = line.slice(6);
630
+ if (data === "[DONE]") {
631
+ onEvent({ type: "done" });
632
+ continue;
633
+ }
634
+ try {
635
+ const event = JSON.parse(data);
636
+ onEvent(event);
637
+ } catch {
638
+ }
639
+ }
640
+ }
641
+ }
642
+ }
643
+ };
644
+ }
645
+ });
646
+
647
+ // src/cli-utils/index.ts
648
+ var init_cli_utils = __esm({
649
+ "src/cli-utils/index.ts"() {
650
+ "use strict";
651
+ init_config();
652
+ init_auth();
653
+ init_ui();
654
+ init_agui();
655
+ }
656
+ });
657
+
658
+ // src/lib/errors.ts
659
+ var CLIError;
660
+ var init_errors = __esm({
661
+ "src/lib/errors.ts"() {
662
+ "use strict";
663
+ CLIError = class extends Error {
664
+ constructor(message, exitCode = 1) {
665
+ super(message);
666
+ this.exitCode = exitCode;
667
+ this.name = "CLIError";
668
+ }
669
+ };
670
+ }
671
+ });
672
+
673
+ // src/lib/pagination.ts
674
+ function isListResult(data) {
675
+ if (!data || typeof data !== "object" || Array.isArray(data)) return false;
676
+ const obj = data;
677
+ return Array.isArray(obj.items) && ("supportsAll" in obj || "meta" in obj && (obj.meta === void 0 || typeof obj.meta === "object"));
678
+ }
679
+ function isPagePaginatedResponse(data) {
680
+ if (!data || typeof data !== "object") return false;
681
+ const obj = data;
682
+ if (!Array.isArray(obj.items) || typeof obj.page !== "number") return false;
683
+ if (typeof obj.totalPages === "number") return true;
684
+ if (typeof obj.total === "number" && typeof obj.pageSize === "number") return true;
685
+ return false;
686
+ }
687
+ function isCursorPaginatedResponse(data) {
688
+ if (!data || typeof data !== "object") return false;
689
+ const obj = data;
690
+ return Array.isArray(obj.items) && typeof obj.hasMore === "boolean";
691
+ }
692
+ function extractPaginationMeta(data) {
693
+ if (isPagePaginatedResponse(data)) {
694
+ const totalPages = data.totalPages ?? (data.pageSize ? Math.ceil(data.total / data.pageSize) : void 0);
695
+ return {
696
+ total: data.total,
697
+ page: data.page,
698
+ totalPages
699
+ };
700
+ }
701
+ if (isCursorPaginatedResponse(data)) {
702
+ return {
703
+ hasMore: data.hasMore,
704
+ nextCursor: data.nextCursor
705
+ };
706
+ }
707
+ return void 0;
708
+ }
709
+ function formatPaginationWarning(meta, supportsAll = true) {
710
+ if (!meta) return void 0;
711
+ const allHint = supportsAll ? " Use --all to fetch all pages." : "";
712
+ if (meta.totalPages !== void 0 && meta.totalPages > 1) {
713
+ const pageNum = meta.page ?? 1;
714
+ return `Showing page ${pageNum} of ${meta.totalPages} (${meta.total} total items).${allHint}`;
715
+ }
716
+ if (meta.hasMore === true) {
717
+ return `More results available.${allHint}`;
718
+ }
719
+ return void 0;
720
+ }
721
+ function unwrapItems(data) {
722
+ if (Array.isArray(data)) return data;
723
+ if (data && typeof data === "object" && "items" in data && Array.isArray(data.items)) {
724
+ return data.items;
725
+ }
726
+ return [data];
727
+ }
728
+ var init_pagination = __esm({
729
+ "src/lib/pagination.ts"() {
730
+ "use strict";
731
+ init_errors();
732
+ }
733
+ });
734
+
735
+ // src/lib/output.ts
736
+ function outputData(data, opts, columns, paginationMeta, supportsAll) {
737
+ let items;
738
+ let meta;
739
+ let allSupported;
740
+ if (isListResult(data)) {
741
+ items = data.items;
742
+ meta = data.meta;
743
+ allSupported = data.supportsAll ?? false;
744
+ } else {
745
+ items = unwrapItems(data);
746
+ meta = paginationMeta ?? extractPaginationMeta(data);
747
+ allSupported = supportsAll ?? false;
748
+ }
749
+ const warning = formatPaginationWarning(meta, allSupported);
750
+ if (warning) {
751
+ process.stderr.write(warning + "\n");
752
+ }
753
+ switch (opts.outputFormat) {
754
+ case "json":
755
+ process.stdout.write(JSON.stringify(items, null, 2) + "\n");
756
+ break;
757
+ case "jsonl":
758
+ for (const item of items) {
759
+ process.stdout.write(JSON.stringify(item) + "\n");
760
+ }
761
+ break;
762
+ case "table":
763
+ if (columns) {
764
+ printTable(items, columns);
765
+ } else {
766
+ process.stdout.write(JSON.stringify(items, null, 2) + "\n");
767
+ }
768
+ break;
769
+ }
770
+ }
771
+ function outputSingle(data, opts, columns) {
772
+ switch (opts.outputFormat) {
773
+ case "json":
774
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
775
+ break;
776
+ case "jsonl":
777
+ process.stdout.write(JSON.stringify(data) + "\n");
778
+ break;
779
+ case "table":
780
+ if (columns) {
781
+ printTable([data], columns);
782
+ } else {
783
+ process.stdout.write(JSON.stringify(data, null, 2) + "\n");
784
+ }
785
+ break;
786
+ }
787
+ }
788
+ function stderr2(msg) {
789
+ process.stderr.write(msg + "\n");
790
+ }
791
+ var init_output = __esm({
792
+ "src/lib/output.ts"() {
793
+ "use strict";
794
+ init_cli_utils();
795
+ init_pagination();
796
+ }
797
+ });
798
+
799
+ // src/lib/realm.ts
800
+ import { execSync } from "child_process";
801
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, statSync, writeFileSync as writeFileSync2 } from "fs";
802
+ import { dirname as dirname2, join as join3 } from "path";
803
+ var CACHE_MAX_AGE_MS;
804
+ var init_realm = __esm({
805
+ "src/lib/realm.ts"() {
806
+ "use strict";
807
+ CACHE_MAX_AGE_MS = 60 * 60 * 1e3;
808
+ }
809
+ });
810
+
811
+ // src/lib/org-auth.ts
812
+ import { execFile as execFile2 } from "child_process";
813
+ var ORG_AUTH_TIMEOUT_MS;
814
+ var init_org_auth = __esm({
815
+ "src/lib/org-auth.ts"() {
816
+ "use strict";
817
+ init_output();
818
+ ORG_AUTH_TIMEOUT_MS = 5 * 60 * 1e3;
819
+ }
820
+ });
821
+
822
+ // src/lib/pinned-fetch.ts
823
+ import { Agent, fetch as undiciFetch } from "undici";
824
+ var init_pinned_fetch = __esm({
825
+ "src/lib/pinned-fetch.ts"() {
826
+ "use strict";
827
+ }
828
+ });
829
+
830
+ // src/lib/context.ts
831
+ import { GravityRailClient } from "@gravity-rail/sdk";
832
+ import { homedir } from "os";
833
+ function requireAuth(ctx) {
834
+ if (!("apiClient" in ctx)) {
835
+ throw new Error("Command handler accessed apiClient without declaring requiresAuth");
836
+ }
837
+ return ctx;
838
+ }
839
+ var init_context = __esm({
840
+ "src/lib/context.ts"() {
841
+ "use strict";
842
+ init_cli_utils();
843
+ init_output();
844
+ init_realm();
845
+ init_errors();
846
+ init_org_auth();
847
+ init_pinned_fetch();
848
+ }
849
+ });
850
+
851
+ // src/lib/concierge-api.ts
852
+ function baseUrl(ctx) {
853
+ return `${ctx.envConfig.apiUrl}/api/v2/concierge`;
854
+ }
855
+ function authHeaders(ctx) {
856
+ return {
857
+ Authorization: `Bearer ${ctx.accessToken}`,
858
+ "Content-Type": "application/json"
859
+ };
860
+ }
861
+ function toCamelChat(raw) {
862
+ return {
863
+ id: raw.id,
864
+ uuid: raw.uuid,
865
+ chatType: raw.chat_type,
866
+ title: raw.title,
867
+ threadId: raw.thread_id,
868
+ createdAt: raw.created_at,
869
+ updatedAt: raw.updated_at
870
+ };
871
+ }
872
+ function toCamelMessage(raw) {
873
+ return {
874
+ id: raw.id,
875
+ role: raw.role,
876
+ content: raw.content ?? "",
877
+ createdAt: raw.createdAt ?? raw.created_at ?? null,
878
+ toolCalls: raw.toolCalls ?? raw.tool_calls ?? null,
879
+ toolCallId: raw.toolCallId ?? raw.tool_call_id ?? null
880
+ };
881
+ }
882
+ async function listConciergeChats(ctx) {
883
+ const res = await fetch(`${baseUrl(ctx)}/chats`, { headers: authHeaders(ctx) });
884
+ if (!res.ok) throw new Error(`Failed to list concierge chats: ${res.status}`);
885
+ const data = await res.json();
886
+ return data.map(toCamelChat);
887
+ }
888
+ async function createConciergeChat(ctx, title) {
889
+ const res = await fetch(`${baseUrl(ctx)}/chats`, {
890
+ method: "POST",
891
+ headers: authHeaders(ctx),
892
+ body: JSON.stringify({ title: title ?? null })
893
+ });
894
+ if (!res.ok) throw new Error(`Failed to create concierge chat: ${res.status}`);
895
+ return toCamelChat(await res.json());
896
+ }
897
+ async function getConciergeChat(ctx, chatId) {
898
+ const res = await fetch(`${baseUrl(ctx)}/chats/${chatId}`, { headers: authHeaders(ctx) });
899
+ if (!res.ok) throw new Error(`Failed to get concierge chat ${chatId}: ${res.status}`);
900
+ return toCamelChat(await res.json());
901
+ }
902
+ async function originateConciergeCall(ctx, kind = "concierge_setup") {
903
+ const res = await fetch(`${baseUrl(ctx)}/calls`, {
904
+ method: "POST",
905
+ headers: authHeaders(ctx),
906
+ body: JSON.stringify({ kind })
907
+ });
908
+ if (res.status === 412) {
909
+ throw new Error("Your account phone is not verified. Run `gr account verify-phone` first.");
910
+ }
911
+ if (!res.ok) {
912
+ const detail = await res.text();
913
+ throw new Error(`Failed to start concierge call: ${res.status} ${detail}`);
914
+ }
915
+ return await res.json();
916
+ }
917
+ async function originateConciergeSms(ctx, kind = "concierge_setup") {
918
+ const res = await fetch(`${baseUrl(ctx)}/sms`, {
919
+ method: "POST",
920
+ headers: authHeaders(ctx),
921
+ body: JSON.stringify({ kind })
922
+ });
923
+ if (res.status === 412) {
924
+ throw new Error("Your account phone is not verified. Run `gr account verify-phone` first.");
925
+ }
926
+ if (!res.ok) {
927
+ const detail = await res.text();
928
+ throw new Error(`Failed to start concierge SMS: ${res.status} ${detail}`);
929
+ }
930
+ return await res.json();
931
+ }
932
+ async function startAccountPhoneVerify(ctx, phone) {
933
+ const res = await fetch(`${ctx.envConfig.apiUrl}/api/v2/account/phone/verify/start`, {
934
+ method: "POST",
935
+ headers: authHeaders(ctx),
936
+ body: JSON.stringify({ phone })
937
+ });
938
+ if (!res.ok) {
939
+ const detail = await res.text();
940
+ throw new Error(`Failed to send verification code: ${res.status} ${detail}`);
941
+ }
942
+ }
943
+ async function confirmAccountPhoneVerify(ctx, code) {
944
+ const res = await fetch(`${ctx.envConfig.apiUrl}/api/v2/account/phone/verify/confirm`, {
945
+ method: "POST",
946
+ headers: authHeaders(ctx),
947
+ body: JSON.stringify({ code })
948
+ });
949
+ if (!res.ok) {
950
+ const detail = await res.text();
951
+ throw new Error(`Failed to confirm verification code: ${res.status} ${detail}`);
952
+ }
953
+ }
954
+ async function getConciergeChatMessages(ctx, chatId) {
955
+ const res = await fetch(`${baseUrl(ctx)}/chats/${chatId}/messages`, {
956
+ headers: authHeaders(ctx)
957
+ });
958
+ if (!res.ok)
959
+ throw new Error(`Failed to get concierge messages for chat ${chatId}: ${res.status}`);
960
+ const data = await res.json();
961
+ return data.map(toCamelMessage);
962
+ }
963
+ var init_concierge_api = __esm({
964
+ "src/lib/concierge-api.ts"() {
965
+ "use strict";
966
+ }
967
+ });
968
+
969
+ // src/commands/account-verify-phone.ts
970
+ var account_verify_phone_exports = {};
971
+ __export(account_verify_phone_exports, {
972
+ accountVerifyPhoneCommand: () => accountVerifyPhoneCommand
973
+ });
974
+ import * as readline6 from "readline/promises";
975
+ import { stdin as input, stdout as output } from "process";
976
+ async function accountVerifyPhoneCommand(hctx) {
977
+ const ctx = requireAuth(hctx.ctx);
978
+ const phoneArg = typeof hctx.args.phone === "string" ? hctx.args.phone : void 0;
979
+ process.stderr.write(`${TCPA_NOTICE}
980
+ `);
981
+ const phone = phoneArg ?? await promptLine("Phone number (E.164, e.g. +15551234567): ");
982
+ if (!phone.trim()) {
983
+ throw new Error("Phone number is required");
984
+ }
985
+ await startAccountPhoneVerify(ctx, phone.trim());
986
+ process.stderr.write("Verification code sent via SMS.\n");
987
+ const code = await promptLine("Enter 6-digit code: ");
988
+ await confirmAccountPhoneVerify(ctx, code.trim());
989
+ process.stderr.write(
990
+ "Phone verified. You can use `gr concierge chat --call` or `gr concierge chat --sms`.\n"
991
+ );
992
+ }
993
+ async function promptLine(question) {
994
+ const rl = readline6.createInterface({ input, output });
995
+ try {
996
+ return await rl.question(question) ?? "";
997
+ } finally {
998
+ rl.close();
999
+ }
1000
+ }
1001
+ var TCPA_NOTICE;
1002
+ var init_account_verify_phone = __esm({
1003
+ "src/commands/account-verify-phone.ts"() {
1004
+ "use strict";
1005
+ init_concierge_api();
1006
+ init_context();
1007
+ TCPA_NOTICE = "By verifying you agree to receive calls and SMS from Gravity Rail at this number.";
1008
+ }
1009
+ });
11
1010
 
1011
+ // src/commands/auth-browser.ts
1012
+ init_output();
1013
+ import { execFile } from "child_process";
1014
+ async function authBrowserCommand(ctx, args) {
1015
+ const noOpen = Boolean(args["no-open"]);
1016
+ const account = await ctx.apiClient.getUserAccount();
1017
+ if (!account.email) {
1018
+ process.stderr.write("Error: No email associated with current account\n");
1019
+ process.exit(1);
1020
+ }
1021
+ const response = await ctx.apiClient.requestMagicLink(account.email);
1022
+ if (!response.success || !response.magicLinkUrl) {
1023
+ process.stderr.write("Error: Failed to create magic link\n");
1024
+ process.exit(1);
1025
+ }
1026
+ const url = response.magicLinkUrl;
1027
+ if (noOpen) {
1028
+ process.stdout.write(url + "\n");
1029
+ return;
1030
+ }
1031
+ stderr2(`Opening browser for ${account.email} (${ctx.env})...`);
1032
+ openUrl(url);
1033
+ stderr2("Browser opened. Link expires in 15 minutes.");
1034
+ }
1035
+ function openUrl(url) {
1036
+ const platform = process.platform;
1037
+ let command;
1038
+ let args;
1039
+ if (platform === "darwin") {
1040
+ command = "open";
1041
+ args = [url];
1042
+ } else if (platform === "win32") {
1043
+ command = "cmd.exe";
1044
+ args = ["/c", "start", "", url];
1045
+ } else {
1046
+ command = "xdg-open";
1047
+ args = [url];
1048
+ }
1049
+ execFile(command, args, (err) => {
1050
+ if (err) {
1051
+ process.stderr.write("Warning: Could not open browser automatically\n");
1052
+ }
1053
+ });
1054
+ }
1055
+
1056
+ // src/commands/manage.ts
1057
+ init_cli_utils();
1058
+ init_errors();
1059
+ init_context();
1060
+
1061
+ // src/lib/cli-client-context.ts
1062
+ import path from "path";
12
1063
  function buildCliConciergeAppContext() {
13
- const shell = process.env.SHELL ? path3.basename(process.env.SHELL) : void 0;
1064
+ const shell = process.env.SHELL ? path.basename(process.env.SHELL) : void 0;
14
1065
  return {
15
1066
  clientSurface: "cli",
16
1067
  channel: "text",
17
1068
  cliContext: {
18
1069
  shell,
19
1070
  os: process.platform,
20
- cwdBasename: path3.basename(process.cwd())
1071
+ cwdBasename: path.basename(process.cwd())
21
1072
  }
22
1073
  };
23
1074
  }
1075
+
1076
+ // src/lib/chat-session.ts
1077
+ init_cli_utils();
1078
+
1079
+ // src/lib/concierge-agui-loop.ts
1080
+ init_agui();
1081
+
1082
+ // src/lib/cli-hitl.ts
1083
+ import readline3 from "readline";
1084
+
1085
+ // src/lib/local-files.ts
1086
+ import fs from "fs";
1087
+ import path2 from "path";
24
1088
  var DEFAULT_MAX_READ_BYTES = 5e4;
25
1089
  var ABSOLUTE_MAX_READ_BYTES = 1e6;
26
1090
  function resolveSafePath(root, relativePath) {
27
- const rootResolved = path3.resolve(root);
28
- const target = path3.resolve(rootResolved, relativePath || ".");
29
- if (target !== rootResolved && !target.startsWith(rootResolved + path3.sep)) {
1091
+ const rootResolved = path2.resolve(root);
1092
+ const target = path2.resolve(rootResolved, relativePath || ".");
1093
+ if (target !== rootResolved && !target.startsWith(rootResolved + path2.sep)) {
30
1094
  return null;
31
1095
  }
32
1096
  let cursor = rootResolved;
33
- const rest = path3.relative(rootResolved, target);
1097
+ const rest = path2.relative(rootResolved, target);
34
1098
  if (rest && rest !== ".") {
35
- for (const segment of rest.split(path3.sep)) {
36
- cursor = path3.join(cursor, segment);
1099
+ for (const segment of rest.split(path2.sep)) {
1100
+ cursor = path2.join(cursor, segment);
37
1101
  try {
38
1102
  const lstat = fs.lstatSync(cursor);
39
1103
  if (lstat.isSymbolicLink()) {
@@ -94,7 +1158,7 @@ function executeCliLocalFileAction(root, interrupt, opts) {
94
1158
  if (interrupt.content === void 0) {
95
1159
  return { ok: false, error: "Missing content for write_file" };
96
1160
  }
97
- fs.mkdirSync(path3.dirname(safe), { recursive: true });
1161
+ fs.mkdirSync(path2.dirname(safe), { recursive: true });
98
1162
  fs.writeFileSync(safe, interrupt.content, "utf8");
99
1163
  return { ok: true, content: "written" };
100
1164
  }
@@ -120,6 +1184,12 @@ function isCliLocalFileInterrupt(value) {
120
1184
  const v = value;
121
1185
  return v.type === "cli_local_file" && typeof v.action === "string" && typeof v.path === "string";
122
1186
  }
1187
+
1188
+ // src/lib/trusted-dirs.ts
1189
+ import fs2 from "fs";
1190
+ import os from "os";
1191
+ import path3 from "path";
1192
+ import readline2 from "readline";
123
1193
  var STORE_VERSION = 1;
124
1194
  function storePath() {
125
1195
  const base = process.env.XDG_CONFIG_HOME ?? (process.platform === "win32" ? path3.join(process.env.APPDATA ?? os.homedir(), "gravityrail") : path3.join(os.homedir(), ".config", "gravityrail"));
@@ -128,7 +1198,7 @@ function storePath() {
128
1198
  function loadTrustedDirsStore() {
129
1199
  const file = storePath();
130
1200
  try {
131
- const raw = JSON.parse(fs.readFileSync(file, "utf8"));
1201
+ const raw = JSON.parse(fs2.readFileSync(file, "utf8"));
132
1202
  if (raw?.version === STORE_VERSION && Array.isArray(raw.always)) {
133
1203
  return { version: STORE_VERSION, always: raw.always.map((p) => path3.resolve(p)) };
134
1204
  }
@@ -139,8 +1209,8 @@ function loadTrustedDirsStore() {
139
1209
  function saveTrustedDirsStore(store) {
140
1210
  const file = storePath();
141
1211
  const dir = path3.dirname(file);
142
- fs.mkdirSync(dir, { recursive: true, mode: 448 });
143
- fs.writeFileSync(file, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
1212
+ fs2.mkdirSync(dir, { recursive: true, mode: 448 });
1213
+ fs2.writeFileSync(file, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
144
1214
  }
145
1215
  function isDirectoryTrusted(dirPath, opts) {
146
1216
  if (opts?.trustCwdFlag) return true;
@@ -149,8 +1219,8 @@ function isDirectoryTrusted(dirPath, opts) {
149
1219
  return store.always.includes(resolved);
150
1220
  }
151
1221
  async function promptTrustInteractive(dirPath) {
152
- const rl = readline__default.createInterface({ input: process.stdin, output: process.stderr });
153
- const question = (q) => new Promise((resolve) => rl.question(q, resolve));
1222
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
1223
+ const question = (q) => new Promise((resolve2) => rl.question(q, resolve2));
154
1224
  process.stderr.write(
155
1225
  `
156
1226
  Gravity Rail may read files in:
@@ -270,9 +1340,9 @@ ${prompt}
270
1340
  resumeInterruptId: typeof payload.interrupt_id === "string" ? payload.interrupt_id : void 0
271
1341
  };
272
1342
  }
273
- const rl = readline__default.createInterface({ input: process.stdin, output: process.stderr });
274
- const answer = await new Promise((resolve) => {
275
- rl.question("Your choice (value): ", resolve);
1343
+ const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
1344
+ const answer = await new Promise((resolve2) => {
1345
+ rl.question("Your choice (value): ", resolve2);
276
1346
  });
277
1347
  rl.close();
278
1348
  return {
@@ -330,6 +1400,13 @@ async function runConciergeAguiLoop(client, initialMessages, onEvent, opts) {
330
1400
  }
331
1401
  return { streamText, hasToolCalls };
332
1402
  }
1403
+
1404
+ // src/lib/chat-session.ts
1405
+ init_errors();
1406
+ import * as readline5 from "readline";
1407
+
1408
+ // src/lib/realtime-client.ts
1409
+ import { EventEmitter } from "events";
333
1410
  var RealtimeClient = class extends EventEmitter {
334
1411
  ws = null;
335
1412
  connected = false;
@@ -349,7 +1426,7 @@ var RealtimeClient = class extends EventEmitter {
349
1426
  new Error("WebSocket not available. Node.js 22+ is required for realtime mode.")
350
1427
  );
351
1428
  }
352
- return new Promise((resolve, reject) => {
1429
+ return new Promise((resolve2, reject) => {
353
1430
  const params = new URLSearchParams();
354
1431
  if (this.options.vadMode) {
355
1432
  params.append("vad", this.options.vadMode);
@@ -404,7 +1481,7 @@ var RealtimeClient = class extends EventEmitter {
404
1481
  }, 1e4);
405
1482
  this.once("connected", () => {
406
1483
  clearTimeout(timeout);
407
- resolve();
1484
+ resolve2();
408
1485
  });
409
1486
  this.once("error", () => {
410
1487
  clearTimeout(timeout);
@@ -578,6 +1655,9 @@ var RealtimeClient = class extends EventEmitter {
578
1655
  }
579
1656
  }
580
1657
  };
1658
+
1659
+ // src/lib/chat-tui.ts
1660
+ import { createWriteStream } from "fs";
581
1661
  function isToolCall(item) {
582
1662
  return "kind" in item && item.kind === "tool_call";
583
1663
  }
@@ -1352,11 +2432,16 @@ function buildDebugSummary(_direction, msg) {
1352
2432
  }
1353
2433
  return type;
1354
2434
  }
2435
+
2436
+ // src/lib/audio.ts
2437
+ import { spawn, execSync as execSync2, spawnSync } from "child_process";
2438
+ import { EventEmitter as EventEmitter2 } from "events";
2439
+ import * as readline4 from "readline";
1355
2440
  var SAMPLE_RATE = 24e3;
1356
2441
  var MIC_CHUNK_BYTES = 4800;
1357
2442
  function checkSoxAvailable() {
1358
2443
  try {
1359
- execSync("which sox", { stdio: "pipe" });
2444
+ execSync2("which sox", { stdio: "pipe" });
1360
2445
  return true;
1361
2446
  } catch {
1362
2447
  return false;
@@ -1369,16 +2454,16 @@ async function listOutputDevices() {
1369
2454
  return listDevices("play");
1370
2455
  }
1371
2456
  function listDevices(command) {
1372
- return new Promise((resolve) => {
2457
+ return new Promise((resolve2) => {
1373
2458
  try {
1374
2459
  const proc = spawnSync(command, ["--list-devices"], {
1375
2460
  encoding: "utf-8",
1376
2461
  stdio: ["ignore", "pipe", "pipe"],
1377
2462
  timeout: 5e3
1378
2463
  });
1379
- const output = (proc.stdout ?? "") + (proc.stderr ?? "");
2464
+ const output2 = (proc.stdout ?? "") + (proc.stderr ?? "");
1380
2465
  const devices = [];
1381
- const lines = output.split("\n");
2466
+ const lines = output2.split("\n");
1382
2467
  for (const line of lines) {
1383
2468
  const match = line.match(/(?:Input|Output)\s+Device\s+(\d+)\s*:\s*(.+)/i);
1384
2469
  if (match && match[1] && match[2]) {
@@ -1393,9 +2478,9 @@ function listDevices(command) {
1393
2478
  if (devices.length === 0) {
1394
2479
  devices.push({ id: "default", name: "System Default", isDefault: true });
1395
2480
  }
1396
- resolve(devices);
2481
+ resolve2(devices);
1397
2482
  } catch {
1398
- resolve([{ id: "default", name: "System Default", isDefault: true }]);
2483
+ resolve2([{ id: "default", name: "System Default", isDefault: true }]);
1399
2484
  }
1400
2485
  });
1401
2486
  }
@@ -1403,7 +2488,7 @@ async function selectDevice(devices, label) {
1403
2488
  if (devices.length <= 1) {
1404
2489
  return devices[0];
1405
2490
  }
1406
- const rl = readline.createInterface({
2491
+ const rl = readline4.createInterface({
1407
2492
  input: process.stdin,
1408
2493
  output: process.stderr,
1409
2494
  terminal: true
@@ -1417,14 +2502,14 @@ async function selectDevice(devices, label) {
1417
2502
  process.stderr.write(` ${i + 1}. ${dev.name}${marker}
1418
2503
  `);
1419
2504
  }
1420
- return new Promise((resolve) => {
2505
+ return new Promise((resolve2) => {
1421
2506
  const defaultIdx = devices.findIndex((d) => d.isDefault);
1422
2507
  const defaultNum = defaultIdx >= 0 ? defaultIdx + 1 : 1;
1423
2508
  rl.question(` Select [${defaultNum}]: `, (answer) => {
1424
2509
  rl.close();
1425
2510
  const num = answer.trim() ? parseInt(answer.trim(), 10) : defaultNum;
1426
2511
  const idx = Math.max(0, Math.min(devices.length - 1, num - 1));
1427
- resolve(devices[idx]);
2512
+ resolve2(devices[idx]);
1428
2513
  });
1429
2514
  });
1430
2515
  }
@@ -1495,7 +2580,7 @@ var MicCapture = class {
1495
2580
  this.buffer = Buffer.alloc(0);
1496
2581
  }
1497
2582
  };
1498
- var AudioPlayer = class extends EventEmitter {
2583
+ var AudioPlayer = class extends EventEmitter2 {
1499
2584
  process = null;
1500
2585
  deviceId;
1501
2586
  // When true, we own the SoX process's stdin and are actively streaming to it.
@@ -1722,6 +2807,7 @@ async function singleTurnChatSession(session, message, debug, autoApprove = fals
1722
2807
  {
1723
2808
  autoApprove,
1724
2809
  allowWrite: allowWrite || !!session.allowWrite,
2810
+ debug,
1725
2811
  trustCwd: session.trustCwd,
1726
2812
  trustDir: session.trustDir,
1727
2813
  nonInteractive: true
@@ -1797,11 +2883,11 @@ async function interactiveChatSession(session, debug, autoApprove, allowWrite) {
1797
2883
  conversationMessages.push({ role: msg.role, content: text });
1798
2884
  }
1799
2885
  }
1800
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
1801
- return new Promise((resolve) => {
2886
+ const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
2887
+ return new Promise((resolve2) => {
1802
2888
  const prompt = () => {
1803
- rl.question("\x1B[34mYou:\x1B[0m ", async (input) => {
1804
- const trimmed = input.trim();
2889
+ rl.question("\x1B[34mYou:\x1B[0m ", async (input2) => {
2890
+ const trimmed = input2.trim();
1805
2891
  if (!trimmed) {
1806
2892
  prompt();
1807
2893
  return;
@@ -1856,7 +2942,7 @@ async function interactiveChatSession(session, debug, autoApprove, allowWrite) {
1856
2942
  };
1857
2943
  rl.on("close", () => {
1858
2944
  console.log("\nGoodbye!");
1859
- resolve();
2945
+ resolve2();
1860
2946
  });
1861
2947
  prompt();
1862
2948
  });
@@ -1953,8 +3039,8 @@ async function realtimeChatSession(session, vad, debug) {
1953
3039
  });
1954
3040
  tui.loadHistory(history);
1955
3041
  tui.start();
1956
- await new Promise((resolve) => {
1957
- client.once("closed", resolve);
3042
+ await new Promise((resolve2) => {
3043
+ client.once("closed", resolve2);
1958
3044
  });
1959
3045
  }
1960
3046
  async function voiceChatSession(session, vad, args, debug) {
@@ -2071,8 +3157,8 @@ Connecting to ${session.label.toLowerCase()}...
2071
3157
  tui.loadHistory(history);
2072
3158
  tui.start();
2073
3159
  mic.start(inputDeviceId);
2074
- await new Promise((resolve) => {
2075
- client.once("closed", resolve);
3160
+ await new Promise((resolve2) => {
3161
+ client.once("closed", resolve2);
2076
3162
  });
2077
3163
  }
2078
3164
  async function listAudioDevices() {
@@ -2095,7 +3181,148 @@ async function listAudioDevices() {
2095
3181
  }
2096
3182
  }
2097
3183
 
3184
+ // src/commands/manage.ts
3185
+ async function manageCommand(hctx) {
3186
+ const ctx = requireAuth(hctx.ctx);
3187
+ const { workspace, args } = hctx;
3188
+ const asMember = typeof args["as-member"] === "string" ? args["as-member"] : void 0;
3189
+ const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
3190
+ const message = typeof args.message === "string" ? args.message : void 0;
3191
+ const chat = await getOrCreateChat(ctx, workspace, chatIdArg, asMember, "manager", !!message);
3192
+ const session = buildWorkspaceSession(ctx, workspace, chat, "Workspace Manager", {
3193
+ appContext: buildCliConciergeAppContext(),
3194
+ allowWrite: true,
3195
+ trustCwd: !!args["trust-cwd"],
3196
+ trustDir: typeof args["trust-dir"] === "string" ? args["trust-dir"] : void 0
3197
+ });
3198
+ process.stderr.write(`Chat #${session.chatId}
3199
+ `);
3200
+ await runChatSession(session, args);
3201
+ process.stderr.write(`Chat #${session.chatId}
3202
+ `);
3203
+ }
3204
+ async function chatCommand(hctx) {
3205
+ const ctx = requireAuth(hctx.ctx);
3206
+ const { workspace, args } = hctx;
3207
+ const chatIdArg = typeof args["chat-id"] === "string" ? args["chat-id"] : void 0;
3208
+ const workflowId = typeof args.workflow === "string" ? args.workflow : void 0;
3209
+ const memberId = typeof args.member === "string" ? args.member : void 0;
3210
+ let chat;
3211
+ if (chatIdArg) {
3212
+ const id = Number(chatIdArg);
3213
+ if (isNaN(id)) throw new CLIError("--chat-id must be a number");
3214
+ chat = await ctx.apiClient.getChat(workspace, id);
3215
+ } else if (workflowId) {
3216
+ chat = await createAssignmentChat(ctx.apiClient, workspace, Number(workflowId), memberId);
3217
+ } else if (memberId) {
3218
+ chat = await createMemberChat(ctx.apiClient, workspace, memberId);
3219
+ } else {
3220
+ throw new CLIError(
3221
+ 'Specify --workflow <id>, --member <id>, or --chat-id <id>.\nUse "gr manage" for workspace manager chats.'
3222
+ );
3223
+ }
3224
+ const session = buildWorkspaceSession(ctx, workspace, chat, "Chat");
3225
+ await runChatSession(session, args);
3226
+ }
3227
+ function buildWorkspaceSession(ctx, workspace, chat, label, extra) {
3228
+ const wsBase = ctx.envConfig.apiUrl.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
3229
+ return {
3230
+ ctx,
3231
+ workspaceUuid: workspace,
3232
+ chatId: chat.id,
3233
+ chatUuid: chat.uuid,
3234
+ chatTitle: chat.title || chat.uuid,
3235
+ aguiEndpointUrl: `${ctx.envConfig.apiUrl}/api/v2/w/${workspace}/web-chat/${chat.id}/chat/agui`,
3236
+ wsUrl: `${wsBase}/api/v2/w/${workspace}/realtime/stream-ws`,
3237
+ loadMessages: () => ctx.apiClient.getChatMessages(workspace, chat.id, 50).catch(() => []),
3238
+ label,
3239
+ model: "manager",
3240
+ ...extra
3241
+ };
3242
+ }
3243
+ async function getOrCreateChat(ctx, workspace, chatIdArg, asMemberId, chatType, skipSelection = false) {
3244
+ if (chatIdArg) {
3245
+ const chatId = Number(chatIdArg);
3246
+ if (isNaN(chatId)) throw new CLIError("--chat-id must be a number");
3247
+ return ctx.apiClient.getChat(workspace, chatId);
3248
+ }
3249
+ if (skipSelection) {
3250
+ return createManagerChat(ctx.apiClient, workspace, asMemberId);
3251
+ }
3252
+ const chatsResponse = await ctx.apiClient.getChats(workspace, {
3253
+ chatType,
3254
+ sortBy: "updatedAt",
3255
+ sortOrder: "desc",
3256
+ pageSize: 20
3257
+ });
3258
+ const chats = chatsResponse.items || [];
3259
+ const { item: selectedChat, isNew } = await selectFromList(chats, {
3260
+ title: "Select a chat or create a new one",
3261
+ labelFn: (chat) => chat.title || `Chat ${chat.uuid.slice(0, 8)}...`,
3262
+ detailFn: (chat) => {
3263
+ const time = formatTimestamp(chat.updatedAt);
3264
+ const msgCount = chat.messageCount || 0;
3265
+ return `${msgCount} msg${msgCount !== 1 ? "s" : ""} - ${time}`;
3266
+ },
3267
+ emptyMessage: "No existing chats.",
3268
+ allowNew: true,
3269
+ newLabel: "+ Start new chat"
3270
+ });
3271
+ if (!selectedChat && !isNew) {
3272
+ throw new CLIError("No chat selected.");
3273
+ }
3274
+ if (isNew) {
3275
+ return createManagerChat(ctx.apiClient, workspace, asMemberId);
3276
+ }
3277
+ return selectedChat;
3278
+ }
3279
+ async function createManagerChat(apiClient, workspace, asMemberId) {
3280
+ const data = {
3281
+ chatType: "manager",
3282
+ channel: "web-chat"
3283
+ };
3284
+ if (asMemberId) {
3285
+ data.memberId = Number(asMemberId);
3286
+ }
3287
+ const chat = await apiClient.createChat(workspace, data);
3288
+ process.stderr.write(`Created chat #${chat.id}
3289
+ `);
3290
+ return chat;
3291
+ }
3292
+ async function createAssignmentChat(apiClient, workspace, workflowId, asMemberId) {
3293
+ if (isNaN(workflowId)) throw new CLIError("--workflow must be a number");
3294
+ const data = {
3295
+ chatType: "assignment",
3296
+ channel: "web-chat",
3297
+ workflowId
3298
+ };
3299
+ if (asMemberId) {
3300
+ data.memberId = Number(asMemberId);
3301
+ }
3302
+ const chat = await apiClient.createChat(workspace, data);
3303
+ process.stderr.write(`Created assignment chat #${chat.id} (workflow ${workflowId})
3304
+ `);
3305
+ return chat;
3306
+ }
3307
+ async function createMemberChat(apiClient, workspace, memberId) {
3308
+ const id = Number(memberId);
3309
+ if (isNaN(id)) throw new CLIError("--member must be a number");
3310
+ const data = {
3311
+ chatType: "member",
3312
+ channel: "web-chat",
3313
+ memberId: id
3314
+ };
3315
+ const chat = await apiClient.createChat(workspace, data);
3316
+ process.stderr.write(`Created DM chat #${chat.id} (member ${memberId})
3317
+ `);
3318
+ return chat;
3319
+ }
3320
+
2098
3321
  // src/commands/concierge.ts
3322
+ init_cli_utils();
3323
+ init_concierge_api();
3324
+ init_context();
3325
+ init_errors();
2099
3326
  async function conciergeOutboundCommand(hctx) {
2100
3327
  const ctx = requireAuth(hctx.ctx);
2101
3328
  const { args } = hctx;
@@ -2194,6 +3421,436 @@ async function getOrCreateConciergeChat(ctx, chatIdArg, skipSelection) {
2194
3421
  return selectedChat;
2195
3422
  }
2196
3423
 
2197
- export { buildCliConciergeAppContext, conciergeCommand, conciergeOutboundCommand, runChatSession };
2198
- //# sourceMappingURL=chunk-QVG4L6BE.js.map
2199
- //# sourceMappingURL=chunk-QVG4L6BE.js.map
3424
+ // src/commands/dev.ts
3425
+ init_errors();
3426
+ init_output();
3427
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync4 } from "fs";
3428
+ import { execSync as execSync3 } from "child_process";
3429
+ import { join as join4 } from "path";
3430
+ async function devImportLocalCommand(ctx, workspace) {
3431
+ stderr2(`Exporting workspace ${workspace}...`);
3432
+ const exportData = await ctx.apiClient.exportWorkspace(workspace);
3433
+ stderr2("Export successful!");
3434
+ stderr2(` Workflows: ${(exportData.workflows || []).length}`);
3435
+ stderr2(` Tasks: ${(exportData.tasks || []).length}`);
3436
+ stderr2(` Data Types: ${(exportData.dataTypes || []).length}`);
3437
+ stderr2(` Sites: ${(exportData.sites || []).length}`);
3438
+ const slug = workspace.slice(0, 8);
3439
+ const importCacheDir = join4(ctx.credentialsManager.getCacheDir(), "imports");
3440
+ if (!existsSync4(importCacheDir)) {
3441
+ mkdirSync3(importCacheDir, { recursive: true });
3442
+ }
3443
+ const filename = `ws-${slug}-${workspace}.json`;
3444
+ const filepath = join4(importCacheDir, filename);
3445
+ writeFileSync3(filepath, JSON.stringify(exportData, null, 2));
3446
+ stderr2(`Saved to: ${filepath}`);
3447
+ stderr2("Running dev:restore...");
3448
+ const rootDir = findProjectRoot();
3449
+ const relativePath = filepath.replace(rootDir + "/", "");
3450
+ try {
3451
+ const result = execSync3(`yarn dev:restore ${relativePath}`, {
3452
+ cwd: rootDir,
3453
+ stdio: "pipe",
3454
+ encoding: "utf-8"
3455
+ });
3456
+ process.stderr.write(result);
3457
+ const failed = result.includes("Failed/Skipped: ") && !result.includes("Failed/Skipped: 0") || result.includes("Successfully restored: 0");
3458
+ if (failed) throw new CLIError("Import failed");
3459
+ } catch (error) {
3460
+ if (error instanceof CLIError) throw error;
3461
+ const execError = error;
3462
+ if (execError.stdout) process.stderr.write(execError.stdout);
3463
+ if (execError.stderr) process.stderr.write(execError.stderr);
3464
+ throw new CLIError("dev:restore failed");
3465
+ }
3466
+ stderr2(`Import complete: ${workspace}`);
3467
+ }
3468
+ function findProjectRoot() {
3469
+ let dir = process.cwd();
3470
+ while (dir !== "/") {
3471
+ const pkgPath = join4(dir, "package.json");
3472
+ if (existsSync4(pkgPath)) {
3473
+ try {
3474
+ const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
3475
+ if (pkg.workspaces) return dir;
3476
+ } catch {
3477
+ }
3478
+ }
3479
+ dir = join4(dir, "..");
3480
+ }
3481
+ return process.cwd();
3482
+ }
3483
+
3484
+ // src/commands/get-started.ts
3485
+ init_context();
3486
+ var WEB_SETUP = "https://app.gravityrail.com/setup";
3487
+ var WEB_GET_STARTED = "https://app.gravityrail.com/get-started";
3488
+ var HUMAN_GUIDE = `Gravity Rail \u2014 getting started
3489
+
3490
+ 1. Authenticate: gr login --env prod
3491
+ Confirm: gr whoami
3492
+
3493
+ 2. Recommended path \u2014 concierge (no workspace required):
3494
+ gr concierge chat --allow-write
3495
+ The concierge can create your org (auto-created on first login), list products,
3496
+ start Stripe checkout, and create your first workspace.
3497
+
3498
+ 3. Verify workspaces: gr workspaces list
3499
+
3500
+ 4. Web wizards (human-friendly): ${WEB_SETUP} and ${WEB_GET_STARTED}
3501
+
3502
+ Headless boundary: Stripe checkout needs a human in a browser \u2014 the concierge returns
3503
+ a checkout URL; an agent should hand that link to the user, not loop on payment.
3504
+
3505
+ Agent-readable steps: gr get-started --format llm
3506
+ `;
3507
+ var LLM_GUIDE = `# Gravity Rail CLI bootstrap (agent instructions)
3508
+
3509
+ ## Preconditions
3510
+ - Install: \`npm install -g @gravity-rail/cli\` or \`npx @gravity-rail/cli\`
3511
+ - Authenticate: \`gr login --env prod\` (interactive OAuth). Verify with \`gr whoami\`.
3512
+ - **Monorepo dev:** from gravity-team with Docker dev up, use \`yarn gr <cmd> --realm superadmin\` (reads \`dev-realms.json\`; no browser). Rebuild: \`yarn workspace @gravity-rail/gr-cli build\`.
3513
+
3514
+ ## Important facts
3515
+ - **Agents/CI:** do not leave stdin open on read-only commands; use \`GRAVITY_RAIL_API_KEY\` or redirect stdin from /dev/null. Commands only read stdin when \`--data\`/\`--file\` is passed.
3516
+ - Use \`--env dev|staging|prod\` (default prod) so credentials match the target; \`gr whoami\` shows the active env.
3517
+ - After login, an **organization is created automatically** \u2014 do not create a duplicate org unless \`gr organizations list\` is empty.
3518
+ - **Workspace is required** for workflow/agent/task commands (\`-w <workspace-uuid>\`).
3519
+ - **Concierge does not require \`-w\`** \u2014 use it for first-run setup.
3520
+
3521
+ ## Recommended path (concierge-driven)
3522
+ \`\`\`bash
3523
+ gr concierge chat --allow-write --message "Help me set up my first Gravity Rail workspace"
3524
+ \`\`\`
3525
+ Flags for unattended turns:
3526
+ - \`--message\` / \`-m\`: single-turn (non-interactive)
3527
+ - \`--auto-approve\`: skip HITL tool approval where policy allows
3528
+ - \`--allow-write\`: required for create/update tools
3529
+
3530
+ The concierge can: list/create orgs, list org/workspace products, create Stripe checkout sessions, create workspace specs, and \`create_workspace\`.
3531
+
3532
+ ## Manual CLI path (if not using concierge)
3533
+ 1. \`gr whoami\` \u2014 confirm account
3534
+ 2. \`gr organizations list\` \u2014 note org slug/UUID
3535
+ 3. \`gr concierge chat\` OR web: ${WEB_GET_STARTED}
3536
+ 4. Product purchase: concierge returns Stripe checkout URL \u2192 **human must complete in browser**
3537
+ 5. \`gr workspaces list\` \u2014 pick workspace UUID for \`-w\`
3538
+
3539
+ ## Support (account-level, no workspace)
3540
+ \`\`\`bash
3541
+ gr support create --type question --title "..." --description "..." --allow-write
3542
+ gr support list
3543
+ \`\`\`
3544
+
3545
+ ## Web fallbacks
3546
+ - Concierge / setup: ${WEB_SETUP}
3547
+ - Wizard: ${WEB_GET_STARTED}
3548
+
3549
+ ## Headless limits (do not retry forever)
3550
+ - Stripe payment / checkout: requires human browser
3551
+ - OAuth login: requires human browser
3552
+ - Directory trust for future local-file tools: use \`--trust-cwd\` only after user confirms files are safe
3553
+
3554
+ ## Local project files (CLI only)
3555
+ After directory trust + \`--allow-write\`, the concierge can use \`read_local_file\`, \`list_local_dir\`, \`write_local_file\` (client-executed via HITL).
3556
+
3557
+ ## Verify success
3558
+ \`gr workspaces list\` then \`gr workflows list -w <uuid>\` or \`gr agents list -w <uuid>\`
3559
+
3560
+ ## Local phone / voice QA (monorepo dev)
3561
+ \`\`\`bash
3562
+ yarn dev:twilio-ngrok --start-ngrok --url gravityrail.ngrok.io dev.<you> -w acme-demo
3563
+ yarn gr phone setup
3564
+ yarn gr account verify-phone --phone +1\u2026 --allow-write --realm superadmin
3565
+ yarn gr concierge chat --call --allow-write --realm superadmin
3566
+ yarn gr phone outbound --realm acme-demo/acme-admin --workflow-id 1 --allow-write
3567
+ \`\`\`
3568
+ See \`.claude/skills/phone-voice-qa/SKILL.md\` and \`./scripts/e2e-voice-phone-matrix.sh\`.
3569
+ `;
3570
+ async function getStartedCommand(hctx) {
3571
+ const format = hctx.args.format ?? hctx.args.agent;
3572
+ const isLlm = format === "llm" || format === "agent" || hctx.args["format"] === "llm" || !!hctx.args.agent;
3573
+ if (!isLlm) {
3574
+ try {
3575
+ requireAuth(hctx.ctx);
3576
+ } catch {
3577
+ }
3578
+ process.stdout.write(HUMAN_GUIDE);
3579
+ return;
3580
+ }
3581
+ process.stdout.write(LLM_GUIDE);
3582
+ }
3583
+
3584
+ // src/commands/login.ts
3585
+ init_cli_utils();
3586
+ init_output();
3587
+ import { GravityRailClient as GravityRailClient2 } from "@gravity-rail/sdk";
3588
+ async function loginCommand(ctx) {
3589
+ const { accessToken } = await performOAuthFlow({
3590
+ env: ctx.env,
3591
+ envConfig: ctx.envConfig,
3592
+ credentialsManager: ctx.credentialsManager
3593
+ });
3594
+ const apiClient = new GravityRailClient2(accessToken, ctx.envConfig.apiUrl);
3595
+ const account = await apiClient.getUserAccount();
3596
+ stderr2(`Logged in as ${account.name} (${account.email})`);
3597
+ }
3598
+ async function logoutCommand(ctx) {
3599
+ ctx.credentialsManager.clear(ctx.env);
3600
+ stderr2(`Credentials cleared for ${ctx.env}`);
3601
+ }
3602
+
3603
+ // src/commands/organizations.ts
3604
+ init_output();
3605
+ var COLUMNS = [
3606
+ { header: "NAME", getValue: (o) => o.organizationName || "", maxWidth: 35 },
3607
+ { header: "SLUG", getValue: (o) => o.organizationSlug || "", maxWidth: 25 },
3608
+ { header: "UUID", getValue: (o) => o.organizationUuid },
3609
+ { header: "ROLE", getValue: (o) => o.role || "" }
3610
+ ];
3611
+ async function organizationsListCommand(ctx) {
3612
+ const organizations = await ctx.apiClient.getMyOrganizations();
3613
+ outputData(organizations, ctx, COLUMNS);
3614
+ }
3615
+
3616
+ // src/commands/whoami.ts
3617
+ init_output();
3618
+ var COLUMNS2 = [
3619
+ { header: "NAME", getValue: (a) => a.name || "", maxWidth: 30 },
3620
+ { header: "EMAIL", getValue: (a) => a.email || "", maxWidth: 40 },
3621
+ { header: "UUID", getValue: (a) => a.uuid },
3622
+ { header: "ENV", getValue: () => "" },
3623
+ { header: "SUPERUSER", getValue: (a) => a.isSuperuser ? "yes" : "no" }
3624
+ ];
3625
+ async function whoamiCommand(ctx) {
3626
+ const account = await ctx.apiClient.getUserAccount();
3627
+ const columns = COLUMNS2.map(
3628
+ (col) => col.header === "ENV" ? { ...col, getValue: () => ctx.env } : col
3629
+ );
3630
+ outputSingle(account, ctx, columns);
3631
+ }
3632
+
3633
+ // src/register-special-commands.ts
3634
+ init_context();
3635
+ function registerSpecialCommands(registry) {
3636
+ registry.registerCommand({
3637
+ path: ["login"],
3638
+ description: "Authenticate via OAuth",
3639
+ requiresAuth: false,
3640
+ handler: async ({ ctx }) => {
3641
+ await loginCommand(ctx);
3642
+ }
3643
+ });
3644
+ registry.registerCommand({
3645
+ path: ["logout"],
3646
+ description: "Clear cached credentials",
3647
+ requiresAuth: false,
3648
+ handler: async ({ ctx }) => {
3649
+ await logoutCommand(ctx);
3650
+ }
3651
+ });
3652
+ registry.registerCommand({
3653
+ path: ["auth", "browser"],
3654
+ description: "Open an authenticated browser session using CLI credentials",
3655
+ args: [
3656
+ { name: "no-open", description: "Print the URL instead of opening browser", isFlag: true }
3657
+ ],
3658
+ handler: async ({ ctx, args }) => {
3659
+ await authBrowserCommand(requireAuth(ctx), args);
3660
+ }
3661
+ });
3662
+ registry.registerCommand({
3663
+ path: ["manage"],
3664
+ description: "Interactive workspace manager chat (text, realtime, or voice)",
3665
+ requiresWorkspace: true,
3666
+ mutates: true,
3667
+ args: [
3668
+ { name: "realtime", description: "Use WebSocket realtime connection", isFlag: true },
3669
+ { name: "voice", description: "Enable voice mode (microphone + speaker)", isFlag: true },
3670
+ { name: "message", alias: "m", description: "Single-turn: send one message and exit" },
3671
+ { name: "as-member", description: "Chat as a specific member (by numeric ID)" },
3672
+ { name: "chat-id", description: "Resume an existing chat by numeric ID" },
3673
+ { name: "vad", description: "Voice activity detection: server (default) or client" },
3674
+ { name: "list-devices", description: "List audio devices and exit", isFlag: true },
3675
+ { name: "input-device", description: "Microphone device name or ID" },
3676
+ { name: "output-device", description: "Speaker device name or ID" },
3677
+ { name: "debug", description: "Enable debug logging", isFlag: true },
3678
+ {
3679
+ name: "auto-approve",
3680
+ description: "Auto-approve all tool calls (skip HITL)",
3681
+ isFlag: true
3682
+ },
3683
+ {
3684
+ name: "trust-cwd",
3685
+ description: "Trust files in the current working directory (non-interactive)",
3686
+ isFlag: true
3687
+ },
3688
+ {
3689
+ name: "trust-dir",
3690
+ description: "Trust files in this directory (non-interactive; absolute path)"
3691
+ }
3692
+ ],
3693
+ handler: async (hctx) => {
3694
+ await manageCommand(hctx);
3695
+ }
3696
+ });
3697
+ registry.registerCommand({
3698
+ path: ["chat"],
3699
+ description: "Chat with a workflow, member, or existing chat (text, realtime, or voice)",
3700
+ requiresWorkspace: true,
3701
+ mutates: true,
3702
+ args: [
3703
+ { name: "workflow", description: "Start assignment chat with workflow (by numeric ID)" },
3704
+ { name: "member", description: "Start DM with a member (by numeric ID)" },
3705
+ { name: "chat-id", description: "Resume an existing chat by numeric ID" },
3706
+ { name: "realtime", description: "Use WebSocket realtime connection", isFlag: true },
3707
+ { name: "voice", description: "Enable voice mode (microphone + speaker)", isFlag: true },
3708
+ { name: "message", alias: "m", description: "Single-turn: send one message and exit" },
3709
+ { name: "vad", description: "Voice activity detection: server (default) or client" },
3710
+ { name: "list-devices", description: "List audio devices and exit", isFlag: true },
3711
+ { name: "input-device", description: "Microphone device name or ID" },
3712
+ { name: "output-device", description: "Speaker device name or ID" },
3713
+ { name: "debug", description: "Enable debug logging", isFlag: true },
3714
+ {
3715
+ name: "auto-approve",
3716
+ description: "Auto-approve all tool calls (skip HITL)",
3717
+ isFlag: true
3718
+ }
3719
+ ],
3720
+ handler: async (hctx) => {
3721
+ await chatCommand(hctx);
3722
+ }
3723
+ });
3724
+ registry.registerCommand({
3725
+ path: ["get-started"],
3726
+ description: "Bootstrap instructions for new users and LLM agents",
3727
+ requiresAuth: false,
3728
+ args: [
3729
+ {
3730
+ name: "format",
3731
+ description: "Output format: llm (agent-readable) or default (human)"
3732
+ },
3733
+ {
3734
+ name: "agent",
3735
+ description: "Alias for --format llm",
3736
+ isFlag: true
3737
+ }
3738
+ ],
3739
+ handler: async (hctx) => {
3740
+ await getStartedCommand(hctx);
3741
+ }
3742
+ });
3743
+ registry.registerCommand({
3744
+ path: ["setup"],
3745
+ description: "Alias for get-started \u2014 bootstrap instructions",
3746
+ requiresAuth: false,
3747
+ args: [
3748
+ { name: "format", description: "Output format: llm or default" },
3749
+ { name: "agent", description: "Alias for --format llm", isFlag: true }
3750
+ ],
3751
+ handler: async (hctx) => {
3752
+ await getStartedCommand(hctx);
3753
+ }
3754
+ });
3755
+ registry.registerCommand({
3756
+ path: ["concierge"],
3757
+ description: "Call or SMS your verified phone via the Concierge (server-side)",
3758
+ requiresWorkspace: false,
3759
+ mutates: true,
3760
+ args: [
3761
+ { name: "call", description: "Ring your verified account phone", isFlag: true },
3762
+ { name: "sms", description: "Start an SMS thread on your verified phone", isFlag: true }
3763
+ ],
3764
+ handler: async (hctx) => {
3765
+ await conciergeOutboundCommand(hctx);
3766
+ }
3767
+ });
3768
+ registry.registerCommand({
3769
+ path: ["account", "verify-phone"],
3770
+ description: "Verify your account phone for concierge chat --call / --sms",
3771
+ requiresWorkspace: false,
3772
+ mutates: true,
3773
+ args: [{ name: "phone", description: "E.164 phone number (optional; prompts if omitted)" }],
3774
+ handler: async (hctx) => {
3775
+ const { accountVerifyPhoneCommand: accountVerifyPhoneCommand2 } = await Promise.resolve().then(() => (init_account_verify_phone(), account_verify_phone_exports));
3776
+ await accountVerifyPhoneCommand2(hctx);
3777
+ }
3778
+ });
3779
+ registry.registerCommand({
3780
+ path: ["concierge", "chat"],
3781
+ description: "Interactive concierge chat (text, realtime, or voice)",
3782
+ requiresWorkspace: false,
3783
+ mutates: true,
3784
+ args: [
3785
+ { name: "realtime", description: "Use WebSocket realtime connection", isFlag: true },
3786
+ { name: "voice", description: "Enable voice mode (microphone + speaker)", isFlag: true },
3787
+ {
3788
+ name: "call",
3789
+ description: "Ring your verified account phone (server-side)",
3790
+ isFlag: true
3791
+ },
3792
+ {
3793
+ name: "sms",
3794
+ description: "SMS your verified account phone (server-side)",
3795
+ isFlag: true
3796
+ },
3797
+ { name: "message", alias: "m", description: "Single-turn: send one message and exit" },
3798
+ { name: "chat-id", description: "Resume an existing chat by numeric ID" },
3799
+ { name: "vad", description: "Voice activity detection: server (default) or client" },
3800
+ { name: "list-devices", description: "List audio devices and exit", isFlag: true },
3801
+ { name: "input-device", description: "Microphone device name or ID" },
3802
+ { name: "output-device", description: "Speaker device name or ID" },
3803
+ { name: "debug", description: "Enable debug logging", isFlag: true },
3804
+ {
3805
+ name: "auto-approve",
3806
+ description: "Auto-approve all tool calls (skip HITL)",
3807
+ isFlag: true
3808
+ },
3809
+ {
3810
+ name: "trust-cwd",
3811
+ description: "Trust files in the current working directory (non-interactive)",
3812
+ isFlag: true
3813
+ },
3814
+ {
3815
+ name: "trust-dir",
3816
+ description: "Trust files in this directory (non-interactive; absolute path)"
3817
+ }
3818
+ ],
3819
+ handler: async (hctx) => {
3820
+ await conciergeCommand(hctx);
3821
+ }
3822
+ });
3823
+ registry.registerCommand({
3824
+ path: ["dev", "import-local"],
3825
+ description: "Import remote workspace locally",
3826
+ requiresWorkspace: true,
3827
+ handler: async ({ ctx, workspace }) => {
3828
+ await devImportLocalCommand(requireAuth(ctx), workspace);
3829
+ }
3830
+ });
3831
+ registry.registerCommand({
3832
+ path: ["whoami"],
3833
+ description: "Show current user info",
3834
+ handler: async ({ ctx }) => {
3835
+ await whoamiCommand(requireAuth(ctx));
3836
+ }
3837
+ });
3838
+ registry.registerCommand({
3839
+ path: ["organizations", "list"],
3840
+ description: "List organizations",
3841
+ handler: async ({ ctx }) => {
3842
+ await organizationsListCommand(requireAuth(ctx));
3843
+ }
3844
+ });
3845
+ registry.registerCommand({
3846
+ path: ["orgs", "list"],
3847
+ description: "List organizations",
3848
+ handler: async ({ ctx }) => {
3849
+ await organizationsListCommand(requireAuth(ctx));
3850
+ }
3851
+ });
3852
+ }
3853
+ export {
3854
+ registerSpecialCommands
3855
+ };
3856
+ //# sourceMappingURL=register-special-commands.js.map