@gravity-rail/cli 0.10.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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 { createSandboxFetchFromEnv } from './pinned-fetch';\n\n/** Build an authenticated API client with the CLI org-auth handler registered. */\nfunction makeApiClient(accessToken: string, apiUrl: string): GravityRailClient {\n const fetchFn = createSandboxFetchFromEnv(apiUrl);\n const apiClient = new GravityRailClient(accessToken, apiUrl, fetchFn);\n registerOrgAuthHandler(apiClient, apiUrl, accessToken);\n return 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 // 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/** 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":[]}
@@ -44,7 +44,7 @@ function detectWorktreePorts() {
44
44
  for (const match of content.matchAll(/-\s*'(\d+):(\d+)'/g)) {
45
45
  const host = parseInt(match[1], 10);
46
46
  const container = parseInt(match[2], 10);
47
- if (container === 5050) apiPort = host;
47
+ if (container === 443 || container === 5054) apiPort = host;
48
48
  if (container === 3e3) frontendPort = host;
49
49
  }
50
50
  return { apiPort, frontendPort };
@@ -86,9 +86,20 @@ function getEnvironmentConfig(env) {
86
86
  // src/cli-utils/auth.ts
87
87
  import { createServer } from "http";
88
88
  import { createHash, randomBytes } from "crypto";
89
- import { readFileSync as readFileSync2, writeFileSync, existsSync as existsSync2, mkdirSync, chmodSync, unlinkSync } from "fs";
90
- import { join as join2, resolve as resolve2, relative, isAbsolute } from "path";
91
89
  import { exec } from "child_process";
90
+
91
+ // src/cli-utils/credentials-manager.ts
92
+ import {
93
+ readFileSync as readFileSync2,
94
+ writeFileSync,
95
+ existsSync as existsSync2,
96
+ mkdirSync,
97
+ chmodSync,
98
+ unlinkSync,
99
+ renameSync
100
+ } from "fs";
101
+ import { join as join2, resolve as resolve2, relative, isAbsolute } from "path";
102
+ var DEFAULT_PROFILE = "default";
92
103
  var CredentialsManager = class {
93
104
  cacheDir;
94
105
  apiUrl;
@@ -96,6 +107,77 @@ var CredentialsManager = class {
96
107
  this.cacheDir = join2(rootDir, ".gr-cli");
97
108
  this.apiUrl = apiUrl;
98
109
  }
110
+ /**
111
+ * Validate a profile name. Allows the legacy realm path form
112
+ * `workspace/member` so `acme-demo/acme-admin` works as a profile name.
113
+ * Rejects path traversal and absolute paths.
114
+ */
115
+ validateProfileName(profile) {
116
+ if (!profile) {
117
+ throw new Error("Invalid profile name: (empty).");
118
+ }
119
+ if (profile.includes("..") || profile.startsWith("/") || profile.endsWith("/")) {
120
+ throw new Error(
121
+ `Invalid profile name: ${profile}. Do not use ".." or leading/trailing "/".`
122
+ );
123
+ }
124
+ if (!/^[a-zA-Z0-9_.-]+(?:\/[a-zA-Z0-9_.-]+)?$/.test(profile)) {
125
+ throw new Error(
126
+ `Invalid profile name: ${profile}. Use letters, digits, '_', '-', '.', or workspace/member form (one slash).`
127
+ );
128
+ }
129
+ }
130
+ /**
131
+ * Resolve which profile to use for this invocation.
132
+ * Precedence: explicit flag → GRAVITY_RAIL_PROFILE → stored current → "default".
133
+ */
134
+ resolveProfileName(explicit, env) {
135
+ if (explicit) {
136
+ this.validateProfileName(explicit);
137
+ return explicit;
138
+ }
139
+ const fromEnv = process.env.GRAVITY_RAIL_PROFILE;
140
+ if (fromEnv) {
141
+ this.validateProfileName(fromEnv);
142
+ return fromEnv;
143
+ }
144
+ if (env) {
145
+ return this.getCurrentProfile(env);
146
+ }
147
+ return DEFAULT_PROFILE;
148
+ }
149
+ getCurrentProfile(env) {
150
+ const parsed = this.readStore(env);
151
+ if (!parsed) return DEFAULT_PROFILE;
152
+ return parsed.store.currentProfile || DEFAULT_PROFILE;
153
+ }
154
+ setCurrentProfile(env, profile) {
155
+ this.validateProfileName(profile);
156
+ const store = this.readStore(env)?.store ?? this.emptyStore(profile);
157
+ if (!store.profiles[profile]) {
158
+ throw new Error(
159
+ `Profile "${profile}" has no stored credentials for ${env}. Run: gr auth login --name ${profile} (or gr auth import-dev for local tokens)`
160
+ );
161
+ }
162
+ store.currentProfile = profile;
163
+ this.writeStore(env, store);
164
+ }
165
+ listProfiles(env) {
166
+ const parsed = this.readStore(env);
167
+ if (!parsed) return [];
168
+ const store = parsed.store;
169
+ const current = store.currentProfile || DEFAULT_PROFILE;
170
+ return Object.keys(store.profiles).sort().map((name) => {
171
+ const creds = store.profiles[name];
172
+ return {
173
+ name,
174
+ current: name === current,
175
+ hasAccessToken: Boolean(creds.accessToken || creds.refreshToken),
176
+ expiresAt: creds.expiresAt,
177
+ workspaceUuid: creds.workspaceUuid
178
+ };
179
+ });
180
+ }
99
181
  getCredentialsPath(env) {
100
182
  if (!/^[a-zA-Z0-9_:.-]+$/.test(env)) {
101
183
  throw new Error(`Invalid environment name: ${env}`);
@@ -112,69 +194,216 @@ var CredentialsManager = class {
112
194
  if (this.apiUrl) {
113
195
  try {
114
196
  const url = new URL(this.apiUrl);
115
- if (url.hostname === "localhost" && url.port && url.port !== "5050") {
116
- return join2(this.cacheDir, `credentials-${env}-${url.port}.json`);
197
+ const port = url.port;
198
+ if (port && port !== "80" && port !== "443") {
199
+ const isCanonicalLocalDev = url.hostname === "localhost" && port === "5050" || url.hostname.endsWith("gravityrail.test") && port === "5054";
200
+ if (!isCanonicalLocalDev) {
201
+ return join2(this.cacheDir, `credentials-${env}-${port}.json`);
202
+ }
117
203
  }
118
204
  } catch {
119
205
  }
120
206
  }
121
207
  return join2(this.cacheDir, `credentials-${env}.json`);
122
208
  }
123
- load(env) {
209
+ emptyStore(currentProfile = DEFAULT_PROFILE) {
210
+ return { version: 2, currentProfile, profiles: {} };
211
+ }
212
+ isCredentialShape(value) {
213
+ if (!value || typeof value !== "object") return false;
214
+ const v = value;
215
+ const hasOAuth = typeof v.clientId === "string" && typeof v.clientSecret === "string";
216
+ const hasToken = typeof v.accessToken === "string" && v.accessToken.length > 0;
217
+ return hasOAuth || hasToken;
218
+ }
219
+ /**
220
+ * Read and normalize the on-disk store (migrating legacy formats in memory).
221
+ * `wasLegacy` is true when the file was not already v2 so callers can rewrite once.
222
+ */
223
+ readStore(env) {
124
224
  const credsPath = this.getCredentialsPath(env);
125
225
  if (!existsSync2(credsPath)) {
126
226
  return null;
127
227
  }
128
228
  try {
129
229
  const raw = JSON.parse(readFileSync2(credsPath, "utf-8"));
130
- let creds;
131
- if (raw.clientId !== void 0) {
132
- creds = raw;
133
- } else if (raw.default) {
134
- creds = raw.default;
135
- this.save(env, creds);
136
- } else {
137
- return null;
138
- }
139
- if (creds.expiresAt) {
140
- const expiresAt = new Date(creds.expiresAt);
141
- if (expiresAt <= /* @__PURE__ */ new Date()) {
142
- if (creds.refreshToken) {
143
- return creds;
230
+ if (raw.version === 2 && raw.profiles && typeof raw.profiles === "object") {
231
+ const profiles = {};
232
+ for (const [name, value] of Object.entries(raw.profiles)) {
233
+ if (this.isCredentialShape(value)) {
234
+ profiles[name] = value;
144
235
  }
145
- return null;
146
236
  }
237
+ const current = typeof raw.currentProfile === "string" && profiles[raw.currentProfile] ? raw.currentProfile : Object.keys(profiles)[0] || DEFAULT_PROFILE;
238
+ return {
239
+ store: { version: 2, currentProfile: current, profiles },
240
+ wasLegacy: false
241
+ };
147
242
  }
148
- return creds;
243
+ if (this.isCredentialShape(raw)) {
244
+ return {
245
+ store: {
246
+ version: 2,
247
+ currentProfile: DEFAULT_PROFILE,
248
+ profiles: { [DEFAULT_PROFILE]: raw }
249
+ },
250
+ wasLegacy: true
251
+ };
252
+ }
253
+ const legacyDefault = raw.default;
254
+ if (this.isCredentialShape(legacyDefault)) {
255
+ return {
256
+ store: {
257
+ version: 2,
258
+ currentProfile: DEFAULT_PROFILE,
259
+ profiles: { [DEFAULT_PROFILE]: legacyDefault }
260
+ },
261
+ wasLegacy: true
262
+ };
263
+ }
264
+ return null;
149
265
  } catch {
150
266
  return null;
151
267
  }
152
268
  }
153
- save(env, creds) {
269
+ ensureCacheDir() {
154
270
  if (!existsSync2(this.cacheDir)) {
155
- mkdirSync(this.cacheDir, { recursive: true });
271
+ mkdirSync(this.cacheDir, { recursive: true, mode: 448 });
272
+ try {
273
+ chmodSync(this.cacheDir, 448);
274
+ } catch {
275
+ }
156
276
  }
277
+ }
278
+ writeStore(env, store) {
279
+ this.ensureCacheDir();
157
280
  const credsPath = this.getCredentialsPath(env);
158
- writeFileSync(credsPath, JSON.stringify(creds, null, 2));
281
+ const tmpPath = `${credsPath}.tmp`;
282
+ writeFileSync(tmpPath, JSON.stringify(store, null, 2) + "\n", { mode: 384 });
283
+ try {
284
+ chmodSync(tmpPath, 384);
285
+ } catch {
286
+ }
287
+ renameSync(tmpPath, credsPath);
159
288
  try {
160
289
  chmodSync(credsPath, 384);
161
290
  } catch {
162
291
  }
163
292
  }
164
- clear(env) {
165
- const credsPath = this.getCredentialsPath(env);
166
- if (!existsSync2(credsPath)) {
293
+ filterExpired(creds) {
294
+ if (creds.expiresAt) {
295
+ const expiresAt = new Date(creds.expiresAt);
296
+ if (expiresAt <= /* @__PURE__ */ new Date()) {
297
+ if (creds.refreshToken) {
298
+ return creds;
299
+ }
300
+ return null;
301
+ }
302
+ }
303
+ return creds;
304
+ }
305
+ /**
306
+ * Load credentials for an environment + profile.
307
+ * When `profile` is omitted, uses the resolved current profile for `env`.
308
+ */
309
+ load(env, profile) {
310
+ const parsed = this.readStore(env);
311
+ if (!parsed) return null;
312
+ const { store, wasLegacy } = parsed;
313
+ const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;
314
+ if (profile) this.validateProfileName(profile);
315
+ const creds = store.profiles[name];
316
+ if (!creds) return null;
317
+ if (wasLegacy) {
318
+ this.writeStore(env, store);
319
+ }
320
+ return this.filterExpired(creds);
321
+ }
322
+ /**
323
+ * Save credentials for an environment + profile.
324
+ * When `profile` is omitted, uses the current profile (or "default").
325
+ * Sets currentProfile to the saved profile when the store was empty.
326
+ */
327
+ save(env, creds, profile) {
328
+ if (profile) this.validateProfileName(profile);
329
+ const store = this.readStore(env)?.store ?? this.emptyStore();
330
+ const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;
331
+ if (profile) {
332
+ store.profiles[name] = creds;
333
+ if (!store.profiles[store.currentProfile] || Object.keys(store.profiles).length === 1) {
334
+ store.currentProfile = name;
335
+ }
336
+ } else {
337
+ store.profiles[name] = creds;
338
+ if (!store.currentProfile) {
339
+ store.currentProfile = name;
340
+ }
341
+ }
342
+ this.writeStore(env, store);
343
+ }
344
+ /**
345
+ * Bulk-save many profiles in one write (used by dev-realms import).
346
+ * Does not change currentProfile unless the store was empty — then uses
347
+ * `preferCurrent` if provided and present, else `superadmin`, else first.
348
+ */
349
+ saveMany(env, profiles, preferCurrent) {
350
+ const names = Object.keys(profiles);
351
+ for (const name of names) {
352
+ this.validateProfileName(name);
353
+ }
354
+ const store = this.readStore(env)?.store ?? this.emptyStore();
355
+ const wasEmpty = Object.keys(store.profiles).length === 0;
356
+ for (const [name, creds] of Object.entries(profiles)) {
357
+ store.profiles[name] = creds;
358
+ }
359
+ if (wasEmpty) {
360
+ if (preferCurrent && store.profiles[preferCurrent]) {
361
+ store.currentProfile = preferCurrent;
362
+ } else if (store.profiles.superadmin) {
363
+ store.currentProfile = "superadmin";
364
+ } else {
365
+ store.currentProfile = names.sort()[0] ?? DEFAULT_PROFILE;
366
+ }
367
+ }
368
+ this.writeStore(env, store);
369
+ return names.length;
370
+ }
371
+ /**
372
+ * Clear one profile (default: current profile). Deletes the file when no profiles remain.
373
+ * Pass a profile name to clear a specific account without touching others.
374
+ */
375
+ clear(env, profile) {
376
+ const parsed = this.readStore(env);
377
+ if (!parsed) return;
378
+ const store = parsed.store;
379
+ const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;
380
+ if (profile) this.validateProfileName(profile);
381
+ delete store.profiles[name];
382
+ if (Object.keys(store.profiles).length === 0) {
383
+ const credsPath = this.getCredentialsPath(env);
384
+ if (existsSync2(credsPath)) {
385
+ try {
386
+ unlinkSync(credsPath);
387
+ } catch {
388
+ }
389
+ }
167
390
  return;
168
391
  }
169
- try {
170
- unlinkSync(credsPath);
171
- } catch {
392
+ if (store.currentProfile === name) {
393
+ store.currentProfile = Object.keys(store.profiles).sort()[0];
172
394
  }
395
+ this.writeStore(env, store);
173
396
  }
174
397
  getCacheDir() {
175
398
  return this.cacheDir;
176
399
  }
400
+ /** Absolute path of the credentials file for `env` (for docs / `auth list`). */
401
+ getCredentialsFilePath(env) {
402
+ return this.getCredentialsPath(env);
403
+ }
177
404
  };
405
+
406
+ // src/cli-utils/auth.ts
178
407
  function stderr(msg) {
179
408
  process.stderr.write(msg + "\n");
180
409
  }
@@ -314,25 +543,31 @@ async function dynamicClientRegistration(envConfig, clientName = "Gravity CLI")
314
543
  return await response.json();
315
544
  }
316
545
  async function performOAuthFlow(options) {
317
- const { env, envConfig, credentialsManager, clientName = "Gravity CLI" } = options;
546
+ const { env, envConfig, credentialsManager, clientName = "Gravity CLI", profile } = options;
547
+ const profileName = credentialsManager.resolveProfileName(profile, env);
318
548
  let hasRetried = false;
319
549
  while (true) {
320
- let creds = credentialsManager.load(env);
321
- if (!creds || !creds.clientId) {
550
+ let creds = credentialsManager.load(env, profileName);
551
+ if (!creds || !creds.clientId || !creds.clientSecret) {
322
552
  const registration = await dynamicClientRegistration(envConfig, clientName);
323
553
  creds = {
324
554
  clientId: registration.client_id,
325
555
  clientSecret: registration.client_secret
326
556
  };
327
- credentialsManager.save(env, creds);
557
+ credentialsManager.save(env, creds, profileName);
328
558
  } else {
329
559
  stderr(" Using cached client credentials");
330
560
  }
561
+ if (!creds.clientId || !creds.clientSecret) {
562
+ throw new Error("OAuth client credentials missing after registration");
563
+ }
564
+ const clientId = creds.clientId;
565
+ const clientSecret = creds.clientSecret;
331
566
  const { codeVerifier, codeChallenge } = generatePKCE();
332
567
  const state = randomBytes(32).toString("base64url");
333
568
  const authParams = new URLSearchParams({
334
569
  response_type: "code",
335
- client_id: creds.clientId,
570
+ client_id: clientId,
336
571
  redirect_uri: REDIRECT_URI,
337
572
  scope: "workspace:admin",
338
573
  state,
@@ -342,6 +577,7 @@ async function performOAuthFlow(options) {
342
577
  const authUrl = `${envConfig.apiUrl}/api/v2/oauth/authorize?${authParams}`;
343
578
  const callbackPromise = startCallbackServer();
344
579
  stderr("\n Opening browser for authorization...");
580
+ stderr(` Profile: ${profileName}`);
345
581
  stderr(` If browser doesn't open, visit:
346
582
  ${authUrl}
347
583
  `);
@@ -355,7 +591,7 @@ async function performOAuthFlow(options) {
355
591
  const msg = err instanceof Error ? err.message : String(err);
356
592
  if (!hasRetried && isOAuthError(msg, "invalid_client")) {
357
593
  stderr(" Cached client credentials expired, re-registering...");
358
- credentialsManager.clear(env);
594
+ credentialsManager.clear(env, profileName);
359
595
  hasRetried = true;
360
596
  continue;
361
597
  }
@@ -367,7 +603,7 @@ async function performOAuthFlow(options) {
367
603
  stderr(" Authorization code received!");
368
604
  stderr(" Exchanging code for tokens...");
369
605
  const tokenUrl = `${envConfig.apiUrl}/api/v2/oauth/token`;
370
- const authHeader = Buffer.from(`${creds.clientId}:${creds.clientSecret}`).toString("base64");
606
+ const authHeader = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
371
607
  const tokenResponse = await fetch(tokenUrl, {
372
608
  method: "POST",
373
609
  headers: {
@@ -385,7 +621,7 @@ async function performOAuthFlow(options) {
385
621
  const body = await tokenResponse.text();
386
622
  if (!hasRetried && isOAuthError(body, "invalid_client")) {
387
623
  stderr(" Cached client credentials expired, re-registering...");
388
- credentialsManager.clear(env);
624
+ credentialsManager.clear(env, profileName);
389
625
  hasRetried = true;
390
626
  continue;
391
627
  }
@@ -396,16 +632,18 @@ async function performOAuthFlow(options) {
396
632
  creds.accessToken = accessToken;
397
633
  creds.refreshToken = refreshToken ?? creds.refreshToken;
398
634
  creds.expiresAt = new Date(Date.now() + expiresIn * 1e3).toISOString();
399
- credentialsManager.save(env, creds);
635
+ credentialsManager.save(env, creds, profileName);
636
+ credentialsManager.setCurrentProfile(env, profileName);
400
637
  stderr(" Authentication successful!");
401
638
  return { accessToken, credentials: creds };
402
639
  }
403
640
  }
404
- async function refreshAccessToken(envConfig, creds, credentialsManager, env) {
641
+ async function refreshAccessToken(envConfig, creds, credentialsManager, env, profile) {
405
642
  if (!creds.refreshToken || !creds.clientId || !creds.clientSecret) {
406
643
  stderr(" Cannot refresh: missing credentials (re-login required)");
407
644
  return null;
408
645
  }
646
+ const profileName = credentialsManager.resolveProfileName(profile, env);
409
647
  const tokenUrl = `${envConfig.apiUrl}/api/v2/oauth/token`;
410
648
  const authHeader = Buffer.from(`${creds.clientId}:${creds.clientSecret}`).toString("base64");
411
649
  try {
@@ -428,7 +666,7 @@ async function refreshAccessToken(envConfig, creds, credentialsManager, env) {
428
666
  } else {
429
667
  stderr(` Token refresh failed (${response.status}), clearing credentials`);
430
668
  }
431
- credentialsManager.clear(env);
669
+ credentialsManager.clear(env, profileName);
432
670
  return null;
433
671
  }
434
672
  const tokenData = await response.json();
@@ -439,11 +677,11 @@ async function refreshAccessToken(envConfig, creds, credentialsManager, env) {
439
677
  refreshToken: refreshToken ?? creds.refreshToken,
440
678
  expiresAt: new Date(Date.now() + expiresIn * 1e3).toISOString()
441
679
  };
442
- credentialsManager.save(env, updated);
680
+ credentialsManager.save(env, updated, profileName);
443
681
  return updated;
444
682
  } catch (err) {
445
683
  stderr(` Token refresh error: ${err instanceof Error ? err.message : "unknown"}`);
446
- credentialsManager.clear(env);
684
+ credentialsManager.clear(env, profileName);
447
685
  return null;
448
686
  }
449
687
  }
@@ -828,6 +1066,7 @@ export {
828
1066
  CALLBACK_PATH,
829
1067
  REDIRECT_URI,
830
1068
  getEnvironmentConfig,
1069
+ DEFAULT_PROFILE,
831
1070
  CredentialsManager,
832
1071
  generatePKCE,
833
1072
  openBrowser,
@@ -846,4 +1085,4 @@ export {
846
1085
  getTextDelta,
847
1086
  getErrorMessage
848
1087
  };
849
- //# sourceMappingURL=chunk-GVUGFPCN.js.map
1088
+ //# sourceMappingURL=chunk-SIV2WU2I.js.map