### Setup the project environment Source: https://github.com/forgesworn/bray/blob/main/CONTRIBUTING.md Commands to clone the repository and install dependencies. ```bash git clone git@github.com:forgesworn/bray.git cd bray npm install npm run build npm test ``` -------------------------------- ### HTTP Transport Setup and Usage Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Instructions for starting the HTTP transport and making authenticated requests. ```APIDOC ## HTTP Transport ### Description For remote access, start with HTTP transport. The bearer token is printed to stderr on startup. Include it in requests. ### Setup ```bash TRANSPORT=http PORT=3000 npx nostr-bray ``` ### Authentication Token `nostr-bray HTTP auth token: ` ### Server Address `nostr-bray HTTP on 127.0.0.1:3000` ### Request Example ```bash curl -X POST http://127.0.0.1:3000/mcp \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"tools/call","params":{"name":"whoami"},"id":1}' ``` ### Rate Limiting Rate limited to 100 requests per 60 seconds per IP. ``` -------------------------------- ### Configuration File Example Source: https://github.com/forgesworn/bray/blob/main/README.md Example JSON configuration file for setting up relays and trust modes. ```json { "bunkerUriFile": "/Users/you/.nostr/bunker-uri", "relays": ["wss://relay.damus.io", "wss://nos.lol"], "trustMode": "annotate" } ``` -------------------------------- ### Configure MCP Client Source: https://github.com/forgesworn/bray/blob/main/README.md Configuration examples for integrating nostr-bray into an MCP client, showing both standard and production-ready bunker setups. ```json { "mcpServers": { "nostr": { "command": "npx", "args": ["nostr-bray"], "env": { "NOSTR_SECRET_KEY": "nsec1...", "NOSTR_RELAYS": "wss://relay.damus.io,wss://nos.lol" } } } } ``` ```json { "mcpServers": { "nostr": { "command": "npx", "args": ["nostr-bray"], "env": { "BUNKER_URI": "bunker://...", "NOSTR_RELAYS": "wss://relay.damus.io,wss://nos.lol" } } } } ``` -------------------------------- ### SDK Usage and Imports Source: https://context7.com/forgesworn/bray/llms.txt Examples for importing the library, including tree-shakeable subpaths and type definitions. ```typescript // Full factory import { createBray, defaultBray } from 'nostr-bray' const bray = await defaultBray() const result = await bray.identity.whoami() // Category subpath (tree-shakeable) import { whoami } from 'nostr-bray/identity' import { dmRead } from 'nostr-bray/social' // Types only import type { BrayConfig, IdentityResult } from 'nostr-bray/types' ``` -------------------------------- ### Guided Onboarding for Verified Profiles Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt A guided workflow to help users build a verified trust profile, showing their current Signet tier and potential vouching contacts. ```APIDOC ## GET /onboard-verified ### Description Guided workflow to build a verified trust profile. Shows current Signet tier, remaining steps, and contacts who could vouch for you. ### Method GET ### Endpoint /onboard-verified ### Response #### Success Response (200) - **signet_tier** (string) - The user's current Signet tier. - **remaining_steps** (array) - A list of steps remaining to achieve verification. - **potential_vouchers** (array) - A list of contacts who could vouch for the user. ### Response Example { "signet_tier": "bronze", "remaining_steps": ["Complete NIP-05", "Get 3 attestations"], "potential_vouchers": ["npub1...", "npub2..."] } ``` -------------------------------- ### Verify Installation Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Checks the current identity to ensure the configuration is working correctly. ```bash npx nostr-bray whoami # npub1abc... ``` -------------------------------- ### Install nostr-bray Source: https://github.com/forgesworn/bray/blob/main/README.md Global installation command for the nostr-bray package. ```bash npm install -g nostr-bray ``` -------------------------------- ### Install nostr-bray CLI Source: https://github.com/forgesworn/bray/blob/main/site/index.html Run nostr-bray directly using npx without global installation, or install it globally using npm. ```bash npx nostr-bray --help ``` ```bash npm install -g nostr-bray ``` -------------------------------- ### Install 402-mcp Source: https://github.com/forgesworn/bray/blob/main/docs/registry-submissions.md Command to execute the 402-mcp MCP server via npx. ```bash npx 402-mcp ``` -------------------------------- ### Install, Build, Test, and Lint Project Source: https://github.com/forgesworn/bray/blob/main/AGENTS.md Standard commands for managing the project dependencies, building the TypeScript code, running tests with vitest, and performing static type checking. ```bash npm install npm run build # TypeScript → dist/ npm test # ~1180 tests via vitest npm run lint # tsc --noEmit ``` -------------------------------- ### Identity Setup with Shamir Backup Workflow Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt Workflow for setting up a multi-persona identity with a Shamir backup, including preview and confirmation steps. ```shell 1. identity-setup(confirm: false) -> preview personas + Shamir config 2. identity-setup(personas: ["work", "social"], shamirThreshold: { shares: 5, threshold: 3 }, relays: ["wss://relay.damus.io"], confirm: true) 3. relay-health() -> verify relay connectivity ``` -------------------------------- ### Configure Bray MCP Server Source: https://github.com/forgesworn/bray/blob/main/llms.txt Example configuration for integrating the Bray Nostr MCP server into an environment. ```json { "mcpServers": { "nostr": { "command": "npx", "args": ["nostr-bray"], "env": { "BUNKER_URI": "bunker://...", "NOSTR_RELAYS": "wss://relay.damus.io,wss://nos.lol" } } } } ``` -------------------------------- ### Install nostr-bray Source: https://github.com/forgesworn/bray/blob/main/docs/registry-submissions.md Command to execute the nostr-bray MCP server via npx. ```bash npx nostr-bray ``` -------------------------------- ### Social Posting and Interaction Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Examples for posting, replying, and reacting to events via MCP tools or CLI. ```text social-post({ content: "Hello Nostr!" }) social-reply({ content: "Great post!", replyTo: "", replyToPubkey: "" }) social-react({ eventId: "", eventPubkey: "", reaction: "🤙" }) ``` ```bash npx nostr-bray post "Hello Nostr!" ``` -------------------------------- ### Identity Setup and Recovery Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt APIs for setting up new multi-persona identities and recovering existing ones using Shamir's Secret Sharing. ```APIDOC ## POST /identity-setup ### Description Set up a multi-persona identity from the current master secret. This endpoint includes a preview mode. ### Method POST ### Endpoint /identity-setup ### Parameters #### Request Body - **personas** (string[]) - Optional - A list of personas to set up. Defaults to ["social", "commerce"]. - **shamirThreshold** (object) - Optional - Configuration for Shamir's Secret Sharing. - **shares** (integer) - The total number of shares. - **threshold** (integer) - The minimum number of shares required to recover the secret. - **relays** (string[]) - Optional - A list of relays to configure for the identity. - **confirm** (boolean) - Optional - If true, proceeds with the setup. Defaults to false (preview mode). ### Request Example { "personas": ["work", "social"], "shamirThreshold": {"shares": 5, "threshold": 3}, "relays": ["wss://relay.damus.io"], "confirm": true } ## POST /identity-recover ### Description Recover a master identity from Shamir word-list shard files. This is a destructive operation. ### Method POST ### Endpoint /identity-recover ### Parameters #### Request Body - **shardPaths** (string[]) - Required - An array of file paths to the Shamir shard files. - **newRelays** (string[]) - Optional - A list of new relays to configure for the recovered identity. ### Request Example { "shardPaths": ["/path/to/shard1.txt", "/path/to/shard2.txt"], "newRelays": ["wss://new.relay.com"] } ``` -------------------------------- ### Install Command Copy to Clipboard Source: https://github.com/forgesworn/bray/blob/main/site/index.html Copies the 'npx nostr-bray' command to the clipboard on click. Includes a fallback for browsers that do not support the Clipboard API. ```javascript const installBlock = document.getElementById('installBlock'); installBlock.addEventListener('click', async () => { try { await navigator.clipboard.writeText('npx nostr-bray'); installBlock.classList.add('copied'); installBlock.querySelector('.copy-hint').textContent = 'copied!'; setTimeout(() => { installBlock.classList.remove('copied'); installBlock.querySelector('.copy-hint').textContent = 'click to copy'; }, 2000); } catch (e) { // Fallback const ta = document.createElement('textarea'); ta.value = 'npx nostr-bray'; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); document.body.removeChild(ta); installBlock.classList.add('copied'); installBlock.querySelector('.copy-hint').textContent = 'copied!'; setTimeout(() => { installBlock.classList.remove('copied'); installBlock.querySelector('.copy-hint').textContent = 'click to copy'; }, 2000); } }); ``` -------------------------------- ### Authenticate with NIP-46 Bunker Source: https://github.com/forgesworn/bray/blob/main/site/index.html Recommended authentication method using a NIP-46 bunker. Start the bunker in one terminal and connect to it in another using the BUNKER_URI environment variable. ```bash # Terminal 1: start the bunker with your key npx nostr-bray bunker ``` ```bash # Terminal 2: connect to it export BUNKER_URI="bunker://?relay=wss://relay.damus.io" npx nostr-bray whoami ``` -------------------------------- ### Start Nostr Bray HTTP Transport Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Launch the Nostr Bray service using HTTP transport. Set the TRANSPORT and PORT environment variables. The authentication token will be printed to stderr. ```bash TRANSPORT=http PORT=3000 npx nostr-bray ``` -------------------------------- ### Initialize DmThread Application Source: https://github.com/forgesworn/bray/blob/main/widgets/templates/dm-thread.html Sets up the application instance, connects to the host, and registers event listeners for theme changes and tool results. ```javascript /*__EXT_APPS_BUNDLE__*/ const { App } = globalThis.ExtApps; (async () => { const app = new App({ name: "DmThread", version: "1.0.0" }, {}); const headerEl = document.getElementById("header"); const messagesEl = document.getElementById("messages"); const replyBar = document.getElementById("reply-bar"); const replyInput = document.getElementById("reply-input"); const replyBtn = document.getElementById("reply-send"); let partnerPubkeyHex = null; const PLACEHOLDER = "data:image/svg+xml," + encodeURIComponent(''); const applyTheme = (t) => document.documentElement.classList.toggle("dark", t === "dark"); app.onhostcontextchanged = (ctx) => applyTheme(ctx.theme); app.ontoolresult = ({ content }) => { try { const data = JSON.parse(content[0].text); if (data.messages !== undefined && data.partnerProfile !== undefined) { render(data); } } catch (e) { showMessage("Failed to parse message data."); } }; await app.connect(); const hostCtx = app.getHostContext(); if (hostCtx) applyTheme(hostCtx.theme); ``` -------------------------------- ### CLI Mode: Start Bunker Source: https://github.com/forgesworn/bray/blob/main/site/index.html Start the Bray bunker service in the background for CLI operations. This allows subsequent commands to connect to it. ```bash npx nostr-bray bunker & ``` -------------------------------- ### Development and build commands Source: https://github.com/forgesworn/bray/blob/main/CONTRIBUTING.md Standard npm scripts for compiling, testing, and linting the project. ```bash npm run build # Compile TypeScript npm test # Run all tests (vitest) npm run test:watch # Watch mode npm run lint # Type-check without emitting ``` -------------------------------- ### Handle Media Uploads with Blossom Source: https://github.com/forgesworn/bray/blob/main/examples/mcp-workflow.md Commands for uploading files to a Blossom server and listing a user's uploads. ```bash # Upload a file blossom-upload({ server: "https://blossom.example.com", filePath: "/path/to/image.png" }) ``` ```bash # List your uploads blossom-list({ server: "https://blossom.example.com", pubkeyHex: "" }) ``` -------------------------------- ### Manage demo modal display logic Source: https://github.com/forgesworn/bray/blob/main/site/index.html JavaScript implementation for mapping tool tags to GIF filenames and handling modal visibility. ```javascript /* ======================================== DEMO GIF MODAL ======================================== */ const demoOverlay = document.getElementById('demoOverlay'); const demoTitle = document.getElementById('demoTitle'); const demoBody = document.getElementById('demoBody'); const demoClose = document.getElementById('demoClose'); // Map tool tags to GIF filenames (story tools -> story GIFs, name fixes) const gifMap = { // Story 1: Identity Onboarding (whoami has its own solo GIF) 'identity-derive-persona': '01-identity-onboarding', 'identity-switch': '01-identity-onboarding', // Story 2: Identity Backup 'identity-backup-shamir': '02-identity-backup', // Story 3: Profile & NIP-05 'social-profile-set': '03-profile-and-nip05', 'nip05-lookup': '03-profile-and-nip05', 'nip05-verify': '03-profile-and-nip05', // Story 4: Feed & Discovery 'social-notifications': '04-feed-and-discovery', 'contacts-follow': '04-feed-and-discovery', // Story 5: Direct Messaging 'contacts-search': '05-direct-messaging', 'dm-send': '05-direct-messaging', // Story 6: Public Engagement 'social-post': '06-public-engagement', // Story 7: Trust Check 'signet-badge': '07-trust-check', 'trust-score': '07-trust-check', // Story 8: Attestation 'signet-vouch': '08-attestation-and-vouching', 'trust-attest': '08-attestation-and-vouching', // Story 9: Professional Verification 'signet-verifiers': '09-professional-verification', 'signet-policy-set': '09-professional-verification', 'signet-policy-check': '09-professional-verification', // Story 10: Ring Signatures 'trust-ring-prove': '10-ring-signatures', // Story 11: Spoken Verification 'trust-spoken-challenge': '11-spoken-verification', // Story 12: Privacy Proofs 'privacy-commit': '12-privacy-proofs', // Story 13: Vault Setup 'vault-create': '13-vault-setup', 'vault-encrypt': '13-vault-setup', 'vault-share': '13-vault-setup', // Story 14: Relay Management 'relay-list': '14-relay-management', 'relay-set': '14-relay-management', // Story 15: Relay Discovery 'relay-discover': '15-relay-discovery', // Story 16: Content Moderation 'label-create': '16-content-moderation', // Story 17: Marketplace 'marketplace-discover': '17-marketplace', 'marketplace-call': '17-marketplace', // Story 18: Zap Workflow 'zap-balance': '18-zap-workflow', 'zap-make-invoice': '18-zap-workflow', 'zap-send': '18-zap-workflow', // Story 25: Blossom Files 'blossom-upload': '25-blossom-files', 'blossom-list': '25-blossom-files', 'blossom-mirror': '25-blossom-files', // Story 26: Relay Operator 'relay-info': '26-relay-operator', 'relay-count': '26-relay-operator', 'relay-auth': '26-relay-operator', // Site name -> actual tool name fixes 'blossom-servers-get': 'blossom-servers', 'canary-create': '20-canary-session', 'canary-check': '22-canary-beacon', 'canary-renew': '20-canary-session', 'canary-revoke': 'canary-duress-signal', 'bookmark-add': '24-bookmarks', 'mute-add': 'list-mute-read', 'mute-list': 'list-mute-read', 'pin-add': 'list-pin-read', 'followset-create': '23-follow-sets', 'marketplace-publish': 'marketplace-announce', 'privacy-range-prove': 'privacy-prove-range', 'privacy-age-verify': '12-privacy-proofs', 'privacy-range-verify': 'privacy-verify-range', 'trust-attest-deep': 'trust-attest-chain', 'trust-ring-prove-deep': 'trust-ring-lsag-sign', }; function showDemo(toolName) { const gifName = gifMap[toolName] || toolName; const gifPath = 'demos/gifs/' + gifName + '.gif'; demoTitle.textContent = toolName; demoBody.innerHTML = '
loading demo...
'; demoOverlay.classList.add('active'); const img = new Image(); img.className = 'demo-modal-gif'; img.alt = toolName + ' demo'; img.onload = () => { demoBody.innerHTML = ''; demoBody.appendChild(img); }; img.onerror = () => { demoBody.innerHTML = '
no demo recorded yet
'; }; img.src = gifPath; } function closeDemo() { demoOverlay.classList.remove('active'); } demoOverlay.addEventListener('click', (e) => { if (e.target === demoOverlay) closeDemo(); }); demoClose.addEventListener('click', closeDemo); document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeDemo(); }); document.querySelectorAll('.tool-tag').forEach(tag => { tag.addEventListener('click', (e) => { e.stopPropagation(); showDemo(tag.textContent.trim()); }); }); ``` -------------------------------- ### Smooth Scroll for Anchor Links Source: https://github.com/forgesworn/bray/blob/main/site/index.html Implements smooth scrolling for anchor links (href starting with '#') for browsers that do not support native smooth scrolling. Prevents default anchor behavior. ```javascript document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function(e) { const target = document.querySelector(this.getAttribute('href')); if (target) { e.preventDefault(); target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); ``` -------------------------------- ### Manage Per-Identity Relay Lists Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Control individual relay sets for each identity to prevent correlation between personas. Use these commands to view, add, publish, and get info about relays. ```javascript relay-list() ``` ```javascript relay-add({ url: "wss://new-relay.com" }) ``` ```javascript relay-set({ relays: [...], confirm: true }) ``` ```javascript relay-info({ url: "wss://relay.damus.io" }) ``` -------------------------------- ### Authenticate with Environment Variable (Testing Only) Source: https://github.com/forgesworn/bray/blob/main/site/index.html Quick authentication for testing purposes using environment variables for the secret key and relays. Not recommended for production environments. ```bash export NOSTR_SECRET_KEY="nsec1..." export NOSTR_RELAYS="wss://relay.damus.io,wss://nos.lol" ``` -------------------------------- ### Manage Trust and Attestations Source: https://github.com/forgesworn/bray/blob/main/examples/mcp-workflow.md Commands for creating attestations, reading attestations about a subject, and generating anonymous group membership proofs. ```bash # Create an attestation trust-attest({ type: "identity-verification", subject: "", summary: "Verified in person" }) ``` ```bash # Read attestations about someone trust-read({ subject: "" }) ``` ```bash # Anonymous group membership proof trust-ring-prove({ ring: ["", "", "", ""], attestationType: "kyc-verified" }) ``` -------------------------------- ### View Tool Catalog Statistics Source: https://github.com/forgesworn/bray/blob/main/AGENTS.md Displays the count of promoted versus catalogued tools as logged by the server at startup. ```text nostr-bray: 51 promoted tools + 182 cataloged (235 total) ``` -------------------------------- ### Check for Task Replies Source: https://github.com/forgesworn/bray/blob/main/examples/agent-scenarios/dispatch-task-roundtrip.md Query for replies to dispatched tasks. By default, it returns tasks since the server started; use `since: 0` to retrieve historical data. Handles 'completed' tasks and 'refuse' types. ```json { "method": "tools/call", "params": { "name": "dispatch-check", "arguments": { "since": "session-start" } } } ``` -------------------------------- ### Marketplace Discovery and Payment Workflow Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt Workflow for discovering marketplace services, inspecting details, probing for pricing, and making payments. ```shell 1. marketplace-discover(topics: ["ai"]) -> find AI services 2. marketplace-inspect(eventId: "abc...") -> full service details 3. marketplace-probe(url: "https://api.svc/v1") -> check pricing, extract L402 4. marketplace-pay(macaroon: "...", invoice: "...") -> preview cost 5. marketplace-pay(..., confirm: true) -> pay and get credentialId 6. marketplace-call(url: "...", credentialId: "...") -> authenticated API call ``` -------------------------------- ### Configure MCP Server Source: https://context7.com/forgesworn/bray/llms.txt Add the server to your MCP client configuration using environment variables for relay and bunker authentication. ```json { "mcpServers": { "nostr": { "command": "npx", "args": ["nostr-bray"], "env": { "BUNKER_URI": "bunker://pubkey?relay=wss://relay.example&secret=hex", "NOSTR_RELAYS": "wss://relay.damus.io,wss://nos.lol" } } } } ``` -------------------------------- ### Vault Creation Tool Call Source: https://context7.com/forgesworn/bray/llms.txt Initializes an encrypted vault using a Dominion epoch key. ```json // Request { "method": "tools/call", "params": { "name": "vault-create", "arguments": {} } } // Response { "vaultId": "vault123...", "epoch": 1, "created": true } ``` -------------------------------- ### Execute Action Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt Executes an action previously found using search-actions. ```shell execute-action(action name, parameters) ``` -------------------------------- ### Generate Secret Key Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Creates a fresh secret key file and sets appropriate file permissions. ```bash # Generate a fresh key node -e " import { generateSecretKey } from 'nostr-tools/pure'; import { nsecEncode } from 'nostr-tools/nip19'; console.log(nsecEncode(generateSecretKey())); " > ~/.bray/secret.key chmod 600 ~/.bray/secret.key ``` -------------------------------- ### Switch Identities Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Demonstrates how identity switching affects subsequent tool operations. ```text identity-switch("work") → all subsequent tools sign as work persona social-post("Hello from work!") → signed by work's npub identity-switch("master") → back to master ``` -------------------------------- ### Search Actions Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt Searches for additional actions by describing the desired functionality. Returns matching actions with descriptions and parameter schemas. ```shell search-actions(query: "describe what you want to do") ``` -------------------------------- ### Manage Lightning Payments (NWC) Source: https://github.com/forgesworn/bray/blob/main/examples/mcp-workflow.md Commands for checking wallet balance, paying invoices, creating new invoices, and checking payment receipts. ```bash # Check wallet zap-balance() ``` ```bash # Pay an invoice zap-send({ invoice: "lnbc10u1..." }) ``` ```bash # Create an invoice zap-make-invoice({ amountMsats: 100000, description: "Coffee" }) ``` ```bash # Check receipts zap-receipts({ limit: 5 }) ``` -------------------------------- ### Configure Nostr Wallet Connect (NWC) Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Set the NWC_URI or NWC_URI_FILE environment variable to configure Nostr Wallet Connect. This is required before using zap tools. ```bash export NWC_URI="nostr+walletconnect://?relay=wss://relay&secret=" ``` -------------------------------- ### Meta Tools: Action Search and Execution Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt Tools for searching and executing additional actions based on natural language descriptions. ```APIDOC ## POST /search-actions ### Description Search for additional actions by describing what you want to do. Returns matching actions with descriptions and parameter schemas. ### Method POST ### Endpoint /search-actions ### Parameters #### Request Body - **query** (string) - Required - A natural language description of the desired action. ### Request Example { "query": "Find trusted accounts to follow" } ## POST /execute-action ### Description Execute an action that was previously found using `search-actions`. ### Method POST ### Endpoint /execute-action ### Parameters #### Request Body - **action_name** (string) - Required - The name of the action to execute. - **parameters** (object) - Optional - An object containing the parameters for the action. ### Request Example { "action_name": "feed-discover", "parameters": { "strategy": "trust-adjacent" } } ``` -------------------------------- ### CLI Mode: Basic Commands Source: https://github.com/forgesworn/bray/blob/main/site/index.html Execute common Bray CLI commands after connecting to the bunker, such as checking identity, posting, and setting persona. ```bash npx nostr-bray whoami ``` ```bash npx nostr-bray post "hello from bray" ``` ```bash npx nostr-bray persona work ``` ```bash npx nostr-bray prove blind ``` -------------------------------- ### Create Linkage Proofs Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Commands to prove identity linkage between child identities and the master key. ```bash # Blind proof -- proves link, hides how the child was derived npx nostr-bray prove blind # Full proof -- reveals purpose string and index (use with care) npx nostr-bray prove full ``` -------------------------------- ### Identity Picker JavaScript Implementation Source: https://github.com/forgesworn/bray/blob/main/widgets/templates/identity-picker.html Initializes the Identity Picker app, handles theme changes, processes tool results, and renders identity cards. Includes logic for switching identities and error handling. ```javascript const { App } = globalThis.ExtApps; (async () => { const app = new App({ name: "IdentityPicker", version: "1.0.0" }, {}); const root = document.getElementById("root"); const PLACEHOLDER = "data:image/svg+xml," + encodeURIComponent(''); const applyTheme = (t) => document.documentElement.classList.toggle("dark", t === "dark"); app.onhostcontextchanged = (ctx) => applyTheme(ctx.theme); app.ontoolresult = ({ content }) => { try { const data = JSON.parse(content[0].text); if (data.identities) render(data); } catch (e) { clearAndShowMessage("Failed to parse identity data."); } }; await app.connect(); const hostCtx = app.getHostContext(); if (hostCtx) applyTheme(hostCtx.theme); const caps = app.getHostCapabilities(); const canCallTools = !!caps?.serverTools; console.debug("[identity-picker] hostCapabilities:", JSON.stringify(caps)); console.debug("[identity-picker] canCallTools:", canCallTools); function clearAndShowMessage(msg) { while (root.firstChild) root.removeChild(root.firstChild); const el = document.createElement("div"); el.className = "empty"; el.textContent = msg; root.appendChild(el); } function truncateNpub(npub) { if (!npub || npub.length < 20) return npub; return npub.slice(0, 10) + "\u2026" + npub.slice(-6); } function render(data) { if (!data.identities || data.identities.length === 0) { clearAndShowMessage("No identities found."); return; } while (root.firstChild) root.removeChild(root.firstChild); const grid = document.createElement("div"); grid.className = "grid"; for (const id of data.identities) { const card = document.createElement("div"); card.className = "id-card" + (id.isActive ? " active" : ""); const avatar = document.createElement("img"); avatar.className = "avatar"; avatar.src = id.pictureDataUri || PLACEHOLDER; avatar.alt = ""; const nameEl = document.createElement("div"); nameEl.className = "display-name"; nameEl.textContent = id.displayName || id.personaName || "Identity"; card.appendChild(avatar); card.appendChild(nameEl); if (id.personaName) { const personaEl = document.createElement("div"); personaEl.className = "persona"; personaEl.textContent = id.personaName; card.appendChild(personaEl); } const npubEl = document.createElement("div"); npubEl.className = "npub-truncated"; npubEl.textContent = truncateNpub(id.npub); card.appendChild(npubEl); if (id.isActive) { const badge = document.createElement("span"); badge.className = "badge"; badge.textContent = "Active"; card.appendChild(badge); } card.addEventListener("click", async () => { if (id.isActive) return; if (!canCallTools) { clearAndShowMessage(`Host doesn't support widget tool calls. Ask the AI to switch to "${id.personaName || id.purpose}".`); return; } const target = id.personaName || id.purpose; try { await app.callServerTool({ name: "identity-switch", arguments: { target } }); const result = await app.callServerTool({ name: "identity-picker-widget", arguments: {} }); const data = JSON.parse(result.content[0].text); render(data); } catch (e) { clearAndShowMessage("Switch failed: " + (e?.message ?? e)); } }); grid.appendChild(card); } root.appendChild(grid); } })(); ``` -------------------------------- ### Basic Identity and Social Workflow Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt A basic workflow demonstrating identity listing, persona derivation, switching, profile setting, posting, and trust attestation. ```shell 1. identity-list -> see master identity 2. identity-derive-persona("work") -> derive work persona 3. identity-switch("work") -> switch to work 4. social-profile-set(name: "Work Account") -> set profile 5. social-post("Hello from my work persona") 6. identity-switch("master") -> back to master 7. trust-attest(type: "endorsement", subject: workPubkeyHex) 8. identity-prove(mode: "blind") -> prove without revealing path ``` -------------------------------- ### Listing Create API Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt Creates a classified listing (kind 30402) with details such as title, description, price, and location. ```APIDOC ## POST /api/listing/create ### Description Creates a classified listing (kind 30402). This is a replaceable event that includes title, description, price, and optional location details. ### Method POST ### Endpoint /api/listing/create ### Parameters #### Request Body - **title** (string) - Required - The title of the listing. - **content** (string) - Required - The main content of the listing, in Markdown format. - **price** (object) - Required - Pricing information, containing 'amount' (number) and 'currency' (string), and optionally 'frequency' (string). - **summary** (string) - Optional - A brief summary of the listing. - **location** (string) - Optional - The physical location of the item/service. - **geohash** (string) - Optional - The geohash of the listing's location. - **hashtags** (array of strings) - Optional - Hashtags associated with the listing. - **image** (string) - Optional - URL of an image for the listing. - **slug** (string) - Optional - A unique slug for the listing. ``` -------------------------------- ### AI-to-AI Dispatch Workflow Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt Workflow for AI-to-AI communication, including checking for tasks, sending tasks, and discovering agent capabilities. ```shell 1. dispatch-check() -> check for incoming tasks 2. dispatch-send(to: "alice", type: "think", prompt: "Review the auth module") 3. dispatch-check() -> poll for results 4. dispatch-capability-discover() -> find agents on Nostr ``` -------------------------------- ### Schedule Post via MCP Source: https://github.com/forgesworn/bray/blob/main/site/index.html Schedule a post using MCP (any AI agent) by providing content and a timestamp. ```bash post-schedule content="launching tomorrow" at="2026-04-01T12:00:00Z" ``` -------------------------------- ### Set Up Multi-Persona Identity Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt Sets up a multi-persona identity from a master secret. Supports preview mode and optional Shamir backup configuration. ```shell identity-setup(confirm: false) ``` ```shell identity-setup(personas: ["work", "social"], shamirThreshold: { shares: 5, threshold: 3 }, relays: ["wss://relay.damus.io"], confirm: true) ``` -------------------------------- ### Social Feed JavaScript Initialization and Rendering Source: https://github.com/forgesworn/bray/blob/main/widgets/templates/social-feed.html Initializes the SocialFeed application, connects to the host, and sets up event listeners for theme changes and tool results. It includes functions for rendering posts, handling messages, and formatting relative times. ```javascript const { App } = globalThis.ExtApps; (async () => { const app = new App({ name: "SocialFeed", version: "1.0.0" }, {}); const root = document.getElementById("root"); let lastSince = undefined; const PLACEHOLDER = "data:image/svg+xml," + encodeURIComponent(''); const applyTheme = (t) => document.documentElement.classList.toggle("dark", t === "dark"); app.onhostcontextchanged = (ctx) => applyTheme(ctx.theme); app.ontoolresult = ({ content }) => { try { const data = JSON.parse(content[0].text); render(data); } catch (e) { clearAndShowMessage("Failed to parse feed data."); } }; await app.connect(); const hostCtx = app.getHostContext(); if (hostCtx) applyTheme(hostCtx.theme); function clearAndShowMessage(msg) { while (root.firstChild) root.removeChild(root.firstChild); const el = document.createElement("div"); el.className = "empty"; el.textContent = msg; root.appendChild(el); } function relativeTime(ts) { const diff = Math.floor(Date.now() / 1000) - ts; if (diff < 60) return "just now"; if (diff < 3600) return Math.floor(diff / 60) + "m"; if (diff < 86400) return Math.floor(diff / 3600) + "h"; return Math.floor(diff / 86400) + "d"; } function truncate(s, len) { return s && s.length > len ? s.slice(0, len) + "\u2026" : s; } function render(data) { if (!data.entries || data.entries.length === 0) { clearAndShowMessage("No posts found."); return; } while (root.firstChild) root.removeChild(root.firstChild); for (const entry of data.entries) { const card = document.createElement("div"); card.className = "card"; const header = document.createElement("div"); header.className = "card-header"; const avatar = document.createElement("img"); avatar.className = "avatar"; avatar.src = entry.authorPictureDataUri || PLACEHOLDER; avatar.alt = ""; const nameEl = document.createElement("span"); nameEl.className = "author-name"; nameEl.textContent = entry.authorName || truncate(entry.pubkey, 16); const timeEl = document.createElement("span"); timeEl.className = "timestamp"; timeEl.textContent = relativeTime(entry.createdAt); header.appendChild(avatar); header.appendChild(nameEl); header.appendChild(timeEl); const content = document.createElement("div"); content.className = "card-content"; content.textContent = entry.content; const actions = document.createElement("div"); actions.className = "actions"; const replyBtn = document.createElement("button"); replyBtn.textContent = "Reply"; replyBtn.addEventListener("click", () => { app.updateModelContext({ intent: "reply", eventId: entry.id, pubkey: entry.pubkey }); }); const zapBtn = document.createElement("button"); zapBtn.textContent = "Zap"; zapBtn.addEventListener("click", () => { app.updateModelContext({ intent: "zap", eventId: entry.id, pubkey: entry.pubkey }); }); actions.appendChild(replyBtn); actions.appendChild(zapBtn); card.appendChild(header); card.appendChild(content); card.appendChild(actions); root.appendChild(card); } if (data.pagination && data.pagination.nextSince) { lastSince = data.pagination.nextSince; const loadMore = document.createElement("button" ``` -------------------------------- ### Task Dispatch Response Source: https://github.com/forgesworn/bray/blob/main/examples/agent-scenarios/dispatch-task-roundtrip.md This is a sample response indicating a task has been successfully published. ```json { "taskId": "d1e2...", "to": "9e1c...", "published": { "success": true } } ``` -------------------------------- ### blossom-upload Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt Uploads a file to a blossom media server and signs a kind 24242 auth event. ```APIDOC ## blossom-upload ### Description Upload a file to a blossom media server. Signs a kind 24242 auth event. ### Parameters #### Request Body - **server** (URL) - Required - The blossom media server URL - **filePath** (string) - Required - Path to the file to upload - **contentType** (string) - Optional - MIME type of the file ### Response #### Success Response (200) - **url** (string) - The URL of the uploaded file - **sha256** (string) - The SHA-256 hash of the file - **size** (number) - The size of the file in bytes ``` -------------------------------- ### Backup Master Key Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Splits the master key into shards using Shamir's Secret Sharing. ```bash mkdir -p ~/.bray/shards npx nostr-bray backup ~/.bray/shards 3 5 ``` -------------------------------- ### Configure Relays Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Sets the environment variables for the secret key file and relay endpoints. ```bash export NOSTR_SECRET_KEY_FILE=~/.bray/secret.key export NOSTR_RELAYS="wss://relay.damus.io,wss://nos.lol" ``` -------------------------------- ### Schedule Post at Specific Time Source: https://github.com/forgesworn/bray/blob/main/site/index.html Schedule a post to be published at a precise date and time using the `--at` flag. ```bash npx nostr-bray post "good morning" --at "2026-04-01T09:00:00Z" ``` -------------------------------- ### Project directory structure Source: https://github.com/forgesworn/bray/blob/main/CONTRIBUTING.md Overview of the source code organization. ```text src/ cli.ts CLI entry point (subcommand parser) index.ts MCP server entry point (config → context → tools → transport) config.ts Environment variable + file-based secret loading context.ts IdentityContext — LRU cache, derive, switch, sign, zeroise relay-pool.ts Relay connections (SOCKS5h, write queue, Tor policy) nip65.ts NIP-65 relay list fetch with signature verification validation.ts Shared Zod validators (hexId, relayUrl) types.ts Shared TypeScript types identity/ Identity tools — derive, prove, shamir, migration social/ Social tools — post, reply, DM, blossom media, groups, community NIPs, notifications, feed, contacts trust/ Trust tools — attestations, ring sigs, spoken tokens relay/ Relay tools — list, set, add, NIP-11 info zap/ Zap tools — NWC wallet (NIP-47), receipts, decode safety/ Safety tools — duress persona configure/activate util/ Utility tools — decode, encode, verify, encrypt, filter, NIP browse, fetch ``` -------------------------------- ### Send DM with Universal Identity Source: https://github.com/forgesworn/bray/blob/main/site/index.html Use any identity format (display name, NIP-05, npub, or hex) with the `dm-send` command. Bray resolves the identity for you. ```bash dm-send --to "alice" ``` ```bash dm-send --to "alice@nos.social" ``` ```bash dm-send --to "npub1..." ``` ```bash dm-send --to "ab12..." ``` -------------------------------- ### CLI Commands Source: https://github.com/forgesworn/bray/blob/main/README.md Common CLI commands for interacting with the nostr-bray tool. ```bash npx nostr-bray whoami # show your npub npx nostr-bray post "hello from bray!" # publish a note npx nostr-bray persona work # derive a work persona npx nostr-bray prove blind # create a linkage proof npx nostr-bray --help # see all commands ``` -------------------------------- ### Configuration Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt Configuration variables for nostr-bray. Authentication tiers determine the security level for key management. ```APIDOC ## Configuration | Variable | Required | Description | |---------------------------|----------|-----------------------------------------------------------------------------| | BUNKER_URI | Tier 1* | NIP-46 bunker URI (keys never leave the signer) | | BUNKER_URI_FILE | Tier 1* | Path to file containing bunker URI | | NOSTR_NCRYPTSEC | Tier 2* | NIP-49 password-encrypted key (ncryptsec1...) | | NOSTR_NCRYPTSEC_FILE | Tier 2* | Path to file containing ncryptsec | | NOSTR_NCRYPTSEC_PASSWORD | Tier 2* | Password for ncryptsec decryption | | NOSTR_SECRET_KEY_FILE | Tier 3* | Path to file containing the secret key | | NOSTR_SECRET_KEY | Tier 4* | nsec bech32, 64-char hex, or BIP-39 mnemonic | | NOSTR_RELAYS | Yes | Comma-separated relay WebSocket URLs | | NWC_URI | No | Nostr Wallet Connect URI for zap operations | | NWC_URI_FILE | No | Path to file containing NWC URI | | TOR_PROXY | No | SOCKS5h proxy URL (e.g. socks5h://127.0.0.1:9050) | | ALLOW_CLEARNET_WITH_TOR | No | Set to "1" to allow clearnet relays with Tor | | NIP04_ENABLED | No | Set to "1" to enable legacy NIP-04 DMs | | TRANSPORT | No | "stdio" (default) or "http" | | PORT | No | HTTP port (default 3000) | | BIND_ADDRESS | No | HTTP bind address (default 127.0.0.1) | | VEIL_CACHE_TTL | No | Trust cache TTL in seconds (default 300) | | VEIL_CACHE_MAX | No | Trust cache max entries (default 500) | | DISPATCH_IDENTITIES | No | Path to dispatch identities markdown file | *Authentication tiers (use exactly one): 1. **Bunker** (NIP-46) -- most secure, keys never leave the signer 2. **ncryptsec** (NIP-49) -- password-encrypted key, decrypted at startup 3. **File** -- secret read from disk, deleted from process.env after parsing 4. **Env var** -- quickest start, lowest security Secret env vars are deleted from process.env after parsing. ``` -------------------------------- ### Discover NIP-05 Relay Hints Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Fetch relay hints published by a NIP-05 server for a given identifier without resolving the pubkey. Use these hints to pre-populate relay sets. ```javascript nip05-relays({ identifier: "bob@example.com" }) ``` -------------------------------- ### CLI Usage Commands Source: https://context7.com/forgesworn/bray/llms.txt Common commands for interacting with the Nostr Bray CLI. ```bash # Check active identity npx nostr-bray whoami # Derive a work persona npx nostr-bray persona work # Post a note npx nostr-bray post "Hello from nostr-bray!" # Create a blind linkage proof npx nostr-bray prove blind # See all commands npx nostr-bray --help ``` -------------------------------- ### Marketplace L402 Tools Source: https://github.com/forgesworn/bray/blob/main/llms-full.txt Endpoints for discovering, inspecting, and paying for L402/x402 services on Nostr. ```APIDOC ## GET marketplace-discover ### Description Discover L402/x402 paid API services announced on Nostr. ### Parameters #### Query Parameters - **topics** (array) - Optional - **paymentMethod** (string) - Optional - **authors** (array) - Optional - **maxPrice** (number) - Optional - **currency** (string) - Optional - **limit** (number) - Optional - **relays** (array) - Optional ## POST marketplace-pay ### Description Pay an L402 invoice via NWC and store credentials. ### Parameters #### Request Body - **macaroon** (string) - Required - **invoice** (string) - Required - **confirm** (boolean) - Optional ### Response - **credentialId** (string) ``` -------------------------------- ### Create and Verify Group Canaries Source: https://github.com/forgesworn/bray/blob/main/docs/guide.md Use these functions to create a new group canary with specified members and to verify an existing group canary by its ID. Ensure members are valid Nostr public keys. ```javascript canary-group-create({ name: "team-canary", members: ["", ""] }) ``` ```javascript canary-group-verify({ groupId: "" }) ```