chcode 0.1.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,371 @@
1
+ """
2
+ 视觉模型配置管理 — 读取/保存 vision_model.json,配置视觉理解模型
3
+
4
+ 视觉模型通过 ModelScope OpenAI 兼容 API 调用,
5
+ 发送 base64 编码图片 + 文本 prompt,获取图像理解结果。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ from pathlib import Path
13
+
14
+ from rich.console import Console
15
+
16
+ from chcode.prompts import select, confirm, password
17
+
18
+ console = Console()
19
+
20
+ CONFIG_DIR = Path.home() / ".chat"
21
+ VISION_JSON = CONFIG_DIR / "vision_model.json"
22
+
23
+ MODELSCOPE_BASE_URL = "https://api-inference.modelscope.cn/v1"
24
+
25
+ # 视觉模型预设(默认 + 备用)
26
+ VISION_MODEL_PRESETS = [
27
+ # 默认模型
28
+ {
29
+ "model": "moonshotai/Kimi-K2.5",
30
+ "base_url": MODELSCOPE_BASE_URL,
31
+ "temperature": 1.0,
32
+ "top_p": 0.95,
33
+ "stream_usage": True,
34
+ },
35
+ # 备用模型
36
+ {
37
+ "model": "Qwen/Qwen3-VL-235B-A22B-Instruct",
38
+ "base_url": MODELSCOPE_BASE_URL,
39
+ "temperature": 1.0,
40
+ "top_p": 0.95,
41
+ "stream_usage": True,
42
+ },
43
+ {
44
+ "model": "Qwen/Qwen3-VL-30B-A3B-Instruct",
45
+ "base_url": MODELSCOPE_BASE_URL,
46
+ "temperature": 1.0,
47
+ "top_p": 0.95,
48
+ "stream_usage": True,
49
+ },
50
+ {
51
+ "model": "Qwen/Qwen3-VL-8B-Instruct",
52
+ "base_url": MODELSCOPE_BASE_URL,
53
+ "temperature": 1.0,
54
+ "top_p": 0.95,
55
+ "stream_usage": True,
56
+ },
57
+ # 非视觉多模态模型(也支持图片输入)
58
+ {
59
+ "model": "Qwen/Qwen3.5-122B-A10B",
60
+ "base_url": MODELSCOPE_BASE_URL,
61
+ "temperature": 1.0,
62
+ "top_p": 0.95,
63
+ "stream_usage": True,
64
+ },
65
+ {
66
+ "model": "Qwen/Qwen3.5-397B-A17B",
67
+ "base_url": MODELSCOPE_BASE_URL,
68
+ "temperature": 1.0,
69
+ "top_p": 0.95,
70
+ "stream_usage": True,
71
+ },
72
+ {
73
+ "model": "Qwen/Qwen3.5-35B-A3B",
74
+ "base_url": MODELSCOPE_BASE_URL,
75
+ "temperature": 1.0,
76
+ "top_p": 0.95,
77
+ "stream_usage": True,
78
+ },
79
+ {
80
+ "model": "Qwen/Qwen3.5-27B",
81
+ "base_url": MODELSCOPE_BASE_URL,
82
+ "temperature": 1.0,
83
+ "top_p": 0.95,
84
+ "stream_usage": True,
85
+ },
86
+ ]
87
+
88
+
89
+ def ensure_config_dir() -> Path:
90
+ CONFIG_DIR.mkdir(exist_ok=True)
91
+ return CONFIG_DIR
92
+
93
+
94
+ _vision_json_cache: tuple[float, dict] | None = None
95
+
96
+
97
+ def load_vision_json() -> dict:
98
+ """加载 vision_model.json,带 mtime 缓存"""
99
+ global _vision_json_cache
100
+ if not VISION_JSON.exists():
101
+ return {}
102
+ try:
103
+ mtime = VISION_JSON.stat().st_mtime
104
+ if _vision_json_cache and _vision_json_cache[0] == mtime:
105
+ return _vision_json_cache[1]
106
+ data = json.loads(VISION_JSON.read_text(encoding="utf-8"))
107
+ _vision_json_cache = (mtime, data)
108
+ return data
109
+ except Exception:
110
+ return {}
111
+
112
+
113
+ def save_vision_json(data: dict) -> None:
114
+ global _vision_json_cache
115
+ ensure_config_dir()
116
+ VISION_JSON.write_text(
117
+ json.dumps(data, indent=4, ensure_ascii=False), encoding="utf-8"
118
+ )
119
+ _vision_json_cache = None
120
+
121
+
122
+ def get_vision_default_model() -> dict | None:
123
+ """获取当前默认视觉模型配置"""
124
+ data = load_vision_json()
125
+ default = data.get("default")
126
+ if default and default.get("api_key"):
127
+ return default
128
+ return None
129
+
130
+
131
+ def get_vision_fallback_models() -> list[dict]:
132
+ """获取备用视觉模型列表"""
133
+ data = load_vision_json()
134
+ fallback = data.get("fallback", {})
135
+ return [v for k, v in fallback.items() if v.get("api_key")]
136
+
137
+
138
+ def _detect_modelscope_api_key() -> str | None:
139
+ """检测 ModelScope API Key(环境变量 → model.json)"""
140
+ # 优先从环境变量
141
+ key = os.getenv("ModelScopeToken", "")
142
+ if key:
143
+ return key
144
+
145
+ # 从已配置的 model.json 中找 ModelScope 的 key
146
+ model_json_path = CONFIG_DIR / "model.json"
147
+ if model_json_path.exists():
148
+ try:
149
+ data = json.loads(model_json_path.read_text(encoding="utf-8"))
150
+ default = data.get("default", {})
151
+ if default.get("base_url") == MODELSCOPE_BASE_URL and default.get("api_key"):
152
+ return default["api_key"]
153
+ # 检查 fallback
154
+ for cfg in data.get("fallback", {}).values():
155
+ if cfg.get("base_url") == MODELSCOPE_BASE_URL and cfg.get("api_key"):
156
+ return cfg["api_key"]
157
+ except Exception: # pragma: no cover
158
+ pass # pragma: no cover
159
+ return None
160
+
161
+
162
+ def _build_vision_config(api_key: str) -> dict:
163
+ """用预设模型 + api_key 构建完整视觉配置"""
164
+ default_cfg = dict(VISION_MODEL_PRESETS[0])
165
+ default_cfg["api_key"] = api_key
166
+
167
+ fallback = {}
168
+ for preset in VISION_MODEL_PRESETS[1:]:
169
+ cfg = dict(preset)
170
+ cfg["api_key"] = api_key
171
+ fallback[cfg["model"]] = cfg
172
+
173
+ return {"default": default_cfg, "fallback": fallback}
174
+
175
+
176
+ def auto_configure_vision() -> dict | None:
177
+ """自动配置视觉模型(静默模式,不需要用户交互)。
178
+
179
+ 从环境变量或已配置的 ModelScope key 自动生成视觉配置。
180
+ 与已有的视觉模型配置合并,不覆盖已有的默认模型。
181
+ 返回默认模型配置,失败返回 None。
182
+ """
183
+ api_key = _detect_modelscope_api_key()
184
+ if not api_key:
185
+ return None
186
+
187
+ data = load_vision_json()
188
+ existing_default = data.get("default", {})
189
+ existing_fallback = data.get("fallback", {})
190
+
191
+ # 已有相同 key 的 ModelScope 默认配置则跳过
192
+ if (
193
+ existing_default.get("base_url") == MODELSCOPE_BASE_URL
194
+ and existing_default.get("api_key") == api_key
195
+ ):
196
+ return existing_default
197
+
198
+ # 已有其他提供商的默认视觉模型 → 只把 ModelScope 模型加入 fallback
199
+ if existing_default and existing_default.get("api_key"):
200
+ # 将 ModelScope 预设模型加入 fallback(去重)
201
+ for preset in VISION_MODEL_PRESETS:
202
+ cfg = dict(preset)
203
+ cfg["api_key"] = api_key
204
+ if cfg["model"] not in existing_fallback:
205
+ existing_fallback[cfg["model"]] = cfg
206
+ data["fallback"] = existing_fallback
207
+ save_vision_json(data)
208
+ return existing_default
209
+
210
+ # 没有默认视觉模型 → ModelScope 设为默认
211
+ new_default = dict(VISION_MODEL_PRESETS[0])
212
+ new_default["api_key"] = api_key
213
+ new_fallback = {}
214
+ for preset in VISION_MODEL_PRESETS[1:]:
215
+ cfg = dict(preset)
216
+ cfg["api_key"] = api_key
217
+ new_fallback[cfg["model"]] = cfg
218
+
219
+ data["default"] = new_default
220
+ data["fallback"] = {**existing_fallback, **new_fallback}
221
+ save_vision_json(data)
222
+ return data["default"]
223
+
224
+
225
+ async def configure_vision_interactive() -> dict | None:
226
+ """交互式配置视觉模型(/vision 命令调用)"""
227
+ ensure_config_dir()
228
+
229
+ current = load_vision_json()
230
+ current_default = current.get("default", {})
231
+ has_config = bool(current_default and current_default.get("api_key"))
232
+
233
+ if has_config:
234
+ action = await select(
235
+ "视觉模型配置:",
236
+ ["查看当前配置", "重新配置", "切换模型", "返回"],
237
+ )
238
+ else:
239
+ action = await select(
240
+ "视觉模型未配置,是否现在配置?",
241
+ ["配置视觉模型", "返回"],
242
+ )
243
+
244
+ if action is None or action == "返回":
245
+ return None
246
+
247
+ if action == "查看当前配置":
248
+ _display_vision_config(current)
249
+ return None
250
+
251
+ if action == "切换模型":
252
+ return await _switch_vision_model()
253
+
254
+ # 配置
255
+ return await _configure_vision_wizard()
256
+
257
+
258
+ async def _configure_vision_wizard() -> dict | None:
259
+ """配置向导"""
260
+ # 选择 API Key 来源
261
+ env_key = os.getenv("ModelScopeToken", "")
262
+ choices = []
263
+ if env_key:
264
+ choices.append(f"使用环境变量 ModelScopeToken ({env_key[:6]}...{env_key[-4:]})")
265
+ choices.append("手动输入 API Key")
266
+
267
+ result = await select("选择 API Key 来源:", choices)
268
+ if result is None:
269
+ return None
270
+
271
+ if result.startswith("使用环境变量"):
272
+ api_key = env_key
273
+ else:
274
+ api_key = await password("输入 ModelScope API Key:")
275
+ if not api_key or not api_key.strip():
276
+ return None
277
+ api_key = api_key.strip()
278
+
279
+ # 选择默认模型
280
+ preset_names = [p["model"] for p in VISION_MODEL_PRESETS]
281
+ default_choice = await select("选择默认视觉模型:", preset_names, default=preset_names[0])
282
+ if default_choice is None:
283
+ return None
284
+
285
+ # 构建:用户选的模型作为 default,其余作为 fallback
286
+ all_presets = {p["model"]: p for p in VISION_MODEL_PRESETS}
287
+ default_preset = all_presets[default_choice]
288
+ default_cfg = dict(default_preset)
289
+ default_cfg["api_key"] = api_key
290
+
291
+ fallback = {}
292
+ for model_name, preset in all_presets.items():
293
+ if model_name == default_choice:
294
+ continue
295
+ cfg = dict(preset)
296
+ cfg["api_key"] = api_key
297
+ fallback[model_name] = cfg
298
+
299
+ config = {"default": default_cfg, "fallback": fallback}
300
+ save_vision_json(config)
301
+
302
+ console.print(f"[green]视觉模型配置完成: {default_choice} (默认)[/green]")
303
+ fallback_names = ", ".join(fallback.keys())
304
+ console.print(f"[dim]备用模型 ({len(fallback)} 个): {fallback_names}[/dim]")
305
+
306
+ return default_cfg
307
+
308
+
309
+ async def _switch_vision_model() -> dict | None:
310
+ """切换视觉模型(从 fallback 列表选择)"""
311
+ data = load_vision_json()
312
+ default = data.get("default", {})
313
+ fallback = data.get("fallback", {})
314
+
315
+ if not default: # pragma: no cover
316
+ console.print("[yellow]请先配置默认视觉模型[/yellow]") # pragma: no cover
317
+ return await _configure_vision_wizard() # pragma: no cover
318
+
319
+ if not fallback: # pragma: no cover
320
+ console.print("[yellow]没有备用视觉模型可切换[/yellow]") # pragma: no cover
321
+ return None # pragma: no cover
322
+
323
+ current_name = default.get("model", "")
324
+ choices = []
325
+ for name in fallback:
326
+ tag = " (当前默认)" if name == current_name else ""
327
+ choices.append(f"{name}{tag}")
328
+
329
+ result = await select("选择要使用的视觉模型:", choices)
330
+ if result is None: # pragma: no cover
331
+ return None # pragma: no cover
332
+
333
+ selected_name = result.replace(" (当前默认)", "")
334
+
335
+ ok = await confirm(f"确定切换到 {selected_name}?当前默认将移至备用列表")
336
+ if not ok:
337
+ return None
338
+
339
+ selected_config = fallback.pop(selected_name)
340
+ if default:
341
+ fallback[current_name] = default
342
+
343
+ data["default"] = selected_config
344
+ data["fallback"] = fallback
345
+ save_vision_json(data)
346
+ console.print(f"[green]已切换到: {selected_name}[/green]")
347
+ return selected_config
348
+
349
+
350
+ def _display_vision_config(config: dict) -> None:
351
+ """显示当前视觉模型配置"""
352
+ from rich.table import Table
353
+
354
+ default = config.get("default", {})
355
+ fallback = config.get("fallback", {})
356
+
357
+ if not default:
358
+ console.print("[yellow]未配置视觉模型[/yellow]")
359
+ return
360
+
361
+ console.print(f"[bold]默认视觉模型:[/bold] {default.get('model', '未知')}")
362
+
363
+ if fallback:
364
+ table = Table(title="备用视觉模型")
365
+ table.add_column("模型", style="cyan")
366
+ table.add_column("状态", style="green")
367
+ for name in fallback:
368
+ table.add_row(name, "✓")
369
+ console.print(table)
370
+ else:
371
+ console.print("[dim]无备用模型[/dim]")
@@ -0,0 +1,275 @@
1
+ Metadata-Version: 2.4
2
+ Name: chcode
3
+ Version: 0.1.0
4
+ Summary: Terminal-based AI coding agent with typer+rich
5
+ Project-URL: Homepage, https://github.com/ScarletMercy/chcode
6
+ Project-URL: Repository, https://github.com/ScarletMercy/chcode
7
+ Project-URL: Issues, https://github.com/ScarletMercy/chcode/issues
8
+ Author-email: Flymo Han <minimizeball@foxmail.com>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Keywords: ai,cli,coding-agent,langchain,rich,typer
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: Programming Language :: Python :: 3.13
18
+ Classifier: Topic :: Software Development :: Code Generators
19
+ Requires-Python: >=3.13
20
+ Requires-Dist: aiofiles>=25.1.0
21
+ Requires-Dist: aiosqlite>=0.22.1
22
+ Requires-Dist: bs4>=0.0.2
23
+ Requires-Dist: charset-normalizer>=3.4.0
24
+ Requires-Dist: httpx>=0.28.0
25
+ Requires-Dist: langchain-openai>=1.1.10
26
+ Requires-Dist: langchain>=1.2.10
27
+ Requires-Dist: langgraph-checkpoint-sqlite>=3.0.3
28
+ Requires-Dist: markdownify>=0.14.0
29
+ Requires-Dist: pillow>=12.2.0
30
+ Requires-Dist: prompt-toolkit>=3.0.0
31
+ Requires-Dist: psutil>=6.0.0
32
+ Requires-Dist: pyyaml>=6.0.0
33
+ Requires-Dist: questionary>=2.1.0
34
+ Requires-Dist: rich>=13.9.0
35
+ Requires-Dist: tavily>=1.1.0
36
+ Requires-Dist: typer>=0.15.0
37
+ Provides-Extra: test
38
+ Requires-Dist: pytest-asyncio>=0.25.0; extra == 'test'
39
+ Requires-Dist: pytest-cov>=6.0.0; extra == 'test'
40
+ Requires-Dist: pytest-mock>=3.14.0; extra == 'test'
41
+ Requires-Dist: pytest-timeout>=2.3.0; extra == 'test'
42
+ Requires-Dist: pytest>=8.0.0; extra == 'test'
43
+ Description-Content-Type: text/markdown
44
+
45
+ # ChCode
46
+
47
+ ```
48
+ ███████╗ ██╗ ██╗ ███████╗ ██████╗ █████╗ ████████╗
49
+ ██╔═════╝ ██║ ██║ ██╔═════╝ ██╔═══██╗ ██╔══██╗ ██╔═════╝
50
+ ██║ ████████║ ██║ ██║ ██║ ██║ ██╗ ████████╗
51
+ ██║ ██╔═══██║ ██║ ██║ ██║ ██║ ██╔╝ ██╔═════╝
52
+ ████████╗ ██║ ██║ ████████╗ ╚██████╔╝ █████╔═╝ ████████╗
53
+ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝ ╚═════╝ ╚════╝ ╚══════╝
54
+ ```
55
+
56
+ Terminal-based AI coding agent, built with LangChain + Typer + Rich.
57
+
58
+ > **Why "ChCode"?** The original prototype was a tkinter + LangChain app called **chat-agent** (chagent). When it evolved into a CLI tool, the name became **ChCode** — chat-agent, meet code.
59
+
60
+ <details>
61
+ <summary>📸 chagent — the original tkinter prototype</summary>
62
+ <img src="https://raw.githubusercontent.com/ScarletMercy/chcode/main/assets/chagent.png" alt="chagent prototype" width="600"/>
63
+ </details>
64
+
65
+ > 6000+ lines of Python, 14 built-in tools, full session persistence, git-aware workflow.
66
+
67
+ ![Python 3.13+](https://img.shields.io/badge/python-3.13%2B-blue)
68
+ ![License](https://img.shields.io/badge/license-MIT-green)
69
+
70
+ [中文文档](README_zh.md)
71
+
72
+ <img src="https://raw.githubusercontent.com/ScarletMercy/chcode/main/assets/chcode.png" alt="ChCode main interface" width="800"/>
73
+
74
+ ## Features
75
+
76
+ ### Model Management
77
+
78
+ - Compatible with **all OpenAI-compatible APIs** (OpenAI, DeepSeek, Qwen, GLM, Claude via proxy, etc.)
79
+ - Built-in quick setup for **ModelScope**, **LongCat**, and major providers
80
+ - **ModelScope**: 2000 free model calls/day
81
+ - **LongCat**: 50M+ free tokens/day minimum
82
+ - First-run wizard with **env auto-detection** (scans `OPENAI_API_KEY`, `DEEPSEEK_API_KEY`, `ZHIPU_API_KEY`, `ModelScopeToken`, etc.)
83
+ - Native **reasoning/thinking model** support — thinking tokens displayed in real time
84
+ - Create / edit / switch models at runtime
85
+ - Per-model hyperparameter tuning (temperature, top_p, top_k, max_completion_tokens, stop_sequences, etc.)
86
+ - Automatic **retry with exponential backoff** (3/10/30/60s) and fallback model switching on persistent failure
87
+
88
+ ### Vision & Multimodal
89
+
90
+ - Dedicated vision model configuration via `/vision` command (independent from main model)
91
+ - Image analysis with **automatic media encoding** and base64 embedding
92
+ - **Video support** — send videos directly to vision models for analysis (MP4, MOV, AVI, MKV, WebM)
93
+ - Automatic image resizing for oversized inputs
94
+ - Supported image formats: PNG, JPG, JPEG, GIF, BMP, WebP, TIFF
95
+
96
+ ### Session & History
97
+
98
+ - **Persistent sessions** with SQLite-backed checkpoints (LangGraph)
99
+ - Session list, switch, rename, delete
100
+ - **Context compression** — auto-summarize when approaching token limit
101
+ - Real-time **context usage display** in status bar
102
+
103
+ ### Git Integration
104
+
105
+ - Working directory **rolls back with message edits**
106
+ - Create **branches from any message** (fork)
107
+ - Edit / fork / delete history messages via `/messages`
108
+ - Checkpoint counter in status bar
109
+
110
+ ### Human-in-the-Loop
111
+
112
+ - **Common mode** — every tool call requires approval, with diff preview for edits
113
+ - **YOLO mode** — auto-approve everything
114
+ - Toggle with `Tab` key or `/mode` command
115
+
116
+ ### Work Environment Isolation
117
+
118
+ - Per-project `.chat/` directory for sessions, skills, agents
119
+ - Global `~/.chat/` for shared skills and settings
120
+ - `/workdir` to switch project root
121
+
122
+ ### Cross-Platform
123
+
124
+ - **Windows** — defaults to Git Bash, falls back to PowerShell
125
+ - **Linux / Mac** — native bash/zsh
126
+ - Persistent shell sessions with **automatic CWD tracking**
127
+
128
+ ### Rich Terminal UI
129
+
130
+ - Real-time **status bar** — context usage %, git checkpoint count, ModelScope API quota
131
+ - **Streaming output** with token-by-token rendering
132
+ - Slash command auto-completion
133
+ - Color-coded tool approval UI with **inline diff preview** for file edits
134
+
135
+ ### Observability
136
+
137
+ - **LangSmith tracing** — toggle on/off via `/langsmith` command
138
+ - Auto-disable tracing on 429 rate limit with user notification
139
+
140
+ ### Sub-Agent System
141
+
142
+ - Three built-in agent types: **Explore** (codebase search, read-only), **Plan** (architecture design), **General** (full-capability coding)
143
+ - **Parallel execution** — launch multiple agents concurrently for independent tasks
144
+ - Sub-agents run with **isolated context**, protecting the main conversation from context pollution
145
+ - **Custom agents** — define your own agent types in `.chat/agents/` with dedicated tools and instructions
146
+
147
+ ### Skill System
148
+
149
+ - Install / delete / manage skills via `/skill`
150
+ - Skills are injected into system prompt via LangChain middleware
151
+ - Supports project-level and global skill directories
152
+
153
+ ### ModelScope Rate Limit
154
+
155
+ - Real-time **API quota display** in status bar (daily limit remaining, per-model remaining)
156
+ - Auto-enabled when using ModelScope models
157
+
158
+ ## Built-in Tools (14)
159
+
160
+ | Tool | Description |
161
+ |------|-------------|
162
+ | `read` | Read file content with line numbers and offset |
163
+ | `write` | Create or overwrite files |
164
+ | `edit` | Surgical string replacement in existing files |
165
+ | `glob` | Find files by name pattern |
166
+ | `grep` | Search file contents with regex |
167
+ | `list_dir` | Browse directory structure |
168
+ | `bash` | Execute shell commands (Git Bash / PowerShell / bash) |
169
+ | `load_skill` | Dynamically load skill instructions via middleware |
170
+ | `web_fetch` | Fetch and convert URL content to markdown |
171
+ | `web_search` | Web search via [Tavily](https://tavily.com) |
172
+ | `ask_user` | Single-select, multi-select, batch questions for user interaction |
173
+ | `agent` | Launch sub-agents (explore, plan, general-purpose, custom), supports parallel execution |
174
+ | `todo_write` | Structured task tracking for complex multi-step work |
175
+ | `vision` | Analyze images and videos via ModelScope vision models |
176
+
177
+ ## Quick Start
178
+
179
+ ### Install
180
+
181
+ ```bash
182
+ # Option 1: Install with pip
183
+ pip install chcode
184
+
185
+ # Option 2: Install with uv (recommended)
186
+ uv tool install chcode
187
+
188
+ # Option 3: Install with pipx
189
+ pipx install chcode
190
+ ```
191
+
192
+ ### Run
193
+
194
+ ```bash
195
+ # Start interactive session
196
+ chcode
197
+
198
+ # Start in YOLO mode
199
+ chcode --yolo
200
+
201
+ # Model management
202
+ chcode config new # add new model
203
+ chcode config edit # edit current model
204
+ chcode config switch # switch model
205
+ ```
206
+
207
+ ### First Run
208
+
209
+ On first launch, ChCode will:
210
+
211
+ 1. Scan environment variables for known API keys
212
+ 2. Guide you through model configuration
213
+ 3. Optionally configure Tavily for web search
214
+
215
+ ## Commands
216
+
217
+ | Command | Description |
218
+ |---------|-------------|
219
+ | `/new` | Start new session |
220
+ | `/history` | Browse and switch sessions |
221
+ | `/model` | Model management (new / edit / switch) |
222
+ | `/vision` | Visual model configuration |
223
+ | `/messages` | Edit / fork / delete history messages |
224
+ | `/compress` | Compress current session |
225
+ | `/skill` | Manage skills |
226
+ | `/search` | Configure Tavily API key |
227
+ | `/workdir` | Switch working directory |
228
+ | `/mode` | Toggle Common / YOLO mode |
229
+ | `/git` | Show git status |
230
+ | `/langsmith` | Toggle LangSmith tracing |
231
+ | `/tools` | List built-in tools |
232
+ | `/quit` | Exit |
233
+
234
+ ## Keybindings
235
+
236
+ | Key | Action |
237
+ |-----|--------|
238
+ | `Enter` | Send message |
239
+ | `Ctrl+Enter` | New line |
240
+ | `Tab` | Toggle Common/YOLO mode (when input empty) |
241
+ | `Ctrl+C` | Interrupt generation |
242
+
243
+ ## Why No MCP?
244
+
245
+ ChCode intentionally does not integrate MCP (Model Context Protocol). The combination of **Skills + CLI tools** covers 95%+ of real-world coding agent scenarios. Skills provide structured, reusable instructions injected via middleware — simpler, faster, and more portable than MCP servers.
246
+
247
+ ## Architecture
248
+
249
+ ```
250
+ chcode/
251
+ ├── cli.py # Typer CLI entry
252
+ ├── chat.py # REPL main loop, slash commands, HITL
253
+ ├── agent_setup.py # Agent construction, middleware, model retry with fallback
254
+ ├── config.py # Model config, Tavily, env detection
255
+ ├── display.py # Rich rendering, streaming, status bar
256
+ ├── prompts.py # Interactive prompts (select/confirm/text)
257
+ ├── session.py # Session manager (SQLite)
258
+ ├── skill_manager.py # Skill install/delete UI
259
+ ├── agents/
260
+ │ ├── definitions.py # Agent types (explore, plan, general)
261
+ │ ├── loader.py # Load custom agents from .chat/agents/
262
+ │ └── runner.py # Sub-agent execution with middleware
263
+ └── utils/
264
+ ├── tools.py # 14 built-in tools
265
+ ├── shell/ # Shell abstraction (Bash/PowerShell providers)
266
+ ├── enhanced_chat_openai.py # Extended ChatOpenAI with reasoning support
267
+ ├── git_manager.py # Git checkpoint management
268
+ ├── skill_loader.py # Skill discovery and loading
269
+ ├── modelscope_ratelimit.py # ModelScope API rate limit monitor
270
+ └── tool_result_pipeline.py # Output truncation and budget enforcement
271
+ ```
272
+
273
+ ## License
274
+
275
+ MIT
@@ -0,0 +1,36 @@
1
+ chcode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ chcode/__main__.py,sha256=Ruf2NjnBOyZwKKlwZYYbzu14axJuL_HHbeQEM3ZSFLg,95
3
+ chcode/agent_setup.py,sha256=5jBoK1boiZjMbRyvcnF-2xReJdjUzWbfXJV8Hdd5NWw,14275
4
+ chcode/chat.py,sha256=5o2J2-4Ga_JenLhwLd23IZ5Zv0q8USS0k3H6QAo-PQk,65830
5
+ chcode/cli.py,sha256=c-UpcoLDxTuJVLLwk4BYTYTuFTiIRFw6HJM0eCtGCSQ,3646
6
+ chcode/config.py,sha256=Eu1owyZkMKPc5wgix9RfXo_RuOelyepZoq9miS36kW8,18893
7
+ chcode/display.py,sha256=4aaNTsYzO0h9Hqlrl3jT7s9pfd32y8fKcvCoHqiEaQs,9309
8
+ chcode/prompts.py,sha256=Pc5lez4UBp_j_d2n_c92mtDm_LVQTOOJk2VTsy31sOM,19167
9
+ chcode/session.py,sha256=ROjLEug4abKJEzHc_bNQeKt9l3gPsKpxQfzatLY7MIg,5254
10
+ chcode/skill_manager.py,sha256=coxZPil_yk9YpAVO3wn1-3l9of5xpWRWFjqS_DtD0jo,4742
11
+ chcode/vision_config.py,sha256=La-kDmPwNAwDzLE48xetce4hLw1NOFpo6ZkLaO9AdrM,11510
12
+ chcode/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
+ chcode/agents/definitions.py,sha256=VD4sBChDF174bvK6_d5qgQY7DbPHJVbLRGoGdc0JNJY,7249
14
+ chcode/agents/loader.py,sha256=jAQfZ__6yO_BkNUCjrcQoJwvnoKAr5WdDcrpyfx0JyE,2693
15
+ chcode/agents/runner.py,sha256=i9QHGbqoc2xOF-1UZmXdo-K4VUCiUORmf_NcdUj_aNs,4939
16
+ chcode/utils/__init__.py,sha256=YrOe3vTZByoRkxMcff2sw3-XPcNtMsDwyZFyR_FkbIw,85
17
+ chcode/utils/enhanced_chat_openai.py,sha256=0eYqgt-UKGN8rhmH0UkLwAW04s2jDM2D6A6liR-tZdk,13965
18
+ chcode/utils/git_checker.py,sha256=nrH3nwTbPB6dMmtCu8zygi-7MvXtE983_tLGnrKWymE,1167
19
+ chcode/utils/git_manager.py,sha256=1EKn9ovUAmjMBxuKfFIFo64YvrgIV6hLbeUGrDXyPRg,9786
20
+ chcode/utils/modelscope_ratelimit.py,sha256=jVNsUD9JRsUr44yt-dfqOAE7Prh58wHwtNp9OAxwsm8,2218
21
+ chcode/utils/multimodal.py,sha256=2PYmUkheD2Dd6L6SGLFfHZdbK3sMmhNkRQBhvg8jY20,8962
22
+ chcode/utils/skill_loader.py,sha256=XL0njUDNSWkAax6FAR4Rhq-CZEQ8YyBNb_q_Fr2rq3I,17689
23
+ chcode/utils/text_utils.py,sha256=Qq4hfTBKKK9hN4VcNGtrx3eTOh6RAL47H-AW1PdN-9s,566
24
+ chcode/utils/tool_result_pipeline.py,sha256=NB5HpI9XUXRhq2Nxd17I3vIq1Vaihcjll7w_02yPowE,7222
25
+ chcode/utils/tools.py,sha256=WGQE-EXbfAgnYMboS6HxWoYgUxNNla-HQSfYLgn2n4Q,53844
26
+ chcode/utils/shell/__init__.py,sha256=sgJnNzMhnwYF3o-0UEnYF_foeyqNeVHBlqkkrMYJvug,564
27
+ chcode/utils/shell/output.py,sha256=G12tftYwM1ZZ0Wi6IwoeGUREuwwEskJNSxSy947V9cU,1807
28
+ chcode/utils/shell/provider.py,sha256=GBscA2IkKM64r6ZFWQWF7MBHyPR6_xq8GwZosYBgS7U,4011
29
+ chcode/utils/shell/result.py,sha256=Zwx_teqeyYrg8RDXIIPgWntzfyyVprGurhI8VWMLxqA,305
30
+ chcode/utils/shell/semantics.py,sha256=ewO-gC_sNuR0HtIPYnGlBo1Iz2oEKhezZ5eLIuk8tHg,1937
31
+ chcode/utils/shell/session.py,sha256=0wCr5gZ40_2C-UllbG-DNCI4NLkO7bhxsqLn9JjjMes,5083
32
+ chcode-0.1.0.dist-info/METADATA,sha256=YuJ7Yg0au6_CllMFasbv5K35grqA1Xz45pyo-Lvs9Sc,10998
33
+ chcode-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
34
+ chcode-0.1.0.dist-info/entry_points.txt,sha256=IfRbpalQYkqvoETi6MCWmx2s7lPWX21L7h_vqIYqCjI,42
35
+ chcode-0.1.0.dist-info/licenses/LICENSE,sha256=nHXAAMmgstqUKPHGO353VN94SKEhxr64CvZOVTV2PII,1066
36
+ chcode-0.1.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
+ chcode = chcode.cli:app