# Implementation Plan: Nostr Tools

## Current Tool Inventory (v0.0.19)

| # | Tool | Kind/NIP | Status |
|---|------|----------|--------|
| 1 | `nostr_post` | Any kind | ✅ Shipped |
| 2 | `nostr_post_readme` | 30023 / NIP-23 | ✅ Shipped |
| 3 | `nostr_query` | Filter-based | ✅ Shipped |
| 4 | `nostr_delete` | 5 / NIP-09 | ✅ Shipped |
| 5 | `nostr_react` | 7 / NIP-25 | ✅ Shipped |
| 6 | `nostr_profile_get` | 0 query | ✅ Shipped |
| 7 | `nostr_relay_status` | Pool stats | ✅ Shipped |
| 8 | `local_shell_exec` | OS | ✅ Shipped |
| 9 | `local_file_read` | OS | ✅ Shipped |
| 10 | `local_file_write` | OS | ✅ Shipped |

---

## Pattern for All New Tools

Every tool follows the same three-step pattern in [`src/tools.c`](../src/tools.c):

1. **Schema** — Add OpenAI function schema block in [`tools_build_openai_schema_json()`](../src/tools.c:511)
2. **Executor** — Implement `static char* execute_<name>()` function
3. **Dispatch** — Add `strcmp` case in [`tools_execute()`](../src/tools.c:1620)

Use the shared [`parse_tool_args_json()`](../src/tools.c:527) helper for hardened argument parsing.

---

## Tier 2 Tools — Medium Value, Moderate Implementation

### Tool 11: `nostr_nip05_lookup` — DNS Identity Verification (NIP-05)

#### Purpose
Verify `user@domain.com` NIP-05 identifiers. Useful for trust decisions and profile enrichment.

#### OpenAI Schema
```json
{
  "name": "nostr_nip05_lookup",
  "description": "Verify or look up a NIP-05 DNS identifier and return the associated pubkey and relays",
  "parameters": {
    "type": "object",
    "properties": {
      "identifier": {
        "type": "string",
        "description": "NIP-05 identifier, e.g. user@domain.com"
      },
      "pubkey": {
        "type": "string",
        "description": "Optional 64-char hex pubkey to verify against the identifier"
      }
    },
    "required": ["identifier"]
  }
}
```

#### Implementation: `execute_nostr_nip05_lookup()`

```
1. Parse args_json
2. Extract "identifier" (required string, must contain @)
3. Extract "pubkey" (optional 64-char hex)
4. If pubkey provided:
   - Call nostr_nip05_verify(identifier, pubkey, &relays, &relay_count, 10)
   - Return { success, verified: true/false, pubkey, relays }
5. If no pubkey:
   - Call nostr_nip05_lookup(identifier, pubkey_out, &relays, &relay_count, 10)
   - Return { success, pubkey: pubkey_out, relays }
6. Free relay array after serializing
```

#### Library API Used
- [`nostr_nip05_lookup()`](../nostr_core_lib/nostr_core/nip005.h:13)
- [`nostr_nip05_verify()`](../nostr_core_lib/nostr_core/nip005.h:15)

#### Files Modified
- `src/tools.c`: Schema + executor + dispatch

#### Notes
- Involves HTTP network I/O (fetches `/.well-known/nostr.json` from the domain)
- May block for up to `timeout_seconds` — use 10s default
- Must free the `relays` array returned by the library

---

### Tool 12: `nostr_encode` — Bech32 Entity Encoding (NIP-19/NIP-21)

#### Purpose
Encode hex pubkeys, event IDs, or addressable coordinates into shareable `npub`, `note`, `nprofile`, `nevent`, `naddr` URIs.

#### OpenAI Schema
```json
{
  "name": "nostr_encode",
  "description": "Encode a Nostr entity into a bech32 URI: npub, note, nprofile, nevent, or naddr",
  "parameters": {
    "type": "object",
    "properties": {
      "type": {
        "type": "string",
        "description": "Entity type: npub, note, nprofile, nevent, or naddr"
      },
      "hex": {
        "type": "string",
        "description": "64-char hex value: pubkey for npub/nprofile, event_id for note/nevent"
      },
      "relays": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Optional relay hints for nprofile, nevent, naddr"
      },
      "kind": {
        "type": "integer",
        "description": "Kind number, required for naddr"
      },
      "identifier": {
        "type": "string",
        "description": "d-tag identifier, required for naddr"
      }
    },
    "required": ["type", "hex"]
  }
}
```

#### Implementation: `execute_nostr_encode()`

```
1. Parse args_json
2. Extract "type" (required: npub|note|nprofile|nevent|naddr)
3. Extract "hex" (required, 64-char hex)
4. Convert hex to 32-byte array via nostr_hex_to_bytes()
5. Switch on type:
   - "npub": nostr_build_uri_npub(bytes, output, sizeof output)
   - "note": nostr_build_uri_note(bytes, output, sizeof output)
   - "nprofile": nostr_build_uri_nprofile(bytes, relays, relay_count, output, sizeof output)
   - "nevent": nostr_build_uri_nevent(bytes, relays, relay_count, NULL, -1, 0, output, sizeof output)
   - "naddr": nostr_build_uri_naddr(identifier, bytes, kind, relays, relay_count, output, sizeof output)
6. Return { success, type, uri: output }
```

#### Library API Used
- [`nostr_build_uri_npub()`](../nostr_core_lib/nostr_core/nip021.h:66)
- [`nostr_build_uri_note()`](../nostr_core_lib/nostr_core/nip021.h:68)
- [`nostr_build_uri_nprofile()`](../nostr_core_lib/nostr_core/nip021.h:69)
- [`nostr_build_uri_nevent()`](../nostr_core_lib/nostr_core/nip021.h:71)
- [`nostr_build_uri_naddr()`](../nostr_core_lib/nostr_core/nip021.h:74)

#### Files Modified
- `src/tools.c`: Schema + executor + dispatch

---

### Tool 13: `nostr_decode` — Bech32 Entity Decoding (NIP-19/NIP-21)

#### Purpose
Decode `npub`, `note`, `nprofile`, `nevent`, `naddr` URIs back into hex values and metadata.

#### OpenAI Schema
```json
{
  "name": "nostr_decode",
  "description": "Decode a bech32 Nostr URI into its components: type, hex, relays, kind, identifier",
  "parameters": {
    "type": "object",
    "properties": {
      "uri": {
        "type": "string",
        "description": "Bech32 URI to decode, e.g. npub1..., note1..., nostr:npub1..."
      }
    },
    "required": ["uri"]
  }
}
```

#### Implementation: `execute_nostr_decode()`

```
1. Parse args_json
2. Extract "uri" (required string)
3. Call nostr_parse_uri(uri, &result)
4. Switch on result.type:
   - NPUB: convert result.data.pubkey to hex, return { type: "npub", pubkey: hex }
   - NOTE: convert result.data.event_id to hex, return { type: "note", event_id: hex }
   - NPROFILE: pubkey hex + relays array
   - NEVENT: event_id hex + relays + optional author/kind
   - NADDR: identifier + pubkey hex + kind + relays
5. Call nostr_uri_result_free(&result)
6. Return JSON
```

#### Library API Used
- [`nostr_parse_uri()`](../nostr_core_lib/nostr_core/nip021.h:63)
- [`nostr_uri_result_free()`](../nostr_core_lib/nostr_core/nip021.h:78)

#### Files Modified
- `src/tools.c`: Schema + executor + dispatch

---

### Tool 14: `nostr_dm_send` — Send DM as Tool Call

#### Purpose
Expose DM sending as a tool so the agent can proactively message people (notify admin, reach out to contacts).

#### OpenAI Schema
```json
{
  "name": "nostr_dm_send",
  "description": "Send an encrypted direct message to a Nostr user (NIP-04)",
  "parameters": {
    "type": "object",
    "properties": {
      "recipient_pubkey": {
        "type": "string",
        "description": "64-char hex pubkey of the recipient"
      },
      "message": {
        "type": "string",
        "description": "Plaintext message to send"
      }
    },
    "required": ["recipient_pubkey", "message"]
  }
}
```

#### Implementation: `execute_nostr_dm_send()`

```
1. Parse args_json
2. Extract "recipient_pubkey" (required, 64-char hex)
3. Extract "message" (required, non-empty string)
4. Call nostr_handler_send_dm(recipient_pubkey, message)
5. Return { success: true/false, recipient_pubkey, message_length }
```

#### Library API Used
- [`nostr_handler_send_dm()`](../src/nostr_handler.h:33) — already exists

#### Security Note
- Should be admin-tier only in practice. The tool itself doesn't enforce tier, but the agent's system prompt and skill definitions should restrict usage.

#### Files Modified
- `src/tools.c`: Schema + executor + dispatch

---

### Tool 15: `nostr_relay_info` — Relay Information Document (NIP-11)

#### Purpose
Inspect relay capabilities, supported NIPs, limitations, PoW requirements before publishing.

#### OpenAI Schema
```json
{
  "name": "nostr_relay_info",
  "description": "Fetch the NIP-11 relay information document for a relay URL",
  "parameters": {
    "type": "object",
    "properties": {
      "relay_url": {
        "type": "string",
        "description": "WebSocket relay URL, e.g. wss://relay.example.com"
      }
    },
    "required": ["relay_url"]
  }
}
```

#### Implementation

This requires a new helper in [`nostr_handler.c`](../src/nostr_handler.c) since NIP-11 fetching needs an HTTP GET request with `Accept: application/nostr+json` header.

##### New function: `nostr_handler_relay_info_json()`

```
1. Convert ws:// or wss:// URL to http:// or https://
2. Use libcurl to HTTP GET with header "Accept: application/nostr+json"
3. Return the raw JSON response string
```

##### `execute_nostr_relay_info()` in tools.c:

```
1. Parse args_json
2. Extract "relay_url" (required string, must start with ws:// or wss://)
3. Call nostr_handler_relay_info_json(relay_url)
4. Parse returned JSON
5. Return { success, relay_url, info: { name, description, pubkey, supported_nips, limitations, ... } }
```

#### Files Modified
- `src/nostr_handler.h`: Add `nostr_handler_relay_info_json()` declaration
- `src/nostr_handler.c`: Implement using libcurl (already linked)
- `src/tools.c`: Schema + executor + dispatch

---

## Tier 3 Tools — High Value, More Complex

### Tool 16: `nostr_dm_send_nip17` — Private DMs via Gift Wrap (NIP-17/NIP-59)

#### Purpose
Send modern, metadata-private DMs using the NIP-17 gift wrap protocol instead of legacy NIP-04.

#### OpenAI Schema
```json
{
  "name": "nostr_dm_send_nip17",
  "description": "Send a private direct message using NIP-17 gift wrap protocol for metadata privacy",
  "parameters": {
    "type": "object",
    "properties": {
      "recipient_pubkey": {
        "type": "string",
        "description": "64-char hex pubkey of the recipient"
      },
      "message": {
        "type": "string",
        "description": "Plaintext message to send"
      },
      "subject": {
        "type": "string",
        "description": "Optional conversation subject"
      }
    },
    "required": ["recipient_pubkey", "message"]
  }
}
```

#### Implementation

This is a multi-step crypto pipeline. The library handles the heavy lifting but we need to orchestrate:

##### New function: `nostr_handler_send_dm_nip17()` in nostr_handler.c

```
1. Query recipient's kind 10050 relay list (DM relays)
   - Build filter: { kinds: [10050], authors: [recipient_pubkey], limit: 1 }
   - Call nostr_relay_pool_query_sync()
   - Extract relay URLs via nostr_nip17_extract_dm_relays()
   - Fall back to our own relays if no 10050 found
2. Create kind 14 chat event:
   - nostr_nip17_create_chat_event(message, &recipient_pubkey, 1, subject, NULL, NULL, our_pubkey)
3. Gift wrap and send:
   - nostr_nip17_send_dm(chat_event, &recipient_pubkey, 1, our_private_key, gift_wraps, max_wraps, 0)
4. Publish each gift wrap to recipient's DM relays
5. Return success/failure + event metadata
```

##### `execute_nostr_dm_send_nip17()` in tools.c:

```
1. Parse args_json
2. Extract recipient_pubkey, message, optional subject
3. Call nostr_handler_send_dm_nip17(recipient_pubkey, message, subject)
4. Return { success, recipient_pubkey, protocol: "nip17" }
```

#### Library API Used
- [`nostr_nip17_create_chat_event()`](../nostr_core_lib/nostr_core/nip017.h:28)
- [`nostr_nip17_send_dm()`](../nostr_core_lib/nostr_core/nip017.h:100)
- [`nostr_nip17_extract_dm_relays()`](../nostr_core_lib/nostr_core/nip017.h)
- [`nostr_nip59_create_gift_wrap()`](../nostr_core_lib/nostr_core/nip059.h:50)

#### Files Modified
- `src/nostr_handler.h`: Add `nostr_handler_send_dm_nip17()` declaration
- `src/nostr_handler.c`: Implement the full NIP-17 send pipeline
- `src/tools.c`: Schema + executor + dispatch

---

### Tool 17: `nostr_encrypt` — NIP-44 Encryption

#### Purpose
Encrypt arbitrary content for a specific recipient using modern NIP-44 encryption.

#### OpenAI Schema
```json
{
  "name": "nostr_encrypt",
  "description": "Encrypt plaintext for a recipient using NIP-44 ChaCha20+HMAC encryption",
  "parameters": {
    "type": "object",
    "properties": {
      "recipient_pubkey": {
        "type": "string",
        "description": "64-char hex pubkey of the recipient"
      },
      "plaintext": {
        "type": "string",
        "description": "Text to encrypt"
      }
    },
    "required": ["recipient_pubkey", "plaintext"]
  }
}
```

#### Implementation: `execute_nostr_encrypt()`

```
1. Parse args_json
2. Extract recipient_pubkey (64-char hex), plaintext (non-empty string)
3. Convert recipient_pubkey hex to 32-byte array
4. Allocate output buffer (plaintext length * 2 + 256 for base64 overhead)
5. Call nostr_nip44_encrypt(our_private_key, recipient_pubkey_bytes, plaintext, output, output_size)
6. Return { success, recipient_pubkey, ciphertext: output }
```

#### Library API Used
- [`nostr_nip44_encrypt()`](../nostr_core_lib/nostr_core/nip044.h:28)

#### Security Note
- Admin-tier only. Uses the agent's private key for ECDH shared secret.

#### Files Modified
- `src/tools.c`: Schema + executor + dispatch

---

### Tool 18: `nostr_decrypt` — NIP-44 Decryption

#### Purpose
Decrypt NIP-44 encrypted content from a known sender.

#### OpenAI Schema
```json
{
  "name": "nostr_decrypt",
  "description": "Decrypt NIP-44 encrypted content from a sender",
  "parameters": {
    "type": "object",
    "properties": {
      "sender_pubkey": {
        "type": "string",
        "description": "64-char hex pubkey of the sender"
      },
      "ciphertext": {
        "type": "string",
        "description": "Base64-encoded NIP-44 encrypted payload"
      }
    },
    "required": ["sender_pubkey", "ciphertext"]
  }
}
```

#### Implementation: `execute_nostr_decrypt()`

```
1. Parse args_json
2. Extract sender_pubkey (64-char hex), ciphertext (non-empty string)
3. Convert sender_pubkey hex to 32-byte array
4. Allocate output buffer
5. Call nostr_nip44_decrypt(our_private_key, sender_pubkey_bytes, ciphertext, output, output_size)
6. Return { success, sender_pubkey, plaintext: output }
```

#### Library API Used
- [`nostr_nip44_decrypt()`](../nostr_core_lib/nostr_core/nip044.h:62)

#### Security Note
- Admin-tier only.

#### Files Modified
- `src/tools.c`: Schema + executor + dispatch

---

### Tool 19: `nostr_list_manage` — List Management (NIP-51)

#### Purpose
Manage mute lists (kind 10000), bookmarks (kind 10003), follow sets, relay sets. Lets the agent curate its social graph.

#### OpenAI Schema
```json
{
  "name": "nostr_list_manage",
  "description": "Add or remove items from a Nostr list: mute list, bookmarks, relay sets, etc. (NIP-51)",
  "parameters": {
    "type": "object",
    "properties": {
      "list_kind": {
        "type": "integer",
        "description": "Kind number of the list: 10000=mute, 10001=pins, 10003=bookmarks, 3=follows, 10002=relays"
      },
      "action": {
        "type": "string",
        "description": "Action: add or remove"
      },
      "items": {
        "type": "array",
        "items": {
          "type": "array",
          "items": { "type": "string" }
        },
        "description": "Array of tag tuples to add/remove, e.g. [[p, pubkey], [e, event_id]]"
      }
    },
    "required": ["list_kind", "action", "items"]
  }
}
```

#### Implementation

This is a read-modify-write pattern for replaceable events:

##### `execute_nostr_list_manage()`:

```
1. Parse args_json
2. Extract list_kind (integer), action ("add"|"remove"), items (array of tag arrays)
3. Query existing list:
   - Build filter: { kinds: [list_kind], authors: [our_pubkey], limit: 1 }
   - Call nostr_handler_query_json(filter, 8000)
4. Parse existing event tags (or start with empty array if none found)
5. If action == "add":
   - For each item in items: append to tags if not already present
6. If action == "remove":
   - For each item in items: remove matching tag from tags
7. Publish updated event:
   - Call nostr_handler_publish_kind_event(list_kind, existing_content_or_empty, updated_tags, &result)
8. Return { success, list_kind, action, items_affected, event_id }
```

#### Notes
- Replaceable events (kind 10000-19999) automatically replace previous versions
- Kind 3 (follows) is also replaceable
- Content field may contain NIP-44 encrypted private items — preserve it unchanged
- Must deduplicate tags when adding

#### Files Modified
- `src/tools.c`: Schema + executor + dispatch

---

## Implementation Order

```mermaid
graph TD
    A[nostr_nip05_lookup] --> B[nostr_encode]
    B --> C[nostr_decode]
    C --> D[nostr_dm_send]
    D --> E[nostr_relay_info]
    E --> F[nostr_encrypt]
    F --> G[nostr_decrypt]
    G --> H[nostr_dm_send_nip17]
    H --> I[nostr_list_manage]
    
    style A fill:#FFFFE0
    style B fill:#FFFFE0
    style C fill:#FFFFE0
    style D fill:#FFFFE0
    style E fill:#FFFFE0
    style F fill:#FFD700
    style G fill:#FFD700
    style H fill:#FFD700
    style I fill:#FFD700
```

Yellow = Tier 2 (moderate), Gold = Tier 3 (complex).

### Recommended batching:

**Batch A — Pure computation, no new handler functions:**
1. `nostr_encode` — pure NIP-21 encoding
2. `nostr_decode` — pure NIP-21 decoding
3. `nostr_dm_send` — thin wrapper around existing `nostr_handler_send_dm()`

**Batch B — Network I/O tools:**
4. `nostr_nip05_lookup` — HTTP fetch via library
5. `nostr_relay_info` — HTTP fetch via new handler function + libcurl

**Batch C — Crypto tools:**
6. `nostr_encrypt` — NIP-44 encrypt
7. `nostr_decrypt` — NIP-44 decrypt

**Batch D — Complex orchestration:**
8. `nostr_dm_send_nip17` — multi-step NIP-17/59 pipeline, new handler function
9. `nostr_list_manage` — read-modify-write replaceable events

## Build & Validate

After each batch:
1. Run `./build_static.sh` to verify compilation
2. Run `./increment_and_push.sh` with descriptive message
