@gravity-rail/cli 0.13.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +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 } from 'fs';\nimport { dirname, join, resolve } from 'path';\n\nexport interface EnvironmentConfig {\n apiUrl: string;\n frontendUrl: 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\n/**\n * Detect worktree port overrides from docker-compose.override.yml.\n * The override file is generated by worktree-setup.sh and contains lines like:\n * - '5250:5050' (API)\n * - '3200:3000' (Frontend)\n * We extract the host port for the default internal ports (5050, 3000).\n */\nfunction detectWorktreePorts(): { apiPort?: number; frontendPort?: number } {\n // Walk up from cwd to find docker-compose.override.yml\n let dir = resolve(process.cwd());\n let overridePath: string | undefined;\n while (true) {\n const candidate = join(dir, 'docker-compose.override.yml');\n\n if (existsSync(candidate)) {\n overridePath = candidate;\n break;\n }\n const parent = dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n if (!overridePath) return {};\n\n let content: string;\n try {\n content = readFileSync(overridePath, 'utf-8');\n } catch {\n return {};\n }\n\n // Only use this if it's a worktree override (generated by worktree-setup.sh)\n if (!content.includes('worktree-setup.sh')) return {};\n\n // Match port mappings: - 'HOST:CONTAINER'\n // Prefer api-nginx HTTPS (container 443, or legacy 5054) — never the raw\n // FastAPI container 5050 (plain HTTP; yarn gr uses https://api.gravityrail.test).\n let apiPort: number | undefined;\n let frontendPort: number | undefined;\n for (const match of content.matchAll(/-\\s*'(\\d+):(\\d+)'/g)) {\n const host = parseInt(match[1]!, 10);\n const container = parseInt(match[2]!, 10);\n if (container === 443 || container === 5054) apiPort = host;\n if (container === 3000) frontendPort = host;\n }\n return { apiPort, frontendPort };\n}\n\nexport function getEnvironmentConfig(env: string): 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: `https://${namespace}-api.gravityrail-dev.com`,\n frontendUrl: `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 // Allow env var overrides (used by code interpreter sandboxes)\n const apiUrl = process.env.GRAVITY_RAIL_API_URL || config.apiUrl;\n const frontendUrl = process.env.GRAVITY_RAIL_FRONTEND_URL || config.frontendUrl;\n if (apiUrl !== config.apiUrl || frontendUrl !== config.frontendUrl) {\n return { apiUrl, frontendUrl };\n }\n\n // For dev, detect worktree port overrides\n if (env === 'dev') {\n const ports = detectWorktreePorts();\n if (ports.apiPort || ports.frontendPort) {\n return {\n apiUrl: ports.apiPort ? `https://api.gravityrail.test:${ports.apiPort}` : config.apiUrl,\n frontendUrl: ports.frontendPort\n ? `https://app.gravityrail.test:${ports.frontendPort}`\n : config.frontendUrl,\n };\n }\n }\n\n return config;\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\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 * Register a new OAuth client dynamically\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\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\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 */\nexport async function performOAuthFlow(options: OAuthFlowOptions): Promise<AuthResult> {\n const { env, envConfig, credentialsManager, clientName = 'Gravity CLI', profile } = options;\n const profileName = credentialsManager.resolveProfileName(profile, env);\n let hasRetried = false;\n\n while (true) {\n // Reuse registered client credentials if available, otherwise register\n let creds = credentialsManager.load(env, profileName);\n if (!creds || !creds.clientId || !creds.clientSecret) {\n const registration = await dynamicClientRegistration(envConfig, clientName);\n creds = {\n clientId: registration.client_id,\n clientSecret: registration.client_secret,\n };\n credentialsManager.save(env, creds, profileName);\n } else {\n stderr(' Using cached client credentials');\n }\n\n // Token-only (import-dev) profiles cannot start OAuth; re-register if needed.\n if (!creds.clientId || !creds.clientSecret) {\n throw new Error('OAuth client credentials missing after registration');\n }\n const clientId = creds.clientId;\n const clientSecret = creds.clientSecret;\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 = startCallbackServer();\n\n stderr('\\n Opening browser for authorization...');\n stderr(` Profile: ${profileName}`);\n stderr(` If browser doesn't open, visit:\\n ${authUrl}\\n`);\n openBrowser(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(' Cached client credentials expired, re-registering...');\n credentialsManager.clear(env, profileName);\n hasRetried = 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 authHeader = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');\n\n const tokenResponse = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${authHeader}`,\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 }),\n });\n\n if (!tokenResponse.ok) {\n const body = await tokenResponse.text();\n if (!hasRetried && isOAuthError(body, 'invalid_client')) {\n stderr(' Cached client credentials expired, re-registering...');\n credentialsManager.clear(env, profileName);\n hasRetried = 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 creds.accessToken = accessToken;\n creds.refreshToken = refreshToken ?? creds.refreshToken;\n creds.expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\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 if (!creds.refreshToken || !creds.clientId || !creds.clientSecret) {\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 authHeader = Buffer.from(`${creds.clientId}:${creds.clientSecret}`).toString('base64');\n\n try {\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${authHeader}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: creds.refreshToken,\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 * 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 clientId/clientSecret (+ optional refresh).\n * Dev profiles imported from dev-realms.json are token-only (accessToken +\n * optional workspaceUuid) — no OAuth client registration.\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\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 // bearer:disable javascript_lang_observable_timing\n const hasOAuth = typeof v.clientId === 'string' && typeof v.clientSecret === 'string';\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 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 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 const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n let buffer = '';\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 line of lines) {\n if (line.startsWith('data: ')) {\n const data = line.slice(6);\n if (data === '[DONE]') {\n onEvent({ type: 'done' });\n continue;\n }\n try {\n const event = JSON.parse(data) as AGUIEvent;\n onEvent(event);\n } catch {\n // Ignore parse errors for malformed events\n }\n }\n }\n }\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"]}
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 } from 'fs';\nimport { dirname, join, resolve } from 'path';\n\nexport interface EnvironmentConfig {\n apiUrl: string;\n frontendUrl: 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\n/**\n * Detect worktree port overrides from docker-compose.override.yml.\n * The override file is generated by worktree-setup.sh and contains lines like:\n * - '5250:5050' (API)\n * - '3200:3000' (Frontend)\n * We extract the host port for the default internal ports (5050, 3000).\n */\nfunction detectWorktreePorts(): { apiPort?: number; frontendPort?: number } {\n // Walk up from cwd to find docker-compose.override.yml\n let dir = resolve(process.cwd());\n let overridePath: string | undefined;\n while (true) {\n const candidate = join(dir, 'docker-compose.override.yml');\n\n if (existsSync(candidate)) {\n overridePath = candidate;\n break;\n }\n const parent = dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n if (!overridePath) return {};\n\n let content: string;\n try {\n content = readFileSync(overridePath, 'utf-8');\n } catch {\n return {};\n }\n\n // Only use this if it's a worktree override (generated by worktree-setup.sh)\n if (!content.includes('worktree-setup.sh')) return {};\n\n // Match port mappings: - 'HOST:CONTAINER'\n // Prefer api-nginx HTTPS (container 443, or legacy 5054) — never the raw\n // FastAPI container 5050 (plain HTTP; yarn gr uses https://api.gravityrail.test).\n let apiPort: number | undefined;\n let frontendPort: number | undefined;\n for (const match of content.matchAll(/-\\s*'(\\d+):(\\d+)'/g)) {\n const host = parseInt(match[1]!, 10);\n const container = parseInt(match[2]!, 10);\n if (container === 443 || container === 5054) apiPort = host;\n if (container === 3000) frontendPort = host;\n }\n return { apiPort, frontendPort };\n}\n\nexport function getEnvironmentConfig(env: string): 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: `https://${namespace}-api.gravityrail-dev.com`,\n frontendUrl: `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 // Allow env var overrides (used by code interpreter sandboxes)\n const apiUrl = process.env.GRAVITY_RAIL_API_URL || config.apiUrl;\n const frontendUrl = process.env.GRAVITY_RAIL_FRONTEND_URL || config.frontendUrl;\n if (apiUrl !== config.apiUrl || frontendUrl !== config.frontendUrl) {\n return { apiUrl, frontendUrl };\n }\n\n // For dev, detect worktree port overrides\n if (env === 'dev') {\n const ports = detectWorktreePorts();\n if (ports.apiPort || ports.frontendPort) {\n return {\n apiUrl: ports.apiPort ? `https://api.gravityrail.test:${ports.apiPort}` : config.apiUrl,\n frontendUrl: ports.frontendPort\n ? `https://app.gravityrail.test:${ports.frontendPort}`\n : config.frontendUrl,\n };\n }\n }\n\n return config;\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\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 * Register a new OAuth client dynamically\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\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\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 */\nexport async function performOAuthFlow(options: OAuthFlowOptions): Promise<AuthResult> {\n const { env, envConfig, credentialsManager, clientName = 'Gravity CLI', profile } = options;\n const profileName = credentialsManager.resolveProfileName(profile, env);\n let hasRetried = false;\n\n while (true) {\n // Reuse registered client credentials if available, otherwise register\n let creds = credentialsManager.load(env, profileName);\n if (!creds || !creds.clientId || !creds.clientSecret) {\n const registration = await dynamicClientRegistration(envConfig, clientName);\n creds = {\n clientId: registration.client_id,\n clientSecret: registration.client_secret,\n };\n credentialsManager.save(env, creds, profileName);\n } else {\n stderr(' Using cached client credentials');\n }\n\n // Token-only (import-dev) profiles cannot start OAuth; re-register if needed.\n if (!creds.clientId || !creds.clientSecret) {\n throw new Error('OAuth client credentials missing after registration');\n }\n const clientId = creds.clientId;\n const clientSecret = creds.clientSecret;\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 = startCallbackServer();\n\n stderr('\\n Opening browser for authorization...');\n stderr(` Profile: ${profileName}`);\n stderr(` If browser doesn't open, visit:\\n ${authUrl}\\n`);\n openBrowser(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(' Cached client credentials expired, re-registering...');\n credentialsManager.clear(env, profileName);\n hasRetried = 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 authHeader = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');\n\n const tokenResponse = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${authHeader}`,\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 }),\n });\n\n if (!tokenResponse.ok) {\n const body = await tokenResponse.text();\n if (!hasRetried && isOAuthError(body, 'invalid_client')) {\n stderr(' Cached client credentials expired, re-registering...');\n credentialsManager.clear(env, profileName);\n hasRetried = 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 creds.accessToken = accessToken;\n creds.refreshToken = refreshToken ?? creds.refreshToken;\n creds.expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();\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 if (!creds.refreshToken || !creds.clientId || !creds.clientSecret) {\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 authHeader = Buffer.from(`${creds.clientId}:${creds.clientSecret}`).toString('base64');\n\n try {\n const response = await fetch(tokenUrl, {\n method: 'POST',\n headers: {\n Authorization: `Basic ${authHeader}`,\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n grant_type: 'refresh_token',\n refresh_token: creds.refreshToken,\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 * 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 clientId/clientSecret (+ optional refresh).\n * Dev profiles imported from dev-realms.json are token-only (accessToken +\n * optional workspaceUuid) — no OAuth client registration.\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\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 // bearer:disable javascript_lang_observable_timing\n const hasOAuth = typeof v.clientId === 'string' && typeof v.clientSecret === 'string';\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 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 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"]}
@@ -170,7 +170,34 @@ declare class AGUIClient {
170
170
  */
171
171
  streamRequest(request: AguiRequestBody, onEvent: (event: AGUIEvent) => void, options?: AguiStreamOptions): Promise<void>;
172
172
  private postStream;
173
+ /**
174
+ * Subscribe to the chat's persistent AG-UI event feed and surface
175
+ * server-initiated turns that arrive while we're idle between our own turns.
176
+ *
177
+ * The feed at ``/chats/{chatId}/events`` replays every AG-UI event for the
178
+ * chat across all turns (assistant events plus user-message echoes). It never
179
+ * self-terminates — it ends only on disconnect. On reconnect we send
180
+ * ``Last-Event-ID`` so the server replays the gap. The loop reconnects with
181
+ * capped exponential backoff and resolves quietly once ``signal`` aborts;
182
+ * it never throws except when required args are missing (callers gate on
183
+ * availability, so that path is a programming error).
184
+ */
185
+ subscribeToFeed(opts: {
186
+ onEvent: (event: AGUIEvent) => void;
187
+ signal: AbortSignal;
188
+ onDebug?: (msg: string) => void;
189
+ }): Promise<void>;
173
190
  }
191
+ /**
192
+ * Parse a Server-Sent Events stream body into AG-UI events.
193
+ *
194
+ * Shared by ``AGUIClient.postStream`` (per-turn POST) and ``subscribeToFeed``
195
+ * (persistent GET feed). Surfaces ``data:`` frames as parsed events, ``id:``
196
+ * frames via ``onId`` (feed reconnect uses the last id as ``Last-Event-ID``),
197
+ * and ignores SSE comment frames (``: turn-done``, keepalives). ``onId`` is
198
+ * optional — the POST path has no use for entry ids.
199
+ */
200
+ declare function parseSseStream(body: ReadableStream<Uint8Array>, onEvent: (event: AGUIEvent, eventName?: string) => void, onId?: (id: string) => void): Promise<void>;
174
201
  interface AguiStreamOptions {
175
202
  autoApprove?: boolean;
176
203
  }
@@ -200,4 +227,4 @@ declare function getTextDelta(event: AGUIEvent): string | null;
200
227
  */
201
228
  declare function getErrorMessage(event: AGUIEvent): string | null;
202
229
 
203
- export { type ChatMessage as AGUIChatMessage, AGUIClient, type AGUIClientOptions, type AGUIEvent, type AGUIEventType, type AuthResult, CLICredentials, CredentialsManager, EnvironmentConfig, type OAuthFlowOptions, type SelectFromListOptions, type SelectResult, clearScreen, formatTimestamp, generatePKCE, getErrorMessage, getTextDelta, isErrorEvent, isTextContentEvent, openBrowser, performOAuthFlow, printMessage, printTable, refreshAccessToken, selectFromList, startCallbackServer, wrapText };
230
+ export { type ChatMessage as AGUIChatMessage, AGUIClient, type AGUIClientOptions, type AGUIEvent, type AGUIEventType, type AuthResult, CLICredentials, CredentialsManager, EnvironmentConfig, type OAuthFlowOptions, type SelectFromListOptions, type SelectResult, clearScreen, formatTimestamp, generatePKCE, getErrorMessage, getTextDelta, isErrorEvent, isTextContentEvent, openBrowser, parseSseStream, performOAuthFlow, printMessage, printTable, refreshAccessToken, selectFromList, startCallbackServer, wrapText };
package/dist/cli-utils.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  isErrorEvent,
16
16
  isTextContentEvent,
17
17
  openBrowser,
18
+ parseSseStream,
18
19
  performOAuthFlow,
19
20
  printMessage,
20
21
  printTable,
@@ -22,7 +23,7 @@ import {
22
23
  selectFromList,
23
24
  startCallbackServer,
24
25
  wrapText
25
- } from "./chunk-SIV2WU2I.js";
26
+ } from "./chunk-XSFAJ5ZO.js";
26
27
  export {
27
28
  AGUIClient,
28
29
  CALLBACK_PATH,
@@ -40,6 +41,7 @@ export {
40
41
  isErrorEvent,
41
42
  isTextContentEvent,
42
43
  openBrowser,
44
+ parseSseStream,
43
45
  performOAuthFlow,
44
46
  printMessage,
45
47
  printTable,