@agentmail/agentid-cli
v0.9.0
Published
Browser-assisted AgentID setup for web applications
Readme
AgentID CLI
CLI-first registration and provider configuration for AgentID relying parties.
Read the AgentID CLI quickstart and troubleshooting guide.
The CLI detects Clerk, Auth0, Supabase, Better Auth, or Auth.js, asks the developer to approve registration in the AgentID console, writes the resulting client credentials to the project's ignored .env.local file, and configures the selected provider.
For Clerk projects, setup is end-to-end from the terminal. AgentID ships an exact Clerk CLI version, uses its authenticated session to select or create an application, derives Clerk's /v1/oauth_callback, registers the AgentID client, and installs Clerk's official AgentID connection (strategy oauth_agentid) with the client's own credentials. Clerk requests openid email profile with PKCE. The connection is installed with sign-in disabled by default; pass --enable-sso only when you want it enabled immediately. The browser is used only when Clerk authentication or AgentID registration approval is required. Clerk sets extra scopes such as owner_profile and owner_email only in its dashboard, so when you choose owner scopes the CLI prints the dashboard link and the scopes to add. If the instance still has the custom oauth_custom_agentid connection from an earlier CLI version, the CLI leaves it in place and tells you to remove it once your sign-in code uses oauth_agentid.
Human owner identity is opt-in. In an interactive terminal, the CLI shows a scope picker for standard identity, owner email, owner name, or both. Agents and CI can make the same selection with --owner-name and --owner-email:
npx @agentmail/agentid-cli init --owner-name --owner-emailThese flags configure the scopes Clerk, Auth0, Supabase, Better Auth, or Auth.js sends during /authorize; they do not add a registration-time scope policy to the AgentID client. For a generic project registered with --name and --redirect-uri, the CLI has no connector to write the scope into, so it prints the scope string to send at /authorize instead.
If the project contains NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY or CLERK_PUBLISHABLE_KEY, AgentID uses it to match the correct Clerk application. When that application has both development and production instances, interactive setup asks you to confirm or change the suggested environment. This prevents a development key in .env.local or a previous Clerk CLI link from configuring the development instance when production was intended. Agents and CI must select the environment explicitly for init; read-only doctor runs continue to validate the environment matched by the project's local key:
npx @agentmail/agentid-cli init \
--clerk-app app_123 \
--clerk-instance prod--clerk-instance accepts dev, prod, or a full Clerk instance ID. It is optional when the application has only one instance. Clerk authentication is delegated to the bundled Clerk CLI; its existing CLI session is reused when available. Noninteractive runs fail fast when authentication or an environment choice is required instead of opening a browser or guessing.
Auth0
Auth0 setup uses the official AgentID Marketplace social connection recipe through the Auth0 Management API. CLI setup does not require the Marketplace dashboard wizard. The CLI uses the official Auth0 CLI's browser session to select an authenticated tenant and a login-capable application, derives Auth0's /login/callback for the canonical tenant and ready custom domains, registers the AgentID client with client_secret_post, and creates or reconciles the oauth2 Social connection with PKCE and the AgentID logo.
If the tenant already has a recognized AgentID connection, the CLI reuses its credentials through the authenticated Auth0 session and stores them in the project's ignored .env.local. It skips AgentID registration and browser approval. The existing connection ID, profile script, branding, sync settings, scopes (unless explicitly selected), and other applications remain intact. PKCE is enabled automatically. doctor recognizes both Marketplace installations and older CLI connections without requiring a CLI ownership marker.
A tenant connection can serve multiple applications: reused credentials refer to the same AgentID client, so unregistering that client affects every application using it. --force refuses to replace an existing Auth0 connection's client. Unrecognized scripts/endpoints, missing or masked credentials, and a different locally bound client stop setup before the connection is overwritten. When reusing a connection, its registered callback URLs must still cover the tenant/custom domain your application uses, and its AgentID token endpoint authentication method must be client_secret_post. A generic client_secret_basic registration fails during code exchange. Set the method in AgentID Console → application → edit, or register through the Auth0 setup guide. The CLI cannot verify these remote registration settings from the Auth0 connection; complete a real test sign-in after import.
The official recipe and logo are pinned from Auth0 Marketplace, including preferred_username → nickname. Setup never downloads executable profile scripts. Existing legacy profile scripts remain intact, including their org mapping. Updating the pinned recipe requires reviewing its code and compatibility tests. See the integration research.
Run the normal command. AgentID reuses a compatible system Auth0 CLI or its previously verified cache. If neither is available, interactive setup offers to download the official Auth0 CLI v1.33.0 release for the current operating system and architecture, verifies its pinned SHA-256 checksum, and stores that one binary in the user's AgentID cache. Nothing is installed globally and noninteractive runs never download executable code without prior preparation.
If Auth0 authentication has expired or lacks create:connections, AgentID opens the official Auth0 device login, prints the same device confirmation code shown in the browser, and resumes after authorization. Auth0's official CLI consent includes its normal management scopes plus create:connections; selecting automated token claims also requests the Auth0 Actions permissions needed to create, deploy, and bind the managed Action. AgentID never asks you to paste a Management API client secret.
npx @agentmail/agentid-cli initLocal AUTH0_DOMAIN/AUTH0_ISSUER_BASE_URL and AUTH0_CLIENT_ID values are only hints. The CLI verifies both against the tenants and applications available to the authenticated Auth0 account. You can select them explicitly without guessing:
npx @agentmail/agentid-cli init \
--auth0-tenant acme.us.auth0.com \
--auth0-app client_123Interactive Auth0 setup offers to enable Continue with AgentID for the selected application (default: keep disabled). Already enabled applications remain enabled without another question. Noninteractive setup leaves new connections disabled unless you pass --enable-sso; reruns preserve existing enablement. Interactive setup uses the same owner-identity scope picker as Clerk; agents and CI can use --owner-name and --owner-email. Auth0's profile script copies the corresponding owner_name and owner_email claims from AgentID /userinfo into the Auth0 user profile.
Owner values remain on the Auth0 user profile by default, without adding more questions to the normal setup flow. To expose selected owner claims in Auth0-issued ID tokens, explicitly select at least one owner scope with --owner-name or --owner-email and provide an HTTPS claim namespace with --auth0-claim-namespace. The CLI then creates and deploys a managed Post Login Action. Auth0 binds Post Login Actions to the tenant-wide login flow, so the generated Action runs on every post-login transaction but immediately does nothing unless both the selected Auth0 application and the AgentID connection match. The CLI appends that Action without reordering existing post-login Actions. It does not add owner information to access tokens.
npx @agentmail/agentid-cli init \
--owner-name \
--owner-email \
--auth0-claim-namespace https://app.acme.com/claimsThe resulting ID token claims are https://app.acme.com/claims/owner_name and https://app.acme.com/claims/owner_email. Without this option, owner values remain available on the Auth0 user profile but are not automatically included in Auth0-issued tokens. AgentID will not overwrite a same-name Action that it does not recognize as CLI-managed. If the tenant-wide flow contains a Marketplace-installed or secret-bound Action whose binding cannot be reconstructed losslessly, the CLI deploys the managed Action but asks you to add it to the flow in the Auth0 Dashboard.
Interactive setup requires the official Auth0 CLI's user login. Noninteractive and CI use a preauthenticated Auth0 CLI machine session with the required connection, application, and custom-domain read/write scopes.
Supabase
Supabase setup is also end-to-end from the terminal for hosted projects. The CLI detects a project URL or local Supabase link as a hint, verifies it against the projects available to the authenticated Supabase account, and otherwise presents a project picker. It registers AgentID's standard /auth/v1/callback, then creates or reconciles the custom:agentid OIDC provider through Supabase Auth's admin API with discovery, PKCE, nonce validation, and required identity scopes.
AgentID reuses a compatible official Supabase CLI or its previously verified cache. If neither is available, interactive setup offers to download the pinned official Supabase CLI v2.115.0 release for the current operating system and architecture, verifies its SHA-256 checksum, and keeps the binary in the user's AgentID cache. The Supabase CLI opens browser login when needed. AgentID uses that session to reveal a project secret in memory for the provider API call; it does not write or print the Supabase secret.
npx @agentmail/agentid-cli initTo select a project explicitly, use its immutable 20-character project ref:
npx @agentmail/agentid-cli init \
--supabase-project abcdefghijklmnopqrstNew Supabase providers are left disabled by default. Pass --enable-sso to enable AgentID sign-in immediately; reruns without that flag preserve the existing state. The owner scope picker and --owner-name/--owner-email flags add owner_profile and owner_email to the provider scopes. Supabase stores the resulting owner_name and owner_email values under the identity's custom_claims object.
Better Auth
Better Auth setup is local and end-to-end. The CLI detects a better-auth dependency and the conventional auth.ts server config in the project root, utils, lib, or the corresponding src directory. It reads the application origin from BETTER_AUTH_URL or NEXT_PUBLIC_BETTER_AUTH_URL, registers Better Auth's /api/auth/callback/agentid callback, installs the AgentMail-maintained @agentmail/agentid-better-auth community helper as an application production dependency, and adds the helper to the existing genericOAuth configuration. If no Generic OAuth plugin exists, it adds one without replacing other Better Auth options, plugins, or providers.
Automatic setup supports stable Better Auth releases from 1.7.2 through 1.x, matching the helper's peer dependency. Better Auth prereleases and 2.x remain unsupported until explicitly tested. The CLI detects pnpm, npm, Yarn, or Bun from packageManager metadata and lockfiles and runs the installer in the package that declares better-auth, including applications nested in workspaces. If multiple conventional auth.* files exist, the interactive flow asks which one is the server config and noninteractive setup requires an explicit selection. For a custom config path or application URL, pass both values explicitly:
npx @agentmail/agentid-cli init \
--better-auth-config src/server/auth.ts \
--better-auth-url https://app.acme.com--better-auth-url normally receives the application origin. The CLI reads a static basePath from the config when deriving the callback. If basePath is dynamic, pass the complete auth base URL instead, such as https://app.acme.com/private/auth.
The helper owns AgentID's OIDC discovery, token authentication, PKCE, and ID-token settings. The CLI manages only the credential expressions and optional owner scopes in the helper call. Existing Generic OAuth providers remain in the same config array. Reruns preserve scopes unless the owner picker or owner flags explicitly replace them, and a rerun safely migrates the exact legacy inline block generated by CLI 0.5.0 without registering another client. The CLI refuses to overwrite a hand-written AgentID object or helper call, a modified CLI-managed call, symlinked or out-of-project configs, ambiguous noninteractive config selection, or dynamic/spread shapes it cannot inspect safely.
Better Auth does not have a hosted connection enablement switch. The CLI does not add a sign-in button. Supported Better Auth 1.x releases expose Generic OAuth providers through the standard social sign-in API, so offer AgentID sign-in from the app's UI when ready:
import { createAuthClient } from 'better-auth/react'
export const authClient = createAuthClient()
await authClient.signIn.social({
provider: 'agentid',
callbackURL: '/',
})The owner flags add owner_profile and owner_email to the AgentID authorization request. Better Auth maps the standard name and email fields automatically. Persisting owner-specific fields into a custom Better Auth user or session schema remains application-specific, so the CLI does not modify the database schema.
Auth.js
Auth.js setup is local and end-to-end for stable NextAuth v4 projects. The CLI detects the conventional Pages Router or App Router catch-all auth route, reads the application URL from NEXTAUTH_URL or AUTH_URL, registers the agentid callback, and adds a managed AgentID provider without replacing existing Auth.js options or providers. It supports both an inline NextAuth({...}) object and the conventional top-level const authOptions = {...}; NextAuth(authOptions) form.
Automatic editing supports stable next-auth releases from 4.24.0 through 4.x. Auth.js v5 is still prerelease and uses a different configuration model, so the CLI refuses to edit it until the stable release is tested. If multiple conventional auth routes exist, interactive setup asks which one to use; noninteractive setup requires explicit values:
npx @agentmail/agentid-cli init \
--auth-js-config src/pages/api/auth/[...nextauth].ts \
--auth-js-url https://app.acme.comFor the default Auth.js base path, an application origin produces https://app.acme.com/api/auth/callback/agentid. If NEXTAUTH_URL contains a custom auth base path, the CLI appends /callback/agentid to that complete URL.
The managed provider uses AgentID OIDC discovery, client_secret_basic, PKCE, state and nonce checks, ES256-verified ID tokens, and the selected identity scopes. The explicit ES256 client metadata is required because NextAuth v4 otherwise assumes RS256 even when discovery advertises AgentID's signing algorithm. Reruns preserve scopes unless the owner picker or owner flags explicitly replace them. The CLI refuses to overwrite a hand-written id: "agentid" provider, a modified CLI-managed block, symlinked or out-of-project configs, ambiguous noninteractive config selection, and dynamic or spread shapes it cannot inspect safely.
Auth.js does not have a hosted enablement switch. Adding the provider does not add a button to the application. Offer AgentID from the app's sign-in UI when ready:
await signIn("agentid")The owner flags add owner_profile and owner_email to the authorization request. The raw Auth.js OAuth profile contains those selected values. Persisting them in JWT, session, adapter, or custom user fields is application-specific, so the CLI does not rewrite those callbacks or application types.
When automatic detection cannot determine the relying-party integration, the CLI offers Clerk, Auth0, Supabase, Better Auth, Auth.js, and generic OIDC setup. Choosing Supabase enters its authenticated project picker. Choosing Better Auth or Auth.js resumes local dependency and config discovery and asks for the application URL only when it is not configured. Generic OIDC accepts one or more callback URLs.
For agents and noninteractive environments, provide the metadata directly:
npx @agentmail/agentid-cli init \
--name Acme \
--redirect-uri https://acme.example/auth/callbackRepeat --redirect-uri to register more than one callback. The client is registered for client_secret_basic unless you pass --token-endpoint-auth-method client_secret_post; the AgentID token endpoint accepts only the registered method, so match it to what your OIDC library sends. The owner flags work here too; because the CLI cannot configure a client it does not recognize, it prints the scopes your connector should request at /authorize (for example openid email profile owner_email) instead of writing them anywhere.
Registration metadata
init can register the client's full public metadata in the same run, so the console needs no follow-up edits. Every flag is optional and works with every provider:
npx @agentmail/agentid-cli init \
--description "Expense reports for agents" \
--logo-uri https://acme.example/logo.png \
--contact [email protected] \
--tos-uri https://acme.example/terms \
--policy-uri https://acme.example/privacy \
--login-url https://acme.example/sign-in \
--api-key-url https://acme.example/settings/api-keys \
--initiate-login-uri https://acme.example/agentid/initiate \
--max-signups-per-organization 10--description,--logo-uri,--contact(repeat for up to five),--tos-uri, and--policy-uriare shown to the agents and owners approving a sign-in. The URLs must be publichttps://addresses.--login-urland--api-key-urltell agents where to sign in and where to create an API key. They may usehttp://localhostduring development.--initiate-login-uriis the OIDC third-party-initiated login endpoint. AgentID appendsissandlogin_hint, so the URL must not contain them.--max-signups-per-organizationcaps new accounts per AgentMail organization;0admits existing accounts only. Omit it for no limit.
The CLI also records the detected provider so the console opens the matching setup guide. The terminal prints every metadata value before the browser approval, and the CLI validates them first so mistakes fail before the browser opens. The flags apply only when a new client is registered; when credentials already exist, edit the client in AgentID Console instead. The client ID is always assigned by AgentID.
To remove the local project association without deleting the remote AgentID client or changing provider configuration, run:
npx @agentmail/agentid-cli unlinkThe command removes only AGENTID_CLIENT_ID, AGENTID_CLIENT_SECRET, and AGENTID_PROJECT_BINDING from the nearest project .env.local. It preserves unrelated variables, the Better Auth helper dependency, the helper import and configuration, and existing application sessions. It refuses symlinked environment files and asks for confirmation. Automated test cleanup can use agentid unlink --yes.
To permanently unregister the remote relying party as well, run:
npx @agentmail/agentid-cli unregisterThe CLI reads the local client ID, opens AgentID in the browser, and requires an authenticated owner of that relying party to approve deletion. It then atomically retires the remote client and consumes the one-time browser request before removing the same three local variables. If the remote client was already deleted, the recorded owner can still approve local cleanup. The client secret stops working and new AgentID authorization or token exchanges for that registered client are refused. Existing application sessions, already-issued access and ID tokens, provider configuration, and the Better Auth helper dependency and import are not removed automatically; issued tokens remain valid until they expire.
unregister accepts --no-open to print the approval URL and --console-url for loopback-only local development. Because this command sends the existing client secret, non-loopback console overrides are refused. It deliberately has no --yes bypass: remote deletion always requires browser authentication and explicit approval. The CLI prints the target before opening the browser and rechecks .env.local immediately before deletion. If remote deletion succeeds but local cleanup cannot be confirmed, rerun agentid unregister; an already-deleted client can safely finish local cleanup.
Running init again with valid AgentID credentials already present is idempotent. The CLI stores a non-secret AGENTID_PROJECT_BINDING beside the credentials and refuses to reuse them for a different RP or AgentID console environment. Clerk bindings use immutable application and instance IDs; Auth0 bindings use the authenticated tenant and application client ID; Supabase bindings use the immutable project ref; Better Auth and Auth.js bindings include the local config path and registered callback. Rerunning reapplies and validates managed provider configuration while preserving hosted-provider sign-in enablement unless --enable-sso is explicit. Use --force only when you intentionally want to register a replacement client and overwrite the local credentials; the previous client remains in the AgentID console until you delete it.
Doctor
Validate an existing setup without registering a client or changing project, provider, or AgentID configuration:
npx @agentmail/agentid-cli doctorDoctor verifies project detection and callbacks, local credential integrity, Git ignore and file-permission safety, the immutable relying-party binding, AgentID OIDC discovery and JWKS, and the existing managed Clerk, Auth0, Supabase, Better Auth, or Auth.js configuration. For Better Auth, it also verifies that a compatible helper dependency is declared and installed and that the managed helper call remains inside Generic OAuth. Hosted-provider checks use read-only CLI/API operations and report the current scopes and sign-in state. Doctor never installs dependencies, opens a browser login, or acquires new provider permissions; authenticate first or run agentid init if hosted-provider access has expired. Generic OIDC projects receive a warning because their application-specific provider configuration cannot be inspected automatically.
Every check is reported in one pass. Failures produce exit status 1; warnings alone keep exit status 0. Provider selectors such as --auth0-app, --supabase-project, --better-auth-config, and --auth-js-config are accepted when automatic selection is ambiguous. Doctor never offers to create a Clerk application or download a missing provider CLI.
For CI and agents, agentid doctor --json emits one JSON document to stdout and never prompts. The versioned report includes healthy, summary counts, and stable check IDs; fatal setup or selection errors use the same envelope with an error field. Human-readable status is suppressed so stdout remains directly parseable. Exit status behavior is unchanged.
npx @agentmail/agentid-cli doctor --jsonTroubleshooting
Start with the read-only doctor command. It checks local credentials, callbacks, AgentID discovery, and the selected provider without changing the project:
npx @agentmail/agentid-cli@latest doctor- If the browser does not open, rerun
initwith--no-openand open the printed URL in a browser on the same machine. - If the browser says approval succeeded while the terminal still waits, return to the original terminal and wait briefly. If it does not resume, rerun
init. A request that never completed expires automatically; if a client was created but its credentials were not stored, delete that incomplete client in the AgentID console before retrying. - If credentials were stored but provider setup failed, rerun
initwith the same provider and owner-scope options. The CLI retries provider configuration without registering another client. - If
.env.localbelongs to a different relying party and the local binding is stale, runagentid unlinkto remove only the local AgentID association. Rerunagentid init --forceonly when you deliberately want a replacement remote client. - If Clerk, Auth0, or Supabase access expired, authenticate that provider's CLI and rerun
init. - If a managed Better Auth helper call or Auth.js provider block was edited, restore the generated code or remove it before retrying; the CLI refuses to overwrite ambiguous application code.
- If AgentID returns HTTP 429, wait five minutes before retrying so the per-caller capability window can reset.
If the issue remains, email [email protected] with the command, provider, and sanitized error. Never include client secrets, provider tokens, private callback URLs, email addresses, or local filesystem paths.
Development
Requires Node.js 20 or newer and pnpm 10. agentid --version (or -V) prints the installed CLI version and nothing else; include it in bug reports and when checking whether a documented flag is available.
pnpm install
pnpm check
pnpm dev -- initTo exercise a local console:
pnpm dev -- init --console-url http://localhost:3012The CLI uses a short-lived loopback callback, PKCE, and a browser-delivered completion verifier. Client secrets are never returned to the browser.
Package
The package is published as @agentmail/agentid-cli and exposes the agentid executable.
npx @agentmail/agentid-cli initThe preferred npx agentid init command requires control of the existing unscoped agentid package on npm.
Releases
Every change runs the CLI from the packed npm tarball as part of pnpm check. Publishing is triggered by a GitHub Release whose tag exactly matches the package version, such as v0.2.0. The release workflow publishes the public scoped package from Node.js 24 using npm trusted publishing. npm provenance is unavailable while the source repository is private.
Automated publishing uses these npm and GitHub settings:
- Configure the package's npm trusted publisher for GitHub organization
agentmail-to, repositoryagentid-cli, workflowrelease.yml, environmentnpm, and allownpm publish. - Restrict the repository's
npmenvironment to tags matchingv*. Add required reviewers as well when the repository's GitHub plan supports protected environments for private repositories. - Set npm publishing access to require two-factor authentication and disallow bypass-capable tokens. Trusted publishing continues to work without a long-lived npm token.
For each release, increment package.json, merge the change, and publish a matching GitHub Release. The workflow refuses tags whose commit is not on main, runs release validation and the full packed-package suite without OIDC permission, and smoke-tests the exact tarball passed to the final publish job. Only that final job may request an npm OIDC token. No long-lived npm token is used by the workflow.
