procyon 0.1.0

Terminal development harness for Stellar and Soroban smart contracts, driven by a language model
# Procyon

A terminal harness for Stellar and Soroban development, with a language model driving the tools.

You describe what you want in plain language. Procyon gives the model tools scoped to your
workspace — search and edit code, build, deploy, invoke, inspect events, look facts up over MCP —
and shows the work as it happens.

<img src="docs/screens/boot.png" alt="Procyon on launch: the Stellar mark, the working directory, the active network and model, and a prompt" width="706">

The interface is a transcript, a prompt and one status line. Where you are — network, provider,
model, contract — lives in that line rather than in a panel, and the network is coloured, with
mainnet called out. Everything else is one keystroke away instead of permanently on screen.

<img src="docs/screens/palette.png" alt="The command palette open over the transcript, filtered list of slash commands" width="706">

`Ctrl+K` opens the palette; `/` completes commands inline as you type; `?` on an empty prompt
prints the shortcuts. `/status` is where the full picture lives:

<img src="docs/screens/status.png" alt="/status output: network, account, contract, provider, model, explain mode and MCP servers" width="706">

## Install

```bash
cargo install procyon
```

Needs Rust 1.88 or newer. The build takes a few minutes the first time. Then run it from a project
directory — Procyon treats **the directory you launch it from** as the workspace, and every file
tool is confined to it.

```bash
cd ~/my-soroban-project
procyon
```

Optional, and only if you want the tools that use them: the
[`stellar` CLI](https://developers.stellar.org/docs/tools/developer-tools/cli/stellar-cli) for
account keys and contract interfaces, and Node/`npx` for the [Caatinga](#caatinga) tools.

## Status

Early. The core loop works — streaming, tool calling, workspace confinement, context compaction,
MCP, session persistence, twelve providers — with 497 tests and CI. What to expect:

- **No per-operation approval.** File writes are confined to the workspace but not individually
  confirmed. Mainnet is a switch you set once, not a prompt. See [Safety]#safety.
- **Deploy and invoke are not proven end to end.** The Caatinga flags are verified against the
  pinned CLI version, but the paths depend on your project's own config.
- **Personas have no tools.** `talk_to` and `party_mode` can reason and answer, but cannot read
  your files or run a build.
- **Skills and MCP servers load once per run.** Adding either means restarting.
- **Session logs are plain JSONL** and grow with the conversation.

## Configure

A credential is all you need. Procyon reads `.env` from the working directory, or the environment:

```bash
ANTHROPIC_API_KEY=sk-ant-...       # or the variable for your provider
```

Everything else is optional, in `~/.config/procyon/config.toml`:

```toml
default_network = "testnet"          # local | testnet | mainnet
theme = "dark"                       # dark | light
provider = "anthropic"               # see Providers
default_model = "claude-sonnet-5"
allow_mainnet = false                # see Safety

# MCP servers. Tools arrive namespaced <name>__<tool>.
[[mcp_servers]]
name = "raven"
url = "https://raven.stellar.org/mcp"
auth = "oauth"                       # or token_env = "PROCYON_MCP_TOKEN"
```

A malformed config fails before the terminal switches to raw mode, so you get a readable error
rather than a scrambled screen.

## Providers

Twelve, over two protocols: Anthropic's `/v1/messages` and the OpenAI `/chat/completions` dialect.
Naming a vendor presets its endpoint and credential variable, so you never look either up.

`anthropic` · `openai` · `deepseek` · `groq` · `openrouter` · `cerebras` · `fireworks` ·
`togetherai` · `xai` · `ollama` · `lmstudio` · `openai-compatible`

The credential variable follows the vendor — `DEEPSEEK_API_KEY`, `GROQ_API_KEY`, and so on — or
point `api_key_env` at your own. **`ollama` and `lmstudio` need no credential at all**, which makes
them the shortest path to running Procyon with no provider account. An explicit `base_url`
overrides any preset, for a proxy or self-hosted gateway.

Switch at runtime with `/model`; the conversation carries over.

## Using it

| Key | |
|---|---|
| `Ctrl+B` / `Ctrl+T` / `Ctrl+D` | Ask the agent to build / test / deploy |
| `Ctrl+K` | Command palette |
| `/` | Complete a command inline |
| `?` | Shortcuts, on an empty prompt |
| `↑` `↓` / `PageUp` `PageDown` | Scroll the chat |
| `Alt+↑` `Alt+↓` | Previous / next command |
| `Ctrl+C` | Quit |

Scrolling is anchored — incoming messages do not yank the view while you read back.

| Command | |
|---|---|
| `/help` | Commands and shortcuts |
| `/status` `/project` | Network, account, contract, provider, MCP servers |
| `/network <local\|testnet\|mainnet>` | Switch network |
| `/model [set\|provider\|model] …` | Show or switch provider and model |
| `/explain` | The agent narrates each step before taking it |
| `/clear` | Clear the chat |

Conversations are logged as they happen, so a crash loses nothing:

```bash
procyon --sessions       # list sessions for this directory
procyon --resume [id]    # resume the most recent, or a specific one
```

## Tools

Built in, plus whatever your MCP servers expose. The agent picks them; you do not call them
directly.

- **Code**`list_dir`, `glob`, `grep`, `read_file`, `write_file`, `edit_file`
- **Projects**`project_init`, `project_info`
- **Contracts**`caatinga_build`, `caatinga_deploy`, `caatinga_read`, `caatinga_invoke`,
  `caatinga_doctor`, `stellar_invoke`, `generate_bindings`
- **Accounts**`account_create`, `account_list`, `account_balance`
- **Inspection**`run_tests`, `get_contract_events`, `filter_contract_events`, `generate_docs`
- **Delegation**`list_skills`, `run_skill`, `list_personas`, `talk_to`, `party_mode`,
  `spawn_agent`
- **Housekeeping**`list_plugins`, `check_update`

## Caatinga

Deploys go through [`@caatinga/cli`](https://www.npmjs.com/package/@caatinga/cli). The `caatinga_*`
tools **only run in a project with a `caatinga.config.ts`**, and they deliberately do not accept:

| Not a parameter | Comes from |
|---|---|
| A wasm path | `caatinga.config.ts`, via the contract name |
| A contract id | `caatinga.artifacts.json`, the source of truth per network |
| A bindings directory or language | `caatinga.config.ts` |

That is the point. An id copied out of a deploy log is stale after the next redeploy, and bindings
written somewhere the config does not expect are bindings the app never imports. Deploying without
naming a contract deploys all of them in dependency order, then regenerates bindings and syncs
frontend env. `dry_run` estimates cost without submitting.

**Signing is always by `stellar` CLI identity alias**, such as `alice`. A secret key, seed phrase
or raw address is rejected before anything spawns, and the rejection does not echo the value back.

Prefer `caatinga_read` over `caatinga_invoke` to read a value — it simulates, so it signs nothing
and costs nothing. Run `caatinga_doctor` when something fails for a reason that is not in the
contract; most of those are environment drift.

## Skills and personas

A **skill** is a directory with a `SKILL.md` — frontmatter plus a Markdown body loaded on demand,
so a hundred installed skills cost two lines of description each rather than their full text.
Discovered from `~/.claude/skills/`, `~/.config/procyon/skills/`, `.procyon/skills/` and
`.stellar-build/skills/`.

Add a `customize.toml` with an `[agent]` section and the skill becomes a **persona**. `talk_to`
runs one as a sub-agent; `party_mode` runs several in parallel and returns each perspective:

```
You: party mode — should this contract be upgradeable?

🏗 Tyler (2 round trips, 4.1s):
  Upgradeability is a governance question before it is a technical one…

💻 Elliot (1 round trip, 2.8s):
  If you do, the storage layout has to be versioned from day one…
```

## Grounding

The difference between an agent that *sounds* knowledgeable about Stellar and one that *is* comes
down to whether it can check.

Procyon rebuilds the workspace context every turn — project, network, registered contracts,
accounts, installed tooling, connected servers — and is an MCP client, over HTTP or stdio. The
system prompt tells the model to look Stellar facts up rather than recall them, and to say so when
nothing is connected.

[Stellar Raven](https://raven.stellar.org) is the intended companion. Declare it with
`auth = "oauth"` and sign in once:

```bash
procyon --authorize raven
```

That runs OAuth 2.1 with PKCE and stores the refresh token under
`~/.local/share/procyon/oauth/` with mode 0600. A server that fails to connect is reported and
skipped — an outage never stops Procyon from starting — and connections rebuild themselves after a
dropped transport.

## Safety

**Workspace confinement.** Every file tool resolves paths against the launch directory and rejects
anything outside it, including a path behind a symlinked parent pointing out of the tree.

**Mainnet is off until you turn it on.** Every tool that signs and submits refuses the public
network unless `allow_mainnet = true` is set, or `PROCYON_ALLOW_MAINNET=1` for one run. This is a
gate, not a prompt instruction: the agent cannot grant it to itself. Those tools also require the
network as an explicit argument, because a default Procyon cannot see would make "unspecified"
indistinguishable from "mainnet".

Two limits: the gate matches the network by name, so a custom network pointing at the public one
under another name escapes it; and it is a switch, not a per-operation confirmation.

**Secrets stay out of the repo.** `.gitignore` covers `.env` and `.procyon/`. Stellar secret keys
stay in the `stellar` CLI keystore, referenced by name. Provider and MCP credentials are named by
variable in `config.toml` — `api_key_env` and `token_env` hold the name, never the value.

**Long conversations.** At 80% of the context window Procyon summarizes older turns into a
checkpoint. The cut never separates a tool call from its result; if no safe cut exists, it refuses
to compact rather than corrupt the transcript.

## Development

```bash
cargo test                 # 497 tests, no network, no toolchain
cargo test -- --ignored    # 8 more, needing network or an installed toolchain
cargo clippy --all-targets -- -D warnings
cargo fmt --all --check
```

A tool is a `Tool` impl under `src/tools/`, registered in `agent_task`. Two rules: resolve any
caller-supplied path through `tools::paths::resolve_in_workspace`, and never block the runtime —
use `tokio::fs`, `tokio::process` or `spawn_blocking`, or the UI freezes while your tool runs.

`src/agent` holds the vocabulary a conversation is made of and knows no wire format; `src/anthropic`
and `src/openai` each hold one client, one `wire` and one `stream`. `ContentPart` is what the
session log persists, so its shape is a durability commitment — which is why a provider's request
shapes are kept out of it.

## License

[Apache-2.0](LICENSE).