Metadata-Version: 2.4
Name: contextr
Version: 2.0.0
Summary: Share your codebase with LLMs - codebase export tool for AI conversations
Author-email: Nathan Luo <nathanluo13@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/nathan-luo/contextr
Project-URL: Bug Tracker, https://github.com/nathan-luo/contextr/issues
Keywords: llm,ai,codebase,clipboard,context
Requires-Python: >=3.12
Description-Content-Type: text/markdown
Requires-Dist: prompt-toolkit>=3.0.48
Requires-Dist: pyperclip>=1.9.0
Requires-Dist: rich>=13.9.4
Requires-Dist: typer>=0.15.1
Provides-Extra: dev
Requires-Dist: ruff>=0.8.6; extra == "dev"
Requires-Dist: pyright>=1.1.389; extra == "dev"
Requires-Dist: pytest>=8.3.4; extra == "dev"
Requires-Dist: pytest-cov>=6.0.0; extra == "dev"
Requires-Dist: pytest-mock>=3.14.0; extra == "dev"
Requires-Dist: pre-commit>=4.0.1; extra == "dev"

# contextr (ctxr)

> **"git add for AI conversations."** Select the files you want an LLM to "see," format them beautifully, and get them onto your clipboard—or into a file—in one command.

`contextr` is a command-line tool for preparing and sharing **code context** with Large Language Models (LLMs) such as ChatGPT, Claude, Gemini, etc. It watches the files you care about, respects git-style ignore rules, and exports a clean, LLM-friendly Markdown bundle with a file tree and (optionally) file contents.

## Why contextr?

When working with LLMs on coding tasks, you often need to share relevant parts of your codebase. Manually copying files is tedious and error-prone. `contextr` solves this by letting you:

- **Curate once, export anytime** — Define what files matter, then get fresh snapshots instantly
- **Stay focused** — Only share what's relevant, not your entire repo
- **Keep secrets safe** — Ignore sensitive files with familiar gitignore patterns
- **Work naturally** — Use glob patterns you already know from git

## Features

- **Focused Context** — Watch only what matters using familiar glob patterns
- **Git-style Ignores** — Last-match-wins semantics, `!` negations, `**` globs, bare directory names
- **One-shot Export** — Copy straight to clipboard or write to a file
- **Smart Formatting** — Auto-detected language hints, dynamic fence handling, file tree visualization
- **Cross-platform** — Works on macOS, Linux, and Windows
- **Typed & Tested** — Strict typing with Pyright

---

## Table of Contents

1. [Installation](#installation)
2. [Quick Start](#quick-start)
3. [CLI Reference](#cli-reference)
4. [Workflows & Examples](#workflows--examples)
5. [Output Format](#output-format)
6. [Ignore Semantics (Git-style)](#ignore-semantics-git-style)
7. [Python API](#python-api)
8. [Configuration & State](#configuration--state)
9. [Troubleshooting](#troubleshooting)
10. [Security & Privacy](#security--privacy)
11. [Development](#development)
12. [FAQ](#faq)
13. [License](#license)

---

## Installation

> Requires **Python 3.12+**

### Option A — PyPI (recommended)

```bash
pip install contextr
```

This installs both commands: `ctxr` (short) and `contextr` (full).

### Option B — pipx (isolated environment)

```bash
pipx install contextr
```

### Option C — From source

```bash
git clone https://github.com/nathan-luo/contextr.git
cd contextr

# For development (uses uv)
uv sync --extra dev

# Or editable install with pip
pip install -e .
```

> **Note (Linux):** Clipboard features rely on `pyperclip`. On some systems you may need a clipboard helper such as `xclip` or `xsel`:
> ```bash
> sudo apt install xclip  # Debian/Ubuntu
> ```

---

## Quick Start

```bash
# 1. Initialize (once per repo)
ctxr init

# 2. Watch files you care about
ctxr watch "src/**/*.py" "pyproject.toml" "README.md"

# 3. Optionally import your .gitignore rules
ctxr gis

# 4. Export context (copies to clipboard)
ctxr

# 5. Paste into your LLM conversation!
```

That's it! Your watched files are now formatted as Markdown and ready to paste.

### Quick Command Reference

| Command | Shortcut | Description |
|---------|----------|-------------|
| `ctxr` | - | Export context to clipboard |
| `ctxr init` | - | Initialize .contextr directory |
| `ctxr watch <patterns>` | - | Add glob patterns to watch |
| `ctxr ignore <patterns>` | - | Add ignore rules |
| `ctxr unignore <patterns>` | - | Remove ignore rules |
| `ctxr gis` | - | Import .gitignore patterns |
| `ctxr watchclear` | `ctxr wc` | Clear watched patterns/files |
| `ctxr ignoreclear` | `ctxr ic` | Clear ignore rules (keeps .gitignore imports) |

---

## CLI Reference

Use `ctxr` (or the full alias `contextr`) for all commands.

### `ctxr` — Export Context (default)

```bash
ctxr [OPTIONS]
```

With no subcommand, `ctxr` refreshes your watched files and exports the context.

| Option | Description |
|--------|-------------|
| `--to-file PATH`, `-o PATH` | Write output to a file instead of clipboard |
| `--no-clipboard` | Don't copy to clipboard (print to stdout) |
| `--absolute` | Use absolute paths in output |
| `--no-contents` | Export only the file tree, no file contents |

**Examples:**

```bash
ctxr                          # Export to clipboard
ctxr -o context.md            # Write to file
ctxr --no-contents            # Just the file tree
ctxr --absolute -o full.md    # Absolute paths to file
```

---

### `ctxr init` — Initialize

```bash
ctxr init
```

Creates the `.contextr/` directory and default `.ignore` file. Run this once per project.

- Creates `.contextr/state.json` for tracking watched patterns
- Creates `.contextr/.ignore` with default `*.lock` rule
- Safe to run multiple times (won't overwrite existing config)

---

### `ctxr watch` — Add Watch Patterns

```bash
ctxr watch <PATTERN...>
```

Add glob patterns to your watch list. Files matching these patterns will be included in exports.

**Examples:**

```bash
# Watch Python source files
ctxr watch "src/**/*.py"

# Watch multiple patterns at once
ctxr watch "src/**/*.py" "tests/**/*.py" "pyproject.toml"

# Watch a specific file
ctxr watch "README.md"

# Watch entire directories
ctxr watch "src" "docs"
```

> **Important:** Always quote glob patterns to prevent shell expansion!

---

### `ctxr ignore` — Add Ignore Rules

```bash
ctxr ignore <PATTERN...>
```

Add patterns to the ignore list. Matching files are immediately removed from context.

**Examples:**

```bash
# Ignore Python cache
ctxr ignore "**/__pycache__/**" "*.pyc"

# Ignore build artifacts
ctxr ignore "build/" "dist/" "*.egg-info"

# Ignore test files
ctxr ignore "**/test_*.py"
```

---

### `ctxr unignore` — Remove Ignore Rules

```bash
ctxr unignore <PATTERN...>
```

Remove specific patterns from the ignore list. Use this to undo previous `ignore` commands.

**Examples:**

```bash
# Stop ignoring log files
ctxr unignore "*.log"

# Remove multiple patterns
ctxr unignore "*.pyc" "**/__pycache__/**"
```

---

### `ctxr gis` — Git Ignore Sync

```bash
ctxr gis
```

Import patterns from your `.gitignore` into `.contextr/.ignore`. This is a one-way sync that adds patterns without removing existing ones.

- Skips patterns already present
- Handles inline comments (`pattern # comment`)
- Run again after updating `.gitignore` to sync new patterns

---

### `ctxr watchclear` / `ctxr wc` — Clear Watched Patterns

```bash
ctxr watchclear
ctxr wc          # shortcut
```

Clear all watched patterns and files from context. **Ignore rules are preserved.**

Use this to start fresh with a new set of files to watch.

---

### `ctxr ignoreclear` / `ctxr ic` — Clear Ignore Rules

```bash
ctxr ignoreclear [OPTIONS]
ctxr ic [OPTIONS]    # shortcut
```

Clear user-defined ignore rules. By default, patterns imported from `.gitignore` are preserved (re-imported after clearing).

| Option | Description |
|--------|-------------|
| `--clear-gis` | Also clear patterns imported from .gitignore |

**Examples:**

```bash
ctxr ic              # Clear user ignores, keep .gitignore patterns
ctxr ic --clear-gis  # Clear everything (except built-in *.lock)
```

> **Note:** The built-in `*.lock` rule is always preserved to prevent accidentally sharing lockfiles.

---

## Workflows & Examples

### Workflow 1: Python Project

```bash
# Setup (once)
ctxr init
ctxr gis                                    # Import .gitignore
ctxr watch "src/**/*.py" "pyproject.toml"   # Watch source + config

# Daily use
ctxr                                        # Export and paste to LLM
```

### Workflow 2: Full-stack Web App

```bash
ctxr init
ctxr gis
ctxr watch "src/**/*.ts" "src/**/*.tsx"     # Frontend
ctxr watch "api/**/*.py"                    # Backend
ctxr watch "*.json" "*.yaml"                # Configs
ctxr ignore "**/node_modules/**" "**/.next/**"
```

### Workflow 3: Documentation Focus

```bash
ctxr init
ctxr watch "docs/**/*.md" "README.md" "CHANGELOG.md"
ctxr --no-contents -o tree.md               # Just structure
ctxr -o full-docs.md                        # Full content
```

### Workflow 4: Debugging a Specific Issue

```bash
# Temporarily focus on problem area
ctxr wc                                     # Clear current watch
ctxr watch "src/auth/**/*.py" "tests/test_auth.py"
ctxr                                        # Share focused context

# Later, restore broader context
ctxr wc
ctxr watch "src/**/*.py"
```

### Workflow 5: Sharing with Different LLMs

```bash
# Claude/ChatGPT (clipboard)
ctxr

# Local LLM / file-based workflow
ctxr -o context.md

# Just structure for initial overview
ctxr --no-contents
```

---

## Output Format

Running `ctxr` produces a Markdown document optimized for LLMs:

````markdown
# Project Context: your-project
Files selected: 5

## File Structure
```
📁 Context
├── src
│   ├── main.py
│   └── utils
│       └── helpers.py
├── tests
│   └── test_main.py
└── pyproject.toml
```

## File Contents

### src/main.py
```python
def main():
    print("Hello, World!")
```

### src/utils/helpers.py
```python
def helper():
    pass
```
````

### Format Features

- **Smart Language Detection** — File extensions map to syntax highlighting hints
- **Dynamic Fences** — Automatically uses longer fence sequences if your code contains backticks
- **Truncation** — Large files are truncated with `[... truncated ...]` marker
- **Clean Tree** — Visual hierarchy with folder/file icons

---

## Ignore Semantics (Git-style)

`contextr` implements **git-style** ignore semantics with ordered rules and "**last match wins**":

| Pattern | Meaning |
|---------|---------|
| `*.log` | Ignore all `.log` files anywhere |
| `build/` | Ignore `build` directory |
| `build` | Same as above (bare name = directory) |
| `/build` | Only ignore `build` at repo root |
| `**/test_*.py` | Ignore test files anywhere |
| `!important.log` | UN-ignore `important.log` (negation) |
| `a/**/b` | Match `a/b`, `a/x/b`, `a/x/y/b`, etc. |

### Platform Behavior

- **macOS/Windows:** Case-insensitive matching
- **Linux:** Case-sensitive matching

### Example `.contextr/.ignore`

```gitignore
# Build artifacts
build/
dist/
*.egg-info

# Cache
**/__pycache__/**
*.pyc
.pytest_cache/

# IDE
.idea/
.vscode/

# But keep VS Code settings
!.vscode/settings.json

# Environment
.env
.env.*
!.env.example

# Always ignored (built-in)
*.lock
```

---

## Python API

Use `contextr` programmatically in your own tools:

```python
from pathlib import Path
from contextr import ContextManager, ProfileManager, format_export_content

# Initialize
cm = ContextManager()

# Watch and ignore
cm.watch_paths(["src/**/*.py", "*.md"])
cm.add_ignore_patterns(["**/__pycache__/**", "*.pyc"])

# Refresh and export
cm.refresh_watched()
output = format_export_content(
    files=cm.files,
    base_dir=cm.base_dir,
    include_contents=True,    # False for tree only
    absolute_paths=False,     # True for absolute paths
    max_bytes=512_000,        # Per-file size limit
)

# Save to file
Path("context.md").write_text(output, encoding="utf-8")

# Or work with files directly
print(f"Watching {len(cm.watched_patterns)} patterns")
print(f"Context has {len(cm.files)} files")
for f in cm.get_file_paths(relative=True):
    print(f"  - {f}")
```

### Using Profiles (Advanced)

```python
from contextr import ContextManager, ProfileManager

cm = ContextManager()
pm = ProfileManager(cm.storage, cm.base_dir)

# Save current watch patterns as a profile
cm.watch_paths(["src/**/*.py"])
pm.save_profile("python-only",
    watched_patterns=list(cm.watched_patterns),
    description="Just Python source files"
)

# Later, load it back
profile = pm.load_profile("python-only")
cm.apply_profile(profile, "python-only")
```

---

## Configuration & State

All state is stored in the `.contextr/` directory:

```
.contextr/
├── state.json      # Current watched patterns and file list
├── .ignore         # Ignore rules (git-style)
└── profiles/       # Saved profiles (if using Python API)
    └── my-profile.json
```

### `state.json` Structure

```json
{
    "current_profile": null,
    "files": [
        "src/main.py",
        "src/utils.py"
    ],
    "watched_patterns": [
        "src/**/*.py"
    ]
}
```

### Adding to `.gitignore`

You may want to add `.contextr/` to your `.gitignore` if you don't want to share your context configuration with collaborators:

```gitignore
# contextr state (optional)
.contextr/
```

Or commit it to share your team's default context setup.

---

## Troubleshooting

### Clipboard doesn't work

**Linux:** Install a clipboard helper:
```bash
sudo apt install xclip    # Debian/Ubuntu
sudo dnf install xclip    # Fedora
sudo pacman -S xclip      # Arch
```

**Workaround:** Use `--to-file` or `--no-clipboard`:
```bash
ctxr -o context.md
ctxr --no-clipboard  # Prints to stdout
```

### Patterns not matching

**Always quote glob patterns** to prevent shell expansion:
```bash
# Correct
ctxr watch "src/**/*.py"

# Wrong - shell expands before ctxr sees it
ctxr watch src/**/*.py
```

### Files still appearing after ignore

Remember: **last match wins**. Check your `.contextr/.ignore` for conflicting rules:

```gitignore
*.log           # Ignores all .log files
!debug.log      # But this un-ignores debug.log
```

### Export is too large

1. Use more specific watch patterns
2. Add ignore rules for generated/vendor code
3. Use `--no-contents` for just the tree
4. Large files are auto-truncated at 512KB

### State seems corrupted

Reset and start fresh:
```bash
rm -rf .contextr
ctxr init
ctxr gis
ctxr watch "your/patterns/**"
```

---

## Security & Privacy

`contextr` is designed with privacy in mind:

- **100% Local** — No network calls, no telemetry, no cloud
- **You Control What's Shared** — Only watched, non-ignored files are exported
- **No Secrets by Default** — `*.lock` files are always ignored
- **Transparent State** — All config is in readable JSON/text files

### Best Practices

1. Run `ctxr gis` to import `.gitignore` rules (which typically exclude secrets)
2. Add sensitive patterns to ignore: `ctxr ignore ".env" "*.pem" "**/secrets/**"`
3. Review the file tree before sharing: `ctxr --no-contents`
4. Don't commit `.contextr/` if it might contain sensitive file paths

---

## Development

### Setup

```bash
git clone https://github.com/nathan-luo/contextr.git
cd contextr
uv sync --extra dev
uv run pre-commit install
```

### Commands

```bash
# Lint & format
uv run ruff check .
uv run ruff format .

# Type check
uv run pyright

# Run tests
uv run pytest

# Run all checks
uv run pre-commit run --all-files
```

### Project Structure

```
src/contextr/
├── __init__.py         # Public API exports
├── cli.py              # Typer CLI commands
├── manager.py          # Core ContextManager class
├── profile.py          # Profile management
├── formatters.py       # Markdown output formatting
├── storage/            # Storage abstraction
│   ├── base.py         # StorageBackend ABC
│   └── json_storage.py # JSON file implementation
└── utils/
    ├── ignore_utils.py # Git-style ignore matching
    └── path_utils.py   # Cross-platform path handling
```

---

## FAQ

**Q: Can I export only the file tree?**

Yes: `ctxr --no-contents`

**Q: How do I share context via file instead of clipboard?**

Use `ctxr -o context.md` or `ctxr --to-file context.md`

**Q: Can I watch directories recursively?**

Yes, use `**` glob: `ctxr watch "src/**/*.py"` or just `ctxr watch "src"` for all files.

**Q: How do I undo an ignore rule?**

Use `ctxr unignore "pattern"` to remove it from the ignore list.

**Q: Will long files break the Markdown output?**

No — files over 512KB are truncated, and fence sequences are dynamically chosen to avoid conflicts with backticks in your code.

**Q: How do I start over completely?**

```bash
rm -rf .contextr && ctxr init
```

**Q: Can multiple people share the same context config?**

Yes, commit `.contextr/` to your repo. Everyone will get the same watch/ignore patterns.

**Q: Does this work with monorepos?**

Yes! Use specific patterns: `ctxr watch "packages/my-pkg/src/**"`

---

## License

MIT — see [LICENSE](./LICENSE) for details.

---

## Contributing

Contributions are welcome! Please:

1. Fork the repository
2. Create a feature branch
3. Run `uv run pre-commit run --all-files` before committing
4. Open a pull request

For bugs and feature requests, please [open an issue](https://github.com/nathan-luo/contextr/issues).
