@gravity-rail/cli 0.36.0 → 0.36.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{account-verify-phone-5DY6ETMV.js → account-verify-phone-KEYD64YM.js} +3 -3
- package/dist/{chunk-KHUZAYUS.js → chunk-CQVOVKKR.js} +22 -69
- package/dist/chunk-CQVOVKKR.js.map +1 -0
- package/dist/{chunk-IGZVMTG5.js → chunk-FSY2BM4J.js} +26 -1
- package/dist/chunk-FSY2BM4J.js.map +1 -0
- package/dist/cli-utils.d.ts +15 -1
- package/dist/cli-utils.js +3 -1
- package/dist/gr.js +133 -66
- package/dist/gr.js.map +1 -1
- package/dist/lib.js +64 -17
- package/dist/lib.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-IGZVMTG5.js.map +0 -1
- package/dist/chunk-KHUZAYUS.js.map +0 -1
- /package/dist/{account-verify-phone-5DY6ETMV.js.map → account-verify-phone-KEYD64YM.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli-utils/config.ts","../src/cli-utils/auth.ts","../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\nfunction 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 { 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\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 (${tokenData.expires_in}), 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 },\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 // `refreshAccessToken` clears the whole profile when a refresh fails.\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 * Refresh an access token using a stored refresh token.\n * Returns updated credentials on success, null on failure.\n * Clears only the named profile on failure (other accounts stay intact).\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 // 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 (!creds.refreshToken || !creds.clientId) {\n stderr(' Cannot refresh: missing credentials (re-login required)');\n return null;\n }\n\n const profileName = credentialsManager.resolveProfileName(profile, env);\n const tokenUrl = `${envConfig.apiUrl}/api/v2/oauth/token`;\n const auth = tokenRequestAuth(creds.clientId, creds.clientSecret);\n\n try {\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n ...auth.headers,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: creds.refreshToken,\n ...auth.params,\n }),\n });\n\n if (!response.ok) {\n const body = await response.text();\n const isExpiredRefresh = isOAuthError(body, 'invalid_grant');\n if (isExpiredRefresh) {\n stderr(' Refresh token expired, re-login required');\n } else {\n stderr(` Token refresh failed (${response.status}), clearing credentials`);\n }\n credentialsManager.clear(env, profileName);\n return null;\n }\n\n const tokenData = (await response.json()) as Record<string, unknown>;\n const { accessToken, refreshToken, expiresIn } = validateTokenResponse(tokenData);\n\n const updated: CLICredentials = {\n ...creds,\n accessToken,\n refreshToken: refreshToken ?? creds.refreshToken,\n expiresAt: new Date(Date.now() + expiresIn * 1000).toISOString(),\n };\n\n credentialsManager.save(env, updated, profileName);\n return updated;\n } catch (err) {\n stderr(` Token refresh error: ${err instanceof Error ? err.message : 'unknown'}`);\n credentialsManager.clear(env, profileName);\n return null;\n }\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 * 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":["readFileSync","existsSync","join","resolve","resolve","resolve","resolve"]}
|
package/dist/cli-utils.d.ts
CHANGED
|
@@ -17,6 +17,12 @@ interface AuthResult {
|
|
|
17
17
|
accessToken: string;
|
|
18
18
|
credentials?: CLICredentials;
|
|
19
19
|
}
|
|
20
|
+
interface NonInteractiveAuthOptions {
|
|
21
|
+
env: string;
|
|
22
|
+
envConfig: EnvironmentConfig;
|
|
23
|
+
credentialsManager: CredentialsManager;
|
|
24
|
+
profile?: string;
|
|
25
|
+
}
|
|
20
26
|
/**
|
|
21
27
|
* Generate PKCE code verifier and challenge
|
|
22
28
|
*/
|
|
@@ -122,6 +128,14 @@ declare function performOAuthFlow(options: OAuthFlowOptions): Promise<AuthResult
|
|
|
122
128
|
* Clears only the named profile on failure (other accounts stay intact).
|
|
123
129
|
*/
|
|
124
130
|
declare function refreshAccessToken(envConfig: EnvironmentConfig, creds: CLICredentials, credentialsManager: CredentialsManager, env: string, profile?: string): Promise<CLICredentials | null>;
|
|
131
|
+
/**
|
|
132
|
+
* Resolve credentials that do not require a browser.
|
|
133
|
+
*
|
|
134
|
+
* Precedence: process API key, then the selected profile's valid access token,
|
|
135
|
+
* then that profile's refresh token. A null result leaves the caller to decide
|
|
136
|
+
* whether interactive OAuth is appropriate for its command.
|
|
137
|
+
*/
|
|
138
|
+
declare function resolveNonInteractiveAuth(options: NonInteractiveAuthOptions): Promise<AuthResult | null>;
|
|
125
139
|
|
|
126
140
|
interface SelectFromListOptions<T> {
|
|
127
141
|
title?: string;
|
|
@@ -303,4 +317,4 @@ declare function getTextDelta(event: AGUIEvent): string | null;
|
|
|
303
317
|
*/
|
|
304
318
|
declare function getErrorMessage(event: AGUIEvent): string | null;
|
|
305
319
|
|
|
306
|
-
export { type ChatMessage as AGUIChatMessage, AGUIClient, type AGUIClientOptions, type AGUIEvent, type AGUIEventType, type AuthResult, CLICredentials, CredentialsManager, EnvironmentConfig, FIRST_PARTY_CLIENT_METADATA_FIELD, type OAuthFlowOptions, type SelectFromListOptions, type SelectResult, clearScreen, fetchFirstPartyClientId, formatTimestamp, generatePKCE, getErrorMessage, getTextDelta, isErrorEvent, isTextContentEvent, openBrowser, parseSseStream, performOAuthFlow, printMessage, printTable, refreshAccessToken, resolveOAuthClient, selectFromList, startCallbackServer, wrapText };
|
|
320
|
+
export { type ChatMessage as AGUIChatMessage, AGUIClient, type AGUIClientOptions, type AGUIEvent, type AGUIEventType, type AuthResult, CLICredentials, CredentialsManager, EnvironmentConfig, FIRST_PARTY_CLIENT_METADATA_FIELD, type NonInteractiveAuthOptions, type OAuthFlowOptions, type SelectFromListOptions, type SelectResult, clearScreen, fetchFirstPartyClientId, formatTimestamp, generatePKCE, getErrorMessage, getTextDelta, isErrorEvent, isTextContentEvent, openBrowser, parseSseStream, performOAuthFlow, printMessage, printTable, refreshAccessToken, resolveNonInteractiveAuth, resolveOAuthClient, selectFromList, startCallbackServer, wrapText };
|
package/dist/cli-utils.js
CHANGED
|
@@ -25,11 +25,12 @@ import {
|
|
|
25
25
|
printMessage,
|
|
26
26
|
printTable,
|
|
27
27
|
refreshAccessToken,
|
|
28
|
+
resolveNonInteractiveAuth,
|
|
28
29
|
resolveOAuthClient,
|
|
29
30
|
selectFromList,
|
|
30
31
|
startCallbackServer,
|
|
31
32
|
wrapText
|
|
32
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-FSY2BM4J.js";
|
|
33
34
|
export {
|
|
34
35
|
AGUIClient,
|
|
35
36
|
CALLBACK_PATH,
|
|
@@ -57,6 +58,7 @@ export {
|
|
|
57
58
|
printMessage,
|
|
58
59
|
printTable,
|
|
59
60
|
refreshAccessToken,
|
|
61
|
+
resolveNonInteractiveAuth,
|
|
60
62
|
resolveOAuthClient,
|
|
61
63
|
selectFromList,
|
|
62
64
|
startCallbackServer,
|