coderouter-cli 2.7.3__py3-none-any.whl → 2.7.5__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.
coderouter/cli.py CHANGED
@@ -10,6 +10,36 @@ import uvicorn
10
10
 
11
11
  from coderouter import __version__
12
12
 
13
+ # Bind addresses that keep the server loopback-only. Anything else means the
14
+ # operator is deliberately exposing CodeRouter beyond this machine, at which
15
+ # point the v2.7.0 Host-header validation (DNS-rebinding guard) will 403 every
16
+ # request whose Host is not allow-listed — a combination that has confused
17
+ # real users ("worked on 2.6, LAN access broken on 2.7").
18
+ _LOOPBACK_BIND_HOSTS = frozenset({"127.0.0.1", "localhost", "::1", "[::1]"})
19
+
20
+
21
+ def _external_bind_warning(host: str, allowed_hosts_env: str | None) -> str | None:
22
+ """Return a startup warning when binding beyond loopback needs config.
23
+
24
+ Fires only for the confusing combination: non-loopback bind (the operator
25
+ wants LAN/external access) while ``CODEROUTER_ALLOWED_HOSTS`` is unset
26
+ (so every non-loopback Host header will be rejected with 403). Returns
27
+ the warning text, or None when the configuration is coherent.
28
+ """
29
+ if host in _LOOPBACK_BIND_HOSTS:
30
+ return None
31
+ if allowed_hosts_env and allowed_hosts_env.strip():
32
+ return None
33
+ return (
34
+ f"serve: binding on {host!r} but CODEROUTER_ALLOWED_HOSTS is not set. "
35
+ "Requests whose Host header is not loopback will be rejected with 403 "
36
+ "(v2.7.0 DNS-rebinding guard). To allow LAN access, set "
37
+ "CODEROUTER_ALLOWED_HOSTS=<THIS machine's address as it appears in the "
38
+ "client's URL bar, e.g. 192.168.x.x — NOT the client's own IP> "
39
+ "(comma-separated, no port). Note the chat endpoints have no "
40
+ "authentication — do not expose CodeRouter directly to the internet."
41
+ )
42
+
13
43
 
14
44
  def _build_parser() -> argparse.ArgumentParser:
15
45
  parser = argparse.ArgumentParser(
@@ -366,6 +396,15 @@ def main(argv: list[str] | None = None) -> int:
366
396
  if stripped:
367
397
  os.environ["CODEROUTER_MODE"] = stripped
368
398
 
399
+ # v2.7.5: warn about the "bound beyond loopback but Host validation
400
+ # will reject everything" trap BEFORE uvicorn takes over the console,
401
+ # so the hint is the first thing an operator sees.
402
+ warning = _external_bind_warning(
403
+ args.host, os.environ.get("CODEROUTER_ALLOWED_HOSTS")
404
+ )
405
+ if warning:
406
+ print(f"WARNING: {warning}", file=sys.stderr)
407
+
369
408
  uvicorn.run(
370
409
  "coderouter.ingress.app:create_app",
371
410
  factory=True,
@@ -655,6 +655,28 @@ async def api_backends(request: Request) -> dict[str, Any]:
655
655
  return {"backends": result}
656
656
 
657
657
 
658
+ def _launcher_provider_config(backend: str, port: int) -> Any:
659
+ """Build the provider entry for a launcher-started backend.
660
+
661
+ ``model`` is left EMPTY on purpose: llama-server / vllm / mlx decide
662
+ which model is actually loaded, and the empty-model /v1/models
663
+ passthrough then surfaces the upstream's real model id to benchmark
664
+ clients — swap the GGUF, no config edit needed.
665
+
666
+ Name embeds backend + port (``launcher-llamacpp-8085``) so restarting
667
+ on the same port REPLACES the entry instead of piling up duplicates.
668
+ """
669
+ from coderouter.config.schemas import ProviderConfig
670
+
671
+ safe_backend = backend.replace(".", "")
672
+ return ProviderConfig(
673
+ name=f"launcher-{safe_backend}-{port}",
674
+ base_url=f"http://localhost:{port}/v1",
675
+ model="",
676
+ timeout_s=120.0,
677
+ )
678
+
679
+
658
680
  @router.post("/api/launcher/start")
659
681
  async def api_start(req: StartRequest, request: Request) -> dict[str, Any]:
660
682
  """Start a new backend process."""
@@ -717,7 +739,35 @@ async def api_start(req: StartRequest, request: Request) -> dict[str, Any]:
717
739
  _background_tasks.add(_task)
718
740
  _task.add_done_callback(_background_tasks.discard)
719
741
 
720
- return {"id": proc_id, "pid": p.pid, "command": cmd}
742
+ # Provider auto-sync: register the just-started backend as a routable
743
+ # provider so requests can reach it without hand-editing providers.yaml
744
+ # (in-memory only — see FallbackEngine.register_provider docstring).
745
+ # Sync failure must never fail the start itself.
746
+ provider_sync: dict[str, Any] | None = None
747
+ engine = getattr(request.app.state, "engine", None)
748
+ if engine is not None and hasattr(engine, "register_provider"):
749
+ try:
750
+ provider_sync = engine.register_provider(
751
+ _launcher_provider_config(req.backend, req.port)
752
+ )
753
+ proc.log_tail.append(
754
+ "[launcher] provider sync: "
755
+ f"{provider_sync['provider']} -> profile "
756
+ f"'{provider_sync['profile']}' (in-memory)"
757
+ )
758
+ except Exception as exc:
759
+ logger.warning(
760
+ "launcher provider sync failed",
761
+ extra={"backend": req.backend, "port": req.port, "error": str(exc)},
762
+ )
763
+ proc.log_tail.append(f"[launcher] provider sync failed: {exc}")
764
+
765
+ return {
766
+ "id": proc_id,
767
+ "pid": p.pid,
768
+ "command": cmd,
769
+ "provider_sync": provider_sync,
770
+ }
721
771
 
722
772
 
723
773
  @router.post("/api/launcher/stop/{proc_id}")
@@ -78,22 +78,99 @@ def _resolve_stream_timeout_s(engine: FallbackEngine, profile: str | None) -> fl
78
78
  return max(_STREAM_TIMEOUT_MIN_S, float(per_call) * _STREAM_TIMEOUT_MULTIPLIER)
79
79
 
80
80
 
81
+ # --- /v1/models upstream passthrough -------------------------------------
82
+ #
83
+ # Providers with an *empty* ``model`` field are passthrough providers: the
84
+ # upstream (llama-server, LM Studio, ...) decides which model is loaded and
85
+ # CodeRouter never sends a model name. For those providers the historic
86
+ # behaviour — returning the provider *name* — makes external benchmarks
87
+ # indistinguishable across loaded GGUFs (the launcher_gui setup always
88
+ # reported ``llama-cpp-local`` regardless of the selected model). We now ask
89
+ # the upstream's own ``/models`` endpoint for its real model id(s) and
90
+ # surface those instead.
91
+ #
92
+ # Guards: only for ``kind == "openai_compat"`` with ``model == ""``; a short
93
+ # per-upstream TTL cache keeps repeated SDK probes cheap; ANY failure (refused
94
+ # connection, timeout, unexpected shape) falls back to the historic
95
+ # provider-name entry, so this is strictly additive. Providers with a
96
+ # configured model keep the exact previous behaviour.
97
+
98
+ _UPSTREAM_MODELS_TTL_S = 30.0
99
+ _UPSTREAM_MODELS_TIMEOUT_S = 2.0
100
+ # base_url -> (expires_monotonic, ids)
101
+ _upstream_models_cache: dict[str, tuple[float, list[str]]] = {}
102
+
103
+
104
+ async def _fetch_upstream_model_ids(base_url: str) -> list[str]:
105
+ """GET {base_url}/models and return the upstream model ids.
106
+
107
+ Raises on any transport / shape problem — the caller treats every
108
+ exception as "fall back to the provider-name entry".
109
+ """
110
+ import httpx # local import keeps module import time flat
111
+
112
+ url = base_url.rstrip("/") + "/models"
113
+ async with httpx.AsyncClient(timeout=_UPSTREAM_MODELS_TIMEOUT_S) as client:
114
+ resp = await client.get(url)
115
+ resp.raise_for_status()
116
+ payload = resp.json()
117
+ ids = [
118
+ str(item["id"])
119
+ for item in payload.get("data", [])
120
+ if isinstance(item, dict) and item.get("id")
121
+ ]
122
+ if not ids:
123
+ raise ValueError(f"upstream {url} returned no model ids")
124
+ return ids
125
+
126
+
127
+ async def _upstream_model_ids_cached(base_url: str) -> list[str]:
128
+ """TTL-cached wrapper around :func:`_fetch_upstream_model_ids`."""
129
+ now = time.monotonic()
130
+ hit = _upstream_models_cache.get(base_url)
131
+ if hit is not None and hit[0] > now:
132
+ return hit[1]
133
+ ids = await _fetch_upstream_model_ids(base_url)
134
+ _upstream_models_cache[base_url] = (now + _UPSTREAM_MODELS_TTL_S, ids)
135
+ return ids
136
+
137
+
81
138
  @router.get("/models")
82
139
  async def list_models(request: Request) -> dict[str, object]:
83
- """Minimal /v1/models so OpenAI SDKs that probe it don't choke."""
140
+ """/v1/models: provider names, with upstream passthrough for empty-model
141
+ providers (see the passthrough note above)."""
84
142
  config = request.app.state.config
85
- return {
86
- "object": "list",
87
- "data": [
143
+ created = int(time.time())
144
+ data: list[dict[str, object]] = []
145
+ for p in config.providers:
146
+ if p.model == "" and p.kind == "openai_compat":
147
+ try:
148
+ ids = await _upstream_model_ids_cached(str(p.base_url))
149
+ except Exception: # any failure means "no passthrough"
150
+ logger.debug(
151
+ "models passthrough failed; falling back to provider name",
152
+ extra={"provider": p.name},
153
+ )
154
+ else:
155
+ data.extend(
156
+ {
157
+ "id": model_id,
158
+ "object": "model",
159
+ "created": created,
160
+ "owned_by": f"coderouter/{p.name}",
161
+ }
162
+ for model_id in ids
163
+ )
164
+ continue
165
+ data.append(
88
166
  {
89
167
  "id": p.name,
90
168
  "object": "model",
91
- "created": int(time.time()),
169
+ "created": created,
92
170
  "owned_by": "coderouter",
93
171
  }
94
- for p in config.providers
95
- ],
96
- }
172
+ )
173
+ return {"object": "list", "data": data}
97
174
 
98
175
 
99
176
  @router.post("/chat/completions", response_model=None)
@@ -1173,6 +1173,80 @@ class FallbackEngine:
1173
1173
  # v2.0-K: persistent state store (None = in-memory only).
1174
1174
  self._state_store: StateStore | None = None
1175
1175
 
1176
+ def register_provider(
1177
+ self, provider: ProviderConfig, profile_name: str = "launcher"
1178
+ ) -> dict[str, Any]:
1179
+ """Register (or update) a provider at runtime — launcher auto-sync.
1180
+
1181
+ Motivation: the embedded launcher can start a llama.cpp / vllm /
1182
+ mlx backend on an arbitrary port, but routing is driven entirely
1183
+ by providers.yaml — historically the operator had to hand-edit the
1184
+ config or the new backend was simply unreachable (observed in the
1185
+ wild as "launcher started on 8085, providers.yaml pointed at
1186
+ 8080"). This method closes that gap in-process:
1187
+
1188
+ - the provider entry is appended to ``config.providers`` (or, when
1189
+ a provider with the same *name* already exists, its config entry
1190
+ and cached adapter are REPLACED — the launcher restarting a
1191
+ backend on the same port lands here),
1192
+ - an adapter is built and cached alongside the static ones,
1193
+ - the ``profile_name`` chain is created on demand, and the
1194
+ provider is moved to the FRONT of that chain (most recently
1195
+ launched backend wins the launcher profile).
1196
+
1197
+ Deliberately **in-memory only** — no providers.yaml rewrite. A
1198
+ YAML round-trip would destroy operator comments (and a
1199
+ comment-preserving writer means a new dependency), and the
1200
+ launcher's process registry is itself in-memory by design: both
1201
+ the process and its provider entry share the lifetime of this
1202
+ server process. ``default_profile`` is never touched — routing to
1203
+ the launcher profile stays an explicit opt-in
1204
+ (``X-CodeRouter-Profile: launcher`` or body ``profile``).
1205
+
1206
+ Returns a small summary dict for the launcher API response.
1207
+ """
1208
+ from coderouter.config.schemas import FallbackChain
1209
+
1210
+ replaced = False
1211
+ for i, existing in enumerate(self.config.providers):
1212
+ if existing.name == provider.name:
1213
+ self.config.providers[i] = provider
1214
+ replaced = True
1215
+ break
1216
+ if not replaced:
1217
+ self.config.providers.append(provider)
1218
+ self._adapters[provider.name] = build_adapter(provider)
1219
+
1220
+ try:
1221
+ chain = self.config.profile_by_name(profile_name)
1222
+ except KeyError:
1223
+ chain = FallbackChain(name=profile_name, providers=[provider.name])
1224
+ self.config.profiles.append(chain)
1225
+ else:
1226
+ if provider.name in chain.providers:
1227
+ chain.providers.remove(provider.name)
1228
+ chain.providers.insert(0, provider.name)
1229
+
1230
+ logger.info(
1231
+ "launcher provider sync: registered %s -> %s (profile %r)",
1232
+ provider.name,
1233
+ provider.base_url,
1234
+ profile_name,
1235
+ extra={
1236
+ "provider": provider.name,
1237
+ "base_url": str(provider.base_url),
1238
+ "profile": profile_name,
1239
+ "replaced": replaced,
1240
+ },
1241
+ )
1242
+ return {
1243
+ "provider": provider.name,
1244
+ "base_url": str(provider.base_url),
1245
+ "profile": profile_name,
1246
+ "replaced": replaced,
1247
+ "persisted": False, # in-memory only, by design (see docstring)
1248
+ }
1249
+
1176
1250
  @property
1177
1251
  def plugins(self) -> PluginRegistry:
1178
1252
  """Return the plugin registry, lazily building an empty one if absent.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: coderouter-cli
3
- Version: 2.7.3
3
+ Version: 2.7.5
4
4
  Summary: Local-first, free-first, fallback-built-in LLM router. Claude Code / OpenAI compatible.
5
5
  Project-URL: Homepage, https://github.com/zephel01/CodeRouter
6
6
  Project-URL: Repository, https://github.com/zephel01/CodeRouter
@@ -98,7 +98,7 @@ Description-Content-Type: text/markdown
98
98
  | backend が落ちたらセッション終了 | ローカル → 無料 → 有料へ自動フォールバック |
99
99
  | 長時間で context 溢れ・drift・ループ | 6 系統ガード + self-healing |
100
100
  | モデル名がハードコード (リタイアで即エラー) | プロファイルで抽象化、差し替え 1 行 |
101
- | 何が悪いか分からない | `doctor` 6 プローブ + `/dashboard` + audit/replay |
101
+ | 何が悪いか分からない | `doctor` 7 プローブ + `/dashboard` + audit/replay |
102
102
 
103
103
  直結で困っていないなら CodeRouter は不要です。**長時間・無人・弱いモデル**のどれかに当てはまったら、戻ってきてください。
104
104
 
@@ -167,14 +167,14 @@ ANTHROPIC_BASE_URL=http://localhost:8088 ANTHROPIC_AUTH_TOKEN=dummy claude
167
167
  | **Drift Detection** | モデルの応答品質が徐々に劣化 → 別 provider に切替 or KV cache flush (6 シグナル、`goal_mode` で目標達成停滞も検知) |
168
168
  | **Self-healing** | backend が落ちた → 自動除外 + restart + 回復 probe で自動復帰 |
169
169
  | **Tool Loop Guard** | 同じツールを無限に呼び続ける → 検知して停止 |
170
- | **Memory Pressure** | GPU メモリ不足を検知軽量モデルに切替 |
170
+ | **Memory Pressure** | OOM を出した backend を一時除外 チェーンの次の provider へフォールスルー |
171
171
  | **Mid-stream Guard** | 応答途中で落ちた → 溜まったテキストを安全に返却 |
172
172
 
173
173
  ### 診断と可視化
174
174
 
175
175
  | 機能 | 何がわかるか |
176
176
  |---|---|
177
- | **`coderouter doctor`** | プロバイダの問題を 6 プローブで即診断 + 修正パッチ出力 |
177
+ | **`coderouter doctor`** | プロバイダの問題を 7 プローブで即診断 + 修正パッチ出力 |
178
178
  | **`/dashboard`** | ブラウザで今何が起きてるかリアルタイム確認 |
179
179
  | **`coderouter audit`** | guard 発火履歴を検索 |
180
180
  | **`coderouter replay`** | provider 切替の効果を統計比較 (A/B 分析) / `--suggest-rules` でルール最適化提案 |
@@ -220,6 +220,8 @@ providers:
220
220
  | **オプションプロファイル** | `providers.yaml` に名前付きプリセットを定義 → ドロップダウンで選択するだけ |
221
221
  | **複数プロセス管理** | llama.cpp と vllm を同時に起動し、ポートごとに独立管理 |
222
222
  | **ログビューア** | 各プロセスの stdout/stderr をブラウザ内でリアルタイム確認 |
223
+ | **provider 自動同期** (v2.7.4) | 起動したバックエンドを provider として自動登録(`launcher-llamacpp-8085` 等)。providers.yaml 無編集で `X-CodeRouter-Profile: launcher` からルーティング可能。メモリ内のみ・serve と同寿命 |
224
+ | **モデル名パススルー** (v2.7.4) | `model: ""` の provider は `/v1/models` が上流のロード中モデル ID(gguf 名)をそのまま返す。gguf を差し替えても config 編集不要 — 外部ベンチからモデルを識別できる |
223
225
 
224
226
  ```yaml
225
227
  # providers.yaml に追記するだけで有効になる
@@ -279,6 +281,7 @@ providers:
279
281
  | 無料で回す | [無料枠ガイド](./docs/guides/free-tier-guide.md) |
280
282
  | llama.cpp / vllm を GUI で起動 | [Launcher ガイド](./docs/backends/launcher.md) |
281
283
  | 言語税を計測・回避する | [言語税ガイド](./docs/guides/language-tax.md) |
284
+ | 別の PC から安全に繋ぐ | [リモートアクセスガイド](./docs/guides/remote-access.md) |
282
285
  | 詰まった | [トラブルシューティング](./docs/guides/troubleshooting.md) |
283
286
  | 設計を知りたい | [アーキテクチャ詳細](./docs/concepts/architecture.md) |
284
287
  | 全リリース履歴 | [CHANGELOG](./CHANGELOG.md) |
@@ -305,7 +308,7 @@ English: [Quickstart](./docs/start/quickstart.en.md) · [Usage guide](./docs/gui
305
308
  ## 技術スペック
306
309
 
307
310
  - **ランタイム依存**: `fastapi` / `uvicorn` / `httpx` / `pydantic` / `pyyaml` の 5 個のみ
308
- - **テスト**: 964 (41 sub-release 連続で依存追加なし)
311
+ - **テスト**: 1,500+ 本(ランタイム依存 5 個は v1 系から不変)
309
312
  - **対応 OS**: macOS (Apple Silicon 推奨) / Linux / Windows WSL2
310
313
  - **対応 backend**: Ollama / llama.cpp / LM Studio / vLLM / MLX-LM / OpenRouter / NVIDIA NIM / Anthropic API
311
314
  - **ライセンス**: MIT
@@ -1,6 +1,6 @@
1
1
  coderouter/__init__.py,sha256=ghdjPrLtnRzY8fyQ4CJZI1UJKADyNTLtA3G7se8H7Ns,696
2
2
  coderouter/__main__.py,sha256=-LCgxJnvgUV240HjQKv7ly-mn2NuKHpC4nCpvTHjeSU,130
3
- coderouter/cli.py,sha256=KE49IACJVw692H6dlfu1tAah1jQgbwH92F4lCkhRk6U,28168
3
+ coderouter/cli.py,sha256=7zxt6riKPTpHGiDms9SVaPbe-n4ytfCrhIVtuPII-oU,30173
4
4
  coderouter/cli_stats.py,sha256=CCjzc1G4hTRHZ2gG1XhxhDpUkJnnl3NXbcbp1T18jpg,29894
5
5
  coderouter/cost.py,sha256=32h6uzb4nxh2eA5d2Hn3kD9yJbtis6CFDAbeIy5KRkM,7431
6
6
  coderouter/doctor.py,sha256=2luNk6BHSRvpQStJnHcqzNvNi-SKdOuKV0WZdorZhVk,82854
@@ -41,9 +41,9 @@ coderouter/ingress/__init__.py,sha256=WQsCH2CGJCAhy0mS6GSEdeYZRkkQu2OHDsP4CJWTLu
41
41
  coderouter/ingress/anthropic_routes.py,sha256=1ThXzx8VgxRV7UCi3NF5ey7kbr0ADPKTXyQuYS5gK1w,27063
42
42
  coderouter/ingress/app.py,sha256=h6s-UWsbGP22rku4pPVlvRdrtbMj4KoEEUZoQQOrTTw,19370
43
43
  coderouter/ingress/dashboard_routes.py,sha256=1SqNNrVTfQiZcx5n2MvjAzWirittX0e61tCt-MKp4Ag,25617
44
- coderouter/ingress/launcher_routes.py,sha256=1bU245mEauCMpEOsq0LY-8FKKQIJWJhDe3CXgag_ut0,55968
44
+ coderouter/ingress/launcher_routes.py,sha256=QzG0l0c2ZmAcQai3E-xs3cV5fUYf3ajjZauvI9pKDh4,57959
45
45
  coderouter/ingress/metrics_routes.py,sha256=M22dwOGn24P05Ge4W3c7d7mYytSGWjIR-pPSPOAiHJY,3965
46
- coderouter/ingress/openai_routes.py,sha256=Mjsk3jwot4qP9f5xshrmR1iuHMW-9sQdsfHMN_Sx5hc,9294
46
+ coderouter/ingress/openai_routes.py,sha256=TrugsQNJfNPgKKLzfT1aXzrs4emWYGE4XebEBuaZoWk,12505
47
47
  coderouter/metrics/__init__.py,sha256=7Es351DPS7yLM0yVF_F0eesmiD83n7Zzhie44chht38,1465
48
48
  coderouter/metrics/collector.py,sha256=wdGuX5yOBWEHCz1KVIfaUBZP5NXW3U34yyYlLGwgoCE,61212
49
49
  coderouter/metrics/prometheus.py,sha256=THYkvrcpQK3ZNW-BV0h3O-PoT2ppNZ0dapQuu1nyHwE,24410
@@ -56,7 +56,7 @@ coderouter/routing/adaptive.py,sha256=G2o377twGSjbUh65wiIFx6klnpFGjsD_nI3oDvcBwh
56
56
  coderouter/routing/auto_router.py,sha256=y4v0c8u5F9f98Vmhx1vRcKPiOgAvpzbFqr6TIh058h0,13341
57
57
  coderouter/routing/budget.py,sha256=PblmVKJGs_BwNa9uDHAA8hmZ4XIVKv38mHAeU0V3OMs,8451
58
58
  coderouter/routing/capability.py,sha256=rRQhzTgQYFTFDNYcSLKvq3xjdw6B1w7T-3jS7PTMI14,27864
59
- coderouter/routing/fallback.py,sha256=vjnDv3z88v76ZnF2Sg1xYUOkKfe0jZm14OtoOrQA7Dc,139707
59
+ coderouter/routing/fallback.py,sha256=L5hwe76DRlmHTR39dXafi27T5zRrgLlgX9uINddcSGk,142983
60
60
  coderouter/state/__init__.py,sha256=XoGcPmmBQSiZWML2S0juSveQ78xfhtdeCliNnVyzu7E,1088
61
61
  coderouter/state/audit_log.py,sha256=n7vuDsTfd5iuNUJhlRQPakJ2tMSsT9EfgPCzs1GEaac,10941
62
62
  coderouter/state/replay.py,sha256=Z_YHKroTKZdrL8qObFxcoLOAQWWXZvXFdLfxzvBhEJg,11230
@@ -67,8 +67,8 @@ coderouter/translation/__init__.py,sha256=PYXN7XVEwpG1uC8RLy6fvnGbzEZhhrEuUapH8I
67
67
  coderouter/translation/anthropic.py,sha256=aZkcYH4x82b0x7efJgJb9RWn9Hbyc9pEOthXe4vjUdU,11113
68
68
  coderouter/translation/convert.py,sha256=VV4kpu4dvLmGBmJfQnCLY5ryy_AnQcV2NuwjqRtaR8Q,52633
69
69
  coderouter/translation/tool_repair.py,sha256=DGOKArM53yfVbIfiAveoul1vGq9NEKmjagf43Qh4IkM,57630
70
- coderouter_cli-2.7.3.dist-info/METADATA,sha256=EqYCtqhIooSQ806Ps89T9XIxVKRhWHiyyTyJsMpu_Lg,14857
71
- coderouter_cli-2.7.3.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
72
- coderouter_cli-2.7.3.dist-info/entry_points.txt,sha256=-dnLfD1YZ2WjH2zSdNCvlO65wYltM9bsHt9Fhg3yGss,51
73
- coderouter_cli-2.7.3.dist-info/licenses/LICENSE,sha256=wkEzoR86jFw33jvfOHjULqmkGEfxTFMgMaJnpR8mPRw,1065
74
- coderouter_cli-2.7.3.dist-info/RECORD,,
70
+ coderouter_cli-2.7.5.dist-info/METADATA,sha256=VsPe8Z7VLlfIRAm5OHEP8VP-gE6CZmWcQFDqWlxiXIE,15546
71
+ coderouter_cli-2.7.5.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
72
+ coderouter_cli-2.7.5.dist-info/entry_points.txt,sha256=-dnLfD1YZ2WjH2zSdNCvlO65wYltM9bsHt9Fhg3yGss,51
73
+ coderouter_cli-2.7.5.dist-info/licenses/LICENSE,sha256=wkEzoR86jFw33jvfOHjULqmkGEfxTFMgMaJnpR8mPRw,1065
74
+ coderouter_cli-2.7.5.dist-info/RECORD,,