# REQ-013: `ievo config` TUI Config Tab + `ievo secret` Rename

## Metadata
- **Status:** ready
- **Priority:** high
- **Dependencies:** REQ-000
- **Created:** 2026-03-05
- **Implemented:**

## Summary

Rename the existing `ievo config` subcommand group to `ievo secret` (same subcommands: `set/get/list/delete`). Make `ievo config` a new active command that opens the TUI with a new "Config" tab active. The Config tab shows global settings, masked credential keys, and current project manifest info.

## Context

### Current state — `ievo config`

`ievo config` is currently a Typer sub-app registered in `src/ievo/cli.py`:

```python
app.add_typer(config_cmd.app, name="config", help="Manage settings and credentials.")
```

It lives in `src/ievo/commands/config_cmd.py` and exposes four subcommands:

- `config set <key> <value>` — stores plaintext or encrypted (if `is_sensitive_key(key)` returns True)
- `config get <key>` — retrieves and masks sensitive values
- `config list` — lists all plaintext config keys and encrypted credential key names
- `config delete <key>` — removes a key from config or credentials

Data layer:
- `src/ievo/core/global_config.py` — `load_global_config(home)`, `get_config(home, key)`, `set_config(home, key, value)` — reads/writes `~/.ievo/config.json`
- `src/ievo/core/credentials.py` — `list_credentials(home)`, `get_credential(home, key)`, `set_credential(home, key, value)`, `delete_credential(home, key)`, `is_sensitive_key(key)` — reads/writes `~/.ievo/credentials.enc` (RSA + AES-256-GCM)
- `src/ievo/core/config.py` — `ensure_home() -> Path` returns `~/.ievo/` and creates it on first call

`DEFAULT_CONFIG` keys in `~/.ievo/config.json`:
```python
{
    "default_model": "sonnet",
    "marketplace_url": "https://github.com/ievo-ai/marketplace",
    "auto_update_registry": True,
    "github_auth": None,     # "gh_cli" | "pat" | None
    "claude_auth": None,     # "cli" | "env" | None
    "telemetry": None,       # True | False | None
}
```

Sensitive key detection: `is_sensitive_key(key)` returns True if the key starts with `ANTHROPIC_`, `GITHUB_`, `SENTRY_`, `AWS_`, `OPENAI_` or contains `TOKEN`, `KEY`, `SECRET`, `PASSWORD`, `CREDENTIAL`, `PAT`.

### Current state — TUI

`src/ievo/tui/app.py` contains `IevoApp(App)` — a Textual TUI. Current tabs (defined in `compose()`):
- `"agents"` — agent selection list
- `"pipeline"` — pipeline diagram
- `"evolution"` — evolution log
- `"files"` — project file tree

Tab switching uses `action_tab(tab_id)` called by key bindings. Initial tab: `TabbedContent(initial="agents")`. Key bindings live in `BINDINGS`:
```python
BINDINGS = [
    Binding("a", "tab('agents')", "Agents"),
    Binding("p", "tab('pipeline')", "Pipeline"),
    Binding("e", "tab('evolution')", "Evolution"),
    Binding("f", "tab('files')", "Files"),
    ...
]
```

`run_tui()` at the bottom of `app.py` is the entry point called from `src/ievo/cli.py`.

### Current state — cli.py

Active commands registered in `src/ievo/cli.py`:
```python
app.add_typer(config_cmd.app, name="config", help="Manage settings and credentials.")
app.command(name="update", ...)(update.update)
app.command(name="uninstall", ...)(uninstall.uninstall)
```

The `main()` callback with `invoke_without_command=True` launches the TUI when no subcommand is given:
```python
if ctx.invoked_subcommand is None:
    ...
    run_tui()
```

### Changes this REQ requires

1. **Rename `ievo config` → `ievo secret`**
   - Add new file `src/ievo/commands/secret_cmd.py` — re-exports the same four subcommands with deprecation wording removed (not deprecated — this is an active command under a new name)
   - OR: simply re-register the existing `config_cmd.app` under the name `"secret"` in `cli.py`
   - Remove the `config_cmd` registration under name `"config"` in `cli.py`

2. **New `ievo config` command**
   - New command `ievo config` registered as an active (non-deprecated) command in `cli.py`
   - Behavior: runs the TUI with the "Config" tab active
   - Must NOT pass `--no-args-is-help` behavior — it has no subcommands

3. **New "Config" tab in TUI**
   - New `TabPane("Config", id="config")` added to `IevoApp.compose()`
   - Key binding: `Binding("c", "tab('config')", "Config")` added to `BINDINGS`
   - `run_tui()` gains an optional parameter: `initial_tab: str = "agents"` — passed to `TabbedContent(initial=...)` to allow opening on a specific tab
   - The Config tab content (three sections):

   **Section 1 — Global Settings** (`~/.ievo/config.json`)
   All keys from `load_global_config(home)` displayed as a table or list:
   ```
   default_model        sonnet
   marketplace_url      https://github.com/ievo-ai/marketplace
   auto_update_registry True
   github_auth          gh_cli
   claude_auth          cli
   telemetry            True
   ```

   **Section 2 — Credentials** (`~/.ievo/credentials.enc`)
   Key names from `list_credentials(home)` with values masked:
   ```
   ANTHROPIC_API_KEY    •••
   GITHUB_TOKEN         •••
   ```
   No decryption is performed — keys are listed only.

   **Section 3 — Project Manifest** (`.ievo/manifest.yaml`)
   From `ProjectManifest.load(root)` (or "No project" message if `find_project_root()` returns None):
   ```
   project   my-project
   version   0.1.0
   created   2026-03-05
   cli_version 0.9.0
   agents    spec-writer, coder, architect
   ```

4. **`ievo config` CLI command**
   - Registered in `cli.py` as a plain `@app.command(name="config")`
   - Calls `run_tui(initial_tab="config")`

### ProjectManifest model

```python
# src/ievo/core/project.py
class ProjectManifest:
    project: str
    version: str
    created: str
    cli_version: str
    agents: dict[str, str]  # name → version

    @classmethod
    def load(cls, root: Path) -> ProjectManifest: ...
    def save(self, root: Path) -> None: ...
```

## Acceptance Criteria

### AC-1: `ievo secret` exposes the same four subcommands

- [ ] `ievo secret set <key> <value>` sets a config value (sensitive keys encrypted)
- [ ] `ievo secret get <key>` retrieves and masks sensitive values
- [ ] `ievo secret list` lists all config keys and credential key names
- [ ] `ievo secret delete <key>` deletes a key from config or credentials
- [ ] `ievo secret` with no subcommand shows help (same behavior as old `ievo config`)

### AC-2: old `ievo config <subcommand>` no longer routes to credential management

- [ ] `ievo config set <key> <value>` does NOT invoke the credential/config setter — it invokes the TUI instead (or shows help)
- [ ] `ievo config list` does NOT list credentials — it invokes the TUI instead

### AC-3: `ievo config` opens TUI with Config tab active

- [ ] `ievo config` launches the Textual TUI
- [ ] On launch, the active tab is "Config" (not "Agents")
- [ ] Key binding `c` switches to the Config tab from any other tab
- [ ] `run_tui()` accepts an `initial_tab: str` parameter; when passed `"config"`, the TUI opens on the Config tab

### AC-4: Config tab — Global Settings section

- [ ] Config tab contains a "Global Settings" section heading
- [ ] All keys from `~/.ievo/config.json` are displayed as `key   value` rows
- [ ] When `~/.ievo/config.json` does not exist, section shows "No global settings found."
- [ ] Values are shown as plain strings (no masking for non-sensitive config keys)

### AC-5: Config tab — Credentials section

- [ ] Config tab contains a "Credentials" section heading
- [ ] Each credential key name is displayed with value shown as `•••` (no decryption)
- [ ] When `~/.ievo/credentials.enc` does not exist or is empty, section shows "No credentials stored."

### AC-6: Config tab — Project Manifest section

- [ ] Config tab contains a "Project" section heading
- [ ] Displays `project`, `version`, `created`, `cli_version` fields from `.ievo/manifest.yaml`
- [ ] Lists installed agent names (comma-separated or as a list)
- [ ] When no iEvo project is found in current directory or parents, section shows "No project found."

### AC-7: `ievo secret` is registered as an active (non-deprecated) command

- [ ] `ievo --help` lists `secret` in the commands table without `[DEPRECATED]` label
- [ ] `ievo --help` lists `config` in the commands table without `[DEPRECATED]` label

### AC-8: Config tab refresh

- [ ] Pressing `r` (existing "Refresh" binding) also refreshes the Config tab content from disk

## Negative Acceptance Criteria

- Does NOT remove `ievo secret set/get/list/delete` functionality — all four subcommands continue to work
- Does NOT add a deprecation warning to `ievo secret` — it is a new active command name, not a deprecated alias
- Does NOT show decrypted credential values — only key names masked as `•••`
- Does NOT require a project to display the Global Settings section — it shows `~/.ievo/config.json` regardless of project state
- Does NOT add a new credential or config management UI inside the TUI — the Config tab is read-only display only
- Does NOT rename or move the backing data files (`~/.ievo/config.json`, `~/.ievo/credentials.enc`)
- Does NOT change the behavior of the `r` key binding in other tabs

## Technical Notes

### run_tui signature change

```python
def run_tui(initial_tab: str = "agents") -> None:
    app = IevoApp(initial_tab=initial_tab)
    app.run()
```

`IevoApp.__init__` stores `initial_tab` and passes it to `TabbedContent(initial=initial_tab)`.

### ievo config command registration

```python
# In src/ievo/cli.py
@app.command(name="config", help="Open TUI Config tab.")
def config_cmd_open() -> None:
    from ievo.tui.app import run_tui
    run_tui(initial_tab="config")
```

The name `config` must not conflict with the old `config_cmd.app` Typer registration — remove the old `add_typer` call.

### Config tab widget layout

Use `VerticalScroll` containing `Static` widgets with Rich markup for section headers and key-value rows. Follow the same style as the existing `#pipeline-view` widget.

### Test file

New tests in `tests/test_config_tab.py` (TUI Pilot tests) and `tests/test_secret_cmd.py` (CLI unit tests).

## Test Specification

### Tests in `tests/test_secret_cmd.py`

| Test | What it verifies |
|------|-----------------|
| `test_secret_set_plaintext` | `ievo secret set default_model opus` → config key saved |
| `test_secret_set_sensitive` | `ievo secret set ANTHROPIC_API_KEY sk-xxx` → credential encrypted |
| `test_secret_get_plaintext` | `ievo secret get default_model` → prints value |
| `test_secret_get_sensitive_masked` | `ievo secret get ANTHROPIC_API_KEY` → prints masked value |
| `test_secret_list_shows_keys` | `ievo secret list` → shows config keys and credential key names |
| `test_secret_delete_plaintext` | `ievo secret delete default_model` → key removed |
| `test_secret_delete_sensitive` | `ievo secret delete ANTHROPIC_API_KEY` → credential removed |
| `test_secret_no_args_shows_help` | `ievo secret` with no subcommand → help output |

### Tests in `tests/test_config_tab.py`

| Test | What it verifies |
|------|-----------------|
| `test_config_command_opens_tui_on_config_tab` | `ievo config` → TUI active tab is "config" |
| `test_run_tui_initial_tab_agents` | `run_tui(initial_tab="agents")` → active tab is "agents" |
| `test_run_tui_initial_tab_config` | `run_tui(initial_tab="config")` → active tab is "config" |
| `test_config_tab_key_binding` | Press `c` from Agents tab → switches to Config tab |
| `test_config_tab_shows_global_settings` | Config tab renders config keys from `~/.ievo/config.json` |
| `test_config_tab_no_config_file` | Config tab with no config file → "No global settings found." |
| `test_config_tab_shows_credential_keys` | Config tab lists credential key names as `•••` |
| `test_config_tab_no_credentials` | Config tab with no credentials → "No credentials stored." |
| `test_config_tab_shows_manifest_info` | Config tab renders project name, version, agents from manifest |
| `test_config_tab_no_project` | Config tab with no project → "No project found." in manifest section |
| `test_refresh_updates_config_tab` | Press `r` → Config tab content reloads from disk |
