interface ExchangeResponse { api_key: string; user: { id: string; githubUsername: string; email: string; }; repository?: { owner: string; name: string; }; githubAppInstallUrl?: string | null; /** Set when an open-source program invite was applied by this login. */ oss_program?: { pro_until: string; }; } export interface LoginOptions { /** Open-source program invite code (`hula login --invite `). */ inviteCode?: string; /** * Force the browser GitHub Device Flow even when a valid token is already * stored locally (`hula login --fresh`). Used to switch GitHub accounts. */ fresh?: boolean; } /** * The server rejected the token exchange because the GitHub token cannot see * the target repository. Login uses the GitHub App Device Flow, so the token * is user-to-server and only sees repositories covered by an App * installation — for a repo the user owns, this almost always means the App * simply isn't installed on it. Carries the install URL the server returned * so the login flow can walk the user through installing and then resume. */ export declare class RepoNotFoundError extends Error { readonly installUrl: string | null; constructor(message: string, installUrl: string | null); } /** * Execute the unified login flow for hub-launch * * This command authenticates the user with both GitHub and hula-project in one flow: * 0. Reuse the globally stored GitHub token when one is valid (skip steps 1-3), * unless `--fresh` is passed. Fall back to the device flow on a 401. * 1. Initiates GitHub App Device Flow * 2. User authorizes in browser * 3. Receives GitHub token * 4. Exchanges token for hula-project API key * 5. Configures gh CLI with GitHub token * 6. Saves API key to config * 7. Asks once about web + phone planning, then (when enabled) syncs the * credential set to HubLaunch so the dashboard/phone can plan immediately. */ export declare function executeLogin(config: any, options?: LoginOptions): Promise; /** * Run the GitHub App Device Flow end-to-end (initiate → display code + open * browser → poll for the token). Factored out of `executeLogin` so other * commands can authenticate with the exact same flow when no reusable token is * stored. Returns the GitHub token (with refresh token / expiry when GitHub * provides them). */ export declare function runGitHubDeviceFlow(config: any, hulaProjectUrl: string): Promise<{ access_token: string; refresh_token?: string; expires_in?: number; }>; /** * Handle a repo_not_found exchange failure: explain that the GitHub App * isn't installed on the repository, open the install page in the browser, * then poll the exchange until the installation makes the repo visible. * Mirrors the device-flow polling pattern used for the GitHub token itself. * * Exported for tests; production code injects the real exchange call. */ export declare function promptInstallAndRetryExchange(error: RepoNotFoundError, repository: { owner: string; repo: string; }, retryExchange: () => Promise, pollIntervalMs?: number, timeoutMs?: number): Promise; /** Exported for tests; production code calls it from executeLogin. */ export declare function exchangeTokenForApiKey(hulaProjectUrl: string, githubToken: string, repository?: { owner: string; repo: string; } | null, inviteCode?: string): Promise; /** * Load saved GitHub token metadata */ export declare function loadGitHubToken(): Promise<{ access_token: string; refresh_token?: string; expires_at?: string; } | null>; /** * Resolve a GitHub token to reuse for login without a browser round-trip. * * Returns the stored token when one is present and not within the refresh * window; when it is within the window it refreshes (if a refresh token * exists) and returns the refreshed token. Returns `null` — printing one * "expired" line — when the token is expiring and refresh fails or there is no * refresh token. Returns `null` silently when there is no stored token at all. * * A dead-but-non-expiring token is deliberately returned as-is: the exchange's * 401 fallback in `executeLogin` handles it, avoiding a redundant client-side * `GET /user` pre-check. * * Exported for tests. */ export declare function resolveStoredGitHubToken(config: any): Promise<{ access_token: string; refresh_token?: string; expires_in?: number; } | null>; /** * True when an exchange error is the server's 401 `Invalid GitHub token` * response (its message ends with `(401)`), i.e. the reused token is no longer * accepted. A 404 (`RepoNotFoundError`) and 5xx are not matched. * * Exported for tests. */ export declare function isUnauthorizedExchangeError(error: unknown): boolean; /** * Check if token is expired or will expire soon */ export declare function isTokenExpired(expiresAt?: string): boolean; /** * Check if a token will expire within the given time window. * Returns false if no expiry info is available (non-expiring token). * @param expiresAt - ISO 8601 expiry timestamp, or null/undefined if no expiry * @param bufferMs - Window in milliseconds (e.g., 2 * 60 * 60 * 1000 for 2 hours) */ export declare function isTokenExpiringWithin(expiresAt: string | null | undefined, bufferMs: number): boolean; /** * Refresh GitHub token using refresh_token */ export declare function refreshGitHubToken(refreshToken: string, config: any): Promise<{ access_token: string; refresh_token?: string; expires_in?: number; } | null>; export {}; //# sourceMappingURL=login.d.ts.map