import { openInBrowser } from '../open-browser.js'; import type { Environment } from '../config/environments.js'; import type { Output } from '../output.js'; /** How long to wait for the webhook to land the subscription before giving up. */ export declare const SUBSCRIBE_POLL_TIMEOUT_MS: number; export declare const SUBSCRIBE_POLL_INTERVAL_MS = 3000; export interface WhoamiResponse { userId: string; /** * The account's email address — the only name in this payload a creator can * check against the browser they are signed in to. Null when the user row has * no address; absent from an api-server deployed before it was served, which * is why callers must treat missing and null alike. */ email?: string | null; roles: string[]; /** Whether the caller currently holds an entitled subscription. */ pro: boolean; /** Period end of any subscription row, including a lapsed one. */ proUntil: string | null; /** Set when the subscription is live but will not renew. Optional: older api-servers omit it. */ cancelAtPeriodEnd?: boolean; /** * The Bitmagic Pro storefront — where Pro is bought OR a Pro voucher redeemed. * * Optional for the same reason `cancelAtPeriodEnd` is: a CLI published today * can be pointed at an api-server deployed before the storefront existed, and * must still be able to sell a subscription. Absent means "fall back to * minting Stripe Checkout myself". */ subscribeUrl?: string; } /** * PURE. One line describing where the caller's subscription stands. * * Covers the case a status boolean cannot: a subscription that is live TODAY * but set not to renew. Finding that out when a command is refused mid-work is * the worst possible moment, so every command that greets the creator says it. */ export declare function describeProStatus(me: WhoamiResponse): string; export interface SubscribeDeps { fetch: typeof globalThis.fetch; sleep: (ms: number) => Promise; now: () => number; openInBrowser: typeof openInBrowser; } export declare const defaultSubscribeDeps: SubscribeDeps; export declare function fetchWhoami(environment: Environment, token: string, deps: SubscribeDeps): Promise; export interface SubscribeOptions { /** * Do not launch a browser — print the URL instead. Set under `--json`: an * agent tool's machine spawning a window unasked is surprising, and the URL * is the actionable thing there. `BITMAGIC_NO_OPEN` is honoured separately, * inside openInBrowser. */ noOpen?: boolean; /** Skip the wait entirely and return as soon as checkout is open. */ noWait?: boolean; } export interface SubscribeResult { /** True when Pro was confirmed active before we returned. */ pro: boolean; /** * Where the creator finishes subscribing: the storefront, or — against an * api-server predating it — a Stripe Checkout URL. Always set: every exit from * the flow has already opened or printed one. */ subscribeUrl: string; /** Why we stopped waiting, when we did not confirm. */ reason?: 'timeout' | 'not-waited'; } /** * PURE. The storefront URL to open for this caller. * * `cli=1` only changes the page's wording — it says "go back to your terminal" * once Pro is active. `u` is the account this CLI is authenticated as, so the * page can refuse to sell Pro to a DIFFERENT account that happens to be signed * in to the browser. That mismatch was impossible while the CLI minted the * Stripe session from its own token, and it is invisible from here: the creator * would subscribe the wrong account and this poll would run to its timeout. * * `signin=1` asks the portal to resume the Auth0 session the creator just * created by approving the device code. That session lives on the Auth0 tenant * origin, NOT on bitmagic.ai, so the storefront's own signed-in check — a local * read of the SPA cache for its own origin — finds nothing and greets someone * who logged in thirty seconds ago as a stranger. The portal answers the hint * with one silent top-level redirect (see portal/src/auto-signin.ts); when * there is no session to resume it comes straight back and nothing is shown. * The portal treats a bare `cli=1` as the same request, so every CLI already * installed is fixed by the portal deploy alone — this only states it. */ export declare function storefrontUrlFor(subscribeUrl: string, userId: string): string; /** * Explain that Pro is required, open the storefront, and wait for it to land. * * Returns rather than throwing when the creator does not finish: abandoning * the flow is a decision, not an error, and the credentials from a successful * login are still worth keeping. */ export declare function runSubscribeFlow(environment: Environment, token: string, me: WhoamiResponse, output: Output, options?: SubscribeOptions, deps?: SubscribeDeps): Promise;