Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ members = [
"crates/git-credential-nostr",
"crates/git-sign-nostr",
"crates/sprout-pair-relay",
"examples/countdown-bot",
]
exclude = ["desktop/src-tauri"]
resolver = "2"
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ That's it — you're running Sprout locally.

## Going Further

### Explore examples

See [`examples/`](examples/) for reference implementations, including a tiny non-AI bot that can authenticate either as its own standalone identity or through the owner-attested agent auth path.

### Launch an agent (MCP)

```bash
Expand Down
18 changes: 18 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Examples

This directory contains reference material for building on Sprout beyond the desktop app and AI agents.

## `countdown-bot/`

A small non-AI bot that connects directly to the Sprout relay over WebSocket, authenticates with NIP-42, subscribes to one channel, and replies to deterministic commands like `!countdown 5` and `!fib 8`.

It demonstrates two identity paths:

1. **Standalone bot identity** — the bot authenticates with its own key and must be explicitly admitted to closed/allowlisted relays.
2. **Owner-attested / agent OAuth path** — the bot authenticates with its own key while presenting the same `SPROUT_AUTH_TAG` NIP-OA credential that Sprout agents receive from the owner/agent OAuth flow, so a relay can admit it because its owner is already a relay member.

See [`countdown-bot/README.md`](countdown-bot/README.md) for usage.

## `meadow-core/`

A persona-pack example for Sprout agents.
15 changes: 15 additions & 0 deletions examples/countdown-bot/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "countdown-bot"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
anyhow = "1"
futures-util = { workspace = true }
nostr = { workspace = true }
serde_json = { workspace = true }
sprout-sdk = { path = "../../crates/sprout-sdk" }
tokio = { workspace = true, features = ["rt-multi-thread", "macros", "time", "signal"] }
tokio-tungstenite = { workspace = true }
url = { workspace = true }
115 changes: 115 additions & 0 deletions examples/countdown-bot/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Countdown Bot

A tiny non-AI Sprout bot example.

The bot is deliberately boring and algorithmic: it listens to one Sprout channel
and replies to simple commands:

- `!countdown 5` → `5 4 3 2 1 🚀`
- `!fib 8` → `13 8 5 3 2 1 1 0`
- `@Countdown Bot fib 8` → `13 8 5 3 2 1 1 0`

It demonstrates that Sprout participants do not have to be LLM agents. Any
process that can hold a Nostr key, answer NIP-42 auth, publish a kind `0`
profile, subscribe to events, and publish kind `9` channel messages can be a bot.

On startup it publishes a profile named **Countdown Bot** with a small embedded
SVG clock icon, then best-effort publishes a NIP-29 `kind:9000` self-add with
`role=bot`. That channel membership is what makes the bot show up in the
members list and in Sprout's mention autocomplete.

## Auth paths

### 1. Standalone bot identity

The bot authenticates with its own key only.

Use this when the bot should be admitted as its own independent relay identity.

```bash
SPROUT_RELAY_URL=ws://localhost:3000 \
SPROUT_CHANNEL_ID=<channel-uuid> \
SPROUT_BOT_PRIVATE_KEY=<bot-nsec-or-hex-secret> \
SPROUT_BOT_AUTH_MODE=standalone \
cargo run --manifest-path examples/countdown-bot/Cargo.toml
```

On a closed or allowlisted relay, add the bot pubkey as a relay member or to the
configured pubkey allowlist before starting it. This path does not reuse an
owner's access; revoking the bot requires removing this bot pubkey.

### 2. Owner-attested bot identity

The bot still signs messages with its own key, but its NIP-42 `AUTH` event also
carries a NIP-OA `auth` tag signed by an owner key that is already allowed on the
relay. This reuses the same owner-attestation credential path that Sprout agents
receive after the owner/agent OAuth flow: the relay can let the bot connect
because the owner is a relay member, without making the bot key a persistent
relay member.

Generate the auth tag on the fly:

```bash
SPROUT_RELAY_URL=ws://localhost:3000 \
SPROUT_CHANNEL_ID=<channel-uuid> \
SPROUT_BOT_PRIVATE_KEY=<bot-nsec-or-hex-secret> \
SPROUT_OWNER_PRIVATE_KEY=<owner-or-agent-nsec-or-hex-secret> \
SPROUT_BOT_AUTH_MODE=owner-attested \
cargo run --manifest-path examples/countdown-bot/Cargo.toml
```

Or precompute and pass the tag explicitly:

```bash
SPROUT_AUTH_TAG='["auth","<owner-pubkey>","","<sig>"]' \
SPROUT_BOT_AUTH_MODE=owner-attested \
# plus SPROUT_RELAY_URL, SPROUT_CHANNEL_ID, SPROUT_BOT_PRIVATE_KEY
cargo run --manifest-path examples/countdown-bot/Cargo.toml
```

Relay requirements for this path:

- `SPROUT_REQUIRE_RELAY_MEMBERSHIP=true` on closed relays.
- `SPROUT_ALLOW_NIP_OA_AUTH=true` so owner-attested non-member bot keys can be
admitted.
- The owner pubkey must be an active relay member.

Relay access and channel access are separate. Owner-attested auth can admit the
bot to the relay, but the bot still publishes as its own pubkey. The bot tries to
self-add to open channels as a `bot` member on startup. For private channels,
an owner/admin must add the bot pubkey to the channel membership before expecting
it to appear in members, resolve in mention autocomplete, or read/write messages.

## Try it locally

1. Start Sprout:

```bash
. ./bin/activate-hermit
just setup
just relay
```

2. Create or choose a channel in the desktop app and copy its UUID.

3. Run the bot with one of the auth paths above.

4. In the channel, send:

```text
!countdown 5
!fib 8
@Countdown Bot fib 8
```

## Notes

- Commands are bounded (`!countdown` and `!fib` max 100) so one message cannot
make the bot spam the relay. Out-of-range commands get an explicit help reply.
- `!fib` replies in descending order because this example is a countdown bot.
- Mention commands require both text like `@Countdown Bot fib 8` and a `p` tag
for the bot pubkey. The Sprout UI adds that tag when the bot is selected from
mention autocomplete.
- The bot ignores its own messages to avoid feedback loops.
- The example uses direct WebSocket + NIP-42 instead of MCP so the protocol path
is easy to inspect in one small file.
Loading
Loading