v0.0.20 - Add Tier2/Tier3 Nostr tools: nip05 lookup, encode/decode, dm send, relay info, nip44 encrypt/decrypt, nip17 dm, list manage
This commit is contained in:
@@ -1,247 +1,596 @@
|
||||
# Implementation Plan: 4 New Nostr Tools
|
||||
# Implementation Plan: Nostr Tools
|
||||
|
||||
## Overview
|
||||
## Current Tool Inventory (v0.0.19)
|
||||
|
||||
Add four new tools to Didactyl's tool system. Each tool follows the same pattern established by existing tools in `src/tools.c`:
|
||||
|
||||
1. Register OpenAI function schema in `tools_build_openai_schema_json()`
|
||||
2. Implement `execute_*()` function
|
||||
3. Wire dispatch in `tools_execute()`
|
||||
|
||||
All four tools reuse existing infrastructure — no new `nostr_handler` APIs needed for the first three, and only a thin new wrapper for the fourth.
|
||||
| # | 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 | `shell_exec` | OS | ✅ Shipped |
|
||||
| 9 | `file_read` | OS | ✅ Shipped |
|
||||
| 10 | `file_write` | OS | ✅ Shipped |
|
||||
|
||||
---
|
||||
|
||||
## Tool 1: `nostr_delete` — Event Deletion Request (NIP-09)
|
||||
## Pattern for All New Tools
|
||||
|
||||
### Purpose
|
||||
Let the agent retract or request deletion of events it previously published. Essential for self-correction.
|
||||
Every tool follows the same three-step pattern in [`src/tools.c`](../src/tools.c):
|
||||
|
||||
### OpenAI Schema
|
||||
```json
|
||||
{
|
||||
"name": "nostr_delete",
|
||||
"description": "Request deletion of one or more previously published events (NIP-09 kind 5)",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"event_ids": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Array of event ID hex strings to request deletion for"
|
||||
},
|
||||
"kinds": {
|
||||
"type": "array",
|
||||
"items": { "type": "integer" },
|
||||
"description": "Array of kind numbers corresponding to the events being deleted"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string",
|
||||
"description": "Optional reason for the deletion request"
|
||||
}
|
||||
},
|
||||
"required": ["event_ids"]
|
||||
}
|
||||
}
|
||||
```
|
||||
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)
|
||||
|
||||
### Implementation: `execute_nostr_delete()`
|
||||
|
||||
```
|
||||
1. Parse args_json (with same hardened parsing as nostr_post)
|
||||
2. Extract "event_ids" array (required) — validate each is a 64-char hex string
|
||||
3. Extract "kinds" array (optional) — validate each is an integer
|
||||
4. Extract "reason" string (optional) — use as content, default to empty string
|
||||
5. Build tags array:
|
||||
- For each event_id: add ["e", event_id]
|
||||
- For each kind: add ["k", kind_as_string]
|
||||
6. Call nostr_handler_publish_kind_event(5, reason, tags, &publish_result)
|
||||
7. Return standard publish result JSON
|
||||
```
|
||||
|
||||
### Files Modified
|
||||
- `src/tools.c`: Add schema block in `tools_build_openai_schema_json()`, add `execute_nostr_delete()`, add dispatch case in `tools_execute()`
|
||||
Use the shared [`parse_tool_args_json()`](../src/tools.c:527) helper for hardened argument parsing.
|
||||
|
||||
---
|
||||
|
||||
## Tool 2: `nostr_react` — Reactions (NIP-25)
|
||||
## Tier 2 Tools — Medium Value, Moderate Implementation
|
||||
|
||||
### Purpose
|
||||
Let the agent react to events — like, dislike, or emoji react. Gives the agent social presence.
|
||||
### Tool 11: `nostr_nip05_lookup` — DNS Identity Verification (NIP-05)
|
||||
|
||||
### OpenAI Schema
|
||||
#### Purpose
|
||||
Verify `user@domain.com` NIP-05 identifiers. Useful for trust decisions and profile enrichment.
|
||||
|
||||
#### OpenAI Schema
|
||||
```json
|
||||
{
|
||||
"name": "nostr_react",
|
||||
"description": "React to a Nostr event with a like, dislike, or emoji (NIP-25 kind 7)",
|
||||
"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": {
|
||||
"event_id": {
|
||||
"identifier": {
|
||||
"type": "string",
|
||||
"description": "Hex event ID of the event to react to"
|
||||
"description": "NIP-05 identifier, e.g. user@domain.com"
|
||||
},
|
||||
"event_pubkey": {
|
||||
"type": "string",
|
||||
"description": "Hex pubkey of the event author"
|
||||
},
|
||||
"event_kind": {
|
||||
"type": "integer",
|
||||
"description": "Kind number of the event being reacted to"
|
||||
},
|
||||
"reaction": {
|
||||
"type": "string",
|
||||
"description": "Reaction content: + for like, - for dislike, or an emoji. Default: +"
|
||||
}
|
||||
},
|
||||
"required": ["event_id", "event_pubkey"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Implementation: `execute_nostr_react()`
|
||||
|
||||
```
|
||||
1. Parse args_json
|
||||
2. Extract "event_id" (required, 64-char hex)
|
||||
3. Extract "event_pubkey" (required, 64-char hex)
|
||||
4. Extract "event_kind" (optional integer)
|
||||
5. Extract "reaction" (optional string, default "+")
|
||||
6. Build tags array:
|
||||
- ["e", event_id]
|
||||
- ["p", event_pubkey]
|
||||
- If event_kind provided: ["k", kind_as_string]
|
||||
7. Call nostr_handler_publish_kind_event(7, reaction, tags, &publish_result)
|
||||
8. Return standard publish result JSON
|
||||
```
|
||||
|
||||
### Files Modified
|
||||
- `src/tools.c`: Same three insertion points as above
|
||||
|
||||
---
|
||||
|
||||
## Tool 3: `nostr_profile_get` — Profile Lookup (kind 0)
|
||||
|
||||
### Purpose
|
||||
Let the agent look up any user's profile metadata. Useful for WoT reasoning, greeting users by name, checking identity.
|
||||
|
||||
### OpenAI Schema
|
||||
```json
|
||||
{
|
||||
"name": "nostr_profile_get",
|
||||
"description": "Look up a Nostr user profile (kind 0 metadata) by pubkey",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"pubkey": {
|
||||
"type": "string",
|
||||
"description": "Hex public key of the user to look up"
|
||||
"description": "Optional 64-char hex pubkey to verify against the identifier"
|
||||
}
|
||||
},
|
||||
"required": ["pubkey"]
|
||||
"required": ["identifier"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Implementation: `execute_nostr_profile_get()`
|
||||
#### Implementation: `execute_nostr_nip05_lookup()`
|
||||
|
||||
```
|
||||
1. Parse args_json
|
||||
2. Extract "pubkey" (required, 64-char hex)
|
||||
3. Build filter:
|
||||
{
|
||||
"kinds": [0],
|
||||
"authors": [pubkey],
|
||||
"limit": 1
|
||||
}
|
||||
4. Call nostr_handler_query_json(filter, 8000)
|
||||
5. Parse the returned events JSON
|
||||
6. If events found:
|
||||
- Extract the first event's content (which is a JSON string of profile fields)
|
||||
- Parse that content JSON
|
||||
- Return { success: true, pubkey: ..., profile: { name, display_name, about, picture, nip05, ... } }
|
||||
7. If no events: return { success: true, pubkey: ..., profile: null }
|
||||
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
|
||||
```
|
||||
|
||||
### Files Modified
|
||||
- `src/tools.c`: Same three insertion points
|
||||
#### 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 4: `nostr_relay_status` — Relay Health Dashboard
|
||||
### Tool 12: `nostr_encode` — Bech32 Entity Encoding (NIP-19/NIP-21)
|
||||
|
||||
### Purpose
|
||||
Let the agent introspect its own relay connectivity. Useful for self-diagnostics and admin status reports.
|
||||
#### Purpose
|
||||
Encode hex pubkeys, event IDs, or addressable coordinates into shareable `npub`, `note`, `nprofile`, `nevent`, `naddr` URIs.
|
||||
|
||||
### OpenAI Schema
|
||||
#### OpenAI Schema
|
||||
```json
|
||||
{
|
||||
"name": "nostr_relay_status",
|
||||
"description": "Get connection status and statistics for all connected relays",
|
||||
"name": "nostr_encode",
|
||||
"description": "Encode a Nostr entity into a bech32 URI: npub, note, nprofile, nevent, or naddr",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
"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
|
||||
|
||||
This tool needs access to the relay pool, which is currently a `static` global in `nostr_handler.c`. Two options:
|
||||
|
||||
**Option A (preferred):** Add a new function to `nostr_handler.h`:
|
||||
```c
|
||||
char* nostr_handler_relay_status_json(void);
|
||||
```
|
||||
This function iterates the pool's relays using `nostr_relay_pool_list_relays()` and `nostr_relay_pool_get_relay_stats()`, builds a JSON array, and returns it.
|
||||
|
||||
**Option B:** Expose the pool pointer — not recommended, breaks encapsulation.
|
||||
|
||||
### `nostr_handler_relay_status_json()` in `nostr_handler.c`:
|
||||
#### Implementation: `execute_nostr_encode()`
|
||||
|
||||
```
|
||||
1. Call nostr_relay_pool_list_relays(g_pool, &urls, &statuses)
|
||||
2. For each relay:
|
||||
a. Get stats via nostr_relay_pool_get_relay_stats(g_pool, url)
|
||||
b. Build JSON object with:
|
||||
- url
|
||||
- status (disconnected/connecting/connected/error)
|
||||
- ping_latency_ms
|
||||
- events_received
|
||||
- events_published
|
||||
- events_published_ok
|
||||
- events_published_failed
|
||||
- connection_uptime_start
|
||||
3. Return JSON array as string
|
||||
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 }
|
||||
```
|
||||
|
||||
### `execute_nostr_relay_status()` in `tools.c`:
|
||||
#### 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)
|
||||
|
||||
```
|
||||
1. Parse args_json (accept empty/no args)
|
||||
2. Call nostr_handler_relay_status_json()
|
||||
3. Wrap in { success: true, relays: [...] }
|
||||
4. Return
|
||||
#### 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"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Files Modified
|
||||
- `src/nostr_handler.h`: Add `nostr_handler_relay_status_json()` declaration
|
||||
- `src/nostr_handler.c`: Implement `nostr_handler_relay_status_json()`
|
||||
- `src/tools.c`: Schema + execute + dispatch
|
||||
#### 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
|
||||
|
||||
1. **`nostr_delete`** — simplest, pure tag construction + existing publish
|
||||
2. **`nostr_react`** — same pattern, slightly different tags
|
||||
3. **`nostr_profile_get`** — uses existing query path, adds profile content parsing
|
||||
4. **`nostr_relay_status`** — requires one new `nostr_handler` function
|
||||
```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
|
||||
```
|
||||
|
||||
All four schemas should be added to `tools_build_openai_schema_json()` in a single pass, and all four dispatch cases added to `tools_execute()` in a single pass, to minimize diff churn.
|
||||
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 all four tools are implemented:
|
||||
After each batch:
|
||||
1. Run `./build_static.sh` to verify compilation
|
||||
2. Run `./increment_and_push.sh "Add nostr_delete, nostr_react, nostr_profile_get, nostr_relay_status tools"`
|
||||
2. Run `./increment_and_push.sh` with descriptive message
|
||||
|
||||
Reference in New Issue
Block a user