Files
eranos/.agents/skills/nostr-relay-pools/SKILL.md
T
Alex Gleason bd68a32708 Split AGENTS.md into skills; compress to 358 lines
Extract eleven topic areas into loadable skills so AGENTS.md can serve
as a scannable overview instead of a specification dump. The file
shrinks from 1480 to 358 lines (~76%) while keeping every concrete
rule, critical code pattern, and pointer that an agent needs on first
read.

New Ditto-specific skills:
- nostr-kinds: NIP-vs-custom-kind decision framework, kind ranges,
  tag design, content-vs-tags, NIP.md update rule, and Ditto's
  seven-location UI registration checklist for new kinds (NoteCard,
  PostDetailPage, extraKinds.ts, KIND_LABELS/KIND_ICONS in
  CommentContext, WELL_KNOWN_KIND_LABELS in ExternalContentHeader,
  EmbeddedNote/EmbeddedNaddr, ReplyComposeModal).
- nostr-publishing: useNostrPublish, the read-modify-write pattern
  via fetchFreshEvent + prev for replaceable/addressable events,
  published_at contract, and d-tag collision prevention.
- nostr-queries: the standard useNostr + useQuery pattern,
  combining kinds into one filter to avoid rate limits, and the
  NIP-52 validator walkthrough.
- theming: @fontsource install flow, the Ditto runtime font-loader
  path (sanitizeUrl + sanitizeCssString), color scheme variables,
  useTheme toggle, and the isolate + negative-z-index gotcha.
- ci-cd-publishing: Zapstore NIP-46 bunker auth (zsp +
  nip46-auth.mjs), nsite deploys (nsyte nbunksec + configured
  relays/servers), and Google Play AAB uploads via fastlane supply
  (service-account JSON base64 encoding and rotation).
- capacitor-compat: WKWebView/WebView limitations, the
  downloadTextFile / openUrl helpers in src/lib/downloadFile.ts,
  platform detection, and the full plugin list.
- git-workflow: pre-commit validation order and the Regression-of:
  trailer convention used by the release skill's changelog
  generator.

Ported from mkstack, lightly adapted where needed:
- nip19-routing: root-level /:nip19 routing and filter construction
  patterns (adapted to reference Ditto's existing NIP19Page).
- nostr-relay-pools: nostr.relay() and nostr.group() for targeted
  queries.
- nostr-encryption: NIP-44 / NIP-04 via the user's signer.
- file-uploads: useUploadFile + Blossom + NIP-94 imeta tag
  construction.

AGENTS.md itself now follows mkstack's density — concrete rules inline,
one code example per section, pointer to the matching skill for details.
The enumerations that previously bloated it (every shadcn primitive,
every hook, every Capacitor plugin, the full NostrMetadata type dump,
the NIP-19 prefix reference table, etc.) are either removed in favor
of "ls the directory" or moved into their skill.
2026-04-26 23:13:30 -05:00

93 lines
3.7 KiB
Markdown

---
name: nostr-relay-pools
description: Query or publish to specific Nostr relays or curated relay groups using nostr.relay() and nostr.group(), instead of the default connection pool. Useful for debugging, testing, specialized relays, or geographically-targeted publishing.
---
# Targeted Nostr Relay Connections
By default, the `nostr` object returned from `useNostr` uses the app's connection pool: it reads from one of the configured relays and publishes to all of them. For most features this is exactly what you want.
Use this skill when you need **more granular control** — talking to a single relay, a curated group of relays, or debugging a specific relay's behavior.
## Single Relay: `nostr.relay(url)`
```ts
import { useNostr } from '@nostrify/react';
function useSpecificRelay() {
const { nostr } = useNostr();
// Connect to a specific relay
const relay = nostr.relay('wss://relay.damus.io');
// Query from this relay only
const events = await relay.query([{ kinds: [1], limit: 15 }]);
// Publish to this relay only
await relay.event({ kind: 1, content: 'Hello from a specific relay!' });
}
```
**Good fits:**
- Testing a relay's behavior in isolation
- Debugging connectivity or rate-limiting issues
- Querying content that only lives on a specialized relay (paid relays, private relays, niche communities)
- Health checks / admin tooling
## Relay Group: `nostr.group(urls)`
```ts
import { useNostr } from '@nostrify/react';
function useRelayGroup() {
const { nostr } = useNostr();
// Create a group of specific relays
const relayGroup = nostr.group([
'wss://relay.damus.io',
'wss://relay.primal.net',
'wss://nos.lol',
]);
// Query from all relays in the group (deduplicated)
const events = await relayGroup.query([{ kinds: [1], limit: 15 }]);
// Publish to all relays in the group
await relayGroup.event({ kind: 1, content: 'Hello from a relay group!' });
}
```
**Good fits:**
- Publishing to a curated set of trusted relays for a specific feature
- Community-scoped queries (e.g. a set of relays known to host a particular topic)
- Geographic/region-targeted delivery
- Load-balancing reads across a known-good subset
## API Consistency
Both the `relay` object and the `group` object expose the **same interface** as the top-level `nostr` object:
- `.query(filters, opts?)` — request events matching filters
- `.req(filters, opts?)` — open a streaming subscription
- `.event(event)` — publish a signed event
- All other Nostrify methods
This means you can drop them into any existing hook or helper that expects a `nostr`-shaped object.
## Choosing Between Pool, Group, and Single Relay
| Scenario | Use |
|----------------------------------------------------|---------------------|
| Default app queries, best reach for publishing | `nostr` (pool) |
| Trusted subset, community-specific publishing | `nostr.group([…])` |
| Single-relay debugging or specialized relay access | `nostr.relay(url)` |
## Tips
- **Don't hard-code user-facing relay lists.** If a feature should publish to "the user's write relays", read from `AppContext.config.relayMetadata` (NIP-65) instead of hard-coding URLs.
- **Compose with TanStack Query.** Wrap `relay.query(...)` / `group.query(...)` inside a `useQuery` hook exactly as you would with the default `nostr` object; the caching layer is identical.
- **Handle unreachable relays.** Specific relays can be offline, rate-limited, or slow. Always wrap calls in `try/catch` and respect the abort signal from the query function (`c.signal`).
- **Avoid leaking subscriptions.** When using `.req(...)` for streaming, always close the subscription on unmount (`controller.abort()` or the returned disposer).