# Skills Demo Page — Design Document

## Purpose

A standalone web page that demonstrates Didactyl skills running in a browser context. The page presents a minimal word-processing interface where users can load text, discover skills from Nostr relays, execute them against the text via an LLM, and edit/republish skill definitions.

This document is the complete specification for building the page. The front-end project consuming this document has its own LLM calling capability and Nostr connectivity.

---

## Concepts

### What is a Skill?

A skill is a Nostr event (kind `31123` for public, `31124` for private) that defines a self-contained LLM execution unit. Each skill specifies:

- A **template** with system and user prompt sections
- An **LLM specification** (model preferences with fallback chain)
- **Parameters** like temperature and max_tokens
- A **description** for human display

Skills are replaceable events keyed by their `d` tag (the d_tag). Publishing a new event with the same `d` tag replaces the previous version.

### Skill Event Structure

```json
{
  "kind": 31123,
  "pubkey": "<author hex pubkey>",
  "created_at": 1709750000,
  "content": "{\"description\":\"Check spelling and grammar\",\"context_mode\":\"full\",\"llm\":\"openai/gpt-4o-mini, cheap\",\"tools\":false,\"max_tokens\":2000,\"temperature\":0.1,\"template\":\"system:\\nYou are a spelling and grammar checker.\\n\\nRules:\\n- Fix spelling errors\\n- Fix grammar errors\\n- Preserve original formatting\\n- Return ONLY the corrected text, no explanations\\n\\nuser:\\n{{message}}\"}",
  "tags": [
    ["d", "spellcheck"],
    ["app", "didactyl"],
    ["scope", "public"],
    ["description", "Spelling and grammar checker"],
    ["t", "text-processing"],
    ["t", "skills-demo"]
  ]
}
```

The `content` field is a **JSON string** containing these fields:

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `description` | string | yes | Human-readable description |
| `context_mode` | string | no | `full` for demo skills (skill provides its own template) |
| `llm` | string | no | LLM model preference with fallback chain |
| `tools` | bool | no | `false` for text-processing skills |
| `max_tokens` | int | no | Max tokens for LLM response |
| `temperature` | float | no | Sampling temperature |
| `template` | string | yes | The prompt template with `system:` and `user:` sections |

### Template Format

Templates use a simple section-based format:

```
system:
You are a spelling and grammar checker.

Rules:
- Fix spelling errors
- Fix grammar errors

user:
{{message}}
```

The `{{message}}` placeholder is replaced with the user's text at execution time.

### Skill Address Format

Skills are referenced by their NIP-33 address: `<kind>:<author_pubkey_hex>:<d_tag>`

Example: `31123:52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8:spellcheck`

---

## Demo Skills

Four skills are published by the Didactyl agent (pubkey `52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8`). All are tagged with `t:skills-demo` for easy discovery.

### 1. Spellcheck — `d:spellcheck`

```
system:
You are a spelling and grammar checker.

Rules:
- Fix spelling errors
- Fix grammar errors
- Preserve original formatting and paragraph structure
- Preserve line breaks exactly as they appear
- Ignore technical terms: API, JSON, HTTP, nostr, pubkey, npub, nsec, NIP
- Canadian English preferred
- Return ONLY the corrected text, no explanations or commentary

user:
{{message}}
```

- temperature: `0.1`
- max_tokens: `4000`

### 2. Condense — `d:condense-5`

```
system:
You are a text condensing editor.

Rules:
- Reduce the text length by approximately 5%
- Preserve the original meaning and tone
- Preserve paragraph structure
- Remove redundant words and phrases
- Tighten sentences without changing voice
- Do not add new information
- Return ONLY the condensed text, no explanations or commentary

user:
{{message}}
```

- temperature: `0.3`
- max_tokens: `4000`

### 3. Translate to Japanese — `d:translate-ja`

```
system:
You are a professional translator specializing in English to Japanese translation.

Rules:
- Translate the entire text from English to Japanese
- Use natural, fluent Japanese appropriate for the content register
- Preserve paragraph structure and formatting
- Preserve any technical terms in their commonly accepted Japanese form
- If a term has no standard Japanese equivalent, keep the English term in katakana
- Return ONLY the Japanese translation, no explanations or commentary

user:
{{message}}
```

- temperature: `0.3`
- max_tokens: `4000`

### 4. Convert to Poem — `d:convert-to-poem`

```
system:
You are a creative poet.

Rules:
- Convert the given text into a poem
- Capture the key themes and meaning of the original text
- Use vivid imagery and poetic devices
- Aim for 12-20 lines
- Free verse is acceptable but light rhyme is welcome
- Return ONLY the poem, no explanations or commentary

user:
{{message}}
```

- temperature: `0.9`
- max_tokens: `2000`

---

## Discovering Skills from Nostr

### Query Filter

To find the demo skills, query relays with this filter:

```json
{
  "kinds": [31123],
  "#t": ["skills-demo"],
  "limit": 20
}
```

To find skills by a specific author:

```json
{
  "kinds": [31123],
  "authors": ["52a3e82f7b3743852fbe804cfcbf4db3448115887895247c001f2b50e790acb8"],
  "#t": ["skills-demo"],
  "limit": 20
}
```

### Recommended Relays

- `wss://relay.damus.io`
- `wss://nos.lol`
- `wss://relay.primal.net`

### Parsing a Skill Event

1. Receive the event from the relay
2. Parse `event.content` as JSON to get the skill definition object
3. Extract `description`, `template`, `temperature`, `max_tokens`
4. Extract the `d` tag value from `event.tags` as the skill d_tag
5. Extract the `description` tag from `event.tags` as a display label
6. The skill is ready to display and execute

---

## Page Layout

```
+------------------------------------------------------------------+
|  SKILLS DEMO                                          [dark/light]|
+------------------------------------------------------------------+
|                                    |                              |
|  +------------------------------+ | +---------------------------+|
|  | TEXT EDITOR                   | | | SKILLS PANEL              ||
|  |                               | | |                           ||
|  | [large editable textarea     ]| | | Loading skills...         ||
|  | [filled with default text    ]| | |                           ||
|  | [                            ]| | | [x] spellcheck            ||
|  | [                            ]| | |     Spelling and grammar  ||
|  | [                            ]| | |     checker               ||
|  | [                            ]| | |                           ||
|  | [                            ]| | | [ ] condense-5            ||
|  | [                            ]| | |     Reduce text by 5%     ||
|  | [                            ]| | |                           ||
|  | [                            ]| | | [ ] translate-ja          ||
|  | [                            ]| | |     Translate to Japanese ||
|  | [                            ]| | |                           ||
|  | [                            ]| | | [ ] convert-to-poem       ||
|  |                               | | |     Convert to poem       ||
|  +------------------------------+ | | |                           ||
|                                    | | [Run Selected Skill]      ||
|  +------------------------------+ | | [Edit Skill]              ||
|  | STATUS BAR                    | | |                           ||
|  | Ready / Running... / Done     | | | ─── Skill Details ───    ||
|  +------------------------------+ | | template: ...             ||
|                                    | | temperature: 0.1          ||
|                                    | | max_tokens: 4000          ||
|                                    | +---------------------------+|
+------------------------------------------------------------------+
```

### Left Column — Text Editor (65% width)

- A large `<textarea>` or `contenteditable` div, minimum 500px tall
- Pre-filled with default sample text (see Default Text section below)
- Fully editable by the user
- When a skill runs, the text content is sent as `{{message}}`
- After the LLM responds, the textarea content is **replaced** with the result
- An undo button restores the previous text (keep a history stack)

### Right Column — Skills Panel (35% width)

- **Skills list**: Radio buttons or selectable cards, one skill selected at a time
- Each skill card shows:
  - Skill d_tag (the `d` tag)
  - Description (from the `description` tag or content field)
  - Author pubkey (truncated, e.g., `52a3e8...0acb8`)
- **Run Selected Skill** button: executes the selected skill against the current text
- **Edit Skill** button: opens the skill editor (see Skill Editor section)
- **Skill Details** section: shows the full template, temperature, max_tokens for the selected skill
- **Refresh Skills** button: re-queries relays for updated skill events

### Status Bar

- Shows current state: `Ready`, `Running skill: spellcheck...`, `Done (1.2s)`, or `Error: ...`
- Displays token usage if available from the LLM response

---

## Skill Execution Flow

```mermaid
sequenceDiagram
    participant User
    participant Page as Demo Page
    participant LLM as LLM Provider

    User->>Page: Select skill + click Run
    Page->>Page: Parse skill template
    Page->>Page: Split template into system/user sections
    Page->>Page: Replace message placeholder with textarea content
    Page->>LLM: Send system prompt + user message
    Note over Page,LLM: Use skill temperature and max_tokens
    LLM-->>Page: Response text
    Page->>Page: Push current text to undo stack
    Page->>Page: Replace textarea with response
    Page->>User: Show completion status
```

### Template Parsing

The template field uses a simple format with `system:` and `user:` section markers:

```
system:
<system prompt content>

user:
<user prompt content with {{message}} placeholder>
```

**Parsing algorithm:**

1. Split the template string on the line `user:` (case-sensitive, must be at start of line)
2. Everything before `user:` is the system section; strip the leading `system:` prefix
3. Everything after `user:` is the user section
4. In the user section, replace `{{message}}` with the textarea content
5. Trim whitespace from both sections

**Pseudocode:**

```javascript
function parseSkillTemplate(template, userText) {
  const userMarkerIndex = template.indexOf('\nuser:\n');
  if (userMarkerIndex === -1) {
    // Fallback: entire template is system, user text is the message
    return {
      system: template.replace(/^system:\n/, '').trim(),
      user: userText
    };
  }

  let systemPart = template.substring(0, userMarkerIndex);
  systemPart = systemPart.replace(/^system:\n/, '').trim();

  let userPart = template.substring(userMarkerIndex + '\nuser:\n'.length);
  userPart = userPart.replace('{{message}}', userText).trim();

  return { system: systemPart, user: userPart };
}
```

### LLM Call

With the parsed system and user prompts, call the LLM:

```javascript
const response = await callLLM({
  messages: [
    { role: 'system', content: parsed.system },
    { role: 'user', content: parsed.user }
  ],
  temperature: skill.temperature || 0.7,
  max_tokens: skill.max_tokens || 2000
});
```

The page's own LLM calling capability handles the actual API request. The skill just provides the prompts and parameters.

---

## Skill Editor

When the user clicks **Edit Skill**, a modal or inline editor opens for the selected skill.

### Editor Fields

| Field | Input Type | Description |
|-------|-----------|-------------|
| Slug | text input (readonly for existing, editable for new) | The `d` tag identifier |
| Description | text input | One-line human description |
| Template | large textarea (monospace) | The full `system:/user:` template |
| Temperature | number input (0.0 - 2.0, step 0.1) | LLM sampling temperature |
| Max Tokens | number input (100 - 8000) | Maximum response tokens |

### Editor Actions

- **Save & Publish**: Builds a new kind `31123` event and publishes to relays
- **Cancel**: Closes editor without changes
- **New Skill**: Opens editor with blank fields for creating a new skill

### Publishing a Skill

```mermaid
sequenceDiagram
    participant Editor as Skill Editor
    participant Signer as Nostr Signer
    participant Relay as Nostr Relays

    Editor->>Editor: Build skill content JSON
    Editor->>Editor: Build event with kind 31123 + tags
    Editor->>Signer: Sign event
    Signer-->>Editor: Signed event
    Editor->>Relay: Publish to connected relays
    Relay-->>Editor: OK confirmation
    Editor->>Editor: Update local skill list
```

**Building the event:**

```javascript
function buildSkillEvent(d_tag, description, template, temperature, maxTokens) {
  const content = JSON.stringify({
    description: description,
    context_mode: 'full',
    llm: 'default',
    tools: false,
    max_tokens: maxTokens,
    temperature: temperature,
    template: template
  });

  return {
    kind: 31123,
    content: content,
    tags: [
      ['d', d_tag],
      ['app', 'didactyl'],
      ['scope', 'public'],
      ['description', description],
      ['t', 'text-processing'],
      ['t', 'skills-demo']
    ],
    created_at: Math.floor(Date.now() / 1000)
  };
}
```

The event must be signed with the user's Nostr private key (via NIP-07 browser extension or the app's own signer) before publishing to relays.

---

## Default Text

Pre-fill the textarea with this sample text that exercises the demo skills well:

```
The Sovereign Agent

In the not-too-distant future, software agents will roam freely across decentralized networks, communicating through cryptographic protocols that no central authority can censor or control. These agents will learn new capabilities not from app stores or corporate APIs, but from each other — sharing skills as freely as humans share ideas.

Imagine an agent that wakes up on any computer in the world. You enter twelve words, and there it is — your agent, with all its memories, skills, and personality intact. It doesn't live on a server you rent. It doesn't depend on a company staying in business. It exists wherever you need it, sovereign and portable.

The key insight is that skills are the new apps. Instead of installing software, agents adopt skills — small, self-contained instructions that teach them how to perform specific tasks. A spelling checker. A translator. A poet. Each skill is just a Nostr event, discoverable by anyone, adoptable by any agent.

This is not science fiction. The protocols exist today. Nostr provides the communication layer. Bitcoin provides the economic layer. Cryptography provides the trust layer. What remains is to build the agents themselves — and to set them free.

There are some erors in this text that a good spellchecker should catch. The writting could also be more concise, and it would be intresting to see it translated or converted into verse.
```

Note: The last paragraph intentionally contains spelling errors (erors, writting, intresting) to demonstrate the spellcheck skill.

---

## Undo/Redo

Maintain a text history stack:

```javascript
const textHistory = [];
let historyIndex = -1;

function pushHistory(text) {
  // Truncate any forward history
  textHistory.splice(historyIndex + 1);
  textHistory.push(text);
  historyIndex = textHistory.length - 1;
}

function undo() {
  if (historyIndex > 0) {
    historyIndex--;
    return textHistory[historyIndex];
  }
  return null;
}

function redo() {
  if (historyIndex < textHistory.length - 1) {
    historyIndex++;
    return textHistory[historyIndex];
  }
  return null;
}
```

- Push the current text to history **before** replacing it with the LLM response
- Push the initial default text as the first history entry
- Show Undo/Redo buttons below the textarea

---

## Responsive Behavior

- On screens wider than 900px: two-column layout as shown
- On screens narrower than 900px: stack vertically — text editor on top, skills panel below
- The textarea should have a minimum height of 400px and resize vertically

---

## Error Handling

| Scenario | Behavior |
|----------|----------|
| No relays connected | Show "No relay connection" in skills panel with retry button |
| No skills found | Show "No skills found. Try different relays or create a new skill." |
| Skill content parse error | Show "Invalid skill format" badge on the skill card, disable Run |
| LLM call fails | Show error in status bar, do not replace textarea content |
| Publish fails | Show error in editor, keep editor open for retry |

---

## Summary of Nostr Operations

| Operation | Kind | Direction | When |
|-----------|------|-----------|------|
| Discover skills | `31123` | Read from relays | Page load + refresh |
| Execute skill | — | N/A (local LLM call) | User clicks Run |
| Publish skill | `31123` | Write to relays | User saves in editor |

The page does NOT need to interact with the Didactyl agent's HTTP API. All skill discovery and publishing happens directly via Nostr relays. Skill execution happens locally using the page's own LLM capability.
