# Didactyl — Interactive Guided Setup Mode

## Overview

When `./didactyl` is run with **no command-line arguments**, the agent enters an interactive guided setup wizard on the terminal. This replaces the current behavior of silently trying to load `./genesis.jsonc` and failing.

The wizard first asks whether the user is creating a new agent or starting an existing one, then branches accordingly.

---

## Trigger Condition

```
if (argc == 1)  ->  enter interactive setup mode
```

Any argument at all (`--config`, `--nsec`, `--help`, etc.) bypasses the wizard and uses the existing startup path. The zero-argument case is the only entry point.

---

## TUI Menu Convention

All menus use **single-letter hotkeys** (case-insensitive). The hotkey letter is rendered **underlined** in the terminal using ANSI escape `\e[4m` (underline on) and `\e[0m` (reset). For example, `[N]ew` displays with the N underlined.

Global shortcuts:
- `q` or `x` -- quit/back out of any menu
- Ctrl+C -- clean exit (restore terminal settings)

Input is read with `fgets()` and matched on the first non-whitespace character.

---

## Wizard Flow

```mermaid
flowchart TD
    A[./didactyl with no args] --> B[Welcome screen]
    B --> C{New or Existing?}
    C -->|New agent| D[New Agent Flow]
    C -->|Existing agent| E[Existing Agent Flow]
    C -->|Load genesis| F[Load genesis.jsonc and boot]
    C -->|Quit| Z[Exit]

    D --> D1[Step: Generate or provide nsec]
    D1 --> D2[Check if pubkey exists on Nostr]
    D2 -->|Exists - warn| D3{Continue or abort?}
    D2 -->|Fresh| D4[Step: Admin npub]
    D3 -->|Continue| D4
    D3 -->|Abort| Z
    D4 --> D5[Step: LLM provider + test]
    D5 --> D6[Step: Relay config]
    D6 --> D7[Step: Review and boot/export]
    D7 --> BOOT[Normal startup]

    E --> E1[Step: Provide nsec]
    E1 --> E2[Connect to bootstrap relays]
    E2 --> E3[Recover config from Nostr]
    E3 -->|Config found| E4{Review recovered config}
    E3 -->|Config missing| E5[Prompt for missing config]
    E4 --> BOOT
    E5 --> D4
```

---

## Step Details

### Welcome Screen

**Prompt:**
```
=============================================
  Didactyl v0.0.71 -- Interactive Setup
=============================================

  [N]ew agent    -- create a fresh Nostr identity
  [E]xisting     -- start an agent you have already set up
  [L]oad         -- boot from a genesis.jsonc file
  [Q]uit

>
```

---

### Path A: New Agent

#### A1. Identity

**Prompt:**
```
New Agent Setup -- Identity

  [G]enerate a new Nostr keypair
  [P]rovide an existing nsec
  [B]ack

>
```

**Option G -- Generate new keypair:**
- Call `nostr_generate_keypair()` from `nip006.h`
- Call `nostr_key_to_bech32()` from `nip019.h` with `hrp="nsec"` and `hrp="npub"` to display bech32 keys
- Display the generated nsec and npub
- **Critical warning**: "Save your nsec securely. It will NOT be stored unless you choose to write a genesis file."
- Prompt user to confirm they have saved the nsec before proceeding

**Option P -- Provide existing nsec:**
- Accept nsec1... bech32 or 64-char hex
- Mask input with `termios` echo disable
- Validate using existing `derive_keys_from_nsec()` logic
- Display derived npub for confirmation

**After key derivation -- Existing identity check:**
- Connect to default bootstrap relays (damus, primal, nos.lol)
- Query kind 10002 for the derived pubkey
- If found: warn the user that this identity already exists on Nostr
  - "This pubkey already has a kind 10002 relay list on Nostr."
  - "Running first-run genesis will overwrite existing profile/relay/skill events."
  - "[C]ontinue anyway or [A]bort?"
- If not found: inform user this is a fresh identity, proceed

#### A2. Administrator

**Prompt:**
```
New Agent Setup -- Administrator

  Enter the admin's Nostr public key (npub1... or hex):
>
```

- Validate using `decode_pubkey_hex_or_npub()`
- Display the decoded hex for confirmation
- This is required -- loop until valid input
- `b` to go back

#### A3. LLM Provider

**Prompt:**
```
New Agent Setup -- LLM Provider

  Didactyl needs an OpenAI-compatible LLM API.

  Base URL [https://api.openai.com/v1]:
  API Key:
  Model [gpt-4o-mini]:
  Max Tokens [512]:
  Temperature [0.7]:
```

- Show defaults in brackets, accept Enter for default
- After collecting all fields, make a **test API call**:
  - Send a minimal chat completion request
  - Display result: "LLM test: OK (model responded)" or "LLM test: FAILED (HTTP 401 -- check API key)"
  - On failure:

```
  LLM test: FAILED (HTTP 401 -- check API key)

  [R]e-enter LLM settings
  [S]kip test and continue anyway
  [Q]uit

>
```

#### A4. Relay Configuration

**Prompt:**
```
New Agent Setup -- Relay Configuration

  Current relays:
    1. wss://relay.damus.io
    2. wss://nos.lol
    3. wss://relay.primal.net

  [A]dd a relay
  [R]emove a relay (by number)
  [D]one -- use this list
  [B]ack

>
```

- Start with the 3 default bootstrap relays
- Allow adding custom relay URLs (validate wss:// or ws:// prefix)
- Allow removing by number (prompt: "Remove which number?")
- Minimum 1 relay required
- Display updated list after each change

#### A5. Review and Confirm

**Prompt:**
```
New Agent Setup -- Review

  Identity:    npub1...
  Admin:       npub1...
  LLM:         claude-haiku-4.5 @ https://api.ppq.ai
  Relays:      3 configured
  DM Protocol: nip04

  [B]oot the agent now
  [W]rite genesis.jsonc (without nsec) and boot
  [I]nclude nsec in genesis.jsonc and boot (security risk!)
  [E]xport genesis.jsonc (without nsec) and exit
  [S]tart over
  [Q]uit

>
```

- **B** -- populate `didactyl_config_t` in memory and proceed to normal startup
- **W** -- write genesis.jsonc without the nsec field, then boot
- **I** -- write genesis.jsonc WITH nsec (with explicit warning), then boot
- **E** -- write file and exit so user can review
- **S** -- restart wizard from welcome screen
- **Q** -- exit

---

### Path B: Existing Agent

#### B1. Provide nsec

**Prompt:**
```
Existing Agent -- Identity

  Enter your agent's nsec (nsec1... or hex):
>
```

- Mask input with `termios` echo disable
- Validate and derive pubkey
- Display npub for confirmation

#### B2. Recover Config from Nostr

After key derivation:
1. Connect to default bootstrap relays
2. Query kind 10002 for the pubkey's relay list
3. If relay list found: expand relay pool with discovered relays
4. Query encrypted kind 30078 events for `d=llm_config` and `d=agent_config`
5. Decrypt and apply recovered config (reuses existing `recover_missing_runtime_config_from_nostr()` logic)

**Display recovery status:**
```
Existing Agent -- Config Recovery

  Relay list (kind 10002):  FOUND (6 relays)
  LLM config:               FOUND (claude-haiku-4.5 @ ppq.ai)
  Admin config:             FOUND (npub1...)

  [B]oot with recovered config
  [E]dit settings before booting
  [Q]uit

>
```

If any required config is missing:
```
Existing Agent -- Config Recovery

  Relay list (kind 10002):  FOUND (6 relays)
  LLM config:               NOT FOUND
  Admin config:             NOT FOUND

  Some required config was not found on Nostr.
  Entering guided setup for missing fields...
```

Then jump to the appropriate new-agent steps (A2/A3) for the missing pieces only.

---

### Path C: Load Genesis File

**Prompt:**
```
Load Genesis File

  Path [./genesis.jsonc]:
>
```

- Accept file path, default to `./genesis.jsonc`
- Load with `config_load()`
- If nsec is missing from the file, prompt for it
- Proceed to normal startup

---

## Implementation Architecture

### New Source File: `src/setup_wizard.c` / `src/setup_wizard.h`

Keep the wizard logic isolated from `main.c`. The interface:

```c
// Returns 0 on success (config populated, ready to boot)
// Returns -1 on abort/error
// Returns 1 on "wrote genesis and exit" (no boot)
int setup_wizard_run(didactyl_config_t* config, char* genesis_path_out, size_t path_size);
```

### Changes to `src/main.c`

In `main()`, before the existing argument parsing:

```c
if (argc == 1) {
    if (!isatty(STDIN_FILENO)) {
        print_usage(argv[0]);
        return 1;
    }

    debug_init(DEBUG_LEVEL_INFO);
    if (nostr_init() != NOSTR_SUCCESS) {
        fprintf(stderr, "Failed to initialize nostr core\n");
        return 1;
    }

    didactyl_config_t cfg;
    memset(&cfg, 0, sizeof(cfg));
    char genesis_path[256] = {0};

    int wizard_rc = setup_wizard_run(&cfg, genesis_path, sizeof(genesis_path));
    if (wizard_rc < 0) {
        nostr_cleanup();
        return 1;  // user aborted
    }
    if (wizard_rc == 1) {
        nostr_cleanup();
        return 0;  // wrote genesis and exit
    }

    // Continue with normal startup using populated cfg
    // Jump past config_load() into the startup checklist
    goto startup_with_config;
}
```

### Changes to `nostr_core_lib`

**No new functions needed.** The existing API surface is sufficient:
- `nostr_generate_keypair()` -- generate private/public key pair
- `nostr_key_to_bech32(key, "nsec", output)` -- encode private key as nsec1...
- `nostr_key_to_bech32(key, "npub", output)` -- encode public key as npub1...
- `nostr_decode_nsec()` / `nostr_decode_npub()` -- decode bech32 inputs

### LLM Test Call

Reuse the existing `llm.c` infrastructure:
- Temporarily initialize `llm_init()` with the test config
- Make a single completion call with a trivial prompt
- Check for HTTP success and valid response
- Clean up with `llm_cleanup()`

### Existing Identity Check / Config Recovery

For both new and existing agent paths:
- Initialize relay pool with bootstrap relays
- Query kind 10002 for the derived pubkey
- For existing agents: also query kind 30078 encrypted config events
- Reuse `is_first_run_from_kind10002()` and `recover_missing_runtime_config_from_nostr()` logic

### Genesis File Writer

A helper function that serializes the collected config to JSONC format:

```c
int setup_wizard_write_genesis(const didactyl_config_t* config,
                                const char* path,
                                int include_nsec);
```

This writes a human-readable JSONC file with comments, matching the existing `genesis.jsonc` format.

---

## Terminal I/O Considerations

- Use `fgets()` for line input (not `scanf`)
- Mask nsec input with `termios` echo disable on Linux
- Handle Ctrl+C gracefully (restore terminal settings via `atexit` handler)
- Use ANSI underline (`\e[4m`) for hotkey letters in menu options
- Use ANSI colors sparingly for emphasis (warnings in yellow/red)
- All prompts go to stderr, so stdout can be piped if needed

---

## Security Considerations

- Generated nsec is held in memory only unless user explicitly chooses to write genesis with nsec
- nsec input is masked (echo disabled) during entry
- If writing genesis with nsec, display a clear warning about the security implications
- The nsec-less genesis file is the recommended default
- After writing, suggest: "For subsequent runs, use: `./didactyl --config genesis.jsonc --nsec <your_nsec>`"
- Or: "Set DIDACTYL_NSEC environment variable for convenience"

---

## Files to Create/Modify

| File | Action | Description |
|------|--------|-------------|
| `src/setup_wizard.h` | Create | Wizard public interface |
| `src/setup_wizard.c` | Create | Interactive wizard implementation |
| `src/main.c` | Modify | Add `argc == 1` check to enter wizard before arg parsing |
| `Makefile` | Modify | Add `setup_wizard.o` to build |
| `docs/GENESIS.md` | Modify | Document the interactive setup mode |
| `README.md` | Modify | Add guided setup to usage section |

---

## Edge Cases

1. **Piped/non-TTY stdin**: Detect with `isatty(STDIN_FILENO)`. If not a TTY, print usage and exit instead of entering wizard.
2. **Ctrl+C during setup**: Signal handler restores terminal settings and exits cleanly.
3. **Genesis file already exists at write path**: Prompt "[O]verwrite or [C]hoose different path?" before overwriting.
4. **LLM test timeout**: Set a reasonable timeout (10s) and allow skipping.
5. **No relay connectivity during identity check**: Warn but allow proceeding -- the check is best-effort.
6. **Existing agent with no config on Nostr**: Seamlessly transition to new-agent steps for the missing pieces.
7. **Back navigation**: Each step supports `b` to go back to the previous step.
