Token导航 LogoToken导航TokenDH.com
研究检索敏感数据github未标认证来源可访问许可证需确认审计异常

applesauce-signers苹果酱签名者

Agent Skill

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

总安装

470

周安装

20

GitHub Stars

30

下载量

165
CodexClaudeCursorGemini CLI

安装说明

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

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

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

请帮我安装这个 Agent Skill:applesauce-signers(苹果酱签名者)
来源仓库:https://github.com/purrgrammer/grimoire
仓库路径:skills/applesauce-signers
安装命令:
npx skills add https://github.com/purrgrammer/grimoire --skill applesauce-signers
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

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

skills.shnpx skills
npx skills add https://github.com/purrgrammer/grimoire --skill applesauce-signers

简介

提供 Nostr 应用中的事件签名抽象和远程签名器集成方案。

  • 适用于实现 NIP-07 浏览器扩展、NIP-46 远端签名或多签支持。
  • 封装统一 signer 接口,管理会话、权限和签名请求处理流程。
  • 需结合具体 signer 实现和安全策略使用,避免敏感操作泄露。
  • applesauce-signers 属于研究检索类 Skill,可作为该场景下的辅助能力补充。

SKILL.md

applesauce-signers Skill

This skill provides comprehensive knowledge and patterns for working with applesauce-signers, a library that provides signing abstractions for Nostr applications.

When to Use This Skill

Use this skill when:

  • Implementing event signing in Nostr applications
  • Integrating with NIP-07 browser extensions
  • Working with NIP-46 remote signers
  • Building custom signer implementations
  • Managing signing sessions
  • Handling signing requests and permissions
  • Implementing multi-signer support

Core Concepts

applesauce-signers Overview

applesauce-signers provides:

  • Signer abstraction - Unified interface for different signers
  • NIP-07 integration - Browser extension support
  • NIP-46 support - Remote signing (Nostr Connect)
  • Simple signers - Direct key signing
  • Permission handling - Manage signing requests
  • Observable patterns - Reactive signing states

Installation

npm install applesauce-signers

Signer Interface

All signers implement a common interface:

interface Signer {
  // Get public key
  getPublicKey(): Promise<string>;

  // Sign event
  signEvent(event: UnsignedEvent): Promise<SignedEvent>;

  // Encrypt (NIP-04)
  nip04Encrypt?(pubkey: string, plaintext: string): Promise<string>;
  nip04Decrypt?(pubkey: string, ciphertext: string): Promise<string>;

  // Encrypt (NIP-44)
  nip44Encrypt?(pubkey: string, plaintext: string): Promise<string>;
  nip44Decrypt?(pubkey: string, ciphertext: string): Promise<string>;
}

Simple Signer

Using Secret Key

import { SimpleSigner } from 'applesauce-signers';
import { generateSecretKey } from 'nostr-tools';

// Create signer with existing key
const signer = new SimpleSigner(secretKey);

// Or generate new key
const newSecretKey = generateSecretKey();
const newSigner = new SimpleSigner(newSecretKey);

// Get public key
const pubkey = await signer.getPublicKey();

// Sign event
const unsignedEvent = {
  kind: 1,
  content: 'Hello Nostr!',
  created_at: Math.floor(Date.now() / 1000),
  tags: []
};

const signedEvent = await signer.signEvent(unsignedEvent);

NIP-04 Encryption

// Encrypt message
const ciphertext = await signer.nip04Encrypt(
  recipientPubkey,
  'Secret message'
);

// Decrypt message
const plaintext = await signer.nip04Decrypt(
  senderPubkey,
  ciphertext
);

NIP-44 Encryption

// Encrypt with NIP-44 (preferred)
const ciphertext = await signer.nip44Encrypt(
  recipientPubkey,
  'Secret message'
);

// Decrypt
const plaintext = await signer.nip44Decrypt(
  senderPubkey,
  ciphertext
);

NIP-07 Signer

Browser Extension Integration

import { Nip07Signer } from 'applesauce-signers';

// Check if extension is available
if (window.nostr) {
  const signer = new Nip07Signer();

  // Get public key (may prompt user)
  const pubkey = await signer.getPublicKey();

  // Sign event (prompts user)
  const signedEvent = await signer.signEvent(unsignedEvent);
}

Handling Extension Availability

function getAvailableSigner() {
  if (typeof window !== 'undefined' && window.nostr) {
    return new Nip07Signer();
  }
  return null;
}

// Wait for extension to load
async function waitForExtension(timeout = 3000) {
  const start = Date.now();

  while (Date.now() - start < timeout) {
    if (window.nostr) {
      return new Nip07Signer();
    }
    await new Promise(r => setTimeout(r, 100));
  }

  return null;
}

Extension Permissions

// Some extensions support granular permissions
const signer = new Nip07Signer();

// Request specific permissions
try {
  // This varies by extension
  await window.nostr.enable();
} catch (error) {
  console.log('User denied permission');
}

NIP-46 Remote Signer

Nostr Connect

import { Nip46Signer } from 'applesauce-signers';

// Create remote signer
const signer = new Nip46Signer({
  // Remote signer's pubkey
  remotePubkey: signerPubkey,

  // Relays for communication
  relays: ['wss://relay.example.com'],

  // Local secret key for encryption
  localSecretKey: localSecretKey,

  // Optional: custom client name
  clientName: 'My Nostr App'
});

// Connect to remote signer
await signer.connect();

// Get public key
const pubkey = await signer.getPublicKey();

// Sign event
const signedEvent = await signer.signEvent(unsignedEvent);

// Disconnect when done
signer.disconnect();

Connection URL

// Parse nostrconnect:// URL
function parseNostrConnectUrl(url) {
  const parsed = new URL(url);

  return {
    pubkey: parsed.pathname.replace('//', ''),
    relay: parsed.searchParams.get('relay'),
    secret: parsed.searchParams.get('secret')
  };
}

// Create signer from URL
const { pubkey, relay, secret } = parseNostrConnectUrl(connectUrl);

const signer = new Nip46Signer({
  remotePubkey: pubkey,
  relays: [relay],
  localSecretKey: generateSecretKey(),
  secret: secret
});

Bunker URL

// Parse bunker:// URL (NIP-46)
function parseBunkerUrl(url) {
  const parsed = new URL(url);

  return {
    pubkey: parsed.pathname.replace('//', ''),
    relays: parsed.searchParams.getAll('relay'),
    secret: parsed.searchParams.get('secret')
  };
}

const { pubkey, relays, secret } = parseBunkerUrl(bunkerUrl);

Signer Management

Signer Store

import { SignerStore } from 'applesauce-signers';

const signerStore = new SignerStore();

// Set active signer
signerStore.setSigner(signer);

// Get active signer
const activeSigner = signerStore.getSigner();

// Clear signer (logout)
signerStore.clearSigner();

// Observable for signer changes
signerStore.signer$.subscribe(signer => {
  if (signer) {
    console.log('Logged in');
  } else {
    console.log('Logged out');
  }
});

Multi-Account Support

class AccountManager {
  constructor() {
    this.accounts = new Map();
    this.activeAccount = null;
  }

  addAccount(pubkey, signer) {
    this.accounts.set(pubkey, signer);
  }

  removeAccount(pubkey) {
    this.accounts.delete(pubkey);
    if (this.activeAccount === pubkey) {
      this.activeAccount = null;
    }
  }

  switchAccount(pubkey) {
    if (this.accounts.has(pubkey)) {
      this.activeAccount = pubkey;
      return this.accounts.get(pubkey);
    }
    return null;
  }

  getActiveSigner() {
    return this.activeAccount
      ? this.accounts.get(this.activeAccount)
      : null;
  }
}

Custom Signers

Implementing a Custom Signer

class CustomSigner {
  constructor(options) {
    this.options = options;
  }

  async getPublicKey() {
    // Return public key
    return this.options.pubkey;
  }

  async signEvent(event) {
    // Implement signing logic
    // Could call external API, hardware wallet, etc.

    const signedEvent = await this.externalSign(event);
    return signedEvent;
  }

  async nip04Encrypt(pubkey, plaintext) {
    // Implement NIP-04 encryption
    throw new Error('NIP-04 not supported');
  }

  async nip04Decrypt(pubkey, ciphertext) {
    throw new Error('NIP-04 not supported');
  }

  async nip44Encrypt(pubkey, plaintext) {
    // Implement NIP-44 encryption
    throw new Error('NIP-44 not supported');
  }

  async nip44Decrypt(pubkey, ciphertext) {
    throw new Error('NIP-44 not supported');
  }
}

Hardware Wallet Signer

class HardwareWalletSigner {
  constructor(devicePath) {
    this.devicePath = devicePath;
  }

  async connect() {
    // Connect to hardware device
    this.device = await connectToDevice(this.devicePath);
  }

  async getPublicKey() {
    // Get public key from device
    return await this.device.getNostrPubkey();
  }

  async signEvent(event) {
    // Sign on device (user confirms on device)
    const signature = await this.device.signNostrEvent(event);

    return {
      ...event,
      pubkey: await this.getPublicKey(),
      id: getEventHash(event),
      sig: signature
    };
  }
}

Read-Only Signer

class ReadOnlySigner {
  constructor(pubkey) {
    this.pubkey = pubkey;
  }

  async getPublicKey() {
    return this.pubkey;
  }

  async signEvent(event) {
    throw new Error('Read-only mode: cannot sign events');
  }

  async nip04Encrypt(pubkey, plaintext) {
    throw new Error('Read-only mode: cannot encrypt');
  }

  async nip04Decrypt(pubkey, ciphertext) {
    throw new Error('Read-only mode: cannot decrypt');
  }
}

Signing Utilities

Event Creation Helper

async function createAndSignEvent(signer, template) {
  const pubkey = await signer.getPublicKey();

  const event = {
    ...template,
    pubkey,
    created_at: template.created_at || Math.floor(Date.now() / 1000)
  };

  return await signer.signEvent(event);
}

// Usage
const signedNote = await createAndSignEvent(signer, {
  kind: 1,
  content: 'Hello!',
  tags: []
});

Batch Signing

async function signEvents(signer, events) {
  const signed = [];

  for (const event of events) {
    const signedEvent = await signer.signEvent(event);
    signed.push(signedEvent);
  }

  return signed;
}

// With parallelization (if signer supports)
async function signEventsParallel(signer, events) {
  return Promise.all(
    events.map(event => signer.signEvent(event))
  );
}

Svelte Integration

Signer Context

<!-- SignerProvider.svelte -->
<script>
  import { setContext } from 'svelte';
  import { writable } from 'svelte/store';

  const signer = writable(null);

  setContext('signer', {
    signer,
    setSigner: (s) => signer.set(s),
    clearSigner: () => signer.set(null)
  });
</script>

<slot />
<!-- Component using signer -->
<script>
  import { getContext } from 'svelte';

  const { signer } = getContext('signer');

  async function publishNote(content) {
    if (!$signer) {
      alert('Please login first');
      return;
    }

    const event = await $signer.signEvent({
      kind: 1,
      content,
      created_at: Math.floor(Date.now() / 1000),
      tags: []
    });

    // Publish event...
  }
</script>

Login Component

<script>
  import { getContext } from 'svelte';
  import { Nip07Signer, SimpleSigner } from 'applesauce-signers';

  const { setSigner, clearSigner, signer } = getContext('signer');

  let nsec = '';

  async function loginWithExtension() {
    if (window.nostr) {
      setSigner(new Nip07Signer());
    } else {
      alert('No extension found');
    }
  }

  function loginWithNsec() {
    try {
      const decoded = nip19.decode(nsec);
      if (decoded.type === 'nsec') {
        setSigner(new SimpleSigner(decoded.data));
        nsec = '';
      }
    } catch (e) {
      alert('Invalid nsec');
    }
  }

  function logout() {
    clearSigner();
  }
</script>

{#if $signer}
  <button on:click={logout}>Logout</button>
{:else}
  <button on:click={loginWithExtension}>
    Login with Extension
  </button>

  <div>
    <input
      type="password"
      bind:value={nsec}
      placeholder="nsec..."
    />
    <button on:click={loginWithNsec}>
      Login with Key
    </button>
  </div>
{/if}

Best Practices

Security

  1. Never store secret keys in plain text - Use secure storage
  2. Prefer NIP-07 - Let extensions manage keys
  3. Clear keys on logout - Don't leave in memory
  4. Validate before signing - Check event content

User Experience

  1. Show signing status - Loading states
  2. Handle rejections gracefully - User may cancel
  3. Provide fallbacks - Multiple login options
  4. Remember preferences - Store signer type

Error Handling

async function safeSign(signer, event) {
  try {
    return await signer.signEvent(event);
  } catch (error) {
    if (error.message.includes('rejected')) {
      console.log('User rejected signing');
      return null;
    }
    if (error.message.includes('timeout')) {
      console.log('Signing timed out');
      return null;
    }
    throw error;
  }
}

Permission Checking

function hasEncryptionSupport(signer) {
  return typeof signer.nip04Encrypt === 'function' ||
         typeof signer.nip44Encrypt === 'function';
}

function getEncryptionMethod(signer) {
  // Prefer NIP-44
  if (typeof signer.nip44Encrypt === 'function') {
    return 'nip44';
  }
  if (typeof signer.nip04Encrypt === 'function') {
    return 'nip04';
  }
  return null;
}

Common Patterns

Signer Detection

async function detectSigners() {
  const available = [];

  // Check NIP-07
  if (typeof window !== 'undefined' && window.nostr) {
    available.push({
      type: 'nip07',
      name: 'Browser Extension',
      create: () => new Nip07Signer()
    });
  }

  // Check stored credentials
  const storedKey = localStorage.getItem('nsec');
  if (storedKey) {
    available.push({
      type: 'stored',
      name: 'Saved Key',
      create: () => new SimpleSigner(storedKey)
    });
  }

  return available;
}

Auto-Reconnect for NIP-46

class ReconnectingNip46Signer {
  constructor(options) {
    this.options = options;
    this.signer = null;
  }

  async connect() {
    this.signer = new Nip46Signer(this.options);
    await this.signer.connect();
  }

  async signEvent(event) {
    try {
      return await this.signer.signEvent(event);
    } catch (error) {
      if (error.message.includes('disconnected')) {
        await this.connect();
        return await this.signer.signEvent(event);
      }
      throw error;
    }
  }
}

Signer Type Persistence

const SIGNER_KEY = 'nostr_signer_type';

function saveSigner(type, data) {
  localStorage.setItem(SIGNER_KEY, JSON.stringify({ type, data }));
}

async function restoreSigner() {
  const saved = localStorage.getItem(SIGNER_KEY);
  if (!saved) return null;

  const { type, data } = JSON.parse(saved);

  switch (type) {
    case 'nip07':
      if (window.nostr) {
        return new Nip07Signer();
      }
      break;
    case 'simple':
      // Don't store secret keys!
      break;
    case 'nip46':
      const signer = new Nip46Signer(data);
      await signer.connect();
      return signer;
  }

  return null;
}

Troubleshooting

Common Issues

Extension not detected:

  • Wait for page load
  • Check window.nostr exists
  • Verify extension is enabled

Signing rejected:

  • User cancelled in extension
  • Handle gracefully with error message

NIP-46 connection fails:

  • Check relay is accessible
  • Verify remote signer is online
  • Check secret matches

Encryption not supported:

  • Check signer has encrypt methods
  • Fall back to alternative method
  • Show user appropriate error

References

Related Skills

  • nostr-tools - Event creation and signing utilities
  • applesauce-core - Event stores and queries
  • nostr - Nostr protocol fundamentals
  • svelte - Building Nostr UIs

适合场景

01

用户想查找某类 Agent Skill 时

02

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

03

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

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

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

能力 4

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

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

平台分布

Codex

36.38%
按下载量换算60

Claude

30.2%
按下载量换算50

Cursor

20.7%
按下载量换算34

Gemini CLI

9.78%
按下载量换算16

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

未通过

权限和风险

敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

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

来源信息

继续浏览同类 Skills