Skip to content

Commit c5f30ed

Browse files
committed
support running on claude and codex sub
1 parent 5a843b9 commit c5f30ed

16 files changed

Lines changed: 931 additions & 264 deletions

DEVELOPMENT.md

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ codewiki/
1414
│ │ └── adapters/ # External integrations
1515
│ ├── src/ # Web application
1616
│ │ ├── be/ # Backend (dependency analysis, agents)
17-
│ │ │ ├── agent_orchestrator.py
17+
│ │ │ ├── backend.py # LLMBackend abstraction + factory
18+
│ │ │ ├── pydantic_ai_backend.py # API-key backend (OpenAI/Anthropic/Bedrock/Azure)
19+
│ │ │ ├── caw_backend.py # Subscription backend (claude / codex CLI via caw)
20+
│ │ │ ├── caw_toolkit.py # CodeWiki tools exposed to caw via MCP
1821
│ │ │ ├── agent_tools/
1922
│ │ │ ├── cluster_modules.py
2023
│ │ │ ├── dependency_analyzer/
@@ -83,11 +86,15 @@ pip install -r requirements.txt
8386
- Feature-oriented module partitioning
8487
- Topological sorting for dependency ordering
8588

86-
#### 3. Agent System (`src/be/agent_orchestrator.py`)
89+
#### 3. Agent System (`src/be/backend.py`, `pydantic_ai_backend.py`, `caw_backend.py`)
8790

88-
- Recursive agent-based documentation generation
89-
- Dynamic delegation for complex modules
90-
- Cross-module reference management
91+
- ``LLMBackend`` abstracts the API-key and CLI-subscription paths.
92+
- ``PydanticAIBackend`` runs the per-module agent via pydantic-ai (used by
93+
``openai-compatible`` / ``anthropic`` / ``bedrock`` / ``azure-openai``).
94+
- ``CawBackend`` routes the per-module agent through the ``claude`` /
95+
``codex`` CLI via the ``caw`` library (used by the ``claude-code`` /
96+
``codex`` providers). CodeWiki's tools are exposed to the CLI via an MCP
97+
server defined in ``caw_toolkit.py``.
9198

9299
#### 4. Agent Tools (`src/be/agent_tools/`)
93100

@@ -168,7 +175,7 @@ class AgentInstructions:
168175
5. **Use in relevant components**:
169176
- File filtering → `dependency_analyzer/ast_parser.py`
170177
- Prompts → `be/prompt_template.py`
171-
- Agent creation → `be/agent_orchestrator.py`
178+
- Agent creation → `be/pydantic_ai_backend.py` (API path) or `be/caw_backend.py` (subscription path)
172179

173180
---
174181

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ codewiki --version
4242

4343
### 2. Configure Your Environment
4444

45-
CodeWiki supports multiple LLM providers: **OpenAI-compatible**, **Anthropic**, **AWS Bedrock**, and **Azure OpenAI**.
45+
CodeWiki supports multiple LLM providers: **OpenAI-compatible**, **Anthropic**, **AWS Bedrock**, **Azure OpenAI**, plus subscription mode via **Claude Code** and **Codex** CLIs (no API key required).
4646

4747
```bash
4848
# Anthropic
@@ -68,8 +68,22 @@ codewiki config set \
6868
--aws-region us-east-1 \
6969
--main-model anthropic.claude-sonnet-4-v2:0 \
7070
--cluster-model anthropic.claude-sonnet-4-v2:0
71+
72+
# Subscription mode (Claude Code) — uses your existing Claude OAuth login.
73+
# Install the Claude Code CLI and run `claude login` first.
74+
codewiki config set \
75+
--provider claude-code \
76+
--main-model claude-sonnet-4-5
77+
78+
# Subscription mode (Codex) — uses your existing Codex CLI login.
79+
# Install the Codex CLI and run `codex login` first.
80+
codewiki config set \
81+
--provider codex \
82+
--main-model gpt-5.2-codex
7183
```
7284

85+
**Subscription mode** routes every LLM call through the local `claude` / `codex` CLI binary (via the [`caw`](https://github.com/zzjas/caw) library), so you can run CodeWiki on a Claude Pro/Max or Codex subscription instead of paying per-token API usage. Claude Code's built-in `Write`/`Edit`/`Bash` tools are disabled inside CodeWiki's agent loop so documentation writes still go through CodeWiki's Mermaid-validating editor.
86+
7387
### 3. Generate Documentation
7488

7589
```bash

codewiki/cli/adapters/doc_generator.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,12 +210,12 @@ async def _run_backend_generation(self, backend_config: BackendConfig):
210210
from codewiki.src.be.cluster_modules import cluster_modules
211211
from codewiki.src.utils import file_manager
212212
from codewiki.src.config import FIRST_MODULE_TREE_FILENAME, MODULE_TREE_FILENAME
213-
213+
214214
working_dir = str(self.output_dir.absolute())
215215
file_manager.ensure_directory(working_dir)
216216
first_module_tree_path = os.path.join(working_dir, FIRST_MODULE_TREE_FILENAME)
217217
module_tree_path = os.path.join(working_dir, MODULE_TREE_FILENAME)
218-
218+
219219
try:
220220
if os.path.exists(first_module_tree_path):
221221
module_tree = file_manager.load_json(first_module_tree_path)
@@ -224,7 +224,13 @@ async def _run_backend_generation(self, backend_config: BackendConfig):
224224
else:
225225
if self.verbose:
226226
self.progress_tracker.update_stage(0.3, f"Clustering {len(leaf_nodes)} leaf nodes with LLM...")
227-
module_tree = cluster_modules(leaf_nodes, components, backend_config)
227+
cluster_model = backend_config.cluster_model or None
228+
module_tree = cluster_modules(
229+
leaf_nodes,
230+
components,
231+
backend_config,
232+
completer=lambda p: doc_generator.backend.complete(p, model=cluster_model),
233+
)
228234
file_manager.save_json(module_tree, first_module_tree_path)
229235

230236
file_manager.save_json(module_tree, module_tree_path)

codewiki/cli/commands/config.py

Lines changed: 150 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,14 @@ def config_group():
8585
)
8686
@click.option(
8787
"--provider",
88-
type=click.Choice(['openai-compatible', 'anthropic', 'bedrock', 'azure-openai'], case_sensitive=False),
89-
help="LLM provider type (default: openai-compatible)"
88+
type=click.Choice(
89+
['openai-compatible', 'anthropic', 'bedrock', 'azure-openai', 'claude-code', 'codex'],
90+
case_sensitive=False,
91+
),
92+
help=(
93+
"LLM provider type (default: openai-compatible). "
94+
"Use 'claude-code' or 'codex' to run on a CLI subscription instead of an API key."
95+
),
9096
)
9197
@click.option(
9298
"--aws-region",
@@ -127,24 +133,33 @@ def config_set(
127133
• Linux: Secret Service (GNOME Keyring, KWallet)
128134
129135
Examples:
130-
136+
131137
\b
132-
# Set all configuration
138+
# Set all configuration (API mode)
133139
$ codewiki config set --api-key sk-abc123 --base-url https://api.anthropic.com \\
134140
--main-model claude-sonnet-4 --cluster-model claude-sonnet-4 --fallback-model glm-4p5
135-
141+
142+
\b
143+
# Subscription mode (Claude Code) — no API key needed,
144+
# authenticate via 'claude login' on the host first
145+
$ codewiki config set --provider claude-code --main-model claude-sonnet-4-5
146+
147+
\b
148+
# Subscription mode (Codex)
149+
$ codewiki config set --provider codex --main-model gpt-5.2-codex
150+
136151
\b
137152
# Update only API key
138153
$ codewiki config set --api-key sk-new-key
139-
154+
140155
\b
141156
# Set max tokens for LLM response
142157
$ codewiki config set --max-tokens 16384
143-
158+
144159
\b
145160
# Set all max token settings
146161
$ codewiki config set --max-tokens 32768 --max-token-per-module 40000 --max-token-per-leaf-module 20000
147-
162+
148163
\b
149164
# Set max depth for hierarchical decomposition
150165
$ codewiki config set --max-depth 3
@@ -354,26 +369,36 @@ def config_show(output_json: bool):
354369
click.echo("━" * 40)
355370
click.echo()
356371

372+
from codewiki.src.be.backend import is_caw_provider
373+
caw_mode = bool(config) and is_caw_provider(config.provider)
374+
357375
click.secho("Credentials", fg="cyan", bold=True)
358-
if api_key:
376+
if caw_mode:
377+
cli_name = "claude" if config.provider == "claude-code" else "codex"
378+
click.secho(
379+
f" Subscription mode: authenticate via '{cli_name} login' (no API key needed)",
380+
fg="cyan",
381+
)
382+
elif api_key:
359383
storage = "system keychain" if manager.keyring_available else "encrypted file"
360384
click.echo(f" API Key: {mask_api_key(api_key)} (in {storage})")
361385
else:
362386
click.secho(" API Key: Not set", fg="yellow")
363-
387+
364388
click.echo()
365389
click.secho("API Settings", fg="cyan", bold=True)
366390
if config:
367-
click.echo(f" Base URL: {config.base_url or 'Not set'}")
368-
click.echo(f" Main Model: {config.main_model or 'Not set'}")
369-
click.echo(f" Cluster Model: {config.cluster_model or 'Not set'}")
370-
click.echo(f" Fallback Model: {config.fallback_model or 'Not set'}")
371391
click.echo(f" Provider: {config.provider}")
372-
if config.provider == "bedrock":
373-
click.echo(f" AWS Region: {config.aws_region}")
374-
elif config.provider == "azure-openai":
375-
click.echo(f" API Version: {config.api_version}")
376-
click.echo(f" Azure Deployment: {config.azure_deployment or 'Not set'}")
392+
click.echo(f" Main Model: {config.main_model or 'Not set'}")
393+
if not caw_mode:
394+
click.echo(f" Base URL: {config.base_url or 'Not set'}")
395+
click.echo(f" Cluster Model: {config.cluster_model or 'Not set'}")
396+
click.echo(f" Fallback Model: {config.fallback_model or 'Not set'}")
397+
if config.provider == "bedrock":
398+
click.echo(f" AWS Region: {config.aws_region}")
399+
elif config.provider == "azure-openai":
400+
click.echo(f" API Version: {config.api_version}")
401+
click.echo(f" Azure Deployment: {config.azure_deployment or 'Not set'}")
377402
else:
378403
click.secho(" Not configured", fg="yellow")
379404

@@ -479,75 +504,129 @@ def config_validate(quick: bool, verbose: bool):
479504
else:
480505
click.secho("✓ Configuration file exists", fg="green")
481506

482-
# Step 2: Check API key
507+
# Load config early so we know the provider for the rest of the checks.
508+
config = manager.get_config()
509+
from codewiki.src.be.backend import is_caw_provider
510+
caw_mode = bool(config) and is_caw_provider(config.provider)
511+
512+
# Step 2: Check API key (skipped for subscription providers)
483513
if verbose:
484514
click.echo()
485515
click.echo("[2/5] Checking API key...")
486-
storage = "system keychain" if manager.keyring_available else "encrypted file"
487-
click.echo(f" Storage: {storage}")
488-
489-
api_key = manager.get_api_key()
490-
if not api_key:
491-
click.secho("✗ API key missing", fg="red")
492-
click.echo()
493-
click.echo("Error: API key not set. Run 'codewiki config set --api-key <key>'")
494-
sys.exit(EXIT_CONFIG_ERROR)
495-
496-
if verbose:
497-
click.secho(f" ✓ API key retrieved", fg="green")
498-
click.secho(f" ✓ Length: {len(api_key)} characters", fg="green")
516+
517+
if caw_mode:
518+
if verbose:
519+
click.secho(" ✓ API key not required (subscription mode)", fg="green")
520+
else:
521+
click.secho("✓ API key not required (subscription mode)", fg="green")
499522
else:
500-
click.secho("✓ API key present (stored in keychain)", fg="green")
501-
502-
# Step 3: Check base URL
503-
config = manager.get_config()
523+
if verbose:
524+
storage = "system keychain" if manager.keyring_available else "encrypted file"
525+
click.echo(f" Storage: {storage}")
526+
527+
api_key = manager.get_api_key()
528+
if not api_key:
529+
click.secho("✗ API key missing", fg="red")
530+
click.echo()
531+
click.echo("Error: API key not set. Run 'codewiki config set --api-key <key>'")
532+
sys.exit(EXIT_CONFIG_ERROR)
533+
534+
if verbose:
535+
click.secho(f" ✓ API key retrieved", fg="green")
536+
click.secho(f" ✓ Length: {len(api_key)} characters", fg="green")
537+
else:
538+
click.secho("✓ API key present (stored in keychain)", fg="green")
539+
540+
# Step 3: Check base URL (skipped for subscription providers)
504541
if verbose:
505542
click.echo()
506543
click.echo("[3/5] Checking base URL...")
507-
click.echo(f" URL: {config.base_url}")
508-
509-
if not config.base_url:
510-
click.secho("✗ Base URL not set", fg="red")
511-
sys.exit(EXIT_CONFIG_ERROR)
512-
513-
try:
514-
validate_url(config.base_url)
544+
545+
if caw_mode:
515546
if verbose:
516-
click.secho(" ✓ Valid HTTPS URL", fg="green")
547+
click.secho(" ✓ Base URL not required (subscription mode)", fg="green")
517548
else:
518-
click.secho(f"✓ Base URL valid: {config.base_url}", fg="green")
519-
except ConfigurationError as e:
520-
click.secho(f"✗ Invalid base URL: {e.message}", fg="red")
521-
sys.exit(EXIT_CONFIG_ERROR)
549+
click.secho("✓ Base URL not required (subscription mode)", fg="green")
550+
else:
551+
if verbose:
552+
click.echo(f" URL: {config.base_url}")
553+
554+
if not config.base_url:
555+
click.secho("✗ Base URL not set", fg="red")
556+
sys.exit(EXIT_CONFIG_ERROR)
557+
558+
try:
559+
validate_url(config.base_url)
560+
if verbose:
561+
click.secho(" ✓ Valid HTTPS URL", fg="green")
562+
else:
563+
click.secho(f"✓ Base URL valid: {config.base_url}", fg="green")
564+
except ConfigurationError as e:
565+
click.secho(f"✗ Invalid base URL: {e.message}", fg="red")
566+
sys.exit(EXIT_CONFIG_ERROR)
522567

523568
# Step 4: Check models
524569
if verbose:
525570
click.echo()
526571
click.echo("[4/5] Checking model configuration...")
527572
click.echo(f" Main model: {config.main_model}")
528-
click.echo(f" Cluster model: {config.cluster_model}")
529-
click.echo(f" Fallback model: {config.fallback_model}")
530-
531-
if not config.main_model or not config.cluster_model or not config.fallback_model:
532-
click.secho("✗ Models not configured", fg="red")
533-
sys.exit(EXIT_CONFIG_ERROR)
534-
535-
if verbose:
536-
click.secho(" ✓ Models configured", fg="green")
573+
if not caw_mode:
574+
click.echo(f" Cluster model: {config.cluster_model}")
575+
click.echo(f" Fallback model: {config.fallback_model}")
576+
577+
if caw_mode:
578+
if not config.main_model:
579+
click.secho("✗ Main model not configured", fg="red")
580+
sys.exit(EXIT_CONFIG_ERROR)
581+
if verbose:
582+
click.secho(" ✓ Main model configured", fg="green")
583+
else:
584+
click.secho(f"✓ Main model configured: {config.main_model}", fg="green")
537585
else:
538-
click.secho(f"✓ Main model configured: {config.main_model}", fg="green")
539-
click.secho(f"✓ Cluster model configured: {config.cluster_model}", fg="green")
540-
click.secho(f"✓ Fallback model configured: {config.fallback_model}", fg="green")
541-
542-
# Warn about non-top-tier cluster model
543-
if not is_top_tier_model(config.cluster_model):
544-
click.secho(
545-
"⚠️ Cluster model is not top-tier. Consider using claude-sonnet-4 or gpt-4.",
546-
fg="yellow"
547-
)
548-
586+
if not config.main_model or not config.cluster_model or not config.fallback_model:
587+
click.secho("✗ Models not configured", fg="red")
588+
sys.exit(EXIT_CONFIG_ERROR)
589+
590+
if verbose:
591+
click.secho(" ✓ Models configured", fg="green")
592+
else:
593+
click.secho(f"✓ Main model configured: {config.main_model}", fg="green")
594+
click.secho(f"✓ Cluster model configured: {config.cluster_model}", fg="green")
595+
click.secho(f"✓ Fallback model configured: {config.fallback_model}", fg="green")
596+
597+
# Warn about non-top-tier cluster model
598+
if not is_top_tier_model(config.cluster_model):
599+
click.secho(
600+
"⚠️ Cluster model is not top-tier. Consider using claude-sonnet-4 or gpt-4.",
601+
fg="yellow"
602+
)
603+
549604
# Step 5: API connectivity test (unless --quick)
550-
if not quick:
605+
if caw_mode:
606+
if verbose:
607+
click.echo()
608+
click.echo("[5/5] Checking CLI availability...")
609+
610+
import shutil
611+
cli_name = "claude" if config.provider == "claude-code" else "codex"
612+
cli_path = shutil.which(cli_name)
613+
if not cli_path:
614+
click.secho(f"✗ {cli_name} CLI not found in PATH", fg="red")
615+
click.echo(
616+
f"\nInstall the {cli_name} CLI and run '{cli_name} login' "
617+
f"to authenticate, then re-run this command."
618+
)
619+
sys.exit(EXIT_CONFIG_ERROR)
620+
621+
if verbose:
622+
click.secho(f" ✓ {cli_name} CLI found at {cli_path}", fg="green")
623+
click.secho(
624+
f" ↳ Ensure '{cli_name} login' has been run on this host.",
625+
fg="cyan",
626+
)
627+
else:
628+
click.secho(f"✓ {cli_name} CLI available (run '{cli_name} login' if not yet authenticated)", fg="green")
629+
elif not quick:
551630
if verbose:
552631
click.echo()
553632
click.echo("[5/5] Testing API connectivity...")

0 commit comments

Comments
 (0)