Lemon CLI

August 21, 2026 ยท View on GitHub

lemon_cli is the user-facing command boundary for packaged Lemon releases. LemonCli.CLI.main/1 is the one process-halting boundary: it receives the forwarded argument vector from the release launcher and turns dispatch results into exit codes. Its non-halting run/1 and command handlers are shared by tests and source-checkout Mix adapters, so release commands do not depend on Mix at runtime.

The runtime CLI is included in lemon_runtime_min and lemon_runtime_full artifacts. The sim_broadcast_platform artifact intentionally does not bundle lemon_cli; it supports only its release-specific doctor --bundle path.

Release CLI contract

The packaged launcher sends runtime CLI arguments through LemonCli.CLI as data, not dynamically constructed Elixir source. The boundary has these user-visible rules:

  • success exits 0;
  • a command failure exits 1;
  • an unknown command, unknown setup/gateway subcommand, or invalid command arguments exits 2;
  • --help, -h, or help prints the relevant command usage and exits 0 without starting a wizard, provider flow, or gateway adapter.

Packaged and source commands

Use the same command nouns in an installed release and a source checkout. Installed releases use lemon; a checkout uses the matching ./bin/lemon wrapper. Direct Mix tasks remain contributor-level alternatives, not the recommended commands for users of a packaged release.

PurposeInstalled releaseSource checkoutContributor Mix adapter
First-time setuplemon setup./bin/lemon setupmix lemon.setup
Configure a model providerlemon model --provider anthropic./bin/lemon model --provider anthropicmix lemon.onboard anthropic
Configure Telegram or Discordlemon gateway setup./bin/lemon gateway setupmix lemon.setup gateway
Diagnosticslemon doctor./bin/lemon doctormix lemon.doctor
Validate/show configlemon config validate./bin/lemon config validatemix lemon.config validate
Manage encrypted secretslemon secrets status./bin/lemon secrets statusmix lemon.secrets.status
Inspect channel readinesslemon channels./bin/lemon channelsmix lemon.channels

First-run setup and readiness

lemon setup is an idempotent state machine over the global config, encrypted secrets, and default provider. Each run derives which steps are complete, creates a minimal config only when it is absent, initializes the secrets master key only when it is absent, skips a provider that is already usable, and re-derives state before reporting the final result. It never replaces an existing config or secrets master key.

When setup onboards a provider, it always performs offline configuration checks and performs the provider's live credential check by default. Use lemon setup --skip-verify or lemon setup provider --skip-verify only to defer that live check when offline; a failed verification is not reported as a completed setup.

The one-line installer starts lemon setup by default when it has a controlling terminal. --skip-setup, an unavailable terminal, and the simulation profile defer that interaction. The first interactive TUI launch consults the same readiness predicate and runs setup before starting an unconfigured agent; if setup remains incomplete, it directs the user to lemon setup.

lemon model is the focused provider onboarding command. It stores the credential in encrypted secrets, updates the provider configuration, and can set the default provider/model. Use the full lemon setup journey when config and secrets may not exist yet or when live provider verification is required.

Gateway setup

lemon gateway setup provides interactive and non-interactive adapters for both supported messaging gateways:

  • telegram stores a Telegram bot token in encrypted secrets and verifies it with the Telegram Bot API.
  • discord stores a Discord bot token in encrypted secrets, enables Discord, writes its secret reference and a default/allowed channel scope to [gateway.discord], and verifies the token with Discord's bot identity API.
# Installed release: choose Telegram or Discord interactively.
lemon gateway setup

# Configure a specific adapter.
lemon gateway setup telegram
lemon gateway setup discord

# Source checkout: use the matching wrapper.
./bin/lemon gateway setup discord --non-interactive \
  --token "$DISCORD_BOT_TOKEN" \
  --default-channel-id 123456789012345678 \
  --allowed-channel-id 234567890123456789

The Discord token is persisted as discord_bot_token by default and only its secret key is written to TOML. Pass --secret-key <name> to use another key, --allowed-guild-id <id> to restrict by guild as well, or --skip-smoke when the Discord API identity check cannot be reached.

Onboarding providers

Guided provider setup uses a plain numbered prompt that works consistently across packaged releases, source checkouts, SSH sessions, and narrow terminals. Press Enter to accept the displayed default, enter a number or exact label to choose another option, or enter q to cancel. The same selector is used for providers, authentication methods, models, and confirmation prompts, so setup does not switch the terminal between cooked and raw modes.

Anthropic provider auth supports API keys or Claude subscription OAuth. Raw API keys live in llm_anthropic_api_key_raw and should be referenced by providers.anthropic.api_key_secret. OAuth-backed Claude Max usage keeps using llm_anthropic_api_key plus providers.anthropic.auth_source = "oauth" / providers.anthropic.oauth_secret, and Lemon prefers refreshable Claude Code credentials from ~/.claude/.credentials.json over a stale static CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_TOKEN.

OpenAI Codex browser sign-in starts a temporary loopback HTTP listener before opening the authorization URL. The listener binds the redirect URI's port (http://localhost:1455/auth/callback by default), captures the authorization code, returns a completion page to the browser, and then shuts down. If the port cannot be bound or no callback arrives within two minutes, onboarding falls back to accepting the callback URL or authorization code manually.

# Installed release
lemon model --provider antigravity --token <token> --set-default --model gemini-3-pro-high
lemon model --provider gemini --project-id your-gcp-project
lemon model --provider openai-codex --token <token> --set-default --model gpt-5.2
lemon model --provider github-copilot --enterprise-domain company.ghe.com

# Source checkout
./bin/lemon model --provider antigravity --token <token> --set-default --model gemini-3-pro-high
./bin/lemon model --provider gemini --project-id your-gcp-project
./bin/lemon model --provider openai-codex --token <token> --set-default --model gpt-5.2
./bin/lemon model --provider github-copilot --enterprise-domain company.ghe.com

# Contributors can invoke the underlying Mix task directly.
mix lemon.onboard codex --token <token> --set-default --model gpt-5.2