Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
11f6979
feat: add LauncherConfig dataclass and arg parsing for explorer launcher
Ovid Mar 22, 2026
629d110
feat: add command existence and Python version preflight checks
Ovid Mar 22, 2026
a7c11f1
feat: add env var, Docker daemon, and sandbox image preflight checks
Ovid Mar 22, 2026
fb5d17e
feat: add run_preflight orchestrator collecting all check errors
Ovid Mar 22, 2026
9e7280f
feat: add frontend build logic with shared UI and --rebuild support
Ovid Mar 22, 2026
839f9af
feat: add launch() function tying together preflight, build, and exec
Ovid Mar 22, 2026
d8c38c9
feat: add per-explorer launch.py config files
Ovid Mar 22, 2026
6da2ca0
refactor: rewrite explorer shell scripts as thin venv-bootstrap shims
Ovid Mar 22, 2026
90bab65
remove: delete scripts/common.sh and stale examples/arxiv-explorer.sh
Ovid Mar 22, 2026
b2d2049
docs: update README Document Explorer section and changelog for launc…
Ovid Mar 22, 2026
03b93a9
chore: lint and format fixes for launcher module
Ovid Mar 22, 2026
25bfeba
Remove completed TODO items
Ovid Mar 22, 2026
2a8e57f
Add Python launcher design and implementation docs
Ovid Mar 22, 2026
c928c31
remove: delete stale tests/scripts/test_common.bats referencing delet…
Ovid Mar 22, 2026
dcf541e
fix: update arxiv-explorer README to reference current launcher path
Ovid Mar 22, 2026
1e1f9f5
fix: add missing shutil.which mocks to Docker-related launcher tests
Ovid Mar 22, 2026
2911196
fix: catch CalledProcessError from build_frontend in launch() for cle…
Ovid Mar 22, 2026
d3ed0cb
fix: document sandbox image default dependency on AnantaConfig.sandbo…
Ovid Mar 22, 2026
9a5e6a7
PAAD agentic review
Ovid Mar 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Changed

- Explorer launcher scripts rewritten: bash logic moved to testable Python (`src/ananta/explorers/launcher.py`), shell scripts reduced to venv-bootstrap shims

### Removed

- `scripts/common.sh` — replaced by Python launcher module
- `examples/arxiv-explorer.sh` — stale launcher pointing at old paths

## [0.24.0] - 2026-03-22

### Changed
Expand Down
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,16 +314,16 @@ The [arXiv Explorer](arxiv-explorer/) is a web-based research tool that lets you

_The screenshot above shows Ananta searching through nearly 25 MB of research papers to answer a complex question._

## Document Explorer (Experimental)
## Document Explorer

A web-based interface for uploading documents, organizing them into topics, and querying them with Ananta. Upload PDFs, Word documents, PowerPoint, Excel, RTF, or plain text files, group them by topic, then ask questions across your collection.

```bash
# Launch the Document Explorer
python -m ananta.experimental.document_explorer
./document-explorer/document-explorer.sh

# Options
python -m ananta.experimental.document_explorer --port 8003 --open --model gpt-4o
./document-explorer/document-explorer.sh --port 8003 --open --model gpt-4o
```

The explorer provides:
Expand All @@ -332,8 +332,6 @@ The explorer provides:
- **Live query streaming** via WebSocket — watch Ananta think in real time
- **Conversation history** per topic for follow-up questions

> **Note:** This is experimental and under active development.

## DeepWiki

We love DeepWiki — it's an amazing tool that covers much of the same ground. But there are reasons you might prefer Ananta:
Expand Down
3 changes: 0 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# Explorers

- rename "experimental" directories to "explorers"
- Also, need to write the shell scripts in Python.
- put all explorers in a single directory (scripts)
- Node.js is v23.3.0 (EOL odd-number release). Homebrew has 25.8.1.
Run `brew upgrade node` and update `.nvmrc` to match.
Expand Down
2 changes: 1 addition & 1 deletion arxiv-explorer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Ananta lets you ask plain-English questions across dozens of arXiv papers at onc

## Setup

If you're comfortable with software development, you can run `./examples/arxiv-explorer.sh` to launch this quickly. It's not well tested (it still requires Docker because the Python code runs in a locked-down Docker container). Otherwise, you `cd arxiv-explorer; docker compose up`, after ensuring the prerequisites are satisfied. Note, the docker and shell script solutions share different data storage, so using one means it won't see what you put in the other.
If you're comfortable with software development, you can run `./arxiv-explorer/arxiv-explorer.sh` to launch this quickly. It still requires Docker because the Python code runs in a locked-down Docker container. Otherwise, you `cd arxiv-explorer; docker compose up`, after ensuring the prerequisites are satisfied. Note, the docker and shell script solutions share different data storage, so using one means it won't see what you put in the other.

### Installing Docker

Expand Down
27 changes: 19 additions & 8 deletions arxiv-explorer/arxiv-explorer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,30 @@
# ./arxiv-explorer/arxiv-explorer.sh # defaults
# ./arxiv-explorer/arxiv-explorer.sh --model gpt-5-mini # pass args to ananta-web
# ./arxiv-explorer/arxiv-explorer.sh --port 8080 # custom port
# ./arxiv-explorer/arxiv-explorer.sh --open # open browser on startup
# ./arxiv-explorer/arxiv-explorer.sh --open # open browser on startup
# ./arxiv-explorer/arxiv-explorer.sh --rebuild # force frontend rebuild

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

APP_NAME="Ananta arXiv Web Explorer"
APP_SLUG="ananta-web"
VENV_DIR="$PROJECT_ROOT/.venv"
PIP_EXTRA="web"
ENTRY_POINT="ananta-web"
FRONTEND_DIR="$PROJECT_ROOT/src/ananta/explorers/arxiv/frontend"
APP_SLUG="ananta-web"

if [ ! -d "$VENV_DIR" ]; then
echo "[ananta] Creating virtual environment..."
python3 -m venv "$VENV_DIR"
fi

# shellcheck source=/dev/null
source "$VENV_DIR/bin/activate"

MARKER="$VENV_DIR/.${APP_SLUG}-installed"
if [ ! -f "$MARKER" ] || [ "$PROJECT_ROOT/pyproject.toml" -nt "$MARKER" ]; then
echo "[ananta] Installing Python dependencies..."
pip install -q -e "$PROJECT_ROOT[$PIP_EXTRA]"
touch "$MARKER"
fi

source "$PROJECT_ROOT/scripts/common.sh"
launch "$@"
exec python "$SCRIPT_DIR/launch.py" "$@"
15 changes: 15 additions & 0 deletions arxiv-explorer/launch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env python3
"""Launch the Ananta arXiv Web Explorer."""

import sys

from ananta.explorers.launcher import LauncherConfig, launch

config = LauncherConfig(
app_name="Ananta arXiv Web Explorer",
entry_point="ananta-web",
frontend_dir="src/ananta/explorers/arxiv/frontend",
)

if __name__ == "__main__":
sys.exit(launch(config))
29 changes: 19 additions & 10 deletions code-explorer/code-explorer.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,30 @@
# ./code-explorer/code-explorer.sh # defaults
# ./code-explorer/code-explorer.sh --model gpt-5-mini # pass args to ananta-code
# ./code-explorer/code-explorer.sh --port 9000 # custom port
# ./code-explorer/code-explorer.sh --open # open browser on startup
# ./code-explorer/code-explorer.sh --open # open browser on startup
# ./code-explorer/code-explorer.sh --rebuild # force frontend rebuild

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"

APP_NAME="Ananta Code Explorer"
APP_SLUG="ananta-code"
VENV_DIR="$PROJECT_ROOT/.venv"
PIP_EXTRA="web"
ENTRY_POINT="ananta-code"
REQUIRES_GIT=true
FRONTEND_DIR="$PROJECT_ROOT/src/ananta/explorers/code/frontend"
SHARED_FRONTEND_DIR="$PROJECT_ROOT/src/ananta/explorers/shared_ui/frontend"
APP_SLUG="ananta-code"

if [ ! -d "$VENV_DIR" ]; then
echo "[ananta] Creating virtual environment..."
python3 -m venv "$VENV_DIR"
fi

# shellcheck source=/dev/null
source "$VENV_DIR/bin/activate"

MARKER="$VENV_DIR/.${APP_SLUG}-installed"
if [ ! -f "$MARKER" ] || [ "$PROJECT_ROOT/pyproject.toml" -nt "$MARKER" ]; then
echo "[ananta] Installing Python dependencies..."
pip install -q -e "$PROJECT_ROOT[$PIP_EXTRA]"
touch "$MARKER"
fi

source "$PROJECT_ROOT/scripts/common.sh"
launch "$@"
exec python "$SCRIPT_DIR/launch.py" "$@"
17 changes: 17 additions & 0 deletions code-explorer/launch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env python3
"""Launch the Ananta Code Explorer."""

import sys

from ananta.explorers.launcher import LauncherConfig, launch

config = LauncherConfig(
app_name="Ananta Code Explorer",
entry_point="ananta-code",
frontend_dir="src/ananta/explorers/code/frontend",
requires_git=True,
shared_frontend_dir="src/ananta/explorers/shared_ui/frontend",
)

if __name__ == "__main__":
sys.exit(launch(config))
153 changes: 153 additions & 0 deletions docs/plans/2026-03-22-python-launcher-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Python Launcher Scripts Design

**Date:** 2026-03-22
**Status:** Approved (post-pushback)

## Motivation

The three explorer launcher scripts (`arxiv-explorer.sh`, `code-explorer.sh`,
`document-explorer.sh`) and their shared logic (`scripts/common.sh`) are bash.
This causes three problems:

1. **Testability** — the launcher logic (preflight checks, build steps) can't be unit tested
2. **Consolidation** — shared logic deserves a proper Python module, not a sourced shell file
3. **Maintainability** — the bash has grown complex enough (staleness markers, shared UI logic, stderr filter) that Python would be clearer

Note: Cross-platform (Windows) support is a future goal but not achieved by this
design alone — the bash shims are still Unix-only. However, the Python logic
(`launch.py` + `launcher.py`) is fully portable, so users who install via pip
can run explorers on Windows without the shims.

## Design

### Architecture

Each explorer keeps a bash shim (`*-explorer.sh`) that handles only the
bootstrapping problem: ensuring a venv exists and dependencies are installed.
All real logic moves to Python.

```
*-explorer/*-explorer.sh (bash shim: venv + pip install)
└── *-explorer/launch.py (thin config + call shared launcher)
└── src/ananta/explorers/launcher.py (all shared logic)
```

### Bash Shim (~20 lines each)

The shim's only responsibilities:
- Determine `PROJECT_ROOT` and `VENV_DIR`
- Create venv if missing
- Activate venv
- Install pip dependencies if stale (marker file vs pyproject.toml mtime)
- `exec python launch.py "$@"`

Each shim differs only in `PIP_EXTRA` and `APP_SLUG` variables.

Example (arxiv):

```bash
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
VENV_DIR="$PROJECT_ROOT/.venv"
PIP_EXTRA="web"
APP_SLUG="ananta-web"

if [ ! -d "$VENV_DIR" ]; then
echo "[ananta] Creating virtual environment..."
python3 -m venv "$VENV_DIR"
fi

# shellcheck source=/dev/null
source "$VENV_DIR/bin/activate"

MARKER="$VENV_DIR/.${APP_SLUG}-installed"
if [ ! -f "$MARKER" ] || [ "$PROJECT_ROOT/pyproject.toml" -nt "$MARKER" ]; then
echo "[ananta] Installing Python dependencies..."
pip install -q -e "$PROJECT_ROOT[$PIP_EXTRA]"
touch "$MARKER"
fi

exec python "$SCRIPT_DIR/launch.py" "$@"
```

### Python `launch.py` (thin, per-explorer)

Each explorer gets a `launch.py` that defines config and calls the shared launcher:

```python
#!/usr/bin/env python3
"""Launch the Ananta arXiv Web Explorer."""

from ananta.explorers.launcher import LauncherConfig, launch

config = LauncherConfig(
app_name="Ananta arXiv Web Explorer",
entry_point="ananta-web",
frontend_dir="src/ananta/explorers/arxiv/frontend",
requires_git=False,
shared_frontend_dir=None,
)

if __name__ == "__main__":
launch(config)
```

### Shared Launcher (`src/ananta/explorers/launcher.py`)

Responsibilities, in order:

1. **Parse args** — strip `--rebuild`, pass the rest through to the entry point
2. **Preflight checks** — collect all errors, report at once:
- Python version >= 3.11
- `node`, `npm`, `docker` on PATH
- `git` on PATH (if `requires_git`)
- `ANANTA_API_KEY` and `ANANTA_MODEL` env vars set
- Docker daemon running
- Sandbox image exists — attempt to build if missing, only error if build fails
3. **Build frontend** — `npm install` + `npm run build` for shared UI (if configured) and explorer frontend, skipped if `dist/` exists unless `--rebuild`
4. **Launch** — `subprocess.run()` + `sys.exit()` the entry point with remaining args (not `os.execvp()`, for Windows compatibility)

Key design points:
- `LauncherConfig` is a dataclass
- Each preflight check is a small method returning an optional error string (testable)
- No stderr filter (dropped — fix root causes instead of filtering symptoms)
- `--rebuild` is the only flag consumed by the launcher; all others pass through unchanged

## Files Changed

### Created
- `src/ananta/explorers/launcher.py` — shared launcher logic
- `arxiv-explorer/launch.py` — arxiv config
- `code-explorer/launch.py` — code config
- `document-explorer/launch.py` — document config

### Rewritten
- `arxiv-explorer/arxiv-explorer.sh` — bash shim
- `code-explorer/code-explorer.sh` — bash shim
- `document-explorer/document-explorer.sh` — bash shim

### Deleted
- `scripts/common.sh` — replaced by `launcher.py`
- `examples/arxiv-explorer.sh` — stale, points at old paths

### Updated
- `README.md` — update Document Explorer section: drop "(Experimental)" label, replace `python -m` invocation with launcher script reference
- `CHANGELOG.md` — entry under Unreleased/Changed

## Decisions

| Decision | Choice | Reason |
|----------|--------|--------|
| Invocation style | Same paths, `.sh` preserved | Preserves existing UX |
| Shared logic location | `src/ananta/explorers/launcher.py` | Importable, testable, scoped to explorers |
| Venv bootstrap | Stays in bash shim | Avoids chicken-and-egg (launcher can't import before pip install) |
| Pip install | Stays in bash shim | Same bootstrap reason |
| Stderr filter | Dropped | Fix root causes, not symptoms |
| Frontend build | Python (`subprocess.run`) | Maximizes testability |
| `--rebuild` handling | Python (only flag consumed by launcher) | Same reason |
| Process launch | `subprocess.run()` + `sys.exit()` | `os.execvp()` is Unix-only; Python logic should be portable |
| Sandbox image build | Auto-build during preflight, error only on failure | Matches current behavior; good UX |
| Per-explorer configs | Separate `launch.py` files (not a registry) | Simpler now; trivial to consolidate into a dict later for unified launcher |
| Cross-platform | Bash shims Unix-only; Python logic portable | Windows users can `pip install` and run `launch.py` directly |
Loading
Loading