Files
didactyl/plans/skills_edit_page.md

16 KiB

Skills Editor Page — skills-edit.html

Overview

A standalone web page in ~/lt/client-ndk/www/ for editing Didactyl agent skills. Built on top of template.html — all existing template functionality (auth, sidenav, hamburger, relay status, footer, theme toggle, logout) remains intact.

The logged-in user IS the agent. The page uses Nostr subscriptions via the NDK worker to fetch skills and the adoption list, and publishes events via publishEvent() to save/adopt/remove skills.


Data Model

Nostr Event Kinds

Kind Purpose Replaceable?
31123 Public skill definition Yes, by d-tag
31124 Private skill definition (NIP-44 encrypted content) Yes, by d-tag
10123 Skill adoption list Yes, single per pubkey

Skill Event Structure

content: markdown template with {{variables}} (plain authored instructions; runtime handles role packaging)
tags:
  [d, slug]
  [description, human-readable description]
  [trigger, dm|cron|nostr-subscription|webhook|chain]  (optional)
  [filter, type-specific filter JSON]                    (optional)
  [llm, fallback chain string]                           (optional)
  [temperature, number]                                  (optional)
  [max_tokens, number]                                   (optional)
  [seed, number]                                         (optional)
  [requires_tool, tool_name]                             (0..N)
  [requires_skill, skill_d_tag]                          (0..N)
  [optional_tool, tool_name]                             (0..N)

Adoption List (kind 10123)

{
  "kind": 10123,
  "tags": [
    ["a", "31123:<pubkey>:skill-slug"],
    ["a", "31124:<pubkey>:private-skill-slug"]
  ]
}

Page Layout

Three-column layout filling the body area between header and footer:

┌─────────────────────────────────────────────────────────────────────┐
│ HEADER  [hamburger]              Skills Editor              [avatar]│
├──────────────┬──────────────────────────────────┬───────────────────┤
│              │                                  │                   │
│  SKILLS      │  SKILL EDITOR                    │  TOOLS            │
│  LIST        │                                  │                   │
│              │  [slug] [description]             │  nostr_post       │
│  ┌────────┐  │  [trigger] [filter]              │  nostr_query      │
│  │ search │  │  [llm] [temp] [tokens]           │  nostr_dm_send    │
│  └────────┘  │                                  │  local_shell_exec │
│              │  ┌──────────────────────────────┐ │  memory_save      │
│  My|Pub|Adpt │  │                              │ │  memory_recall    │
│              │  │  Large textarea with          │ │  skill_create     │
│  skill-1  ★  │  │  syntax highlighting for     │ │  ...              │
│  skill-2     │  │  {{variables}} and tool names │ │                   │
│  skill-3  ★  │  │                              │ │  Double-click to  │
│  skill-4     │  │                              │ │  add as           │
│  ...         │  │                              │ │  requires_tool    │
│              │  └──────────────────────────────┘ │                   │
│  ──────────  │                                  │                   │
│  ADOPTED     │  [Save] [Save Private] [New]     │                   │
│  identity ↕  │  [Adopt] [Remove from adopted]   │                   │
│  chat     ↕  │                                  │                   │
│  monitor  ↕  │                                  │                   │
│              │                                  │                   │
├──────────────┴──────────────────────────────────┴───────────────────┤
│ FOOTER  [relay status]          [status]              [info]        │
└─────────────────────────────────────────────────────────────────────┘

Left Column — Skills List (~20% width)

  • Search input at top to filter skills by slug/description
  • Filter tabs: My | Public | Adopted
    • My: skills authored by the current pubkey (kind 31123 + 31124)
    • Public: public skills from anyone (kind 31123 only, no author filter) — for discovery and copying
    • Adopted: skills currently in the kind 10123 adoption list
  • Scrollable skill list: each item shows slug, description snippet, author name/short-pubkey, ★ if adopted
  • Clicking a skill loads it into the editor
  • Copy/Fork button: when viewing a public skill from another author, a "Copy to My Skills" button creates a new skill under the current pubkey with the same content (new d-tag to avoid collision)
  • Adopted Skills section below the list:
    • Shows current adoption list entries in order
    • Drag-to-reorder support (or up/down buttons)
    • Visual indicator of adoption order

Center Column — Skill Editor (~60% width)

  • Metadata row: slug (d-tag), description inputs
  • Trigger row: trigger type dropdown, filter input
  • Execution params row: llm input, temperature, max_tokens, seed
  • Requirements display: shows current requires_tool, requires_skill, optional_tool tags as removable chips/badges
  • Large textarea for skill content (markdown template)
    • Syntax highlighting overlay for {{variable_names}} in accent color
    • Tool name references highlighted when they match known tools
  • Action buttons:
    • Save (publish as kind 31123)
    • Save Private (publish as kind 31124 with NIP-44 encryption)
    • New Skill (clear editor)
    • Adopt (add to kind 10123 list)
    • Remove from Adopted (remove from kind 10123 list)
    • Delete Skill (kind 5 deletion event)

Right Column — Tools List (~20% width)

  • Hardcoded list of all Didactyl tools from TOOLS.md
  • Each tool shows name and brief description
  • Double-click a tool to add it as a requires_tool tag on the current skill
  • Visual indicator if tool is already required by current skill
  • Search/filter input at top

Syntax Highlighting Approach

Use a transparent overlay <div> positioned exactly over the <textarea>. The textarea has transparent text color; the overlay div renders the same text with highlighted spans. Both scroll together.

Highlighted elements:

  • {{variable_name}} — accent color (e.g., var(--accent-color))
  • Tool names (when matching known tools) — distinct highlight color

Nostr Subscriptions

On page load (after auth):

  1. My skills subscription: { kinds: [31123, 31124], authors: [currentPubkey], limit: 200 } — fetch agent's own skills (public + private)
  2. Public skills subscription (when Public filter active): { kinds: [31123], limit: 200 } — fetch public skills from anyone for discovery/copying
  3. Adoption list subscription: { kinds: [10123], authors: [currentPubkey], limit: 1 } — fetch current adoption list

The Public subscription is activated on demand when the user switches to the Public tab, to avoid fetching large amounts of data unnecessarily. My skills and adoption list are always loaded.

Event handling:

  • ndkEvent listener processes incoming skill events and adoption list events
  • Skills are parsed and stored in a local array, keyed by pubkey:d-tag
  • Adoption list tags are parsed to determine which skills are adopted and their order

Skill Publishing

Save Public Skill (kind 31123)

const event = {
  kind: 31123,
  content: editorContent,  // raw markdown template
  tags: [
    [d, slug],
    [description, descriptionText],
    // trigger tags if set
    // execution param tags
    // requires_tool tags
    // requires_skill tags
    // optional_tool tags
  ],
  created_at: Math.floor(Date.now() / 1000)
};
await publishEvent(event);

Save Private Skill (kind 31124)

Same structure but content is NIP-44 encrypted. The d tag stays in plaintext; other metadata moves into the encrypted payload per SKILLS.md spec.

Update Adoption List (kind 10123)

const event = {
  kind: 10123,
  content: '',
  tags: adoptedSkillAddresses.map(addr => [a, addr]),
  created_at: Math.floor(Date.now() / 1000)
};
await publishEvent(event);

Hardcoded Tools List

Extracted from TOOLS.md — all tool names with descriptions:

const DIDACTYL_TOOLS = [
  { name: 'nostr_post', description: 'Publish a Nostr event to connected relays' },
  { name: 'nostr_delete', description: 'Request deletion of events - NIP-09 kind 5' },
  { name: 'nostr_react', description: 'React to a Nostr event - NIP-25 kind 7' },
  { name: 'nostr_query', description: 'Query events from relays using a Nostr filter' },
  { name: 'nostr_my_events', description: 'Query recent events authored by this agent' },
  { name: 'nostr_dm_send', description: 'Send a NIP-04 encrypted DM' },
  { name: 'nostr_dm_send_nip17', description: 'Send a private DM using NIP-17 gift wrap' },
  { name: 'nostr_profile_get', description: 'Look up a Nostr profile by pubkey' },
  { name: 'nostr_nip05_lookup', description: 'Look up or verify a NIP-05 identifier' },
  { name: 'nostr_encode', description: 'Encode a Nostr entity into nostr: URI' },
  { name: 'nostr_decode', description: 'Decode a Nostr bech32/nostr: URI' },
  { name: 'nostr_relay_status', description: 'Get relay connection status and statistics' },
  { name: 'nostr_relay_info', description: 'Fetch NIP-11 relay information document' },
  { name: 'nostr_subscription_status', description: 'List managed runtime Nostr subscriptions' },
  { name: 'nostr_subscription_set', description: 'Update a managed runtime subscription' },
  { name: 'nostr_encrypt', description: 'Encrypt plaintext using NIP-44' },
  { name: 'nostr_decrypt', description: 'Decrypt NIP-44 ciphertext' },
  { name: 'nostr_list_manage', description: 'Add/remove tags in replaceable list events' },
  { name: 'nostr_block_list', description: 'View blocked pubkeys/events/hashtags' },
  { name: 'nostr_block_edit', description: 'Add or remove blocked tuples in kind-10000' },
  { name: 'nostr_pubkey', description: 'Return agent pubkey in hex' },
  { name: 'nostr_npub', description: 'Return agent pubkey as npub bech32' },
  { name: 'agent_identity', description: 'Build agent identity context block' },
  { name: 'admin_identity', description: 'Build admin identity context block' },
  { name: 'nostr_admin_profile', description: 'Build admin profile context block' },
  { name: 'nostr_admin_contacts', description: 'Build admin contacts context block' },
  { name: 'nostr_admin_relays', description: 'Build admin relay context block' },
  { name: 'nostr_admin_notes', description: 'Build admin notes context block' },
  { name: 'nostr_agent_profile', description: 'Build agent profile context block' },
  { name: 'nostr_agent_contacts', description: 'Build agent contacts context block' },
  { name: 'nostr_agent_relays', description: 'Build agent relay context block' },
  { name: 'nostr_agent_notes', description: 'Build agent notes context block' },
  { name: 'skill_create', description: 'Create or update a skill definition' },
  { name: 'skill_edit', description: 'Edit an existing self skill by d tag' },
  { name: 'skill_list', description: 'List available skills with adoption status' },
  { name: 'skill_adopt', description: 'Adopt a skill into kind 10123 list' },
  { name: 'skill_remove', description: 'Remove a skill from kind 10123 list' },
  { name: 'skill_search', description: 'Search public skills by query/author' },
  { name: 'trigger_list', description: 'List active triggered skills and status' },
  { name: 'task_list', description: 'Build current task list context block' },
  { name: 'task_manage', description: 'Manage agent task memory on Nostr' },
  { name: 'memory_save', description: 'Prepend entry to encrypted agent memory' },
  { name: 'memory_recall', description: 'Recall encrypted agent memory' },
  { name: 'model_get', description: 'Get current LLM runtime configuration' },
  { name: 'model_set', description: 'Update active LLM configuration' },
  { name: 'model_list', description: 'List available model IDs' },
  { name: 'config_store', description: 'Encrypt and publish agent config' },
  { name: 'config_recall', description: 'Fetch and decrypt agent config' },
  { name: 'agent_version', description: 'Return current Didactyl version' },
  { name: 'local_http_fetch', description: 'Fetch HTTP/S resources' },
  { name: 'local_shell_exec', description: 'Execute a shell command' },
  { name: 'local_file_read', description: 'Read a local file as text' },
  { name: 'local_file_write', description: 'Write text content to a local file' },
  { name: 'tool_list', description: 'List available tools with schemas' },
  { name: 'cashu_wallet_balance', description: 'Return wallet balances by mint' },
  { name: 'cashu_wallet_info', description: 'Fetch mint info' },
  { name: 'cashu_wallet_mint_quote', description: 'Request a mint quote' },
  { name: 'cashu_wallet_mint_check', description: 'Check mint quote status' },
  { name: 'cashu_wallet_mint_claim', description: 'Claim minted proofs' },
  { name: 'cashu_wallet_melt_quote', description: 'Request a melt quote' },
  { name: 'cashu_wallet_melt_pay', description: 'Pay a melt quote' },
  { name: 'cashu_wallet_check_proofs', description: 'Check proof states' },
  { name: 'cashu_wallet_receive_token', description: 'Receive an ecash token' },
  { name: 'cashu_wallet_send_token', description: 'Create outbound ecash token' },
  { name: 'cashu_wallet_mints_set', description: 'Set wallet/public mints' },
  { name: 'blossom_upload', description: 'Upload a file to Blossom server' },
  { name: 'blossom_download', description: 'Download a Blossom blob' },
  { name: 'blossom_head', description: 'Fetch Blossom blob metadata' },
  { name: 'blossom_delete', description: 'Delete a Blossom blob' },
  { name: 'blossom_list', description: 'List Blossom blobs for a pubkey' },
  { name: 'nostr_post_readme', description: 'Publish README.md as kind 30023' },
  { name: 'nostr_file_md_to_longform_post', description: 'Publish markdown file as kind 30023' },
];

Implementation Sequence

flowchart TD
    A[Copy template.html to skills-edit.html] --> B[Add page-specific CSS for 3-column layout]
    B --> C[Build HTML structure: left/center/right columns]
    C --> D[Implement Nostr subscriptions for skills + adoption list]
    D --> E[Build skills list rendering with search and filter]
    E --> F[Build skill editor form with metadata fields]
    F --> G[Build large textarea with syntax highlighting overlay]
    G --> H[Build tools list column with double-click to add]
    H --> I[Implement skill save/publish for kind 31123]
    I --> J[Implement private skill save for kind 31124]
    J --> K[Implement adoption list management - adopt/remove/reorder]
    K --> L[Implement skill deletion via kind 5]
    L --> M[Wire up all event listeners and UI interactions]
    M --> N[Test and refine]

Files Modified

File Change
~/lt/client-ndk/www/skills-edit.html NEW — the entire skill editor page
~/lt/client-ndk/www/index.html Add navigation entry for skills-edit page (optional)