Files
buzz/crates/sprout-cli/Cargo.toml
Michael Neale 84f84931f1 feat(serverless): run channels, DMs, messages, and agents against a generic Nostr relay
Adds a 'serverless' workspace mode that points the desktop app at any generic
public Nostr relay (e.g. wss://relay.damus.io) with zero Sprout server
infrastructure — no Postgres, no HTTP /query|/events bridge, no NIP-98 auth.
Reuses Sprout's native event kinds (39000 channel metadata, 39002 membership,
kind 9 messages, h-tag scoping); 'serverless' is purely a transport + auth
concern, so the server-mode path is untouched.

Transport: reads/writes go over plain WebSocket (REQ/EOSE, EVENT/OK) instead
of the HTTP bridge; NIP-42 AUTH is answered only if the relay challenges.

Desktop backend:
- AppState.serverless flag + is_serverless(); apply_workspace gains a flag
- ws_relay.rs: query_relay_ws / submit_event_ws / publish_signed_event_ws
- relay.rs: query_relay/submit_event + agent profile sync branch to WS
- events.rs: build_channel_metadata_serverless (39000) + members (39002)
- create_channel/open_dm publish those events directly (DM ids = UUIDv5 over
  sorted participants so both sides converge without a server)
- managed agents inherit SPROUT_SERVERLESS into the ACP subprocess env

Agents (full support, not a follow-up):
- sprout-acp: RestClient + HarnessRelay gain serverless mode; query/submit use
  plain WS, NIP-42 handshake skipped on (re)connect; SPROUT_SERVERLESS env/arg
- sprout-cli: SproutClient serverless WS query/submit; --serverless flag,
  to_ws_url helper, NetworkMsg error variant
- npub respond-to permissions are in-process and work unchanged

Frontend:
- Workspace.mode + workspaceMode()/isServerlessWorkspace() helpers
- relayClient.setServerless() skips the AUTH-wait on connect; late challenges
  still answered so writes succeed on relays that require auth
- ServerlessContext/useIsServerless() hides search, pulse, projects, workflows
- Serverless toggle in the Add Workspace dialog and Welcome screen

Docs: docs/SPROUT_LITE_MODE.md updated with the implementation + testing steps.
2026-06-01 20:42:29 +10:00

69 lines
1.9 KiB
TOML

[package]
name = "sprout-cli"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "Agent-first CLI for Sprout relay"
[lib]
name = "sprout_cli"
path = "src/lib.rs"
[[bin]]
name = "sprout"
path = "src/main.rs"
[dependencies]
# CLI argument parsing — derive macros + env var support (SPROUT_API_TOKEN auto-wired)
clap = { version = "4", features = ["derive", "env"] }
# HTTP client — async REST calls to the relay
reqwest = { workspace = true, features = ["json"] }
# Async runtime — tokio macros + multi-thread for reqwest
tokio = { workspace = true, features = ["macros", "rt-multi-thread", "time"] }
# WebSocket transport for serverless mode (generic relays, no HTTP bridge)
tokio-tungstenite = { workspace = true }
futures-util = { workspace = true }
# Serialization — JSON body building and response passthrough
serde = { workspace = true }
serde_json = { workspace = true }
# Structured error types with exit code mapping
thiserror = { workspace = true }
# Nostr event signing — used in `sprout auth`, auto-mint, and signed event writes
nostr = { workspace = true }
# UUID parsing for validate_uuid + event building
uuid = { workspace = true }
# Typed event builders for all write operations
sprout-sdk = { workspace = true }
sprout-core = { workspace = true }
# Base64 encoding — NIP-98 event serialization for Authorization header
base64 = "0.22"
# SHA-256 — NIP-98 payload hash tag, Blossom file hash, mem patch base-hash
sha2 = "0.11"
# Unified-diff parser and strict applier — `mem patch`
diffy = "0.5"
# Hex encoding — SHA-256 hash output for Blossom uploads
hex = { workspace = true }
# MIME type detection via magic bytes — file upload validation
infer = "0.19"
# URL parsing — extract server domain for Blossom auth tag
url = { workspace = true }
# Persona pack parsing, validation, and resolution
sprout-persona = { path = "../sprout-persona" }