sterlai

Terminal AI agent for Stellar. Chat-driven payments, path payments, trustlines, and friendbot funding on testnet, with a separate signer daemon that prompts for explicit approval on every signature.

Package details

extensionskill

Install sterlai from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:sterlai
Package
sterlai
Version
0.1.14
Published
Apr 21, 2026
Downloads
1,705/mo · 230/wk
Author
ibrahim_1
License
MIT
Types
extension, skill
Size
1.6 MB
Dependencies
13 dependencies · 0 peers
Pi manifest JSON
{
  "extensions": [
    "./dist/extensions"
  ],
  "skills": [
    "./src/skills"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

Sterl is a terminal AI agent for the Stellar network. It runs the Pi coding agent runtime with a Stellar-specific extension that exposes accounts, balances, payments, path payments, and trustlines as LLM-callable tools - and routes every signature through a separate signer daemon that prompts the user for explicit approval.

The default target is Stellar Testnet. Mainnet is supported but the setup wizard, friendbot integration, and policy defaults assume you are starting on testnet.

Architecture

┌─────────────────┐       Unix socket NDJSON      ┌────────────────────┐
│  sterl (CLI)    │ ─────────────────────────────► │  sterl-signer      │
│  Pi runtime     │                                │  daemon            │
│  + extension    │ ◄───────────────────────────── │  encrypted         │
│  + tools        │       signed XDR + sig         │  Ed25519 keystore  │
└────────┬────────┘                                └────────┬───────────┘
         │                                                  │
         │  Horizon (REST) + Soroban RPC                    │  PBKDF2 + AES-256-GCM
         ▼                                                  ▼
   stellar.org / your own Horizon                  ~/.sterl/keys/stellar.json
  • The agent process never holds a key; it only ever holds the public address.
  • The signer daemon decrypts the keystore in-memory at startup, then waits for sign requests on a Unix domain socket. Every request is summarised on stderr and requires y to approve.
  • Spending is constrained by ~/.sterl/policy.yaml: per-asset per-tx and per-day caps, optional destination/issuer allowlists, and a security profile (locked / balanced / relaxed).

Install

From npm (recommended)

npm install -g sterlai      # or: pnpm add -g sterlai@latest
sterl setup                  # one-time wizard (network, keypair, friendbot, LLM)
sterl                        # chat with the agent

That's it. The npm package id is sterlai (the unscoped name sterl was rejected by npm's anti-typosquatting filter), but the binary it installs is still sterl - every command in this README works as written.

The tarball ships with the prebuilt macOS Touch ID helper (universal binary covering both Apple Silicon and Intel) so per-transaction biometric approval works out of the box on macOS 12+ - no Xcode required on the install machine.

From source

If you are working from this repo (pnpm 9 is the supported package manager):

git clone https://github.com/fozagtx/sterl.git
cd sterl
pnpm install
pnpm run build
pnpm link --global

Don't have pnpm? Install with npm install -g pnpm or corepack enable pnpm.

Update to the latest version

npm update -g sterlai        # or: pnpm up -g sterlai
npm ls -g sterlai            # confirm the installed version

Quickstart (testnet)

sterl setup            # network, keypair, friendbot, AND pick LLM provider in one go
sterl signer           # Terminal 1: unlock keystore + run signer daemon
sterl                  # Terminal 2: open the chat UI

sterl setup walks you through five things on first run:

  1. Network (testnet/mainnet)
  2. Generate an Ed25519 keypair encrypted with your passphrase
  3. Friendbot funding (testnet only)
  4. macOS Keychain (Touch ID) - on macOS, optionally save your passphrase to the login keychain so the signer unlocks with a fingerprint instead of a typed passphrase. One Touch ID prompt per sterl signer launch, not per transaction. Skip on Linux/Windows or decline to fall back to typed passphrase.
  5. Model provider - pick one of:
    • OAuth subscription (Claude Pro/Max, ChatGPT Plus/Pro, Copilot, Gemini) - finishes inside chat with /login
    • API key - detects ANTHROPIC_API_KEY / OPENAI_API_KEY / GROQ_API_KEY / etc. in your env, or run sterl login for a guided wizard that pastes the key into ~/.pi/agent/auth.json for you
    • Local Ollama - probes http://localhost:11434, lists your installed models, writes ~/.pi/agent/models.json for you
    • Skip - configure later with sterl model

You can re-run just the provider step any time: sterl model (or sterl model --ollama / --apikey / --oauth to jump straight to one path).

In the chat:

Check my balance. Send 5 XLM to alice*sterl.dev. Quote a path payment of 10 XLM into USDC. Add a trustline for USDC. Run doctor.

Other useful commands:

sterl signer status     # daemon health
sterl signer            # unlock keystore + run signer daemon (foreground)
sterl signer --no-keychain      # force a typed passphrase even if Touch ID is enrolled
sterl signer keychain enroll    # save the passphrase to macOS Keychain (Touch ID)
sterl signer keychain status    # check whether the Keychain entry exists
sterl signer keychain clear     # remove the passphrase from macOS Keychain
sterl pay <url>         # pay an MPP-gated HTTP endpoint (Stellar Machine Payments Protocol)
sterl pay --quote <url> # show the 402 challenge without paying
sterl model             # pick / re-pick LLM provider (OAuth, API key, local Ollama)
sterl model --ollama    # auto-detect localhost:11434 and wire models into ~/.pi/agent/models.json
sterl login             # guided wizard to save an API key (OpenRouter, Anthropic, Groq, Gemini, xAI, Mistral, Cerebras, ZAI)
sterl login openrouter  # skip the picker, jump straight to OpenRouter
sterl login --list      # list every supported API-key provider
sterl faucet            # friendbot-fund the configured testnet wallet
sterl faucet <G…>       # friendbot-fund any other testnet address
sterl fund <G…>         # alias of `faucet`
sterl balance           # quick balance check (configured wallet)
sterl balance <G…>      # quick balance check (any account)
sterl address           # print the configured public key (pipe-friendly)
sterl                   # plain agent launch
sterl doctor            # one-shot health report

Inside the chat you also have slash commands:

/about     show Sterl version & key paths
/balance   balances for the configured wallet
/balance G… balances for any account
/faucet    friendbot-fund the configured testnet wallet
/faucet G… friendbot-fund any testnet address
/doctor    health report
/login     OAuth into Claude Pro/Max, ChatGPT Plus/Pro, Copilot, Gemini, Antigravity
/model     pick the active model from any provider you have credentials for

Adding LLM providers (sterl login)

sterl login is a guided wizard that saves an LLM provider API key to ~/.pi/agent/auth.json (perms 0600) without making you edit JSON or set environment variables. Pair it with the in-chat /login command (which only handles OAuth) and you can wire up every supported provider without touching a config file.

sterl login              # numbered picker for every API-key provider
sterl login openrouter   # skip the picker, jump to OpenRouter
sterl login anthropic    # save a raw Anthropic API key (use /login for Claude Pro/Max OAuth)
sterl login --list       # list every supported provider with a setup URL
sterl login --help       # full usage

Supported providers (all stack into /model after login - mix freely):

Provider Best for Get a key
OpenRouter one key, hundreds of models (Claude, GPT, Llama, Qwen, Gemini, …) https://openrouter.ai/keys
Anthropic (raw) Claude API without subscription https://console.anthropic.com/settings/keys
OpenAI GPT models https://platform.openai.com/api-keys
Groq free tier; very fast Llama / Qwen / Kimi https://console.groq.com/keys
Google Gemini free tier; Gemini 2.x https://aistudio.google.com/apikey
xAI (Grok) Grok 4 family https://console.x.ai
Mistral Mistral Large, Codestral https://console.mistral.ai/api-keys/
Cerebras free tier; very fast Llama 3.3 / Qwen 3 https://cloud.cerebras.ai/
ZAI (GLM) GLM-4.6 / GLM-4.5 https://z.ai

The wizard validates the key prefix (sk-or- for OpenRouter, sk-ant- for Anthropic, gsk_ for Groq, etc.) and asks before overwriting an existing entry. Existing OAuth tokens from the in-chat /login are preserved - the wizard only edits the provider entry it touches.

After login, launch sterl and pick the model with /model (or cycle with Ctrl+P).

MCP server (Cursor, Claude Desktop, …)

Sterl ships an MCP (Model Context Protocol) server that exposes its Stellar toolkit to any MCP-compatible client over stdio.

sterl mcp                  # start the MCP server
sterl mcp --read-only      # hide write tools (send_payment, swap, change_trust)
sterl mcp --help           # full usage + example client config

Add this to your client config (Cursor: ~/.cursor/mcp.json, Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "sterl": {
      "command": "sterl",
      "args": ["mcp"]
    }
  }
}

What gets exposed (same set as the in-chat tools):

Tool Kind Notes
account_info read Public key, network, sequence, thresholds.
get_balances read XLM + trustline balances for any account.
fund_testnet_account write (faucet) Friendbot - testnet only, no signer needed.
send_payment write (signer) Pays XLM or any asset; auto create_account if needed.
change_trust write (signer) Add or remove trustlines.
swap write (signer) Strict-send path payment with a slippage floor.
quote_path_payment read Best-path quote via Horizon.
resolve_federation read SEP-2 federation lookup.
parse_sep7 read Parse web+stellar:pay?… URIs.
doctor read Health check.
show_policy read Active Sterl policy (YAML).
show_recent_activity read Tail the audit log.
quote_mpp_url read Probe an MPP-gated URL for its 402 challenge (no payment).

Write tools still go through the local sterl-signer daemon, which prompts for explicit y/N approval on its own stderr before any signature is produced - so the MCP client can request a transaction, but only the human at the daemon terminal can authorize it. Make sure the daemon is running first:

sterl signer

All MCP traffic is JSON-RPC over stdin/stdout; Sterl's own logs go to stderr so they never leak into the protocol stream.

Machine Payments Protocol (MPP)

Sterl ships first-class support for the Stellar Machine Payments Protocol, the open standard for HTTP-402-gated APIs that settle in SEP-41 token transfers on Soroban.

sterl pay --quote https://api.example.com/paid     # see the 402 challenge
sterl pay https://api.example.com/paid             # unlock keystore + pay + fetch
sterl pay -X POST -H "Content-Type: application/json" \
  -d '{"prompt":"hello"}' https://api.example.com/llm

How it works:

  1. The first request returns 402 Payment Required with a stellar-charge challenge (amount, currency SAC, recipient).
  2. sterl pay prompts you for your keystore passphrase, decrypts the Ed25519 key in-process for this single request, signs the SEP-41 transfer per draft-stellar-charge-00, and retries with X-Payment: <signed-xdr>.
  3. The server submits the transaction, returns 200 OK with the resource and an X-Payment-Receipt header.
  4. The process exits; the key is gone from memory.

Why doesn't MPP go through the signer daemon? The official @stellar/mpp client signs Soroban auth entries in its own process and has no hook for delegating to a remote signer. To preserve the "agent process never holds a key" rule, sterl pay is intentionally a separate one-shot CLI command that you (the human) invoke directly. The chat agent has a read-only quote_mpp_url tool that previews the 402 challenge but tells you to run sterl pay yourself to actually pay.

Inside chat, ask: "Quote the MPP price for https://...." The agent calls quote_mpp_url, shows you the cost, then you decide whether to run sterl pay in your terminal.

Faucet

sterl faucet taps Stellar's official testnet Friendbot and credits the target account with 10,000 XLM. It works in three places:

  • Setup wizard - automatically offered on the first run (sterl setup).
  • CLI - sterl faucet (or sterl fund) - also reports the post-funding balance and a Stellar Expert link.
  • Inside the chat - ask the agent: "Top up my testnet wallet from the faucet." It calls the fund_testnet_account tool.

Friendbot is testnet-only; the command refuses to run on mainnet.

Configuration

Path Purpose
~/.sterl/config.yaml Network, signer backend, public key, friendbot URL override, Keychain/Touch ID flag.
~/.sterl/policy.yaml Security profile and spending caps.
~/.sterl/keys/stellar.json Encrypted Ed25519 keystore (AES-256-GCM + PBKDF2).
~/.sterl/signer.sock Unix domain socket for signer IPC.
~/.sterl/db/sterl.db SQLite store for audit log + daily spending totals.
~/.sterl/agent/ Pi runtime state (sessions, settings).

Reset / start over

If you want to wipe Sterl's local state and re-run the setup wizard from scratch (new keypair, new passphrase, fresh config), paste this into a terminal that is not inside a sterl chat window:

pkill -f "sterl.*signer|cli\.js signer" 2>/dev/null    # stop any running signer daemon
rm -f ~/.sterl/signer.sock                              # remove its unix socket
security delete-generic-password -a "$USER" -s sterl-signer 2>/dev/null   # macOS only: drop the Keychain passphrase
rm -rf ~/.sterl                                         # wipe config, keystore, policy, audit DB, pi runtime state

Then re-run:

sterl setup        # or: node dist/cli.js   if you're working from the repo

What each step clears:

step what lives there consequence
pkill + rm signer.sock running sterl-signer process + its IPC socket next launch creates a fresh socket.
security delete-generic-password sterl-signer / $USER entry in the login keychain (biometric-gated passphrase) Touch ID can no longer unlock - the wizard will ask for a new passphrase and re-enroll if you opt in.
rm -rf ~/.sterl config.yaml, keys/stellar.json, policy.yaml, db/sterl.db, agent/ (Pi sessions, chat history) irreversible - your old Ed25519 keypair is gone. On testnet this is fine; on mainnet you'd lose access to any funds tied to that address.

What is not touched:

  • ~/.pi/agent/auth.json - your LLM provider credentials (Anthropic key, Claude Pro OAuth token, etc.). Kept so you don't have to re-login every time. Remove it manually if you want to switch accounts.
  • Any global sterl install or Pi cache.

If you're on mainnet or just want a safety net, snapshot first:

cp -R ~/.sterl ~/.sterl.bak-$(date +%Y%m%d-%H%M%S)

Tools the agent can call

Tool Purpose
account_info Public key, network, sequence, thresholds, signers.
get_balances XLM and trustline balances for any account.
fund_testnet_account Friendbot faucet - funds an account with 10,000 XLM on testnet.
send_payment Payment + auto-create_account for unfunded XLM destinations.
change_trust Add or remove trustlines.
quote_path_payment Strict-send path quote via Horizon.
swap Strict-send path payment with a slippage floor.
resolve_federation SEP-2 federation lookup (alice*example.comG…).
parse_sep7 Parse web+stellar:pay?… URIs (typically QR codes).
show_policy Print active policy as YAML.
show_recent_activity Tail the audit log.
doctor One-shot health check.

Security model

  • Single-key software wallet. The keystore is encrypted at rest; the daemon decrypts it once at startup and keeps the secret in process memory only.
  • The daemon is the only component that ever touches a private key. It is launched and managed independently from the agent process and you can run it under your preferred process supervisor.
  • Every signing request is interactive by default - the daemon requires explicit approval on stderr before producing a signature.
  • The policy engine is enforced in the agent process and double-checked by daily spending counts in SQLite. The signer is independent and will sign whatever the user approves; treat the policy as a guard against autonomous misuse, not as a hardware enforcement.

macOS Touch ID (optional)

On macOS, sterl setup offers to store your keystore passphrase in the user's login keychain. When that flag is on, the signer demands a real biometric approval at three distinct points:

  1. At daemon startup - before reading the Keychain, the signer pops a sterl-signer Touch ID modal. Only after you authenticate does it run /usr/bin/security find-generic-password to fetch the passphrase and decrypt the keystore in-process.
  2. At every signTransaction request - instead of a y/N stderr prompt, the daemon shows a Touch ID modal whose body contains the action summary, source account, and tx hash. Touch ID is the approval; cancelling rejects the transaction.
  3. At every sterl pay MPP charge - before settling a Stellar Machine Payments transfer, sterl pay pops a Touch ID modal whose body shows the URL, amount, and recipient. The same biometric prompt also reads the keystore passphrase out of the Keychain in one step, so a single fingerprint both approves the charge and unlocks the key for the one-shot payment.

Both prompts go through Apple's LocalAuthentication framework using LAPolicyDeviceOwnerAuthentication, so the fallback chain is:

Touch ID  →  Apple Watch tap  →  device password

How the prompt is wired

We ship a tiny ad-hoc-signed Swift helper at native/dist/sterl-biometric-arm64 (and -universal if you build it). The signer process spawns this helper with the reason text and a --tx-summary; the helper calls LAContext.evaluatePolicy and exits 0/2/3/4 depending on the outcome. Embedding a real Info.plist in the binary gives the system modal a sterl-signer title - without it, Touch ID prompts driven from JavaScript-for-Automation show "osascript".

If the prebuilt helper is missing, the wrong architecture, or fails to launch, the signer transparently falls back to a JXA-based code path that calls the same LAContext API. Functionality is identical, just the modal title is less polished. You can check which path your install is using with:

sterl doctor   # reports biometric backend: native | jxa | unavailable

Notes

  • The passphrase lives only in ~/Library/Keychains/login.keychain-db, scoped to your user account.
  • Cancel the Touch ID dialog at startup and the daemon falls back to typing your passphrase. Add --no-keychain to skip the Keychain unlock path entirely for a single launch.
  • Cancel the per-transaction Touch ID dialog and the request is rejected immediately. If LAContext itself hard-fails mid-session (e.g. biometry got removed), the daemon prints the error and drops back to a typed y/N prompt for that one transaction.
  • sterl signer keychain enroll | status | clear manages the Keychain entry. Removing it leaves the encrypted keystore file intact - only the cached unlock secret is gone.
  • Linux and Windows fall through to the typed-passphrase + typed-y/N flow; nothing on those platforms touches the macOS keychain or LocalAuthentication. The fallback is automatic - no flag needed - and applies to both the long-running signer daemon (sterl signer) and the one-shot MPP path (sterl pay).

Rebuilding the native helper

Only needed if you are forking Sterl or want to ship a universal binary. Requires Xcode command-line tools (xcode-select --install):

pnpm run build:native             # current host arch (arm64 or x86_64)
pnpm run build:native:universal   # arm64 + x86_64 fat binary via lipo

The script ad-hoc-signs the binary (codesign -s -), so distributed installs don't need an Apple Developer ID. Gatekeeper stays quiet because we invoke the helper as a subprocess rather than via open(1).

A future release can swap the software keystore for Ledger or another hardware backend without changing the IPC protocol.

Releasing

Maintainers cut a release with one command:

pnpm release           # patch:  0.1.0 -> 0.1.1
pnpm release:minor     # minor:  0.1.x -> 0.2.0
pnpm release:major     # major:  0.x.x -> 1.0.0

scripts/release.sh runs in this order so a failed publish never leaves a half-released state on the remote:

  1. preflight - must be on main, clean working tree, in sync with origin, logged in to npm
  2. typecheck + lint + test + build
  3. pnpm version <bump> - bumps package.json, makes a commit and a vX.Y.Z tag locally
  4. npm publish - prompts for the npm 2FA OTP if you have it enabled
  5. git push --follow-tags - only runs if publish succeeded

If publish fails (bad OTP, registry hiccup) the bump and tag stay local; rerun npm publish to retry, or git tag -d vX.Y.Z && git reset --hard HEAD~1 to undo.

Landing page hosting

The marketing site at https://sterlai.xyz is the static docs/ folder served by Vercel (Hobby tier, free). The deploy is fully managed by vercel.json at the repo root - every push to main triggers a fresh build within seconds, no GitHub Actions or gh-pages branch involved.

DNS lives at Spaceship (apex A 76.76.21.21, www CNAME cname.vercel-dns.com). The apex sterlai.xyz is canonical and www.sterlai.xyz 301-redirects to it. Nothing in docs/ is read by GitHub Pages anymore - the old Pages config has been disabled and docs/CNAME removed.

To preview the site locally without Vercel:

npx serve docs    # or: python3 -m http.server -d docs

What's intentionally not here

  • No mock signer. Sterl always uses the real keystore - there is no developer-mode bypass.
  • No EVM. Sterl does not speak Ethereum, Base, or any EVM chain.
  • No manage_offer. Path payments cover the AI agent's needs; resting orders are out of scope.
  • No Soroban yet. Soroban RPC client is configured but no contract tools are wired through.