corecoder 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,78 @@
1
+ """Content search with regex support."""
2
+
3
+ import re
4
+ from pathlib import Path
5
+ from .base import Tool
6
+
7
+ # skip these dirs to avoid noise
8
+ _SKIP_DIRS = {".git", "node_modules", "__pycache__", ".venv", "venv", ".tox", "dist", "build"}
9
+
10
+
11
+ class GrepTool(Tool):
12
+ name = "grep"
13
+ description = (
14
+ "Search file contents with regex. "
15
+ "Returns matching lines with file path and line number."
16
+ )
17
+ parameters = {
18
+ "type": "object",
19
+ "properties": {
20
+ "pattern": {
21
+ "type": "string",
22
+ "description": "Regex pattern to search for",
23
+ },
24
+ "path": {
25
+ "type": "string",
26
+ "description": "File or directory to search (default: cwd)",
27
+ },
28
+ "include": {
29
+ "type": "string",
30
+ "description": "Only search files matching this glob (e.g. '*.py')",
31
+ },
32
+ },
33
+ "required": ["pattern"],
34
+ }
35
+
36
+ def execute(self, pattern: str, path: str = ".", include: str | None = None) -> str:
37
+ try:
38
+ regex = re.compile(pattern)
39
+ except re.error as e:
40
+ return f"Invalid regex: {e}"
41
+
42
+ base = Path(path).expanduser().resolve()
43
+ if not base.exists():
44
+ return f"Error: {path} not found"
45
+
46
+ if base.is_file():
47
+ files = [base]
48
+ else:
49
+ files = self._walk(base, include)
50
+
51
+ matches = []
52
+ for fp in files:
53
+ try:
54
+ text = fp.read_text(errors="ignore")
55
+ except OSError:
56
+ continue
57
+ for lineno, line in enumerate(text.splitlines(), 1):
58
+ if regex.search(line):
59
+ matches.append(f"{fp}:{lineno}: {line.rstrip()}")
60
+ if len(matches) >= 200:
61
+ matches.append("... (200 match limit reached)")
62
+ return "\n".join(matches)
63
+
64
+ return "\n".join(matches) if matches else "No matches found."
65
+
66
+ @staticmethod
67
+ def _walk(root: Path, include: str | None) -> list[Path]:
68
+ """Walk dir tree, skipping junk dirs."""
69
+ results = []
70
+ for item in root.rglob(include or "*"):
71
+ # skip hidden/junk directories
72
+ if any(part in _SKIP_DIRS for part in item.parts):
73
+ continue
74
+ if item.is_file():
75
+ results.append(item)
76
+ if len(results) >= 5000:
77
+ break
78
+ return results
@@ -0,0 +1,53 @@
1
+ """File reading with line numbers."""
2
+
3
+ from pathlib import Path
4
+ from .base import Tool
5
+
6
+
7
+ class ReadFileTool(Tool):
8
+ name = "read_file"
9
+ description = (
10
+ "Read a file's contents with line numbers. "
11
+ "Always read a file before editing it."
12
+ )
13
+ parameters = {
14
+ "type": "object",
15
+ "properties": {
16
+ "file_path": {
17
+ "type": "string",
18
+ "description": "Path to the file",
19
+ },
20
+ "offset": {
21
+ "type": "integer",
22
+ "description": "Start line (1-based). Default 1.",
23
+ },
24
+ "limit": {
25
+ "type": "integer",
26
+ "description": "Max lines to read. Default 2000.",
27
+ },
28
+ },
29
+ "required": ["file_path"],
30
+ }
31
+
32
+ def execute(self, file_path: str, offset: int = 1, limit: int = 2000) -> str:
33
+ try:
34
+ p = Path(file_path).expanduser().resolve()
35
+ if not p.exists():
36
+ return f"Error: {file_path} not found"
37
+ if not p.is_file():
38
+ return f"Error: {file_path} is a directory, not a file"
39
+
40
+ text = p.read_text(errors="replace")
41
+ lines = text.splitlines()
42
+ total = len(lines)
43
+
44
+ start = max(0, offset - 1)
45
+ chunk = lines[start : start + limit]
46
+ numbered = [f"{start + i + 1}\t{ln}" for i, ln in enumerate(chunk)]
47
+ result = "\n".join(numbered)
48
+
49
+ if total > start + limit:
50
+ result += f"\n... ({total} lines total, showing {start+1}-{start+len(chunk)})"
51
+ return result or "(empty file)"
52
+ except Exception as e:
53
+ return f"Error: {e}"
@@ -0,0 +1,36 @@
1
+ """File creation / overwrite."""
2
+
3
+ from pathlib import Path
4
+ from .base import Tool
5
+
6
+
7
+ class WriteFileTool(Tool):
8
+ name = "write_file"
9
+ description = (
10
+ "Create a new file or completely overwrite an existing one. "
11
+ "For small edits to existing files, prefer edit_file instead."
12
+ )
13
+ parameters = {
14
+ "type": "object",
15
+ "properties": {
16
+ "file_path": {
17
+ "type": "string",
18
+ "description": "Path for the file",
19
+ },
20
+ "content": {
21
+ "type": "string",
22
+ "description": "Full file content to write",
23
+ },
24
+ },
25
+ "required": ["file_path", "content"],
26
+ }
27
+
28
+ def execute(self, file_path: str, content: str) -> str:
29
+ try:
30
+ p = Path(file_path).expanduser().resolve()
31
+ p.parent.mkdir(parents=True, exist_ok=True)
32
+ p.write_text(content)
33
+ n_lines = content.count("\n") + (1 if content and not content.endswith("\n") else 0)
34
+ return f"Wrote {n_lines} lines to {file_path}"
35
+ except Exception as e:
36
+ return f"Error: {e}"
@@ -0,0 +1,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: corecoder
3
+ Version: 0.2.0
4
+ Summary: Minimal AI coding agent (~1300 LoC) inspired by Claude Code. Works with any LLM. (formerly NanoCoder)
5
+ Project-URL: Homepage, https://github.com/he-yufeng/CoreCoder
6
+ Project-URL: Repository, https://github.com/he-yufeng/CoreCoder
7
+ Project-URL: Issues, https://github.com/he-yufeng/CoreCoder/issues
8
+ Author-email: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: agent,ai,claude-code,cli,coding,llm
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Software Development
18
+ Requires-Python: >=3.10
19
+ Requires-Dist: openai>=1.0
20
+ Requires-Dist: prompt-toolkit>=3.0
21
+ Requires-Dist: python-dotenv>=1.0
22
+ Requires-Dist: rich>=13.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.0; extra == 'dev'
25
+ Description-Content-Type: text/markdown
26
+
27
+ # CoreCoder
28
+
29
+ > Formerly **NanoCoder** — renamed to avoid confusion with [Nano-Collective/nanocoder](https://github.com/Nano-Collective/nanocoder). All links from the old repo redirect here automatically.
30
+
31
+
32
+ [![PyPI](https://img.shields.io/pypi/v/corecoder)](https://pypi.org/project/corecoder/)
33
+ [![Python](https://img.shields.io/badge/python-3.10+-blue)](https://python.org)
34
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](LICENSE)
35
+ [![Tests](https://github.com/he-yufeng/CoreCoder/actions/workflows/ci.yml/badge.svg)](https://github.com/he-yufeng/CoreCoder/actions)
36
+
37
+ [中文](README_CN.md) | [English](README.md) | [Claude Code Architecture Deep Dive (7 articles)](article/)
38
+
39
+ **512,000 lines of TypeScript → 950 lines of Python.**
40
+
41
+ I spent two days reverse-engineering the leaked Claude Code source — all half a million lines. Then I stripped it down to the load-bearing walls and rebuilt them in Python. The result: **every key architectural pattern from Claude Code, in a codebase you can read in one sitting.**
42
+
43
+ CoreCoder is not another AI coding tool. It's a **blueprint** — the [nanoGPT](https://github.com/karpathy/nanoGPT) of coding agents. Read it, fork it, build your own.
44
+
45
+ ---
46
+
47
+ ```
48
+ $ corecoder -m kimi-k2.5
49
+
50
+ You > read main.py and fix the broken import
51
+
52
+ > read_file(file_path='main.py')
53
+ > edit_file(file_path='main.py', ...)
54
+
55
+ --- a/main.py
56
+ +++ b/main.py
57
+ @@ -1 +1 @@
58
+ -from utils import halper
59
+ +from utils import helper
60
+
61
+ Fixed: halper → helper.
62
+ ```
63
+
64
+ ## What You Get
65
+
66
+ Claude Code's 512K lines distilled to 7 patterns that actually matter:
67
+
68
+ | Pattern | Claude Code | CoreCoder |
69
+ |---|---|---|
70
+ | Search-and-replace editing (unique match + diff) | FileEditTool | `tools/edit.py` — 70 lines |
71
+ | Parallel tool execution | StreamingToolExecutor (530 lines) | `agent.py` — ThreadPool |
72
+ | 3-layer context compression | HISTORY_SNIP → Microcompact → CONTEXT_COLLAPSE | `context.py` — 145 lines |
73
+ | Sub-agent with isolated context | AgentTool (1,397 lines) | `tools/agent.py` — 50 lines |
74
+ | Dangerous command blocking | BashTool (1,143 lines) | `tools/bash.py` — 95 lines |
75
+ | Session persistence | QueryEngine (1,295 lines) | `session.py` — 65 lines |
76
+ | Dynamic system prompt | prompts.ts (914 lines) | `prompt.py` — 35 lines |
77
+
78
+ Every pattern is a real, runnable implementation — not a diagram or a blog post.
79
+
80
+ ## Install
81
+
82
+ ```bash
83
+ pip install corecoder
84
+ ```
85
+
86
+ Pick your model — any OpenAI-compatible API works. You can `export` env vars or drop a `.env` file in your project root:
87
+
88
+ ```bash
89
+ # Kimi K2.5
90
+ export OPENAI_API_KEY=your-key OPENAI_BASE_URL=https://api.moonshot.ai/v1
91
+ corecoder -m kimi-k2.5
92
+
93
+ # Claude Opus 4.6 (via OpenRouter)
94
+ export OPENAI_API_KEY=your-key OPENAI_BASE_URL=https://openrouter.ai/api/v1
95
+ corecoder -m anthropic/claude-opus-4-6
96
+
97
+ # OpenAI GPT-5
98
+ export OPENAI_API_KEY=sk-...
99
+ corecoder -m gpt-5
100
+
101
+ # DeepSeek V3
102
+ export OPENAI_API_KEY=sk-... OPENAI_BASE_URL=https://api.deepseek.com
103
+ corecoder -m deepseek-chat
104
+
105
+ # Qwen 3.5
106
+ export OPENAI_API_KEY=sk-... OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
107
+ corecoder -m qwen-max
108
+
109
+ # Ollama (local)
110
+ export OPENAI_API_KEY=ollama OPENAI_BASE_URL=http://localhost:11434/v1
111
+ corecoder -m qwen3:32b
112
+
113
+ # One-shot mode
114
+ corecoder -p "add error handling to parse_config()"
115
+ ```
116
+
117
+ ## Architecture
118
+
119
+ The whole thing fits in your head:
120
+
121
+ ```
122
+ corecoder/
123
+ ├── cli.py REPL + commands 160 lines
124
+ ├── agent.py Agent loop + parallel tools 120 lines
125
+ ├── llm.py Streaming client + retry 150 lines
126
+ ├── context.py 3-layer compression 145 lines
127
+ ├── session.py Save/resume 65 lines
128
+ ├── prompt.py System prompt 35 lines
129
+ ├── config.py Env config 30 lines
130
+ └── tools/
131
+ ├── bash.py Shell + safety + cd tracking 95 lines
132
+ ├── edit.py Search-replace + diff 70 lines
133
+ ├── read.py File reading 40 lines
134
+ ├── write.py File writing 30 lines
135
+ ├── glob_tool.py File search 35 lines
136
+ ├── grep.py Content search 65 lines
137
+ └── agent.py Sub-agent spawning 50 lines
138
+ ```
139
+
140
+ ## Use as a Library
141
+
142
+ ```python
143
+ from corecoder import Agent, LLM
144
+
145
+ llm = LLM(model="kimi-k2.5", api_key="your-key", base_url="https://api.moonshot.ai/v1")
146
+ agent = Agent(llm=llm)
147
+ response = agent.chat("find all TODO comments in this project and list them")
148
+ ```
149
+
150
+ ## Add Your Own Tools (~20 lines)
151
+
152
+ ```python
153
+ from corecoder.tools.base import Tool
154
+
155
+ class HttpTool(Tool):
156
+ name = "http"
157
+ description = "Fetch a URL."
158
+ parameters = {"type": "object", "properties": {"url": {"type": "string"}}, "required": ["url"]}
159
+
160
+ def execute(self, url: str) -> str:
161
+ import urllib.request
162
+ return urllib.request.urlopen(url).read().decode()[:5000]
163
+ ```
164
+
165
+ ## Commands
166
+
167
+ ```
168
+ /model <name> Switch model mid-conversation
169
+ /compact Compress context (like Claude Code's /compact)
170
+ /tokens Token usage
171
+ /save Save session to disk
172
+ /sessions List saved sessions
173
+ /reset Clear history
174
+ quit Exit
175
+ ```
176
+
177
+ ## How It Compares
178
+
179
+ | | Claude Code | Claw-Code | Aider | CoreCoder |
180
+ |---|---|---|---|---|
181
+ | Code | 512K lines (closed) | 100K+ lines | 50K+ lines | **1,300 lines** |
182
+ | Models | Anthropic only | Multi | Multi | **Any OpenAI-compatible** |
183
+ | Readable? | No | Hard | Medium | **One afternoon** |
184
+ | Purpose | Use it | Use it | Use it | **Understand it, build yours** |
185
+
186
+ ## The Deep Dive
187
+
188
+ I wrote [7 articles](article/) breaking down Claude Code's architecture — the agent loop, tool system, context compression, streaming executor, multi-agent, and 44 hidden feature flags. If you want to understand *why* CoreCoder is designed this way, start there.
189
+
190
+ ## License
191
+
192
+ MIT. Fork it, learn from it, ship something better. A mention of this project is appreciated.
193
+
194
+ ---
195
+
196
+ Built by **[Yufeng He](https://github.com/he-yufeng)** · Agentic AI Researcher @ Moonshot AI (Kimi)
197
+
198
+ [Claude Code Source Analysis — 170K+ reads, 6000 bookmarks on Zhihu](https://zhuanlan.zhihu.com/p/1898797658343862272)
@@ -0,0 +1,23 @@
1
+ corecoder/__init__.py,sha256=IpBUpD22XoGnNJzB-2b3pabXT2Yk9itFGCXUbrRJW4U,310
2
+ corecoder/__main__.py,sha256=Ohjiuim_Pt8_jdLVyp0Fti41Gnc7QGG0UeuVVlJZYBw,39
3
+ corecoder/agent.py,sha256=QL9wg8lhqBVXzhlkoj_7pCfP3-6Zwd89s2Us1qX1o40,4497
4
+ corecoder/cli.py,sha256=lsztGNGFSxVYOmQlnKH2KusyT-5e2kOW3n0d6fC0cw4,7779
5
+ corecoder/config.py,sha256=nh_n03ADCHbHXKuETwv408Z-J4d7OHGMCb0LSgj_krg,1810
6
+ corecoder/context.py,sha256=-svtv7NPzib6qz2OsnEqutpZZoHwR2Cvzjn1gQaA4Zs,7164
7
+ corecoder/llm.py,sha256=RVfYkEIIPRkiItIE2E3hircKfVppFqnma431EawoGHs,5226
8
+ corecoder/prompt.py,sha256=Be6r8nK9_5-X-aFvkqj2Mha0dZKLKUynHbp3QGwU_-c,1383
9
+ corecoder/session.py,sha256=KDT7vb14HnVIAYccYwrsqy1o26HmU8tHG2FLKC0P8Vw,2076
10
+ corecoder/tools/__init__.py,sha256=THftHXyz5Y95gzASb150Js_wwXas_6M0NW7yRNnnmFU,524
11
+ corecoder/tools/agent.py,sha256=Unc8OiEfNgfxXVJgkN0vftehd_-ta2l1FcdbahCLY-g,1970
12
+ corecoder/tools/base.py,sha256=iGda4FgrV81BeTRty6Bfe7hoiTeJtU2i67jIP0moR18,689
13
+ corecoder/tools/bash.py,sha256=Oc2SG5NwQK_3BInrnOcTkPEk4nheHiquEoedU_Je6Og,3898
14
+ corecoder/tools/edit.py,sha256=EE2ZKxx2uoX9gUVOnG7sjE783i6rQvxg7YDXvmnfxxw,3001
15
+ corecoder/tools/glob_tool.py,sha256=zpruBoqrUSymZqkzDQ5YNzWo5SJCa-Mgl64RfjC-uuM,1428
16
+ corecoder/tools/grep.py,sha256=7IlHRPaGDQ1uP5wkU9DzUiPNq6ErEyyu0OCuxXKJ_0s,2484
17
+ corecoder/tools/read.py,sha256=fFNp5bJQl-rTEn2g-AK0fTh6-XscLIFXIYWf3y1Fe0s,1706
18
+ corecoder/tools/write.py,sha256=M8wFP5-3-a_MZQZ981O1GJ4qRMg6s45SPEJAYUrvu4s,1136
19
+ corecoder-0.2.0.dist-info/METADATA,sha256=hBCt7BZPWDwy8ifCjSbz51yy6VClXOqKVPuYyW2v7xg,7318
20
+ corecoder-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
21
+ corecoder-0.2.0.dist-info/entry_points.txt,sha256=INMm-xNoimHL7S7WDb0r2VP0Ui_EXogRw9viml0v89k,49
22
+ corecoder-0.2.0.dist-info/licenses/LICENSE,sha256=Gjpln1GILd5PHMjjXZpdojQ-DGlvmxFP_nr19R6m9xo,1066
23
+ corecoder-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ corecoder = corecoder.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yufeng He
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.