my-freecode
§ · Nested detail page

客户端入口与配置边界 深度解析

拆解 CLI launcher 如何接管 Claude Code/Codex/OpenCode 等客户端的 base URL 和认证变量。

§ 00 · IMPORT CHAIN

导入链与契约

▸入口契约

多客户端入口通过 ClientSpec 表达。sources/my-free-code/my_free_code/cli/launchers.py:12 到 sources/my-free-code/my_free_code/cli/launchers.py:22 注册 Claude Code、Codex、OpenCode、Cline、Pi、Hermes 等可启动客户端,sources/my-free-code/my_free_code/cli/launchers.py:24 到 sources/my-free-code/my_free_code/cli/launchers.py:39 统一注入本地 base URL、token 和 NO_PROXY。

sources/my-free-code/my_free_code/cli/launchers.py:5 - sources/my-free-code/my_free_code/cli/launchers.py:39Python
5@dataclass(frozen=True)
6class ClientSpec:
7    name: str
8    executable: str
9    base_env: str
10    args: tuple[str, ...] = ()
11
12CLIENTS = {
13    "claude": ClientSpec("Claude Code", "claude", "ANTHROPIC_BASE_URL"),
14    "codex": ClientSpec("Codex", "codex", "OPENAI_BASE_URL"),
15    "opencode": ClientSpec("OpenCode", "opencode", "OPENAI_BASE_URL"),
16    "cline": ClientSpec("Cline", "cline", "OPENAI_BASE_URL"),
17    "pi": ClientSpec("Pi", "pi", "ANTHROPIC_BASE_URL"),
18    "hermes": ClientSpec("Hermes", "hermes", "OPENAI_BASE_URL"),
19    "deepseek-harness": ClientSpec("DeepSeek Harness", "deepseek-harness", "OPENAI_BASE_URL"),
20    "grok": ClientSpec("Grok Build", "grok", "OPENAI_BASE_URL"),
21    "muse": ClientSpec("Muse Code", "muse", "OPENAI_BASE_URL"),
22}
23
24def launch(client: str, args=None):
25    spec = CLIENTS[client]
26    exe = shutil.which(spec.executable)
27    if not exe:
28        raise SystemExit(f"{spec.executable!r} was not found on PATH.")
29    env = os.environ.copy()
30    base = f"http://{settings.host}:{settings.port}"
31    env["ANTHROPIC_BASE_URL"] = base
32    env["OPENAI_BASE_URL"] = base + "/v1"
33    env["ANTHROPIC_AUTH_TOKEN"] = settings.auth_token
34    env["OPENAI_API_KEY"] = settings.auth_token
35    env["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] = "1"
36    no_proxy = env.get("NO_PROXY", "")
37    env["NO_PROXY"] = ",".join(x for x in [no_proxy, "127.0.0.1", "localhost", "::1"] if x)
38    env["no_proxy"] = env["NO_PROXY"]
39    return subprocess.call([exe, *(args or [])], env=env)
§ 01 · CORE IMPL

核心实现,逐段拆

▸Claude Code 专用 launcher

Claude 专用入口更窄,只要求系统里存在 claude 可执行文件。sources/my-free-code/my_free_code/cli/claude.py:7 到 sources/my-free-code/my_free_code/cli/claude.py:9 做 PATH 检查,sources/my-free-code/my_free_code/cli/claude.py:10 到 sources/my-free-code/my_free_code/cli/claude.py:14 设置 ANTHROPIC_BASE_URL、AUTH_TOKEN 和模型发现变量后透传 argv。

sources/my-free-code/my_free_code/cli/claude.py:1 - sources/my-free-code/my_free_code/cli/claude.py:17Python
1import os
2import shutil
3import subprocess
4from ..config import settings
5
6def main():
7    claude = shutil.which("claude")
8    if not claude:
9        raise SystemExit("Claude Code CLI was not found on PATH.")
10    env = os.environ.copy()
11    env["ANTHROPIC_BASE_URL"] = f"http://{settings.host}:{settings.port}"
12    env["ANTHROPIC_AUTH_TOKEN"] = settings.auth_token
13    env["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] = "1"
14    raise SystemExit(subprocess.call([claude, *os.sys.argv[1:]], env=env))
15
16if __name__ == "__main__":
17    main()

▸配置默认值与密钥读取

配置对象是 frozen dataclass,sources/my-free-code/my_free_code/config.py:9 到 sources/my-free-code/my_free_code/config.py:23 从环境变量读取 host、port、auth token、默认模型、fallback 和 provider 限流参数;sources/my-free-code/my_free_code/config.py:25 到 sources/my-free-code/my_free_code/config.py:26 延迟读取 provider key,让 catalog 可以只存 env var 名称。

sources/my-free-code/my_free_code/config.py:7 - sources/my-free-code/my_free_code/config.py:26Python
7@dataclass(frozen=True)
8class Settings:
9    host: str = os.getenv("HOST", "127.0.0.1")
10    port: int = int(os.getenv("PORT", "8082"))
11    auth_token: str = os.getenv("PROXY_AUTH_TOKEN", "local")
12    model: str = os.getenv("MODEL", "open_router/openrouter/free")
13    fable: str = os.getenv("MODEL_FABLE", "")
14    opus: str = os.getenv("MODEL_OPUS", "")
15    sonnet: str = os.getenv("MODEL_SONNET", "")
16    haiku: str = os.getenv("MODEL_HAIKU", "")
17    fallbacks: list[str] = field(default_factory=lambda: [
18        x.strip() for x in os.getenv("FALLBACK_MODELS", "").split(",") if x.strip()
19    ])
20    max_concurrency: int = int(os.getenv("PROVIDER_MAX_CONCURRENCY", "5"))
21    rate_limit: int = int(os.getenv("PROVIDER_RATE_LIMIT", "10"))
22    rate_window: float = float(os.getenv("PROVIDER_RATE_WINDOW", "3"))
23    http_timeout: float = float(os.getenv("HTTP_TIMEOUT", "180"))
24
25    def key(self, env_name: str | None) -> str:
26        return os.getenv(env_name, "").strip() if env_name else ""
§ 02 · KEY FLOW

关键流程

shell command -> console script -> launcher -> local env override -> existing coding CLI -> localhost gateway -> provider runtime

包入口和脚本声明要一起看:sources/my-free-code/pyproject.toml:19 到 sources/my-free-code/pyproject.toml:22 暴露 my-free-code、mfc-claude 和 mfc-client,sources/my-free-code/my_free_code/__main__.py:1 到 sources/my-free-code/my_free_code/__main__.py:7 在 module execution 时先触发 point 同步再启动 main。这里也暴露了 sources/my-free-code/my_free_code/cli/mfc.py:3 的语法风险。

§ 03 · EDGE CASES

边界 / 失败模式

  • sources/my-free-code/my_free_code/cli/mfc.py:3 是无效 Python import,mfc-client 这条入口需要修复后才能作为稳定产品路径。
  • sources/my-free-code/my_free_code/config.py:11 默认 token 是 local,适合单机开发,不适合多人共享代理。
  • sources/my-free-code/my_free_code/cli/launchers.py:36 到 sources/my-free-code/my_free_code/cli/launchers.py:38 自动设置 NO_PROXY,能避免本地代理被系统代理劫持,但也要求用户理解网络路径。
Sub-page · 2026-08-31 Source · hkqr/my-free-code ↑ my-free-code 主页面