Skip to content
Closed
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
  •  
  •  
  •  
80 changes: 21 additions & 59 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -1,87 +1,49 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

# Sample workflow for building and deploying a Jekyll site to GitHub Pages
name: Deploy Jekyll site to Pages
name: Deploy core docs site

on:
push:
branches:
- "main"
- main
paths:
- "cecli/website/**"
- "bright_vision_core/website/**"
- ".github/workflows/pages.yml"

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write

# Allow one concurrent deployment
concurrency:
group: "pages"
cancel-in-progress: true
group: pages-bright-vision-core
cancel-in-progress: false

jobs:
# Build job
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: cecli/website
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Ruby
uses: ruby/setup-ruby@v1
- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.3' # Not needed with a .ruby-version file
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
cache-version: 0 # Increment this number if you need to re-download cached gems
working-directory: '${{ github.workspace }}/cecli/website'
- name: Setup Pages
id: pages
uses: actions/configure-pages@v3
- name: Build with Jekyll
# Outputs to the './_site' directory by default
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
env:
JEKYLL_ENV: production
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
ruby-version: "3.3"
bundler-cache: true
working-directory: bright_vision_core/website

- name: Build Jekyll site
working-directory: bright_vision_core/website
run: bundle exec jekyll build --destination ../../_site

- uses: actions/upload-pages-artifact@v3
with:
path: "cecli/website/_site"
path: _site

# Deployment job
deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
- id: deployment
uses: actions/deploy-pages@v4

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

- name: Install linkchecker
run: |
python -m pip install --upgrade pip
python -m pip install linkchecker

- name: Run linkchecker
run: |
linkchecker --ignore-url='.+\.(mp4|mov|avi)' https://cecli.dev
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# Recursively allow files under subtree
!/.github/**
!/cecli/**
!/bright_vision_core/**
!/benchmark/**
!/docker/**
!/requirements/**
Expand Down Expand Up @@ -39,8 +40,12 @@ aider/_version.py
cecli/__version__.py
cecli/_version.py
cecli/website/Gemfile.lock
bright_vision_core/website/vendor/
*.pyc
env/

# Ignore Folders
cecli/website/_site/*
cecli/website/_site/*
bright_vision_core/website/_site/*
bright_vision_core/website/.jekyll-cache/*
_site/*
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ recursive-exclude cecli/website/assets *
recursive-exclude cecli/website *.js
recursive-exclude cecli/website *.html
recursive-exclude cecli/website *.yml

# BrightVision Core marketing/docs site (deploy via GitHub Pages, not PyPI)
recursive-exclude bright_vision_core/website *
50 changes: 47 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,38 @@
# BrightVision Core

**PyPI:** [`bright-vision-core`](https://pypi.org/project/bright-vision-core/) — cecli engine + headless HTTP API for the [BrightVision](https://github.com/Digital-Defiance/BrightVision) desktop IDE.

| Piece | Role |
|-------|------|
| **`cecli/`** | Coding agent (coders, commands, LiteLLM, git) — fork of [cecli](https://github.com/dwash96/cecli) |
| **`bright_vision_core/`** | Headless FastAPI on `:8741`, sessions, superproject git, EARS/spec todos |

### Lineage (fork²)

This repo is a **biforkation**: [Aider](https://github.com/Aider-AI/aider) → [cecli](https://github.com/dwash96/cecli) → **bright-vision-core** (cecli + `bright_vision_core`). The Vision HTTP layer was **ported** from our earlier **`aider-vision-core`** tree (file merge, not replayed git history). Product docs were adapted from the old core site (Aider → Bright prose); the desktop site is [bright-vision.digitaldefiance.org](https://bright-vision.digitaldefiance.org). Details: [docs/LINEAGE.md](docs/LINEAGE.md).

## Install (PyPI)

```bash
pip install bright-vision-core
bright-vision-core-serve # HTTP API at http://127.0.0.1:8741
curl -s http://127.0.0.1:8741/health
```

Editable dev install (BrightVision monorepo submodule):

```bash
pip install -e ./bright-vision-core
```

The same wheel also provides the **`cecli`** CLI (`cecli`, `aider-ce`, `ce.cli`) for terminal use.

**BrightVision desktop** manages Ollama locally (Terminal → Local LLM) and spawns this core on **Terminal → Start**. You do not need a separate `local-llm.sh` repo for the app.

Release: [docs/PUBLISHING.md](docs/PUBLISHING.md). Lineage: [docs/LINEAGE.md](docs/LINEAGE.md). Vision API: [bright_vision_core/README.md](bright_vision_core/README.md). **Website:** [bright_vision_core/website/](bright_vision_core/website/) → [bright-vision-core.digitaldefiance.org](https://bright-vision-core.digitaldefiance.org) (not `cecli/website/`).

---

## Why `cecli`?

`cecli` (probably pronounced like "Cecily") is yet another cli agent crafted for extensibility and customization. Originally a fork of the [Aider](https://cecli.dev/) AI pair programming tool, we aim to make agentic coding as maximally effective as it can be based on the growing capabilities of large language models.
Expand Down Expand Up @@ -34,21 +69,30 @@ LLMs are a part of our lives from here on out so join us in learning about and c
This project can be installed using several methods:

### Package Installation

**BrightVision Core** (cecli + `bright_vision_core` + `bright-vision-core-serve`):

```bash
pip install bright-vision-core
```

Upstream cecli-only name on PyPI (this repo publishes **`bright-vision-core`** instead):

```bash
pip install cecli-dev
```

or

```bash
uv pip install --native-tls cecli-dev
uv pip install --native-tls bright-vision-core
```

The package exports a `cecli` command that can start the application
The package exports `cecli` and `bright-vision-core-serve`.

### Tool Installation
```bash
uv tool install --native-tls --python python3.12 cecli-dev
uv tool install --native-tls --python python3.12 bright-vision-core
```

Use the tool installation so cecli doesn't interfere with your development environment
Expand Down
11 changes: 11 additions & 0 deletions bright_vision_core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# bright_vision_core

HTTP/SSE integration layer for **BrightVision** on top of [cecli](https://github.com/dwash96/cecli). Ported from `aider_vision_core` — see [docs/LINEAGE.md](../docs/LINEAGE.md).

- `http_api.py` — FastAPI + SSE (`:8741`)
- `session.py` — headless async sessions
- `git_workspace.py` — superproject + submodule `RepoSet`
- `workspace_todos.py` — EARS/spec tasks (`.aider-vision/todos.json`)

Install: `pip install bright-vision-core` (includes `cecli`).
Run: `bright-vision-core-serve` or `python scripts/vision_serve.py` → `http://127.0.0.1:8741`.
8 changes: 8 additions & 0 deletions bright_vision_core/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""BrightVision integration layer on top of cecli."""

__all__ = ["__version__"]

try:
from cecli._version import version as __version__
except Exception:
__version__ = "0.0.0"
118 changes: 118 additions & 0 deletions bright_vision_core/async_bridge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
"""Run cecli async APIs from sync HTTP/session code."""

from __future__ import annotations

import asyncio
import queue
import threading
from collections.abc import AsyncIterator, Callable, Coroutine, Iterator
from typing import Any, TypeVar

T = TypeVar("T")


def rebind_coder_loop_primitives(coder: Any) -> None:
"""
Replace asyncio primitives that were bound to a closed event loop.

Headless sessions call ``asyncio.run()`` for setup/preproc, then stream
``send_message`` on a dedicated loop in a worker thread. Reusing the same
``asyncio.Event`` across those loops raises "bound to a different event loop".
"""
coder.interrupt_event = asyncio.Event()
linter = getattr(coder, "linter", None)
if linter is not None:
linter.interrupt_event = coder.interrupt_event

_DONE = object()
# Yielded to the sync iterator so Session.run_message can flush io.events to SSE.
HEARTBEAT_PULSE = object()


def run(coro: Coroutine[object, object, T]) -> T:
"""Run one coroutine in a fresh event loop (sync callers only)."""
return asyncio.run(coro)


def iterate_async(agen: AsyncIterator[T]) -> Iterator[T]:
"""Bridge an async generator to a sync iterator (one loop per call)."""
loop = asyncio.new_event_loop()
try:
ait = agen.__aiter__()

def _next() -> T:
return loop.run_until_complete(ait.__anext__())

while True:
try:
yield _next()
except StopAsyncIteration:
break
finally:
loop.close()


def iterate_async_with_heartbeats(
make_agen: Callable[[], AsyncIterator[T]],
io: Any,
*,
coder: Any | None = None,
label: str = "LLM",
message: str = "Waiting for model response…",
interval_s: float = 8.0,
) -> Iterator[T | object]:
"""
Bridge an async iterator and emit ``progress`` events while blocked on the next chunk.

``make_agen`` is invoked inside the worker loop (not on the caller thread) so async
generators and ``asyncio.Event`` on ``coder`` are created/bound to that loop.

Yields :data:`HEARTBEAT_PULSE` between pulses so callers flush ``io.events`` to SSE.
"""
from bright_vision_core.gui_progress import emit_progress

out: queue.Queue[Any] = queue.Queue()

def producer() -> None:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
try:

async def consume() -> None:
if coder is not None:
rebind_coder_loop_primitives(coder)
agen = make_agen()
async for item in agen:
out.put(item)

loop.run_until_complete(consume())
out.put(_DONE)
except Exception as err:
out.put(err)
finally:
loop.close()

thread = threading.Thread(target=producer, daemon=True)
thread.start()

wait_s = max(2.0, interval_s)
pulse = 0
while True:
try:
item = out.get(timeout=wait_s)
except queue.Empty:
pulse += 1
emit_progress(
io,
label=label,
message=f"{message} ({int(pulse * wait_s)}s)",
)
yield HEARTBEAT_PULSE
continue
if item is _DONE:
break
if isinstance(item, Exception):
raise item
yield item

thread.join(timeout=0.1)
10 changes: 10 additions & 0 deletions bright_vision_core/brand.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
"""
Product identity — keep in sync with bright-vision/src/brand.ts
"""

PRODUCT_VISION = "bright-vision"
PRODUCT_CORE = "bright-vision-core"

DISPLAY_VISION = "BrightVision"
DISPLAY_CORE = "BrightVision Core"
DISPLAY_MONOGRAM = "BV"
7 changes: 7 additions & 0 deletions bright_vision_core/cli_serve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""Console entry point: ``aider-vision-core-serve``."""

from bright_vision_core.vision_serve import run


def main():
run()
Loading