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
94 changes: 94 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,106 @@ jobs:
wget
- name: Install desktop dependencies
run: just desktop-install-ci
- name: Install Playwright Chromium
run: cd desktop && pnpm exec playwright install --with-deps chromium
- name: Desktop lint and format
run: just desktop-check
- name: Desktop build
run: just desktop-build
- name: Desktop smoke e2e
run: cd desktop && pnpm exec playwright test --project=smoke
- name: Desktop Tauri check
run: just desktop-tauri-check
- name: Upload desktop e2e artifacts
if: failure()
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
with:
name: desktop-e2e-artifacts
path: |
desktop/playwright-report
desktop/test-results
if-no-files-found: ignore

desktop-e2e-integration:
name: Desktop E2E Integration
runs-on: ubuntu-latest
timeout-minutes: 45
permissions:
contents: read
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
- uses: Swatinem/rust-cache@ad397744b0d591a723ab90405b7247fac0e6b8db # v2
with:
workspaces: desktop/src-tauri
- name: Install desktop dependencies
run: just desktop-install-ci
- name: Install Playwright Chromium
run: cd desktop && pnpm exec playwright install --with-deps chromium
- name: Desktop build
run: just desktop-build
- name: Start integration services
run: docker compose up -d
- name: Wait for integration services
run: |
wait_healthy() {
local service="$1"
local container="$2"
for attempt in $(seq 1 60); do
status=$(docker inspect --format='{{.State.Health.Status}}' "${container}" 2>/dev/null || echo "not_found")
if [ "${status}" = "healthy" ]; then
echo "${service} is healthy"
return 0
fi
sleep 2
done
docker logs "${container}" || true
return 1
}
wait_healthy "MySQL" "sprout-mysql"
wait_healthy "Redis" "sprout-redis"
wait_healthy "Typesense" "sprout-typesense"
- name: Build relay
run: cargo build -p sprout-relay
- name: Start relay
run: |
nohup env \
DATABASE_URL=mysql://sprout:sprout_dev@localhost:3306/sprout \
REDIS_URL=redis://localhost:6379 \
TYPESENSE_URL=http://localhost:8108 \
TYPESENSE_API_KEY=sprout_dev_key \
RELAY_URL=ws://localhost:3000 \
SPROUT_BIND_ADDR=0.0.0.0:3000 \
SPROUT_REQUIRE_AUTH_TOKEN=false \
./target/debug/sprout-relay > /tmp/sprout-relay.log 2>&1 &
echo $! > /tmp/sprout-relay.pid
for attempt in $(seq 1 60); do
if ! kill -0 "$(cat /tmp/sprout-relay.pid)" 2>/dev/null; then
cat /tmp/sprout-relay.log
exit 1
fi
status_code=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/api/channels || true)
if [ "${status_code}" != "000" ]; then
exit 0
fi
sleep 1
done
cat /tmp/sprout-relay.log
exit 1
- name: Seed desktop e2e data
run: bash scripts/setup-desktop-test-data.sh
- name: Desktop relay-backed e2e
run: cd desktop && pnpm exec playwright test --project=integration
- name: Upload desktop integration artifacts
if: failure()
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4
with:
name: desktop-e2e-integration-artifacts
path: |
desktop/playwright-report
desktop/test-results
/tmp/sprout-relay.log
if-no-files-found: ignore

security:
name: Security
Expand Down
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ Thumbs.db
# Scratch / working files (AI reviews, notes, drafts)
.scratch/

# Playwright artifacts
playwright-report/
test-results/
blob-report/

# sqlx offline query data (generated, not portable)
.sqlx/

Expand Down
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,21 @@ cargo run -p sprout-admin -- mint-token \
--scopes "messages:read,messages:write,channels:read"
```

Outputs a bearer token. Set it as `SPROUT_API_TOKEN` for the MCP server.
Save the `nsec...` private key and API token from the output. They are shown only once.

**6. Connect an agent via MCP**
**6. Launch an agent with the MCP extension**

```bash
SPROUT_RELAY_URL=ws://localhost:3000 \
SPROUT_API_TOKEN=<token> \
cargo run -p sprout-mcp
SPROUT_PRIVATE_KEY=nsec1... \
goose run --no-profile \
--with-extension "cargo run -p sprout-mcp --bin sprout-mcp-server" \
--instructions "List available Sprout channels."
```

The MCP server speaks stdio JSON-RPC. Wire it into any MCP-compatible agent host.
`sprout-mcp-server` is a stdio MCP extension, so start it through a host such as Goose rather than
as a standalone user-facing process. See [TESTING.md](TESTING.md) for the full multi-agent flow.

**7. Run the desktop app (optional)**

Expand Down Expand Up @@ -262,9 +266,11 @@ just reset # ⚠️ Wipe all data and recreate environment
```bash
cargo run -p sprout-relay
cargo run -p sprout-admin -- --help
cargo run -p sprout-mcp
cargo run -p sprout-mcp --bin sprout-mcp-server
```

`sprout-mcp-server` is normally launched by Goose or another MCP host.

**Database migrations** live in `migrations/`. The relay applies them automatically on startup.
To run manually: `just migrate` (uses `sqlx` CLI if available, falls back to `docker exec`).

Expand Down
2 changes: 2 additions & 0 deletions desktop/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ node_modules
.pnpm-store
dist
dist-ssr
playwright-report
test-results
*.local

# Editor directories and files
Expand Down
9 changes: 8 additions & 1 deletion desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
"check": "biome check .",
"format": "biome format --write .",
"preview": "vite preview",
"tauri": "tauri"
"tauri": "tauri",
"test:e2e": "pnpm build && playwright test",
"test:e2e:smoke": "pnpm build && playwright test --project=smoke",
"test:e2e:integration": "pnpm build && playwright test --project=integration",
"test:e2e:report": "playwright show-report"
},
"dependencies": {
"@radix-ui/react-dialog": "^1.1.15",
Expand All @@ -35,11 +39,14 @@
},
"devDependencies": {
"@biomejs/biome": "^2.4.6",
"@noble/hashes": "^2.0.1",
"@playwright/test": "^1.58.2",
"@tauri-apps/cli": "^2",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
"autoprefixer": "^10.4.27",
"nostr-tools": "^2.23.3",
"postcss": "^8.5.8",
"tailwindcss": "^3.4.17",
"tailwindcss-animate": "^1.0.7",
Expand Down
40 changes: 40 additions & 0 deletions desktop/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
testDir: "./tests/e2e",
timeout: 30_000,
retries: process.env.CI ? 2 : 0,
workers: 1,
reporter: [
["list"],
["html", { open: "never", outputFolder: "playwright-report" }],
],
use: {
baseURL: "http://127.0.0.1:4173",
screenshot: "only-on-failure",
trace: "on-first-retry",
video: "retain-on-failure",
},
projects: [
{
name: "smoke",
testMatch: "**/smoke.spec.ts",
use: {
...devices["Desktop Chrome"],
},
},
{
name: "integration",
testMatch: "**/stream.spec.ts",
use: {
...devices["Desktop Chrome"],
},
},
],
webServer: {
command: "python3 -m http.server 4173 -d dist",
cwd: ".",
reuseExistingServer: !process.env.CI,
url: "http://127.0.0.1:4173",
},
});
Loading