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.
Files changed (128) hide show
  1. openclose-0.1.0/.github/workflows/ci.yml +31 -0
  2. openclose-0.1.0/.gitignore +18 -0
  3. openclose-0.1.0/ARCHITECTURE.md +184 -0
  4. openclose-0.1.0/LICENSE +21 -0
  5. openclose-0.1.0/PKG-INFO +242 -0
  6. openclose-0.1.0/README.md +200 -0
  7. openclose-0.1.0/docs/guide-agents-customization.md +307 -0
  8. openclose-0.1.0/docs/guide-personnalisation-agents.md +308 -0
  9. openclose-0.1.0/pyproject.toml +74 -0
  10. openclose-0.1.0/src/openclose/__init__.py +3 -0
  11. openclose-0.1.0/src/openclose/__main__.py +6 -0
  12. openclose-0.1.0/src/openclose/agent/__init__.py +14 -0
  13. openclose-0.1.0/src/openclose/agent/agent.py +138 -0
  14. openclose-0.1.0/src/openclose/agent/loop.py +467 -0
  15. openclose-0.1.0/src/openclose/agent/prompt.py +99 -0
  16. openclose-0.1.0/src/openclose/bus/__init__.py +5 -0
  17. openclose-0.1.0/src/openclose/bus/bus.py +61 -0
  18. openclose-0.1.0/src/openclose/cli/__init__.py +1 -0
  19. openclose-0.1.0/src/openclose/cli/cli.py +142 -0
  20. openclose-0.1.0/src/openclose/config/__init__.py +19 -0
  21. openclose-0.1.0/src/openclose/config/agents.py +173 -0
  22. openclose-0.1.0/src/openclose/config/config.py +111 -0
  23. openclose-0.1.0/src/openclose/config/paths.py +66 -0
  24. openclose-0.1.0/src/openclose/config/schema.py +96 -0
  25. openclose-0.1.0/src/openclose/file/__init__.py +8 -0
  26. openclose-0.1.0/src/openclose/file/binary.py +32 -0
  27. openclose-0.1.0/src/openclose/file/diff.py +103 -0
  28. openclose-0.1.0/src/openclose/file/ignore.py +56 -0
  29. openclose-0.1.0/src/openclose/file/watcher.py +56 -0
  30. openclose-0.1.0/src/openclose/flag.py +26 -0
  31. openclose-0.1.0/src/openclose/format/__init__.py +5 -0
  32. openclose-0.1.0/src/openclose/format/formatter.py +91 -0
  33. openclose-0.1.0/src/openclose/id.py +32 -0
  34. openclose-0.1.0/src/openclose/lint/__init__.py +5 -0
  35. openclose-0.1.0/src/openclose/lint/linter.py +113 -0
  36. openclose-0.1.0/src/openclose/log.py +23 -0
  37. openclose-0.1.0/src/openclose/patch/__init__.py +5 -0
  38. openclose-0.1.0/src/openclose/patch/patch.py +82 -0
  39. openclose-0.1.0/src/openclose/permission/__init__.py +16 -0
  40. openclose-0.1.0/src/openclose/permission/broker.py +118 -0
  41. openclose-0.1.0/src/openclose/permission/extract.py +78 -0
  42. openclose-0.1.0/src/openclose/permission/permission.py +120 -0
  43. openclose-0.1.0/src/openclose/permission/rules.py +41 -0
  44. openclose-0.1.0/src/openclose/permission/schema.py +25 -0
  45. openclose-0.1.0/src/openclose/project/__init__.py +7 -0
  46. openclose-0.1.0/src/openclose/project/project.py +42 -0
  47. openclose-0.1.0/src/openclose/project/snapshot.py +68 -0
  48. openclose-0.1.0/src/openclose/project/worktree.py +83 -0
  49. openclose-0.1.0/src/openclose/provider/__init__.py +13 -0
  50. openclose-0.1.0/src/openclose/provider/auth.py +37 -0
  51. openclose-0.1.0/src/openclose/provider/models.py +51 -0
  52. openclose-0.1.0/src/openclose/provider/provider.py +128 -0
  53. openclose-0.1.0/src/openclose/scheduler/__init__.py +5 -0
  54. openclose-0.1.0/src/openclose/scheduler/scheduler.py +71 -0
  55. openclose-0.1.0/src/openclose/server/__init__.py +1 -0
  56. openclose-0.1.0/src/openclose/server/app.py +55 -0
  57. openclose-0.1.0/src/openclose/server/routes.py +460 -0
  58. openclose-0.1.0/src/openclose/server/sse.py +34 -0
  59. openclose-0.1.0/src/openclose/server/templates/base.html +21 -0
  60. openclose-0.1.0/src/openclose/server/templates/session.html +140 -0
  61. openclose-0.1.0/src/openclose/server/templates/static/app.js +920 -0
  62. openclose-0.1.0/src/openclose/server/templates/static/style.css +369 -0
  63. openclose-0.1.0/src/openclose/session/__init__.py +22 -0
  64. openclose-0.1.0/src/openclose/session/cancel.py +49 -0
  65. openclose-0.1.0/src/openclose/session/compaction.py +217 -0
  66. openclose-0.1.0/src/openclose/session/message.py +24 -0
  67. openclose-0.1.0/src/openclose/session/processor.py +298 -0
  68. openclose-0.1.0/src/openclose/session/prompt.py +23 -0
  69. openclose-0.1.0/src/openclose/session/session.py +319 -0
  70. openclose-0.1.0/src/openclose/storage/__init__.py +19 -0
  71. openclose-0.1.0/src/openclose/storage/db.py +92 -0
  72. openclose-0.1.0/src/openclose/storage/migrations.py +66 -0
  73. openclose-0.1.0/src/openclose/storage/schema.py +71 -0
  74. openclose-0.1.0/src/openclose/tool/__init__.py +12 -0
  75. openclose-0.1.0/src/openclose/tool/registry.py +46 -0
  76. openclose-0.1.0/src/openclose/tool/tool.py +92 -0
  77. openclose-0.1.0/src/openclose/tool/tools/__init__.py +33 -0
  78. openclose-0.1.0/src/openclose/tool/tools/bash.py +95 -0
  79. openclose-0.1.0/src/openclose/tool/tools/edit.py +94 -0
  80. openclose-0.1.0/src/openclose/tool/tools/glob.py +58 -0
  81. openclose-0.1.0/src/openclose/tool/tools/grep.py +104 -0
  82. openclose-0.1.0/src/openclose/tool/tools/lint.py +81 -0
  83. openclose-0.1.0/src/openclose/tool/tools/ls.py +51 -0
  84. openclose-0.1.0/src/openclose/tool/tools/multiedit.py +123 -0
  85. openclose-0.1.0/src/openclose/tool/tools/patch.py +136 -0
  86. openclose-0.1.0/src/openclose/tool/tools/question.py +41 -0
  87. openclose-0.1.0/src/openclose/tool/tools/question_broker.py +151 -0
  88. openclose-0.1.0/src/openclose/tool/tools/read.py +78 -0
  89. openclose-0.1.0/src/openclose/tool/tools/todo.py +79 -0
  90. openclose-0.1.0/src/openclose/tool/tools/webfetch.py +66 -0
  91. openclose-0.1.0/src/openclose/tool/tools/write.py +53 -0
  92. openclose-0.1.0/src/openclose/tool/truncation.py +27 -0
  93. openclose-0.1.0/src/openclose/util/__init__.py +1 -0
  94. openclose-0.1.0/src/openclose/util/fs.py +43 -0
  95. openclose-0.1.0/src/openclose/util/git.py +38 -0
  96. openclose-0.1.0/src/openclose/util/process.py +53 -0
  97. openclose-0.1.0/tests/__init__.py +0 -0
  98. openclose-0.1.0/tests/conftest.py +30 -0
  99. openclose-0.1.0/tests/test_agent.py +298 -0
  100. openclose-0.1.0/tests/test_agent_loop.py +48 -0
  101. openclose-0.1.0/tests/test_bus.py +112 -0
  102. openclose-0.1.0/tests/test_cli.py +25 -0
  103. openclose-0.1.0/tests/test_cli_extended.py +34 -0
  104. openclose-0.1.0/tests/test_config.py +69 -0
  105. openclose-0.1.0/tests/test_edge_coverage.py +156 -0
  106. openclose-0.1.0/tests/test_file.py +101 -0
  107. openclose-0.1.0/tests/test_format.py +29 -0
  108. openclose-0.1.0/tests/test_format_extended.py +37 -0
  109. openclose-0.1.0/tests/test_id.py +33 -0
  110. openclose-0.1.0/tests/test_lint.py +27 -0
  111. openclose-0.1.0/tests/test_lint_extended.py +50 -0
  112. openclose-0.1.0/tests/test_more_coverage.py +235 -0
  113. openclose-0.1.0/tests/test_more_tools.py +243 -0
  114. openclose-0.1.0/tests/test_parallel_tool_calls.py +324 -0
  115. openclose-0.1.0/tests/test_patch.py +45 -0
  116. openclose-0.1.0/tests/test_permission_broker.py +102 -0
  117. openclose-0.1.0/tests/test_permissions.py +136 -0
  118. openclose-0.1.0/tests/test_project.py +74 -0
  119. openclose-0.1.0/tests/test_provider.py +61 -0
  120. openclose-0.1.0/tests/test_scheduler.py +36 -0
  121. openclose-0.1.0/tests/test_server.py +70 -0
  122. openclose-0.1.0/tests/test_session.py +526 -0
  123. openclose-0.1.0/tests/test_sse.py +54 -0
  124. openclose-0.1.0/tests/test_storage.py +131 -0
  125. openclose-0.1.0/tests/test_tool_sandboxing.py +95 -0
  126. openclose-0.1.0/tests/test_tools.py +330 -0
  127. openclose-0.1.0/tests/test_util.py +121 -0
  128. 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,18 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .venv/
8
+ .env
9
+ .mypy_cache/
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .claude/
13
+ .vscode/
14
+ .openclose/
15
+ *.sqlite
16
+ *.db
17
+ .coverage
18
+ htmlcov/
@@ -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 |
@@ -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.
@@ -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)