@gravity-rail/cli 0.55.0 → 0.55.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli-utils/config.ts","../src/cli-utils/auth.ts","../package.json","../src/cli-utils/credentials-manager.ts","../src/cli-utils/ui.ts","../src/cli-utils/agui.ts"],"sourcesContent":["/**\n * CLI configuration for remote environments\n */\n\nimport { existsSync, readFileSync, statSync } from 'fs';\nimport { CORE_SCHEMA, defineSequenceTag, load } from 'js-yaml';\nimport { dirname, join, resolve } from 'path';\n\nexport interface EnvironmentConfig {\n apiUrl: string;\n frontendUrl: string;\n}\n\n/** Optional per-field URL overrides and the checkout to classify. */\nexport interface EnvironmentConfigOptions {\n apiUrl?: string;\n frontendUrl?: string;\n /** Directory to start the worktree walk from. Defaults to `process.cwd()`. */\n startDir?: string;\n}\n\nexport const ENVIRONMENTS: Record<string, EnvironmentConfig> = {\n dev: {\n apiUrl: 'https://api.gravityrail.test:5054',\n frontendUrl: 'https://app.gravityrail.test:3000',\n },\n prod: {\n apiUrl: 'https://api.gravityrail.com',\n frontendUrl: 'https://app.gravityrail.com',\n },\n staging: {\n apiUrl: 'https://api.gr-staging.com',\n frontendUrl: 'https://app.gr-staging.com',\n },\n};\n\nexport const CALLBACK_PORT = 31337;\nexport const CALLBACK_PATH = '/oauth/callback';\nexport const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;\n\nconst API_CONTAINER_PORT = 443;\nconst FRONTEND_CONTAINER_PORT = 3000;\nconst WORKTREE_SETUP_MARKER = 'worktree-setup.sh';\nconst OVERRIDE_FILENAME = 'docker-compose.override.yml';\n\n/**\n * Docker Compose `ports: !override` is an identity sequence tag. Register it\n * so generated worktree overrides load; do not implement merge/replace.\n */\nconst OVERRIDE_SEQUENCE_TAG = defineSequenceTag<unknown[]>('!override', {\n create: () => [],\n addItem: (carrier, item) => {\n // Must not return a value — js-yaml treats a truthy addItem return as a parse error.\n carrier.push(item);\n },\n identify: () => false,\n});\n\nconst COMPOSE_OVERRIDE_SCHEMA = CORE_SCHEMA.withTags(OVERRIDE_SEQUENCE_TAG);\n\nexport type CheckoutClassification =\n | { kind: 'non-worktree'; root?: string }\n | {\n kind: 'resolved-worktree';\n root: string;\n overridePath: string;\n apiPort: number;\n frontendPort: number;\n }\n | {\n kind: 'unresolved-worktree';\n root: string;\n overridePath: string;\n reason: string;\n };\n\nexport class WorktreeConfigError extends Error {\n constructor(\n message: string,\n readonly checkoutRoot: string,\n readonly overridePath: string\n ) {\n super(message);\n this.name = 'WorktreeConfigError';\n }\n}\n\n/** A plain JSON object: not null, not an array. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction parsePortToken(token: string | undefined): number | undefined {\n if (token === undefined || !/^\\d+$/.test(token)) return undefined;\n const value = Number(token);\n if (!Number.isInteger(value) || value <= 0 || value > 65535) return undefined;\n return value;\n}\n\n/**\n * Parse `published` and `target` from the right edge of a short-form mapping.\n * The optional bind-address prefix (including `${DOCKER_BIND_IP:-...}`) stays\n * opaque — it is never interpolated.\n */\nexport function parsePublishedTarget(\n mapping: string\n): { published: number; target: number } | undefined {\n const parts = mapping.split(':');\n if (parts.length < 2) return undefined;\n const target = parsePortToken(parts[parts.length - 1]);\n const published = parsePortToken(parts[parts.length - 2]);\n if (target === undefined || published === undefined) return undefined;\n return { published, target };\n}\n\nfunction publishedPortForTarget(ports: unknown, target: number): number | undefined {\n if (!Array.isArray(ports)) return undefined;\n for (const entry of ports) {\n if (typeof entry !== 'string') continue;\n const parsed = parsePublishedTarget(entry);\n if (parsed?.target === target) return parsed.published;\n }\n return undefined;\n}\n\nfunction servicePorts(services: Record<string, unknown>, name: string): unknown {\n const service = services[name];\n if (!isRecord(service)) return undefined;\n return service['ports'];\n}\n\nfunction findNearestGit(startDir: string): { root: string; gitIsFile: boolean } | undefined {\n let dir = resolve(startDir);\n while (true) {\n const gitPath = join(dir, '.git');\n if (existsSync(gitPath)) {\n try {\n const st = statSync(gitPath);\n return { root: dir, gitIsFile: st.isFile() };\n } catch {\n // Unreadable .git entry — keep walking.\n }\n }\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n}\n\nfunction unresolved(\n root: string,\n overridePath: string,\n reason: string\n): Extract<CheckoutClassification, { kind: 'unresolved-worktree' }> {\n return { kind: 'unresolved-worktree', root, overridePath, reason };\n}\n\nfunction loadComposeDocument(content: string): Record<string, unknown> | undefined {\n let loaded: unknown;\n try {\n loaded = load(content, { schema: COMPOSE_OVERRIDE_SCHEMA });\n } catch {\n return undefined;\n }\n return isRecord(loaded) ? loaded : undefined;\n}\n\n/**\n * Classify the checkout rooted at (or above) `startDir`.\n *\n * A linked worktree has a `.git` *file*. A primary checkout has a `.git`\n * directory. No repository is treated as non-worktree so packaged CLI\n * invocations keep environment defaults.\n */\nexport function classifyCheckout(startDir: string = process.cwd()): CheckoutClassification {\n const git = findNearestGit(startDir);\n if (!git || !git.gitIsFile) {\n return { kind: 'non-worktree', root: git?.root };\n }\n\n const overridePath = join(git.root, OVERRIDE_FILENAME);\n if (!existsSync(overridePath)) {\n return unresolved(\n git.root,\n overridePath,\n `docker-compose.override.yml (generated by ${WORKTREE_SETUP_MARKER})`\n );\n }\n\n let content: string;\n try {\n content = readFileSync(overridePath, 'utf-8');\n } catch {\n return unresolved(git.root, overridePath, 'readable generated worktree override');\n }\n\n if (!content.includes(WORKTREE_SETUP_MARKER)) {\n return unresolved(\n git.root,\n overridePath,\n `generated ${WORKTREE_SETUP_MARKER} override marker`\n );\n }\n\n const document = loadComposeDocument(content);\n if (!document) {\n return unresolved(git.root, overridePath, 'valid generated worktree override YAML');\n }\n\n const services = document['services'];\n if (!isRecord(services)) {\n return unresolved(\n git.root,\n overridePath,\n `api-nginx published port for container ${API_CONTAINER_PORT}; frontend published port for container ${FRONTEND_CONTAINER_PORT}`\n );\n }\n\n const apiPort = publishedPortForTarget(servicePorts(services, 'api-nginx'), API_CONTAINER_PORT);\n const frontendPort = publishedPortForTarget(\n servicePorts(services, 'frontend'),\n FRONTEND_CONTAINER_PORT\n );\n const missing: string[] = [];\n if (apiPort === undefined) {\n missing.push(`api-nginx published port for container ${API_CONTAINER_PORT}`);\n }\n if (frontendPort === undefined) {\n missing.push(`frontend published port for container ${FRONTEND_CONTAINER_PORT}`);\n }\n if (apiPort === undefined || frontendPort === undefined) {\n return unresolved(git.root, overridePath, missing.join('; '));\n }\n\n return {\n kind: 'resolved-worktree',\n root: git.root,\n overridePath,\n apiPort,\n frontendPort,\n };\n}\n\nfunction worktreeFailClosed(\n checkout: Extract<CheckoutClassification, { kind: 'unresolved-worktree' }>,\n missingFields: string[]\n): never {\n throw new WorktreeConfigError(\n `Cannot resolve ${missingFields.join(' and ')} URL(s) for this linked worktree.\\n` +\n `Checkout: ${checkout.root}\\n` +\n `Override: ${checkout.overridePath}\\n` +\n `Missing: ${checkout.reason}\\n` +\n `Re-run scripts/worktree-setup.sh . from this checkout, or pass explicit ` +\n `--api-url and GRAVITY_RAIL_FRONTEND_URL (or both GRAVITY_RAIL_API_URL and ` +\n `GRAVITY_RAIL_FRONTEND_URL) so neither URL inherits the primary checkout.`,\n checkout.root,\n checkout.overridePath\n );\n}\n\nfunction worktreeDevUrls(apiPort: number, frontendPort: number): EnvironmentConfig {\n return {\n apiUrl: `https://api.gravityrail.test:${apiPort}`,\n frontendUrl: `https://app.gravityrail.test:${frontendPort}`,\n };\n}\n\nexport function getEnvironmentConfig(\n env: string,\n options: EnvironmentConfigOptions = {}\n): EnvironmentConfig {\n // Handle dev-cloud environments: --env dev-cloud:NAMESPACE\n if (env.startsWith('dev-cloud:')) {\n const namespace = env.slice('dev-cloud:'.length);\n if (!namespace) {\n throw new Error('dev-cloud environment requires a namespace: --env dev-cloud:NAMESPACE');\n }\n return {\n apiUrl:\n options.apiUrl ||\n process.env.GRAVITY_RAIL_API_URL ||\n `https://${namespace}-api.gravityrail-dev.com`,\n frontendUrl:\n options.frontendUrl ||\n process.env.GRAVITY_RAIL_FRONTEND_URL ||\n `https://${namespace}-app.gravityrail-dev.com`,\n };\n }\n\n const config = ENVIRONMENTS[env];\n if (!config) {\n throw new Error(\n `Invalid environment: ${env}. Use 'dev', 'staging', 'prod', or 'dev-cloud:NAMESPACE'.`\n );\n }\n\n const explicitApi = options.apiUrl || process.env.GRAVITY_RAIL_API_URL;\n const explicitFrontend = options.frontendUrl || process.env.GRAVITY_RAIL_FRONTEND_URL;\n\n if (env !== 'dev') {\n return {\n apiUrl: explicitApi || config.apiUrl,\n frontendUrl: explicitFrontend || config.frontendUrl,\n };\n }\n\n const checkout = classifyCheckout(options.startDir ?? process.cwd());\n\n if (checkout.kind === 'non-worktree') {\n return {\n apiUrl: explicitApi || config.apiUrl,\n frontendUrl: explicitFrontend || config.frontendUrl,\n };\n }\n\n if (checkout.kind === 'resolved-worktree') {\n const detected = worktreeDevUrls(checkout.apiPort, checkout.frontendPort);\n return {\n apiUrl: explicitApi || detected.apiUrl,\n frontendUrl: explicitFrontend || detected.frontendUrl,\n };\n }\n\n const missingFields: string[] = [];\n if (!explicitApi) missingFields.push('API');\n if (!explicitFrontend) missingFields.push('frontend');\n if (missingFields.length > 0) {\n worktreeFailClosed(checkout, missingFields);\n }\n return { apiUrl: explicitApi!, frontendUrl: explicitFrontend! };\n}\n","/**\n * CLI OAuth2 authentication utilities\n *\n * Provides OAuth2 with PKCE flow for CLI tools, including:\n * - Credential caching (per environment + named profile)\n * - Dynamic client registration\n * - Token refresh\n * - Browser-based authorization\n *\n * Credential persistence lives in `./credentials-manager` (multi-account profiles).\n */\n\nimport { createServer } from 'http';\nimport { createHash, randomBytes } from 'crypto';\nimport { exec } from 'child_process';\nimport { existsSync, mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport lockfile from 'proper-lockfile';\nimport pkg from '../../package.json';\nimport { EnvironmentConfig, CALLBACK_PORT, CALLBACK_PATH, REDIRECT_URI } from './config';\nimport { CredentialsManager, type CLICredentials } from './credentials-manager';\n\nexport {\n CredentialsManager,\n DEFAULT_PROFILE,\n type CLICredentials,\n type ProfileSummary,\n} from './credentials-manager';\n\nexport interface AuthResult {\n accessToken: string;\n credentials?: CLICredentials;\n}\n\nexport interface NonInteractiveAuthOptions {\n env: string;\n envConfig: EnvironmentConfig;\n credentialsManager: CredentialsManager;\n profile?: string;\n}\n\nconst ACCESS_TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;\n\n/**\n * Sent on every token grant so reuse investigations can tell CLI releases\n * apart. Self-reported identification, not authentication.\n */\nconst TOKEN_REQUEST_USER_AGENT = `gravityrail-cli/${pkg.version}`;\n\n/** The one timeout on a refresh: the token POST, including reading its body. */\nconst REFRESH_REQUEST_TIMEOUT_MS = 15_000;\n\n/**\n * How long a refresh waits for another process's refresh of the same\n * credential file: 100, 200, 400 and 800 ms, then 1 s per retry — about 20.5 s\n * over 23 retries. That outlasts one whole refresh by the lock holder (bounded\n * by `REFRESH_REQUEST_TIMEOUT_MS`), so a waiter adopts the winner's result\n * instead of giving up. The library keeps a held lock fresh and reclaims one\n * abandoned by a crashed process, so its `stale`/`update` defaults are kept.\n */\nconst REFRESH_LOCK_RETRIES = { retries: 23, factor: 2, minTimeout: 100, maxTimeout: 1000 };\n\n// Refresh failures that are not a verdict on the stored token. Fixed strings\n// by design: never interpolate tokens, response bodies or transport errors.\nconst REFRESH_LOCK_FAILED =\n 'Could not lock the credential store to refresh the access token (another gr process may still be refreshing). Stored credentials were kept; retry the command.';\nconst REFRESH_REQUEST_FAILED =\n 'Token refresh did not complete (network error or timeout). Stored credentials were kept; retry the command.';\nconst REFRESH_FAILED =\n 'Token refresh failed. Stored credentials were kept; retry the command, and log in again if it keeps failing.';\nconst REFRESH_SUPERSEDED =\n 'Token refresh was rejected, but the stored credentials changed meanwhile and were kept. Retry the command.';\nconst REFRESH_BAD_RESPONSE =\n 'Token refresh returned an unusable response. Stored credentials were kept; retry the command.';\nconst REFRESH_SAVE_FAILED =\n 'Token refresh succeeded, but the new credentials could not be saved. Retry the command, and log in again if it keeps failing.';\n\nfunction stderr(msg: string): void {\n process.stderr.write(msg + '\\n');\n}\n\n/**\n * Parse an OAuth error response body, checking the JSON `error` field first,\n * falling back to text matching.\n */\nfunction isOAuthError(body: string, errorCode: string): boolean {\n try {\n const parsed = JSON.parse(body);\n if (parsed.error === errorCode) return true;\n } catch {\n // Not JSON — fall through to text match\n }\n return body.includes(errorCode);\n}\n\n/**\n * Validate and extract fields from an OAuth token response.\n * Throws on malformed responses per RFC 6749 §5.1.\n */\nfunction validateTokenResponse(tokenData: Record<string, unknown>): {\n accessToken: string;\n refreshToken?: string;\n expiresIn: number;\n} {\n if (!tokenData.access_token || typeof tokenData.access_token !== 'string') {\n throw new Error('Token response missing access_token');\n }\n\n if (tokenData.token_type && String(tokenData.token_type).toLowerCase() !== 'bearer') {\n throw new Error(`Unsupported token type: ${tokenData.token_type}`);\n }\n\n let expiresIn = 86400; // default 24h\n if (tokenData.expires_in != null) {\n const raw = Number(tokenData.expires_in);\n if (!Number.isFinite(raw) || raw < 60 || raw > 31536000) {\n stderr(' Warning: unusual expires_in value in the token response, using default');\n } else {\n expiresIn = raw;\n }\n }\n\n return {\n accessToken: tokenData.access_token,\n refreshToken:\n typeof tokenData.refresh_token === 'string' ? tokenData.refresh_token : undefined,\n expiresIn,\n };\n}\n\n/**\n * Generate PKCE code verifier and challenge\n */\nexport function generatePKCE(): { codeVerifier: string; codeChallenge: string } {\n const codeVerifier = randomBytes(64).toString('base64url').slice(0, 128);\n const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');\n return { codeVerifier, codeChallenge };\n}\n\n/**\n * Open a URL in the default browser\n */\nexport function openBrowser(url: string): void {\n const platform = process.platform;\n let cmd: string;\n if (platform === 'darwin') {\n cmd = `open \"${url}\"`;\n } else if (platform === 'win32') {\n cmd = `start \"\" \"${url}\"`;\n } else {\n cmd = `xdg-open \"${url}\"`;\n }\n\n exec(cmd, (err) => {\n if (err) {\n stderr(' Could not open browser automatically.');\n }\n });\n}\n\n/**\n * Start a local HTTP server to receive the OAuth callback\n */\nexport function startCallbackServer(): Promise<{ code: string; state: string }> {\n return new Promise((resolve, reject) => {\n let authCode: string | null = null;\n let error: string | null = null;\n let receivedState: string | null = null;\n\n const server = createServer((req, res) => {\n const url = new URL(req.url!, `http://localhost:${CALLBACK_PORT}`);\n\n if (url.pathname !== CALLBACK_PATH) {\n res.writeHead(404);\n res.end('Not found');\n return;\n }\n\n if (url.searchParams.has('error')) {\n error = url.searchParams.get('error');\n const errorDesc = url.searchParams.get('error_description') || 'Unknown error';\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(`\n <html>\n <head><title>Authorization Failed</title></head>\n <body style=\"font-family: system-ui; text-align: center; padding: 50px;\">\n <h1 style=\"color: #dc2626;\">Authorization Failed</h1>\n <p>${errorDesc}</p>\n <p>You can close this window.</p>\n </body>\n </html>\n `);\n server.close();\n reject(new Error(`Authorization failed: ${error}`));\n } else if (url.searchParams.has('code')) {\n authCode = url.searchParams.get('code')!;\n receivedState = url.searchParams.get('state')!;\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(`\n <html>\n <head><title>Authorization Successful</title></head>\n <body style=\"font-family: system-ui; text-align: center; padding: 50px;\">\n <h1 style=\"color: #16a34a;\">Authorization Successful!</h1>\n <p>You can close this window and return to the terminal.</p>\n <script>setTimeout(() => window.close(), 2000);</script>\n </body>\n </html>\n `);\n server.close();\n resolve({ code: authCode, state: receivedState });\n } else {\n res.writeHead(400);\n res.end('Missing code parameter');\n }\n });\n\n server.listen(CALLBACK_PORT, 'localhost', () => {\n // Server started\n });\n\n // Timeout after 5 minutes (unref so it doesn't keep the process alive)\n const timeout = setTimeout(\n () => {\n if (!authCode && !error) {\n server.close();\n reject(new Error('Authorization timeout - no response received'));\n }\n },\n 5 * 60 * 1000\n );\n timeout.unref();\n });\n}\n\n/**\n * Vendor field in the RFC 8414 discovery document naming the server's pinned\n * first-party CLI client. Must match `FIRST_PARTY_CLIENT_METADATA_FIELD` in\n * `lib/routes/oauth/first_party.py`.\n */\nexport const FIRST_PARTY_CLIENT_METADATA_FIELD = 'gravityrail_first_party_cli_client_id';\n\n/**\n * Ask the server for its pinned first-party CLI client id, or null if it has none.\n *\n * Null is the dynamic-registration fallback trigger, and it has to cover two\n * different situations that the CLI cannot and need not tell apart: a server\n * deployed before the pinned client shipped, and a discovery request that\n * failed. Both mean \"we have no pinned identity to use here\", and DCR is the\n * correct answer to both — so every failure mode collapses to null rather than\n * throwing and taking down a login that would otherwise have worked.\n */\nexport async function fetchFirstPartyClientId(\n envConfig: EnvironmentConfig\n): Promise<string | null> {\n try {\n const response = await fetch(`${envConfig.apiUrl}/.well-known/oauth-authorization-server`, {\n headers: { Accept: 'application/json' },\n });\n if (!response.ok) return null;\n const metadata = (await response.json()) as Record<string, unknown>;\n const clientId = metadata[FIRST_PARTY_CLIENT_METADATA_FIELD];\n return typeof clientId === 'string' && clientId.length > 0 ? clientId : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Register a new OAuth client dynamically.\n *\n * Retained for servers that predate the pinned first-party client — older\n * self-hosted and dev deployments — not as the normal path. See\n * `resolveOAuthClient`.\n */\nasync function dynamicClientRegistration(\n envConfig: EnvironmentConfig,\n clientName: string = 'Gravity CLI'\n): Promise<{ client_id: string; client_secret: string }> {\n stderr(' Registering OAuth client...');\n\n const registrationUrl = `${envConfig.apiUrl}/api/v2/oauth/register`;\n\n const registrationData = {\n client_name: clientName,\n redirect_uris: [REDIRECT_URI],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'client_secret_basic',\n scope: 'workspace:admin',\n };\n\n const response = await fetch(registrationUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(registrationData),\n });\n\n if (!response.ok) {\n throw new Error(`Client registration failed: ${response.status} - ${await response.text()}`);\n }\n\n return (await response.json()) as { client_id: string; client_secret: string };\n}\n\n/**\n * Build the client-authentication half of a token request.\n *\n * A confidential client (one we registered dynamically, which issued us a\n * secret) authenticates with HTTP Basic. The pinned first-party client has no\n * secret and identifies itself with a body `client_id` instead — RFC 6749\n * §2.3.1, and RFC 8252 §8.5 for why a CLI cannot hold one in the first place.\n * The server accepts either and reads the id from the header first.\n */\nfunction tokenRequestAuth(\n clientId: string,\n clientSecret?: string\n): { headers: Record<string, string>; params: Record<string, string> } {\n if (clientSecret) {\n const authHeader = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');\n return {\n headers: { Authorization: `Basic ${authHeader}` },\n params: {},\n };\n }\n return { headers: {}, params: { client_id: clientId } };\n}\n\n/**\n * Decide which OAuth client this login should use.\n *\n * The pinned first-party client wins whenever the server has one, including\n * for profiles that were created by an older CLI against the same server —\n * that is what retires per-login dynamic registration rather than merely\n * stopping new profiles from using it. Swapping is safe precisely here and\n * nowhere else: `performOAuthFlow` mints a fresh token pair anyway, so nothing\n * bound to the old client survives the call. Profiles that are never logged in\n * again keep refreshing against their old client untouched.\n *\n * `forceDynamicRegistration` is the retry path: the server has just answered\n * `invalid_client` for whichever client the branches below chose, so it must\n * skip **both** of the reuse branches, not only discovery. Skipping discovery\n * alone would fall through to the cached branch and hand back the very client\n * that was rejected — and when the rejected client *was* the cached one, that\n * is an identical second attempt. \"Register a fresh client\" is the only\n * response that can differ from the attempt that just failed.\n *\n * Exported so the three branches can be tested directly — `performOAuthFlow`\n * around it opens a browser and binds a loopback port, which no unit test\n * should have to stand up to assert which client was chosen.\n */\nexport async function resolveOAuthClient(\n envConfig: EnvironmentConfig,\n stored: CLICredentials | null,\n clientName: string = 'Gravity CLI',\n forceDynamicRegistration: boolean = false\n): Promise<{ clientId: string; clientSecret?: string }> {\n if (!forceDynamicRegistration) {\n const pinned = await fetchFirstPartyClientId(envConfig);\n if (pinned) {\n stderr(' Using the first-party CLI client');\n return { clientId: pinned };\n }\n\n // No pinned client on this server. Reuse whatever this profile\n // registered before, so an older environment does not accumulate a\n // client per login.\n if (stored?.clientId && stored.clientSecret) {\n stderr(' Using cached client credentials');\n return { clientId: stored.clientId, clientSecret: stored.clientSecret };\n }\n }\n\n const registration = await dynamicClientRegistration(envConfig, clientName);\n return { clientId: registration.client_id, clientSecret: registration.client_secret };\n}\n\nexport interface OAuthFlowOptions {\n env: string;\n envConfig: EnvironmentConfig;\n credentialsManager: CredentialsManager;\n clientName?: string;\n /** Named account profile to store tokens under (default: resolved current). */\n profile?: string;\n /**\n * Test seam: wait for the loopback callback. Defaults to the real server.\n *\n * The two side effects below are the reason this function had no direct\n * tests — one shells out to the system browser and the other binds a fixed\n * port, neither of which a unit test may do. Injecting them is what lets\n * the \"a failed login must not touch the stored profile\" invariant be\n * tested at all. Production never passes either.\n */\n startCallback?: () => Promise<{ code: string; state: string }>;\n /** Test seam: open the authorization URL. Defaults to the real browser. */\n openAuthUrl?: (url: string) => void;\n}\n\n/**\n * Perform the full OAuth2 browser-based flow with PKCE.\n *\n * This always opens the browser for authorization. It never returns a cached\n * token — that's the job of createAuthenticatedContext. The only thing reused\n * is the registered OAuth client ID (which is identity, not auth).\n *\n * **Nothing is written to the credential store until the token exchange has\n * succeeded.** A login can end at the consent screen the member cancels, at a\n * network failure, or at the five-minute callback timeout, and every one of\n * those must leave the profile exactly as it was — see the comment on the\n * single `save` below for what persisting early actually broke.\n */\nexport async function performOAuthFlow(options: OAuthFlowOptions): Promise<AuthResult> {\n const {\n env,\n envConfig,\n credentialsManager,\n clientName = 'Gravity CLI',\n profile,\n startCallback = startCallbackServer,\n openAuthUrl = openBrowser,\n } = options;\n const profileName = credentialsManager.resolveProfileName(profile, env);\n let hasRetried = false;\n let forceDynamicRegistration = false;\n\n while (true) {\n const stored = credentialsManager.load(env, profileName);\n const { clientId, clientSecret } = await resolveOAuthClient(\n envConfig,\n stored,\n clientName,\n forceDynamicRegistration\n );\n\n const { codeVerifier, codeChallenge } = generatePKCE();\n const state = randomBytes(32).toString('base64url');\n\n const authParams = new URLSearchParams({\n response_type: 'code',\n client_id: clientId,\n redirect_uri: REDIRECT_URI,\n scope: 'workspace:admin',\n state: state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n });\n\n const authUrl = `${envConfig.apiUrl}/api/v2/oauth/authorize?${authParams}`;\n\n const callbackPromise = startCallback();\n\n stderr('\\n Opening browser for authorization...');\n stderr(` Profile: ${profileName}`);\n stderr(` If browser doesn't open, visit:\\n ${authUrl}\\n`);\n openAuthUrl(authUrl);\n\n stderr(' Waiting for authorization (timeout: 5 minutes)...');\n let code: string;\n let receivedState: string;\n try {\n ({ code, state: receivedState } = await callbackPromise);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!hasRetried && isOAuthError(msg, 'invalid_client')) {\n stderr(' Client rejected by the server, re-registering...');\n hasRetried = true;\n // The rejected client was only ever held in memory, so there is\n // nothing on disk to clean up — `forceDynamicRegistration` alone\n // is what makes the next pass register a fresh one.\n forceDynamicRegistration = true;\n continue;\n }\n throw err;\n }\n\n if (receivedState !== state) {\n throw new Error('State mismatch - possible CSRF attack');\n }\n\n stderr(' Authorization code received!');\n stderr(' Exchanging code for tokens...');\n\n const tokenUrl = `${envConfig.apiUrl}/api/v2/oauth/token`;\n const auth = tokenRequestAuth(clientId, clientSecret);\n\n const tokenResponse = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n ...auth.headers,\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': TOKEN_REQUEST_USER_AGENT,\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: code,\n redirect_uri: REDIRECT_URI,\n code_verifier: codeVerifier,\n ...auth.params,\n }),\n });\n\n if (!tokenResponse.ok) {\n const body = await tokenResponse.text();\n if (!hasRetried && isOAuthError(body, 'invalid_client')) {\n stderr(' Client rejected by the server, re-registering...');\n hasRetried = true;\n forceDynamicRegistration = true;\n continue;\n }\n throw new Error(`Token exchange failed: ${tokenResponse.status} - ${body}`);\n }\n\n const tokenData = (await tokenResponse.json()) as Record<string, unknown>;\n const { accessToken, refreshToken, expiresIn } = validateTokenResponse(tokenData);\n\n // The one and only write, and it happens after the exchange has already\n // succeeded. Persisting the client identity before the browser step used\n // to leave a cancelled, failed or timed-out login with the NEW client id\n // sitting next to the OLD tokens — and the refresh grant looks a token\n // up by `refresh_token AND app_id` (`lib/routes/oauth/oauth_token.py`),\n // so that pairing matches no row and the next refresh is a guaranteed\n // `invalid_grant`. A member who simply closed the consent tab lost the\n // session they already had.\n //\n // A refresh token is only carried over when the client identity is\n // unchanged, for the same reason: one minted for the previous client is\n // not merely useless under the new one, it is actively harmful, because\n // the server answers it with `invalid_grant` and `refreshAccessToken`\n // clears the profile on that definitive rejection.\n // Dropping it degrades to \"log in again\", which is the honest outcome.\n //\n // `clientSecret` is written unconditionally, and when it is `undefined`\n // that is load-bearing rather than a no-op: the spread has just copied a\n // stale DCR secret out of `stored`, and this key overwrites it with\n // `undefined`, which `JSON.stringify` then omits entirely from the\n // persisted store. Both halves matter — in memory `tokenRequestAuth`\n // reads `undefined` and takes the body-`client_id` path, and on disk the\n // secret is gone rather than lying in wait for the next load. Dropping\n // this key \"because undefined does nothing\" would put the pinned public\n // client back on the Basic-auth path with a secret it does not have.\n //\n // Everything else on the profile (pinned workspace, cached org sessions)\n // is preserved — only the client identity and the tokens are decided\n // here, and org sessions are keyed to the account, not the client.\n const clientChanged = stored?.clientId !== clientId;\n const creds: CLICredentials = {\n ...(stored ?? {}),\n clientId,\n clientSecret,\n accessToken,\n refreshToken: refreshToken ?? (clientChanged ? undefined : stored?.refreshToken),\n expiresAt: new Date(Date.now() + expiresIn * 1000).toISOString(),\n };\n\n credentialsManager.save(env, creds, profileName);\n credentialsManager.setCurrentProfile(env, profileName);\n stderr(' Authentication successful!');\n\n return { accessToken, credentials: creds };\n }\n}\n\n/**\n * The `error` code of a JSON OAuth error body, or undefined when the body is\n * not one.\n *\n * Unlike `isOAuthError` there is no substring fallback: this decides whether a\n * profile is deleted, and only a structured server verdict may do that.\n */\nfunction parseOAuthErrorCode(body: string): string | undefined {\n try {\n const parsed: unknown = JSON.parse(body);\n if (parsed && typeof parsed === 'object') {\n const code = (parsed as { error?: unknown }).error;\n if (typeof code === 'string') return code;\n }\n } catch {\n // Not JSON — no verdict.\n }\n return undefined;\n}\n\n/**\n * Whether the stored access token has enough life left to run a command on.\n *\n * The same `ACCESS_TOKEN_EXPIRY_BUFFER_MS` horizon the resolver uses, so a\n * single process that entered the refresh with a few minutes left still\n * rotates instead of adopting a token that may expire mid-command. A waiter\n * still adopts the winner's successor without rotating it again: the server\n * mints access tokens for far longer than the buffer (24 h, `oauth_token.py`),\n * so a successor another process just stored always clears this check. A token\n * with no recorded expiry is usable, as everywhere else.\n */\nfunction isFreshEnough(creds: CLICredentials): boolean {\n return (\n !creds.expiresAt ||\n new Date(creds.expiresAt).getTime() > Date.now() + ACCESS_TOKEN_EXPIRY_BUFFER_MS\n );\n}\n\n/**\n * Take the cross-process refresh lock for one credential file.\n *\n * The target is the credential file's own path, so every refresh that shares\n * an environment (and API port) file serializes, whichever profile it is for.\n * The file need not exist: the library locks with a sibling `<path>.lock`\n * directory and `realpath: false` skips resolving the target.\n */\nasync function acquireRefreshLock(credentialsPath: string): Promise<() => Promise<void>> {\n try {\n const cacheDir = dirname(credentialsPath);\n if (!existsSync(cacheDir)) {\n mkdirSync(cacheDir, { recursive: true, mode: 0o700 });\n }\n return await lockfile.lock(credentialsPath, {\n realpath: false,\n retries: REFRESH_LOCK_RETRIES,\n // The library default throws from a timer, which would crash the\n // command. Losing the lock mid-refresh is worth a warning, not that.\n // The library has already dropped its in-process record of the lock\n // by the time this runs, so the `release()` in `refreshAccessToken`'s\n // `finally` is refused (ENOTACQUIRED) and can never remove a lock\n // another process has since taken.\n onCompromised: () => {\n stderr(\n ' Warning: lost the credential refresh lock; another gr process may refresh at the same time'\n );\n },\n });\n } catch {\n // Never fall back to an unlocked refresh: that is exactly the race this\n // lock exists to prevent.\n throw new Error(REFRESH_LOCK_FAILED);\n }\n}\n\n/**\n * Refresh the selected profile's access token, coordinating with other `gr`\n * processes on this machine (GitHub #29807).\n *\n * The server rotates refresh tokens and rejects a spent one as reuse, so two\n * processes presenting the same token race: one wins, the other gets\n * `invalid_grant`. Everything from reading the stored token to saving its\n * successor therefore runs under one lock on the credential file. A process\n * that waited re-reads the store and adopts what the winner saved instead of\n * presenting the spent token.\n *\n * `creds` is the caller's pre-lock snapshot and is deliberately not consulted:\n * every decision — whether a refresh is needed, which token to present, what\n * to save — is made from the profile read under the lock, because the snapshot\n * may already have been rotated away, logged out, or replaced by a login. The\n * parameter stays for the exported signature.\n *\n * Returns the profile's usable credentials, or null when the profile cannot\n * authenticate — including after a definitive rejection, which clears that\n * profile only. Any other failure (network, timeout, server error, malformed\n * response, a busy lock, a failed save) leaves the store untouched and throws a\n * plain Error whose message is fixed: no token, response body, or transport\n * detail ever reaches it.\n */\nexport async function refreshAccessToken(\n envConfig: EnvironmentConfig,\n _creds: CLICredentials,\n credentialsManager: CredentialsManager,\n env: string,\n profile?: string\n): Promise<CLICredentials | null> {\n // Resolved once, before waiting, so a `gr auth switch` in another process\n // cannot redirect this command to a different profile mid-refresh.\n const profileName = credentialsManager.resolveProfileName(profile, env);\n const release = await acquireRefreshLock(credentialsManager.getCredentialsFilePath(env));\n try {\n return await refreshUnderLock(envConfig, credentialsManager, env, profileName);\n } finally {\n // Rejects only when the lock was already lost (see `onCompromised`);\n // there is nothing left to release then.\n await release().catch(() => undefined);\n }\n}\n\nasync function refreshUnderLock(\n envConfig: EnvironmentConfig,\n credentialsManager: CredentialsManager,\n env: string,\n profileName: string\n): Promise<CLICredentials | null> {\n const stored = credentialsManager.load(env, profileName);\n\n // Another process may have refreshed while this one waited for the lock.\n if (stored?.accessToken && isFreshEnough(stored)) {\n return stored;\n }\n // A client secret is no longer part of the requirement: the pinned\n // first-party client is public and has none, and demanding one here would\n // silently strand every profile logged in with it — `context.ts` treats a\n // null return as \"re-login required\", so the CLI would open a browser on\n // every command instead of refreshing.\n if (!stored?.refreshToken || !stored.clientId) {\n stderr(' Cannot refresh: missing credentials (re-login required)');\n return null;\n }\n\n const presentedRefreshToken = stored.refreshToken;\n const auth = tokenRequestAuth(stored.clientId, stored.clientSecret);\n\n let response: Response;\n try {\n response = await fetch(`${envConfig.apiUrl}/api/v2/oauth/token`, {\n method: 'POST',\n headers: {\n ...auth.headers,\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': TOKEN_REQUEST_USER_AGENT,\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: presentedRefreshToken,\n ...auth.params,\n }),\n // Bounds the request and the body reads below. Never retried: a\n // second POST of the same token is itself a reuse.\n //\n // Known residual: an abort only stops *this* side. If the server\n // commits the rotation after the deadline, the response carrying the\n // successor is lost and the stored token is spent; the next refresh\n // of it is a genuine invalid_grant and clears the profile. The client\n // cannot recover an unseen successor; the deadline is long enough\n // that this needs a token endpoint stalled for 15 s, not a slow one.\n signal: AbortSignal.timeout(REFRESH_REQUEST_TIMEOUT_MS),\n });\n } catch {\n throw new Error(REFRESH_REQUEST_FAILED);\n }\n\n if (!response.ok) {\n let body: string;\n try {\n body = await response.text();\n } catch {\n throw new Error(REFRESH_REQUEST_FAILED);\n }\n if (response.status === 400 && parseOAuthErrorCode(body) === 'invalid_grant') {\n // The verdict is about the token presented, not whatever the store\n // holds now. Clear only if that same token is still stored, so a\n // stale rejection can never delete a replacement saved meanwhile.\n const current = credentialsManager.load(env, profileName);\n if (!current) {\n stderr(' The server rejected the stored refresh token; re-login required');\n return null;\n }\n // Two local values from the user's own credential file; no remote\n // party can observe this comparison's timing. FP (CWE-208).\n // bearer:disable javascript_lang_observable_timing\n if (current.refreshToken === presentedRefreshToken) {\n credentialsManager.clear(env, profileName);\n stderr(' The server rejected the stored refresh token; re-login required');\n return null;\n }\n throw new Error(REFRESH_SUPERSEDED);\n }\n if (parseOAuthErrorCode(body) === 'invalid_client') {\n // The OAuth client this profile was issued under no longer exists\n // (`lib/routes/oauth/oauth_token.py` answers 401 invalid_client).\n // Nothing in the profile can authenticate again, so surface the\n // existing re-login path — but it is a verdict on the client, not\n // on the stored tokens, so leave the profile for `gr login` to\n // replace rather than deleting it here.\n stderr(\" The server no longer recognises this profile's OAuth client; re-login required\");\n return null;\n }\n throw new Error(REFRESH_FAILED);\n }\n\n let tokens: ReturnType<typeof validateTokenResponse>;\n try {\n tokens = validateTokenResponse((await response.json()) as Record<string, unknown>);\n } catch {\n throw new Error(REFRESH_BAD_RESPONSE);\n }\n\n // Merge into a fresh read so profile metadata (pinned workspace, cached\n // org sessions) written while the request was in flight survives. Login\n // and logout take no refresh lock, so the profile may also be gone or be\n // a different grant by now; the successor belongs to the grant that was\n // presented and must not be written over either.\n const latest = credentialsManager.load(env, profileName);\n if (!latest) {\n // Logged out while the request was in flight. The successor dies with\n // the grant it belongs to; resurrecting the profile would undo the logout.\n stderr(' Cannot refresh: missing credentials (re-login required)');\n return null;\n }\n // Two local values from the user's own credential file; no remote party\n // can observe this comparison's timing. FP (CWE-208).\n // bearer:disable javascript_lang_observable_timing\n if (latest.clientId !== stored.clientId || latest.refreshToken !== presentedRefreshToken) {\n // A login replaced the profile. Its tokens are a different grant (the\n // server keys them by refresh_token AND client), so writing this\n // successor over them would make the new login's next refresh an\n // invalid_grant. Use the login if it can authenticate; otherwise this\n // refresh has nothing valid to offer.\n if (latest.accessToken && isFreshEnough(latest)) {\n return latest;\n }\n throw new Error(REFRESH_SUPERSEDED);\n }\n\n const updated: CLICredentials = {\n ...latest,\n accessToken: tokens.accessToken,\n // A server may omit `refresh_token` when it does not rotate; the\n // presented token then stays valid.\n refreshToken: tokens.refreshToken ?? presentedRefreshToken,\n expiresAt: new Date(Date.now() + tokens.expiresIn * 1000).toISOString(),\n };\n try {\n credentialsManager.save(env, updated, profileName);\n } catch {\n throw new Error(REFRESH_SAVE_FAILED);\n }\n return updated;\n}\n\n/**\n * Resolve credentials that do not require a browser.\n *\n * Precedence: process API key, then the selected profile's valid access token,\n * then that profile's refresh token. A null result leaves the caller to decide\n * whether interactive OAuth is appropriate for its command.\n */\nexport async function resolveNonInteractiveAuth(\n options: NonInteractiveAuthOptions\n): Promise<AuthResult | null> {\n const { env, envConfig, credentialsManager, profile } = options;\n\n // Process env only — API keys are never persisted in the credential store.\n const apiKey = process.env.GRAVITY_RAIL_API_KEY;\n if (apiKey) {\n return { accessToken: apiKey };\n }\n\n const profileName = credentialsManager.resolveProfileName(profile, env);\n const credentials = credentialsManager.load(env, profileName);\n if (!credentials?.accessToken) return null;\n\n const notExpired =\n !credentials.expiresAt ||\n new Date(credentials.expiresAt) > new Date(Date.now() + ACCESS_TOKEN_EXPIRY_BUFFER_MS);\n if (notExpired) {\n return { accessToken: credentials.accessToken, credentials };\n }\n\n const refreshed = await refreshAccessToken(\n envConfig,\n credentials,\n credentialsManager,\n env,\n profileName\n );\n if (!refreshed?.accessToken) return null;\n\n return { accessToken: refreshed.accessToken, credentials: refreshed };\n}\n","{\n \"name\": \"@gravity-rail/cli\",\n \"version\": \"0.55.0\",\n \"description\": \"Gravity Rail CLI — manage workspaces from the command line\",\n \"type\": \"module\",\n \"bin\": {\n \"gr\": \"./dist/gr.js\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/lib.d.ts\",\n \"import\": \"./dist/lib.js\"\n },\n \"./lib\": {\n \"types\": \"./dist/lib.d.ts\",\n \"import\": \"./dist/lib.js\"\n },\n \"./cli-utils\": {\n \"types\": \"./dist/cli-utils.d.ts\",\n \"import\": \"./dist/cli-utils.js\"\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://developer.gravityrail.com\"\n },\n \"homepage\": \"https://developer.gravityrail.com\",\n \"keywords\": [\n \"gravity-rail\",\n \"cli\",\n \"healthcare\",\n \"workspace\"\n ],\n \"engines\": {\n \"node\": \">=18\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"generate:skill-docs\": \"tsx scripts/generate-skill-docs.ts\",\n \"clean\": \"rm -rf dist\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"jest --forceExit\",\n \"test:ci\": \"jest --forceExit --passWithNoTests --coverage --coverageReporters=json-summary --coverageReporters=text-summary\"\n },\n \"dependencies\": {\n \"@gravity-rail/sdk\": \"workspace:^\",\n \"js-yaml\": \"^5.4.3\",\n \"proper-lockfile\": \"^4.1.2\"\n },\n \"devDependencies\": {\n \"@gravity-rail/sandbox\": \"workspace:*\",\n \"@types/jest\": \"^30.0.0\",\n \"@types/node\": \"^26.6.4\",\n \"@types/proper-lockfile\": \"^4.1.4\",\n \"jest\": \"^30.5.2\",\n \"ts-jest\": \"29.4.9\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"^6.0.3\",\n \"undici\": \"6.28.1\"\n }\n}\n","/**\n * CLI credential store with multi-account profile support.\n *\n * Store location: `~/.gr-cli/credentials-<env>.json` (mode 0600; dir mode 0700).\n * Format is versioned JSON with named profiles (aws-style). Flat single-credential\n * files from older CLI versions migrate to profile \"default\" on first read/write.\n *\n * Security notes (for operators / security review):\n * - Tokens and client secrets live only on the local filesystem under the user's home.\n * - Files are written mode 0600; the cache directory is created mode 0700.\n * - Profile and environment names are allowlist-validated with a\n * resolve()/relative() containment check so path traversal cannot escape `.gr-cli`.\n * - Callers must never log credential field values (accessToken, refreshToken, secrets).\n * - `GRAVITY_RAIL_API_KEY` is not persisted here; it remains process-env only and\n * overrides stored profiles for non-interactive automation.\n * - Dev profiles imported from `dev-realms.json` store bearer tokens only (no OAuth\n * client secret). They are local-dev conveniences and must never be committed.\n */\n\nimport {\n readFileSync,\n writeFileSync,\n existsSync,\n mkdirSync,\n chmodSync,\n unlinkSync,\n renameSync,\n} from 'fs';\nimport { join, resolve, relative, isAbsolute } from 'path';\n\nexport const DEFAULT_PROFILE = 'default';\n\n/**\n * Stored credentials for one named profile.\n *\n * OAuth logins have a clientId (+ optional refresh). `clientSecret` is present\n * only for a dynamically registered confidential client; profiles using the\n * pinned first-party public client have none, by design (GRA-7843).\n * Dev profiles imported from dev-realms.json are token-only (accessToken +\n * optional workspaceUuid) — no OAuth client registration.\n */\n/**\n * A cached org-scoped SSO session (`gr_sess_...`) obtained via browser step-up.\n * Replayed as the `X-Org-Session` header so subsequent invocations skip the\n * browser until the session expires (GRA-6995).\n */\nexport interface StoredOrgSession {\n session: string;\n /** ISO expiry; treated as a cache miss once passed. */\n expiresAt?: string;\n}\n\nexport interface CLICredentials {\n clientId?: string;\n clientSecret?: string;\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: string;\n /** Default workspace UUID (workspace/member profiles from dev-realms). */\n workspaceUuid?: string;\n /**\n * Cached org-scoped SSO sessions keyed by organization UUID. Populated by the\n * org-auth step-up handler; independent of the account token's own expiry.\n */\n orgSessions?: Record<string, StoredOrgSession>;\n}\n\nexport interface ProfileSummary {\n name: string;\n current: boolean;\n hasAccessToken: boolean;\n /** ISO timestamp if known; never includes the token itself. */\n expiresAt?: string;\n /** Present when the profile pins a default workspace. */\n workspaceUuid?: string;\n}\n\ninterface CredentialStoreV2 {\n version: 2;\n currentProfile: string;\n profiles: Record<string, CLICredentials>;\n}\n\n/**\n * Manages CLI credentials storage (one file per environment, multi-profile).\n */\nexport class CredentialsManager {\n private cacheDir: string;\n private apiUrl?: string;\n\n constructor(rootDir: string, apiUrl?: string) {\n // `rootDir` is supplied only by CLI bootstrap (context.ts passes\n // os.homedir()) — the local user's own home directory, never remote or\n // request input — joined with the constant '.gr-cli'. FP (CWE-22).\n // bearer:disable javascript_lang_path_traversal\n // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal\n this.cacheDir = join(rootDir, '.gr-cli');\n this.apiUrl = apiUrl;\n }\n\n /**\n * Validate a profile name. Allows the legacy realm path form\n * `workspace/member` so `acme-demo/acme-admin` works as a profile name.\n * Rejects path traversal and absolute paths.\n */\n validateProfileName(profile: string): void {\n if (!profile) {\n throw new Error('Invalid profile name: (empty).');\n }\n if (profile.includes('..') || profile.startsWith('/') || profile.endsWith('/')) {\n throw new Error(\n `Invalid profile name: ${profile}. Do not use \"..\" or leading/trailing \"/\".`\n );\n }\n // Letters, digits, '_', '-', '.', and a single-level '/' for workspace/member.\n if (!/^[a-zA-Z0-9_.-]+(?:\\/[a-zA-Z0-9_.-]+)?$/.test(profile)) {\n throw new Error(\n `Invalid profile name: ${profile}. Use letters, digits, '_', '-', '.', ` +\n `or workspace/member form (one slash).`\n );\n }\n }\n\n /**\n * Resolve which profile to use for this invocation.\n * Precedence: explicit flag → GRAVITY_RAIL_PROFILE → stored current → \"default\".\n */\n resolveProfileName(explicit?: string | null, env?: string): string {\n if (explicit) {\n this.validateProfileName(explicit);\n return explicit;\n }\n const fromEnv = process.env.GRAVITY_RAIL_PROFILE;\n if (fromEnv) {\n this.validateProfileName(fromEnv);\n return fromEnv;\n }\n if (env) {\n return this.getCurrentProfile(env);\n }\n return DEFAULT_PROFILE;\n }\n\n getCurrentProfile(env: string): string {\n const parsed = this.readStore(env);\n if (!parsed) return DEFAULT_PROFILE;\n return parsed.store.currentProfile || DEFAULT_PROFILE;\n }\n\n setCurrentProfile(env: string, profile: string): void {\n this.validateProfileName(profile);\n const store = this.readStore(env)?.store ?? this.emptyStore(profile);\n if (!store.profiles[profile]) {\n throw new Error(\n `Profile \"${profile}\" has no stored credentials for ${env}. ` +\n `Run: gr auth login --name ${profile} (or gr auth import-dev for local tokens)`\n );\n }\n store.currentProfile = profile;\n this.writeStore(env, store);\n }\n\n listProfiles(env: string): ProfileSummary[] {\n const parsed = this.readStore(env);\n if (!parsed) return [];\n const store = parsed.store;\n const current = store.currentProfile || DEFAULT_PROFILE;\n return Object.keys(store.profiles)\n .sort()\n .map((name) => {\n const creds = store.profiles[name]!;\n return {\n name,\n current: name === current,\n hasAccessToken: Boolean(creds.accessToken || creds.refreshToken),\n expiresAt: creds.expiresAt,\n workspaceUuid: creds.workspaceUuid,\n };\n });\n }\n\n private getCredentialsPath(env: string): string {\n // `env` comes from --env and the dev-cloud:NAMESPACE branch accepts an\n // arbitrary namespace, so it could contain `..` or a path separator and\n // escape the cache directory. Validate against an allowlist first (real\n // env names are alphanumerics, `-`, `_`, `.`, and the dev-cloud `:`\n // separator). This rejects path separators outright — including the\n // `foo/bar` case that would otherwise resolve inside .gr-cli but fail\n // later with a cryptic ENOENT from the un-created subdirectory.\n if (!/^[a-zA-Z0-9_:.-]+$/.test(env)) {\n throw new Error(`Invalid environment name: ${env}`);\n }\n // Defense-in-depth: also contain the resolved path before any fs\n // operation, so a future change to buildCredentialsPath can't reintroduce\n // an escape even if the allowlist is loosened.\n const candidate = this.buildCredentialsPath(env);\n // `this.cacheDir` is rooted at os.homedir() (see constructor) — a constant\n // resolve of the user's own dir, no attacker input. FP (CWE-22).\n // bearer:disable javascript_lang_path_traversal\n const resolvedDir = resolve(this.cacheDir);\n // This resolve()/relative() pair IS the containment sanitizer — `env` was\n // already allowlist-validated above. Semgrep's taint tracker flags the\n // resolve() of the env-derived candidate without modelling the guard.\n // bearer:disable javascript_lang_path_traversal\n // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal\n const rel = relative(resolvedDir, resolve(candidate));\n if (rel.startsWith('..') || isAbsolute(rel)) {\n throw new Error(`Invalid environment name: ${env}`);\n }\n return candidate;\n }\n\n private buildCredentialsPath(env: string): string {\n // Isolate credential files by non-default API port so worktrees (shifted\n // ports + separate DBs) do not clobber each other's imported tokens.\n if (this.apiUrl) {\n try {\n const url = new URL(this.apiUrl);\n const port = url.port;\n if (port && port !== '80' && port !== '443') {\n const isCanonicalLocalDev =\n (url.hostname === 'localhost' && port === '5050') ||\n (url.hostname.endsWith('gravityrail.test') && port === '5054');\n if (!isCanonicalLocalDev) {\n // `env` is allowlist-validated in getCredentialsPath before this\n // builder runs, cacheDir is homedir-rooted, and url.port is a\n // parsed URL port — no traversal component. FP (CWE-22).\n // bearer:disable javascript_lang_path_traversal\n // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal\n return join(this.cacheDir, `credentials-${env}-${port}.json`);\n }\n }\n } catch {\n // ignore invalid URLs\n }\n }\n // `env` is allowlist-validated in getCredentialsPath before this builder\n // runs and cacheDir is homedir-rooted — no traversal component. FP (CWE-22).\n // bearer:disable javascript_lang_path_traversal\n // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal\n return join(this.cacheDir, `credentials-${env}.json`);\n }\n\n private emptyStore(currentProfile = DEFAULT_PROFILE): CredentialStoreV2 {\n return { version: 2, currentProfile, profiles: {} };\n }\n\n private isCredentialShape(value: unknown): value is CLICredentials {\n if (!value || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n // Local JSON shape check only — typeof / field presence, not secret comparison.\n // FP (CWE-208): no secret values are compared; short-circuit cannot leak content.\n // A client id alone is a complete OAuth identity: the pinned first-party\n // client is public and has no secret (GRA-7843). Requiring `clientSecret`\n // here would make every such profile fail the shape check and be silently\n // dropped on read — losing the pinned id and the tokens stored beside it.\n // bearer:disable javascript_lang_observable_timing\n const hasOAuth = typeof v.clientId === 'string' && v.clientId.length > 0;\n // bearer:disable javascript_lang_observable_timing\n const hasToken = typeof v.accessToken === 'string' && v.accessToken.length > 0;\n return hasOAuth || hasToken;\n }\n\n /**\n * Read and normalize the on-disk store (migrating legacy formats in memory).\n * `wasLegacy` is true when the file was not already v2 so callers can rewrite once.\n */\n private readStore(env: string): { store: CredentialStoreV2; wasLegacy: boolean } | null {\n const credsPath = this.getCredentialsPath(env);\n if (!existsSync(credsPath)) {\n return null;\n }\n\n try {\n const raw = JSON.parse(readFileSync(credsPath, 'utf-8')) as Record<string, unknown>;\n\n // v2 multi-profile\n if (raw.version === 2 && raw.profiles && typeof raw.profiles === 'object') {\n const profiles: Record<string, CLICredentials> = {};\n for (const [name, value] of Object.entries(raw.profiles as Record<string, unknown>)) {\n if (this.isCredentialShape(value)) {\n profiles[name] = value;\n }\n }\n const current =\n typeof raw.currentProfile === 'string' && profiles[raw.currentProfile]\n ? raw.currentProfile\n : Object.keys(profiles)[0] || DEFAULT_PROFILE;\n return {\n store: { version: 2, currentProfile: current, profiles },\n wasLegacy: false,\n };\n }\n\n // Flat single-credential format (pre-multi-account)\n if (this.isCredentialShape(raw)) {\n return {\n store: {\n version: 2,\n currentProfile: DEFAULT_PROFILE,\n profiles: { [DEFAULT_PROFILE]: raw },\n },\n wasLegacy: true,\n };\n }\n\n // Old org-namespaced format { default: {...} }\n const legacyDefault = (raw as { default?: unknown }).default;\n if (this.isCredentialShape(legacyDefault)) {\n return {\n store: {\n version: 2,\n currentProfile: DEFAULT_PROFILE,\n profiles: { [DEFAULT_PROFILE]: legacyDefault },\n },\n wasLegacy: true,\n };\n }\n\n return null;\n } catch {\n return null;\n }\n }\n\n private ensureCacheDir(): void {\n if (!existsSync(this.cacheDir)) {\n mkdirSync(this.cacheDir, { recursive: true, mode: 0o700 });\n try {\n chmodSync(this.cacheDir, 0o700);\n } catch {\n // Ignore permission errors on Windows\n }\n }\n }\n\n private writeStore(env: string, store: CredentialStoreV2): void {\n this.ensureCacheDir();\n const credsPath = this.getCredentialsPath(env);\n // Atomic replace: write a sibling temp file, chmod, then rename into place.\n // POSIX rename is atomic, so a crash mid-write cannot leave a partial JSON\n // that would wipe every profile on the next parse failure.\n const tmpPath = `${credsPath}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(store, null, 2) + '\\n', { mode: 0o600 });\n try {\n chmodSync(tmpPath, 0o600);\n } catch {\n // Ignore permission errors on Windows\n }\n renameSync(tmpPath, credsPath);\n try {\n chmodSync(credsPath, 0o600);\n } catch {\n // Ignore permission errors on Windows\n }\n }\n\n private filterExpired(creds: CLICredentials): CLICredentials | null {\n if (creds.expiresAt) {\n const expiresAt = new Date(creds.expiresAt);\n if (expiresAt <= new Date()) {\n if (creds.refreshToken) {\n return creds;\n }\n return null;\n }\n }\n return creds;\n }\n\n /**\n * Load credentials for an environment + profile.\n * When `profile` is omitted, uses the resolved current profile for `env`.\n */\n load(env: string, profile?: string): CLICredentials | null {\n const parsed = this.readStore(env);\n if (!parsed) return null;\n\n const { store, wasLegacy } = parsed;\n const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;\n if (profile) this.validateProfileName(profile);\n const creds = store.profiles[name];\n if (!creds) return null;\n\n // Persist migration from flat/org formats so subsequent multi-profile ops work.\n // wasLegacy comes from the single parse in readStore — no second file read.\n if (wasLegacy) {\n this.writeStore(env, store);\n }\n\n return this.filterExpired(creds);\n }\n\n /**\n * Save credentials for an environment + profile.\n * When `profile` is omitted, uses the current profile (or \"default\").\n * Sets currentProfile to the saved profile when the store was empty.\n */\n save(env: string, creds: CLICredentials, profile?: string): void {\n if (profile) this.validateProfileName(profile);\n const store = this.readStore(env)?.store ?? this.emptyStore();\n const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;\n if (profile) {\n // Explicit profile on save also becomes current when first created, or when\n // the caller is logging into a named account.\n store.profiles[name] = creds;\n if (!store.profiles[store.currentProfile] || Object.keys(store.profiles).length === 1) {\n store.currentProfile = name;\n }\n } else {\n store.profiles[name] = creds;\n if (!store.currentProfile) {\n store.currentProfile = name;\n }\n }\n this.writeStore(env, store);\n }\n\n /**\n * Bulk-save many profiles in one write (used by dev-realms import).\n * Does not change currentProfile unless the store was empty — then uses\n * `preferCurrent` if provided and present, else `superadmin`, else first.\n */\n saveMany(env: string, profiles: Record<string, CLICredentials>, preferCurrent?: string): number {\n const names = Object.keys(profiles);\n for (const name of names) {\n this.validateProfileName(name);\n }\n const store = this.readStore(env)?.store ?? this.emptyStore();\n const wasEmpty = Object.keys(store.profiles).length === 0;\n for (const [name, creds] of Object.entries(profiles)) {\n store.profiles[name] = creds;\n }\n if (wasEmpty) {\n if (preferCurrent && store.profiles[preferCurrent]) {\n store.currentProfile = preferCurrent;\n } else if (store.profiles.superadmin) {\n store.currentProfile = 'superadmin';\n } else {\n store.currentProfile = names.sort()[0] ?? DEFAULT_PROFILE;\n }\n }\n this.writeStore(env, store);\n return names.length;\n }\n\n /**\n * Clear one profile (default: current profile). Deletes the file when no profiles remain.\n * Pass a profile name to clear a specific account without touching others.\n */\n clear(env: string, profile?: string): void {\n const parsed = this.readStore(env);\n if (!parsed) return;\n const store = parsed.store;\n\n const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;\n if (profile) this.validateProfileName(profile);\n delete store.profiles[name];\n\n if (Object.keys(store.profiles).length === 0) {\n const credsPath = this.getCredentialsPath(env);\n if (existsSync(credsPath)) {\n try {\n unlinkSync(credsPath);\n } catch {\n // Ignore\n }\n }\n return;\n }\n\n if (store.currentProfile === name) {\n store.currentProfile = Object.keys(store.profiles).sort()[0]!;\n }\n this.writeStore(env, store);\n }\n\n /**\n * Read a cached org-scoped SSO session for an org, if present and unexpired.\n * Independent of the account token's expiry — a valid org session is reusable\n * even while the account token is being refreshed.\n */\n getOrgSession(env: string, profile: string | undefined, orgUuid: string): string | undefined {\n const parsed = this.readStore(env);\n if (!parsed) return undefined;\n const name = profile ?? parsed.store.currentProfile ?? DEFAULT_PROFILE;\n if (profile) this.validateProfileName(profile);\n const entry = parsed.store.profiles[name]?.orgSessions?.[orgUuid];\n if (!entry?.session) return undefined;\n if (entry.expiresAt && new Date(entry.expiresAt) <= new Date()) return undefined;\n return entry.session;\n }\n\n /**\n * Cache an org-scoped SSO session under an existing profile, keyed by org UUID.\n * No-op when the profile does not exist — org sessions only attach to a\n * logged-in account profile (never create a token-less profile row).\n */\n setOrgSession(\n env: string,\n profile: string | undefined,\n orgUuid: string,\n session: string,\n expiresAt?: string\n ): void {\n const parsed = this.readStore(env);\n if (!parsed) return;\n const store = parsed.store;\n const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;\n if (profile) this.validateProfileName(profile);\n const creds = store.profiles[name];\n if (!creds) return;\n creds.orgSessions = {\n ...(creds.orgSessions ?? {}),\n [orgUuid]: { session, ...(expiresAt ? { expiresAt } : {}) },\n };\n this.writeStore(env, store);\n }\n\n getCacheDir(): string {\n return this.cacheDir;\n }\n\n /** Absolute path of the credentials file for `env` (for docs / `auth list`). */\n getCredentialsFilePath(env: string): string {\n return this.getCredentialsPath(env);\n }\n}\n","/**\n * CLI terminal UI utilities\n *\n * Provides interactive terminal UI components for CLI tools\n */\n\nimport * as readline from 'readline';\n\nexport interface SelectOption<T> {\n item: T;\n label: string;\n detail?: string;\n}\n\nexport interface SelectFromListOptions<T> {\n title?: string;\n labelFn?: (item: T) => string;\n detailFn?: (item: T) => string | null;\n env?: string;\n emptyMessage?: string;\n allowNew?: boolean;\n newLabel?: string;\n}\n\nexport interface SelectResult<T> {\n item: T | null;\n isNew: boolean;\n}\n\nconst NEW_ITEM_MARKER = Symbol('NEW_ITEM');\n\n/**\n * Interactive list picker using arrow keys\n */\nexport async function selectFromList<T>(\n items: T[],\n options: SelectFromListOptions<T> = {}\n): Promise<SelectResult<T>> {\n const {\n title = 'Select an item',\n labelFn = (item: T) => String((item as any).name || (item as any).title || item),\n detailFn = null,\n env = null,\n emptyMessage = 'No items found.',\n allowNew = false,\n newLabel = 'Create new...',\n } = options;\n\n if ((!items || items.length === 0) && !allowNew) {\n console.log(`\\n ${emptyMessage}`);\n return { item: null, isNew: false };\n }\n\n // Prepend \"Create new\" option if allowed\n const displayItems: (T | typeof NEW_ITEM_MARKER)[] = allowNew\n ? [NEW_ITEM_MARKER, ...items]\n : items;\n\n return new Promise((resolve) => {\n let selectedIndex = allowNew ? 1 : 0;\n let filterText = '';\n\n const getFilteredItems = (): (T | typeof NEW_ITEM_MARKER)[] => {\n if (!filterText) return displayItems;\n const lower = filterText.toLowerCase();\n return displayItems.filter((item) => {\n if (item === NEW_ITEM_MARKER) return true;\n const label = labelFn(item);\n return label.toLowerCase().includes(lower);\n });\n };\n\n const render = (): void => {\n const filtered = getFilteredItems();\n\n // Clear screen and move cursor to top\n process.stdout.write('\\x1B[2J\\x1B[H');\n\n console.log('='.repeat(60));\n console.log(`${title} (↑/↓ to move, Enter to select, Esc to cancel)`);\n console.log('='.repeat(60));\n if (env) {\n console.log(`Environment: ${env}`);\n }\n console.log(`Filter: ${filterText}${filterText ? '' : '(type to filter)'}`);\n console.log('');\n\n if (filtered.length === 0) {\n console.log(` ${emptyMessage}`);\n console.log('');\n return;\n }\n\n // Show up to 15 items around the selected one\n const maxVisible = 15;\n let startIdx = Math.max(0, selectedIndex - Math.floor(maxVisible / 2));\n let endIdx = Math.min(filtered.length, startIdx + maxVisible);\n if (endIdx - startIdx < maxVisible && startIdx > 0) {\n startIdx = Math.max(0, endIdx - maxVisible);\n }\n\n if (startIdx > 0) {\n console.log(` ... ${startIdx} more above ...`);\n }\n\n for (let i = startIdx; i < endIdx; i++) {\n const item = filtered[i]!;\n const prefix = i === selectedIndex ? '> ' : ' ';\n\n let line: string;\n if (item === NEW_ITEM_MARKER) {\n line = `\\x1B[32m${newLabel}\\x1B[0m`; // Green color for \"Create new\"\n } else {\n const typedItem = item as T;\n line = labelFn(typedItem);\n if (detailFn) {\n const detail = detailFn(typedItem);\n if (detail) {\n line = `${line} \\x1B[90m${detail}\\x1B[0m`;\n }\n }\n }\n\n if (i === selectedIndex) {\n console.log(`\\x1B[7m${prefix}${line}\\x1B[0m`);\n } else {\n console.log(`${prefix}${line}`);\n }\n }\n\n if (endIdx < filtered.length) {\n console.log(` ... ${filtered.length - endIdx} more below ...`);\n }\n\n console.log('');\n console.log(` ${filtered.length} item(s)`);\n };\n\n // Set up raw mode for arrow key input\n readline.emitKeypressEvents(process.stdin);\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(true);\n }\n\n render();\n\n const handleKeypress = (str: string | undefined, key: readline.Key): void => {\n const filtered = getFilteredItems();\n\n if (key.name === 'up') {\n selectedIndex = Math.max(0, selectedIndex - 1);\n render();\n } else if (key.name === 'down') {\n selectedIndex = Math.min(filtered.length - 1, selectedIndex + 1);\n render();\n } else if (key.name === 'return') {\n cleanup();\n if (filtered.length > 0 && selectedIndex < filtered.length) {\n const selected = filtered[selectedIndex]!;\n if (selected === NEW_ITEM_MARKER) {\n resolve({ item: null, isNew: true });\n } else {\n resolve({ item: selected as T, isNew: false });\n }\n } else {\n resolve({ item: null, isNew: false });\n }\n } else if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {\n cleanup();\n resolve({ item: null, isNew: false });\n } else if (key.name === 'backspace') {\n filterText = filterText.slice(0, -1);\n selectedIndex = allowNew ? 0 : 0;\n render();\n } else if (str && str.length === 1 && !key.ctrl && !key.meta) {\n filterText += str;\n selectedIndex = allowNew ? 0 : 0;\n render();\n }\n };\n\n const cleanup = (): void => {\n process.stdin.removeListener('keypress', handleKeypress);\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n process.stdout.write('\\x1B[2J\\x1B[H');\n };\n\n process.stdin.on('keypress', handleKeypress);\n process.stdin.resume();\n });\n}\n\n/**\n * Print a table of items\n */\nexport function printTable<T>(\n items: T[],\n columns: Array<{\n header: string;\n getValue: (item: T) => string;\n maxWidth?: number;\n }>,\n options: { title?: string; env?: string } = {}\n): void {\n if (!items || items.length === 0) {\n console.log('\\nNo items found.');\n return;\n }\n\n if (options.title) {\n console.log(`\\n=== ${options.title} ===`);\n }\n if (options.env) {\n console.log(`Environment: ${options.env}`);\n }\n console.log('');\n\n // Calculate column widths\n const widths = columns.map((col) => {\n const maxContent = Math.max(...items.map((item) => String(col.getValue(item) ?? '').length));\n const width = Math.max(col.header.length, maxContent);\n return col.maxWidth ? Math.min(width, col.maxWidth) : width;\n });\n\n // Print header\n const headerLine = columns.map((col, i) => col.header.padEnd(widths[i]!)).join(' ');\n console.log(headerLine);\n console.log(widths.map((w) => '-'.repeat(w)).join(' '));\n\n // Print rows\n for (const item of items) {\n const row = columns\n .map((col, i) => {\n const value = String(col.getValue(item) ?? '');\n const width = widths[i]!;\n return value.slice(0, width).padEnd(width);\n })\n .join(' ');\n console.log(row);\n }\n\n console.log('');\n console.log(`Total: ${items.length} item(s)`);\n}\n\n/**\n * Format a timestamp for display\n */\nexport function formatTimestamp(isoString: string | null | undefined): string {\n if (!isoString) return '';\n const date = new Date(isoString);\n const now = new Date();\n const diff = now.getTime() - date.getTime();\n\n if (diff < 60000) return 'just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n if (diff < 604800000) return `${Math.floor(diff / 86400000)}d ago`;\n\n return date.toLocaleDateString();\n}\n\n/**\n * Wrap text to a maximum width\n */\nexport function wrapText(text: string, width: number = 70): string[] {\n const words = text.split(' ');\n const lines: string[] = [];\n let currentLine = '';\n\n for (const word of words) {\n if (currentLine.length + word.length + 1 <= width) {\n currentLine += (currentLine ? ' ' : '') + word;\n } else {\n if (currentLine) lines.push(currentLine);\n currentLine = word;\n }\n }\n if (currentLine) lines.push(currentLine);\n return lines;\n}\n\n/**\n * Print a chat message with formatting\n */\nexport function printMessage(\n content: string,\n role: 'user' | 'assistant' | 'system',\n indent: string = ' '\n): void {\n const roleColors: Record<string, string> = {\n user: '\\x1B[34m', // Blue\n assistant: '\\x1B[32m', // Green\n system: '\\x1B[90m', // Gray\n };\n const resetColor = '\\x1B[0m';\n const color = roleColors[role] || resetColor;\n\n const label = role === 'user' ? 'You' : role === 'assistant' ? 'Assistant' : 'System';\n console.log(`${indent}${color}[${label}]${resetColor}`);\n\n const lines = content.split('\\n');\n for (const line of lines) {\n const wrapped = wrapText(line, 70);\n for (const wrappedLine of wrapped) {\n console.log(`${indent} ${wrappedLine}`);\n }\n }\n console.log('');\n}\n\n/**\n * Clear the terminal screen\n */\nexport function clearScreen(): void {\n process.stdout.write('\\x1B[2J\\x1B[H');\n}\n","/**\n * AG-UI Client for CLI chat interactions\n *\n * Implements the AG-UI (Agent-User Interaction) protocol for streaming\n * chat responses. See: https://docs.ag-ui.com/concepts/events\n */\n\nimport type { ConciergeAppContextPayload } from '../lib/cli-client-context';\n\n/**\n * AG-UI event types\n */\nexport type AGUIEventType =\n | 'RUN_STARTED'\n | 'RUN_FINISHED'\n | 'RUN_ERROR'\n | 'TEXT_MESSAGE_START'\n | 'TEXT_MESSAGE_CONTENT'\n | 'TEXT_MESSAGE_END'\n | 'TOOL_CALL_START'\n | 'TOOL_CALL_ARGS'\n | 'TOOL_CALL_END'\n | 'TOOL_CALL_RESULT'\n | 'STATE_SNAPSHOT'\n | 'STATE_DELTA'\n // Lowercase variants (some backends use these)\n | 'run_started'\n | 'run_finished'\n | 'run_error'\n | 'text_message_start'\n | 'text_message_content'\n | 'text_message_end'\n | 'tool_call_start'\n | 'tool_call_args'\n | 'tool_call_end'\n | 'tool_call_result'\n | 'state_snapshot'\n | 'state_delta'\n | 'done';\n\nexport interface AGUIEvent {\n type: AGUIEventType;\n messageId?: string;\n role?: string;\n delta?: string;\n content?: string;\n message?: string;\n code?: string;\n toolCallId?: string;\n toolCallName?: string;\n result?: any;\n snapshot?: any;\n operations?: any[];\n [key: string]: any;\n}\n\nexport interface ChatMessage {\n role: 'user' | 'assistant' | 'system';\n content: string;\n}\n\nexport interface AGUIClientOptions {\n baseUrl: string;\n workspaceUuid?: string;\n chatId?: number | string; // Numeric chat id (documented) or chat uuid (accepted)\n /** Full URL override — when provided, baseUrl/workspaceUuid/chatId are ignored for POST */\n endpointUrl?: string;\n accessToken: string;\n debug?: boolean;\n /** Model field sent in request body (default: 'manager'). Set to undefined to omit. */\n model?: string;\n /** Concierge / client context forwarded to the API (e.g. CLI surface hints). */\n appContext?: ConciergeAppContextPayload;\n}\n\n/**\n * AG-UI Client for streaming chat responses\n */\nexport class AGUIClient {\n private endpointUrl: string;\n private baseUrl: string;\n private workspaceUuid: string;\n private chatId: number | string;\n private accessToken: string;\n private debug: boolean;\n private model: string | undefined;\n private appContext: ConciergeAppContextPayload | undefined;\n\n constructor(options: AGUIClientOptions) {\n this.baseUrl = options.baseUrl;\n this.workspaceUuid = options.workspaceUuid ?? '';\n this.chatId = options.chatId ?? 0;\n if (!options.endpointUrl && (!options.workspaceUuid || options.chatId === undefined)) {\n throw new Error('AGUIClient requires workspaceUuid and chatId, or endpointUrl');\n }\n this.endpointUrl =\n options.endpointUrl ??\n `${options.baseUrl}/api/v2/w/${options.workspaceUuid}/web-chat/${options.chatId}/chat/agui`;\n this.accessToken = options.accessToken;\n this.debug = options.debug ?? false;\n this.model = 'model' in options ? options.model : 'manager';\n this.appContext = options.appContext;\n }\n\n private chatRouteUrl(suffix: string): string | null {\n // Numeric chatId: treat 0 / negative as \"no chat\" (<= 0 rather than a\n // falsy check so a deliberate sentinel of 0 doesn't mean \"valid chat\").\n // String chatId is a uuid; empty string means \"no chat\".\n const noChat = typeof this.chatId === 'number' ? this.chatId <= 0 : this.chatId.length === 0;\n if (!this.workspaceUuid || noChat) return null;\n return `${this.baseUrl}/api/v2/w/${this.workspaceUuid}/chats/${this.chatId}/${suffix}`;\n }\n\n /**\n * The operator's own live inline-connect link for this chat, or null when\n * none is live. The `inline_connect` prompt carries no URL on purpose (it is\n * a bearer, and the tool result is persisted and model-visible), so the\n * terminal fetches it here when the operator chooses to connect.\n */\n async fetchInlineConnectLink(\n appSlug: string\n ): Promise<{ connectUrl: string; expiresInMinutes: number } | null> {\n const url = this.chatRouteUrl(`inline-connect/${encodeURIComponent(appSlug)}/link`);\n if (!url) return null;\n try {\n const res = await fetch(url, { headers: { Authorization: `Bearer ${this.accessToken}` } });\n if (!res.ok) return null;\n const body = (await res.json()) as { connectUrl?: unknown; expiresInMinutes?: unknown };\n if (typeof body.connectUrl !== 'string') return null;\n return {\n connectUrl: body.connectUrl,\n expiresInMinutes: typeof body.expiresInMinutes === 'number' ? body.expiresInMinutes : 0,\n };\n } catch {\n return null;\n }\n }\n\n private inferenceStatusUrl(): string | null {\n // Numeric chatId: treat 0 / negative as \"no chat\" (<= 0 rather than a\n // falsy check so a deliberate sentinel of 0 doesn't mean \"valid chat\").\n // String chatId is a uuid; empty string means \"no chat\".\n const noChat = typeof this.chatId === 'number' ? this.chatId <= 0 : this.chatId.length === 0;\n if (!this.workspaceUuid || noChat) return null;\n return `${this.baseUrl}/api/v2/w/${this.workspaceUuid}/chats/${this.chatId}/inference/status`;\n }\n\n private async waitForInferenceInactive(maxWaitMs = 60_000): Promise<boolean> {\n const url = this.inferenceStatusUrl();\n if (!url) return false;\n const deadline = Date.now() + maxWaitMs;\n while (Date.now() < deadline) {\n try {\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${this.accessToken}` },\n });\n if (res.ok) {\n const body = (await res.json()) as { active?: boolean };\n if (!body.active) return true;\n }\n } catch (err) {\n // Transient network errors (DNS blips, connection resets) shouldn't\n // crash the 409-retry loop. Log under --debug and keep polling\n // until the deadline; the caller will fall back to the original\n // 409 error if we can't confirm inactive in time.\n if (this.debug) {\n console.log(\n `[DEBUG] waitForInferenceInactive fetch failed: ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n }\n }\n await new Promise((r) => setTimeout(r, 2000));\n }\n return false;\n }\n\n /**\n * Send messages and stream the response\n */\n async sendMessage(\n messages: ChatMessage[],\n onEvent: (event: AGUIEvent) => void,\n options?: AguiStreamOptions\n ): Promise<void> {\n return this.streamRequest({ messages }, onEvent, options);\n }\n\n /**\n * Stream an AG-UI request (initial messages or HITL resume).\n */\n async streamRequest(\n request: AguiRequestBody,\n onEvent: (event: AGUIEvent) => void,\n options?: AguiStreamOptions\n ): Promise<void> {\n const body = JSON.stringify({\n ...(this.model ? { model: this.model } : {}),\n messages: request.messages ?? [],\n ...(this.appContext ? { appContext: this.appContext } : {}),\n ...(request.resumeValue !== undefined ? { resumeValue: request.resumeValue } : {}),\n ...(request.resumeInterruptId ? { resumeInterruptId: request.resumeInterruptId } : {}),\n ...(request.resume !== undefined ? { resume: request.resume } : {}),\n ...(options?.autoApprove ? { context: { autoApprove: true } } : {}),\n });\n\n if (this.debug) {\n console.log(`\\n[DEBUG] POST ${this.endpointUrl}`);\n console.log(\n `[DEBUG] Body: ${request.resumeValue !== undefined ? 'resume' : `${(request.messages ?? []).length} message(s)`}`\n );\n }\n\n await this.postStream(body, onEvent, options);\n }\n\n private async postStream(\n body: string,\n onEvent: (event: AGUIEvent) => void,\n options?: AguiStreamOptions,\n conflictRetries = 0\n ): Promise<void> {\n const response = await fetch(this.endpointUrl, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n },\n body,\n });\n\n if (this.debug) {\n console.log(`[DEBUG] Response status: ${response.status}`);\n }\n\n if (response.status === 409 && conflictRetries < 3) {\n const ready = await this.waitForInferenceInactive();\n if (ready) {\n await this.postStream(body, onEvent, options, conflictRetries + 1);\n return;\n }\n }\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => '');\n if (this.debug) {\n console.log(`[DEBUG] Error response: ${errorText}`);\n }\n throw new Error(\n `Chat request failed: ${response.status}${errorText ? `: ${errorText.slice(0, 200)}` : ''}`\n );\n }\n\n await parseSseStream(response.body!, onEvent);\n }\n\n /**\n * Subscribe to the chat's persistent AG-UI event feed and surface\n * server-initiated turns that arrive while we're idle between our own turns.\n *\n * The feed at ``/chats/{chatId}/events`` replays every AG-UI event for the\n * chat across all turns (assistant events plus user-message echoes). It never\n * self-terminates — it ends only on disconnect. On reconnect we send\n * ``Last-Event-ID`` so the server replays the gap. The loop reconnects with\n * capped exponential backoff and resolves quietly once ``signal`` aborts;\n * it never throws except when required args are missing (callers gate on\n * availability, so that path is a programming error).\n */\n async subscribeToFeed(opts: {\n onEvent: (event: AGUIEvent) => void;\n signal: AbortSignal;\n onDebug?: (msg: string) => void;\n }): Promise<void> {\n const { onEvent, signal, onDebug } = opts;\n\n // Guard even though callers gate on availability: the feed URL needs a\n // real workspace + chat. Mirror inferenceStatusUrl's \"no chat\" logic.\n const noChat = typeof this.chatId === 'number' ? this.chatId <= 0 : this.chatId.length === 0;\n if (!this.baseUrl || !this.workspaceUuid || noChat) {\n throw new Error('subscribeToFeed requires baseUrl, workspaceUuid, and a chatId');\n }\n const feedUrl = `${this.baseUrl}/api/v2/w/${this.workspaceUuid}/chats/${this.chatId}/events`;\n\n let lastEventId: string | undefined;\n let backoffMs = 1000;\n const maxBackoffMs = 15_000;\n // A connection that survived this long was healthy even if the chat\n // stayed silent the whole time — keepalive comment frames are\n // stripped by the parser, so they can never count as received data.\n const healthyConnectionMs = 30_000;\n\n while (!signal.aborted) {\n let receivedData = false;\n const connectedAt = Date.now();\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.accessToken}`,\n Accept: 'text/event-stream',\n };\n if (lastEventId !== undefined) headers['Last-Event-ID'] = lastEventId;\n\n const response = await fetch(feedUrl, { method: 'GET', headers, signal });\n if (!response.ok || !response.body) {\n throw new Error(`feed request failed: ${response.status}`);\n }\n onDebug?.(`feed connected${lastEventId ? ` (resume ${lastEventId})` : ''}`);\n\n await parseSseStream(\n response.body,\n (event, eventName) => {\n receivedData = true;\n // Retention lapsed — the server couldn't replay the gap.\n // Detected via the SSE ``event: feed_reset`` name (the\n // payload type is a belt-and-suspenders duplicate). For\n // v1 we just note it; some events may have been missed.\n if (\n eventName === 'feed_reset' ||\n (typeof event.type === 'string' && event.type.toUpperCase() === 'FEED_RESET')\n ) {\n onDebug?.('feed reset — server could not replay missed events');\n return;\n }\n onEvent(event);\n },\n (id) => {\n receivedData = true;\n lastEventId = id;\n }\n );\n // Stream ended (disconnect) — fall through to reconnect with backoff.\n } catch (err) {\n if (signal.aborted) return;\n onDebug?.(`feed error: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n if (signal.aborted) return;\n\n // Reset only on evidence of health (delivered frames, or a\n // long-lived idle connection); otherwise wait out the current\n // backoff and grow it for the NEXT attempt (1s, 2s, 4s … cap).\n // Healthy connections must never escalate — an earlier version\n // doubled unconditionally after the reset, so steady\n // reconnects crept toward the cap.\n const wasHealthy = receivedData || Date.now() - connectedAt >= healthyConnectionMs;\n if (wasHealthy) backoffMs = 1000;\n const completed = await abortableDelay(backoffMs, signal);\n if (!completed) return; // aborted while waiting\n if (!wasHealthy) backoffMs = Math.min(backoffMs * 2, maxBackoffMs);\n }\n }\n}\n\n/**\n * Parse a Server-Sent Events stream body into AG-UI events.\n *\n * Shared by ``AGUIClient.postStream`` (per-turn POST) and ``subscribeToFeed``\n * (persistent GET feed). Surfaces ``data:`` frames as parsed events, ``id:``\n * frames via ``onId`` (feed reconnect uses the last id as ``Last-Event-ID``),\n * and ignores SSE comment frames (``: turn-done``, keepalives). ``onId`` is\n * optional — the POST path has no use for entry ids.\n */\nexport async function parseSseStream(\n body: ReadableStream<Uint8Array>,\n onEvent: (event: AGUIEvent, eventName?: string) => void,\n onId?: (id: string) => void\n): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n // Per SSE, an ``event:`` line names the dispatch it precedes (the\n // server's feed_reset signal uses this). Cleared once consumed or at\n // the blank-line frame boundary.\n let pendingEventName: string | undefined;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const rawLine of lines) {\n // Tolerate CRLF endings without changing the \\n-split behavior.\n const line = rawLine.endsWith('\\r') ? rawLine.slice(0, -1) : rawLine;\n\n // Blank line = SSE frame boundary; the pending event name only\n // applies within its own frame.\n if (line === '') {\n pendingEventName = undefined;\n continue;\n }\n\n // SSE comment frame (turn boundaries, keepalives) — ignore.\n if (line.startsWith(':')) continue;\n\n if (line.startsWith('event:')) {\n const nameValue = line.slice(6);\n pendingEventName = nameValue.startsWith(' ') ? nameValue.slice(1) : nameValue;\n continue;\n }\n\n if (line.startsWith('id:')) {\n const idValue = line.slice(3);\n // Per SSE, a single leading space after the colon is stripped.\n onId?.(idValue.startsWith(' ') ? idValue.slice(1) : idValue);\n continue;\n }\n\n if (line.startsWith('data: ')) {\n const data = line.slice(6);\n if (data === '[DONE]') {\n onEvent({ type: 'done' }, pendingEventName);\n pendingEventName = undefined;\n continue;\n }\n try {\n onEvent(JSON.parse(data) as AGUIEvent, pendingEventName);\n } catch {\n // Ignore parse errors for malformed events.\n }\n pendingEventName = undefined;\n }\n }\n }\n}\n\n/**\n * Resolve after ``ms`` unless ``signal`` aborts first. Returns true if the delay\n * completed, false if it was aborted — lets a reconnect loop exit cleanly.\n */\nfunction abortableDelay(ms: number, signal: AbortSignal): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n if (signal.aborted) {\n resolve(false);\n return;\n }\n const onAbort = (): void => {\n clearTimeout(timer);\n resolve(false);\n };\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve(true);\n }, ms);\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\nexport interface AguiStreamOptions {\n autoApprove?: boolean;\n}\n\nexport interface AguiRequestBody {\n messages?: ChatMessage[];\n /** Concierge AGUI resume fields. */\n resumeValue?: string;\n resumeInterruptId?: string;\n /** Workspace-chat HITL resume (ChatCompletionRequest.resume shape:\n * { decisions: [{type: 'approve'|'edit'|'reject', ...}], interruptId }). */\n resume?: unknown;\n}\n\n/**\n * Helper to check if an event is a text content event\n */\nexport function isTextContentEvent(event: AGUIEvent): boolean {\n const type = event.type.toLowerCase();\n return type === 'text_message_content';\n}\n\n/**\n * Helper to check if an event is an error event\n */\nexport function isErrorEvent(event: AGUIEvent): boolean {\n const type = event.type.toLowerCase();\n return type === 'run_error';\n}\n\n/**\n * Helper to extract text delta from an event\n */\nexport function getTextDelta(event: AGUIEvent): string | null {\n if (isTextContentEvent(event) && event.delta) {\n return event.delta;\n }\n return null;\n}\n\n/**\n * Helper to extract error message from an event\n */\nexport function getErrorMessage(event: AGUIEvent): string | null {\n if (isErrorEvent(event)) {\n return event.message || event.code || 'Unknown error';\n }\n return null;\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","dirname","readFileSync","existsSync","join","resolve","resolve","dirname","existsSync","mkdirSync","resolve","resolve"]}
1
+ {"version":3,"sources":["../src/cli-utils/config.ts","../src/cli-utils/auth.ts","../package.json","../src/cli-utils/credentials-manager.ts","../src/cli-utils/ui.ts","../src/cli-utils/agui.ts"],"sourcesContent":["/**\n * CLI configuration for remote environments\n */\n\nimport { existsSync, readFileSync, statSync } from 'fs';\nimport { CORE_SCHEMA, defineSequenceTag, load } from 'js-yaml';\nimport { dirname, join, resolve } from 'path';\n\nexport interface EnvironmentConfig {\n apiUrl: string;\n frontendUrl: string;\n}\n\n/** Optional per-field URL overrides and the checkout to classify. */\nexport interface EnvironmentConfigOptions {\n apiUrl?: string;\n frontendUrl?: string;\n /** Directory to start the worktree walk from. Defaults to `process.cwd()`. */\n startDir?: string;\n}\n\nexport const ENVIRONMENTS: Record<string, EnvironmentConfig> = {\n dev: {\n apiUrl: 'https://api.gravityrail.test:5054',\n frontendUrl: 'https://app.gravityrail.test:3000',\n },\n prod: {\n apiUrl: 'https://api.gravityrail.com',\n frontendUrl: 'https://app.gravityrail.com',\n },\n staging: {\n apiUrl: 'https://api.gr-staging.com',\n frontendUrl: 'https://app.gr-staging.com',\n },\n};\n\nexport const CALLBACK_PORT = 31337;\nexport const CALLBACK_PATH = '/oauth/callback';\nexport const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}${CALLBACK_PATH}`;\n\nconst API_CONTAINER_PORT = 443;\nconst FRONTEND_CONTAINER_PORT = 3000;\nconst WORKTREE_SETUP_MARKER = 'worktree-setup.sh';\nconst OVERRIDE_FILENAME = 'docker-compose.override.yml';\n\n/**\n * Docker Compose `ports: !override` is an identity sequence tag. Register it\n * so generated worktree overrides load; do not implement merge/replace.\n */\nconst OVERRIDE_SEQUENCE_TAG = defineSequenceTag<unknown[]>('!override', {\n create: () => [],\n addItem: (carrier, item) => {\n // Must not return a value — js-yaml treats a truthy addItem return as a parse error.\n carrier.push(item);\n },\n identify: () => false,\n});\n\nconst COMPOSE_OVERRIDE_SCHEMA = CORE_SCHEMA.withTags(OVERRIDE_SEQUENCE_TAG);\n\nexport type CheckoutClassification =\n | { kind: 'non-worktree'; root?: string }\n | {\n kind: 'resolved-worktree';\n root: string;\n overridePath: string;\n apiPort: number;\n frontendPort: number;\n }\n | {\n kind: 'unresolved-worktree';\n root: string;\n overridePath: string;\n reason: string;\n };\n\nexport class WorktreeConfigError extends Error {\n constructor(\n message: string,\n readonly checkoutRoot: string,\n readonly overridePath: string\n ) {\n super(message);\n this.name = 'WorktreeConfigError';\n }\n}\n\n/** A plain JSON object: not null, not an array. */\nexport function isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction parsePortToken(token: string | undefined): number | undefined {\n if (token === undefined || !/^\\d+$/.test(token)) return undefined;\n const value = Number(token);\n if (!Number.isInteger(value) || value <= 0 || value > 65535) return undefined;\n return value;\n}\n\n/**\n * Parse `published` and `target` from the right edge of a short-form mapping.\n * The optional bind-address prefix (including `${DOCKER_BIND_IP:-...}`) stays\n * opaque — it is never interpolated.\n */\nexport function parsePublishedTarget(\n mapping: string\n): { published: number; target: number } | undefined {\n const parts = mapping.split(':');\n if (parts.length < 2) return undefined;\n const target = parsePortToken(parts[parts.length - 1]);\n const published = parsePortToken(parts[parts.length - 2]);\n if (target === undefined || published === undefined) return undefined;\n return { published, target };\n}\n\nfunction publishedPortForTarget(ports: unknown, target: number): number | undefined {\n if (!Array.isArray(ports)) return undefined;\n for (const entry of ports) {\n if (typeof entry !== 'string') continue;\n const parsed = parsePublishedTarget(entry);\n if (parsed?.target === target) return parsed.published;\n }\n return undefined;\n}\n\nfunction servicePorts(services: Record<string, unknown>, name: string): unknown {\n const service = services[name];\n if (!isRecord(service)) return undefined;\n return service['ports'];\n}\n\nfunction findNearestGit(startDir: string): { root: string; gitIsFile: boolean } | undefined {\n let dir = resolve(startDir);\n while (true) {\n const gitPath = join(dir, '.git');\n if (existsSync(gitPath)) {\n try {\n const st = statSync(gitPath);\n return { root: dir, gitIsFile: st.isFile() };\n } catch {\n // Unreadable .git entry — keep walking.\n }\n }\n const parent = dirname(dir);\n if (parent === dir) return undefined;\n dir = parent;\n }\n}\n\nfunction unresolved(\n root: string,\n overridePath: string,\n reason: string\n): Extract<CheckoutClassification, { kind: 'unresolved-worktree' }> {\n return { kind: 'unresolved-worktree', root, overridePath, reason };\n}\n\nfunction loadComposeDocument(content: string): Record<string, unknown> | undefined {\n let loaded: unknown;\n try {\n loaded = load(content, { schema: COMPOSE_OVERRIDE_SCHEMA });\n } catch {\n return undefined;\n }\n return isRecord(loaded) ? loaded : undefined;\n}\n\n/**\n * Classify the checkout rooted at (or above) `startDir`.\n *\n * A linked worktree has a `.git` *file*. A primary checkout has a `.git`\n * directory. No repository is treated as non-worktree so packaged CLI\n * invocations keep environment defaults.\n */\nexport function classifyCheckout(startDir: string = process.cwd()): CheckoutClassification {\n const git = findNearestGit(startDir);\n if (!git || !git.gitIsFile) {\n return { kind: 'non-worktree', root: git?.root };\n }\n\n const overridePath = join(git.root, OVERRIDE_FILENAME);\n if (!existsSync(overridePath)) {\n return unresolved(\n git.root,\n overridePath,\n `docker-compose.override.yml (generated by ${WORKTREE_SETUP_MARKER})`\n );\n }\n\n let content: string;\n try {\n content = readFileSync(overridePath, 'utf-8');\n } catch {\n return unresolved(git.root, overridePath, 'readable generated worktree override');\n }\n\n if (!content.includes(WORKTREE_SETUP_MARKER)) {\n return unresolved(\n git.root,\n overridePath,\n `generated ${WORKTREE_SETUP_MARKER} override marker`\n );\n }\n\n const document = loadComposeDocument(content);\n if (!document) {\n return unresolved(git.root, overridePath, 'valid generated worktree override YAML');\n }\n\n const services = document['services'];\n if (!isRecord(services)) {\n return unresolved(\n git.root,\n overridePath,\n `api-nginx published port for container ${API_CONTAINER_PORT}; frontend published port for container ${FRONTEND_CONTAINER_PORT}`\n );\n }\n\n const apiPort = publishedPortForTarget(servicePorts(services, 'api-nginx'), API_CONTAINER_PORT);\n const frontendPort = publishedPortForTarget(\n servicePorts(services, 'frontend'),\n FRONTEND_CONTAINER_PORT\n );\n const missing: string[] = [];\n if (apiPort === undefined) {\n missing.push(`api-nginx published port for container ${API_CONTAINER_PORT}`);\n }\n if (frontendPort === undefined) {\n missing.push(`frontend published port for container ${FRONTEND_CONTAINER_PORT}`);\n }\n if (apiPort === undefined || frontendPort === undefined) {\n return unresolved(git.root, overridePath, missing.join('; '));\n }\n\n return {\n kind: 'resolved-worktree',\n root: git.root,\n overridePath,\n apiPort,\n frontendPort,\n };\n}\n\nfunction worktreeFailClosed(\n checkout: Extract<CheckoutClassification, { kind: 'unresolved-worktree' }>,\n missingFields: string[]\n): never {\n throw new WorktreeConfigError(\n `Cannot resolve ${missingFields.join(' and ')} URL(s) for this linked worktree.\\n` +\n `Checkout: ${checkout.root}\\n` +\n `Override: ${checkout.overridePath}\\n` +\n `Missing: ${checkout.reason}\\n` +\n `Re-run scripts/worktree-setup.sh . from this checkout, or pass explicit ` +\n `--api-url and GRAVITY_RAIL_FRONTEND_URL (or both GRAVITY_RAIL_API_URL and ` +\n `GRAVITY_RAIL_FRONTEND_URL) so neither URL inherits the primary checkout.`,\n checkout.root,\n checkout.overridePath\n );\n}\n\nfunction worktreeDevUrls(apiPort: number, frontendPort: number): EnvironmentConfig {\n return {\n apiUrl: `https://api.gravityrail.test:${apiPort}`,\n frontendUrl: `https://app.gravityrail.test:${frontendPort}`,\n };\n}\n\nexport function getEnvironmentConfig(\n env: string,\n options: EnvironmentConfigOptions = {}\n): EnvironmentConfig {\n // Handle dev-cloud environments: --env dev-cloud:NAMESPACE\n if (env.startsWith('dev-cloud:')) {\n const namespace = env.slice('dev-cloud:'.length);\n if (!namespace) {\n throw new Error('dev-cloud environment requires a namespace: --env dev-cloud:NAMESPACE');\n }\n return {\n apiUrl:\n options.apiUrl ||\n process.env.GRAVITY_RAIL_API_URL ||\n `https://${namespace}-api.gravityrail-dev.com`,\n frontendUrl:\n options.frontendUrl ||\n process.env.GRAVITY_RAIL_FRONTEND_URL ||\n `https://${namespace}-app.gravityrail-dev.com`,\n };\n }\n\n const config = ENVIRONMENTS[env];\n if (!config) {\n throw new Error(\n `Invalid environment: ${env}. Use 'dev', 'staging', 'prod', or 'dev-cloud:NAMESPACE'.`\n );\n }\n\n const explicitApi = options.apiUrl || process.env.GRAVITY_RAIL_API_URL;\n const explicitFrontend = options.frontendUrl || process.env.GRAVITY_RAIL_FRONTEND_URL;\n\n if (env !== 'dev') {\n return {\n apiUrl: explicitApi || config.apiUrl,\n frontendUrl: explicitFrontend || config.frontendUrl,\n };\n }\n\n const checkout = classifyCheckout(options.startDir ?? process.cwd());\n\n if (checkout.kind === 'non-worktree') {\n return {\n apiUrl: explicitApi || config.apiUrl,\n frontendUrl: explicitFrontend || config.frontendUrl,\n };\n }\n\n if (checkout.kind === 'resolved-worktree') {\n const detected = worktreeDevUrls(checkout.apiPort, checkout.frontendPort);\n return {\n apiUrl: explicitApi || detected.apiUrl,\n frontendUrl: explicitFrontend || detected.frontendUrl,\n };\n }\n\n const missingFields: string[] = [];\n if (!explicitApi) missingFields.push('API');\n if (!explicitFrontend) missingFields.push('frontend');\n if (missingFields.length > 0) {\n worktreeFailClosed(checkout, missingFields);\n }\n return { apiUrl: explicitApi!, frontendUrl: explicitFrontend! };\n}\n","/**\n * CLI OAuth2 authentication utilities\n *\n * Provides OAuth2 with PKCE flow for CLI tools, including:\n * - Credential caching (per environment + named profile)\n * - Dynamic client registration\n * - Token refresh\n * - Browser-based authorization\n *\n * Credential persistence lives in `./credentials-manager` (multi-account profiles).\n */\n\nimport { createServer } from 'http';\nimport { createHash, randomBytes } from 'crypto';\nimport { exec } from 'child_process';\nimport { existsSync, mkdirSync } from 'fs';\nimport { dirname } from 'path';\nimport lockfile from 'proper-lockfile';\nimport pkg from '../../package.json';\nimport { EnvironmentConfig, CALLBACK_PORT, CALLBACK_PATH, REDIRECT_URI } from './config';\nimport { CredentialsManager, type CLICredentials } from './credentials-manager';\n\nexport {\n CredentialsManager,\n DEFAULT_PROFILE,\n type CLICredentials,\n type ProfileSummary,\n} from './credentials-manager';\n\nexport interface AuthResult {\n accessToken: string;\n credentials?: CLICredentials;\n}\n\nexport interface NonInteractiveAuthOptions {\n env: string;\n envConfig: EnvironmentConfig;\n credentialsManager: CredentialsManager;\n profile?: string;\n}\n\nconst ACCESS_TOKEN_EXPIRY_BUFFER_MS = 5 * 60 * 1000;\n\n/**\n * Sent on every token grant so reuse investigations can tell CLI releases\n * apart. Self-reported identification, not authentication.\n */\nconst TOKEN_REQUEST_USER_AGENT = `gravityrail-cli/${pkg.version}`;\n\n/** The one timeout on a refresh: the token POST, including reading its body. */\nconst REFRESH_REQUEST_TIMEOUT_MS = 15_000;\n\n/**\n * How long a refresh waits for another process's refresh of the same\n * credential file: 100, 200, 400 and 800 ms, then 1 s per retry — about 20.5 s\n * over 23 retries. That outlasts one whole refresh by the lock holder (bounded\n * by `REFRESH_REQUEST_TIMEOUT_MS`), so a waiter adopts the winner's result\n * instead of giving up. The library keeps a held lock fresh and reclaims one\n * abandoned by a crashed process, so its `stale`/`update` defaults are kept.\n */\nconst REFRESH_LOCK_RETRIES = { retries: 23, factor: 2, minTimeout: 100, maxTimeout: 1000 };\n\n// Refresh failures that are not a verdict on the stored token. Fixed strings\n// by design: never interpolate tokens, response bodies or transport errors.\nconst REFRESH_LOCK_FAILED =\n 'Could not lock the credential store to refresh the access token (another gr process may still be refreshing). Stored credentials were kept; retry the command.';\nconst REFRESH_REQUEST_FAILED =\n 'Token refresh did not complete (network error or timeout). Stored credentials were kept; retry the command.';\nconst REFRESH_FAILED =\n 'Token refresh failed. Stored credentials were kept; retry the command, and log in again if it keeps failing.';\nconst REFRESH_SUPERSEDED =\n 'Token refresh was rejected, but the stored credentials changed meanwhile and were kept. Retry the command.';\nconst REFRESH_BAD_RESPONSE =\n 'Token refresh returned an unusable response. Stored credentials were kept; retry the command.';\nconst REFRESH_SAVE_FAILED =\n 'Token refresh succeeded, but the new credentials could not be saved. Retry the command, and log in again if it keeps failing.';\n\nfunction stderr(msg: string): void {\n process.stderr.write(msg + '\\n');\n}\n\n/**\n * Parse an OAuth error response body, checking the JSON `error` field first,\n * falling back to text matching.\n */\nfunction isOAuthError(body: string, errorCode: string): boolean {\n try {\n const parsed = JSON.parse(body);\n if (parsed.error === errorCode) return true;\n } catch {\n // Not JSON — fall through to text match\n }\n return body.includes(errorCode);\n}\n\n/**\n * Validate and extract fields from an OAuth token response.\n * Throws on malformed responses per RFC 6749 §5.1.\n */\nfunction validateTokenResponse(tokenData: Record<string, unknown>): {\n accessToken: string;\n refreshToken?: string;\n expiresIn: number;\n} {\n if (!tokenData.access_token || typeof tokenData.access_token !== 'string') {\n throw new Error('Token response missing access_token');\n }\n\n if (tokenData.token_type && String(tokenData.token_type).toLowerCase() !== 'bearer') {\n throw new Error(`Unsupported token type: ${tokenData.token_type}`);\n }\n\n let expiresIn = 86400; // default 24h\n if (tokenData.expires_in != null) {\n const raw = Number(tokenData.expires_in);\n if (!Number.isFinite(raw) || raw < 60 || raw > 31536000) {\n stderr(' Warning: unusual expires_in value in the token response, using default');\n } else {\n expiresIn = raw;\n }\n }\n\n return {\n accessToken: tokenData.access_token,\n refreshToken:\n typeof tokenData.refresh_token === 'string' ? tokenData.refresh_token : undefined,\n expiresIn,\n };\n}\n\n/**\n * Generate PKCE code verifier and challenge\n */\nexport function generatePKCE(): { codeVerifier: string; codeChallenge: string } {\n const codeVerifier = randomBytes(64).toString('base64url').slice(0, 128);\n const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');\n return { codeVerifier, codeChallenge };\n}\n\n/**\n * Open a URL in the default browser\n */\nexport function openBrowser(url: string): void {\n const platform = process.platform;\n let cmd: string;\n if (platform === 'darwin') {\n cmd = `open \"${url}\"`;\n } else if (platform === 'win32') {\n cmd = `start \"\" \"${url}\"`;\n } else {\n cmd = `xdg-open \"${url}\"`;\n }\n\n exec(cmd, (err) => {\n if (err) {\n stderr(' Could not open browser automatically.');\n }\n });\n}\n\n/**\n * Start a local HTTP server to receive the OAuth callback\n */\nexport function startCallbackServer(): Promise<{ code: string; state: string }> {\n return new Promise((resolve, reject) => {\n let authCode: string | null = null;\n let error: string | null = null;\n let receivedState: string | null = null;\n\n const server = createServer((req, res) => {\n const url = new URL(req.url!, `http://localhost:${CALLBACK_PORT}`);\n\n if (url.pathname !== CALLBACK_PATH) {\n res.writeHead(404);\n res.end('Not found');\n return;\n }\n\n if (url.searchParams.has('error')) {\n error = url.searchParams.get('error');\n const errorDesc = url.searchParams.get('error_description') || 'Unknown error';\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(`\n <html>\n <head><title>Authorization Failed</title></head>\n <body style=\"font-family: system-ui; text-align: center; padding: 50px;\">\n <h1 style=\"color: #dc2626;\">Authorization Failed</h1>\n <p>${errorDesc}</p>\n <p>You can close this window.</p>\n </body>\n </html>\n `);\n server.close();\n reject(new Error(`Authorization failed: ${error}`));\n } else if (url.searchParams.has('code')) {\n authCode = url.searchParams.get('code')!;\n receivedState = url.searchParams.get('state')!;\n res.writeHead(200, { 'Content-Type': 'text/html' });\n res.end(`\n <html>\n <head><title>Authorization Successful</title></head>\n <body style=\"font-family: system-ui; text-align: center; padding: 50px;\">\n <h1 style=\"color: #16a34a;\">Authorization Successful!</h1>\n <p>You can close this window and return to the terminal.</p>\n <script>setTimeout(() => window.close(), 2000);</script>\n </body>\n </html>\n `);\n server.close();\n resolve({ code: authCode, state: receivedState });\n } else {\n res.writeHead(400);\n res.end('Missing code parameter');\n }\n });\n\n server.listen(CALLBACK_PORT, 'localhost', () => {\n // Server started\n });\n\n // Timeout after 5 minutes (unref so it doesn't keep the process alive)\n const timeout = setTimeout(\n () => {\n if (!authCode && !error) {\n server.close();\n reject(new Error('Authorization timeout - no response received'));\n }\n },\n 5 * 60 * 1000\n );\n timeout.unref();\n });\n}\n\n/**\n * Vendor field in the RFC 8414 discovery document naming the server's pinned\n * first-party CLI client. Must match `FIRST_PARTY_CLIENT_METADATA_FIELD` in\n * `lib/routes/oauth/first_party.py`.\n */\nexport const FIRST_PARTY_CLIENT_METADATA_FIELD = 'gravityrail_first_party_cli_client_id';\n\n/**\n * Ask the server for its pinned first-party CLI client id, or null if it has none.\n *\n * Null is the dynamic-registration fallback trigger, and it has to cover two\n * different situations that the CLI cannot and need not tell apart: a server\n * deployed before the pinned client shipped, and a discovery request that\n * failed. Both mean \"we have no pinned identity to use here\", and DCR is the\n * correct answer to both — so every failure mode collapses to null rather than\n * throwing and taking down a login that would otherwise have worked.\n */\nexport async function fetchFirstPartyClientId(\n envConfig: EnvironmentConfig\n): Promise<string | null> {\n try {\n const response = await fetch(`${envConfig.apiUrl}/.well-known/oauth-authorization-server`, {\n headers: { Accept: 'application/json' },\n });\n if (!response.ok) return null;\n const metadata = (await response.json()) as Record<string, unknown>;\n const clientId = metadata[FIRST_PARTY_CLIENT_METADATA_FIELD];\n return typeof clientId === 'string' && clientId.length > 0 ? clientId : null;\n } catch {\n return null;\n }\n}\n\n/**\n * Register a new OAuth client dynamically.\n *\n * Retained for servers that predate the pinned first-party client — older\n * self-hosted and dev deployments — not as the normal path. See\n * `resolveOAuthClient`.\n */\nasync function dynamicClientRegistration(\n envConfig: EnvironmentConfig,\n clientName: string = 'Gravity CLI'\n): Promise<{ client_id: string; client_secret: string }> {\n stderr(' Registering OAuth client...');\n\n const registrationUrl = `${envConfig.apiUrl}/api/v2/oauth/register`;\n\n const registrationData = {\n client_name: clientName,\n redirect_uris: [REDIRECT_URI],\n grant_types: ['authorization_code', 'refresh_token'],\n response_types: ['code'],\n token_endpoint_auth_method: 'client_secret_basic',\n scope: 'workspace:admin',\n };\n\n const response = await fetch(registrationUrl, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(registrationData),\n });\n\n if (!response.ok) {\n throw new Error(`Client registration failed: ${response.status} - ${await response.text()}`);\n }\n\n return (await response.json()) as { client_id: string; client_secret: string };\n}\n\n/**\n * Build the client-authentication half of a token request.\n *\n * A confidential client (one we registered dynamically, which issued us a\n * secret) authenticates with HTTP Basic. The pinned first-party client has no\n * secret and identifies itself with a body `client_id` instead — RFC 6749\n * §2.3.1, and RFC 8252 §8.5 for why a CLI cannot hold one in the first place.\n * The server accepts either and reads the id from the header first.\n */\nfunction tokenRequestAuth(\n clientId: string,\n clientSecret?: string\n): { headers: Record<string, string>; params: Record<string, string> } {\n if (clientSecret) {\n const authHeader = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');\n return {\n headers: { Authorization: `Basic ${authHeader}` },\n params: {},\n };\n }\n return { headers: {}, params: { client_id: clientId } };\n}\n\n/**\n * Decide which OAuth client this login should use.\n *\n * The pinned first-party client wins whenever the server has one, including\n * for profiles that were created by an older CLI against the same server —\n * that is what retires per-login dynamic registration rather than merely\n * stopping new profiles from using it. Swapping is safe precisely here and\n * nowhere else: `performOAuthFlow` mints a fresh token pair anyway, so nothing\n * bound to the old client survives the call. Profiles that are never logged in\n * again keep refreshing against their old client untouched.\n *\n * `forceDynamicRegistration` is the retry path: the server has just answered\n * `invalid_client` for whichever client the branches below chose, so it must\n * skip **both** of the reuse branches, not only discovery. Skipping discovery\n * alone would fall through to the cached branch and hand back the very client\n * that was rejected — and when the rejected client *was* the cached one, that\n * is an identical second attempt. \"Register a fresh client\" is the only\n * response that can differ from the attempt that just failed.\n *\n * Exported so the three branches can be tested directly — `performOAuthFlow`\n * around it opens a browser and binds a loopback port, which no unit test\n * should have to stand up to assert which client was chosen.\n */\nexport async function resolveOAuthClient(\n envConfig: EnvironmentConfig,\n stored: CLICredentials | null,\n clientName: string = 'Gravity CLI',\n forceDynamicRegistration: boolean = false\n): Promise<{ clientId: string; clientSecret?: string }> {\n if (!forceDynamicRegistration) {\n const pinned = await fetchFirstPartyClientId(envConfig);\n if (pinned) {\n stderr(' Using the first-party CLI client');\n return { clientId: pinned };\n }\n\n // No pinned client on this server. Reuse whatever this profile\n // registered before, so an older environment does not accumulate a\n // client per login.\n if (stored?.clientId && stored.clientSecret) {\n stderr(' Using cached client credentials');\n return { clientId: stored.clientId, clientSecret: stored.clientSecret };\n }\n }\n\n const registration = await dynamicClientRegistration(envConfig, clientName);\n return { clientId: registration.client_id, clientSecret: registration.client_secret };\n}\n\nexport interface OAuthFlowOptions {\n env: string;\n envConfig: EnvironmentConfig;\n credentialsManager: CredentialsManager;\n clientName?: string;\n /** Named account profile to store tokens under (default: resolved current). */\n profile?: string;\n /**\n * Test seam: wait for the loopback callback. Defaults to the real server.\n *\n * The two side effects below are the reason this function had no direct\n * tests — one shells out to the system browser and the other binds a fixed\n * port, neither of which a unit test may do. Injecting them is what lets\n * the \"a failed login must not touch the stored profile\" invariant be\n * tested at all. Production never passes either.\n */\n startCallback?: () => Promise<{ code: string; state: string }>;\n /** Test seam: open the authorization URL. Defaults to the real browser. */\n openAuthUrl?: (url: string) => void;\n}\n\n/**\n * Perform the full OAuth2 browser-based flow with PKCE.\n *\n * This always opens the browser for authorization. It never returns a cached\n * token — that's the job of createAuthenticatedContext. The only thing reused\n * is the registered OAuth client ID (which is identity, not auth).\n *\n * **Nothing is written to the credential store until the token exchange has\n * succeeded.** A login can end at the consent screen the member cancels, at a\n * network failure, or at the five-minute callback timeout, and every one of\n * those must leave the profile exactly as it was — see the comment on the\n * single `save` below for what persisting early actually broke.\n */\nexport async function performOAuthFlow(options: OAuthFlowOptions): Promise<AuthResult> {\n const {\n env,\n envConfig,\n credentialsManager,\n clientName = 'Gravity CLI',\n profile,\n startCallback = startCallbackServer,\n openAuthUrl = openBrowser,\n } = options;\n const profileName = credentialsManager.resolveProfileName(profile, env);\n let hasRetried = false;\n let forceDynamicRegistration = false;\n\n while (true) {\n const stored = credentialsManager.load(env, profileName);\n const { clientId, clientSecret } = await resolveOAuthClient(\n envConfig,\n stored,\n clientName,\n forceDynamicRegistration\n );\n\n const { codeVerifier, codeChallenge } = generatePKCE();\n const state = randomBytes(32).toString('base64url');\n\n const authParams = new URLSearchParams({\n response_type: 'code',\n client_id: clientId,\n redirect_uri: REDIRECT_URI,\n scope: 'workspace:admin',\n state: state,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n });\n\n const authUrl = `${envConfig.apiUrl}/api/v2/oauth/authorize?${authParams}`;\n\n const callbackPromise = startCallback();\n\n stderr('\\n Opening browser for authorization...');\n stderr(` Profile: ${profileName}`);\n stderr(` If browser doesn't open, visit:\\n ${authUrl}\\n`);\n openAuthUrl(authUrl);\n\n stderr(' Waiting for authorization (timeout: 5 minutes)...');\n let code: string;\n let receivedState: string;\n try {\n ({ code, state: receivedState } = await callbackPromise);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!hasRetried && isOAuthError(msg, 'invalid_client')) {\n stderr(' Client rejected by the server, re-registering...');\n hasRetried = true;\n // The rejected client was only ever held in memory, so there is\n // nothing on disk to clean up — `forceDynamicRegistration` alone\n // is what makes the next pass register a fresh one.\n forceDynamicRegistration = true;\n continue;\n }\n throw err;\n }\n\n if (receivedState !== state) {\n throw new Error('State mismatch - possible CSRF attack');\n }\n\n stderr(' Authorization code received!');\n stderr(' Exchanging code for tokens...');\n\n const tokenUrl = `${envConfig.apiUrl}/api/v2/oauth/token`;\n const auth = tokenRequestAuth(clientId, clientSecret);\n\n const tokenResponse = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n ...auth.headers,\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': TOKEN_REQUEST_USER_AGENT,\n },\n body: new URLSearchParams({\n grant_type: 'authorization_code',\n code: code,\n redirect_uri: REDIRECT_URI,\n code_verifier: codeVerifier,\n ...auth.params,\n }),\n });\n\n if (!tokenResponse.ok) {\n const body = await tokenResponse.text();\n if (!hasRetried && isOAuthError(body, 'invalid_client')) {\n stderr(' Client rejected by the server, re-registering...');\n hasRetried = true;\n forceDynamicRegistration = true;\n continue;\n }\n throw new Error(`Token exchange failed: ${tokenResponse.status} - ${body}`);\n }\n\n const tokenData = (await tokenResponse.json()) as Record<string, unknown>;\n const { accessToken, refreshToken, expiresIn } = validateTokenResponse(tokenData);\n\n // The one and only write, and it happens after the exchange has already\n // succeeded. Persisting the client identity before the browser step used\n // to leave a cancelled, failed or timed-out login with the NEW client id\n // sitting next to the OLD tokens — and the refresh grant looks a token\n // up by `refresh_token AND app_id` (`lib/routes/oauth/oauth_token.py`),\n // so that pairing matches no row and the next refresh is a guaranteed\n // `invalid_grant`. A member who simply closed the consent tab lost the\n // session they already had.\n //\n // A refresh token is only carried over when the client identity is\n // unchanged, for the same reason: one minted for the previous client is\n // not merely useless under the new one, it is actively harmful, because\n // the server answers it with `invalid_grant` and `refreshAccessToken`\n // clears the profile on that definitive rejection.\n // Dropping it degrades to \"log in again\", which is the honest outcome.\n //\n // `clientSecret` is written unconditionally, and when it is `undefined`\n // that is load-bearing rather than a no-op: the spread has just copied a\n // stale DCR secret out of `stored`, and this key overwrites it with\n // `undefined`, which `JSON.stringify` then omits entirely from the\n // persisted store. Both halves matter — in memory `tokenRequestAuth`\n // reads `undefined` and takes the body-`client_id` path, and on disk the\n // secret is gone rather than lying in wait for the next load. Dropping\n // this key \"because undefined does nothing\" would put the pinned public\n // client back on the Basic-auth path with a secret it does not have.\n //\n // Everything else on the profile (pinned workspace, cached org sessions)\n // is preserved — only the client identity and the tokens are decided\n // here, and org sessions are keyed to the account, not the client.\n const clientChanged = stored?.clientId !== clientId;\n const creds: CLICredentials = {\n ...(stored ?? {}),\n clientId,\n clientSecret,\n accessToken,\n refreshToken: refreshToken ?? (clientChanged ? undefined : stored?.refreshToken),\n expiresAt: new Date(Date.now() + expiresIn * 1000).toISOString(),\n };\n\n credentialsManager.save(env, creds, profileName);\n credentialsManager.setCurrentProfile(env, profileName);\n stderr(' Authentication successful!');\n\n return { accessToken, credentials: creds };\n }\n}\n\n/**\n * The `error` code of a JSON OAuth error body, or undefined when the body is\n * not one.\n *\n * Unlike `isOAuthError` there is no substring fallback: this decides whether a\n * profile is deleted, and only a structured server verdict may do that.\n */\nfunction parseOAuthErrorCode(body: string): string | undefined {\n try {\n const parsed: unknown = JSON.parse(body);\n if (parsed && typeof parsed === 'object') {\n const code = (parsed as { error?: unknown }).error;\n if (typeof code === 'string') return code;\n }\n } catch {\n // Not JSON — no verdict.\n }\n return undefined;\n}\n\n/**\n * Whether the stored access token has enough life left to run a command on.\n *\n * The same `ACCESS_TOKEN_EXPIRY_BUFFER_MS` horizon the resolver uses, so a\n * single process that entered the refresh with a few minutes left still\n * rotates instead of adopting a token that may expire mid-command. A waiter\n * still adopts the winner's successor without rotating it again: the server\n * mints access tokens for far longer than the buffer (24 h, `oauth_token.py`),\n * so a successor another process just stored always clears this check. A token\n * with no recorded expiry is usable, as everywhere else.\n */\nfunction isFreshEnough(creds: CLICredentials): boolean {\n return (\n !creds.expiresAt ||\n new Date(creds.expiresAt).getTime() > Date.now() + ACCESS_TOKEN_EXPIRY_BUFFER_MS\n );\n}\n\n/**\n * Take the cross-process refresh lock for one credential file.\n *\n * The target is the credential file's own path, so every refresh that shares\n * an environment (and API port) file serializes, whichever profile it is for.\n * The file need not exist: the library locks with a sibling `<path>.lock`\n * directory and `realpath: false` skips resolving the target.\n */\nasync function acquireRefreshLock(credentialsPath: string): Promise<() => Promise<void>> {\n try {\n const cacheDir = dirname(credentialsPath);\n if (!existsSync(cacheDir)) {\n mkdirSync(cacheDir, { recursive: true, mode: 0o700 });\n }\n return await lockfile.lock(credentialsPath, {\n realpath: false,\n retries: REFRESH_LOCK_RETRIES,\n // The library default throws from a timer, which would crash the\n // command. Losing the lock mid-refresh is worth a warning, not that.\n // The library has already dropped its in-process record of the lock\n // by the time this runs, so the `release()` in `refreshAccessToken`'s\n // `finally` is refused (ENOTACQUIRED) and can never remove a lock\n // another process has since taken.\n onCompromised: () => {\n stderr(\n ' Warning: lost the credential refresh lock; another gr process may refresh at the same time'\n );\n },\n });\n } catch {\n // Never fall back to an unlocked refresh: that is exactly the race this\n // lock exists to prevent.\n throw new Error(REFRESH_LOCK_FAILED);\n }\n}\n\n/**\n * Refresh the selected profile's access token, coordinating with other `gr`\n * processes on this machine (GitHub #29807).\n *\n * The server rotates refresh tokens and rejects a spent one as reuse, so two\n * processes presenting the same token race: one wins, the other gets\n * `invalid_grant`. Everything from reading the stored token to saving its\n * successor therefore runs under one lock on the credential file. A process\n * that waited re-reads the store and adopts what the winner saved instead of\n * presenting the spent token.\n *\n * `creds` is the caller's pre-lock snapshot and is deliberately not consulted:\n * every decision — whether a refresh is needed, which token to present, what\n * to save — is made from the profile read under the lock, because the snapshot\n * may already have been rotated away, logged out, or replaced by a login. The\n * parameter stays for the exported signature.\n *\n * Returns the profile's usable credentials, or null when the profile cannot\n * authenticate — including after a definitive rejection, which clears that\n * profile only. Any other failure (network, timeout, server error, malformed\n * response, a busy lock, a failed save) leaves the store untouched and throws a\n * plain Error whose message is fixed: no token, response body, or transport\n * detail ever reaches it.\n */\nexport async function refreshAccessToken(\n envConfig: EnvironmentConfig,\n _creds: CLICredentials,\n credentialsManager: CredentialsManager,\n env: string,\n profile?: string\n): Promise<CLICredentials | null> {\n // Resolved once, before waiting, so a `gr auth switch` in another process\n // cannot redirect this command to a different profile mid-refresh.\n const profileName = credentialsManager.resolveProfileName(profile, env);\n const release = await acquireRefreshLock(credentialsManager.getCredentialsFilePath(env));\n try {\n return await refreshUnderLock(envConfig, credentialsManager, env, profileName);\n } finally {\n // Rejects only when the lock was already lost (see `onCompromised`);\n // there is nothing left to release then.\n await release().catch(() => undefined);\n }\n}\n\nasync function refreshUnderLock(\n envConfig: EnvironmentConfig,\n credentialsManager: CredentialsManager,\n env: string,\n profileName: string\n): Promise<CLICredentials | null> {\n const stored = credentialsManager.load(env, profileName);\n\n // Another process may have refreshed while this one waited for the lock.\n if (stored?.accessToken && isFreshEnough(stored)) {\n return stored;\n }\n // A client secret is no longer part of the requirement: the pinned\n // first-party client is public and has none, and demanding one here would\n // silently strand every profile logged in with it — `context.ts` treats a\n // null return as \"re-login required\", so the CLI would open a browser on\n // every command instead of refreshing.\n if (!stored?.refreshToken || !stored.clientId) {\n stderr(' Cannot refresh: missing credentials (re-login required)');\n return null;\n }\n\n const presentedRefreshToken = stored.refreshToken;\n const auth = tokenRequestAuth(stored.clientId, stored.clientSecret);\n\n let response: Response;\n try {\n response = await fetch(`${envConfig.apiUrl}/api/v2/oauth/token`, {\n method: 'POST',\n headers: {\n ...auth.headers,\n 'Content-Type': 'application/x-www-form-urlencoded',\n 'User-Agent': TOKEN_REQUEST_USER_AGENT,\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: presentedRefreshToken,\n ...auth.params,\n }),\n // Bounds the request and the body reads below. Never retried: a\n // second POST of the same token is itself a reuse.\n //\n // Known residual: an abort only stops *this* side. If the server\n // commits the rotation after the deadline, the response carrying the\n // successor is lost and the stored token is spent; the next refresh\n // of it is a genuine invalid_grant and clears the profile. The client\n // cannot recover an unseen successor; the deadline is long enough\n // that this needs a token endpoint stalled for 15 s, not a slow one.\n signal: AbortSignal.timeout(REFRESH_REQUEST_TIMEOUT_MS),\n });\n } catch {\n throw new Error(REFRESH_REQUEST_FAILED);\n }\n\n if (!response.ok) {\n let body: string;\n try {\n body = await response.text();\n } catch {\n throw new Error(REFRESH_REQUEST_FAILED);\n }\n if (response.status === 400 && parseOAuthErrorCode(body) === 'invalid_grant') {\n // The verdict is about the token presented, not whatever the store\n // holds now. Clear only if that same token is still stored, so a\n // stale rejection can never delete a replacement saved meanwhile.\n const current = credentialsManager.load(env, profileName);\n if (!current) {\n stderr(' The server rejected the stored refresh token; re-login required');\n return null;\n }\n // Two local values from the user's own credential file; no remote\n // party can observe this comparison's timing. FP (CWE-208).\n // bearer:disable javascript_lang_observable_timing\n if (current.refreshToken === presentedRefreshToken) {\n credentialsManager.clear(env, profileName);\n stderr(' The server rejected the stored refresh token; re-login required');\n return null;\n }\n throw new Error(REFRESH_SUPERSEDED);\n }\n if (parseOAuthErrorCode(body) === 'invalid_client') {\n // The OAuth client this profile was issued under no longer exists\n // (`lib/routes/oauth/oauth_token.py` answers 401 invalid_client).\n // Nothing in the profile can authenticate again, so surface the\n // existing re-login path — but it is a verdict on the client, not\n // on the stored tokens, so leave the profile for `gr login` to\n // replace rather than deleting it here.\n stderr(\" The server no longer recognises this profile's OAuth client; re-login required\");\n return null;\n }\n throw new Error(REFRESH_FAILED);\n }\n\n let tokens: ReturnType<typeof validateTokenResponse>;\n try {\n tokens = validateTokenResponse((await response.json()) as Record<string, unknown>);\n } catch {\n throw new Error(REFRESH_BAD_RESPONSE);\n }\n\n // Merge into a fresh read so profile metadata (pinned workspace, cached\n // org sessions) written while the request was in flight survives. Login\n // and logout take no refresh lock, so the profile may also be gone or be\n // a different grant by now; the successor belongs to the grant that was\n // presented and must not be written over either.\n const latest = credentialsManager.load(env, profileName);\n if (!latest) {\n // Logged out while the request was in flight. The successor dies with\n // the grant it belongs to; resurrecting the profile would undo the logout.\n stderr(' Cannot refresh: missing credentials (re-login required)');\n return null;\n }\n // Two local values from the user's own credential file; no remote party\n // can observe this comparison's timing. FP (CWE-208).\n // bearer:disable javascript_lang_observable_timing\n if (latest.clientId !== stored.clientId || latest.refreshToken !== presentedRefreshToken) {\n // A login replaced the profile. Its tokens are a different grant (the\n // server keys them by refresh_token AND client), so writing this\n // successor over them would make the new login's next refresh an\n // invalid_grant. Use the login if it can authenticate; otherwise this\n // refresh has nothing valid to offer.\n if (latest.accessToken && isFreshEnough(latest)) {\n return latest;\n }\n throw new Error(REFRESH_SUPERSEDED);\n }\n\n const updated: CLICredentials = {\n ...latest,\n accessToken: tokens.accessToken,\n // A server may omit `refresh_token` when it does not rotate; the\n // presented token then stays valid.\n refreshToken: tokens.refreshToken ?? presentedRefreshToken,\n expiresAt: new Date(Date.now() + tokens.expiresIn * 1000).toISOString(),\n };\n try {\n credentialsManager.save(env, updated, profileName);\n } catch {\n throw new Error(REFRESH_SAVE_FAILED);\n }\n return updated;\n}\n\n/**\n * Resolve credentials that do not require a browser.\n *\n * Precedence: process API key, then the selected profile's valid access token,\n * then that profile's refresh token. A null result leaves the caller to decide\n * whether interactive OAuth is appropriate for its command.\n */\nexport async function resolveNonInteractiveAuth(\n options: NonInteractiveAuthOptions\n): Promise<AuthResult | null> {\n const { env, envConfig, credentialsManager, profile } = options;\n\n // Process env only — API keys are never persisted in the credential store.\n const apiKey = process.env.GRAVITY_RAIL_API_KEY;\n if (apiKey) {\n return { accessToken: apiKey };\n }\n\n const profileName = credentialsManager.resolveProfileName(profile, env);\n const credentials = credentialsManager.load(env, profileName);\n if (!credentials?.accessToken) return null;\n\n const notExpired =\n !credentials.expiresAt ||\n new Date(credentials.expiresAt) > new Date(Date.now() + ACCESS_TOKEN_EXPIRY_BUFFER_MS);\n if (notExpired) {\n return { accessToken: credentials.accessToken, credentials };\n }\n\n const refreshed = await refreshAccessToken(\n envConfig,\n credentials,\n credentialsManager,\n env,\n profileName\n );\n if (!refreshed?.accessToken) return null;\n\n return { accessToken: refreshed.accessToken, credentials: refreshed };\n}\n","{\n \"name\": \"@gravity-rail/cli\",\n \"version\": \"0.55.1\",\n \"description\": \"Gravity Rail CLI — manage workspaces from the command line\",\n \"type\": \"module\",\n \"bin\": {\n \"gr\": \"./dist/gr.js\"\n },\n \"exports\": {\n \".\": {\n \"types\": \"./dist/lib.d.ts\",\n \"import\": \"./dist/lib.js\"\n },\n \"./lib\": {\n \"types\": \"./dist/lib.d.ts\",\n \"import\": \"./dist/lib.js\"\n },\n \"./cli-utils\": {\n \"types\": \"./dist/cli-utils.d.ts\",\n \"import\": \"./dist/cli-utils.js\"\n }\n },\n \"files\": [\n \"dist\"\n ],\n \"license\": \"MIT\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://developer.gravityrail.com\"\n },\n \"homepage\": \"https://developer.gravityrail.com\",\n \"keywords\": [\n \"gravity-rail\",\n \"cli\",\n \"healthcare\",\n \"workspace\"\n ],\n \"engines\": {\n \"node\": \">=18\"\n },\n \"scripts\": {\n \"build\": \"tsup\",\n \"generate:skill-docs\": \"tsx scripts/generate-skill-docs.ts\",\n \"clean\": \"rm -rf dist\",\n \"typecheck\": \"tsc --noEmit\",\n \"test\": \"jest --forceExit\",\n \"test:ci\": \"jest --forceExit --passWithNoTests --coverage --coverageReporters=json-summary --coverageReporters=text-summary\"\n },\n \"dependencies\": {\n \"@gravity-rail/sdk\": \"workspace:^\",\n \"js-yaml\": \"^5.4.3\",\n \"proper-lockfile\": \"^4.1.2\"\n },\n \"devDependencies\": {\n \"@gravity-rail/sandbox\": \"workspace:*\",\n \"@types/jest\": \"^30.0.0\",\n \"@types/node\": \"^26.6.5\",\n \"@types/proper-lockfile\": \"^4.1.4\",\n \"jest\": \"^30.5.2\",\n \"ts-jest\": \"29.4.9\",\n \"tsup\": \"^8.5.1\",\n \"typescript\": \"^6.0.3\",\n \"undici\": \"6.28.1\"\n }\n}\n","/**\n * CLI credential store with multi-account profile support.\n *\n * Store location: `~/.gr-cli/credentials-<env>.json` (mode 0600; dir mode 0700).\n * Format is versioned JSON with named profiles (aws-style). Flat single-credential\n * files from older CLI versions migrate to profile \"default\" on first read/write.\n *\n * Security notes (for operators / security review):\n * - Tokens and client secrets live only on the local filesystem under the user's home.\n * - Files are written mode 0600; the cache directory is created mode 0700.\n * - Profile and environment names are allowlist-validated with a\n * resolve()/relative() containment check so path traversal cannot escape `.gr-cli`.\n * - Callers must never log credential field values (accessToken, refreshToken, secrets).\n * - `GRAVITY_RAIL_API_KEY` is not persisted here; it remains process-env only and\n * overrides stored profiles for non-interactive automation.\n * - Dev profiles imported from `dev-realms.json` store bearer tokens only (no OAuth\n * client secret). They are local-dev conveniences and must never be committed.\n */\n\nimport {\n readFileSync,\n writeFileSync,\n existsSync,\n mkdirSync,\n chmodSync,\n unlinkSync,\n renameSync,\n} from 'fs';\nimport { join, resolve, relative, isAbsolute } from 'path';\n\nexport const DEFAULT_PROFILE = 'default';\n\n/**\n * Stored credentials for one named profile.\n *\n * OAuth logins have a clientId (+ optional refresh). `clientSecret` is present\n * only for a dynamically registered confidential client; profiles using the\n * pinned first-party public client have none, by design (GRA-7843).\n * Dev profiles imported from dev-realms.json are token-only (accessToken +\n * optional workspaceUuid) — no OAuth client registration.\n */\n/**\n * A cached org-scoped SSO session (`gr_sess_...`) obtained via browser step-up.\n * Replayed as the `X-Org-Session` header so subsequent invocations skip the\n * browser until the session expires (GRA-6995).\n */\nexport interface StoredOrgSession {\n session: string;\n /** ISO expiry; treated as a cache miss once passed. */\n expiresAt?: string;\n}\n\nexport interface CLICredentials {\n clientId?: string;\n clientSecret?: string;\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: string;\n /** Default workspace UUID (workspace/member profiles from dev-realms). */\n workspaceUuid?: string;\n /**\n * Cached org-scoped SSO sessions keyed by organization UUID. Populated by the\n * org-auth step-up handler; independent of the account token's own expiry.\n */\n orgSessions?: Record<string, StoredOrgSession>;\n}\n\nexport interface ProfileSummary {\n name: string;\n current: boolean;\n hasAccessToken: boolean;\n /** ISO timestamp if known; never includes the token itself. */\n expiresAt?: string;\n /** Present when the profile pins a default workspace. */\n workspaceUuid?: string;\n}\n\ninterface CredentialStoreV2 {\n version: 2;\n currentProfile: string;\n profiles: Record<string, CLICredentials>;\n}\n\n/**\n * Manages CLI credentials storage (one file per environment, multi-profile).\n */\nexport class CredentialsManager {\n private cacheDir: string;\n private apiUrl?: string;\n\n constructor(rootDir: string, apiUrl?: string) {\n // `rootDir` is supplied only by CLI bootstrap (context.ts passes\n // os.homedir()) — the local user's own home directory, never remote or\n // request input — joined with the constant '.gr-cli'. FP (CWE-22).\n // bearer:disable javascript_lang_path_traversal\n // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal\n this.cacheDir = join(rootDir, '.gr-cli');\n this.apiUrl = apiUrl;\n }\n\n /**\n * Validate a profile name. Allows the legacy realm path form\n * `workspace/member` so `acme-demo/acme-admin` works as a profile name.\n * Rejects path traversal and absolute paths.\n */\n validateProfileName(profile: string): void {\n if (!profile) {\n throw new Error('Invalid profile name: (empty).');\n }\n if (profile.includes('..') || profile.startsWith('/') || profile.endsWith('/')) {\n throw new Error(\n `Invalid profile name: ${profile}. Do not use \"..\" or leading/trailing \"/\".`\n );\n }\n // Letters, digits, '_', '-', '.', and a single-level '/' for workspace/member.\n if (!/^[a-zA-Z0-9_.-]+(?:\\/[a-zA-Z0-9_.-]+)?$/.test(profile)) {\n throw new Error(\n `Invalid profile name: ${profile}. Use letters, digits, '_', '-', '.', ` +\n `or workspace/member form (one slash).`\n );\n }\n }\n\n /**\n * Resolve which profile to use for this invocation.\n * Precedence: explicit flag → GRAVITY_RAIL_PROFILE → stored current → \"default\".\n */\n resolveProfileName(explicit?: string | null, env?: string): string {\n if (explicit) {\n this.validateProfileName(explicit);\n return explicit;\n }\n const fromEnv = process.env.GRAVITY_RAIL_PROFILE;\n if (fromEnv) {\n this.validateProfileName(fromEnv);\n return fromEnv;\n }\n if (env) {\n return this.getCurrentProfile(env);\n }\n return DEFAULT_PROFILE;\n }\n\n getCurrentProfile(env: string): string {\n const parsed = this.readStore(env);\n if (!parsed) return DEFAULT_PROFILE;\n return parsed.store.currentProfile || DEFAULT_PROFILE;\n }\n\n setCurrentProfile(env: string, profile: string): void {\n this.validateProfileName(profile);\n const store = this.readStore(env)?.store ?? this.emptyStore(profile);\n if (!store.profiles[profile]) {\n throw new Error(\n `Profile \"${profile}\" has no stored credentials for ${env}. ` +\n `Run: gr auth login --name ${profile} (or gr auth import-dev for local tokens)`\n );\n }\n store.currentProfile = profile;\n this.writeStore(env, store);\n }\n\n listProfiles(env: string): ProfileSummary[] {\n const parsed = this.readStore(env);\n if (!parsed) return [];\n const store = parsed.store;\n const current = store.currentProfile || DEFAULT_PROFILE;\n return Object.keys(store.profiles)\n .sort()\n .map((name) => {\n const creds = store.profiles[name]!;\n return {\n name,\n current: name === current,\n hasAccessToken: Boolean(creds.accessToken || creds.refreshToken),\n expiresAt: creds.expiresAt,\n workspaceUuid: creds.workspaceUuid,\n };\n });\n }\n\n private getCredentialsPath(env: string): string {\n // `env` comes from --env and the dev-cloud:NAMESPACE branch accepts an\n // arbitrary namespace, so it could contain `..` or a path separator and\n // escape the cache directory. Validate against an allowlist first (real\n // env names are alphanumerics, `-`, `_`, `.`, and the dev-cloud `:`\n // separator). This rejects path separators outright — including the\n // `foo/bar` case that would otherwise resolve inside .gr-cli but fail\n // later with a cryptic ENOENT from the un-created subdirectory.\n if (!/^[a-zA-Z0-9_:.-]+$/.test(env)) {\n throw new Error(`Invalid environment name: ${env}`);\n }\n // Defense-in-depth: also contain the resolved path before any fs\n // operation, so a future change to buildCredentialsPath can't reintroduce\n // an escape even if the allowlist is loosened.\n const candidate = this.buildCredentialsPath(env);\n // `this.cacheDir` is rooted at os.homedir() (see constructor) — a constant\n // resolve of the user's own dir, no attacker input. FP (CWE-22).\n // bearer:disable javascript_lang_path_traversal\n const resolvedDir = resolve(this.cacheDir);\n // This resolve()/relative() pair IS the containment sanitizer — `env` was\n // already allowlist-validated above. Semgrep's taint tracker flags the\n // resolve() of the env-derived candidate without modelling the guard.\n // bearer:disable javascript_lang_path_traversal\n // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal\n const rel = relative(resolvedDir, resolve(candidate));\n if (rel.startsWith('..') || isAbsolute(rel)) {\n throw new Error(`Invalid environment name: ${env}`);\n }\n return candidate;\n }\n\n private buildCredentialsPath(env: string): string {\n // Isolate credential files by non-default API port so worktrees (shifted\n // ports + separate DBs) do not clobber each other's imported tokens.\n if (this.apiUrl) {\n try {\n const url = new URL(this.apiUrl);\n const port = url.port;\n if (port && port !== '80' && port !== '443') {\n const isCanonicalLocalDev =\n (url.hostname === 'localhost' && port === '5050') ||\n (url.hostname.endsWith('gravityrail.test') && port === '5054');\n if (!isCanonicalLocalDev) {\n // `env` is allowlist-validated in getCredentialsPath before this\n // builder runs, cacheDir is homedir-rooted, and url.port is a\n // parsed URL port — no traversal component. FP (CWE-22).\n // bearer:disable javascript_lang_path_traversal\n // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal\n return join(this.cacheDir, `credentials-${env}-${port}.json`);\n }\n }\n } catch {\n // ignore invalid URLs\n }\n }\n // `env` is allowlist-validated in getCredentialsPath before this builder\n // runs and cacheDir is homedir-rooted — no traversal component. FP (CWE-22).\n // bearer:disable javascript_lang_path_traversal\n // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal\n return join(this.cacheDir, `credentials-${env}.json`);\n }\n\n private emptyStore(currentProfile = DEFAULT_PROFILE): CredentialStoreV2 {\n return { version: 2, currentProfile, profiles: {} };\n }\n\n private isCredentialShape(value: unknown): value is CLICredentials {\n if (!value || typeof value !== 'object') return false;\n const v = value as Record<string, unknown>;\n // Local JSON shape check only — typeof / field presence, not secret comparison.\n // FP (CWE-208): no secret values are compared; short-circuit cannot leak content.\n // A client id alone is a complete OAuth identity: the pinned first-party\n // client is public and has no secret (GRA-7843). Requiring `clientSecret`\n // here would make every such profile fail the shape check and be silently\n // dropped on read — losing the pinned id and the tokens stored beside it.\n // bearer:disable javascript_lang_observable_timing\n const hasOAuth = typeof v.clientId === 'string' && v.clientId.length > 0;\n // bearer:disable javascript_lang_observable_timing\n const hasToken = typeof v.accessToken === 'string' && v.accessToken.length > 0;\n return hasOAuth || hasToken;\n }\n\n /**\n * Read and normalize the on-disk store (migrating legacy formats in memory).\n * `wasLegacy` is true when the file was not already v2 so callers can rewrite once.\n */\n private readStore(env: string): { store: CredentialStoreV2; wasLegacy: boolean } | null {\n const credsPath = this.getCredentialsPath(env);\n if (!existsSync(credsPath)) {\n return null;\n }\n\n try {\n const raw = JSON.parse(readFileSync(credsPath, 'utf-8')) as Record<string, unknown>;\n\n // v2 multi-profile\n if (raw.version === 2 && raw.profiles && typeof raw.profiles === 'object') {\n const profiles: Record<string, CLICredentials> = {};\n for (const [name, value] of Object.entries(raw.profiles as Record<string, unknown>)) {\n if (this.isCredentialShape(value)) {\n profiles[name] = value;\n }\n }\n const current =\n typeof raw.currentProfile === 'string' && profiles[raw.currentProfile]\n ? raw.currentProfile\n : Object.keys(profiles)[0] || DEFAULT_PROFILE;\n return {\n store: { version: 2, currentProfile: current, profiles },\n wasLegacy: false,\n };\n }\n\n // Flat single-credential format (pre-multi-account)\n if (this.isCredentialShape(raw)) {\n return {\n store: {\n version: 2,\n currentProfile: DEFAULT_PROFILE,\n profiles: { [DEFAULT_PROFILE]: raw },\n },\n wasLegacy: true,\n };\n }\n\n // Old org-namespaced format { default: {...} }\n const legacyDefault = (raw as { default?: unknown }).default;\n if (this.isCredentialShape(legacyDefault)) {\n return {\n store: {\n version: 2,\n currentProfile: DEFAULT_PROFILE,\n profiles: { [DEFAULT_PROFILE]: legacyDefault },\n },\n wasLegacy: true,\n };\n }\n\n return null;\n } catch {\n return null;\n }\n }\n\n private ensureCacheDir(): void {\n if (!existsSync(this.cacheDir)) {\n mkdirSync(this.cacheDir, { recursive: true, mode: 0o700 });\n try {\n chmodSync(this.cacheDir, 0o700);\n } catch {\n // Ignore permission errors on Windows\n }\n }\n }\n\n private writeStore(env: string, store: CredentialStoreV2): void {\n this.ensureCacheDir();\n const credsPath = this.getCredentialsPath(env);\n // Atomic replace: write a sibling temp file, chmod, then rename into place.\n // POSIX rename is atomic, so a crash mid-write cannot leave a partial JSON\n // that would wipe every profile on the next parse failure.\n const tmpPath = `${credsPath}.tmp`;\n writeFileSync(tmpPath, JSON.stringify(store, null, 2) + '\\n', { mode: 0o600 });\n try {\n chmodSync(tmpPath, 0o600);\n } catch {\n // Ignore permission errors on Windows\n }\n renameSync(tmpPath, credsPath);\n try {\n chmodSync(credsPath, 0o600);\n } catch {\n // Ignore permission errors on Windows\n }\n }\n\n private filterExpired(creds: CLICredentials): CLICredentials | null {\n if (creds.expiresAt) {\n const expiresAt = new Date(creds.expiresAt);\n if (expiresAt <= new Date()) {\n if (creds.refreshToken) {\n return creds;\n }\n return null;\n }\n }\n return creds;\n }\n\n /**\n * Load credentials for an environment + profile.\n * When `profile` is omitted, uses the resolved current profile for `env`.\n */\n load(env: string, profile?: string): CLICredentials | null {\n const parsed = this.readStore(env);\n if (!parsed) return null;\n\n const { store, wasLegacy } = parsed;\n const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;\n if (profile) this.validateProfileName(profile);\n const creds = store.profiles[name];\n if (!creds) return null;\n\n // Persist migration from flat/org formats so subsequent multi-profile ops work.\n // wasLegacy comes from the single parse in readStore — no second file read.\n if (wasLegacy) {\n this.writeStore(env, store);\n }\n\n return this.filterExpired(creds);\n }\n\n /**\n * Save credentials for an environment + profile.\n * When `profile` is omitted, uses the current profile (or \"default\").\n * Sets currentProfile to the saved profile when the store was empty.\n */\n save(env: string, creds: CLICredentials, profile?: string): void {\n if (profile) this.validateProfileName(profile);\n const store = this.readStore(env)?.store ?? this.emptyStore();\n const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;\n if (profile) {\n // Explicit profile on save also becomes current when first created, or when\n // the caller is logging into a named account.\n store.profiles[name] = creds;\n if (!store.profiles[store.currentProfile] || Object.keys(store.profiles).length === 1) {\n store.currentProfile = name;\n }\n } else {\n store.profiles[name] = creds;\n if (!store.currentProfile) {\n store.currentProfile = name;\n }\n }\n this.writeStore(env, store);\n }\n\n /**\n * Bulk-save many profiles in one write (used by dev-realms import).\n * Does not change currentProfile unless the store was empty — then uses\n * `preferCurrent` if provided and present, else `superadmin`, else first.\n */\n saveMany(env: string, profiles: Record<string, CLICredentials>, preferCurrent?: string): number {\n const names = Object.keys(profiles);\n for (const name of names) {\n this.validateProfileName(name);\n }\n const store = this.readStore(env)?.store ?? this.emptyStore();\n const wasEmpty = Object.keys(store.profiles).length === 0;\n for (const [name, creds] of Object.entries(profiles)) {\n store.profiles[name] = creds;\n }\n if (wasEmpty) {\n if (preferCurrent && store.profiles[preferCurrent]) {\n store.currentProfile = preferCurrent;\n } else if (store.profiles.superadmin) {\n store.currentProfile = 'superadmin';\n } else {\n store.currentProfile = names.sort()[0] ?? DEFAULT_PROFILE;\n }\n }\n this.writeStore(env, store);\n return names.length;\n }\n\n /**\n * Clear one profile (default: current profile). Deletes the file when no profiles remain.\n * Pass a profile name to clear a specific account without touching others.\n */\n clear(env: string, profile?: string): void {\n const parsed = this.readStore(env);\n if (!parsed) return;\n const store = parsed.store;\n\n const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;\n if (profile) this.validateProfileName(profile);\n delete store.profiles[name];\n\n if (Object.keys(store.profiles).length === 0) {\n const credsPath = this.getCredentialsPath(env);\n if (existsSync(credsPath)) {\n try {\n unlinkSync(credsPath);\n } catch {\n // Ignore\n }\n }\n return;\n }\n\n if (store.currentProfile === name) {\n store.currentProfile = Object.keys(store.profiles).sort()[0]!;\n }\n this.writeStore(env, store);\n }\n\n /**\n * Read a cached org-scoped SSO session for an org, if present and unexpired.\n * Independent of the account token's expiry — a valid org session is reusable\n * even while the account token is being refreshed.\n */\n getOrgSession(env: string, profile: string | undefined, orgUuid: string): string | undefined {\n const parsed = this.readStore(env);\n if (!parsed) return undefined;\n const name = profile ?? parsed.store.currentProfile ?? DEFAULT_PROFILE;\n if (profile) this.validateProfileName(profile);\n const entry = parsed.store.profiles[name]?.orgSessions?.[orgUuid];\n if (!entry?.session) return undefined;\n if (entry.expiresAt && new Date(entry.expiresAt) <= new Date()) return undefined;\n return entry.session;\n }\n\n /**\n * Cache an org-scoped SSO session under an existing profile, keyed by org UUID.\n * No-op when the profile does not exist — org sessions only attach to a\n * logged-in account profile (never create a token-less profile row).\n */\n setOrgSession(\n env: string,\n profile: string | undefined,\n orgUuid: string,\n session: string,\n expiresAt?: string\n ): void {\n const parsed = this.readStore(env);\n if (!parsed) return;\n const store = parsed.store;\n const name = profile ?? store.currentProfile ?? DEFAULT_PROFILE;\n if (profile) this.validateProfileName(profile);\n const creds = store.profiles[name];\n if (!creds) return;\n creds.orgSessions = {\n ...(creds.orgSessions ?? {}),\n [orgUuid]: { session, ...(expiresAt ? { expiresAt } : {}) },\n };\n this.writeStore(env, store);\n }\n\n getCacheDir(): string {\n return this.cacheDir;\n }\n\n /** Absolute path of the credentials file for `env` (for docs / `auth list`). */\n getCredentialsFilePath(env: string): string {\n return this.getCredentialsPath(env);\n }\n}\n","/**\n * CLI terminal UI utilities\n *\n * Provides interactive terminal UI components for CLI tools\n */\n\nimport * as readline from 'readline';\n\nexport interface SelectOption<T> {\n item: T;\n label: string;\n detail?: string;\n}\n\nexport interface SelectFromListOptions<T> {\n title?: string;\n labelFn?: (item: T) => string;\n detailFn?: (item: T) => string | null;\n env?: string;\n emptyMessage?: string;\n allowNew?: boolean;\n newLabel?: string;\n}\n\nexport interface SelectResult<T> {\n item: T | null;\n isNew: boolean;\n}\n\nconst NEW_ITEM_MARKER = Symbol('NEW_ITEM');\n\n/**\n * Interactive list picker using arrow keys\n */\nexport async function selectFromList<T>(\n items: T[],\n options: SelectFromListOptions<T> = {}\n): Promise<SelectResult<T>> {\n const {\n title = 'Select an item',\n labelFn = (item: T) => String((item as any).name || (item as any).title || item),\n detailFn = null,\n env = null,\n emptyMessage = 'No items found.',\n allowNew = false,\n newLabel = 'Create new...',\n } = options;\n\n if ((!items || items.length === 0) && !allowNew) {\n console.log(`\\n ${emptyMessage}`);\n return { item: null, isNew: false };\n }\n\n // Prepend \"Create new\" option if allowed\n const displayItems: (T | typeof NEW_ITEM_MARKER)[] = allowNew\n ? [NEW_ITEM_MARKER, ...items]\n : items;\n\n return new Promise((resolve) => {\n let selectedIndex = allowNew ? 1 : 0;\n let filterText = '';\n\n const getFilteredItems = (): (T | typeof NEW_ITEM_MARKER)[] => {\n if (!filterText) return displayItems;\n const lower = filterText.toLowerCase();\n return displayItems.filter((item) => {\n if (item === NEW_ITEM_MARKER) return true;\n const label = labelFn(item);\n return label.toLowerCase().includes(lower);\n });\n };\n\n const render = (): void => {\n const filtered = getFilteredItems();\n\n // Clear screen and move cursor to top\n process.stdout.write('\\x1B[2J\\x1B[H');\n\n console.log('='.repeat(60));\n console.log(`${title} (↑/↓ to move, Enter to select, Esc to cancel)`);\n console.log('='.repeat(60));\n if (env) {\n console.log(`Environment: ${env}`);\n }\n console.log(`Filter: ${filterText}${filterText ? '' : '(type to filter)'}`);\n console.log('');\n\n if (filtered.length === 0) {\n console.log(` ${emptyMessage}`);\n console.log('');\n return;\n }\n\n // Show up to 15 items around the selected one\n const maxVisible = 15;\n let startIdx = Math.max(0, selectedIndex - Math.floor(maxVisible / 2));\n let endIdx = Math.min(filtered.length, startIdx + maxVisible);\n if (endIdx - startIdx < maxVisible && startIdx > 0) {\n startIdx = Math.max(0, endIdx - maxVisible);\n }\n\n if (startIdx > 0) {\n console.log(` ... ${startIdx} more above ...`);\n }\n\n for (let i = startIdx; i < endIdx; i++) {\n const item = filtered[i]!;\n const prefix = i === selectedIndex ? '> ' : ' ';\n\n let line: string;\n if (item === NEW_ITEM_MARKER) {\n line = `\\x1B[32m${newLabel}\\x1B[0m`; // Green color for \"Create new\"\n } else {\n const typedItem = item as T;\n line = labelFn(typedItem);\n if (detailFn) {\n const detail = detailFn(typedItem);\n if (detail) {\n line = `${line} \\x1B[90m${detail}\\x1B[0m`;\n }\n }\n }\n\n if (i === selectedIndex) {\n console.log(`\\x1B[7m${prefix}${line}\\x1B[0m`);\n } else {\n console.log(`${prefix}${line}`);\n }\n }\n\n if (endIdx < filtered.length) {\n console.log(` ... ${filtered.length - endIdx} more below ...`);\n }\n\n console.log('');\n console.log(` ${filtered.length} item(s)`);\n };\n\n // Set up raw mode for arrow key input\n readline.emitKeypressEvents(process.stdin);\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(true);\n }\n\n render();\n\n const handleKeypress = (str: string | undefined, key: readline.Key): void => {\n const filtered = getFilteredItems();\n\n if (key.name === 'up') {\n selectedIndex = Math.max(0, selectedIndex - 1);\n render();\n } else if (key.name === 'down') {\n selectedIndex = Math.min(filtered.length - 1, selectedIndex + 1);\n render();\n } else if (key.name === 'return') {\n cleanup();\n if (filtered.length > 0 && selectedIndex < filtered.length) {\n const selected = filtered[selectedIndex]!;\n if (selected === NEW_ITEM_MARKER) {\n resolve({ item: null, isNew: true });\n } else {\n resolve({ item: selected as T, isNew: false });\n }\n } else {\n resolve({ item: null, isNew: false });\n }\n } else if (key.name === 'escape' || (key.ctrl && key.name === 'c')) {\n cleanup();\n resolve({ item: null, isNew: false });\n } else if (key.name === 'backspace') {\n filterText = filterText.slice(0, -1);\n selectedIndex = allowNew ? 0 : 0;\n render();\n } else if (str && str.length === 1 && !key.ctrl && !key.meta) {\n filterText += str;\n selectedIndex = allowNew ? 0 : 0;\n render();\n }\n };\n\n const cleanup = (): void => {\n process.stdin.removeListener('keypress', handleKeypress);\n if (process.stdin.isTTY) {\n process.stdin.setRawMode(false);\n }\n process.stdin.pause();\n process.stdout.write('\\x1B[2J\\x1B[H');\n };\n\n process.stdin.on('keypress', handleKeypress);\n process.stdin.resume();\n });\n}\n\n/**\n * Print a table of items\n */\nexport function printTable<T>(\n items: T[],\n columns: Array<{\n header: string;\n getValue: (item: T) => string;\n maxWidth?: number;\n }>,\n options: { title?: string; env?: string } = {}\n): void {\n if (!items || items.length === 0) {\n console.log('\\nNo items found.');\n return;\n }\n\n if (options.title) {\n console.log(`\\n=== ${options.title} ===`);\n }\n if (options.env) {\n console.log(`Environment: ${options.env}`);\n }\n console.log('');\n\n // Calculate column widths\n const widths = columns.map((col) => {\n const maxContent = Math.max(...items.map((item) => String(col.getValue(item) ?? '').length));\n const width = Math.max(col.header.length, maxContent);\n return col.maxWidth ? Math.min(width, col.maxWidth) : width;\n });\n\n // Print header\n const headerLine = columns.map((col, i) => col.header.padEnd(widths[i]!)).join(' ');\n console.log(headerLine);\n console.log(widths.map((w) => '-'.repeat(w)).join(' '));\n\n // Print rows\n for (const item of items) {\n const row = columns\n .map((col, i) => {\n const value = String(col.getValue(item) ?? '');\n const width = widths[i]!;\n return value.slice(0, width).padEnd(width);\n })\n .join(' ');\n console.log(row);\n }\n\n console.log('');\n console.log(`Total: ${items.length} item(s)`);\n}\n\n/**\n * Format a timestamp for display\n */\nexport function formatTimestamp(isoString: string | null | undefined): string {\n if (!isoString) return '';\n const date = new Date(isoString);\n const now = new Date();\n const diff = now.getTime() - date.getTime();\n\n if (diff < 60000) return 'just now';\n if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;\n if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;\n if (diff < 604800000) return `${Math.floor(diff / 86400000)}d ago`;\n\n return date.toLocaleDateString();\n}\n\n/**\n * Wrap text to a maximum width\n */\nexport function wrapText(text: string, width: number = 70): string[] {\n const words = text.split(' ');\n const lines: string[] = [];\n let currentLine = '';\n\n for (const word of words) {\n if (currentLine.length + word.length + 1 <= width) {\n currentLine += (currentLine ? ' ' : '') + word;\n } else {\n if (currentLine) lines.push(currentLine);\n currentLine = word;\n }\n }\n if (currentLine) lines.push(currentLine);\n return lines;\n}\n\n/**\n * Print a chat message with formatting\n */\nexport function printMessage(\n content: string,\n role: 'user' | 'assistant' | 'system',\n indent: string = ' '\n): void {\n const roleColors: Record<string, string> = {\n user: '\\x1B[34m', // Blue\n assistant: '\\x1B[32m', // Green\n system: '\\x1B[90m', // Gray\n };\n const resetColor = '\\x1B[0m';\n const color = roleColors[role] || resetColor;\n\n const label = role === 'user' ? 'You' : role === 'assistant' ? 'Assistant' : 'System';\n console.log(`${indent}${color}[${label}]${resetColor}`);\n\n const lines = content.split('\\n');\n for (const line of lines) {\n const wrapped = wrapText(line, 70);\n for (const wrappedLine of wrapped) {\n console.log(`${indent} ${wrappedLine}`);\n }\n }\n console.log('');\n}\n\n/**\n * Clear the terminal screen\n */\nexport function clearScreen(): void {\n process.stdout.write('\\x1B[2J\\x1B[H');\n}\n","/**\n * AG-UI Client for CLI chat interactions\n *\n * Implements the AG-UI (Agent-User Interaction) protocol for streaming\n * chat responses. See: https://docs.ag-ui.com/concepts/events\n */\n\nimport type { ConciergeAppContextPayload } from '../lib/cli-client-context';\n\n/**\n * AG-UI event types\n */\nexport type AGUIEventType =\n | 'RUN_STARTED'\n | 'RUN_FINISHED'\n | 'RUN_ERROR'\n | 'TEXT_MESSAGE_START'\n | 'TEXT_MESSAGE_CONTENT'\n | 'TEXT_MESSAGE_END'\n | 'TOOL_CALL_START'\n | 'TOOL_CALL_ARGS'\n | 'TOOL_CALL_END'\n | 'TOOL_CALL_RESULT'\n | 'STATE_SNAPSHOT'\n | 'STATE_DELTA'\n // Lowercase variants (some backends use these)\n | 'run_started'\n | 'run_finished'\n | 'run_error'\n | 'text_message_start'\n | 'text_message_content'\n | 'text_message_end'\n | 'tool_call_start'\n | 'tool_call_args'\n | 'tool_call_end'\n | 'tool_call_result'\n | 'state_snapshot'\n | 'state_delta'\n | 'done';\n\nexport interface AGUIEvent {\n type: AGUIEventType;\n messageId?: string;\n role?: string;\n delta?: string;\n content?: string;\n message?: string;\n code?: string;\n toolCallId?: string;\n toolCallName?: string;\n result?: any;\n snapshot?: any;\n operations?: any[];\n [key: string]: any;\n}\n\nexport interface ChatMessage {\n role: 'user' | 'assistant' | 'system';\n content: string;\n}\n\nexport interface AGUIClientOptions {\n baseUrl: string;\n workspaceUuid?: string;\n chatId?: number | string; // Numeric chat id (documented) or chat uuid (accepted)\n /** Full URL override — when provided, baseUrl/workspaceUuid/chatId are ignored for POST */\n endpointUrl?: string;\n accessToken: string;\n debug?: boolean;\n /** Model field sent in request body (default: 'manager'). Set to undefined to omit. */\n model?: string;\n /** Concierge / client context forwarded to the API (e.g. CLI surface hints). */\n appContext?: ConciergeAppContextPayload;\n}\n\n/**\n * AG-UI Client for streaming chat responses\n */\nexport class AGUIClient {\n private endpointUrl: string;\n private baseUrl: string;\n private workspaceUuid: string;\n private chatId: number | string;\n private accessToken: string;\n private debug: boolean;\n private model: string | undefined;\n private appContext: ConciergeAppContextPayload | undefined;\n\n constructor(options: AGUIClientOptions) {\n this.baseUrl = options.baseUrl;\n this.workspaceUuid = options.workspaceUuid ?? '';\n this.chatId = options.chatId ?? 0;\n if (!options.endpointUrl && (!options.workspaceUuid || options.chatId === undefined)) {\n throw new Error('AGUIClient requires workspaceUuid and chatId, or endpointUrl');\n }\n this.endpointUrl =\n options.endpointUrl ??\n `${options.baseUrl}/api/v2/w/${options.workspaceUuid}/web-chat/${options.chatId}/chat/agui`;\n this.accessToken = options.accessToken;\n this.debug = options.debug ?? false;\n this.model = 'model' in options ? options.model : 'manager';\n this.appContext = options.appContext;\n }\n\n private chatRouteUrl(suffix: string): string | null {\n // Numeric chatId: treat 0 / negative as \"no chat\" (<= 0 rather than a\n // falsy check so a deliberate sentinel of 0 doesn't mean \"valid chat\").\n // String chatId is a uuid; empty string means \"no chat\".\n const noChat = typeof this.chatId === 'number' ? this.chatId <= 0 : this.chatId.length === 0;\n if (!this.workspaceUuid || noChat) return null;\n return `${this.baseUrl}/api/v2/w/${this.workspaceUuid}/chats/${this.chatId}/${suffix}`;\n }\n\n /**\n * The operator's own live inline-connect link for this chat, or null when\n * none is live. The `inline_connect` prompt carries no URL on purpose (it is\n * a bearer, and the tool result is persisted and model-visible), so the\n * terminal fetches it here when the operator chooses to connect.\n */\n async fetchInlineConnectLink(\n appSlug: string\n ): Promise<{ connectUrl: string; expiresInMinutes: number } | null> {\n const url = this.chatRouteUrl(`inline-connect/${encodeURIComponent(appSlug)}/link`);\n if (!url) return null;\n try {\n const res = await fetch(url, { headers: { Authorization: `Bearer ${this.accessToken}` } });\n if (!res.ok) return null;\n const body = (await res.json()) as { connectUrl?: unknown; expiresInMinutes?: unknown };\n if (typeof body.connectUrl !== 'string') return null;\n return {\n connectUrl: body.connectUrl,\n expiresInMinutes: typeof body.expiresInMinutes === 'number' ? body.expiresInMinutes : 0,\n };\n } catch {\n return null;\n }\n }\n\n private inferenceStatusUrl(): string | null {\n // Numeric chatId: treat 0 / negative as \"no chat\" (<= 0 rather than a\n // falsy check so a deliberate sentinel of 0 doesn't mean \"valid chat\").\n // String chatId is a uuid; empty string means \"no chat\".\n const noChat = typeof this.chatId === 'number' ? this.chatId <= 0 : this.chatId.length === 0;\n if (!this.workspaceUuid || noChat) return null;\n return `${this.baseUrl}/api/v2/w/${this.workspaceUuid}/chats/${this.chatId}/inference/status`;\n }\n\n private async waitForInferenceInactive(maxWaitMs = 60_000): Promise<boolean> {\n const url = this.inferenceStatusUrl();\n if (!url) return false;\n const deadline = Date.now() + maxWaitMs;\n while (Date.now() < deadline) {\n try {\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${this.accessToken}` },\n });\n if (res.ok) {\n const body = (await res.json()) as { active?: boolean };\n if (!body.active) return true;\n }\n } catch (err) {\n // Transient network errors (DNS blips, connection resets) shouldn't\n // crash the 409-retry loop. Log under --debug and keep polling\n // until the deadline; the caller will fall back to the original\n // 409 error if we can't confirm inactive in time.\n if (this.debug) {\n console.log(\n `[DEBUG] waitForInferenceInactive fetch failed: ${\n err instanceof Error ? err.message : String(err)\n }`\n );\n }\n }\n await new Promise((r) => setTimeout(r, 2000));\n }\n return false;\n }\n\n /**\n * Send messages and stream the response\n */\n async sendMessage(\n messages: ChatMessage[],\n onEvent: (event: AGUIEvent) => void,\n options?: AguiStreamOptions\n ): Promise<void> {\n return this.streamRequest({ messages }, onEvent, options);\n }\n\n /**\n * Stream an AG-UI request (initial messages or HITL resume).\n */\n async streamRequest(\n request: AguiRequestBody,\n onEvent: (event: AGUIEvent) => void,\n options?: AguiStreamOptions\n ): Promise<void> {\n const body = JSON.stringify({\n ...(this.model ? { model: this.model } : {}),\n messages: request.messages ?? [],\n ...(this.appContext ? { appContext: this.appContext } : {}),\n ...(request.resumeValue !== undefined ? { resumeValue: request.resumeValue } : {}),\n ...(request.resumeInterruptId ? { resumeInterruptId: request.resumeInterruptId } : {}),\n ...(request.resume !== undefined ? { resume: request.resume } : {}),\n ...(options?.autoApprove ? { context: { autoApprove: true } } : {}),\n });\n\n if (this.debug) {\n console.log(`\\n[DEBUG] POST ${this.endpointUrl}`);\n console.log(\n `[DEBUG] Body: ${request.resumeValue !== undefined ? 'resume' : `${(request.messages ?? []).length} message(s)`}`\n );\n }\n\n await this.postStream(body, onEvent, options);\n }\n\n private async postStream(\n body: string,\n onEvent: (event: AGUIEvent) => void,\n options?: AguiStreamOptions,\n conflictRetries = 0\n ): Promise<void> {\n const response = await fetch(this.endpointUrl, {\n method: 'POST',\n headers: {\n Authorization: `Bearer ${this.accessToken}`,\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream',\n },\n body,\n });\n\n if (this.debug) {\n console.log(`[DEBUG] Response status: ${response.status}`);\n }\n\n if (response.status === 409 && conflictRetries < 3) {\n const ready = await this.waitForInferenceInactive();\n if (ready) {\n await this.postStream(body, onEvent, options, conflictRetries + 1);\n return;\n }\n }\n\n if (!response.ok) {\n const errorText = await response.text().catch(() => '');\n if (this.debug) {\n console.log(`[DEBUG] Error response: ${errorText}`);\n }\n throw new Error(\n `Chat request failed: ${response.status}${errorText ? `: ${errorText.slice(0, 200)}` : ''}`\n );\n }\n\n await parseSseStream(response.body!, onEvent);\n }\n\n /**\n * Subscribe to the chat's persistent AG-UI event feed and surface\n * server-initiated turns that arrive while we're idle between our own turns.\n *\n * The feed at ``/chats/{chatId}/events`` replays every AG-UI event for the\n * chat across all turns (assistant events plus user-message echoes). It never\n * self-terminates — it ends only on disconnect. On reconnect we send\n * ``Last-Event-ID`` so the server replays the gap. The loop reconnects with\n * capped exponential backoff and resolves quietly once ``signal`` aborts;\n * it never throws except when required args are missing (callers gate on\n * availability, so that path is a programming error).\n */\n async subscribeToFeed(opts: {\n onEvent: (event: AGUIEvent) => void;\n signal: AbortSignal;\n onDebug?: (msg: string) => void;\n }): Promise<void> {\n const { onEvent, signal, onDebug } = opts;\n\n // Guard even though callers gate on availability: the feed URL needs a\n // real workspace + chat. Mirror inferenceStatusUrl's \"no chat\" logic.\n const noChat = typeof this.chatId === 'number' ? this.chatId <= 0 : this.chatId.length === 0;\n if (!this.baseUrl || !this.workspaceUuid || noChat) {\n throw new Error('subscribeToFeed requires baseUrl, workspaceUuid, and a chatId');\n }\n const feedUrl = `${this.baseUrl}/api/v2/w/${this.workspaceUuid}/chats/${this.chatId}/events`;\n\n let lastEventId: string | undefined;\n let backoffMs = 1000;\n const maxBackoffMs = 15_000;\n // A connection that survived this long was healthy even if the chat\n // stayed silent the whole time — keepalive comment frames are\n // stripped by the parser, so they can never count as received data.\n const healthyConnectionMs = 30_000;\n\n while (!signal.aborted) {\n let receivedData = false;\n const connectedAt = Date.now();\n try {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.accessToken}`,\n Accept: 'text/event-stream',\n };\n if (lastEventId !== undefined) headers['Last-Event-ID'] = lastEventId;\n\n const response = await fetch(feedUrl, { method: 'GET', headers, signal });\n if (!response.ok || !response.body) {\n throw new Error(`feed request failed: ${response.status}`);\n }\n onDebug?.(`feed connected${lastEventId ? ` (resume ${lastEventId})` : ''}`);\n\n await parseSseStream(\n response.body,\n (event, eventName) => {\n receivedData = true;\n // Retention lapsed — the server couldn't replay the gap.\n // Detected via the SSE ``event: feed_reset`` name (the\n // payload type is a belt-and-suspenders duplicate). For\n // v1 we just note it; some events may have been missed.\n if (\n eventName === 'feed_reset' ||\n (typeof event.type === 'string' && event.type.toUpperCase() === 'FEED_RESET')\n ) {\n onDebug?.('feed reset — server could not replay missed events');\n return;\n }\n onEvent(event);\n },\n (id) => {\n receivedData = true;\n lastEventId = id;\n }\n );\n // Stream ended (disconnect) — fall through to reconnect with backoff.\n } catch (err) {\n if (signal.aborted) return;\n onDebug?.(`feed error: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n if (signal.aborted) return;\n\n // Reset only on evidence of health (delivered frames, or a\n // long-lived idle connection); otherwise wait out the current\n // backoff and grow it for the NEXT attempt (1s, 2s, 4s … cap).\n // Healthy connections must never escalate — an earlier version\n // doubled unconditionally after the reset, so steady\n // reconnects crept toward the cap.\n const wasHealthy = receivedData || Date.now() - connectedAt >= healthyConnectionMs;\n if (wasHealthy) backoffMs = 1000;\n const completed = await abortableDelay(backoffMs, signal);\n if (!completed) return; // aborted while waiting\n if (!wasHealthy) backoffMs = Math.min(backoffMs * 2, maxBackoffMs);\n }\n }\n}\n\n/**\n * Parse a Server-Sent Events stream body into AG-UI events.\n *\n * Shared by ``AGUIClient.postStream`` (per-turn POST) and ``subscribeToFeed``\n * (persistent GET feed). Surfaces ``data:`` frames as parsed events, ``id:``\n * frames via ``onId`` (feed reconnect uses the last id as ``Last-Event-ID``),\n * and ignores SSE comment frames (``: turn-done``, keepalives). ``onId`` is\n * optional — the POST path has no use for entry ids.\n */\nexport async function parseSseStream(\n body: ReadableStream<Uint8Array>,\n onEvent: (event: AGUIEvent, eventName?: string) => void,\n onId?: (id: string) => void\n): Promise<void> {\n const reader = body.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\n // Per SSE, an ``event:`` line names the dispatch it precedes (the\n // server's feed_reset signal uses this). Cleared once consumed or at\n // the blank-line frame boundary.\n let pendingEventName: string | undefined;\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() ?? '';\n\n for (const rawLine of lines) {\n // Tolerate CRLF endings without changing the \\n-split behavior.\n const line = rawLine.endsWith('\\r') ? rawLine.slice(0, -1) : rawLine;\n\n // Blank line = SSE frame boundary; the pending event name only\n // applies within its own frame.\n if (line === '') {\n pendingEventName = undefined;\n continue;\n }\n\n // SSE comment frame (turn boundaries, keepalives) — ignore.\n if (line.startsWith(':')) continue;\n\n if (line.startsWith('event:')) {\n const nameValue = line.slice(6);\n pendingEventName = nameValue.startsWith(' ') ? nameValue.slice(1) : nameValue;\n continue;\n }\n\n if (line.startsWith('id:')) {\n const idValue = line.slice(3);\n // Per SSE, a single leading space after the colon is stripped.\n onId?.(idValue.startsWith(' ') ? idValue.slice(1) : idValue);\n continue;\n }\n\n if (line.startsWith('data: ')) {\n const data = line.slice(6);\n if (data === '[DONE]') {\n onEvent({ type: 'done' }, pendingEventName);\n pendingEventName = undefined;\n continue;\n }\n try {\n onEvent(JSON.parse(data) as AGUIEvent, pendingEventName);\n } catch {\n // Ignore parse errors for malformed events.\n }\n pendingEventName = undefined;\n }\n }\n }\n}\n\n/**\n * Resolve after ``ms`` unless ``signal`` aborts first. Returns true if the delay\n * completed, false if it was aborted — lets a reconnect loop exit cleanly.\n */\nfunction abortableDelay(ms: number, signal: AbortSignal): Promise<boolean> {\n return new Promise<boolean>((resolve) => {\n if (signal.aborted) {\n resolve(false);\n return;\n }\n const onAbort = (): void => {\n clearTimeout(timer);\n resolve(false);\n };\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve(true);\n }, ms);\n signal.addEventListener('abort', onAbort, { once: true });\n });\n}\n\nexport interface AguiStreamOptions {\n autoApprove?: boolean;\n}\n\nexport interface AguiRequestBody {\n messages?: ChatMessage[];\n /** Concierge AGUI resume fields. */\n resumeValue?: string;\n resumeInterruptId?: string;\n /** Workspace-chat HITL resume (ChatCompletionRequest.resume shape:\n * { decisions: [{type: 'approve'|'edit'|'reject', ...}], interruptId }). */\n resume?: unknown;\n}\n\n/**\n * Helper to check if an event is a text content event\n */\nexport function isTextContentEvent(event: AGUIEvent): boolean {\n const type = event.type.toLowerCase();\n return type === 'text_message_content';\n}\n\n/**\n * Helper to check if an event is an error event\n */\nexport function isErrorEvent(event: AGUIEvent): boolean {\n const type = event.type.toLowerCase();\n return type === 'run_error';\n}\n\n/**\n * Helper to extract text delta from an event\n */\nexport function getTextDelta(event: AGUIEvent): string | null {\n if (isTextContentEvent(event) && event.delta) {\n return event.delta;\n }\n return null;\n}\n\n/**\n * Helper to extract error message from an event\n */\nexport function getErrorMessage(event: AGUIEvent): string | null {\n if (isErrorEvent(event)) {\n return event.message || event.code || 'Unknown error';\n }\n return null;\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","dirname","readFileSync","existsSync","join","resolve","resolve","dirname","existsSync","mkdirSync","resolve","resolve"]}