- testClient: add ensureFundedWallet() with USDC/ETH balance checks and faucet requests
- fund-wallet.sh: update faucet format for Coinbase CDP API
- start-test-server.sh: use Coinbase facilitator URL, add payment endpoint verification
- test-endpoints.yaml: fund test wallet before server, pass RPC/Faucet URLs to test client
- Add MATRIX_WEBHOOK_URL and MATRIX_SERVER_ID config fields (optional, defaults to hostname)
- Create internal/server/webhook.go: WebhookSender with exponential backoff retry (5 attempts),
first/last attempt logging, non-blocking goroutine integration
- Wire webhook into OnAfterSettle callback in server.go (goroutine, fire-and-forget)
- Migrate all persistent interface{} to any (Go 1.25.4 convention):
db.go: ...any in DBTX interface
models.go: Embedding any
FindSimilarUsers.sql.go: QueryEmbedding any, Embedding any
UpsertStyleProfiles.sql.go: Embedding any
- Migrate server.go map[string]interface{} to map[string]any in webhook payload
- Add TestStoreSaveTransaction integration test (skipped without TEST_DATABASE_URL)
- Add TestSettleHook unit test for param mapping
- TestSettleHook (internal/server/mcp_test.go): verifies SaveTransactionParams field mapping
- Tests all 6 params: X402TransactionHash, Amount, Asset, Network, Recipient, Sender
- Ensures params are settable without panic, critical for legal compliance of x402 flow
- TestStoreSaveTransaction (internal/store/integration_test.go): integration test against real Postgres
- Exercised SaveTransaction query end-to-end with full param mapping
- Skipped without TEST_DATABASE_URL (same pattern as TestStoreRoundTrip)
- Verifies: insert returns ID and CreatedAt, ID is positive, CreatedAt is valid
- Uses unique transaction hash per test run via time.Now().Unix()
Both tests ensure the critical transaction-saving mechanism is verified at both unit and integration levels, providing regression protection for the legal-compliance x402 payment flow.
- TestSaveTransactionSQL: verifies the sqlc-generated SaveTransaction query constant matches the source SQL file
- TestSaveTransactionParams: verifies SaveTransactionParams struct can be constructed with valid and edge-case values (empty hash, empty amount)
Both tests are pure-unit tests that don't require a database connection. They ensure the critical transaction-saving SQL query and its parameter mapping remain consistent, which is important for legal compliance of the x402 payment flow.
- Add missing ModelVersion, SchemaVersion, AlgorithmVersion fields to UserStyleProfile struct
- Update sqlc version from v1.30.0 to v1.31.1 in db.go and models.go
- Fix any/interface{} type consistency in db.go
- Rewrite GetStyleProfile, FindSimilarUsers, UpsertStyleProfiles SQL queries
to use @named parameters instead of positional placeholders
- Regenerate sqlc Go files with meaningful parameter names
- Update server.go and integration test to use new parameter field names
- Fix Similarity int32→float64 type conversion
- Add upto scheme support for dynamic pricing on getStyleProfile
- computeTwitterStyleProfileCost: calculates actual charge based on posts fetched + user lookup
- User read: /bin/bash.01 (if not cached)
- Posts read: /bin/bash.005 per post, max 50 posts = /bin/bash.25
- Minimum: /bin/bash.10
- Maximum: /bin/bash.26
- handleRESTCall: apply settlement override for upto scheme on getStyleProfile
- GetStyleProfileResponse: local schema type for cost calculation
- Change base URI from api.twitter.com to api.x.com/2
- Rate limit: honor x-rate-limit-remaining and x-rate-limit-reset headers
- Remove GetActor/SearchActors from Twitter client (policy: prohibits reselling user/profile data)
- Block Twitter from getActor, searchActors, getPost, getLatestPosts, getPostsInTime, getFeed in MCP server
- Allow Twitter for getStyleProfile with exact /bin/bash.10 pricing (minimum across all platforms)
- Remove GetActor/SearchActors from twitterBackend interface
- Update tests for new Twitter restrictions
- Add upto EVM scheme support for future dynamic pricing
Return merged results whenever at least one platform succeeds (even if others errored or
returned empty). Only surface an error when every platform failed, so a caller is never
billed for a search that produced nothing while still getting partial results when available.
Co-Authored-By: Claude (AI assistant) <noreply@anthropic.com>
The x402 layer only settles payment when a tool handler succeeds (server.go:604-605
creates a cancellation dispatcher, not a settlement, on handler error / non-2xx). So
returning a real error is what prevents billing a caller for a deliverable they didn't
ask for.
- Revert the Ollama-down graceful degradation in buildStyleProfile: getStyleProfile and
findSimilarUsers now return an error when embedding/summary generation fails, instead of a
successful zero-embedding profile.
- searchActors now fails fast: if any platform client errors, the error is surfaced rather
than silently returning a partial (other-platforms-only) result.
Co-Authored-By: Claude (AI assistant) <noreply@anthropic.com>
Runnable (HTTP-mocked) unit tests:
- activitypub: GetActor (accounts/lookup) plus id-lookup fallback, and SearchActors
(accounts/search).
- twitter: GetActor (/users/by/username) and SearchActors (/users/search).
- nostr: SearchActors best-effort path (non-resolvable query returns empty, no network).
- store: extractUpSQL parsing helper.
Store integration test (TestStoreRoundTrip) is gated behind TEST_DATABASE_URL and skipped
when no Postgres+pgvector is available; it runs Migrate and exercises the Upsert/Get/
FindSimilarUsers round-trip against a real database.
Co-Authored-By: Claude (AI assistant) <noreply@anthropic.com>
Introduce a shared actor.Profile type (internal/actor) so every platform client can return a
common shape without importing the server package (which would be an import cycle).
- Bluesky: GetActor (app.bsky.actor.getProfile) and SearchActors (app.bsky.actor.searchActors) via XRPC.
- ActivityPub: GetActor (accounts/lookup, falling back to accounts/{id}) and SearchActors
(accounts/search).
- Nostr: GetActor subscribes to kind:0 metadata; SearchActors is best-effort (resolves a
pubkey/npub query to a single profile; Nostr has no global actor search).
- Twitter: GetActor (/users/by/username) and SearchActors (/users/search).
server: register getActor and searchActors as payable tools; searchActors fans the query out
to all four platforms and merges results, skipping any platform that errors.
Schema fixes: document that getPost.collection is Bluesky-only, and that getFeed.uri is a feed
URI for Bluesky but an author handle for other platforms.
Co-Authored-By: Claude (AI assistant) <noreply@anthropic.com>
- Add dependency-free in-process schema migrator (internal/store/migrate.go) that applies
sql/schema/*.sql in order, tracking versions in schema_migrations. Gated by MIGRATE_ON_START
and MIGRATIONS_DIR (default sql/schema).
- Fix migration ordering: 002 now creates the 'vector' extension before defining the
user_style_profiles table (previously the extension was only created later in 003).
- Bound concurrent background enrichment with a 4-slot semaphore so bursts of live
getStyleProfile calls cannot overwhelm Ollama.
- buildStyleProfile degrades gracefully when Ollama is down: stores a zero embedding and a
keyword-based fallback summary instead of failing the whole request.
- Add a /readyz side server (READYZ_PORT, default 9090) that checks DB connectivity and
Ollama availability.
Co-Authored-By: Claude (AI assistant) <noreply@anthropic.com>
Add runnable (HTTP-mocked) unit tests for the client methods added when extending the
post tools to all platforms:
- twitter: GetPost (single-tweet object response + author expansion + public_metrics) and
GetPostsInTimeframe (start_time/end_time query params + metrics)
- activitypub: GetPostsInTimeframe (paginated Mastodon statuses within a window)
- nostr: resolvePubkey/resolveEventID resolution helpers via real npub/note encoding
Also fix a latent bug in internal/nostr: resolvePubkey/resolveEventID wrapped the wrong
error variable (the nip19 error instead of the hex-decode error) when the 64-hex fallback
failed, producing misleading messages.
Co-Authored-By: Claude (AI assistant) <noreply@anthropic.com>
Previously these tools were Bluesky-only. They now dispatch by platform:
- getPost: Bluesky uses actor+collection+rkey; ActivityPub uses GetPost(statusID);
Nostr uses GetPost(noteID); Twitter uses GetPost(tweetID)
- getLatestPosts / getPostsInTime: delegate to each platform client
- getFeed: Bluesky uses GetFeed; other platforms treat the URI as an author handle and
return their latest posts (no custom-feed concept elsewhere)
Client additions:
- twitter: GetPost, GetPostsInTimeframe; Post now carries like/repost/quote/reply
counts from public_metrics (populated in GetLatestNPosts and GetPostsInTimeframe)
- nostr: GetPost, GetPostsInTimeframe (relay queries with Since/Until); resolveEventID
helper mirroring resolvePubkey
Server gains normalized mapping/dispatch helpers (bskyPostViewToResponse,
apPostToResponse, nostrPostToResponse, twitterPostToResponse, fetchSinglePost,
fetchLatestPosts, fetchPostsInTime, fetchFeed) that return a uniform
GetPostResponsePost, mapping missing engagement counts to 0.
Co-Authored-By: Claude (AI assistant) <noreply@anthropic.com>
- register findSimilarUsers (price 0.05) which reuses requestStyleProfile to get the source
persona embedding, encodes it with utils.FormatVector, and queries the existing (previously
unused) FindSimilarUsers pgvector query for ranked cross-platform matches; self excluded
- add FindSimilarUsersRequest/Response/SimilarUser types and an OutputSchema for the tool
- fix generated store: FindSimilarUsersRow.Similarity int32 -> float64 (SQL returns
1 - (embedding <=> $1::vector), a float8; scanning into int32 would error)
- refactor requestStyleProfile into a shared requestStyleProfileForced core (resolve -> cache
check -> build) so a specific platform can be targeted regardless of identifier format
- on a live getStyleProfile request, fire-and-forget enrichCrossPlatform: bounded fan-out
(<=3 goroutines) tries the same identifier on every other platform, logs/skips mismatches,
and still obeys the 1-week update gate; uses a detached context (context.WithoutCancel +
5m timeout) so it outlives the request
- trigger only on live tool calls (not the prewarmer) and gated by ENRICH_CROSS_PLATFORM
(default true)
- add platformFromString helper
- Nostr client (fiatjaf.com/nostr) fetching kind:1 notes from relays
- Twitter client using raw Twitter API v2 (no external X library), optional (no token = lazy error)
- getStyleProfile dispatches by platform; a profile is updated only when a request is
made and the cached profile is older than a week (served from cache otherwise)
- shared requestStyleProfile path used by both the tool and the prewarmer
- StartPrewarm discovers interesting authors (from a feed URI or seed list) and
pre-fetches at most one profile per minute; fresh profiles are never re-embedded