# REQ-004: CLI Self-Update

## Metadata
- **Status:** ready
- **Priority:** high
- **Dependencies:** REQ-000
- **Blocks:** —
- **Questions:** 0
- **Created:** 2026-03-03

## Context

ievo is a Python CLI published to PyPI as the package `ievo`. Users install it via three methods:

| Method | Install command | Update command |
|--------|----------------|---------------|
| `uvx` | `uvx ievo` (ephemeral, no install) | `uv cache clean ievo` then next `uvx` picks latest |
| `uv tool` | `uv tool install ievo` | `uv tool upgrade ievo` |
| `pip` | `pip install ievo` | `pip install --upgrade ievo` |

Currently `ievo update` is a deprecated CLI command that updates installed agents and redirects users to "the HR agent". Per CR-001, that agent-update behavior is being removed and `ievo update` is repurposed to self-update the CLI tool itself.

**Current version is read from** `src/ievo/version.py` via `ievo.__version__`. The version string is also accessible programmatically from the installed package metadata:
```python
from importlib.metadata import version
version("ievo")  # e.g. "0.1.0"
```

**Latest PyPI version** is fetched from the PyPI JSON API — no authentication required:
```
GET https://pypi.org/pypi/ievo/json
Response: {"info": {"version": "0.1.2"}, ...}
```

**Installation method detection** — detect how ievo was installed by inspecting the running process and environment. The canonical detection approach (in priority order):

1. Check if the running script path contains `.cache/uv/` or `uvx` — indicates `uvx` ephemeral run
2. Check if the running script path is under `~/.local/share/uv/tools/ievo/` (uv tool bin dir) — indicates `uv tool install`
3. Fall back to `pip` — covers pip, pipx, system Python, or any other installer

**Update commands to execute** (using `subprocess.run`):
- `uvx`: advise user to clear the uv cache — `uv cache clean ievo`. Update happens automatically on next `uvx ievo` call. No sub-process needed; just print the instruction.
- `uv tool`: `uv tool upgrade ievo`
- `pip`: `pip install --upgrade ievo`

**Deprecation pattern (per D-009):** the current `update()` function in `src/ievo/commands/update.py` calls `deprecated_command()` at the top. After this REQ is implemented, `update()` no longer calls `deprecated_command()` — it is a first-class, non-deprecated command that self-updates. The `[dim][DEPRECATED][/dim]` label in `cli.py`'s help text for the `update` command must also be removed.

**Key files:**
- `src/ievo/commands/update.py` — replace the agent-update body with self-update logic
- `src/ievo/cli.py` — remove deprecated label from the `update` command registration
- `tests/test_update.py` — existing test file to be replaced with new self-update tests

**Console helpers** (from `ievo.utils.console`):
- `step(msg)` — progress step (cyan)
- `success(msg)` — green success
- `warn(msg)` — yellow warning
- `error(msg)` — red error
- `info(msg)` — dim info

## User-facing behavior

```
$ ievo update
Checking for updates...
Current version: 0.1.0
Latest version:  0.1.2

Detected installer: uv tool
Upgrading via: uv tool upgrade ievo
...
ievo updated to 0.1.2.
```

If already up to date:
```
$ ievo update
Checking for updates...
ievo 0.1.0 is already up to date.
```

If uvx (ephemeral):
```
$ ievo update
ievo is running via uvx (ephemeral). The next time you run uvx ievo,
it will use the latest version automatically.
To force a refresh now, run: uv cache clean ievo
```

## Acceptance Criteria

- `ievo update` when current version equals PyPI latest → prints "already up to date" message containing the current version, exits with code 0, does NOT run any subprocess
- `ievo update` when PyPI has a newer version and installer is `uv tool` → runs `uv tool upgrade ievo` as a subprocess, then prints success message containing the new version
- `ievo update` when PyPI has a newer version and installer is `pip` → runs `pip install --upgrade ievo` as a subprocess, then prints success message
- `ievo update` when installer is `uvx` (ephemeral) → prints an informational message explaining that uvx uses the latest automatically, prints the `uv cache clean ievo` hint, exits with code 0, does NOT run any upgrade subprocess
- `ievo update` when PyPI is unreachable (network error) → prints an error message stating that the version check failed, exits with code 1
- `ievo --help` → `update` command help text does NOT contain `[DEPRECATED]`

## Negative Acceptance Criteria

- Does NOT update installed agents from the marketplace (that was the old behavior — removed per CR-001)
- Does NOT call `deprecated_command()` — `ievo update` is now a first-class command
- Does NOT require a project directory (`.ievo/manifest.yaml` not required — self-update works anywhere)
- Does NOT require Docker or the sandbox container
- Does NOT support `ievo update <agent-name>` with an argument — the command takes no arguments

## Technical Notes

Files to modify:
- `src/ievo/commands/update.py` — replace body entirely
- `src/ievo/cli.py` — remove `[dim][DEPRECATED][/dim]` from update command help string
- `tests/test_update.py` — replace with self-update tests

New helper module (optional, if complexity warrants): `src/ievo/core/updater.py` — version check + installer detection logic (callable from tests without Typer).

The PyPI fetch should use `httpx` (already in dependencies) for consistency with registry fetches. Use `httpx.get("https://pypi.org/pypi/ievo/json", timeout=10)`.

Installer detection reads `sys.argv[0]` or `__file__` of the entry point at runtime.
