ccoding 0.3.0__tar.gz → 0.5.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.
- {ccoding-0.3.0 → ccoding-0.5.0}/CLAUDE.md +3 -3
- {ccoding-0.3.0 → ccoding-0.5.0}/PKG-INFO +3 -2
- {ccoding-0.3.0 → ccoding-0.5.0}/README.md +2 -1
- {ccoding-0.3.0 → ccoding-0.5.0}/pyproject.toml +1 -1
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/__init__.py +1 -1
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/api.py +12 -4
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/config.py +46 -18
- ccoding-0.5.0/src/ccode/env_schema.json +2214 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/ui.py +55 -27
- {ccoding-0.3.0 → ccoding-0.5.0}/uv.lock +1 -1
- ccoding-0.3.0/src/ccode/env_schema.json +0 -979
- {ccoding-0.3.0 → ccoding-0.5.0}/.github/workflows/publish.yml +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/.gitignore +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/LICENSE +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/screenshot.png +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/screenshot_mobile1.jpeg +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/screenshot_mobile2.jpeg +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/__main__.py +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/cli.py +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/logo.py +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/remote.py +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/remote_cli.py +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/remote_client.py +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/remote_common.py +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/remote_server.py +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/web/__init__.py +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/web/app.js +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/web/assets/addon-fit.js +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/web/assets/xterm-LICENSE +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/web/assets/xterm-addon-fit-LICENSE +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/web/assets/xterm.css +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/web/assets/xterm.js +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/web/index.html +0 -0
- {ccoding-0.3.0 → ccoding-0.5.0}/src/ccode/web/style.css +0 -0
|
@@ -24,14 +24,14 @@ uv build # 构建 wheel / sdist
|
|
|
24
24
|
- **`config.py`** — 所有持久化逻辑。配置文件路径 `~/.ccode/config.json`。`toggles` 字段存储任意环境变量键值对(`dict[str, str]`)。提供 `load_env_schema()` / `env_schema_keys()` / `env_schema_description()` / `env_schema_enum()` 四个函数,从 `env_schema.json` 读取已知变量的元数据。
|
|
25
25
|
- **`api.py`** — 无状态函数层:HTTP 拉取 `/v1/models`、构建启动环境变量 `build_env()`、调用 `subprocess.run(["claude", ...])` 启动 claude。
|
|
26
26
|
- **`logo.py`** — LOGOS 数据(3 种 ASCII 字体)及 4 种渲染风格函数(wave / pulse / glitch / rain),接收独立参数,不依赖 `CursesApp`。
|
|
27
|
-
- **`ui.py`** — `CursesApp` 类,持有全部 UI 状态。配置界面的 field 列表由 `_config_fields()` 动态构建(`base_url`、`api_key`、每个 toggle key、`__add__` 新增行),支持运行时增删 toggle。新增 toggle 时 key 输入框实时展示候选列表,Tab 键补全,候选来源为 `env_schema.json`
|
|
27
|
+
- **`ui.py`** — `CursesApp` 类,持有全部 UI 状态。配置界面的 field 列表由 `_config_fields()` 动态构建(`base_url`、`api_key`、每个 toggle key、`__add__` 新增行),支持运行时增删 toggle。新增 toggle 时 key 输入框实时展示候选列表,Tab 键补全,候选来源为 `env_schema.json` 中的已知变量。
|
|
28
28
|
- **`cli.py`** — 入口,`main(argv)` 构造 `CursesApp` 并调用 `curses.wrapper`。
|
|
29
|
-
- **`env_schema.json`** —
|
|
29
|
+
- **`env_schema.json`** — 从官方文档 `https://code.claude.com/docs/en/env-vars` 同步的 Claude Code 环境变量 JSON Schema,含 `description` 和 `enum` 字段,供 UI 补全和提示使用。更新 claude code 版本后重新抓取该页面的变量表并同步此文件的 `properties` 节(仅保留文档未收录的 `CLAUDE_CODE_AUTO_MODE_MODEL` 等本地条目)。
|
|
30
30
|
|
|
31
31
|
## 关键数据流
|
|
32
32
|
|
|
33
33
|
1. `load_config()` 读取 `~/.ccode/config.json`,toggles 值统一为字符串。
|
|
34
|
-
2. 主界面 → `fetch_models()` 拉取可用模型 → 用户选择 opus/sonnet/haiku 的 owned_by 和 model_id
|
|
34
|
+
2. 主界面 → `fetch_models()` 拉取可用模型 → 用户选择 opus/sonnet/haiku/auto 的 owned_by 和 model_id(auto 可选项,对应 `CLAUDE_CODE_AUTO_MODE_MODEL`)。
|
|
35
35
|
3. 启动时 `build_env()` 将 `base_url`、`api_key`、model id、toggles 全部注入环境变量,执行 `claude`。
|
|
36
36
|
4. 配置界面 `ESC` 自动保存后刷新模型列表。
|
|
37
37
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: ccoding
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Curses-based Claude Code launcher
|
|
5
5
|
Project-URL: Homepage, https://github.com/8DE4732A/ccode
|
|
6
6
|
Project-URL: Repository, https://github.com/8DE4732A/ccode
|
|
@@ -194,7 +194,8 @@ uv sync --extra remote # 同时安装 client/server 依赖
|
|
|
194
194
|
"models": {
|
|
195
195
|
"opus": {"owned_by": null, "id": null},
|
|
196
196
|
"sonnet": {"owned_by": null, "id": null},
|
|
197
|
-
"haiku": {"owned_by": null, "id": null}
|
|
197
|
+
"haiku": {"owned_by": null, "id": null},
|
|
198
|
+
"auto": {"owned_by": null, "id": null}
|
|
198
199
|
},
|
|
199
200
|
"toggles": {
|
|
200
201
|
"CLAUDE_CODE_ENABLE_TELEMETRY": "0",
|
|
@@ -143,7 +143,8 @@ uv sync --extra remote # 同时安装 client/server 依赖
|
|
|
143
143
|
"models": {
|
|
144
144
|
"opus": {"owned_by": null, "id": null},
|
|
145
145
|
"sonnet": {"owned_by": null, "id": null},
|
|
146
|
-
"haiku": {"owned_by": null, "id": null}
|
|
146
|
+
"haiku": {"owned_by": null, "id": null},
|
|
147
|
+
"auto": {"owned_by": null, "id": null}
|
|
147
148
|
},
|
|
148
149
|
"toggles": {
|
|
149
150
|
"CLAUDE_CODE_ENABLE_TELEMETRY": "0",
|
|
@@ -7,7 +7,10 @@ import urllib.error
|
|
|
7
7
|
import urllib.request
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
from .config import MODEL_KEYS, mask_secret
|
|
10
|
+
from .config import MODEL_ENV_VARS, MODEL_KEYS, REQUIRED_MODEL_KEYS, mask_secret
|
|
11
|
+
|
|
12
|
+
# Claude Code 识别小写的 1M 上下文变体后缀(如 claude-sonnet-4-5[1m])
|
|
13
|
+
MODEL_1M_SUFFIX = "[1m]"
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
def _truncate_body(text: str, max_len: int = 200) -> str:
|
|
@@ -69,7 +72,9 @@ def validate_models(config: dict[str, Any], models: list[dict[str, str]]) -> boo
|
|
|
69
72
|
model_id = entry.get("id")
|
|
70
73
|
if not owned_by or not model_id or (owned_by, model_id) not in valid:
|
|
71
74
|
if entry.get("owned_by") is not None or entry.get("id") is not None:
|
|
72
|
-
|
|
75
|
+
# 只清空选择字段,保留 1m 等模型级偏好和未来新增字段
|
|
76
|
+
entry["owned_by"] = None
|
|
77
|
+
entry["id"] = None
|
|
73
78
|
changed = True
|
|
74
79
|
return changed
|
|
75
80
|
|
|
@@ -107,7 +112,10 @@ def build_env(config: dict[str, Any], masked: bool = False) -> dict[str, str]:
|
|
|
107
112
|
entry = config["models"].get(key, {})
|
|
108
113
|
model_id = entry.get("id")
|
|
109
114
|
if model_id:
|
|
110
|
-
|
|
115
|
+
# 用户开启 1M 开关时追加官方小写后缀;模型 id 本身已带后缀则不重复追加。
|
|
116
|
+
if entry.get("1m") and not model_id.lower().endswith(MODEL_1M_SUFFIX):
|
|
117
|
+
model_id = f"{model_id}{MODEL_1M_SUFFIX}"
|
|
118
|
+
env[MODEL_ENV_VARS[key]] = model_id
|
|
111
119
|
for key, value in config.get("toggles", {}).items():
|
|
112
120
|
env[key] = str(value)
|
|
113
121
|
return env
|
|
@@ -118,7 +126,7 @@ def validate_launch_requirements(config: dict[str, Any]) -> str | None:
|
|
|
118
126
|
api_key = config.get("api_key", "").strip()
|
|
119
127
|
if not base_url or not api_key:
|
|
120
128
|
return "Base URL and API key are required."
|
|
121
|
-
for key in
|
|
129
|
+
for key in REQUIRED_MODEL_KEYS:
|
|
122
130
|
entry = config["models"].get(key, {})
|
|
123
131
|
if not entry.get("owned_by") or not entry.get("id"):
|
|
124
132
|
return "OPUS, SONNET, and HAIKU selections are required."
|
|
@@ -11,12 +11,20 @@ from typing import Any
|
|
|
11
11
|
CONFIG_DIR = Path.home() / ".ccode"
|
|
12
12
|
CONFIG_PATH = CONFIG_DIR / "config.json"
|
|
13
13
|
DEFAULT_BASE_URL = "http://127.0.0.1:8317"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"
|
|
18
|
-
"
|
|
14
|
+
# 模型 key 及其对应的环境变量,dict 顺序即主界面展示顺序。
|
|
15
|
+
# auto 对应客户端 auto mode 分类器模型,其余沿用 ANTHROPIC_DEFAULT_<KEY>_MODEL。
|
|
16
|
+
MODEL_ENV_VARS = {
|
|
17
|
+
"opus": "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
18
|
+
"sonnet": "ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
19
|
+
"haiku": "ANTHROPIC_DEFAULT_HAIKU_MODEL",
|
|
20
|
+
"auto": "CLAUDE_CODE_AUTO_MODE_MODEL",
|
|
19
21
|
}
|
|
22
|
+
MODEL_KEYS = tuple(MODEL_ENV_VARS)
|
|
23
|
+
MODEL_LABELS = {key: key.upper() for key in MODEL_KEYS}
|
|
24
|
+
|
|
25
|
+
# auto 为可选项,其余模型启动时必选
|
|
26
|
+
OPTIONAL_MODEL_KEYS = ("auto",)
|
|
27
|
+
REQUIRED_MODEL_KEYS = tuple(key for key in MODEL_KEYS if key not in OPTIONAL_MODEL_KEYS)
|
|
20
28
|
|
|
21
29
|
# toggles 的默认值:值统一用字符串存储
|
|
22
30
|
DEFAULT_TOGGLES: dict[str, str] = {
|
|
@@ -81,15 +89,20 @@ def env_schema_enum(key: str) -> list[str]:
|
|
|
81
89
|
return load_env_schema().get(key, {}).get("enum", [])
|
|
82
90
|
|
|
83
91
|
|
|
92
|
+
def model_entry(
|
|
93
|
+
owner: str | None = None,
|
|
94
|
+
model_id: str | None = None,
|
|
95
|
+
one_m: bool = False,
|
|
96
|
+
) -> dict[str, Any]:
|
|
97
|
+
"""模型条目的唯一构造入口,新增字段时只需改这里。"""
|
|
98
|
+
return {"owned_by": owner, "id": model_id, "1m": one_m}
|
|
99
|
+
|
|
100
|
+
|
|
84
101
|
def default_config() -> dict[str, Any]:
|
|
85
102
|
return {
|
|
86
103
|
"base_url": DEFAULT_BASE_URL,
|
|
87
104
|
"api_key": "",
|
|
88
|
-
"models": {
|
|
89
|
-
"opus": {"owned_by": None, "id": None},
|
|
90
|
-
"sonnet": {"owned_by": None, "id": None},
|
|
91
|
-
"haiku": {"owned_by": None, "id": None},
|
|
92
|
-
},
|
|
105
|
+
"models": {key: model_entry() for key in MODEL_KEYS},
|
|
93
106
|
"toggles": DEFAULT_TOGGLES.copy(),
|
|
94
107
|
"skip_permissions": False,
|
|
95
108
|
"remote": copy.deepcopy(DEFAULT_REMOTE),
|
|
@@ -204,10 +217,12 @@ def load_config() -> dict[str, Any]:
|
|
|
204
217
|
if isinstance(entry, dict):
|
|
205
218
|
owned_by = entry.get("owned_by")
|
|
206
219
|
model_id = entry.get("id")
|
|
207
|
-
|
|
208
|
-
|
|
220
|
+
one_m = entry.get("1m")
|
|
221
|
+
config["models"][key] = model_entry(
|
|
222
|
+
owned_by if isinstance(owned_by, str) else None,
|
|
223
|
+
model_id if isinstance(model_id, str) else None,
|
|
224
|
+
one_m if isinstance(one_m, bool) else False,
|
|
209
225
|
)
|
|
210
|
-
config["models"][key]["id"] = model_id if isinstance(model_id, str) else None
|
|
211
226
|
|
|
212
227
|
toggles = data.get("toggles")
|
|
213
228
|
if isinstance(toggles, dict):
|
|
@@ -247,16 +262,29 @@ def mask_secret(value: str) -> str:
|
|
|
247
262
|
return f"{value[:4]}...{value[-4:]}"
|
|
248
263
|
|
|
249
264
|
|
|
265
|
+
def _model_entry(
|
|
266
|
+
config: dict[str, Any],
|
|
267
|
+
key: str,
|
|
268
|
+
owner: str | None,
|
|
269
|
+
model_id: str | None,
|
|
270
|
+
) -> dict[str, Any]:
|
|
271
|
+
"""重建模型条目,保留已有的 1M 上下文开关状态。"""
|
|
272
|
+
existing = config["models"].get(key, {})
|
|
273
|
+
return model_entry(owner if owner else None, model_id, bool(existing.get("1m")))
|
|
274
|
+
|
|
275
|
+
|
|
250
276
|
def update_model_owner(config: dict[str, Any], key: str, owner: str | None) -> None:
|
|
251
|
-
config["models"][key] =
|
|
277
|
+
config["models"][key] = _model_entry(config, key, owner, None)
|
|
252
278
|
save_config(config)
|
|
253
279
|
|
|
254
280
|
|
|
255
281
|
def update_model_id(
|
|
256
282
|
config: dict[str, Any], key: str, owner: str | None, model_id: str | None
|
|
257
283
|
) -> None:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
284
|
+
config["models"][key] = _model_entry(config, key, owner, model_id)
|
|
285
|
+
save_config(config)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def update_model_1m(config: dict[str, Any], key: str, enabled: bool) -> None:
|
|
289
|
+
config["models"][key]["1m"] = bool(enabled)
|
|
262
290
|
save_config(config)
|