import arg from "arg"; import { createRebaseClient } from "@rebasepro/client"; /** Project-local link file: /.rebase/cloud.json */ export declare function projectLinkPath(cwd?: string): string; /** Persist the active organization id for a host. */ export declare function setContextOrg(url: string, org: string | undefined): void; export declare function getContextOrg(url: string): string | undefined; /** Mark a host as the active context (called on login). */ export declare function setCurrentContext(url: string): void; export declare function resolveCloudUrl(rawArgs: string[]): string; export type CloudClient = ReturnType; /** * Build an SDK client bound to a control-plane host, backed by the on-disk * credential store. `autoRefresh` is disabled so we never leave a dangling * setTimeout that keeps the CLI process alive; token refresh is done on demand * by `requireClient`. */ export declare function createCloudClient(url: string): CloudClient; /** * Return an authenticated client for the resolved host, refreshing the access * token if it is close to expiry. Exits with a helpful message when there is no * usable session (never logged in, or the refresh token was revoked). */ export declare function requireClient(rawArgs: string[]): Promise<{ client: CloudClient; url: string; }>; /** One place a deploy can actually land, as the control plane describes it. */ export interface DeployTarget { clusterId?: string | null; provider: string; region?: string; label?: string; baseDomain?: string; } export interface PlatformConfig { tenantBaseDomain?: string; deployTargets?: DeployTarget[]; } export declare function fetchPlatformConfig(client: CloudClient, url: string): Promise; /** * The base domain tenant projects are served at, derived from the same * TENANT_BASE_DOMAIN the ingress and the console read (see * saas/backend/src/utils/tenant-domain.ts). */ export declare function fetchTenantBaseDomain(client: CloudClient, url: string): Promise; /** * The infrastructure a deploy for this control plane would ACTUALLY use, in the * resolver's own preference order (saas/backend/src/k8s/resolve.ts). * * @returns the targets, or `undefined` when the control plane cannot say. */ export declare function fetchDeployTargets(client: CloudClient, url: string): Promise; /** * Public host for a project — `.`, or the bare subdomain when * the base domain is unknown. * * It deliberately never falls back to a guessed domain. The user copies this * string into a browser, so a plausible-but-wrong hostname is worse than an * obviously incomplete one: `acme.rebase.pro` looks reachable and isn't, while * `acme` reads as "the subdomain is acme" and prompts no wasted debugging. */ export declare function formatTenantHost(subdomain: string | undefined, baseDomain: string | undefined): string | undefined; /** The fields of a project row this module needs to render a host. */ export interface HostableProject { subdomain?: string; /** Resolved server-side; absent on control planes older than the host hook. */ host?: string; } /** * The host to display for a project. * * Prefers `host` off the record: the control plane resolves it through the same * `tenantHost()` the ingress uses, so it accounts for the project's *cluster* * base domain. The CLI cannot compute that itself — `clusters` is admin-only * under RLS, so a normal user's token cannot read `baseDomain`, and a project on * a second cluster is served somewhere the platform default does not name. * * `baseDomain` (from `platform-config`) remains the fallback for a control plane * that predates the hook — right for the single-cluster case, which is every * project today. */ export declare function projectHost(project: HostableProject, baseDomain: string | undefined): string | undefined; export interface ProjectLink { url: string; projectId: string; /** The project's subdomain — the slug users see in console URLs and type into --project. */ slug?: string; projectName?: string; orgId?: string; /** * Base URL of the project's own API. * * For a cloud project this is a convenience derived from the subdomain. For * a **self-hosted** project it is the entire link: there is no control plane * to look anything up in, so `projectId` is empty and this is what commands * resolve against. * * Keeping both kinds of link in one file is deliberate. A second link file * for self-hosting would fork every command that reads one, and the tooling * would drift into being cloud-only by accident. */ apiUrl?: string; /** * How this checkout is linked. Absent means `cloud` — which is every link * written before this field existed. */ mode?: "cloud" | "direct"; } export declare function readLink(cwd?: string): ProjectLink | null; export declare function writeLink(link: ProjectLink, cwd?: string): void; export declare function removeLink(cwd?: string): boolean; /** * Flags that may appear anywhere on a `rebase cloud` line, including *before* * the resource group. * * They have to be declared wherever positionals are resolved, because `arg`'s * `permissive: true` does not merely tolerate an undeclared flag — it pushes it * into `_` alongside the positionals, and for a flag that takes a value it * pushes the value in too. So `cloud --project acme storage create` parsed * without this spec yields `_` of `["--project", "acme", "storage", "create"]`, * and the group reads as `"acme"`: a real project name, in the group position, * dispatching to nothing. Skipping tokens that start with `-` does not save you * there — the damage is the orphaned value, which looks exactly like a * positional. * * Only genuinely global flags belong here. Group-specific ones (`--bucket`, * `--region`, …) are declared by the handler that owns them and always follow * the group, so they cannot shift the group or action. * * `-p` is `--project` in eighteen places and `--password` in `login`. That * ambiguity does not matter to the one caller that reads this spec: it resolves * positionals and never looks at a flag's value, so all it needs to know is * that `-p` takes one. Anything that wants the value must keep declaring it * itself, with the meaning its own command gives it. */ export declare const GLOBAL_CLOUD_FLAGS: { readonly "--json": BooleanConstructor; readonly "--yes": BooleanConstructor; readonly "--help": BooleanConstructor; readonly "--project": StringConstructor; readonly "-p": "--project"; readonly "-y": "--yes"; readonly "-h": "--help"; }; /** * The raw project reference to operate on: explicit `--project` flag wins, * otherwise the linked project. Exits with guidance when neither is present. * The value is a slug (the project's subdomain, as shown in console URLs) or, * for old scripts and link files, a raw project UUID. */ export declare function requireProjectRef(rawArgs: string[]): string; /** * Resolve a project reference — slug or UUID — to the internal id the API * takes, or undefined when no such project is visible. Slugs cost one lookup; * UUIDs pass through untouched so linked directories and old scripts skip the * round-trip. */ export declare function lookupProjectId(ref: string, client: CloudClient): Promise; /** Like `lookupProjectId`, but exits with guidance when the ref matches nothing. */ export declare function resolveProjectRef(ref: string, client: CloudClient): Promise; /** `requireProjectRef` + `resolveProjectRef` in one step. */ export declare function requireProject(rawArgs: string[], client: CloudClient): Promise; /** * The project reference to SHOW: the slug the user typed or the linked slug. * Never resolves — for human output only. Old link files predate `slug` and * fall back to the stored id. */ export declare function displayProjectRef(rawArgs: string[]): string; /** * Resolve and latch the output mode for this invocation. Call once at the top of * `cloudCommand`, before anything can print or `fail`. Returns the resolved mode * (handy for tests, which otherwise leave it at its `false` default). */ export declare function initOutputMode(rawArgs: string[]): boolean; /** Whether the current invocation is emitting machine-readable JSON. */ export declare function isJsonMode(): boolean; /** Force the mode (tests only — production latches it via `initOutputMode`). */ export declare function setJsonModeForTest(value: boolean): void; /** * Write one JSON value to stdout, followed by a newline. * * Indented, because the overwhelmingly common reader is a person or an agent * looking at a terminal — JSON mode is entered automatically whenever stdout is * not a TTY, so `rebase cloud deployments list` piped anywhere at all produced * a project's entire deployment history as one unwrapped line. `JSON.parse` * does not care about the whitespace; everything else does. */ export declare function printJson(value: unknown): void; /** * The one output primitive every new command uses: in JSON mode emit `json` * (and nothing else); otherwise run `human`. Keeping the two behind a single * call is what guarantees a command can never print a table AND a JSON blob. */ export declare function emit(human: () => void, json: unknown): void; /** * Print a help page — the human one, or a machine-readable description of the * same command in JSON mode. * * `--help` is the one place where "stdout is not a TTY" is a weak signal: a * person runs `rebase cloud db --help | less` and wants the page. But the rule * this family promises is that stdout carries one JSON value whenever it is not * a terminal, and a help page is the easiest possible thing to describe * structurally — so rather than carve out an exception, help answers the same * question in the reader's own language. For an agent, `--help` piped is then a * discovery call rather than 60 lines of ANSI to scrape. * * `env` shipped this shape first, alone; this generalises it so every group * answers the same way. */ export declare function emitHelp(command: string, actions: string[], human: () => void, extra?: Record): void; /** * Print a warning (+ optional hint) — in every output mode, always to stderr. * * `emit` is for a command's *result*, and JSON mode legitimately replaces the * human rendering of one. A warning is not a result: it says the command is * about to do something the caller may not have meant, and that is exactly as * true when the output is piped. Gating one on `!isJsonMode()` deleted it * precisely where nobody was watching the terminal — a `--source` deploy ejected * a live project off the managed runtime and said so only to a TTY that wasn't * there. * * stdout carries the JSON value and nothing else, so warnings go to stderr: * a machine parser reading stdout cannot be corrupted by one. Only the * *formatting* may depend on the mode — colour and indentation for a terminal, * plain ASCII otherwise. Whether a warning is emitted at all may not. * * Anything a caller might branch on belongs in the JSON payload as well; stderr * is for whoever reads the transcript afterwards. */ export declare function warn(message: string, hint?: string): void; /** * Print an error (+ optional hint) and exit non-zero. Never returns. * * `code` is the field a caller branches on, and it defaults to `"error"` rather * than `null`. An envelope whose only machine-readable field is null is not * machine-readable — `{"error":{"message":"No project specified…","code":null}}` * forced the very substring-matching on `message` that the envelope exists to * make unnecessary, and `message` is the field most likely to be reworded. * * `"error"` is deliberately a poor code: it says "this refusal has not been * classified yet" without ever being absent. Anything a caller might plausibly * want to distinguish — `usage`, `not_found`, `unauthenticated` — passes a real * one. Codes are part of the CLI's contract once shipped; see * `cloud-reporting.test.ts`, which pins the ones commands are documented to * return. */ export declare function fail(message: string, hint?: string, code?: string): never; /** * Confirm a destructive/irreversible action, respecting non-interactive use. * * With `--yes`/`-y` it proceeds silently. In JSON mode or a non-TTY it REFUSES * to prompt — a prompt that can hang is a known repo landmine — and fails, * telling the caller to pass `--yes`. Only an interactive terminal gets a real * confirm prompt; declining there aborts cleanly (exit 0). */ export declare function confirmDestructive(opts: { yes: boolean; prompt: string; }): Promise; /** * Refuse, rather than prompt, when there is nobody to answer. * * `confirmDestructive` has always done this for yes/no confirmations. The * *value* prompts had no such guard: `cloud login`, `cloud link`, `cloud use`, * `cloud orgs create` and `cloud db create` all called `inquirer.prompt` * unconditionally, so piping any of them — which is how an agent runs every * command in this family — parked the process on a prompt reading from a stdin * that was never going to produce a line. A hang is the worst failure mode * available here: no output, no exit code, nothing to retry on. * * @param what what the prompt would have asked for, e.g. "an email and password" * @param flags the flags that supply it non-interactively */ export declare function requireInteractive(what: string, flags: string): void; /** * Resolve one cloud command's flags and ARGUMENTS from the full `process.argv`. * * This replaces `cloudPositionals`, which was `rawArgs.slice(3).filter(a => * !a.startsWith("-"))`. Dropping `-`-prefixed tokens looks like it solves the * permissive-parse problem and does not: a flag that takes a VALUE leaves the * value behind, an ordinary word in the argument position that no filter can * tell from a real one. `--project` is the flag every one of these commands * documents, so the failure was reachable from the help page: * * rebase cloud env unset -p acme → removed the variable "acme" * rebase cloud env set KEY -p acme → stored the value "acme" * rebase cloud domains add -p acme → registered the domain "acme" * rebase cloud webhooks delete -p acme 42 → deleted webhook "acme", not 42 * rebase cloud cancel -p acme → cancelled deployment id "acme" * * The filter's other half is quieter. A flag nobody declared *is* dropped by * it — but only from the operands, never from the run: nothing rejects it, so * the command proceeds with the argument missing or defaulted. `db backup * --dry-run` listed backups, `domains remove --dry-run` detached the domain, * and `env set KEY=v --secrett` stored the value as an ordinary variable that * `env reveal` will hand back. The one place an undeclared flag became the * argument outright is `projects info|delete`, which resolved its id through * `positionals()` instead — that skips only LEADING `-` tokens, so `projects * delete --force` looked up a project named "--force". * * So: parse the whole line strictly, through the same `parseCommandArgs` the * non-cloud commands use — `arg` then consumes each declared flag *with its * value* wherever it appears, and rejects the undeclared, leaving `_` holding * the command words followed by the real arguments. `commandWords` counts from * `cloud` itself (`cloud env set` ⇒ 3), and is applied to the parsed * positionals, so a flag written before the group shifts nothing. * * Two things this adds over calling `parseCommandArgs` directly, and the reason * it is worth a wrapper: * * - `GLOBAL_CLOUD_FLAGS` is merged in. `--json`, `--yes` and `--project` may * appear anywhere on a cloud line including before the group, so a strict * parse that did not declare them would reject the CLI's own documented * usage. (`parseCommandArgs` adds `--debug`/`--help` on top of that.) * - A parse error is reported through `fail`, not thrown. A throw reaches * `bin/rebase.js`, which prints `✗ …` to stderr — which is right for every * other command and wrong here: `rebase cloud` is in JSON mode whenever * stdout is not a TTY, i.e. always for the agents this family is built for, * and it promises them exactly one JSON value. `fail` keeps that promise, * with the same `usage` code the other refusals in this family use. */ export declare function parseCloudArgs(opts: { spec: S; rawArgs: string[]; commandWords: number; /** Names the command in errors, e.g. `cloud env set` (no leading `rebase`). */ command: string; maxPositionals?: number; }): { flags: arg.Result; positionals: string[]; }; /** * Announce an outcome — "Logged in as …", "Deleted project …". * * On **stderr**, in both modes. It reads like a result and is not one: the * result is the JSON value (or the table) on stdout, and every JSON payload in * this family already carries `success: true`. Leaving this on stdout meant a * successful `rebase cloud link | jq` was handed a green tick followed by an * object — one stream, two syntaxes, and only the second parseable. * * It stays visible in JSON mode, unlike `note`: an agent that got a `success` * line on a command it expected to refuse has learned something. */ export declare function success(message: string): void; /** * Narrate progress, or point at the next step — "Signing in to …", "Redeploy * for the tenant to pick this up". * * stderr, and **suppressed entirely in JSON mode**. This is the one helper that * a mode may silence, and the distinction from `warn` is worth keeping sharp: * * - A warning is a *condition*. It is as true when piped as when watched, so * silencing it hides something the caller would want to know. `warn` never * silences. * - A note is *hand-holding*. "Next: run `rebase generate-sdk`" tells a person * what to type; the agent reading the JSON already has the same information * structurally, or does not need it. Printing it anyway is transcript noise. * * When in doubt it is a warning. The cost of a needless warning is a line; the * cost of a swallowed one is the deploy that ejected a project off the managed * runtime and said so only to a terminal nobody was looking at. */ export declare function note(message: string, indent?: string): void; /** A blank spacer line on the narration stream. No-op in JSON mode. */ export declare function noteBlank(): void; /** Colorize a deployment / resource status token. */ export declare function colorStatus(status: string | undefined): string; /** * Render a two-column key/value block with aligned keys. Empty rows are skipped * — including `null`, which the API sends for an unset column and which used to * print the literal string "null" (e.g. `Custom domain: null`). */ export declare function keyValues(rows: Array<[string, string | null | undefined]>): void; /** * Surface an SDK/HTTP error consistently. The SDK throws RebaseApiError with * a `.status` and `.message`; anything else falls back to its string form. */ export declare function reportError(e: unknown, context: string): never; /** * Open a URL in the user's default browser (best effort). Always announces the * URL first so it stays usable over SSH or when no browser is available. * * The announcement is narration, not the result — it goes to stderr, and in * JSON mode it is silent. Every caller `emit`s the same URL in its payload, so * a machine reader gets it from the one place it is guaranteed to be parseable * rather than from a line that happens to end in a URL. */ export declare function openUrl(target: string, label?: string): void;