openclose 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openclose-0.1.0/.github/workflows/ci.yml +31 -0
- openclose-0.1.0/.gitignore +18 -0
- openclose-0.1.0/ARCHITECTURE.md +184 -0
- openclose-0.1.0/LICENSE +21 -0
- openclose-0.1.0/PKG-INFO +242 -0
- openclose-0.1.0/README.md +200 -0
- openclose-0.1.0/docs/guide-agents-customization.md +307 -0
- openclose-0.1.0/docs/guide-personnalisation-agents.md +308 -0
- openclose-0.1.0/pyproject.toml +74 -0
- openclose-0.1.0/src/openclose/__init__.py +3 -0
- openclose-0.1.0/src/openclose/__main__.py +6 -0
- openclose-0.1.0/src/openclose/agent/__init__.py +14 -0
- openclose-0.1.0/src/openclose/agent/agent.py +138 -0
- openclose-0.1.0/src/openclose/agent/loop.py +467 -0
- openclose-0.1.0/src/openclose/agent/prompt.py +99 -0
- openclose-0.1.0/src/openclose/bus/__init__.py +5 -0
- openclose-0.1.0/src/openclose/bus/bus.py +61 -0
- openclose-0.1.0/src/openclose/cli/__init__.py +1 -0
- openclose-0.1.0/src/openclose/cli/cli.py +142 -0
- openclose-0.1.0/src/openclose/config/__init__.py +19 -0
- openclose-0.1.0/src/openclose/config/agents.py +173 -0
- openclose-0.1.0/src/openclose/config/config.py +111 -0
- openclose-0.1.0/src/openclose/config/paths.py +66 -0
- openclose-0.1.0/src/openclose/config/schema.py +96 -0
- openclose-0.1.0/src/openclose/file/__init__.py +8 -0
- openclose-0.1.0/src/openclose/file/binary.py +32 -0
- openclose-0.1.0/src/openclose/file/diff.py +103 -0
- openclose-0.1.0/src/openclose/file/ignore.py +56 -0
- openclose-0.1.0/src/openclose/file/watcher.py +56 -0
- openclose-0.1.0/src/openclose/flag.py +26 -0
- openclose-0.1.0/src/openclose/format/__init__.py +5 -0
- openclose-0.1.0/src/openclose/format/formatter.py +91 -0
- openclose-0.1.0/src/openclose/id.py +32 -0
- openclose-0.1.0/src/openclose/lint/__init__.py +5 -0
- openclose-0.1.0/src/openclose/lint/linter.py +113 -0
- openclose-0.1.0/src/openclose/log.py +23 -0
- openclose-0.1.0/src/openclose/patch/__init__.py +5 -0
- openclose-0.1.0/src/openclose/patch/patch.py +82 -0
- openclose-0.1.0/src/openclose/permission/__init__.py +16 -0
- openclose-0.1.0/src/openclose/permission/broker.py +118 -0
- openclose-0.1.0/src/openclose/permission/extract.py +78 -0
- openclose-0.1.0/src/openclose/permission/permission.py +120 -0
- openclose-0.1.0/src/openclose/permission/rules.py +41 -0
- openclose-0.1.0/src/openclose/permission/schema.py +25 -0
- openclose-0.1.0/src/openclose/project/__init__.py +7 -0
- openclose-0.1.0/src/openclose/project/project.py +42 -0
- openclose-0.1.0/src/openclose/project/snapshot.py +68 -0
- openclose-0.1.0/src/openclose/project/worktree.py +83 -0
- openclose-0.1.0/src/openclose/provider/__init__.py +13 -0
- openclose-0.1.0/src/openclose/provider/auth.py +37 -0
- openclose-0.1.0/src/openclose/provider/models.py +51 -0
- openclose-0.1.0/src/openclose/provider/provider.py +128 -0
- openclose-0.1.0/src/openclose/scheduler/__init__.py +5 -0
- openclose-0.1.0/src/openclose/scheduler/scheduler.py +71 -0
- openclose-0.1.0/src/openclose/server/__init__.py +1 -0
- openclose-0.1.0/src/openclose/server/app.py +55 -0
- openclose-0.1.0/src/openclose/server/routes.py +460 -0
- openclose-0.1.0/src/openclose/server/sse.py +34 -0
- openclose-0.1.0/src/openclose/server/templates/base.html +21 -0
- openclose-0.1.0/src/openclose/server/templates/session.html +140 -0
- openclose-0.1.0/src/openclose/server/templates/static/app.js +920 -0
- openclose-0.1.0/src/openclose/server/templates/static/style.css +369 -0
- openclose-0.1.0/src/openclose/session/__init__.py +22 -0
- openclose-0.1.0/src/openclose/session/cancel.py +49 -0
- openclose-0.1.0/src/openclose/session/compaction.py +217 -0
- openclose-0.1.0/src/openclose/session/message.py +24 -0
- openclose-0.1.0/src/openclose/session/processor.py +298 -0
- openclose-0.1.0/src/openclose/session/prompt.py +23 -0
- openclose-0.1.0/src/openclose/session/session.py +319 -0
- openclose-0.1.0/src/openclose/storage/__init__.py +19 -0
- openclose-0.1.0/src/openclose/storage/db.py +92 -0
- openclose-0.1.0/src/openclose/storage/migrations.py +66 -0
- openclose-0.1.0/src/openclose/storage/schema.py +71 -0
- openclose-0.1.0/src/openclose/tool/__init__.py +12 -0
- openclose-0.1.0/src/openclose/tool/registry.py +46 -0
- openclose-0.1.0/src/openclose/tool/tool.py +92 -0
- openclose-0.1.0/src/openclose/tool/tools/__init__.py +33 -0
- openclose-0.1.0/src/openclose/tool/tools/bash.py +95 -0
- openclose-0.1.0/src/openclose/tool/tools/edit.py +94 -0
- openclose-0.1.0/src/openclose/tool/tools/glob.py +58 -0
- openclose-0.1.0/src/openclose/tool/tools/grep.py +104 -0
- openclose-0.1.0/src/openclose/tool/tools/lint.py +81 -0
- openclose-0.1.0/src/openclose/tool/tools/ls.py +51 -0
- openclose-0.1.0/src/openclose/tool/tools/multiedit.py +123 -0
- openclose-0.1.0/src/openclose/tool/tools/patch.py +136 -0
- openclose-0.1.0/src/openclose/tool/tools/question.py +41 -0
- openclose-0.1.0/src/openclose/tool/tools/question_broker.py +151 -0
- openclose-0.1.0/src/openclose/tool/tools/read.py +78 -0
- openclose-0.1.0/src/openclose/tool/tools/todo.py +79 -0
- openclose-0.1.0/src/openclose/tool/tools/webfetch.py +66 -0
- openclose-0.1.0/src/openclose/tool/tools/write.py +53 -0
- openclose-0.1.0/src/openclose/tool/truncation.py +27 -0
- openclose-0.1.0/src/openclose/util/__init__.py +1 -0
- openclose-0.1.0/src/openclose/util/fs.py +43 -0
- openclose-0.1.0/src/openclose/util/git.py +38 -0
- openclose-0.1.0/src/openclose/util/process.py +53 -0
- openclose-0.1.0/tests/__init__.py +0 -0
- openclose-0.1.0/tests/conftest.py +30 -0
- openclose-0.1.0/tests/test_agent.py +298 -0
- openclose-0.1.0/tests/test_agent_loop.py +48 -0
- openclose-0.1.0/tests/test_bus.py +112 -0
- openclose-0.1.0/tests/test_cli.py +25 -0
- openclose-0.1.0/tests/test_cli_extended.py +34 -0
- openclose-0.1.0/tests/test_config.py +69 -0
- openclose-0.1.0/tests/test_edge_coverage.py +156 -0
- openclose-0.1.0/tests/test_file.py +101 -0
- openclose-0.1.0/tests/test_format.py +29 -0
- openclose-0.1.0/tests/test_format_extended.py +37 -0
- openclose-0.1.0/tests/test_id.py +33 -0
- openclose-0.1.0/tests/test_lint.py +27 -0
- openclose-0.1.0/tests/test_lint_extended.py +50 -0
- openclose-0.1.0/tests/test_more_coverage.py +235 -0
- openclose-0.1.0/tests/test_more_tools.py +243 -0
- openclose-0.1.0/tests/test_parallel_tool_calls.py +324 -0
- openclose-0.1.0/tests/test_patch.py +45 -0
- openclose-0.1.0/tests/test_permission_broker.py +102 -0
- openclose-0.1.0/tests/test_permissions.py +136 -0
- openclose-0.1.0/tests/test_project.py +74 -0
- openclose-0.1.0/tests/test_provider.py +61 -0
- openclose-0.1.0/tests/test_scheduler.py +36 -0
- openclose-0.1.0/tests/test_server.py +70 -0
- openclose-0.1.0/tests/test_session.py +526 -0
- openclose-0.1.0/tests/test_sse.py +54 -0
- openclose-0.1.0/tests/test_storage.py +131 -0
- openclose-0.1.0/tests/test_tool_sandboxing.py +95 -0
- openclose-0.1.0/tests/test_tools.py +330 -0
- openclose-0.1.0/tests/test_util.py +121 -0
- openclose-0.1.0/uv.lock +1329 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- name: Install uv
|
|
16
|
+
uses: astral-sh/setup-uv@v4
|
|
17
|
+
|
|
18
|
+
- name: Set up Python 3.12
|
|
19
|
+
run: uv python install 3.12
|
|
20
|
+
|
|
21
|
+
- name: Install dependencies
|
|
22
|
+
run: uv sync --all-extras
|
|
23
|
+
|
|
24
|
+
- name: Lint (ruff)
|
|
25
|
+
run: uv run ruff check src/ tests/
|
|
26
|
+
|
|
27
|
+
- name: Type check (mypy)
|
|
28
|
+
run: uv run mypy --strict src/ tests/
|
|
29
|
+
|
|
30
|
+
- name: Test (pytest)
|
|
31
|
+
run: uv run pytest tests/ --cov=openclose --cov-report=term-missing --cov-fail-under=80
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Architecture — OpenClose
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
OpenClose is a local-first Python AI coding assistant that uses OpenAI-compatible APIs (vLLM, llama.cpp, Ollama, OpenAI). All data stays local in SQLite. The UI is a simple HTML interface served by FastAPI.
|
|
6
|
+
|
|
7
|
+
## Package Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/openclose/
|
|
11
|
+
├── __init__.py # Version
|
|
12
|
+
├── __main__.py # python -m openclose entry
|
|
13
|
+
├── id.py # ULID-based ID generation
|
|
14
|
+
├── flag.py # Feature flags from environment
|
|
15
|
+
├── log.py # Structured logging (rich)
|
|
16
|
+
│
|
|
17
|
+
├── config/ # Multi-layer config (TOML + env + defaults)
|
|
18
|
+
│ ├── config.py # Load/merge/reload
|
|
19
|
+
│ ├── paths.py # Platform-specific directories
|
|
20
|
+
│ ├── schema.py # Pydantic v2 settings models
|
|
21
|
+
│ └── agents.py # Agent definitions + inheritance resolution
|
|
22
|
+
│
|
|
23
|
+
├── storage/ # SQLite persistence
|
|
24
|
+
│ ├── db.py # Engine + Database class
|
|
25
|
+
│ ├── schema.py # SQLModel tables (Session, Message, MessagePart, Project)
|
|
26
|
+
│ └── migrations.py # Schema versioning
|
|
27
|
+
│
|
|
28
|
+
├── bus/ # Async event bus (pub/sub)
|
|
29
|
+
│ └── bus.py
|
|
30
|
+
│
|
|
31
|
+
├── provider/ # OpenAI-compatible LLM provider
|
|
32
|
+
│ ├── provider.py # AsyncOpenAI wrapper, streaming
|
|
33
|
+
│ ├── models.py # Model registry
|
|
34
|
+
│ └── auth.py # API key resolution
|
|
35
|
+
│
|
|
36
|
+
├── agent/ # Agent definitions and orchestration
|
|
37
|
+
│ ├── agent.py # Build/Plan/General agents, custom agents
|
|
38
|
+
│ ├── prompt.py # System prompt assembly
|
|
39
|
+
│ └── loop.py # Main agent loop (stream → tool calls → repeat)
|
|
40
|
+
│
|
|
41
|
+
├── session/ # Conversation management
|
|
42
|
+
│ ├── session.py # CRUD operations
|
|
43
|
+
│ ├── message.py # Role/PartType enums
|
|
44
|
+
│ ├── processor.py # Orchestrates agent loop + persistence
|
|
45
|
+
│ ├── compaction.py # Context window management
|
|
46
|
+
│ ├── prompt.py # Message history builder
|
|
47
|
+
│ └── cancel.py # Session cancellation
|
|
48
|
+
│
|
|
49
|
+
├── tool/ # Tool system
|
|
50
|
+
│ ├── tool.py # Tool base class, OpenAI schema generation
|
|
51
|
+
│ ├── registry.py # Tool registry + executor
|
|
52
|
+
│ ├── truncation.py # Output truncation
|
|
53
|
+
│ └── tools/ # Built-in tools (13)
|
|
54
|
+
│ ├── read.py, write.py, edit.py # File operations
|
|
55
|
+
│ ├── glob.py, grep.py, ls.py # Search/listing
|
|
56
|
+
│ ├── bash.py # Shell execution
|
|
57
|
+
│ ├── patch.py # Unified diff application
|
|
58
|
+
│ ├── webfetch.py # HTTP fetch
|
|
59
|
+
│ ├── todo.py # Todo list
|
|
60
|
+
│ ├── question.py # User clarification
|
|
61
|
+
│ ├── question_broker.py # Async question broker
|
|
62
|
+
│ ├── lint.py # Lint tool
|
|
63
|
+
│ └── multiedit.py # Multi-file edits
|
|
64
|
+
│
|
|
65
|
+
├── permission/ # Tool access control
|
|
66
|
+
│ ├── permission.py # Evaluation engine
|
|
67
|
+
│ ├── rules.py # Allow/deny/ask rules
|
|
68
|
+
│ ├── schema.py # Request/response types
|
|
69
|
+
│ ├── broker.py # Async permission request broker
|
|
70
|
+
│ └── extract.py # Path extraction + sandbox checking
|
|
71
|
+
│
|
|
72
|
+
├── file/ # File system utilities
|
|
73
|
+
│ ├── binary.py # Binary file detection
|
|
74
|
+
│ ├── ignore.py # .gitignore pattern handling
|
|
75
|
+
│ ├── diff.py # Change tracking
|
|
76
|
+
│ └── watcher.py # File watching (watchfiles)
|
|
77
|
+
│
|
|
78
|
+
├── format/ # Auto-formatters (ruff, black, gofmt, etc.)
|
|
79
|
+
│ └── formatter.py
|
|
80
|
+
│
|
|
81
|
+
├── lint/ # Linter integration (ruff, mypy, eslint, etc.)
|
|
82
|
+
│ └── linter.py
|
|
83
|
+
│
|
|
84
|
+
├── project/ # Project/VCS management
|
|
85
|
+
│ ├── project.py # Detection and metadata
|
|
86
|
+
│ ├── worktree.py # Git worktree management
|
|
87
|
+
│ └── snapshot.py # Git GC and snapshots
|
|
88
|
+
│
|
|
89
|
+
├── patch/ # Unified diff engine
|
|
90
|
+
│ └── patch.py
|
|
91
|
+
│
|
|
92
|
+
├── scheduler/ # Background task scheduling
|
|
93
|
+
│ └── scheduler.py
|
|
94
|
+
│
|
|
95
|
+
├── server/ # FastAPI + HTML UI
|
|
96
|
+
│ ├── app.py # FastAPI app creation
|
|
97
|
+
│ ├── routes.py # API + HTML routes
|
|
98
|
+
│ ├── sse.py # Server-Sent Events streaming
|
|
99
|
+
│ └── templates/ # Jinja2 HTML templates + static assets
|
|
100
|
+
│
|
|
101
|
+
├── cli/ # Command-line interface
|
|
102
|
+
│ └── cli.py # serve, run -p, sessions commands
|
|
103
|
+
│
|
|
104
|
+
└── util/ # Shared utilities
|
|
105
|
+
├── process.py # Async subprocess
|
|
106
|
+
├── git.py # Git command wrapper
|
|
107
|
+
└── fs.py # File system helpers
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Key Design Decisions
|
|
111
|
+
|
|
112
|
+
### 1. OpenAI-compatible API only
|
|
113
|
+
**Decision:** Use the `openai` Python SDK targeting any OpenAI-compatible endpoint.
|
|
114
|
+
**Why:** Covers vLLM, llama.cpp, Ollama, and OpenAI with a single client. No need for 75+ provider adapters.
|
|
115
|
+
|
|
116
|
+
### 2. Local-only, no cloud
|
|
117
|
+
**Decision:** All data in local SQLite. No sharing, no remote auth, no cloud infra.
|
|
118
|
+
**Why:** User requirement for data sovereignty and simplicity.
|
|
119
|
+
|
|
120
|
+
### 3. HTML UI via FastAPI (not TUI)
|
|
121
|
+
**Decision:** Replace the original Bubble Tea TUI with a simple HTML UI served by FastAPI + Jinja2 + vanilla JS.
|
|
122
|
+
**Why:** Easier to customize and modify than a terminal UI framework. SSE provides real-time streaming.
|
|
123
|
+
|
|
124
|
+
### 4. Synchronous SQLite via SQLModel
|
|
125
|
+
**Decision:** Use SQLModel (SQLAlchemy under the hood) with synchronous access, not async.
|
|
126
|
+
**Why:** SQLite is inherently single-writer. The overhead of async wrappers adds complexity without benefit for local-only usage. The main async work is in LLM streaming and tool execution.
|
|
127
|
+
|
|
128
|
+
### 5. dict[str, Any] for LLM messages
|
|
129
|
+
**Decision:** Use plain dicts internally for message passing to/from the LLM, not OpenAI typed dicts.
|
|
130
|
+
**Why:** The OpenAI type system for `ChatCompletionMessageParam` is a complex union that creates unnecessary friction with mypy. We cast at the boundary when calling the API.
|
|
131
|
+
|
|
132
|
+
### 6. Tool system with function callbacks
|
|
133
|
+
**Decision:** Tools are defined as `Tool` objects with `ToolParameter` metadata and an async execute function.
|
|
134
|
+
**Why:** Simple, composable, and generates OpenAI function-calling schemas automatically. No metaclass magic.
|
|
135
|
+
|
|
136
|
+
### 7. Permission engine with last-match-wins
|
|
137
|
+
**Decision:** Permission rules are evaluated in order; last matching rule determines the action.
|
|
138
|
+
**Why:** Matches the OpenCode original and allows general rules early with specific overrides later.
|
|
139
|
+
|
|
140
|
+
### 8. Agent loop as iterator
|
|
141
|
+
**Decision:** `AgentLoop.run()` is an async iterator yielding `StreamEvent` objects.
|
|
142
|
+
**Why:** Enables both SSE streaming (server) and direct consumption (CLI). The caller decides how to handle events.
|
|
143
|
+
|
|
144
|
+
## Data Flow
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
User Input
|
|
148
|
+
→ CLI (-p flag) or HTML UI (POST /api/sessions/{id}/messages)
|
|
149
|
+
→ SessionProcessor
|
|
150
|
+
→ Persists user message to SQLite
|
|
151
|
+
→ Creates AgentLoop with tool schemas
|
|
152
|
+
→ AgentLoop streams from Provider (OpenAI-compatible)
|
|
153
|
+
→ Text chunks → StreamEvent("text")
|
|
154
|
+
→ Tool calls → ToolRegistry.execute() → StreamEvent("tool_result")
|
|
155
|
+
→ Loop continues until done or max_steps
|
|
156
|
+
→ Persists assistant response to SQLite
|
|
157
|
+
→ Response streamed via SSE (server) or printed (CLI)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Configuration Priority
|
|
161
|
+
|
|
162
|
+
1. Environment variables (`OPENCLOSE_*`)
|
|
163
|
+
2. Project config (`.openclose/config.toml`)
|
|
164
|
+
3. User config (`~/.config/openclose/config.toml`)
|
|
165
|
+
4. Defaults (Pydantic model defaults)
|
|
166
|
+
|
|
167
|
+
## Dependencies
|
|
168
|
+
|
|
169
|
+
| Package | Purpose |
|
|
170
|
+
|---|---|
|
|
171
|
+
| pydantic / pydantic-settings | Config and data models |
|
|
172
|
+
| sqlmodel | SQLite ORM |
|
|
173
|
+
| openai | LLM API client |
|
|
174
|
+
| tiktoken | Token counting |
|
|
175
|
+
| fastapi / uvicorn / jinja2 | HTML UI server |
|
|
176
|
+
| sse-starlette | Server-Sent Events |
|
|
177
|
+
| httpx | HTTP client (webfetch tool) |
|
|
178
|
+
| beautifulsoup4 | HTML parsing (webfetch tool) |
|
|
179
|
+
| rich | Terminal output and logging |
|
|
180
|
+
| watchfiles | File system monitoring |
|
|
181
|
+
| pathspec | Gitignore pattern matching |
|
|
182
|
+
| python-ulid | ID generation |
|
|
183
|
+
| aiosqlite | Async SQLite adapter |
|
|
184
|
+
| aiofiles | Async file I/O |
|
openclose-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 leflakk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
openclose-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openclose
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python AI coding assistant — local-first, OpenAI-compatible
|
|
5
|
+
Project-URL: Homepage, https://github.com/leflakk/openclose
|
|
6
|
+
Project-URL: Repository, https://github.com/leflakk/openclose
|
|
7
|
+
Project-URL: Issues, https://github.com/leflakk/openclose/issues
|
|
8
|
+
Author: OpenClose Contributors
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
17
|
+
Requires-Python: >=3.12
|
|
18
|
+
Requires-Dist: aiofiles>=24.1
|
|
19
|
+
Requires-Dist: aiosqlite>=0.20
|
|
20
|
+
Requires-Dist: beautifulsoup4>=4.12
|
|
21
|
+
Requires-Dist: fastapi>=0.115
|
|
22
|
+
Requires-Dist: httpx>=0.28
|
|
23
|
+
Requires-Dist: jinja2>=3.1
|
|
24
|
+
Requires-Dist: openai>=1.55
|
|
25
|
+
Requires-Dist: pathspec>=0.12
|
|
26
|
+
Requires-Dist: pydantic-settings>=2.5
|
|
27
|
+
Requires-Dist: pydantic<3,>=2.9
|
|
28
|
+
Requires-Dist: python-ulid>=3.0
|
|
29
|
+
Requires-Dist: rich>=13.9
|
|
30
|
+
Requires-Dist: sqlmodel>=0.0.22
|
|
31
|
+
Requires-Dist: sse-starlette>=2.1
|
|
32
|
+
Requires-Dist: tiktoken>=0.8
|
|
33
|
+
Requires-Dist: uvicorn>=0.32
|
|
34
|
+
Requires-Dist: watchfiles>=1.0
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
37
|
+
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
|
|
38
|
+
Requires-Dist: pytest-cov>=6.0; extra == 'dev'
|
|
39
|
+
Requires-Dist: pytest>=8.3; extra == 'dev'
|
|
40
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
41
|
+
Description-Content-Type: text/markdown
|
|
42
|
+
|
|
43
|
+
# OpenClose
|
|
44
|
+
|
|
45
|
+
A local-first Python AI coding assistant that works with any OpenAI-compatible API.
|
|
46
|
+
|
|
47
|
+
OpenClose runs entirely on your machine. Connect it to vLLM, llama.cpp, Ollama, or OpenAI — all conversations and data stay in a local SQLite database. The web UI streams responses in real time via SSE, and a headless CLI mode lets you script it into workflows.
|
|
48
|
+
|
|
49
|
+
## Features
|
|
50
|
+
|
|
51
|
+
- **Local-first** — SQLite storage, no cloud, no remote auth, no telemetry
|
|
52
|
+
- **Provider-agnostic** — works with any OpenAI-compatible endpoint (vLLM, llama.cpp, Ollama, OpenAI)
|
|
53
|
+
- **Web UI** — three-column layout with session sidebar, chat, and context info panel
|
|
54
|
+
- **Real-time streaming** — Server-Sent Events for live token-by-token output
|
|
55
|
+
- **Two built-in agents** — `build` (full tool access) and `plan` (read-only analysis)
|
|
56
|
+
- **Custom agents** — define your own in `config.toml` with inheritance, custom prompts, and tool restrictions
|
|
57
|
+
- **13 built-in tools** — read, write, edit, glob, grep, ls, bash, patch, webfetch, todo, question, lint, multiedit
|
|
58
|
+
- **Permission system** — per-tool allow/deny/ask rules with last-match-wins semantics
|
|
59
|
+
- **Auto-formatting** — ruff, black, gofmt, rustfmt, prettier, shfmt, clang-format
|
|
60
|
+
- **Linting** — ruff, mypy, eslint, golangci-lint, clippy, clang-tidy, cppcheck
|
|
61
|
+
- **Context management** — automatic compaction when approaching the context window limit
|
|
62
|
+
- **CLI mode** — `openclose run -p "..."` for non-interactive scripting with optional JSON output
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
Requires **Python 3.12+**.
|
|
67
|
+
|
|
68
|
+
### From PyPI
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
pip install openclose
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### With uv (recommended)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
uv pip install openclose
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### From source
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
git clone https://github.com/leflakk/openclose.git
|
|
84
|
+
cd openclose
|
|
85
|
+
uv sync
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Quickstart
|
|
89
|
+
|
|
90
|
+
### 1. Configure a provider
|
|
91
|
+
|
|
92
|
+
Create `~/.config/openclose/config.toml` (Linux) or `~/Library/Application Support/openclose/config.toml` (macOS):
|
|
93
|
+
|
|
94
|
+
```toml
|
|
95
|
+
[[providers]]
|
|
96
|
+
name = "default"
|
|
97
|
+
base_url = "http://localhost:8000/v1"
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If your provider requires an API key, set it via environment variable or config:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
export OPENCLOSE_API_KEY="sk-..."
|
|
104
|
+
# or
|
|
105
|
+
export OPENAI_API_KEY="sk-..."
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Key resolution order: `OPENCLOSE_API_KEY` env var > `OPENAI_API_KEY` env var > config file `api_key` field.
|
|
109
|
+
|
|
110
|
+
### 2. Start the web UI
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
openclose serve
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Opens your browser to `http://127.0.0.1:9876`. Use `--host`, `--port`, `--no-browser`, or `--project-dir` to customize.
|
|
117
|
+
|
|
118
|
+
### 3. Or run headless
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
openclose run -p "Explain the main function in src/main.py"
|
|
122
|
+
openclose run -p "Add error handling to the parser" --agent build
|
|
123
|
+
openclose run -p "Analyze the test coverage gaps" --agent plan --json
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 4. List sessions
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
openclose sessions
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Configuration
|
|
133
|
+
|
|
134
|
+
Configuration is loaded in priority order (highest wins):
|
|
135
|
+
|
|
136
|
+
1. Environment variables (`OPENCLOSE_*`)
|
|
137
|
+
2. Project config (`.openclose/config.toml` in your project directory)
|
|
138
|
+
3. User config (`~/.config/openclose/config.toml`)
|
|
139
|
+
4. Defaults
|
|
140
|
+
|
|
141
|
+
Example `config.toml`:
|
|
142
|
+
|
|
143
|
+
```toml
|
|
144
|
+
# Provider — any OpenAI-compatible endpoint
|
|
145
|
+
[[providers]]
|
|
146
|
+
name = "default"
|
|
147
|
+
base_url = "http://localhost:8000/v1"
|
|
148
|
+
api_key = ""
|
|
149
|
+
default_model = ""
|
|
150
|
+
|
|
151
|
+
# Session defaults
|
|
152
|
+
default_agent = "build"
|
|
153
|
+
max_context_tokens = 128000
|
|
154
|
+
compaction_threshold = 0.9
|
|
155
|
+
|
|
156
|
+
# Override built-in agent settings
|
|
157
|
+
[[agents]]
|
|
158
|
+
name = "build"
|
|
159
|
+
model = "qwen3-30b-a3b"
|
|
160
|
+
|
|
161
|
+
# Custom agent with inheritance
|
|
162
|
+
[[agents]]
|
|
163
|
+
name = "review"
|
|
164
|
+
extends = "plan"
|
|
165
|
+
description = "Code reviewer"
|
|
166
|
+
system_prompt = "You are a code reviewer. Focus on bugs, security, and clarity."
|
|
167
|
+
|
|
168
|
+
# Permission rules (last match wins)
|
|
169
|
+
[[permissions]]
|
|
170
|
+
tool = "*"
|
|
171
|
+
action = "ask"
|
|
172
|
+
|
|
173
|
+
[[permissions]]
|
|
174
|
+
tool = "read"
|
|
175
|
+
action = "allow"
|
|
176
|
+
|
|
177
|
+
[[permissions]]
|
|
178
|
+
tool = "bash"
|
|
179
|
+
path = "/tmp/*"
|
|
180
|
+
action = "deny"
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
See [docs/guide-agents-customization.md](docs/guide-agents-customization.md) for the full agent customization guide.
|
|
184
|
+
|
|
185
|
+
## Agents
|
|
186
|
+
|
|
187
|
+
| Agent | Description | Tool restrictions |
|
|
188
|
+
|-------|-------------|-------------------|
|
|
189
|
+
| `build` | Primary agent with full tool access for code writing and execution | None |
|
|
190
|
+
| `plan` | Read-only analysis agent (extends `build`) | Denied: write, edit, bash, patch |
|
|
191
|
+
|
|
192
|
+
Custom agents support:
|
|
193
|
+
|
|
194
|
+
- **Inheritance** via `extends` — create variants without repeating config
|
|
195
|
+
- **Semantic traits** — e.g. `["readonly"]` adjusts the system prompt automatically
|
|
196
|
+
- **Tool filtering** — `allowed_tools` and `denied_tools` control what the LLM can call
|
|
197
|
+
- **Custom system prompts** with `$variable` substitution
|
|
198
|
+
|
|
199
|
+
## Web UI
|
|
200
|
+
|
|
201
|
+
The web UI has a three-column layout: sessions sidebar, chat panel, and context/files sidebar.
|
|
202
|
+
|
|
203
|
+
### Slash commands
|
|
204
|
+
|
|
205
|
+
| Command | Description |
|
|
206
|
+
|---------|-------------|
|
|
207
|
+
| `/new` | Start a new session |
|
|
208
|
+
| `/sessions` | Switch to another session |
|
|
209
|
+
| `/agents` | Switch agent |
|
|
210
|
+
| `/compact` | Compress context window |
|
|
211
|
+
| `/undo` | Remove last message pair |
|
|
212
|
+
| `/export` | Export session as JSON |
|
|
213
|
+
| `/copy` | Copy last response |
|
|
214
|
+
| `/skip` | Toggle auto-approve permissions |
|
|
215
|
+
| `/help` | Show available commands |
|
|
216
|
+
|
|
217
|
+
Sessions can be forked to continue a conversation with a different agent.
|
|
218
|
+
|
|
219
|
+
## Architecture
|
|
220
|
+
|
|
221
|
+
See [ARCHITECTURE.md](ARCHITECTURE.md) for the full package structure, design decisions, and data flow diagrams.
|
|
222
|
+
|
|
223
|
+
## Contributing
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
git clone https://github.com/leflakk/openclose.git
|
|
227
|
+
cd openclose
|
|
228
|
+
uv sync
|
|
229
|
+
uv run pytest tests/
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Code quality requirements:
|
|
233
|
+
|
|
234
|
+
- **Linting** — `uv run ruff check src/ tests/`
|
|
235
|
+
- **Type checking** — `uv run mypy --strict src/ tests/`
|
|
236
|
+
- **Tests** — `uv run pytest tests/ --cov=openclose --cov-fail-under=80`
|
|
237
|
+
|
|
238
|
+
CI runs all three checks on every push and pull request via GitHub Actions.
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# OpenClose
|
|
2
|
+
|
|
3
|
+
A local-first Python AI coding assistant that works with any OpenAI-compatible API.
|
|
4
|
+
|
|
5
|
+
OpenClose runs entirely on your machine. Connect it to vLLM, llama.cpp, Ollama, or OpenAI — all conversations and data stay in a local SQLite database. The web UI streams responses in real time via SSE, and a headless CLI mode lets you script it into workflows.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Local-first** — SQLite storage, no cloud, no remote auth, no telemetry
|
|
10
|
+
- **Provider-agnostic** — works with any OpenAI-compatible endpoint (vLLM, llama.cpp, Ollama, OpenAI)
|
|
11
|
+
- **Web UI** — three-column layout with session sidebar, chat, and context info panel
|
|
12
|
+
- **Real-time streaming** — Server-Sent Events for live token-by-token output
|
|
13
|
+
- **Two built-in agents** — `build` (full tool access) and `plan` (read-only analysis)
|
|
14
|
+
- **Custom agents** — define your own in `config.toml` with inheritance, custom prompts, and tool restrictions
|
|
15
|
+
- **13 built-in tools** — read, write, edit, glob, grep, ls, bash, patch, webfetch, todo, question, lint, multiedit
|
|
16
|
+
- **Permission system** — per-tool allow/deny/ask rules with last-match-wins semantics
|
|
17
|
+
- **Auto-formatting** — ruff, black, gofmt, rustfmt, prettier, shfmt, clang-format
|
|
18
|
+
- **Linting** — ruff, mypy, eslint, golangci-lint, clippy, clang-tidy, cppcheck
|
|
19
|
+
- **Context management** — automatic compaction when approaching the context window limit
|
|
20
|
+
- **CLI mode** — `openclose run -p "..."` for non-interactive scripting with optional JSON output
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
Requires **Python 3.12+**.
|
|
25
|
+
|
|
26
|
+
### From PyPI
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install openclose
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### With uv (recommended)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
uv pip install openclose
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### From source
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git clone https://github.com/leflakk/openclose.git
|
|
42
|
+
cd openclose
|
|
43
|
+
uv sync
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quickstart
|
|
47
|
+
|
|
48
|
+
### 1. Configure a provider
|
|
49
|
+
|
|
50
|
+
Create `~/.config/openclose/config.toml` (Linux) or `~/Library/Application Support/openclose/config.toml` (macOS):
|
|
51
|
+
|
|
52
|
+
```toml
|
|
53
|
+
[[providers]]
|
|
54
|
+
name = "default"
|
|
55
|
+
base_url = "http://localhost:8000/v1"
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
If your provider requires an API key, set it via environment variable or config:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
export OPENCLOSE_API_KEY="sk-..."
|
|
62
|
+
# or
|
|
63
|
+
export OPENAI_API_KEY="sk-..."
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Key resolution order: `OPENCLOSE_API_KEY` env var > `OPENAI_API_KEY` env var > config file `api_key` field.
|
|
67
|
+
|
|
68
|
+
### 2. Start the web UI
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
openclose serve
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Opens your browser to `http://127.0.0.1:9876`. Use `--host`, `--port`, `--no-browser`, or `--project-dir` to customize.
|
|
75
|
+
|
|
76
|
+
### 3. Or run headless
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
openclose run -p "Explain the main function in src/main.py"
|
|
80
|
+
openclose run -p "Add error handling to the parser" --agent build
|
|
81
|
+
openclose run -p "Analyze the test coverage gaps" --agent plan --json
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### 4. List sessions
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
openclose sessions
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
Configuration is loaded in priority order (highest wins):
|
|
93
|
+
|
|
94
|
+
1. Environment variables (`OPENCLOSE_*`)
|
|
95
|
+
2. Project config (`.openclose/config.toml` in your project directory)
|
|
96
|
+
3. User config (`~/.config/openclose/config.toml`)
|
|
97
|
+
4. Defaults
|
|
98
|
+
|
|
99
|
+
Example `config.toml`:
|
|
100
|
+
|
|
101
|
+
```toml
|
|
102
|
+
# Provider — any OpenAI-compatible endpoint
|
|
103
|
+
[[providers]]
|
|
104
|
+
name = "default"
|
|
105
|
+
base_url = "http://localhost:8000/v1"
|
|
106
|
+
api_key = ""
|
|
107
|
+
default_model = ""
|
|
108
|
+
|
|
109
|
+
# Session defaults
|
|
110
|
+
default_agent = "build"
|
|
111
|
+
max_context_tokens = 128000
|
|
112
|
+
compaction_threshold = 0.9
|
|
113
|
+
|
|
114
|
+
# Override built-in agent settings
|
|
115
|
+
[[agents]]
|
|
116
|
+
name = "build"
|
|
117
|
+
model = "qwen3-30b-a3b"
|
|
118
|
+
|
|
119
|
+
# Custom agent with inheritance
|
|
120
|
+
[[agents]]
|
|
121
|
+
name = "review"
|
|
122
|
+
extends = "plan"
|
|
123
|
+
description = "Code reviewer"
|
|
124
|
+
system_prompt = "You are a code reviewer. Focus on bugs, security, and clarity."
|
|
125
|
+
|
|
126
|
+
# Permission rules (last match wins)
|
|
127
|
+
[[permissions]]
|
|
128
|
+
tool = "*"
|
|
129
|
+
action = "ask"
|
|
130
|
+
|
|
131
|
+
[[permissions]]
|
|
132
|
+
tool = "read"
|
|
133
|
+
action = "allow"
|
|
134
|
+
|
|
135
|
+
[[permissions]]
|
|
136
|
+
tool = "bash"
|
|
137
|
+
path = "/tmp/*"
|
|
138
|
+
action = "deny"
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
See [docs/guide-agents-customization.md](docs/guide-agents-customization.md) for the full agent customization guide.
|
|
142
|
+
|
|
143
|
+
## Agents
|
|
144
|
+
|
|
145
|
+
| Agent | Description | Tool restrictions |
|
|
146
|
+
|-------|-------------|-------------------|
|
|
147
|
+
| `build` | Primary agent with full tool access for code writing and execution | None |
|
|
148
|
+
| `plan` | Read-only analysis agent (extends `build`) | Denied: write, edit, bash, patch |
|
|
149
|
+
|
|
150
|
+
Custom agents support:
|
|
151
|
+
|
|
152
|
+
- **Inheritance** via `extends` — create variants without repeating config
|
|
153
|
+
- **Semantic traits** — e.g. `["readonly"]` adjusts the system prompt automatically
|
|
154
|
+
- **Tool filtering** — `allowed_tools` and `denied_tools` control what the LLM can call
|
|
155
|
+
- **Custom system prompts** with `$variable` substitution
|
|
156
|
+
|
|
157
|
+
## Web UI
|
|
158
|
+
|
|
159
|
+
The web UI has a three-column layout: sessions sidebar, chat panel, and context/files sidebar.
|
|
160
|
+
|
|
161
|
+
### Slash commands
|
|
162
|
+
|
|
163
|
+
| Command | Description |
|
|
164
|
+
|---------|-------------|
|
|
165
|
+
| `/new` | Start a new session |
|
|
166
|
+
| `/sessions` | Switch to another session |
|
|
167
|
+
| `/agents` | Switch agent |
|
|
168
|
+
| `/compact` | Compress context window |
|
|
169
|
+
| `/undo` | Remove last message pair |
|
|
170
|
+
| `/export` | Export session as JSON |
|
|
171
|
+
| `/copy` | Copy last response |
|
|
172
|
+
| `/skip` | Toggle auto-approve permissions |
|
|
173
|
+
| `/help` | Show available commands |
|
|
174
|
+
|
|
175
|
+
Sessions can be forked to continue a conversation with a different agent.
|
|
176
|
+
|
|
177
|
+
## Architecture
|
|
178
|
+
|
|
179
|
+
See [ARCHITECTURE.md](ARCHITECTURE.md) for the full package structure, design decisions, and data flow diagrams.
|
|
180
|
+
|
|
181
|
+
## Contributing
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
git clone https://github.com/leflakk/openclose.git
|
|
185
|
+
cd openclose
|
|
186
|
+
uv sync
|
|
187
|
+
uv run pytest tests/
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Code quality requirements:
|
|
191
|
+
|
|
192
|
+
- **Linting** — `uv run ruff check src/ tests/`
|
|
193
|
+
- **Type checking** — `uv run mypy --strict src/ tests/`
|
|
194
|
+
- **Tests** — `uv run pytest tests/ --cov=openclose --cov-fail-under=80`
|
|
195
|
+
|
|
196
|
+
CI runs all three checks on every push and pull request via GitHub Actions.
|
|
197
|
+
|
|
198
|
+
## License
|
|
199
|
+
|
|
200
|
+
[MIT](LICENSE)
|