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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Project local history
.code-orbit/history.json
.wepaircode/history.json

# Python
__pycache__/
Expand Down Expand Up @@ -37,8 +37,8 @@ config.yaml.local
.idea/

.DS_Store
.code-orbit
.code-orbit-history
.wepaircode
.wepaircode-history
config.local.yaml

INNER_DOC/
26 changes: 13 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# ◉ CodeOrbit
# ◉ WePairCode

![Python Version](https://img.shields.io/badge/python-3.14-blue)
![Backend](https://img.shields.io/badge/backend-Local--LLM-orange)
![Interface](https://img.shields.io/badge/UI-Rich-green)
![Type](https://img.shields.io/badge/type-CLI-informational)
![License](https://img.shields.io/badge/license-MIT-success)
![Visitors](https://visitor-badge.laobi.icu/badge?page_id=ostefani.code-orbit)
![Visitors](https://visitor-badge.laobi.icu/badge?page_id=ostefani.wepaircode)

**Key Rotation Note:** New signature keys effective Sep 16, 2026. Legacy keys are no longer active on this profile. Commits signed before that date may appear Unverified as a result of the rotation.

Expand All @@ -32,7 +32,7 @@ chat provider ← provider-selected chat adapter powers planning/editing
architect plan ← high-level JSON plan with files, goals, and reasoning
│
▼
review / edit plan ← user can edit .code-orbit/plan.json before approval
review / edit plan ← user can edit .wepaircode/plan.json before approval
│
▼
coder response ← exact file replacements from the approved plan
Expand All @@ -58,8 +58,8 @@ Install from a release or from source using the setup steps below.

```bash
# 1. Clone
git clone https://github.com/ostefani/code-orbit
cd code-orbit
git clone https://github.com/ostefani/wepaircode
cd wepaircode

# 2. Create virtual environment
python3 -m venv .venv
Expand Down Expand Up @@ -119,17 +119,17 @@ Chat lifecycle at a glance:
- `create_chat_adapter()` is the startup convenience path that combines build,
validation, and optional probe.

Set `chat_probe_on_startup: true` if you want Code Orbit to make one live chat
Set `chat_probe_on_startup: true` if you want WePairCode to make one live chat
provider readiness check at startup to verify credentials and reachability.
For OpenAI-compatible providers this is a live `models.list()` request, so
leave it off to keep startup cheap.
Set `embedding_probe_on_startup: true` if you want Code Orbit to make one live
Set `embedding_probe_on_startup: true` if you want WePairCode to make one live
embedding request at startup to verify credentials and reachability. Leave it
off to keep startup cheap and let the first semantic operation hit the backend.

### Using other local providers (Optional)

Code Orbit still works with any OpenAI-compatible local server.
WePairCode still works with any OpenAI-compatible local server.

**Ollama**

Expand Down Expand Up @@ -165,13 +165,13 @@ python main.py --dir . --prompt "" --tree
python main.py --dir . --prompt "..." --config config.local.yaml
```

When you edit the plan, Code Orbit reads `EDITOR` as a command line rather than a shell string, so values like `vim -u NONE` or `code --wait` work. The launcher is currently tuned for Unix-like environments; if Windows support becomes a goal, the editor parsing strategy will need a small portability review because `shlex.split()` uses POSIX rules.
When you edit the plan, WePairCode reads `EDITOR` as a command line rather than a shell string, so values like `vim -u NONE` or `code --wait` work. The launcher is currently tuned for Unix-like environments; if Windows support becomes a goal, the editor parsing strategy will need a small portability review because `shlex.split()` uses POSIX rules.

Tokens are counted using `tokenizer_backend: estimate`. If you want to learn more on how to use tokenizers and improve tokens counting see TOKEN_COUNTING.md.

## Public API

Code Orbit now exposes a small Python API for external runners and other Python consumers.
WePairCode now exposes a small Python API for external runners and other Python consumers.

```python
from pathlib import Path
Expand Down Expand Up @@ -249,7 +249,7 @@ chat_provider_options: {} # provider-specific SDK kwargs

For personal overrides without affecting git, use `config.local.yaml` (already in `.gitignore`).

Code Orbit also stores runtime artifacts under `.code-orbit/`, including the embeddings cache and prompt history. That directory is ignored in this repo by default, so you should not commit those generated files.
WePairCode also stores runtime artifacts under `.wepaircode/`, including the embeddings cache and prompt history. That directory is ignored in this repo by default, so you should not commit those generated files.

## Testing

Expand All @@ -262,7 +262,7 @@ python3 main.py

## Recommended models

These models are optimized for the agentic workflows, long context, and structured JSON output required by Code Orbit.
These models are optimized for the agentic workflows, long context, and structured JSON output required by WePairCode.

| Model | Size | Context | Notes |
| :---------------- | :-------------- | :------ | :---------------------------------------------------------------------------------------- |
Expand Down Expand Up @@ -298,7 +298,7 @@ llama-server \
## Project structure

```
code-orbit/
wepaircode/
├── main.py # CLI wrapper, history, prompt handling
├── api/ # Public request / result / status models
├── agent/
Expand Down
6 changes: 3 additions & 3 deletions agent/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ConfigLoadResult:
".venv",
"venv",
"env",
".code-orbit",
".wepaircode",
"dist",
"build",
"*.egg-info",
Expand Down Expand Up @@ -135,8 +135,8 @@ def _normalize_input(cls, data: Any, info: ValidationInfo) -> Any:
patterns = normalized.get("ignore_patterns")
if patterns is not None:
normalized_patterns = tuple(patterns)
if ".code-orbit" not in normalized_patterns:
normalized_patterns = normalized_patterns + (".code-orbit",)
if ".wepaircode" not in normalized_patterns:
normalized_patterns = normalized_patterns + (".wepaircode",)
normalized["ignore_patterns"] = normalized_patterns

if normalized.get("chat_context_window") is None:
Expand Down
2 changes: 1 addition & 1 deletion agent/embeddings/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .types import ChunkEmbedding, FileEmbeddingRecord


DEFAULT_CACHE_DIR = ".code-orbit"
DEFAULT_CACHE_DIR = ".wepaircode"
DEFAULT_CACHE_FILENAME = "embeddings_cache.npz"
_CACHE_FORMAT_VERSION = 2

Expand Down
4 changes: 2 additions & 2 deletions agent/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ def _open(self) -> IO[str]:

def _resolve_log_dir(log_dir: Path | str | None) -> Path:
if log_dir is None:
return (Path.cwd() / ".code-orbit").resolve()
return (Path.cwd() / ".wepaircode").resolve()
return Path(log_dir).expanduser().resolve()


Expand All @@ -303,7 +303,7 @@ def _build_event_logger_name(log_dir: Path) -> str:
str(log_dir).encode("utf-8"),
usedforsecurity=False,
).hexdigest()[:12]
return f"code_orbit.events.{digest}"
return f"wepaircode.events.{digest}"


def build_event_log_handler(log_dir: Path) -> DeferredRotatingFileHandler:
Expand Down
2 changes: 1 addition & 1 deletion cliff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ body = """
{% for group, commits in commits | group_by(attribute="group") %}
### {{ group }}
{% for commit in commits %}
- {{ commit.message }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/ostefani/code-orbit/commit/{{ commit.id }}))
- {{ commit.message }} ([{{ commit.id | truncate(length=7, end="") }}](https://github.com/ostefani/wepaircode/commit/{{ commit.id }}))
{%- endfor %}
{% endfor %}
"""
Expand Down
32 changes: 16 additions & 16 deletions docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Code Orbit | Navigate Your Development Universe</title>
<title>WePairCode | We Pair Code — Navigate Your Development Universe</title>
<meta
name="description"
content="A high-performance, local-first AI coding agent. Compatible with llama.cpp, Ollama, and LM Studio." />
content="WePairCode — we pair code with you. A high-performance, local-first AI coding agent compatible with llama.cpp, Ollama, and LM Studio." />

<meta property="og:title" content="Code Orbit" />
<meta property="og:description" content="Open-source local-first AI coding agent written in Python." />
<meta property="og:title" content="WePairCode — We Pair Code" />
<meta property="og:description" content="WePairCode — we pair code. Open-source local-first AI coding agent written in Python." />
<meta property="og:type" content="website" />
<meta property="og:image" content="assets/cover_image.jpg" />
<meta name="twitter:card" content="summary_large_image" />
Expand All @@ -30,8 +30,8 @@
<div class="site-shell">
<header class="site-header">
<div class="container header-inner">
<a class="logo" href="#hero" aria-label="Code Orbit home">
<img class="logo-image" src="assets/logo.svg" />CodeOrbit
<a class="logo" href="#hero" aria-label="WePairCode home">
<img class="logo-image" src="assets/logo.svg" alt="WePairCode logo" />WePairCode
</a>

<nav class="site-nav" id="site-nav">
Expand All @@ -40,7 +40,7 @@
<a href="#install">Get Started</a>
<a
class="btn btn-solid btn-small"
href="https://github.com/ostefani/code-orbit"
href="https://github.com/ostefani/wepaircode"
target="_blank"
rel="noreferrer"
>GitHub ↗</a
Expand All @@ -56,16 +56,16 @@
</div>

<div class="container hero-inner">
<span class="hero-kicker reveal">Local-first AI workflow</span>
<span class="hero-kicker reveal">We Pair Code · Local-first AI workflow</span>
<h1 class="reveal reveal-delay-1">Navigate Your Development<br />Universe</h1>
<p class="reveal reveal-delay-2">
A high-performance, local-first AI coding agent. Privacy focused, context-aware, and
WePairCode is a high-performance, local-first AI coding agent. Privacy focused, context-aware, and
compatible with any local OpenAI-compatible server (llama.cpp, Ollama, LM Studio).
</p>
<div class="hero-actions reveal reveal-delay-3">
<a
class="btn btn-outline"
href="https://github.com/ostefani/subnetlens"
href="https://github.com/ostefani/wepaircode"
target="_blank"
rel="noopener">
<svg width="15" height="15" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
Expand Down Expand Up @@ -94,15 +94,15 @@ <h2>Demo</h2>
</div>
<div class="terminal-body" id="terminal">
<div class="line terminal-line">
<span class="color-dim">(.venv) username@hostname code-orbit %</span> python3
<span class="color-dim">(.venv) username@hostname wepaircode %</span> python3
main.py --dir ~/Code/temp/ --prompt "Create button component in react that accept
onClick callback and children. use css modules" --profile gemma-e4b
</div>

<div class="rich-panel panel-settings terminal-block">
<div class="rich-panel-title">🔧 settings</div>
<div class="rich-panel-body">
<span class="color-cyan">Code Orbit</span><br />
<span class="color-cyan">WePairCode</span><br />
<span class="color-dim">Target : </span
><span class="color-green">/Users/username/Code/temp</span><br />
<span class="color-dim">Model : </span
Expand Down Expand Up @@ -462,15 +462,15 @@ <h3>Local Speed</h3>
</svg>

<div class="install-content">
<h2>Ready to CodeOrbit?</h2>
<h2>Ready to Code?</h2>
<p class="install-subtitle">
Get the cross-platform version from GitHub and run it locally with a simple setup.<br />Python
Get WePairCode from GitHub and run it locally with a simple setup.<br />Python
3.12 or later required.
</p>

<div class="install-cmd-wrap">
<pre id="install-code"><span class="comment"># Clone and install locally</span>
<span class="code-line"><span class="prompt">$</span> git clone /path-to-the-clone</span>
<span class="code-line"><span class="prompt">$</span> git clone https://github.com/ostefani/wepaircode</span>

<span class="comment"># Run the agent</span>
<span class="code-line"><span class="prompt">$</span> python3 main.py --prompt "Fix it"</span>
Expand Down Expand Up @@ -502,7 +502,7 @@ <h2>Ready to CodeOrbit?</h2>

<footer class="site-footer">
<div class="container footer-inner">
<p>&copy; 2026 Olha Stefanishyna · MIT License.<br />Built for modern development.</p>
<p>&copy; 2026 Olha Stefanishyna · MIT License.<br />WePairCode — we pair code. Built for modern development.</p>
</div>
</footer>
</div>
Expand Down
6 changes: 3 additions & 3 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
from workflow import WorkflowError, run_workflow


HISTORY_DIR = Path(".code-orbit")
HISTORY_DIR = Path(".wepaircode")
HISTORY_FILE = HISTORY_DIR / "history.json"
LEGACY_HISTORY_FILE = Path(".code-orbit-history")
LEGACY_HISTORY_FILE = Path(".wepaircode-history")


def parse_args() -> argparse.Namespace:
Expand Down Expand Up @@ -77,7 +77,7 @@ def parse_args() -> argparse.Namespace:
"--version",
"-v",
action="version",
version=f"code-orbit {version('code-orbit')}",
version=f"wepaircode {version('wepaircode')}",
)
return parser.parse_args()

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

[project]
name = "code-orbit"
name = "wepaircode"
version = "1.8.10"
description = "AI-powered codebase editor using local llama.cpp models"
readme = "README.md"
Expand Down
4 changes: 2 additions & 2 deletions tests/test_embeddings.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def test_embedding_cache_ignores_non_npz_file(tmp_path: Path) -> None:
def test_embedding_cache_ignores_legacy_json_sibling_for_missing_npz(
tmp_path: Path,
) -> None:
cache_path = tmp_path / ".code-orbit" / "embeddings_cache.npz"
cache_path = tmp_path / ".wepaircode" / "embeddings_cache.npz"
cache_path.parent.mkdir()
legacy_path = cache_path.parent / "embeddings_cache.json"
legacy_path.write_text('{"version": 1, "files": {}}', encoding="utf-8")
Expand All @@ -185,7 +185,7 @@ def test_build_embedding_sync_does_not_reuse_legacy_json_cache(
) -> None:
_write_codebase(tmp_path)
config = Config(ignore_patterns=[".git", "node_modules"])
legacy_cache_dir = tmp_path / ".code-orbit"
legacy_cache_dir = tmp_path / ".wepaircode"
legacy_cache_dir.mkdir()
(legacy_cache_dir / "embeddings_cache.json").write_text(
'{"version": 1, "files": {}}', encoding="utf-8"
Expand Down
10 changes: 5 additions & 5 deletions tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ async def collect_events() -> list[AgentEvent[object]]:


def test_logging_subscriber_emits_json() -> None:
logger = logging.getLogger("test.code_orbit.events")
logger = logging.getLogger("test.wepaircode.events")
logger.handlers.clear()
logger.propagate = False
logger.setLevel(logging.INFO)
Expand Down Expand Up @@ -214,7 +214,7 @@ def failing_subscriber(_event) -> None:


def test_logging_formatter_preserves_utf8() -> None:
logger = logging.getLogger("test.code_orbit.utf8")
logger = logging.getLogger("test.wepaircode.utf8")
logger.handlers.clear()
logger.propagate = False
logger.setLevel(logging.INFO)
Expand Down Expand Up @@ -287,8 +287,8 @@ def test_build_event_logger_uses_rotating_file_handler(tmp_path: Path) -> None:
log_dir = tmp_path / "custom-logs"
other_dir = tmp_path / "other-logs"

assert logger.name.startswith("code_orbit.events.")
assert other.name.startswith("code_orbit.events.")
assert logger.name.startswith("wepaircode.events.")
assert other.name.startswith("wepaircode.events.")
assert other is not logger
assert other.name != logger.name
assert len(logger.handlers) == 1
Expand Down Expand Up @@ -341,7 +341,7 @@ def test_build_event_logger_uses_rotating_file_handler(tmp_path: Path) -> None:


def test_configure_event_logger_rejects_reconfiguration(tmp_path: Path) -> None:
logger = logging.getLogger("test.code_orbit.events.reconfigure")
logger = logging.getLogger("test.wepaircode.events.reconfigure")
prior_handlers = logger.handlers[:]
logger.handlers.clear()

Expand Down
8 changes: 4 additions & 4 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -968,7 +968,7 @@ def test_open_plan_in_editor_parses_modified_plan(monkeypatch, tmp_path) -> None
],
)

temp_path = tmp_path / "code-orbit-plan.json"
temp_path = tmp_path / "wepaircode-plan.json"

def fake_run(cmd, check=False):
assert cmd[:2] == ["vim", "-u"]
Expand All @@ -985,20 +985,20 @@ def fake_run(cmd, check=False):
assert approved.tasks[0].files == ["src/app.py", "agent/llm.py"]


def test_history_is_saved_under_code_orbit(monkeypatch, tmp_path: Path) -> None:
def test_history_is_saved_under_wepaircode(monkeypatch, tmp_path: Path) -> None:
monkeypatch.chdir(tmp_path)

save_history("Add logging")

history_path = tmp_path / ".code-orbit" / "history.json"
history_path = tmp_path / ".wepaircode" / "history.json"
assert history_path.exists()
assert json.loads(history_path.read_text(encoding="utf-8")) == ["Add logging"]


def test_history_loads_legacy_file_on_first_run(monkeypatch, tmp_path: Path) -> None:
monkeypatch.chdir(tmp_path)

legacy_path = tmp_path / ".code-orbit-history"
legacy_path = tmp_path / ".wepaircode-history"
legacy_path.write_text(
json.dumps(["Legacy prompt", "Older prompt"]),
encoding="utf-8",
Expand Down
Loading
Loading