mirror of
https://github.com/Alishahryar1/free-claude-code.git
synced 2026-07-10 00:14:16 +00:00
## Problem
Runtime modules were published as generic top-level packages like `api`,
`cli`, and `providers`. That shape is fragile for PyPI packaging and
weakens explicit ownership boundaries.
## Changes
| Before | After |
| --- | --- |
| Runtime code lived in root-level packages. | Runtime code lives under
`src/free_claude_code/`. |
| Console scripts targeted top-level modules. | Console scripts target
namespaced modules. |
| Tests and smoke helpers imported old package roots. | Tests and smoke
helpers import `free_claude_code.*`. |
| Packaging listed six root packages. | Packaging builds the single
namespaced package. |
| Contracts allowed old root package directories. | Contracts require
the src namespace and reject old root imports. |
<!-- greptile_comment -->
<details open><summary><h3>Greptile Summary</h3></summary>
This PR moves the runtime packages into the `src/free_claude_code`
namespace. The main changes are:
- Console scripts now point to `free_claude_code.*` entrypoints.
- Runtime imports, tests, and smoke helpers now use the namespaced
package.
- Packaging now builds the single `src/free_claude_code` package.
- Contract tests now reject old top-level runtime package roots and
imports.
</details>
<h3>Confidence Score: 5/5</h3>
This PR is safe to merge with minimal risk.
The changes are a broad but mostly mechanical namespace and
package-layout migration with updated packaging, tests, and contract
coverage.
No files require special attention.
<details><summary><h3><a href="https://www.greptile.com/trex"><img
alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="20" align="absmiddle"></a> T-Rex Logs</h3></summary>
**What T-Rex did**
- Reviewed the primary contract validation by examining the namespace
validation log, which documents the exact commands executed, the working
directory, exit codes, pytest output, wheel build output, install
output, and import/entrypoint resolution.
- Verified the wheel listing by inspecting the wheel listing artifact,
confirming the available wheel filenames for the namespace validation.
- Ran and inspected the isolated import/entrypoint validation harness
saved as package-installed-import-check.py to validate import resolution
and entrypoint exposure.
- Captured and noted the wheel filename record in
package-wheel-filename.txt to enable traceability of the observed
artifact.
<a
href="https://app.greptile.com/trex/runs/13810533/artifacts"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifactsDark.svg?v=4"><source
media="(prefers-color-scheme: light)"
srcset="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"><img
alt="View all artifacts"
src="https://greptile-static-assets.s3.amazonaws.com/badges/ViewAllArtifacts.svg?v=4"></picture></a>
<sub><a href="https://www.greptile.com/trex"><img alt="T-Rex"
src="https://greptile-static-assets.s3.amazonaws.com/trex/trex_green.svg"
height="14" align="absmiddle"></a> Ran code and verified through
T-Rex</sub>
</details>
<details open><summary><h3>Important Files Changed</h3></summary>
| Filename | Overview |
|----------|----------|
| pyproject.toml | Updates packaging to build the single
`src/free_claude_code` package and retargets console scripts to
namespaced modules. |
| src/free_claude_code/config/env_template.py | Loads `.env.example`
from packaged resources with a source-checkout fallback after the
runtime package move. |
| src/free_claude_code/cli/entrypoints.py | Updates CLI entrypoint
imports to `free_claude_code.*` and continues to use the shared env
template loader. |
| src/free_claude_code/api/routes.py | Retargets API route dependencies
and handlers to the namespaced package without changing route behavior.
|
| src/free_claude_code/api/app.py | Updates app factory imports to the
namespaced package while preserving middleware, routers, and exception
handling. |
| src/free_claude_code/providers/runtime/factory.py | Updates lazy
provider factory imports to `free_claude_code.providers.*` under the new
package layout. |
| tests/contracts/test_import_boundaries.py | Adds contract coverage
requiring runtime packages to live under `src/free_claude_code` and
rejecting old top-level imports. |
| smoke/lib/child_process.py | Updates smoke child-process helpers to
import CLI entrypoints from the namespaced package. |
| README.md | Updates the project layout and extension guidance to refer
to `src/free_claude_code` and importable `free_claude_code.*` modules. |
| uv.lock | Reflects the package version bump associated with the
runtime packaging move. |
</details>
<details open><summary><h3>Sequence Diagram</h3></summary>
<a href="#gh-light-mode-only">
```mermaid
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant User as User / CLI
participant Script as Console script
participant Pkg as free_claude_code package
participant API as free_claude_code.api
participant Runtime as free_claude_code.providers.runtime
participant Provider as Provider adapter
User->>Script: run fcc-server / free-claude-code
Script->>Pkg: load free_claude_code.cli.entrypoints:serve
Pkg->>API: create FastAPI app and routes
API->>Runtime: resolve configured provider
Runtime->>Provider: instantiate namespaced adapter
Provider-->>Runtime: stream/model responses
Runtime-->>API: provider result
API-->>User: Anthropic/OpenAI-compatible response
```
</a>
<a href="#gh-dark-mode-only">
```mermaid
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant User as User / CLI
participant Script as Console script
participant Pkg as free_claude_code package
participant API as free_claude_code.api
participant Runtime as free_claude_code.providers.runtime
participant Provider as Provider adapter
User->>Script: run fcc-server / free-claude-code
Script->>Pkg: load free_claude_code.cli.entrypoints:serve
Pkg->>API: create FastAPI app and routes
API->>Runtime: resolve configured provider
Runtime->>Provider: instantiate namespaced adapter
Provider-->>Runtime: stream/model responses
Runtime-->>API: provider result
API-->>User: Anthropic/OpenAI-compatible response
```
</a>
</details>
<sub>Reviews (2): Last reviewed commit: ["Fix documented package import
paths"](bfa9f2704c)
| [Re-trigger
Greptile](https://app.greptile.com/api/retrigger?id=42950471)</sub>
<!-- /greptile_comment -->
164 lines
5.1 KiB
Python
164 lines
5.1 KiB
Python
"""CLI entry points for the installed package."""
|
|
|
|
import os
|
|
import shutil
|
|
import sys
|
|
import threading
|
|
import time
|
|
import webbrowser
|
|
from pathlib import Path
|
|
|
|
import uvicorn
|
|
|
|
from free_claude_code.api.admin_urls import local_admin_url, local_proxy_root_url
|
|
from free_claude_code.api.app import GracefulLifespanApp, create_app
|
|
from free_claude_code.cli.launchers.common import preflight_proxy
|
|
from free_claude_code.cli.process_registry import (
|
|
kill_all_best_effort,
|
|
)
|
|
from free_claude_code.config.env_migrations import (
|
|
explicit_env_file_huggingface_warning,
|
|
migrate_owned_env_files,
|
|
)
|
|
from free_claude_code.config.env_template import load_env_template
|
|
from free_claude_code.config.paths import (
|
|
config_dir_path,
|
|
legacy_env_paths,
|
|
managed_env_path,
|
|
)
|
|
from free_claude_code.config.settings import Settings, get_settings
|
|
|
|
SERVER_GRACEFUL_SHUTDOWN_SECONDS = 5
|
|
|
|
|
|
def serve() -> None:
|
|
"""Start the FastAPI server (registered as `fcc-server` script)."""
|
|
opened_admin_browser = False
|
|
try:
|
|
try:
|
|
while True:
|
|
_migrate_legacy_env_if_missing()
|
|
_migrate_config_env_keys()
|
|
settings = get_settings()
|
|
if not _run_supervised_server(
|
|
settings, open_admin_browser=not opened_admin_browser
|
|
):
|
|
return
|
|
opened_admin_browser = True
|
|
get_settings.cache_clear()
|
|
except KeyboardInterrupt:
|
|
return
|
|
finally:
|
|
kill_all_best_effort()
|
|
|
|
|
|
def _admin_browser_open_enabled() -> bool:
|
|
"""Whether to open /admin when the server becomes reachable (FCC_OPEN_BROWSER)."""
|
|
|
|
raw = os.environ.get("FCC_OPEN_BROWSER", "true").strip().lower()
|
|
return raw not in {"", "0", "false", "no"}
|
|
|
|
|
|
def _schedule_open_admin_browser(settings: Settings) -> None:
|
|
"""After /health succeeds, open the admin UI in the default browser (daemon thread)."""
|
|
|
|
if not _admin_browser_open_enabled():
|
|
return
|
|
|
|
admin_url = local_admin_url(settings)
|
|
proxy_root_url = local_proxy_root_url(settings)
|
|
|
|
def open_when_ready() -> None:
|
|
deadline = time.monotonic() + 30.0
|
|
while time.monotonic() < deadline:
|
|
if preflight_proxy(proxy_root_url) is None:
|
|
webbrowser.open(admin_url)
|
|
return
|
|
time.sleep(0.15)
|
|
|
|
threading.Thread(
|
|
target=open_when_ready, name="fcc-open-admin-browser", daemon=True
|
|
).start()
|
|
|
|
|
|
def _run_supervised_server(settings: Settings, *, open_admin_browser: bool) -> bool:
|
|
"""Run one uvicorn server instance; return whether admin requested restart."""
|
|
|
|
restart_requested = False
|
|
server_holder: dict[str, uvicorn.Server] = {}
|
|
|
|
def request_restart() -> None:
|
|
nonlocal restart_requested
|
|
restart_requested = True
|
|
if server := server_holder.get("server"):
|
|
server.should_exit = True
|
|
|
|
app = create_app(lifespan_enabled=False)
|
|
app.state.admin_restart_callback = request_restart
|
|
asgi_app = GracefulLifespanApp(app)
|
|
config = uvicorn.Config(
|
|
asgi_app,
|
|
host=settings.host,
|
|
port=settings.port,
|
|
log_level="debug",
|
|
timeout_graceful_shutdown=SERVER_GRACEFUL_SHUTDOWN_SECONDS,
|
|
)
|
|
server = uvicorn.Server(config)
|
|
server_holder["server"] = server
|
|
if open_admin_browser:
|
|
_schedule_open_admin_browser(settings)
|
|
server.run()
|
|
return restart_requested
|
|
|
|
|
|
def init() -> None:
|
|
"""Scaffold config at ~/.fcc/.env (registered as `fcc-init`)."""
|
|
config_dir = config_dir_path()
|
|
env_file = managed_env_path()
|
|
|
|
migrated_from = _migrate_legacy_env_if_missing()
|
|
_migrate_config_env_keys()
|
|
if migrated_from is not None:
|
|
print(f"Config migrated from {migrated_from} to {env_file}")
|
|
print(
|
|
"Edit it to set your API keys and model preferences, then run: fcc-server"
|
|
)
|
|
return
|
|
|
|
if env_file.exists():
|
|
print(f"Config already exists at {env_file}")
|
|
print("Delete it first if you want to reset to defaults.")
|
|
return
|
|
|
|
config_dir.mkdir(parents=True, exist_ok=True)
|
|
template = load_env_template()
|
|
env_file.write_text(template, encoding="utf-8")
|
|
print(f"Config created at {env_file}")
|
|
print("Edit it to set your API keys and model preferences, then run: fcc-server")
|
|
|
|
|
|
def _migrate_legacy_env_if_missing() -> Path | None:
|
|
"""Copy a legacy user env into the managed config path when absent."""
|
|
|
|
env_file = managed_env_path()
|
|
if env_file.exists():
|
|
return None
|
|
|
|
# TODO: Remove after the ~/.fcc/.env migration has had a release cycle.
|
|
for legacy_env in legacy_env_paths():
|
|
if not legacy_env.is_file():
|
|
continue
|
|
env_file.parent.mkdir(parents=True, exist_ok=True)
|
|
shutil.copyfile(legacy_env, env_file)
|
|
return legacy_env
|
|
|
|
return None
|
|
|
|
|
|
def _migrate_config_env_keys() -> tuple[Path, ...]:
|
|
"""Apply dotenv key migrations before Settings loads config."""
|
|
|
|
migrated = migrate_owned_env_files()
|
|
if warning := explicit_env_file_huggingface_warning(os.environ):
|
|
print(warning, file=sys.stderr)
|
|
return migrated
|