# jlabx

Zero-dependency Python CLI that launches JupyterLab with configurable extensions. Works with both uv and pixi projects.

## Problem

You want JupyterLab with specific extensions (vim, collaboration, LSP, etc.) in a repo that already has a `pyproject.toml` or `pixi.toml` — but you can't add jupyterlab to the project's dependencies. jlabx solves this by managing extensions separately, auto-detecting your environment (pixi or uv), and injecting them at launch time.

## Project structure

```
src/jlabx/__init__.py   # Entire application — single module, no dependencies
src/jlabx/_version.py   # Auto-generated by hatch-vcs (gitignored)
pyproject.toml           # Package config (hatchling + hatch-vcs, entry point: jlabx:main)
docs/                    # Starlight (Astro) docs site
.github/workflows/       # docs.yml (GitHub Pages on release), publish.yml (PyPI on release)
```

## Key design decisions

- **Zero dependencies**: Only uses Python stdlib. TOML config is parsed manually to avoid requiring `tomllib` (3.11+) or `tomli`.
- **Single file**: Everything lives in `src/jlabx/__init__.py`. Keep it that way unless complexity genuinely demands splitting.
- **Four launch modes**: Pixi project (ephemeral venv on top of pixi env), Python project (`uv run --with`), standalone (`uvx --from`), notebook (`uvx juv run --with`). Detection is based on file presence in cwd or `.ipynb` arg.
- **Ephemeral only**: All modes MUST inject extensions ephemerally. Never modify `pixi.toml`, `pyproject.toml`, or install packages into the user's project environment. Pixi mode uses a temporary venv with `--system-site-packages` layered on the pixi env; the temp dir is cleaned up on exit.
- **Notebook mode**: Delegates to [juv](https://github.com/manzt/juv) for per-notebook dependency management. Auto-detects imports if no PEP 723 metadata. `--init-deps` persists detected deps via juv.
- **Fuzzy notebook matching**: If a `.ipynb` path doesn't exist, suggests similarly-named notebooks via `difflib.get_close_matches`. Also corrects common extension typos (`.ipnyb`, `.ipnb`, etc.).
- **Config location**: `$XDG_CONFIG_HOME/jlabx/config.toml` (defaults to `~/.config/jlabx/config.toml`).
- **Requires uv**: Uses `uv run` / `uvx` to launch JupyterLab. Since jlabx is installed via `uv tool install`, uv is always available.
- **Versioning**: Tag-based via `hatch-vcs`. Version comes from git tags (e.g., `git tag 0.3.0`). Never hardcode versions.

## Development

```bash
# Install in editable mode as a tool
uv tool install -e .

# Run directly
jlabx --help
jlabx --version
```

## Code quality

```bash
uvx ruff check src/        # Lint
uvx ruff format src/        # Format
uvx mypy src/jlabx/__init__.py  # Type check
```

Pre-commit hooks are configured in `.pre-commit-config.yaml` (ruff + mypy).

## Testing

No test suite yet. Manual testing:

```bash
# In a directory with pyproject.toml
jlabx              # Should detect Python project, use uv run --with
jlabx --no-extras  # Should skip user extensions
jlabx --uv         # Should force uv even in a pixi project
jlabx --python 3.11  # Should pass --python to uv (uv/standalone modes)
jlabx list         # Should show core + user extensions
jlabx add foo      # Should add to config
jlabx remove foo   # Should remove from config

# Notebook mode
jlabx notebook.ipynb             # Launch existing notebook via juv
jlabx new-notebook.ipynb         # Create new notebook and launch
jlabx notebook.ipynb --init-deps # Persist detected imports as PEP 723 metadata
```

## Docs

The docs site uses Starlight (Astro) in `docs/`. To develop locally:

```bash
cd docs && npm install && npm run dev
```

Deployed to GitHub Pages on release (not on every push), via `.github/workflows/docs.yml`.

## Releasing

1. Tag the commit: `git tag 0.X.0 && git push --tags`
2. Create a GitHub release from the tag
3. Both workflows trigger automatically: PyPI publish + docs deploy
