/** * `vana app request` - ask a person for access and get the grant back. * * The half of the loop that cannot be driven headlessly: the CLI creates * the access request, prints the approval URL, and polls until the person * approves, denies, or it expires. With `--no-input` it prints the URL and * exits 7, so an agent can hand the link to a human and come back later * with `vana app requests show `. * * The request is persisted the moment it is created, before any approval, * so a `--no-input` run is findable afterwards. */ import { type GatewayClient } from "@opendatalabs/vana-sdk"; import { createDirectDataController } from "@opendatalabs/vana-sdk/server"; import { resolveAppKey } from "../../core/app-key.js"; import { type RequestsStore } from "../../core/requests-store.js"; import { readAppProfile } from "../../core/app-profile.js"; import { type GrantUnion } from "../../core/grant-union.js"; import { type AppCommandOptions } from "./outcome.js"; export interface RequestCommandOptions extends AppCommandOptions { scopes?: string; /** Live grant entries to give up instead of carrying them over. */ removeScopes?: string; /** Whose live grant to extend, when this machine cannot tell. */ owner?: string; /** `false` (`--no-merge-grant`) sends --scopes verbatim. */ mergeGrant?: boolean; /** Derivative question text, requires --derived and --sources. */ question?: string; derived?: string; sources?: string; /** Where the approval UI returns the person after they decide. */ returnUrl?: string; /** Seconds to poll before giving up. */ timeout?: string; appId?: string; appName?: string; appUrl?: string; } export interface RequestDeps { resolveKey?: typeof resolveAppKey; createController?: typeof createDirectDataController; requests?: RequestsStore; sleep?: (ms: number) => Promise; now?: () => number; readProfile?: typeof readAppProfile; createClient?: (gatewayUrl: string) => GrantUnionClient; } type GrantUnionClient = Pick; /** * What a new request does to the app's live grant: the gateway keeps one * grant per owner and app, and an approval replaces its scopes, so the * request carries what the grant already covers. */ export type GrantUnionPlan = GrantUnion & { status: "merged" | "no_live_grant" | "owner_unknown" | "owner_ambiguous" | "disabled" | "unavailable"; owner?: string; grantId?: string; reason?: string; /** * The explicit `--remove-scopes` list. The request always carries it, * whatever the live grant read found: the approval page re-reads the live * grant and leaves these entries out of the union it signs. */ removeScopes: string[]; }; /** How long one live grant read may take before it counts as failed. */ export declare const GRANT_LOOKUP_TIMEOUT_MS = 10000; /** Live grant reads are retried once on a timeout or network error. */ export declare const GRANT_LOOKUP_ATTEMPTS = 2; /** * Work out whose live grant to extend and merge it. * * The person is anonymous until they approve, so the owner comes from * `--owner` or from an earlier approval of this app key on this machine. * Grant ids are one per owner and app, so two different grant ids mean two * different people approved, and merging either one's scopes would ask the * other for data they never granted: that case is left to the approval * page, which signs the union for whoever actually approves. * * Never throws for the network: a gateway that does not answer within the * timeout (tried twice) sends the request with --scopes only. The explicit * --remove-scopes list is kept on every outcome, so the request still * carries it to the approval page. */ export declare function planGrantUnion(input: { scopes: string[]; removeScopes: string[]; owner?: string; merge: boolean; appAddress: string; network: string; gatewayUrl: string; requests: RequestsStore; createClient: (gatewayUrl: string) => GrantUnionClient; /** Per-attempt timeout; defaults to {@link GRANT_LOOKUP_TIMEOUT_MS}. */ timeoutMs?: number; /** Total attempts; defaults to {@link GRANT_LOOKUP_ATTEMPTS}. */ attempts?: number; }): Promise; /** * The connector source shown on the approval screen. * * It has to come from a scope the person actually connected, not from the * first entry in the list: with a derivative question the derived scope can * come first, and a request for `coach.weekly,spotify.history` would ask * the person to "Connect coach", a source that does not exist. Source * scopes win, then any non-derived scope, and `write:` prefixes never count * as a source name. */ export declare function resolveSourceKey(scopes: string[], derived: string | undefined, sourceScopes: string[]): string; export declare function runAppRequest(options: RequestCommandOptions, deps?: RequestDeps): Promise; export {}; //# sourceMappingURL=request.d.ts.map