Token导航 LogoToken导航TokenDH.com
研究检索操作浏览器github未标认证来源可访问许可证需确认审计提醒

nostr-client-patternsnostr 客户端模式

Agent Skill

nostr-client-patterns 用于查找、检索和筛选相关信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要根据关键词、任务场景或来源线索快速定位候选结果时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

250

周安装

10

GitHub Stars

4

下载量

81
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:nostr-client-patterns(nostr 客户端模式)
来源仓库:https://github.com/accolver/skill-maker
仓库路径:skills/nostr-client-patterns
安装命令:
npx skills add https://github.com/accolver/skill-maker --skill nostr-client-patterns
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/accolver/skill-maker --skill nostr-client-patterns

简介

用于查找、检索和筛选相关信息,快速定位候选结果。

  • 适合在关键词搜索、任务场景或来源线索下使用,提升信息获取效率。
  • 可结合来源仓库和原始 README 核验具体用法,确保适用性。
  • 安装方式:通过 npx 从 GitHub 仓库添加,支持 Codex、Claude、Cursor、Gemini CLI。
  • 注意:安装前建议确认权限范围和维护状态,避免触发不必要的联网或文件操作。

SKILL.md

Nostr Client Patterns

Overview

Implement robust Nostr client architecture. This skill covers the patterns agents miss: relay pool connection management, subscription state machines that correctly handle EOSE/CLOSED transitions, event deduplication across relays, optimistic UI with OK message error recovery, and reconnection with gap-free event delivery.

When to Use

  • The task is building or debugging a Nostr client’s relay, subscription, caching, publish, or reconnect architecture.
  • The user needs client-side patterns for handling relay pools, EOSE/CLOSED states, deduplication, or optimistic UI.
  • The problem is lifecycle management of events in a client, not protocol selection or relay-server code.
  • The request involves missed events, duplicate events, relay churn, or local cache behavior.

Do NOT use when:

  • The task is constructing event JSON or tags.
  • The work is relay-server implementation.
  • The main problem is filter design rather than client architecture.

Response format

Always structure the final response with these top-level sections, in this order:

  1. Summary — state the task, scope, and main conclusion in 1-3 sentences.
  2. Decision / Approach — state the key classification, assumptions, or chosen path.
  3. Artifacts — provide the primary deliverable(s) for this skill. Use clear subheadings for multiple files, commands, JSON payloads, queries, or documents.
  4. Validation — state checks performed, important risks, caveats, or unresolved questions.
  5. Next steps — list concrete follow-up actions, or write None if nothing remains.

Rules:

  • Do not omit a section; write None when a section does not apply.
  • If files are produced, list each file path under Artifacts before its contents.
  • If commands, JSON, SQL, YAML, or code are produced, put each artifact in fenced code blocks with the correct language tag when possible.
  • Keep section names exactly as written above so output stays predictable across skills.

Workflow

1. Design the Relay Pool

A relay pool manages WebSocket connections to multiple relays. Each relay connection has a lifecycle that must be tracked independently.

Connection states:

disconnected → connecting → connected → disconnecting → disconnected
                    ↓                        ↑
                  failed ──(backoff)──→ connecting

Key rules:

  • One WebSocket per relay (NIP-01). Never open parallel connections to the same relay URL.
  • Normalize relay URLs before comparing: lowercase scheme/host, remove trailing slash, default port 443 for wss.
  • Track state per relay: {url, ws, state, retryCount, lastConnected, activeSubscriptions, pendingPublishes}.
  • Implement connection limits (e.g., max 10 concurrent connections).
  • Use NIP-65 relay lists (kind:10002) to determine which relays to connect to for each user. Write relays for fetching a user's events, read relays for fetching events that mention them.
interface RelayConnection {
  url: string;
  ws: WebSocket | null;
  state: "disconnected" | "connecting" | "connected" | "disconnecting";
  retryCount: number;
  lastConnectedAt: number | null;
  lastEoseTimestamps: Map<string, number>; // subId → timestamp
  authChallenge: string | null;
}

See references/relay-pool.md for full implementation patterns including backoff and NIP-42 auth.

2. Implement the Subscription Lifecycle

Subscriptions follow a state machine with distinct phases. Getting this wrong causes either missing events or infinite loading states.

Subscription states:

idle → loading → live → closed
                  ↑       ↓
                  └─ replacing (new REQ with same sub-id)

The lifecycle:

  1. Open: Send ["REQ", "<sub-id>", <filters...>] to relay(s)
  2. Loading (stored events): Receive ["EVENT", "<sub-id>", <event>] for historical matches. UI shows loading indicator.
  3. EOSE received: ["EOSE", "<sub-id>"] — transition from "loading" to "live". Remove loading indicator, display stored events.
  4. Live events: Continue receiving EVENTs. These are new, real-time events. Display immediately.
  5. Close: Send ["CLOSE", "<sub-id>"] when the view unmounts or the subscription is no longer needed.

Critical transitions:

  • EOSE is per-relay. If subscribed to 5 relays, you get 5 EOSE messages. Track EOSE per relay per subscription. Transition to "live" when ALL relays have sent EOSE (or timed out).
  • Replacing: Send a new REQ with the same sub-id to change filters without closing. The relay replaces the old subscription. Reset EOSE tracking.
  • CLOSED from relay: ["CLOSED", "<sub-id>", "<reason>"] means the relay terminated your subscription. Handle by reason prefix:

- auth-required: → authenticate with NIP-42, then re-subscribe - error: → log error, maybe retry after backoff - restricted: → user lacks permission, don't retry

  • Timeout: If a relay doesn't send EOSE within a reasonable time (e.g., 10s), treat it as EOSE for that relay to avoid infinite loading.

See references/subscription-patterns.md for state machine implementation and multi-relay coordination.

3. Deduplicate Events

The same event can arrive from multiple relays. Events have globally unique IDs (SHA-256 of serialized content), so deduplication is straightforward.

Regular events (kinds 1-9999 excluding replaceable):

const seen = new Set<string>();

function processEvent(event: NostrEvent): boolean {
  if (seen.has(event.id)) return false; // duplicate
  seen.add(event.id);
  // process event...
  return true;
}

Replaceable events (kinds 0, 3, 10000-19999):

Keep only the latest per pubkey + kind. When a newer event arrives, replace the old one. Break ties by lowest id (lexicographic comparison).

const replaceableKey = `${event.pubkey}:${event.kind}`;
const existing = replaceableStore.get(replaceableKey);
if (existing) {
  if (event.created_at < existing.created_at) return false;
  if (event.created_at === existing.created_at && event.id >= existing.id) {
    return false;
  }
}
replaceableStore.set(replaceableKey, event);

Addressable events (kinds 30000-39999):

Same as replaceable, but key includes the d tag value:

const dTag = event.tags.find((t) => t[0] === "d")?.[1] ?? "";
const addressableKey = `${event.pubkey}:${event.kind}:${dTag}`;

Memory management: Use an LRU cache or periodic cleanup for the seen set. In long-running clients, unbounded sets will leak memory.

4. Implement Optimistic UI for Publishing

Show events immediately in the UI before relay confirmation. Handle failures gracefully.

The flow:

User action → Create event → Show in UI (optimistic) → Sign → Publish
                                                                  ↓
                                                          Wait for OK
                                                         ↙          ↘
                                                   OK:true        OK:false
                                                   Confirm        Show error
                                                                  Allow retry

Implementation:

  1. Create the unsigned event from user input
  2. Add to local state with status "pending"
  3. Sign the event (NIP-07 browser extension or local key)
  4. Send ["EVENT", <signed-event>] to connected relays
  5. Track OK responses per relay:

- ["OK", "<id>", true, ""] → mark relay as confirmed - ["OK", "<id>", true, "duplicate:"] → also success (relay already had it) - ["OK", "<id>", false, "reason"] → track failure reason

  1. Update UI status:

- At least one true → status "confirmed" - All relays responded false → status "failed", show error, allow retry - Timeout (e.g., 10s) with no OK → status "timeout", allow retry

OK message reason prefixes:

PrefixMeaningAction
duplicate:Already have itTreat as success
pow:Proof of work issueAdd PoW and retry
blocked:Client/user blockedShow error, don't retry
rate-limited:Too many eventsBackoff and retry
invalid:Protocol violationFix event and retry
restricted:Permission deniedShow error, don't retry
auth-required:Need NIP-42 auth firstAuthenticate, then retry
error:General relay errorRetry after backoff

5. Handle Reconnection

When a relay disconnects, reconnect without losing events or duplicating subscriptions.

Reconnection strategy:

  1. Detect disconnect (WebSocket close or error event)
  2. Set relay state to disconnected
  3. Calculate backoff: min(baseDelay * 2^retryCount + jitter, maxDelay)

- Recommended: base=1s, max=60s, jitter=0-1s random

  1. After backoff, set state to connecting, open new WebSocket
  2. On successful connect:

- Reset retryCount to 0 - Re-authenticate if relay previously required NIP-42 auth - Re-send all active subscriptions with since parameter set to the last EOSE timestamp for that relay + subscription

  1. On failed connect: increment retryCount, go to step 3

Gap-free event delivery:

The key insight: track the created_at of the last event received before disconnect (or the EOSE timestamp). On reconnect, add since: lastTimestamp to the filter to fetch only events you missed. This avoids re-fetching the entire history.

function reconnectSubscription(
  relay: RelayConnection,
  subId: string,
  originalFilter: Filter,
) {
  const lastSeen = relay.lastEoseTimestamps.get(subId);
  const reconnectFilter = lastSeen
    ? { ...originalFilter, since: lastSeen }
    : originalFilter;
  relay.ws.send(JSON.stringify(["REQ", subId, reconnectFilter]));
}

6. Cache Events Locally

Reduce bandwidth and improve load times by caching events.

Cache strategies:

  • IndexedDB (browser): Store events by id, index by kind, pubkey, created_at. Good for offline-first clients.
  • SQLite (desktop/mobile): Same schema, better query performance.
  • In-memory LRU (ephemeral): For deduplication and short-term caching.

Cache-first loading pattern:

  1. Load cached events matching the filter → display immediately
  2. Open subscription with since: latestCachedTimestamp
  3. Merge new events into cache and UI
  4. On EOSE, cache is now up-to-date

For replaceable events: Only cache the latest version. When a newer version arrives, replace the cached entry.

Checklist

  • Relay pool tracks per-relay connection state with proper lifecycle
  • One WebSocket per relay URL (normalized)
  • Exponential backoff with jitter on reconnection
  • Subscriptions track EOSE per relay, transition loading → live correctly
  • CLOSED messages handled by reason prefix (auth, error, restricted)
  • Events deduplicated by id before processing
  • Replaceable events keep only latest (by created_at, then lowest id)
  • Optimistic UI shows events before relay confirmation
  • OK messages parsed with reason prefix for error handling
  • Reconnection re-subscribes with since to avoid gaps
  • Event cache used for faster initial loads

Common Mistakes

MistakeWhy It BreaksFix
Opening multiple WebSockets to same relayViolates NIP-01, wastes resources, causes duplicate eventsNormalize URL and enforce one connection per relay
Treating EOSE as global (not per-relay)Loading state never resolves if one relay is slowTrack EOSE per relay per subscription, use timeout fallback
No deduplication of eventsSame event processed multiple times, corrupts counts/UIDeduplicate by event.id using a Set before processing
Replacing events by created_at onlyTie-breaking is undefined without id comparisonOn equal created_at, keep the event with the lowest id
Showing "failed" on duplicate: OKDuplicate means the relay already has it — that's successCheck the reason prefix, not just the boolean
Fixed retry delay (no backoff)Hammers relay during outages, may get IP-bannedUse exponential backoff: min(base * 2^n + jitter, max)
Not re-authenticating after reconnectNIP-42 auth is per-connection, lost on disconnectStore challenge, re-send AUTH event after reconnect
Reconnecting without since filterRe-fetches entire history, wastes bandwidthTrack last EOSE timestamp, use since on reconnect
Unbounded dedup SetMemory leak in long-running clientsUse LRU cache or periodic cleanup
Ignoring CLOSED messagesSubscription silently stops receiving eventsHandle CLOSED, re-subscribe if appropriate

Quick Reference

MessageDirectionFormatPurpose
REQClient→Relay["REQ", subId,...filters]Subscribe to events
EVENT (send)Client→Relay["EVENT", event]Publish an event
CLOSEClient→Relay["CLOSE", subId]End a subscription
AUTHClient→Relay["AUTH", signedEvent]Authenticate (NIP-42)
EVENT (recv)Relay→Client["EVENT", subId, event]Deliver matching event
OKRelay→Client["OK", eventId, bool, msg]Publish acknowledgment
EOSERelay→Client["EOSE", subId]End of stored events
CLOSEDRelay→Client["CLOSED", subId, msg]Subscription terminated
NOTICERelay→Client["NOTICE", msg]Human-readable info
AUTHRelay→Client["AUTH", challenge]Auth challenge (NIP-42)

Key Principles

  1. One connection per relay — Normalize URLs and enforce a single WebSocket per relay. Multiple connections cause duplicate events, wasted bandwidth, and violate NIP-01.
  2. EOSE is the loading/live boundary — Before EOSE, you're receiving stored history. After EOSE, you're receiving live events. This distinction drives UI state (loading spinners, "new event" indicators).
  3. Deduplicate before processing — Events have globally unique IDs. Check the dedup set before any processing, state updates, or UI rendering. For replaceable events, also compare created_at and id for tie-breaking.
  4. Optimistic with recovery — Show events immediately, confirm via OK. Parse OK reason prefixes to distinguish retriable errors (rate-limited, auth) from permanent failures (blocked, restricted).
  5. Reconnect without gaps — Track the last-seen timestamp per relay per subscription. On reconnect, use since to fetch only missed events. Always re-authenticate and re-subscribe after reconnection.

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

33.73%
按下载量换算27

Claude

30.72%
按下载量换算25

Cursor

18.72%
按下载量换算15

Gemini CLI

8.9%
按下载量换算7

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

操作浏览器

该 Skill 可能涉及浏览器控制能力,使用时可能读取或操作网页内容,需要在受控环境中确认权限边界。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills