@gravity-rail/cli 0.13.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-verify-phone-7YQXKU3X.js → account-verify-phone-N7EJS4J3.js} +3 -3
- package/dist/{chunk-GRLUFHMI.js → chunk-LE5IR5MC.js} +24 -6
- package/dist/chunk-LE5IR5MC.js.map +1 -0
- package/dist/{chunk-SIV2WU2I.js → chunk-XSFAJ5ZO.js} +124 -21
- package/dist/{chunk-SIV2WU2I.js.map → chunk-XSFAJ5ZO.js.map} +1 -1
- package/dist/cli-utils.d.ts +28 -1
- package/dist/cli-utils.js +3 -1
- package/dist/gr.js +216 -44
- package/dist/gr.js.map +1 -1
- package/dist/lib.js +88 -18
- package/dist/lib.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-GRLUFHMI.js.map +0 -1
- /package/dist/{account-verify-phone-7YQXKU3X.js.map → account-verify-phone-N7EJS4J3.js.map} +0 -0
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
confirmAccountPhoneVerify,
|
|
3
3
|
requireAuth,
|
|
4
4
|
startAccountPhoneVerify
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-LE5IR5MC.js";
|
|
6
|
+
import "./chunk-XSFAJ5ZO.js";
|
|
7
7
|
import "./chunk-WZVMWN3S.js";
|
|
8
8
|
|
|
9
9
|
// src/commands/account-verify-phone.ts
|
|
@@ -38,4 +38,4 @@ async function promptLine(question) {
|
|
|
38
38
|
export {
|
|
39
39
|
accountVerifyPhoneCommand
|
|
40
40
|
};
|
|
41
|
-
//# sourceMappingURL=account-verify-phone-
|
|
41
|
+
//# sourceMappingURL=account-verify-phone-N7EJS4J3.js.map
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
getEnvironmentConfig,
|
|
4
4
|
printTable,
|
|
5
5
|
refreshAccessToken
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XSFAJ5ZO.js";
|
|
7
7
|
import {
|
|
8
8
|
AuthRequiredError,
|
|
9
9
|
CLIError
|
|
@@ -404,6 +404,16 @@ function withAuditHeaders(fetchFn, headers) {
|
|
|
404
404
|
return fetchFn(input, { ...init, headers: reqHeaders });
|
|
405
405
|
};
|
|
406
406
|
}
|
|
407
|
+
function createBrokerFetchFromEnv(_brokerUrl) {
|
|
408
|
+
const env = typeof process !== "undefined" ? process.env : {};
|
|
409
|
+
const chatIdRaw = env.GRAVITY_RAIL_CHAT_ID;
|
|
410
|
+
const chatId = chatIdRaw ? Number(chatIdRaw) : void 0;
|
|
411
|
+
return withAuditHeaders(fetch, {
|
|
412
|
+
chatId: chatId !== void 0 && Number.isFinite(chatId) ? chatId : void 0,
|
|
413
|
+
sessionId: env.GRAVITY_RAIL_SESSION_ID,
|
|
414
|
+
toolCallId: env.GRAVITY_RAIL_TOOL_CALL_ID
|
|
415
|
+
});
|
|
416
|
+
}
|
|
407
417
|
function createSandboxFetchFromEnv(apiUrl) {
|
|
408
418
|
const env = typeof process !== "undefined" ? process.env : {};
|
|
409
419
|
const apiIp = env.GRAVITY_RAIL_API_IP;
|
|
@@ -418,12 +428,18 @@ function createSandboxFetchFromEnv(apiUrl) {
|
|
|
418
428
|
}
|
|
419
429
|
|
|
420
430
|
// src/lib/context.ts
|
|
421
|
-
function makeApiClient(accessToken, apiUrl) {
|
|
422
|
-
const
|
|
423
|
-
const apiClient = new GravityRailClient(accessToken, apiUrl,
|
|
424
|
-
registerOrgAuthHandler(apiClient, apiUrl, accessToken);
|
|
431
|
+
function makeApiClient(accessToken, apiUrl, fetchFn) {
|
|
432
|
+
const resolvedFetch = fetchFn ?? createSandboxFetchFromEnv(apiUrl);
|
|
433
|
+
const apiClient = new GravityRailClient(accessToken, apiUrl, resolvedFetch);
|
|
434
|
+
registerOrgAuthHandler(apiClient, apiUrl, accessToken ?? "");
|
|
425
435
|
return apiClient;
|
|
426
436
|
}
|
|
437
|
+
function tryBrokerContext(ctx) {
|
|
438
|
+
const brokerUrl = process.env.GRAVITY_RAIL_BROKER_URL;
|
|
439
|
+
if (!brokerUrl) return null;
|
|
440
|
+
const apiClient = makeApiClient(void 0, brokerUrl, createBrokerFetchFromEnv(brokerUrl));
|
|
441
|
+
return { ...ctx, accessToken: "", apiClient };
|
|
442
|
+
}
|
|
427
443
|
async function tryTokenRefresh(ctx, creds) {
|
|
428
444
|
if (!creds.refreshToken) return null;
|
|
429
445
|
const refreshed = await refreshAccessToken(
|
|
@@ -460,6 +476,8 @@ function createContext(opts) {
|
|
|
460
476
|
}
|
|
461
477
|
async function createAuthenticatedContext(opts) {
|
|
462
478
|
const ctx = createContext(opts);
|
|
479
|
+
const brokerCtx = tryBrokerContext(ctx);
|
|
480
|
+
if (brokerCtx) return brokerCtx;
|
|
463
481
|
const apiKey = process.env.GRAVITY_RAIL_API_KEY;
|
|
464
482
|
if (apiKey) {
|
|
465
483
|
const apiClient = makeApiClient(apiKey, ctx.envConfig.apiUrl);
|
|
@@ -690,4 +708,4 @@ export {
|
|
|
690
708
|
confirmAccountPhoneVerify,
|
|
691
709
|
getConciergeChatMessages
|
|
692
710
|
};
|
|
693
|
-
//# sourceMappingURL=chunk-
|
|
711
|
+
//# sourceMappingURL=chunk-LE5IR5MC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/lib/pagination.ts","../src/lib/output.ts","../src/lib/context.ts","../src/lib/org-auth.ts","../src/lib/pinned-fetch.ts","../src/lib/concierge-api.ts"],"sourcesContent":["import type { ArgDef, ParsedArgs } from './types';\nimport { CLIError } from './errors';\n\n// ── Response shape detection ─────────────────────────────────────────\n\n/** Metadata extracted from a paginated API response */\nexport interface PaginationMeta {\n /** Total number of items across all pages (page-based only) */\n total?: number;\n /** Current page number (page-based only) */\n page?: number;\n /** Total pages (page-based only) */\n totalPages?: number;\n /** Whether more pages exist (cursor-based only) */\n hasMore?: boolean;\n /** Cursor for next page (cursor-based only) */\n nextCursor?: string;\n}\n\n/** Explicit pagination contract passed from commands to outputData */\nexport interface ListResult {\n items: unknown[];\n meta?: PaginationMeta;\n supportsAll?: boolean;\n}\n\n/** Type guard: is the value a ListResult (explicit contract)? */\nexport function isListResult(data: unknown): data is ListResult {\n if (!data || typeof data !== 'object' || Array.isArray(data)) return false;\n const obj = data as Record<string, unknown>;\n return (\n Array.isArray(obj.items) &&\n ('supportsAll' in obj ||\n ('meta' in obj && (obj.meta === undefined || typeof obj.meta === 'object')))\n );\n}\n\n/** Check if a response looks like a page-based paginated envelope.\n * Matches both `{ items, total, totalPages, page }` (standard) and\n * `{ items, total, page, pageSize }` (legacy — e.g. InvitationListResponse,\n * InquiryListResponse). This is a pure type guard with no side effects;\n * totalPages computation for legacy shapes happens in extractPaginationMeta. */\nexport function isPagePaginatedResponse(data: unknown): data is {\n items: unknown[];\n total: number;\n page: number;\n totalPages?: number;\n pageSize?: number;\n} {\n if (!data || typeof data !== 'object') return false;\n const obj = data as Record<string, unknown>;\n if (!Array.isArray(obj.items) || typeof obj.page !== 'number') return false;\n\n // Standard shape: has totalPages\n if (typeof obj.totalPages === 'number') return true;\n\n // Legacy shape: has total + pageSize but no totalPages\n if (typeof obj.total === 'number' && typeof obj.pageSize === 'number') return true;\n\n return false;\n}\n\n/** Check if a response looks like a cursor-based paginated envelope */\nexport function isCursorPaginatedResponse(data: unknown): data is {\n items: unknown[];\n hasMore: boolean;\n nextCursor?: string;\n} {\n if (!data || typeof data !== 'object') return false;\n const obj = data as Record<string, unknown>;\n return Array.isArray(obj.items) && typeof obj.hasMore === 'boolean';\n}\n\n/** Check if a response is any paginated envelope ({ items: [...], ... }) */\nexport function isPaginatedResponse(data: unknown): boolean {\n return isPagePaginatedResponse(data) || isCursorPaginatedResponse(data);\n}\n\n/** Extract pagination metadata from a response, or return undefined for non-paginated responses.\n * For legacy shapes without totalPages, computes it from total/pageSize. */\nexport function extractPaginationMeta(data: unknown): PaginationMeta | undefined {\n if (isPagePaginatedResponse(data)) {\n const totalPages =\n data.totalPages ?? (data.pageSize ? Math.ceil(data.total / data.pageSize) : undefined);\n return {\n total: data.total,\n page: data.page,\n totalPages,\n };\n }\n if (isCursorPaginatedResponse(data)) {\n return {\n hasMore: data.hasMore,\n nextCursor: data.nextCursor,\n };\n }\n return undefined;\n}\n\n// ── Warning formatting ───────────────────────────────────────────────\n\n/** Format a pagination warning for stderr. Returns undefined if no warning needed.\n * When `supportsAll` is false, the \"Use --all\" hint is omitted because the\n * command does not actually expose the --all flag. */\nexport function formatPaginationWarning(\n meta: PaginationMeta | undefined,\n supportsAll: boolean = true\n): string | undefined {\n if (!meta) return undefined;\n\n const allHint = supportsAll ? ' Use --all to fetch all pages.' : '';\n\n // Page-based: warn when there are multiple pages\n if (meta.totalPages !== undefined && meta.totalPages > 1) {\n const pageNum = meta.page ?? 1;\n return `Showing page ${pageNum} of ${meta.totalPages} (${meta.total} total items).${allHint}`;\n }\n\n // Cursor-based: warn when more pages exist\n if (meta.hasMore === true) {\n return `More results available.${allHint}`;\n }\n\n return undefined;\n}\n\n// ── Unwrapping ───────────────────────────────────────────────────────\n\n/** Unwrap items from a paginated response or return the data as-is if it's an array */\nexport function unwrapItems(data: unknown): unknown[] {\n if (Array.isArray(data)) return data;\n if (\n data &&\n typeof data === 'object' &&\n 'items' in data &&\n Array.isArray((data as Record<string, unknown>).items)\n ) {\n return (data as Record<string, unknown>).items as unknown[];\n }\n return [data];\n}\n\n// ── Arg definitions ──────────────────────────────────────────────────\n\n/** Default pagination args for list commands (page, page-size, all). No search. */\nexport const LIST_PAGINATION_ARGS: ArgDef[] = [\n { name: 'page', description: 'Page number' },\n { name: 'page-size', description: 'Page size' },\n { name: 'all', description: 'Fetch all pages and return combined results', isFlag: true },\n];\n\n// ── Arg-to-options helpers ───────────────────────────────────────────\n\n/** Convert parsed CLI args to API pagination options */\nexport function parsePaginationArgs(args: ParsedArgs): {\n page?: number;\n pageSize?: number;\n all?: boolean;\n} {\n const result: { page?: number; pageSize?: number; all?: boolean } = {};\n\n if (args.all === true) {\n result.all = true;\n }\n\n if (args.page !== undefined) {\n const num = Number(args.page);\n if (isNaN(num) || num < 1) throw new CLIError('--page must be a positive integer');\n result.page = num;\n }\n\n if (args['page-size'] !== undefined) {\n const num = Number(args['page-size']);\n if (isNaN(num) || num < 1) throw new CLIError('--page-size must be a positive integer');\n if (num > 100) throw new CLIError('--page-size must be at most 100 (API limit)');\n result.pageSize = num;\n }\n\n return result;\n}\n\n// ── fetchAll helpers ─────────────────────────────────────────────────\n\nconst MAX_PAGE_SIZE = 100;\nconst MAX_PAGES = 200; // Safety limit to prevent infinite loops\n\n/**\n * Fetch all pages from a page-based paginated endpoint.\n * The fetcher receives { page, pageSize } and should return the raw API response.\n * Throws CLIError if MAX_PAGES is reached without exhausting all pages.\n */\nexport async function fetchAllPages(\n fetcher: (opts: { page: number; pageSize: number }) => Promise<unknown>\n): Promise<unknown[]> {\n const allItems: unknown[] = [];\n let page = 1;\n\n while (page <= MAX_PAGES) {\n const result = await fetcher({ page, pageSize: MAX_PAGE_SIZE });\n const items = unwrapItems(result);\n allItems.push(...items);\n\n // Use server metadata for termination\n const meta = extractPaginationMeta(result);\n if (meta?.totalPages !== undefined) {\n if (page >= meta.totalPages) break;\n } else if (!isPagePaginatedResponse(result)) {\n // Raw array or no metadata — single fetch is enough\n if (items.length < MAX_PAGE_SIZE) break;\n }\n\n if (page === MAX_PAGES) {\n throw new CLIError(\n `Reached maximum page limit (${MAX_PAGES}). Results may be incomplete.`\n );\n }\n\n page++;\n }\n\n return allItems;\n}\n\n/**\n * Fetch all pages from a cursor-based paginated endpoint.\n * The fetcher receives { cursor? } and should return the raw API response.\n * Throws CLIError if MAX_PAGES is reached without exhausting all pages.\n */\nexport async function fetchAllCursorPages(\n fetcher: (opts: { cursor?: string }) => Promise<unknown>\n): Promise<unknown[]> {\n const allItems: unknown[] = [];\n let cursor: string | undefined;\n let pages = 0;\n\n while (pages < MAX_PAGES) {\n const result = await fetcher({ cursor });\n const items = unwrapItems(result);\n allItems.push(...items);\n pages++;\n\n if (isCursorPaginatedResponse(result) && result.hasMore && result.nextCursor) {\n cursor = result.nextCursor;\n } else {\n break;\n }\n\n if (pages === MAX_PAGES) {\n throw new CLIError(\n `Reached maximum page limit (${MAX_PAGES}). Results may be incomplete.`\n );\n }\n }\n\n return allItems;\n}\n\n// ── ListResult factory ──────────────────────────────────────────────\n\n/**\n * Wrap a raw API response into a ListResult with explicit pagination metadata.\n * Works with both page-based and cursor-based responses, as well as plain arrays.\n *\n * @param data - Raw API response (paginated envelope or plain array)\n * @param supportsAll - Whether the calling command exposes --all\n */\nexport function listResult(data: unknown, supportsAll: boolean = false): ListResult {\n return {\n items: unwrapItems(data),\n meta: extractPaginationMeta(data),\n supportsAll,\n };\n}\n","import { printTable } from '../cli-utils';\nimport {\n type PaginationMeta,\n isListResult,\n extractPaginationMeta,\n formatPaginationWarning,\n unwrapItems,\n} from './pagination';\n\nexport type OutputFormat = 'json' | 'jsonl' | 'table';\n\nexport interface OutputOptions {\n outputFormat: OutputFormat;\n}\n\nexport interface TableColumn<T> {\n header: string;\n getValue: (item: T) => string;\n maxWidth?: number;\n}\n\n/** Detect default output format based on whether stdout is a TTY */\nexport function detectOutputFormat(explicit?: string): OutputFormat {\n if (explicit) return explicit as OutputFormat;\n return process.stdout.isTTY ? 'table' : 'json';\n}\n\n/**\n * Write a list of items to stdout in the requested format.\n *\n * Accepts:\n * - A `ListResult` (explicit contract with items, meta, supportsAll) — preferred.\n * - Raw arrays, paginated responses ({ items: [...] }), or any object — legacy.\n *\n * When a `ListResult` is passed, its explicit `meta` and `supportsAll` are used.\n * When raw data is passed, pagination metadata is auto-detected with\n * `supportsAll: false` as the safe default.\n *\n * The optional `paginationMeta` and `supportsAll` parameters are kept for\n * backward compatibility but are ignored when a `ListResult` is passed.\n */\nexport function outputData(\n data: unknown,\n opts: OutputOptions,\n columns?: TableColumn<any>[],\n paginationMeta?: PaginationMeta,\n supportsAll?: boolean\n): void {\n let items: unknown[];\n let meta: PaginationMeta | undefined;\n let allSupported: boolean;\n\n if (isListResult(data)) {\n // Explicit contract: use ListResult fields directly\n items = data.items;\n meta = data.meta;\n allSupported = data.supportsAll ?? false;\n } else {\n // Legacy path: auto-detect from raw data\n items = unwrapItems(data);\n meta = paginationMeta ?? extractPaginationMeta(data);\n allSupported = supportsAll ?? false;\n }\n\n const warning = formatPaginationWarning(meta, allSupported);\n if (warning) {\n process.stderr.write(warning + '\\n');\n }\n\n switch (opts.outputFormat) {\n case 'json':\n process.stdout.write(JSON.stringify(items, null, 2) + '\\n');\n break;\n case 'jsonl':\n for (const item of items) {\n process.stdout.write(JSON.stringify(item) + '\\n');\n }\n break;\n case 'table':\n if (columns) {\n printTable(items, columns);\n } else {\n process.stdout.write(JSON.stringify(items, null, 2) + '\\n');\n }\n break;\n }\n}\n\n/** Write a single object to stdout in the requested format */\nexport function outputSingle<T>(data: T, opts: OutputOptions, columns?: TableColumn<T>[]): void {\n switch (opts.outputFormat) {\n case 'json':\n process.stdout.write(JSON.stringify(data, null, 2) + '\\n');\n break;\n case 'jsonl':\n process.stdout.write(JSON.stringify(data) + '\\n');\n break;\n case 'table':\n if (columns) {\n printTable([data], columns);\n } else {\n process.stdout.write(JSON.stringify(data, null, 2) + '\\n');\n }\n break;\n }\n}\n\n/** Common column factory helpers for domain files.\n *\n * GR app-facing API wire is camelCase. First-party consumers (CLI, lambdas we\n * own) read that shape only — no snake_case dual-read \"bandaid\". When the API\n * flips a key, convert the consumer; do not carry both. (#19305)\n */\nexport const COL = {\n id: (): TableColumn<any> => ({ header: 'ID', getValue: (item) => String(item.id ?? '') }),\n uuid: (): TableColumn<any> => ({ header: 'UUID', getValue: (item) => item.uuid ?? '' }),\n name: (): TableColumn<any> => ({ header: 'NAME', getValue: (item) => item.name ?? '' }),\n status: (): TableColumn<any> => ({ header: 'STATUS', getValue: (item) => item.status ?? '' }),\n created: (): TableColumn<any> => ({\n header: 'CREATED',\n getValue: (item) => item.createdAt ?? '',\n }),\n updated: (): TableColumn<any> => ({\n header: 'UPDATED',\n getValue: (item) => item.updatedAt ?? '',\n }),\n description: (): TableColumn<any> => ({\n header: 'DESCRIPTION',\n getValue: (item) => item.description ?? '',\n }),\n enabled: (): TableColumn<any> => ({\n header: 'ENABLED',\n getValue: (item) => String(item.enabled ?? ''),\n }),\n};\n\n/** Write a status/progress message to stderr (never pollutes stdout) */\nexport function stderr(msg: string): void {\n process.stderr.write(msg + '\\n');\n}\n","import { GravityRailClient } from '@gravity-rail/sdk';\nimport {\n CredentialsManager,\n getEnvironmentConfig,\n refreshAccessToken,\n type CLICredentials,\n type EnvironmentConfig,\n} from '../cli-utils';\nimport { homedir } from 'os';\nimport { detectOutputFormat, type OutputFormat } from './output';\nimport { AuthRequiredError } from './errors';\nimport { registerOrgAuthHandler } from './org-auth';\nimport { createBrokerFetchFromEnv, createSandboxFetchFromEnv } from './pinned-fetch';\n\n/** Build an authenticated API client with the CLI org-auth handler registered. */\nfunction makeApiClient(\n accessToken: string | undefined,\n apiUrl: string,\n fetchFn?: typeof fetch\n): GravityRailClient {\n const resolvedFetch = fetchFn ?? createSandboxFetchFromEnv(apiUrl);\n const apiClient = new GravityRailClient(accessToken, apiUrl, resolvedFetch);\n registerOrgAuthHandler(apiClient, apiUrl, accessToken ?? '');\n return apiClient;\n}\n\n/**\n * Credential-broker context (code-interpreter sandbox): the scoped token is held\n * by the broker and absent from the env, so the CLI talks to the loopback broker\n * with NO auth header. Returns null when not in the sandbox (no broker URL) — the\n * standalone CLI then falls through to GRAVITY_RAIL_API_KEY / profile auth.\n */\nfunction tryBrokerContext(ctx: CommandContext): AuthenticatedContext | null {\n const brokerUrl = process.env.GRAVITY_RAIL_BROKER_URL;\n if (!brokerUrl) return null;\n const apiClient = makeApiClient(undefined, brokerUrl, createBrokerFetchFromEnv(brokerUrl));\n return { ...ctx, accessToken: '', apiClient };\n}\n\n/** Try to silently refresh an expired access token. Returns an AuthenticatedContext or null. */\nasync function tryTokenRefresh(\n ctx: CommandContext,\n creds: CLICredentials\n): Promise<AuthenticatedContext | null> {\n if (!creds.refreshToken) return null;\n const refreshed = await refreshAccessToken(\n ctx.envConfig,\n creds,\n ctx.credentialsManager,\n ctx.env,\n ctx.profile\n );\n if (!refreshed?.accessToken) return null;\n const apiClient = makeApiClient(refreshed.accessToken, ctx.envConfig.apiUrl);\n return {\n ...ctx,\n accessToken: refreshed.accessToken,\n apiClient,\n profileWorkspace: refreshed.workspaceUuid ?? creds.workspaceUuid,\n };\n}\n\nexport interface GlobalOptions {\n env: string;\n /** Override API base URL (also: GRAVITY_RAIL_API_URL env var). */\n apiUrl?: string;\n outputFormat?: string;\n verbose?: boolean;\n allowWrite?: boolean;\n /** Named credential profile (`--profile` / GRAVITY_RAIL_PROFILE). */\n profile?: string;\n}\n\nexport interface PublicGlobalOptions {\n env?: string;\n apiUrl?: string;\n frontendUrl?: string;\n outputFormat?: string;\n verbose?: boolean;\n allowWrite?: boolean;\n /** Named credential profile (`--profile` / GRAVITY_RAIL_PROFILE). */\n profile?: string;\n}\n\nexport interface CommandContext {\n env: string;\n envConfig: EnvironmentConfig;\n outputFormat: OutputFormat;\n verbose: boolean;\n allowWrite: boolean;\n credentialsManager: CredentialsManager;\n /** Resolved credential profile for this invocation. */\n profile: string;\n}\n\nexport interface AuthenticatedContext extends CommandContext {\n accessToken: string;\n apiClient: GravityRailClient;\n /** Default workspace UUID from the profile (dev workspace/member profiles). */\n profileWorkspace?: string;\n}\n\n/** Create a base command context (no auth required) */\nexport function createContext(opts: GlobalOptions): CommandContext {\n const baseConfig = getEnvironmentConfig(opts.env);\n const apiUrl = opts.apiUrl || process.env.GRAVITY_RAIL_API_URL || baseConfig.apiUrl;\n const envConfig = apiUrl === baseConfig.apiUrl ? baseConfig : { ...baseConfig, apiUrl };\n const credentialsManager = new CredentialsManager(homedir(), envConfig.apiUrl);\n const profile = credentialsManager.resolveProfileName(opts.profile, opts.env);\n return {\n env: opts.env,\n envConfig,\n outputFormat: detectOutputFormat(opts.outputFormat),\n verbose: opts.verbose ?? false,\n allowWrite: opts.allowWrite ?? false,\n credentialsManager,\n profile,\n };\n}\n\n/**\n * Create an authenticated command context.\n * Order: GRAVITY_RAIL_API_KEY → named/current profile store. Never auto-triggers OAuth.\n *\n * Dev accounts that used to be selected with `--realm` are now named profiles\n * (import with `gr auth import-dev` after provisioning).\n */\nexport async function createAuthenticatedContext(\n opts: GlobalOptions\n): Promise<AuthenticatedContext> {\n // eslint-disable-next-line @eslint-react/naming-convention-context-name -- CLI handler context, not React\n const ctx = createContext(opts);\n\n // Credential-broker mode takes precedence: token lives in the root daemon,\n // not the env, so route through the loopback broker with no auth header.\n const brokerCtx = tryBrokerContext(ctx);\n if (brokerCtx) return brokerCtx;\n\n // Try GRAVITY_RAIL_API_KEY first (process env; never persisted to the profile store)\n const apiKey = process.env.GRAVITY_RAIL_API_KEY;\n if (apiKey) {\n const apiClient = makeApiClient(apiKey, ctx.envConfig.apiUrl);\n return { ...ctx, accessToken: apiKey, apiClient };\n }\n\n // Try cached credentials for the resolved profile (login / import-dev)\n const creds = ctx.credentialsManager.load(ctx.env, ctx.profile);\n if (creds?.accessToken) {\n const buffer = 5 * 60 * 1000;\n const notExpired =\n !creds.expiresAt || new Date(creds.expiresAt) > new Date(Date.now() + buffer);\n if (notExpired) {\n const apiClient = makeApiClient(creds.accessToken, ctx.envConfig.apiUrl);\n return {\n ...ctx,\n accessToken: creds.accessToken,\n apiClient,\n profileWorkspace: creds.workspaceUuid,\n };\n }\n\n // Token expired — try refresh (OAuth profiles only)\n const refreshed = await tryTokenRefresh(ctx, creds);\n if (refreshed) return refreshed;\n }\n\n if (ctx.profile !== 'default') {\n throw new AuthRequiredError(\n `No credentials for profile \"${ctx.profile}\" (${ctx.env}). ` +\n `Run: gr auth login --name ${ctx.profile} or gr auth import-dev`\n );\n }\n throw new AuthRequiredError(\n 'Not authenticated. Run `gr login`, `gr auth import-dev` (local dev), or set GRAVITY_RAIL_API_KEY.'\n );\n}\n\n/**\n * Narrow a handler context to `AuthenticatedContext`.\n *\n * Handler `ctx` is typed as `AuthenticatedContext | CommandContext` so that\n * `requiresAuth: false` handlers can also share the type. At runtime, every\n * handler that did not opt out of auth will receive an `AuthenticatedContext`,\n * but TypeScript cannot verify this — call this helper before accessing\n * `apiClient` / `accessToken`.\n *\n * Throws if called from a handler that did not declare `requiresAuth`.\n */\nexport function requireAuth(ctx: AuthenticatedContext | CommandContext): AuthenticatedContext {\n if (!('apiClient' in ctx)) {\n throw new Error('Command handler accessed apiClient without declaring requiresAuth');\n }\n return ctx;\n}\n\n/** Create a base command context for the public CLI (defaults to prod) */\nexport function createPublicContext(opts: PublicGlobalOptions): CommandContext {\n // INVARIANT — the public `gr` CLI defaults to PROD. Public API consumers have NO access to\n // our dev/staging environments; prod is the only API they can reach, so an unset env MUST\n // resolve to prod. Do NOT change this to 'dev'/'staging' for internal convenience or to\n // satisfy a ticket claiming the prod default is a bug (see #15819 — the bug was the --help\n // text, not this default). Internal devs select a non-prod env with an explicit `--env`.\n const env = opts.env ?? 'prod';\n const envConfig = opts.apiUrl\n ? {\n apiUrl: opts.apiUrl,\n frontendUrl:\n opts.frontendUrl ||\n process.env.GRAVITY_RAIL_FRONTEND_URL ||\n 'https://app.gravityrail.com',\n }\n : getEnvironmentConfig(env);\n const credentialsManager = new CredentialsManager(homedir(), envConfig.apiUrl);\n const profile = credentialsManager.resolveProfileName(opts.profile, env);\n return {\n env,\n envConfig,\n outputFormat: detectOutputFormat(opts.outputFormat),\n verbose: opts.verbose ?? false,\n allowWrite: opts.allowWrite ?? false,\n credentialsManager,\n profile,\n };\n}\n\n/** Create an authenticated context for the public CLI. Tries env var, then cached token. */\nexport async function createPublicAuthenticatedContext(\n opts: PublicGlobalOptions\n): Promise<AuthenticatedContext> {\n const ctx = createPublicContext(opts);\n\n // Process env only — not written into ~/.gr-cli (see credentials-manager security notes).\n const apiKey = process.env.GRAVITY_RAIL_API_KEY;\n if (apiKey) {\n const apiClient = makeApiClient(apiKey, ctx.envConfig.apiUrl);\n return { ...ctx, accessToken: apiKey, apiClient };\n }\n\n const creds = ctx.credentialsManager.load(ctx.env, ctx.profile);\n if (creds?.accessToken) {\n const buffer = 5 * 60 * 1000;\n const notExpired =\n !creds.expiresAt || new Date(creds.expiresAt) > new Date(Date.now() + buffer);\n if (notExpired) {\n const apiClient = makeApiClient(creds.accessToken, ctx.envConfig.apiUrl);\n return {\n ...ctx,\n accessToken: creds.accessToken,\n apiClient,\n profileWorkspace: creds.workspaceUuid,\n };\n }\n\n // Token expired — try refresh\n const refreshed = await tryTokenRefresh(ctx, creds);\n if (refreshed) return refreshed;\n }\n\n throw new AuthRequiredError();\n}\n","/**\n * CLI handler for organization-level authentication challenges.\n *\n * When a workspace API call returns a 401 with WWW-Authenticate: Bearer realm=\"org\",\n * the SDK calls this handler. The CLI attempts to satisfy the challenge by:\n *\n * 1. POSTing to /api/v2/o/{org_uuid}/join with the existing bearer token.\n * This works for organizations that allow internal (non-SSO) authentication.\n * The response Set-Cookie header contains the org session credential.\n *\n * 2. If the org requires SSO, falls back to opening the auth URL in the browser\n * and prompting the user to complete authentication manually, then errors.\n */\n\nimport { execFile } from 'child_process';\nimport type { GravityRailClient } from '@gravity-rail/sdk';\nimport type { OrgAuthInfo } from '@gravity-rail/sdk';\nimport { stderr } from './output';\n\nconst ORG_AUTH_TIMEOUT_MS = 5 * 60 * 1000;\n\n/** Parse an org session credential from Set-Cookie header strings. */\nfunction extractOrgSessionFromSetCookies(\n setCookieValues: string[],\n preferredCookieName: string\n): string | undefined {\n let fallback: string | undefined;\n\n for (const value of setCookieValues) {\n const nameValue = value.split(';')[0]?.trim() ?? '';\n const eqIdx = nameValue.indexOf('=');\n if (eqIdx === -1) continue;\n\n const name = nameValue.slice(0, eqIdx).trim();\n const credentialValue = nameValue.slice(eqIdx + 1).trim() || undefined;\n if (!credentialValue) continue;\n\n if (name === preferredCookieName) return credentialValue;\n // Prefer opaque orgSession... cookies. Legacy orgToken... JWE cookies are\n // accepted only as a compatibility fallback during the cutover.\n if (!fallback && (name.startsWith('orgToken') || name.startsWith('orgSession'))) {\n fallback = credentialValue;\n }\n }\n\n return fallback;\n}\n\n/**\n * Attempt to satisfy an org auth challenge via the direct join endpoint.\n * Returns the org session credential on success, null when SSO is required.\n * Throws on unexpected network / server errors.\n */\nasync function tryDirectJoin(\n apiUrl: string,\n accessToken: string,\n orgAuthInfo: OrgAuthInfo\n): Promise<string | null> {\n const response = await fetch(`${apiUrl}/api/v2/o/${orgAuthInfo.orgUuid}/join`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${accessToken}`,\n },\n body: '{}',\n });\n\n if (response.status === 400) {\n // Org requires SSO — direct join is not permitted for this account\n return null;\n }\n\n if (!response.ok) {\n // Don't include the response body — the backend currently returns safe\n // JSON, but a future change could put PHI or other sensitive data there.\n // Status code is sufficient to disambiguate the failure mode.\n throw new Error(`Org join failed (HTTP ${response.status})`);\n }\n\n // Read Set-Cookie headers to extract the org session value.\n // Node ≥18 exposes getSetCookie() on Headers; fall back to get() for older runtimes.\n const rawHeaders = response.headers as Headers & {\n getSetCookie?: () => string[];\n };\n const setCookies: string[] =\n rawHeaders.getSetCookie?.() ??\n (rawHeaders.get('set-cookie') ? [rawHeaders.get('set-cookie')!] : []);\n\n if (setCookies.length === 0) {\n throw new Error('Org join succeeded but no Set-Cookie header returned');\n }\n\n const orgSession = extractOrgSessionFromSetCookies(setCookies, orgAuthInfo.cookieName);\n if (!orgSession) {\n throw new Error('Org join succeeded but org session cookie not found in Set-Cookie');\n }\n\n return orgSession;\n}\n\n/**\n * Validate that an auth URL is safe to hand to the OS URL opener.\n *\n * `open` / `xdg-open` / `cmd.exe start` will follow any URI scheme the OS\n * registers — including `file://`, `javascript:`, and custom app handlers —\n * so a compromised/MITMed server returning an attacker-controlled `authUrl`\n * could trigger arbitrary URI execution. Restrict to http(s) and require\n * the same parent domain as the configured API host (e.g. SSO login on\n * `app.gravityrail.com` is allowed when the API is `api.gravityrail.com`,\n * but `attacker.example` against `api.gravityrail.com` is rejected).\n *\n * \"Same parent domain\" is a last-2-labels match. This is a heuristic — it\n * does not consult the Public Suffix List, so `foo.co.uk` and `bar.co.uk`\n * would falsely match — but Gravity Rail's auth hosts do not live under\n * those ccTLDs, and the alternative (vendoring `psl`) is overkill for a CLI\n * gating check. The configured `apiUrl` is itself a trust anchor controlled\n * by the user; the threat is a hostile *response* from that API (or a MITM)\n * pivoting the URL opener.\n */\nfunction isAllowedAuthUrl(url: string, apiUrl: string): boolean {\n try {\n const parsed = new URL(url);\n const api = new URL(apiUrl);\n if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') return false;\n if (parsed.hostname === api.hostname) return true;\n\n const lastTwo = (host: string): string => {\n const parts = host.split('.').filter(Boolean);\n return parts.length >= 2 ? parts.slice(-2).join('.') : host;\n };\n // Reject IP-literal hosts from the parent-domain heuristic — last-2\n // labels of \"1.2.3.4\" would naively match \"3.4\" and admit anything.\n const isIp = /^[0-9.]+$|^\\[.*\\]$/.test(parsed.hostname);\n if (isIp) return false;\n\n return lastTwo(parsed.hostname) === lastTwo(api.hostname);\n } catch {\n return false;\n }\n}\n\n/** Open a URL in the system browser (cross-platform). */\nfunction openBrowser(url: string): void {\n let command: string;\n let args: string[];\n\n if (process.platform === 'darwin') {\n command = 'open';\n args = [url];\n } else if (process.platform === 'win32') {\n command = 'cmd.exe';\n args = ['/c', 'start', '', url];\n } else {\n command = 'xdg-open';\n args = [url];\n }\n\n execFile(command, args, (_err) => {\n // Failure is non-fatal — the URL is printed to stderr so the user can open it manually.\n });\n}\n\n/**\n * Register a CLI org-auth handler on the given client.\n *\n * The handler satisfies org-auth 401 challenges without relying on browser\n * cookies (which are not available in Node.js):\n *\n * - Non-SSO orgs: calls the join endpoint directly and reads the org session\n * credential from the Set-Cookie response header, then sets it via\n * client.setOrgSession(). Opaque gr_sess_ credentials retry with\n * X-Org-Session; legacy JWE credentials retry with X-Org-Token.\n *\n * - SSO orgs: opens the browser at the authUrl and returns a timeout error\n * after ORG_AUTH_TIMEOUT_MS, because the SSO token is only ever set as a\n * browser cookie that the CLI cannot intercept.\n */\nexport function registerOrgAuthHandler(\n apiClient: GravityRailClient,\n apiUrl: string,\n accessToken: string\n): void {\n apiClient.setOrgAuthHandler(async (orgAuthInfo: OrgAuthInfo): Promise<void> => {\n // Fast path: non-SSO org — join directly and extract the session credential.\n try {\n const orgSession = await tryDirectJoin(apiUrl, accessToken, orgAuthInfo);\n if (orgSession !== null) {\n // Set the session on the client so the SDK's fallback check\n // picks it up after the handler resolves (Node.js has no document.cookie).\n // Pass the org_uuid so the SDK can detect a stale token if the next\n // request targets a different org.\n apiClient.setOrgSession(orgSession, orgAuthInfo.orgUuid);\n return;\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n stderr(`Warning: org direct-join failed (${msg}); trying browser auth`);\n }\n\n // Slow path: SSO org — the browser must complete the flow. Reject the\n // auth URL up-front if it doesn't match the configured API origin so a\n // hostile server can't pivot the OS URL opener to a `javascript:` /\n // `file://` / custom-scheme handler.\n if (!isAllowedAuthUrl(orgAuthInfo.authUrl, apiUrl)) {\n throw new Error(\n `Refusing to open organization auth URL: scheme/host does not match the configured API host. ` +\n `Re-run with the expected --api-url, or contact support.`\n );\n }\n\n stderr(`\\nThis workspace requires organization SSO authentication.`);\n stderr(`Opening browser to complete authentication...`);\n stderr(` ${orgAuthInfo.authUrl}\\n`);\n stderr(`If the browser does not open, visit the URL above manually.`);\n stderr(\n `After signing in, re-run the command — the CLI cannot receive the SSO session directly.`\n );\n openBrowser(orgAuthInfo.authUrl);\n\n // Reject after timeout so the CLI doesn't hang indefinitely.\n await new Promise<void>((_, reject) => {\n const t = setTimeout(\n () =>\n reject(\n new Error(\n 'Organization authentication timed out.\\n' +\n 'Please complete authentication in the browser, then re-run the command.'\n )\n ),\n ORG_AUTH_TIMEOUT_MS\n );\n // Unref so the timeout doesn't keep the Node.js event loop alive\n // if the parent process is otherwise done.\n t.unref?.();\n });\n });\n}\n","/** Optional DNS pin + audit headers for code-interpreter sandboxes. */\n\nimport { Agent, fetch as undiciFetch, type RequestInit as UndiciRequestInit } from 'undici';\n\nexport interface SandboxAuditHeaders {\n chatId?: number;\n sessionId?: string;\n toolCallId?: string;\n}\n\ntype ToolCallGlobal = typeof globalThis & {\n __GRAVITY_RAIL_TOOL_CALL_ID__?: string;\n};\n\ntype WebRequest = Parameters<typeof fetch>[0];\ntype WebRequestInit = Parameters<typeof fetch>[1];\n\n/** Subset of sandbox env used when building fetch (kept here to avoid circular imports). */\nexport interface SandboxFetchEnv {\n apiUrl: string;\n apiIp?: string;\n chatId?: number;\n sessionId?: string;\n toolCallId?: string;\n}\n\n/**\n * Route API hostname traffic to a pinned private ENI IP when present.\n *\n * Deployed sandboxes cannot reach the API ALB's public IPs; Python sandboxes\n * patch urllib3 at the socket layer. Node uses undici with `connect.servername`\n * so TLS SNI still matches the real hostname while the TCP connection targets\n * the pinned private IP (Host header is also set for HTTP routing).\n */\nexport function createPinnedFetch(apiUrl: string, apiIp?: string): typeof fetch {\n if (!apiIp) return fetch;\n\n let parsed: URL;\n try {\n parsed = new URL(apiUrl);\n } catch {\n return fetch;\n }\n const hostname = parsed.hostname;\n\n const dispatcher = new Agent({\n connect: {\n servername: hostname,\n },\n });\n\n return async (input: WebRequest, init?: WebRequestInit): Promise<Response> => {\n const url = typeof input === 'string' ? input : input instanceof URL ? input.href : input.url;\n\n let reqUrl: URL;\n try {\n reqUrl = new URL(url, apiUrl);\n } catch {\n return fetch(input, init);\n }\n\n const headers = new Headers(init?.headers);\n if (reqUrl.hostname === hostname) {\n headers.set('Host', hostname);\n reqUrl = new URL(reqUrl.href);\n reqUrl.hostname = apiIp;\n }\n\n return undiciFetch(reqUrl, {\n ...(init as UndiciRequestInit | undefined),\n headers,\n dispatcher,\n }) as unknown as Promise<Response>;\n };\n}\n\nexport function withAuditHeaders(\n fetchFn: typeof fetch,\n headers: SandboxAuditHeaders\n): typeof fetch {\n const { chatId, sessionId, toolCallId } = headers;\n\n return async (input: WebRequest, init?: WebRequestInit): Promise<Response> => {\n const reqHeaders = new Headers(init?.headers);\n const dynamicToolCallId =\n (globalThis as ToolCallGlobal).__GRAVITY_RAIL_TOOL_CALL_ID__ ||\n (typeof process !== 'undefined' ? process.env.GRAVITY_RAIL_TOOL_CALL_ID : undefined);\n if (chatId !== undefined) {\n reqHeaders.set('X-GR-Chat-Id', String(chatId));\n }\n if (sessionId) {\n reqHeaders.set('X-GR-Session-Id', sessionId);\n }\n if (dynamicToolCallId || toolCallId) {\n reqHeaders.set('X-GR-Tool-Call-Id', dynamicToolCallId || toolCallId || '');\n }\n return fetchFn(input, { ...init, headers: reqHeaders });\n };\n}\n\n/** Build fetch for sandbox sessions from explicit env (no process.env reads). */\nexport function createSandboxFetch(env: SandboxFetchEnv): typeof fetch {\n let fetchFn = createPinnedFetch(env.apiUrl, env.apiIp);\n return withAuditHeaders(fetchFn, {\n chatId: env.chatId,\n sessionId: env.sessionId,\n toolCallId: env.toolCallId,\n });\n}\n\n/**\n * Build fetch for credential-broker mode: audit headers only, NO DNS pin.\n *\n * The broker is on 127.0.0.1 (not the API host), so the API-IP pin must not\n * apply — a pinned fetch would rewrite the loopback hostname to the API's\n * private IP and break the call. Auth is added by the root broker, not here.\n */\nexport function createBrokerFetchFromEnv(_brokerUrl: string): typeof fetch {\n const env: Record<string, string | undefined> =\n typeof process !== 'undefined' ? process.env : {};\n const chatIdRaw = env.GRAVITY_RAIL_CHAT_ID;\n const chatId = chatIdRaw ? Number(chatIdRaw) : undefined;\n return withAuditHeaders(fetch, {\n chatId: chatId !== undefined && Number.isFinite(chatId) ? chatId : undefined,\n sessionId: env.GRAVITY_RAIL_SESSION_ID,\n toolCallId: env.GRAVITY_RAIL_TOOL_CALL_ID,\n });\n}\n\n/** Build fetch for sandbox sessions from GRAVITY_RAIL_* env vars. */\nexport function createSandboxFetchFromEnv(apiUrl: string): typeof fetch {\n const env: Record<string, string | undefined> =\n typeof process !== 'undefined' ? process.env : {};\n const apiIp = env.GRAVITY_RAIL_API_IP;\n let fetchFn = createPinnedFetch(apiUrl, apiIp);\n\n const chatIdRaw = env.GRAVITY_RAIL_CHAT_ID;\n const chatId = chatIdRaw ? Number(chatIdRaw) : undefined;\n return withAuditHeaders(fetchFn, {\n chatId: chatId !== undefined && Number.isFinite(chatId) ? chatId : undefined,\n sessionId: env.GRAVITY_RAIL_SESSION_ID,\n toolCallId: env.GRAVITY_RAIL_TOOL_CALL_ID,\n });\n}\n","/**\n * Concierge API client — direct HTTP calls to the global concierge endpoints.\n *\n * The concierge is account-level (no workspace), so these bypass the ApiClient\n * which is workspace-oriented.\n */\n\nimport type { AuthenticatedContext } from './context';\n\nexport interface ConciergeChat {\n id: number;\n uuid: string;\n chatType: string;\n title: string | null;\n threadId: string;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface ConciergeMessage {\n id: string;\n role: string;\n content: string;\n createdAt: string | null;\n toolCalls: any[] | null;\n toolCallId: string | null;\n}\n\nfunction baseUrl(ctx: AuthenticatedContext): string {\n return `${ctx.envConfig.apiUrl}/api/v2/concierge`;\n}\n\nfunction authHeaders(ctx: AuthenticatedContext): Record<string, string> {\n return {\n Authorization: `Bearer ${ctx.accessToken}`,\n 'Content-Type': 'application/json',\n };\n}\n\n/**\n * Map GlobalChatResponse wire → local shape.\n *\n * App fields are camelCase only (we own both ends — no snake dual-read).\n * `tool_calls` / `tool_call_id` on messages stay snake_case intentionally\n * (OpenAI message protocol on ConciergeMessageResponse). (#19305)\n */\nfunction toCamelChat(raw: any): ConciergeChat {\n return {\n id: raw.id,\n uuid: raw.uuid,\n chatType: raw.chatType,\n title: raw.title,\n threadId: raw.threadId,\n createdAt: raw.createdAt,\n updatedAt: raw.updatedAt,\n };\n}\n\nfunction toCamelMessage(raw: any): ConciergeMessage {\n return {\n id: raw.id,\n role: raw.role,\n content: raw.content ?? '',\n createdAt: raw.createdAt ?? null,\n // Protocol-exempt snake_case on wire (see ConciergeMessageResponse)\n toolCalls: raw.tool_calls ?? null,\n toolCallId: raw.tool_call_id ?? null,\n };\n}\n\nexport async function listConciergeChats(ctx: AuthenticatedContext): Promise<ConciergeChat[]> {\n const res = await fetch(`${baseUrl(ctx)}/chats`, { headers: authHeaders(ctx) });\n if (!res.ok) throw new Error(`Failed to list concierge chats: ${res.status}`);\n const data = await res.json();\n return (data as any[]).map(toCamelChat);\n}\n\nexport async function createConciergeChat(\n ctx: AuthenticatedContext,\n title?: string\n): Promise<ConciergeChat> {\n const res = await fetch(`${baseUrl(ctx)}/chats`, {\n method: 'POST',\n headers: authHeaders(ctx),\n body: JSON.stringify({ title: title ?? null }),\n });\n if (!res.ok) throw new Error(`Failed to create concierge chat: ${res.status}`);\n return toCamelChat(await res.json());\n}\n\nexport async function getConciergeChat(\n ctx: AuthenticatedContext,\n chatId: number\n): Promise<ConciergeChat> {\n const res = await fetch(`${baseUrl(ctx)}/chats/${chatId}`, { headers: authHeaders(ctx) });\n if (!res.ok) throw new Error(`Failed to get concierge chat ${chatId}: ${res.status}`);\n return toCamelChat(await res.json());\n}\n\nexport interface ConciergeCallResult {\n callSid: string;\n startedAt: string;\n}\n\nexport interface ConciergeSmsResult {\n messageSid: string;\n conversationId: string;\n}\n\nexport async function originateConciergeCall(\n ctx: AuthenticatedContext,\n kind = 'concierge_setup',\n outboundMessage?: string,\n clientSurface?: string\n): Promise<ConciergeCallResult> {\n const body: Record<string, string | undefined> = { kind };\n if (outboundMessage) body.outbound_message = outboundMessage;\n if (clientSurface) body.client_surface = clientSurface;\n const res = await fetch(`${baseUrl(ctx)}/calls`, {\n method: 'POST',\n headers: authHeaders(ctx),\n body: JSON.stringify(body),\n });\n if (res.status === 412) {\n throw new Error('Your account phone is not verified. Run `gr account verify-phone` first.');\n }\n if (!res.ok) {\n const detail = await res.text();\n throw new Error(`Failed to start concierge call: ${res.status} ${detail}`);\n }\n return (await res.json()) as ConciergeCallResult;\n}\n\nexport async function originateConciergeSms(\n ctx: AuthenticatedContext,\n kind = 'concierge_setup'\n): Promise<ConciergeSmsResult> {\n const res = await fetch(`${baseUrl(ctx)}/sms`, {\n method: 'POST',\n headers: authHeaders(ctx),\n body: JSON.stringify({ kind }),\n });\n if (res.status === 412) {\n throw new Error('Your account phone is not verified. Run `gr account verify-phone` first.');\n }\n if (!res.ok) {\n const detail = await res.text();\n throw new Error(`Failed to start concierge SMS: ${res.status} ${detail}`);\n }\n return (await res.json()) as ConciergeSmsResult;\n}\n\nexport async function startAccountPhoneVerify(\n ctx: AuthenticatedContext,\n phone: string\n): Promise<void> {\n const res = await fetch(`${ctx.envConfig.apiUrl}/api/v2/account/phone/verify/start`, {\n method: 'POST',\n headers: authHeaders(ctx),\n body: JSON.stringify({ phone }),\n });\n if (!res.ok) {\n const detail = await res.text();\n throw new Error(`Failed to send verification code: ${res.status} ${detail}`);\n }\n}\n\nexport async function confirmAccountPhoneVerify(\n ctx: AuthenticatedContext,\n code: string\n): Promise<void> {\n const res = await fetch(`${ctx.envConfig.apiUrl}/api/v2/account/phone/verify/confirm`, {\n method: 'POST',\n headers: authHeaders(ctx),\n body: JSON.stringify({ code }),\n });\n if (!res.ok) {\n const detail = await res.text();\n throw new Error(`Failed to confirm verification code: ${res.status} ${detail}`);\n }\n}\n\nexport async function getConciergeChatMessages(\n ctx: AuthenticatedContext,\n chatId: number\n): Promise<ConciergeMessage[]> {\n const res = await fetch(`${baseUrl(ctx)}/chats/${chatId}/messages`, {\n headers: authHeaders(ctx),\n });\n if (!res.ok)\n throw new Error(`Failed to get concierge messages for chat ${chatId}: ${res.status}`);\n const data = await res.json();\n return (data as any[]).map(toCamelMessage);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1013,32 +1013,134 @@ var AGUIClient = class {
|
|
|
1013
1013
|
`Chat request failed: ${response.status}${errorText ? `: ${errorText.slice(0, 200)}` : ""}`
|
|
1014
1014
|
);
|
|
1015
1015
|
}
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1016
|
+
await parseSseStream(response.body, onEvent);
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Subscribe to the chat's persistent AG-UI event feed and surface
|
|
1020
|
+
* server-initiated turns that arrive while we're idle between our own turns.
|
|
1021
|
+
*
|
|
1022
|
+
* The feed at ``/chats/{chatId}/events`` replays every AG-UI event for the
|
|
1023
|
+
* chat across all turns (assistant events plus user-message echoes). It never
|
|
1024
|
+
* self-terminates — it ends only on disconnect. On reconnect we send
|
|
1025
|
+
* ``Last-Event-ID`` so the server replays the gap. The loop reconnects with
|
|
1026
|
+
* capped exponential backoff and resolves quietly once ``signal`` aborts;
|
|
1027
|
+
* it never throws except when required args are missing (callers gate on
|
|
1028
|
+
* availability, so that path is a programming error).
|
|
1029
|
+
*/
|
|
1030
|
+
async subscribeToFeed(opts) {
|
|
1031
|
+
const { onEvent, signal, onDebug } = opts;
|
|
1032
|
+
const noChat = typeof this.chatId === "number" ? this.chatId <= 0 : this.chatId.length === 0;
|
|
1033
|
+
if (!this.baseUrl || !this.workspaceUuid || noChat) {
|
|
1034
|
+
throw new Error("subscribeToFeed requires baseUrl, workspaceUuid, and a chatId");
|
|
1035
|
+
}
|
|
1036
|
+
const feedUrl = `${this.baseUrl}/api/v2/w/${this.workspaceUuid}/chats/${this.chatId}/events`;
|
|
1037
|
+
let lastEventId;
|
|
1038
|
+
let backoffMs = 1e3;
|
|
1039
|
+
const maxBackoffMs = 15e3;
|
|
1040
|
+
const healthyConnectionMs = 3e4;
|
|
1041
|
+
while (!signal.aborted) {
|
|
1042
|
+
let receivedData = false;
|
|
1043
|
+
const connectedAt = Date.now();
|
|
1044
|
+
try {
|
|
1045
|
+
const headers = {
|
|
1046
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
1047
|
+
Accept: "text/event-stream"
|
|
1048
|
+
};
|
|
1049
|
+
if (lastEventId !== void 0) headers["Last-Event-ID"] = lastEventId;
|
|
1050
|
+
const response = await fetch(feedUrl, { method: "GET", headers, signal });
|
|
1051
|
+
if (!response.ok || !response.body) {
|
|
1052
|
+
throw new Error(`feed request failed: ${response.status}`);
|
|
1053
|
+
}
|
|
1054
|
+
onDebug?.(`feed connected${lastEventId ? ` (resume ${lastEventId})` : ""}`);
|
|
1055
|
+
await parseSseStream(
|
|
1056
|
+
response.body,
|
|
1057
|
+
(event, eventName) => {
|
|
1058
|
+
receivedData = true;
|
|
1059
|
+
if (eventName === "feed_reset" || typeof event.type === "string" && event.type.toUpperCase() === "FEED_RESET") {
|
|
1060
|
+
onDebug?.("feed reset \u2014 server could not replay missed events");
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1034
1063
|
onEvent(event);
|
|
1035
|
-
}
|
|
1064
|
+
},
|
|
1065
|
+
(id) => {
|
|
1066
|
+
receivedData = true;
|
|
1067
|
+
lastEventId = id;
|
|
1036
1068
|
}
|
|
1037
|
-
|
|
1069
|
+
);
|
|
1070
|
+
} catch (err) {
|
|
1071
|
+
if (signal.aborted) return;
|
|
1072
|
+
onDebug?.(`feed error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1038
1073
|
}
|
|
1074
|
+
if (signal.aborted) return;
|
|
1075
|
+
const wasHealthy = receivedData || Date.now() - connectedAt >= healthyConnectionMs;
|
|
1076
|
+
if (wasHealthy) backoffMs = 1e3;
|
|
1077
|
+
const completed = await abortableDelay(backoffMs, signal);
|
|
1078
|
+
if (!completed) return;
|
|
1079
|
+
if (!wasHealthy) backoffMs = Math.min(backoffMs * 2, maxBackoffMs);
|
|
1039
1080
|
}
|
|
1040
1081
|
}
|
|
1041
1082
|
};
|
|
1083
|
+
async function parseSseStream(body, onEvent, onId) {
|
|
1084
|
+
const reader = body.getReader();
|
|
1085
|
+
const decoder = new TextDecoder();
|
|
1086
|
+
let buffer = "";
|
|
1087
|
+
let pendingEventName;
|
|
1088
|
+
while (true) {
|
|
1089
|
+
const { done, value } = await reader.read();
|
|
1090
|
+
if (done) break;
|
|
1091
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1092
|
+
const lines = buffer.split("\n");
|
|
1093
|
+
buffer = lines.pop() ?? "";
|
|
1094
|
+
for (const rawLine of lines) {
|
|
1095
|
+
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
1096
|
+
if (line === "") {
|
|
1097
|
+
pendingEventName = void 0;
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
if (line.startsWith(":")) continue;
|
|
1101
|
+
if (line.startsWith("event:")) {
|
|
1102
|
+
const nameValue = line.slice(6);
|
|
1103
|
+
pendingEventName = nameValue.startsWith(" ") ? nameValue.slice(1) : nameValue;
|
|
1104
|
+
continue;
|
|
1105
|
+
}
|
|
1106
|
+
if (line.startsWith("id:")) {
|
|
1107
|
+
const idValue = line.slice(3);
|
|
1108
|
+
onId?.(idValue.startsWith(" ") ? idValue.slice(1) : idValue);
|
|
1109
|
+
continue;
|
|
1110
|
+
}
|
|
1111
|
+
if (line.startsWith("data: ")) {
|
|
1112
|
+
const data = line.slice(6);
|
|
1113
|
+
if (data === "[DONE]") {
|
|
1114
|
+
onEvent({ type: "done" }, pendingEventName);
|
|
1115
|
+
pendingEventName = void 0;
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
try {
|
|
1119
|
+
onEvent(JSON.parse(data), pendingEventName);
|
|
1120
|
+
} catch {
|
|
1121
|
+
}
|
|
1122
|
+
pendingEventName = void 0;
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
function abortableDelay(ms, signal) {
|
|
1128
|
+
return new Promise((resolve3) => {
|
|
1129
|
+
if (signal.aborted) {
|
|
1130
|
+
resolve3(false);
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
const onAbort = () => {
|
|
1134
|
+
clearTimeout(timer);
|
|
1135
|
+
resolve3(false);
|
|
1136
|
+
};
|
|
1137
|
+
const timer = setTimeout(() => {
|
|
1138
|
+
signal.removeEventListener("abort", onAbort);
|
|
1139
|
+
resolve3(true);
|
|
1140
|
+
}, ms);
|
|
1141
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1042
1144
|
function isTextContentEvent(event) {
|
|
1043
1145
|
const type = event.type.toLowerCase();
|
|
1044
1146
|
return type === "text_message_content";
|
|
@@ -1080,9 +1182,10 @@ export {
|
|
|
1080
1182
|
printMessage,
|
|
1081
1183
|
clearScreen,
|
|
1082
1184
|
AGUIClient,
|
|
1185
|
+
parseSseStream,
|
|
1083
1186
|
isTextContentEvent,
|
|
1084
1187
|
isErrorEvent,
|
|
1085
1188
|
getTextDelta,
|
|
1086
1189
|
getErrorMessage
|
|
1087
1190
|
};
|
|
1088
|
-
//# sourceMappingURL=chunk-
|
|
1191
|
+
//# sourceMappingURL=chunk-XSFAJ5ZO.js.map
|