## 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"](
|
||
|---|---|---|
| .. | ||
| lib | ||
| prereq | ||
| product | ||
| __init__.py | ||
| capabilities.py | ||
| conftest.py | ||
| features.py | ||
| README.md | ||
Product E2E Smoke Tests
smoke/ is local-only. It can launch subprocesses, call real providers, touch
local model servers, and optionally send/delete bot messages. Hermetic contracts
belong under tests/ and must stay green with plain uv run pytest.
Taxonomy
smoke/prereq/: liveness checks that prove the server, routes, auth, CLI scripts, provider pings, local/models, and bot permissions are reachable. These are prerequisites only.smoke/product/: end-to-end product scenarios. Feature smoke coverage comes from these tests, not from route/header/provider pings.smoke/features.py: source-of-truth feature map: feature -> subfeature -> scenario -> env -> expected behavior -> failure class.
Required Local Commands
uv run pytest smoke --collect-only -q
uv run pytest smoke -n 0 -s --tb=short
The second command skips everything unless FCC_LIVE_SMOKE=1 is set, but still
writes skip entries to .smoke-results/.
Product Smoke Run
$env:FCC_LIVE_SMOKE = "1"
uv run pytest smoke -n 0 -s --tb=short
Provider smoke scenarios can run providers in parallel while preserving sequential execution within each provider:
$env:FCC_LIVE_SMOKE = "1"
$env:FCC_SMOKE_TARGETS = "providers"
uv run pytest smoke -n auto --dist=loadgroup -s --tb=short
Provider product E2E runs once per configured provider, independent of MODEL,
MODEL_OPUS, MODEL_SONNET, and MODEL_HAIKU. Defaults come from the provider
catalog/docs and can be overridden with FCC_SMOKE_MODEL_<PROVIDER>, for example
FCC_SMOKE_MODEL_DEEPSEEK=deepseek-v4-pro (or deepseek-v4-flash). If no provider smoke model is
configured, live product smoke fails as missing_env unless you explicitly set
FCC_ALLOW_NO_PROVIDER_SMOKE=1.
Targets
Default targets do not send real bot messages or load voice backends:
| Target | Product scenarios | Required environment |
|---|---|---|
api |
messages, count_tokens full payload, errors, /stop, optimizations |
configured provider only for streaming messages |
auth |
x-api-key, bearer, anthropic-auth-token, invalid/missing auth | none; test sets an isolated token |
cli |
fcc-init, server entrypoint, Claude CLI adaptive thinking, session cleanup |
Claude CLI binary and provider only for real CLI |
clients |
VS Code and JetBrains protocol payloads | configured provider |
config |
env precedence, removed-env migration, proxy/timeouts | none |
extensibility |
provider runtime and platform factory construction | none |
messaging |
fake Discord/Telegram full flow, commands, trees, persistence, voice cancel | none |
providers |
multi-turn text, adaptive thinking history, tools, disconnect, errors | configured providers, optional FCC_SMOKE_MODEL_* |
tools |
forced tool_use and tool_result continuation | tool-capable configured provider |
rate_limit |
disconnect cleanup and follow-up request | configured provider |
lmstudio |
local /models plus native /messages through proxy |
running LM Studio server |
llamacpp |
local /models plus native /messages through proxy |
running llama-server |
ollama |
local /api/tags plus native Anthropic messages through proxy |
running Ollama server |
Heavy/side-effectful targets are opt-in:
| Target | Product scenarios | Required environment |
|---|---|---|
nvidia_nim_cli |
Claude Code CLI feature matrix across NIM models | NVIDIA_NIM_API_KEY, Claude CLI |
openrouter_free_cli |
Claude Code CLI feature matrix across OpenRouter free models | OPENROUTER_API_KEY, Claude CLI |
telegram |
getMe, send, edit, delete, optional manual inbound | token and chat/user ID |
discord |
channel access, send, edit, delete, optional manual inbound | token and channel ID |
voice |
generated WAV through local Whisper or NVIDIA NIM transcription | VOICE_NOTE_ENABLED=true, FCC_SMOKE_RUN_VOICE=1 |
Examples
$env:FCC_LIVE_SMOKE = "1"
$env:FCC_SMOKE_PROVIDER_MATRIX = "open_router,nvidia_nim,deepseek,lmstudio,llamacpp,ollama"
uv run pytest smoke/product -n 0 -s --tb=short
$env:FCC_LIVE_SMOKE = "1"
$env:FCC_SMOKE_TARGETS = "ollama"
$env:OLLAMA_BASE_URL = "http://localhost:11434"
uv run pytest smoke/prereq smoke/product -n 0 -s --tb=short
$env:FCC_LIVE_SMOKE = "1"
$env:FCC_SMOKE_TARGETS = "telegram,discord,voice"
$env:FCC_SMOKE_RUN_VOICE = "1"
uv run pytest smoke/product -n 0 -s --tb=short
$env:FCC_LIVE_SMOKE = "1"
$env:FCC_SMOKE_TARGETS = "nvidia_nim_cli"
$env:FCC_SMOKE_NIM_MODELS = "z-ai/glm-5.2,moonshotai/kimi-k2.6,minimaxai/minimax-m2.7,nvidia/nemotron-3-super-120b-a12b,deepseek-ai/deepseek-v4-pro,deepseek-ai/deepseek-v4-flash"
uv run pytest smoke/product -n 0 -s --tb=short
$env:FCC_LIVE_SMOKE = "1"
$env:FCC_SMOKE_TARGETS = "openrouter_free_cli"
$env:FCC_SMOKE_OPENROUTER_FREE_MODELS = "nvidia/nemotron-3-super-120b-a12b:free,openai/gpt-oss-120b:free,poolside/laguna-m.1:free"
uv run pytest smoke/product -n 0 -s --tb=short
$env:FCC_LIVE_SMOKE = "1"
$env:FCC_SMOKE_TARGETS = "messaging,config,extensibility"
uv run pytest smoke/product -n 0 -s --tb=short
Environment
FCC_ENV_FILE: explicit dotenv path for startup/config scenarios.FCC_LIVE_SMOKE=1: enables live smoke execution.FCC_ALLOW_NO_PROVIDER_SMOKE=1: permits no-provider live smoke for harness work.FCC_SMOKE_TARGETS: comma-separated targets, orall.FCC_SMOKE_PROVIDER_MATRIX: comma-separated provider prefixes to require.FCC_SMOKE_MODEL_NVIDIA_NIM,FCC_SMOKE_MODEL_OPEN_ROUTER,FCC_SMOKE_MODEL_MISTRAL,FCC_SMOKE_MODEL_MISTRAL_REASONING,FCC_SMOKE_MODEL_MISTRAL_CODESTRAL,FCC_SMOKE_MODEL_DEEPSEEK,FCC_SMOKE_MODEL_KIMI,FCC_SMOKE_MODEL_WAFER,FCC_SMOKE_MODEL_MINIMAX,FCC_SMOKE_MODEL_OPENCODE,FCC_SMOKE_MODEL_OPENCODE_GO,FCC_SMOKE_MODEL_ZAI,FCC_SMOKE_MODEL_FIREWORKS,FCC_SMOKE_MODEL_CLOUDFLARE,FCC_SMOKE_MODEL_GEMINI,FCC_SMOKE_MODEL_GROQ,FCC_SMOKE_MODEL_CEREBRAS,FCC_SMOKE_MODEL_LMSTUDIO,FCC_SMOKE_MODEL_LLAMACPP,FCC_SMOKE_MODEL_OLLAMA: optional per-provider smoke model overrides. Values may include the provider prefix or just the model name for that provider.FCC_SMOKE_MODEL_MISTRAL_REASONING: optional override for the dedicated Mistral native reasoning smoke, defaultmistral/mistral-medium-3-5.FCC_SMOKE_NIM_MODELS: optional comma-separated NVIDIA NIM CLI matrix models that replace the default characterization set.FCC_SMOKE_NIM_EXTRA_MODELS: optional comma-separated NVIDIA NIM CLI matrix models appended to the default or replacement set.FCC_SMOKE_OPENROUTER_FREE_MODELS: optional comma-separated OpenRouter free CLI matrix models that replace the default characterization set.FCC_SMOKE_OPENROUTER_FREE_EXTRA_MODELS: optional comma-separated OpenRouter free CLI matrix models appended to the default or replacement set.FCC_SMOKE_TIMEOUT_S: per-request/subprocess timeout, default45.FCC_SMOKE_CLAUDE_BIN: Claude CLI executable name, defaultclaude.FCC_SMOKE_TELEGRAM_CHAT_ID: Telegram chat/user ID for send/edit/delete.FCC_SMOKE_DISCORD_CHANNEL_ID: Discord channel ID for send/edit/delete.FCC_SMOKE_INTERACTIVE=1: enables manual inbound Telegram/Discord checks.FCC_SMOKE_RUN_VOICE=1: allows voice transcription backends to load/run.
Windows / nested uv run
Run smoke the same way you run tests (uv run pytest smoke from the repo). Child
processes use the same Python interpreter as the test runner, not nested
uv run, so Windows does not try to replace free-claude-code.exe while it is
locked.
Failure Classes
Smoke artifacts are written to .smoke-results/ and redact env values whose
names contain KEY, TOKEN, SECRET, WEBHOOK, or AUTH.
missing_env: required credentials, binary, provider config, local provider server/model, or opt-in flag is absent.upstream_unavailable: a real provider or bot API is not reachable.probe_timeout: the smoke driver reached the target, but the CLI/probe did not complete within the smoke timeout.product_failure: the app accepted the scenario but returned the wrong shape, crashed, leaked state, or violated the product contract.harness_bug: the smoke test or driver made an invalid assumption.target_disabled: skipped becauseFCC_SMOKE_TARGETSintentionally selected a different target.
product_failure and harness_bug are failures. missing_env,
upstream_unavailable, and probe_timeout are skips except when the user
explicitly selected a provider in FCC_SMOKE_PROVIDER_MATRIX;
selected-but-missing providers fail.