Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
37 changes: 37 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: CI

on:
push:
branches:
- master
- main
pull_request:

jobs:
package-smoke:
runs-on: ubuntu-latest
env:
RECODE_HOME: ${{ runner.temp }}/recode-home
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Set up uv
uses: astral-sh/setup-uv@v3

- name: Sync dependencies
run: uv sync --extra dev

- name: Smoke test CLI
run: |
uv run python -m recode --version
uv run python -m recode --paths
uv run python -m recode --doctor

- name: Build distributions
run: uv build
32 changes: 32 additions & 0 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Publish to PyPI

on:
push:
tags:
- "v*"

permissions:
contents: read
id-token: write

jobs:
publish:
runs-on: ubuntu-latest
environment: pypi
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Set up uv
uses: astral-sh/setup-uv@v3

- name: Build package
run: uv build

- name: Publish to PyPI (trusted publishing)
uses: pypa/gh-action-pypi-publish@release/v1
34 changes: 34 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Release

on:
push:
tags:
- "v*"

permissions:
contents: write

jobs:
build-and-release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Set up uv
uses: astral-sh/setup-uv@v3

- name: Build package
run: uv build

- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
dist/*
generate_release_notes: true
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@ __pycache__/
*.pyc
.DS_Store
.python-version
uv.lock
.tmp_*
.pytest_cache/
build/
dist/
*.egg-info/
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Ever

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
4 changes: 4 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
include LICENSE
include README.md
recursive-include recode/problems *
global-exclude __pycache__ *.py[cod] .DS_Store
95 changes: 67 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ A terminal-based spaced repetition tool for practicing any code from memory. Wri
- **OpenCode chat modal** — conversational help in-context while you study a problem
- **Agent-like chat tools** — presets (`/nudge`, `/test-me`), TODO capture, diff split view, and `/health`
- **158 themes** — full terminal.sexy palette, switchable live from the command palette
- **Extensible** — add any problem by dropping a `.py` file into `problems/`
- **Extensible** — add any problem by dropping a `.py`, `.jl`, or `.R` file into your writable `problems_dir`

---

Expand All @@ -36,36 +36,64 @@ A terminal-based spaced repetition tool for practicing any code from memory. Wri
## Requirements

- Python 3.10+
- [`uv`](https://github.com/astral-sh/uv) (recommended) or `pip`
- [`uv`](https://github.com/astral-sh/uv) (recommended), `pipx`, or `pip`
- A Gemini or OpenRouter API key
- [OpenCode CLI](https://opencode.ai/) on your PATH for in-app chat (auto-started by Recode)

---

## Setup
## Install

**1. Clone the repo**
### Homebrew

```bash
git clone https://github.com/yourusername/recode.git
brew tap ever-oli/homebrew-tap
brew install ever-oli/homebrew-tap/recode
```

### PyPI

```bash
uv tool install recode-cli
# or
pipx install recode-cli
```

### Local dev

```bash
git clone https://github.com/ever-oli/recode.git
cd recode
uv sync
uv run python -m recode
```

**2. Create a `.env` file** in the project root with your API key:
---

## Configuration

Recode reads environment variables from your shell, a local `.env`, or `~/.config/recode/.env`.
Use `recode --paths` to print the exact runtime directories for your machine.

On first run, Recode seeds its bundled problem set into your writable `problems_dir` so generated and imported problems live beside the defaults instead of inside the installed package.

Example `.env`:

```env
# Option A — Google Gemini (default)
# Option A — Google Gemini
GEMINI_API_KEY=your_gemini_api_key_here
AI_PROVIDER=gemini

# Option B — OpenRouter
OPENROUTER_API_KEY=your_openrouter_api_key_here
AI_PROVIDER=openrouter

# Optional: override the default editor (default: hx / Helix)
# Optional: override the editor (default: hx)
EDITOR=nvim

# Optional: override the problems directory
# Optional: override runtime locations
# PROBLEMS_DIR=/path/to/your/problems
# DB_PATH=/path/to/recode.db

# Optional: OpenCode server URL for in-app chat modal
# OPENCODE_SERVER_URL=http://127.0.0.1:4096
Expand All @@ -74,22 +102,25 @@ EDITOR=nvim
# OPENCODE_AUTOSTART=0
```

**3. Install dependencies and run**
---

## Run

With `uv` (recommended):
Once installed:

```bash
uv run app.py
recode
```

With `pip`:
Useful non-interactive commands:

```bash
pip install -r requirements.txt
python app.py
recode --version
recode --paths
recode --doctor
```

**4. Chat modal behavior (OpenCode)**
### Chat modal behavior (OpenCode)

By default, pressing `c` in a problem will auto-start a local OpenCode server if it is not already running.
The chat modal shows a live status line (`connected`, `auto-started OpenCode`, or `offline`).
Expand All @@ -109,16 +140,30 @@ opencode serve --port 4096

| Key | Action |
|-----|--------|
| `Enter` | Open the selected problem in your editor, then review |
| `c` | Open in-problem chat modal (OpenCode-backed) |
| `Enter` | Open the selected problem |
| `/` | Focus search |
| `c` | Change collection on the main menu |
| `g` | Generate problems from an arXiv paper |
| `i` | Import from Exercism or LeetCode |
| `r` | Refresh the problem list |
| `Ctrl+P` | Open the command palette (theme switcher, etc.) |
| `q` / `Ctrl+C` | Quit |

Inside a problem:

| Key | Action |
|-----|--------|
| `e` | Open the editor |
| `s` | Submit and review |
| `h` | Ask for a hint |
| `f` | Ask for a suggested fix |
| `c` | Open the in-problem chat modal |
| `x` | Explain the solution or gap |
| `q` | Return to the menu |

### Workflow

1. Select a problem from the list and press `Enter`
2. Your editor opens — write the implementation from memory
2. Press `e` to open your editor and write the implementation from memory
3. Save and close the editor
4. Recode shows a side-by-side diff of your attempt vs. the reference
5. Use **Hint** or **Suggest Fix** if you need AI assistance
Expand All @@ -129,8 +174,8 @@ opencode serve --port 4096

## Adding Problems

Problems are plain `.py` files. Drop any `.py` file into the `problems/` folder and it will appear in the list on the next refresh (`r`).
`TensorPoly` is now vendored as a normal folder inside `problems/` (not a submodule), and can be selected with collection switch (`c`) in the menu.
Problems are plain `.py`, `.jl`, or `.R` files. Drop them into the writable `problems_dir` from `recode --paths` and they will appear in the list on the next refresh (`r`).
`TensorPoly` ships as a bundled collection and is copied into your writable problems directory on first run. The built-in importer currently supports Exercism Python and free LeetCode problems.

A problem file contains two things:

Expand All @@ -151,12 +196,6 @@ DESCRIPTION = "Implement the sigmoid function using NumPy."

---

## Themes

Recode ships with 158 themes from [terminal.sexy](https://terminal.sexy). Switch themes live via `Ctrl+P` -> search "theme".

---

## License

MIT
54 changes: 54 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Releasing Recode

## PyPI

1. Create the `recode` project on PyPI if it does not exist yet.
Use the package name `recode-cli`.
2. Add a Trusted Publisher for:
- owner/repo: `ever-oli/recode`
- workflow: `.github/workflows/publish-pypi.yml`
- environment: `pypi`
3. Bump the version in:
- `pyproject.toml`
- `recode/__init__.py`
4. Push a tag like `v0.1.0`.

The tag will trigger:

- `.github/workflows/release.yml` to attach `dist/*` to a GitHub release
- `.github/workflows/publish-pypi.yml` to publish to PyPI

## Homebrew

Update `ever-oli/homebrew-tap/Formula/recode.rb` after the tag is pushed so the formula points at the immutable GitHub source tarball for that release.

Formula template:

```ruby
class Recode < Formula
include Language::Python::Virtualenv

desc "Terminal spaced repetition for coding problems and reference solutions"
homepage "https://github.com/ever-oli/recode"
url "https://github.com/ever-oli/recode/archive/refs/tags/v0.1.0.tar.gz"
sha256 "<fill-from-downloaded-tarball>"
license "MIT"

depends_on "python@3.12"

def install
virtualenv_install_with_resources
end

test do
assert_match "problems_dir=", shell_output("#{bin}/recode --paths")
end
end
```

To compute the SHA locally:

```bash
curl -L -o /tmp/recode-v0.1.0.tar.gz https://github.com/ever-oli/recode/archive/refs/tags/v0.1.0.tar.gz
shasum -a 256 /tmp/recode-v0.1.0.tar.gz
```
Loading