Skip to content

Plugin API

Provider plugins live in @valetkey/core types. A plugin is a plain object; there is no class hierarchy or registration step.

type ProviderPlugin<TConfig = unknown> = {
name: string;
config: TConfig;
detect?: (projectDir: string) => Promise<boolean>;
seed?: (ctx: MintContext, persona: Persona) => Promise<void>;
mint: (ctx: MintContext, persona: Persona) => Promise<SessionArtifact>;
verify?: (ctx: MintContext, artifact: SessionArtifact) => Promise<SessionStatus>;
revoke?: (ctx: MintContext, artifact: SessionArtifact) => Promise<void>;
};
type SessionStatus = "valid" | "expired" | "invalid";

mint is the only required hook. seed provisions the persona’s user and must be idempotent. verify lets valetkey ask whether a stored session is still good. revoke runs on logout and should invalidate the session server-side. detect exists for future init auto-detection of the auth system in a project.

type MintContext = {
projectDir: string;
origins: Array<string>;
secrets: { get: (key: string) => Promise<string> };
fetch: typeof fetch;
browser: AgentBrowser;
log: (event: string, data?: Record<string, unknown>) => void;
notify: (message: string) => void;
};
type AgentBrowser = {
withPage: <T>(fn: (page: CdpPage) => Promise<T>) => Promise<T>;
readCookies: (origins: Array<string>) => Promise<Array<StorageStateCookie>>;
readLocalStorage: (
origins: Array<string>,
) => Promise<Array<{ origin: string; entries: Record<string, string> }>>;
};
type CdpPage = {
goto: (url: string) => Promise<void>;
evaluate: <T>(expression: string) => Promise<T>;
};

secrets.get reads the project vault and registers every returned value for output redaction. log writes provider-scoped audit events. notify shows a message to the person running valetkey, which interactive providers use to ask for a sign-in; under the MCP server it is a no-op.

browser is the managed Chrome profile. It is only live when the browser is running, which means valetkey login --browser, an MCP valetkey_login_as call, or an already-open browser; otherwise every method throws with a message telling the user to start it. Anything a plugin does through withPage happens in valetkey’s process and never enters agent context, which is what makes interactive sign-in safe. See the record provider for a plugin built on it.

type Persona = {
name: string;
seed: Record<string, unknown>;
};
type SessionArtifact = {
persona: string;
provider: string;
origins: Array<string>;
createdAt: string;
expiresAt: string | null;
cookies: Array<StorageStateCookie>;
localStorage: Array<{ origin: string; entries: Record<string, string> }>;
headers: Record<string, string>;
meta: { userId?: string; email?: string; role?: string };
};
type StorageStateCookie = {
name: string;
value: string;
domain: string;
path: string;
expires: number;
httpOnly: boolean;
secure: boolean;
sameSite: "Strict" | "Lax" | "None";
};

Build artifacts with createSessionArtifact from @valetkey/custom (re-exported from core), which fills createdAt, empty localStorage, headers, and meta. Leave persona and provider alone; the core stamps them after mint returns. headers is reserved for the planned API-delivery mode and is ignored by browser delivery.

  • expiresAt is clamped to the session TTL. null means “use the full TTL”.
  • Every cookie’s domain must match a configured origin’s hostname, or the whole artifact is rejected.
  • Artifacts are validated against a schema when read back, so a corrupt or tampered file loads as no session rather than a broken one.
  • Anything you throw is surfaced to the user with vault values redacted.