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.
- corecoder/__init__.py +10 -0
- corecoder/__main__.py +3 -0
- corecoder/agent.py +122 -0
- corecoder/cli.py +218 -0
- corecoder/config.py +55 -0
- corecoder/context.py +196 -0
- corecoder/llm.py +156 -0
- corecoder/prompt.py +33 -0
- corecoder/session.py +68 -0
- corecoder/tools/__init__.py +27 -0
- corecoder/tools/agent.py +58 -0
- corecoder/tools/base.py +27 -0
- corecoder/tools/bash.py +115 -0
- corecoder/tools/edit.py +85 -0
- corecoder/tools/glob_tool.py +47 -0
- corecoder/tools/grep.py +78 -0
- corecoder/tools/read.py +53 -0
- corecoder/tools/write.py +36 -0
- corecoder-0.2.0.dist-info/METADATA +198 -0
- corecoder-0.2.0.dist-info/RECORD +23 -0
- corecoder-0.2.0.dist-info/WHEEL +4 -0
- corecoder-0.2.0.dist-info/entry_points.txt +2 -0
- corecoder-0.2.0.dist-info/licenses/LICENSE +21 -0
corecoder/tools/grep.py
ADDED
|
@@ -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
|
corecoder/tools/read.py
ADDED
|
@@ -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}"
|
corecoder/tools/write.py
ADDED
|
@@ -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
|
+
[](https://pypi.org/project/corecoder/)
|
|
33
|
+
[](https://python.org)
|
|
34
|
+
[](LICENSE)
|
|
35
|
+
[](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,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.
|