codedd-cli 0.1.1__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.
- codedd_cli/__init__.py +3 -0
- codedd_cli/__main__.py +19 -0
- codedd_cli/api/__init__.py +4 -0
- codedd_cli/api/client.py +120 -0
- codedd_cli/api/endpoints.py +44 -0
- codedd_cli/api/exceptions.py +24 -0
- codedd_cli/auditor/__init__.py +6 -0
- codedd_cli/auditor/architecture_analyzer.py +1251 -0
- codedd_cli/auditor/architecture_prompts.py +173 -0
- codedd_cli/auditor/complexity_analyzer.py +1739 -0
- codedd_cli/auditor/dependency_scanner.py +2485 -0
- codedd_cli/auditor/file_auditor.py +578 -0
- codedd_cli/auditor/git_stats_collector.py +417 -0
- codedd_cli/auditor/response_parser.py +484 -0
- codedd_cli/auditor/vulnerability_validator.py +323 -0
- codedd_cli/auth/__init__.py +4 -0
- codedd_cli/auth/session.py +40 -0
- codedd_cli/auth/token_manager.py +86 -0
- codedd_cli/cli.py +69 -0
- codedd_cli/commands/__init__.py +1 -0
- codedd_cli/commands/audit_cmd.py +1987 -0
- codedd_cli/commands/audits_cmd.py +276 -0
- codedd_cli/commands/auth_cmd.py +235 -0
- codedd_cli/commands/config_cmd.py +421 -0
- codedd_cli/commands/scope_cmd.py +1016 -0
- codedd_cli/config/__init__.py +4 -0
- codedd_cli/config/constants.py +22 -0
- codedd_cli/config/settings.py +389 -0
- codedd_cli/llm/__init__.py +1 -0
- codedd_cli/llm/key_manager.py +267 -0
- codedd_cli/models/__init__.py +5 -0
- codedd_cli/models/account.py +13 -0
- codedd_cli/models/audit.py +31 -0
- codedd_cli/models/local_directory.py +25 -0
- codedd_cli/scanner/__init__.py +18 -0
- codedd_cli/scanner/file_classifier.py +752 -0
- codedd_cli/scanner/file_walker.py +213 -0
- codedd_cli/scanner/line_counter.py +80 -0
- codedd_cli/utils/__init__.py +1 -0
- codedd_cli/utils/directory_validator.py +178 -0
- codedd_cli/utils/display.py +497 -0
- codedd_cli/utils/payload_inspector.py +178 -0
- codedd_cli/utils/security.py +14 -0
- codedd_cli/utils/validators.py +37 -0
- codedd_cli-0.1.1.dist-info/METADATA +306 -0
- codedd_cli-0.1.1.dist-info/RECORD +49 -0
- codedd_cli-0.1.1.dist-info/WHEEL +4 -0
- codedd_cli-0.1.1.dist-info/entry_points.txt +3 -0
- codedd_cli-0.1.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
"""
|
|
2
|
+
``codedd config`` sub-commands.
|
|
3
|
+
|
|
4
|
+
General configuration:
|
|
5
|
+
show – display the current CLI configuration
|
|
6
|
+
set – update a configuration value (e.g. ``api_url``)
|
|
7
|
+
|
|
8
|
+
LLM key management:
|
|
9
|
+
set-key – store an Anthropic or OpenAI API key in the OS keychain
|
|
10
|
+
show-keys – show which LLM providers have keys configured
|
|
11
|
+
remove-key – remove a stored LLM API key
|
|
12
|
+
provider – set the preferred LLM provider (anthropic / openai / both)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import getpass
|
|
16
|
+
|
|
17
|
+
import typer
|
|
18
|
+
from rich.console import Console
|
|
19
|
+
from rich.panel import Panel
|
|
20
|
+
from rich.prompt import Confirm, IntPrompt
|
|
21
|
+
from rich.table import Table
|
|
22
|
+
|
|
23
|
+
from codedd_cli.config.settings import ConfigManager
|
|
24
|
+
from codedd_cli.llm.key_manager import (
|
|
25
|
+
PROVIDER_MODELS,
|
|
26
|
+
VALID_PROVIDERS,
|
|
27
|
+
LLMKeyManager,
|
|
28
|
+
)
|
|
29
|
+
from codedd_cli.utils.display import SYMBOL_FAIL, SYMBOL_INFO, SYMBOL_OK, SYMBOL_WARN
|
|
30
|
+
|
|
31
|
+
console = Console()
|
|
32
|
+
config_app = typer.Typer(no_args_is_help=True)
|
|
33
|
+
|
|
34
|
+
# Keys that users are allowed to modify
|
|
35
|
+
_WRITABLE_KEYS = {
|
|
36
|
+
"api_url": ("server", "api_url"),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@config_app.command("show")
|
|
41
|
+
def show() -> None:
|
|
42
|
+
"""
|
|
43
|
+
Display the current CLI configuration.
|
|
44
|
+
"""
|
|
45
|
+
cfg = ConfigManager()
|
|
46
|
+
|
|
47
|
+
lines = [
|
|
48
|
+
f"[bold]Config file:[/bold] {cfg.config_path}",
|
|
49
|
+
"",
|
|
50
|
+
"[bold underline]Server[/bold underline]",
|
|
51
|
+
f" api_url = {cfg.api_url}",
|
|
52
|
+
"",
|
|
53
|
+
"[bold underline]Session[/bold underline]",
|
|
54
|
+
f" authenticated = {'[green]yes[/green]' if cfg.is_authenticated else '[red]no[/red]'}",
|
|
55
|
+
f" account_name = {cfg.account_name or '—'}",
|
|
56
|
+
f" account_uuid = {cfg.account_uuid[:8] + '…' if cfg.account_uuid else '—'}",
|
|
57
|
+
f" token_hint = {cfg.token_hint or '—'}",
|
|
58
|
+
"",
|
|
59
|
+
"[bold underline]Active Audit[/bold underline]",
|
|
60
|
+
f" audit_uuid = {cfg.active_audit_uuid[:8] + '…' if cfg.active_audit_uuid else '—'}",
|
|
61
|
+
f" audit_type = {cfg.active_audit_type or '—'}",
|
|
62
|
+
f" audit_name = {cfg.active_audit_name or '—'}",
|
|
63
|
+
"",
|
|
64
|
+
"[bold underline]LLM[/bold underline]",
|
|
65
|
+
f" provider = {cfg.llm_provider}",
|
|
66
|
+
f" concurrency = {cfg.llm_concurrency}",
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
console.print(Panel("\n".join(lines), title="CodeDD CLI Configuration", expand=False))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
@config_app.command("set")
|
|
73
|
+
def set_value(
|
|
74
|
+
key: str = typer.Argument(help="Configuration key to set (e.g. api_url)."),
|
|
75
|
+
value: str = typer.Argument(help="Value to assign to the key."),
|
|
76
|
+
) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Update a configuration value.
|
|
79
|
+
|
|
80
|
+
Currently supported keys: ``api_url``
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
codedd config set api_url http://localhost:8000/django_codedd
|
|
84
|
+
codedd config set api_url https://api.codedd.ai/django_codedd
|
|
85
|
+
"""
|
|
86
|
+
if key not in _WRITABLE_KEYS:
|
|
87
|
+
console.print(f"[red]Unknown key:[/red] {key}\nSupported keys: {', '.join(_WRITABLE_KEYS.keys())}")
|
|
88
|
+
raise typer.Exit(code=1)
|
|
89
|
+
|
|
90
|
+
# Validate api_url format
|
|
91
|
+
if key == "api_url":
|
|
92
|
+
value = value.rstrip("/")
|
|
93
|
+
if not (value.startswith("http://") or value.startswith("https://")):
|
|
94
|
+
console.print(
|
|
95
|
+
"[red]Invalid URL format.[/red] "
|
|
96
|
+
"URL must start with http:// or https://\n"
|
|
97
|
+
"Example: [bold]http://localhost:8000/django_codedd[/bold]"
|
|
98
|
+
)
|
|
99
|
+
raise typer.Exit(code=1)
|
|
100
|
+
|
|
101
|
+
# Warn if /django_codedd path is missing
|
|
102
|
+
if "/django_codedd" not in value:
|
|
103
|
+
console.print(
|
|
104
|
+
"[yellow]Warning:[/yellow] URL doesn't include '/django_codedd' path.\n"
|
|
105
|
+
"Did you mean: [bold]" + value + "/django_codedd[/bold] ?\n"
|
|
106
|
+
"[dim]Continuing with the provided URL...[/dim]"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
section, config_key = _WRITABLE_KEYS[key]
|
|
110
|
+
cfg = ConfigManager()
|
|
111
|
+
cfg.set(section, config_key, value)
|
|
112
|
+
|
|
113
|
+
console.print(f"[green]{SYMBOL_OK}[/green] Set [bold]{key}[/bold] = {value}")
|
|
114
|
+
|
|
115
|
+
if key == "api_url":
|
|
116
|
+
console.print("[dim]Tip:[/dim] Use [bold]codedd config show[/bold] to verify your configuration.")
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# codedd config set-key
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@config_app.command("set-key")
|
|
125
|
+
def set_key(
|
|
126
|
+
provider: str | None = typer.Argument(
|
|
127
|
+
None,
|
|
128
|
+
help="LLM provider: 'anthropic' or 'openai'. Prompted if omitted.",
|
|
129
|
+
),
|
|
130
|
+
skip_validation: bool = typer.Option(
|
|
131
|
+
False,
|
|
132
|
+
"--skip-validation",
|
|
133
|
+
help="Store the key without making a validation API call.",
|
|
134
|
+
),
|
|
135
|
+
) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Store an LLM API key securely in the OS keychain.
|
|
138
|
+
|
|
139
|
+
The key is validated with a lightweight API call before storage
|
|
140
|
+
(unless ``--skip-validation`` is passed). If validation fails the
|
|
141
|
+
user is asked whether to store the key anyway.
|
|
142
|
+
|
|
143
|
+
Examples::
|
|
144
|
+
|
|
145
|
+
codedd config set-key anthropic
|
|
146
|
+
codedd config set-key openai
|
|
147
|
+
codedd config set-key # interactive provider prompt
|
|
148
|
+
"""
|
|
149
|
+
# -- Provider selection --
|
|
150
|
+
if provider is None:
|
|
151
|
+
provider = _prompt_provider_choice()
|
|
152
|
+
|
|
153
|
+
provider = provider.lower().strip()
|
|
154
|
+
if provider not in ("anthropic", "openai"):
|
|
155
|
+
console.print(
|
|
156
|
+
f"[red]{SYMBOL_FAIL}[/red] Unknown provider [bold]{provider}[/bold]. "
|
|
157
|
+
"Use [bold]anthropic[/bold] or [bold]openai[/bold]."
|
|
158
|
+
)
|
|
159
|
+
raise typer.Exit(code=1)
|
|
160
|
+
|
|
161
|
+
model = PROVIDER_MODELS.get(provider, "")
|
|
162
|
+
console.print(f"\n[bold]Provider:[/bold] {provider} [dim](model: {model})[/dim]\n")
|
|
163
|
+
|
|
164
|
+
# -- If key already exists, show preview and prompt Keep / Update / Delete --
|
|
165
|
+
existing_key = LLMKeyManager.retrieve_key(provider)
|
|
166
|
+
if existing_key:
|
|
167
|
+
preview = LLMKeyManager.mask_key_preview(existing_key)
|
|
168
|
+
console.print(f" [dim]A key for [bold]{provider}[/bold] is already stored:[/dim] [bold]{preview}[/bold]\n")
|
|
169
|
+
choice = _prompt_keep_update_delete()
|
|
170
|
+
if choice == 1:
|
|
171
|
+
console.print(f" [green]{SYMBOL_OK}[/green] Keeping existing {provider} key.")
|
|
172
|
+
return
|
|
173
|
+
if choice == 3:
|
|
174
|
+
LLMKeyManager.remove_key(provider)
|
|
175
|
+
console.print(f" [green]{SYMBOL_OK}[/green] {provider} API key removed.")
|
|
176
|
+
return
|
|
177
|
+
# choice == 2: Update — fall through to paste/validate/store
|
|
178
|
+
|
|
179
|
+
# -- Key input (masked) --
|
|
180
|
+
try:
|
|
181
|
+
api_key = getpass.getpass(f" Paste your {provider} API key: ").strip()
|
|
182
|
+
except (EOFError, KeyboardInterrupt):
|
|
183
|
+
console.print("\n[dim]Cancelled.[/dim]")
|
|
184
|
+
raise typer.Exit()
|
|
185
|
+
|
|
186
|
+
if not api_key:
|
|
187
|
+
console.print(f"[red]{SYMBOL_FAIL}[/red] No key entered.")
|
|
188
|
+
raise typer.Exit(code=1)
|
|
189
|
+
|
|
190
|
+
# -- Validation --
|
|
191
|
+
if not skip_validation:
|
|
192
|
+
console.print(f"\n[dim]Validating key with {provider} API...[/dim]")
|
|
193
|
+
is_valid, message = LLMKeyManager.validate_key(provider, api_key)
|
|
194
|
+
|
|
195
|
+
if is_valid:
|
|
196
|
+
console.print(f" [green]{SYMBOL_OK}[/green] {message}")
|
|
197
|
+
else:
|
|
198
|
+
console.print(f" [yellow]{SYMBOL_WARN}[/yellow] {message}")
|
|
199
|
+
if not Confirm.ask(" Store the key anyway?", default=False):
|
|
200
|
+
console.print("[dim]Key not stored.[/dim]")
|
|
201
|
+
raise typer.Exit()
|
|
202
|
+
|
|
203
|
+
# -- Store --
|
|
204
|
+
try:
|
|
205
|
+
LLMKeyManager.store_key(provider, api_key)
|
|
206
|
+
except Exception as exc:
|
|
207
|
+
console.print(f"[red]{SYMBOL_FAIL}[/red] Failed to store key in OS keychain: {exc}")
|
|
208
|
+
raise typer.Exit(code=1)
|
|
209
|
+
|
|
210
|
+
masked = LLMKeyManager.mask_key(api_key)
|
|
211
|
+
console.print(f"\n[green]{SYMBOL_OK}[/green] {provider} API key stored [dim]({masked})[/dim]")
|
|
212
|
+
|
|
213
|
+
# -- Auto-set provider preference if this is the first key --
|
|
214
|
+
cfg = ConfigManager()
|
|
215
|
+
configured = LLMKeyManager.get_configured_providers()
|
|
216
|
+
if len(configured) == 1:
|
|
217
|
+
cfg.llm_provider = configured[0]
|
|
218
|
+
cfg.save()
|
|
219
|
+
console.print(f"[dim]{SYMBOL_INFO}[/dim] LLM provider set to [bold]{configured[0]}[/bold].")
|
|
220
|
+
elif len(configured) == 2 and cfg.llm_provider not in VALID_PROVIDERS:
|
|
221
|
+
cfg.llm_provider = "both"
|
|
222
|
+
cfg.save()
|
|
223
|
+
console.print(
|
|
224
|
+
f"[dim]{SYMBOL_INFO}[/dim] Both providers configured — using Anthropic (primary) + OpenAI (fallback)."
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
# ---------------------------------------------------------------------------
|
|
229
|
+
# codedd config show-keys
|
|
230
|
+
# ---------------------------------------------------------------------------
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@config_app.command("show-keys")
|
|
234
|
+
def show_keys() -> None:
|
|
235
|
+
"""
|
|
236
|
+
Show which LLM providers have API keys stored in the OS keychain.
|
|
237
|
+
|
|
238
|
+
Keys are displayed in masked form (first 8 characters only).
|
|
239
|
+
"""
|
|
240
|
+
cfg = ConfigManager()
|
|
241
|
+
preference = cfg.llm_provider
|
|
242
|
+
|
|
243
|
+
table = Table(
|
|
244
|
+
title="LLM API Keys",
|
|
245
|
+
show_header=True,
|
|
246
|
+
padding=(0, 1),
|
|
247
|
+
)
|
|
248
|
+
table.add_column("Provider", style="bold", width=12)
|
|
249
|
+
table.add_column("Key", width=20)
|
|
250
|
+
table.add_column("Model", width=24)
|
|
251
|
+
table.add_column("Status", width=14)
|
|
252
|
+
|
|
253
|
+
for provider in ("anthropic", "openai"):
|
|
254
|
+
key = LLMKeyManager.retrieve_key(provider)
|
|
255
|
+
if key:
|
|
256
|
+
masked = LLMKeyManager.mask_key(key)
|
|
257
|
+
model = PROVIDER_MODELS.get(provider, "")
|
|
258
|
+
# Determine if this provider is active based on preference
|
|
259
|
+
if preference == "both" or preference == provider:
|
|
260
|
+
status = "[green]active[/green]"
|
|
261
|
+
else:
|
|
262
|
+
status = "[dim]stored[/dim]"
|
|
263
|
+
table.add_row(provider, masked, model, status)
|
|
264
|
+
else:
|
|
265
|
+
table.add_row(provider, "[dim]not set[/dim]", "", "[dim]—[/dim]")
|
|
266
|
+
|
|
267
|
+
console.print(table)
|
|
268
|
+
console.print(f"\n[dim]Provider preference:[/dim] [bold]{preference}[/bold]")
|
|
269
|
+
console.print(
|
|
270
|
+
"[dim]Use [bold]codedd config set-key[/bold] to add a key, "
|
|
271
|
+
"[bold]codedd config provider[/bold] to change preference.[/dim]"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# ---------------------------------------------------------------------------
|
|
276
|
+
# codedd config remove-key
|
|
277
|
+
# ---------------------------------------------------------------------------
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@config_app.command("remove-key")
|
|
281
|
+
def remove_key(
|
|
282
|
+
provider: str = typer.Argument(
|
|
283
|
+
help="LLM provider whose key to remove: 'anthropic' or 'openai'.",
|
|
284
|
+
),
|
|
285
|
+
) -> None:
|
|
286
|
+
"""
|
|
287
|
+
Remove an LLM API key from the OS keychain.
|
|
288
|
+
"""
|
|
289
|
+
provider = provider.lower().strip()
|
|
290
|
+
if provider not in ("anthropic", "openai"):
|
|
291
|
+
console.print(
|
|
292
|
+
f"[red]{SYMBOL_FAIL}[/red] Unknown provider [bold]{provider}[/bold]. "
|
|
293
|
+
"Use [bold]anthropic[/bold] or [bold]openai[/bold]."
|
|
294
|
+
)
|
|
295
|
+
raise typer.Exit(code=1)
|
|
296
|
+
|
|
297
|
+
if not LLMKeyManager.has_key(provider):
|
|
298
|
+
console.print(f"[dim]{SYMBOL_INFO}[/dim] No {provider} key is currently stored.")
|
|
299
|
+
raise typer.Exit()
|
|
300
|
+
|
|
301
|
+
if not Confirm.ask(f"Remove the stored [bold]{provider}[/bold] API key?", default=False):
|
|
302
|
+
console.print("[dim]Cancelled.[/dim]")
|
|
303
|
+
raise typer.Exit()
|
|
304
|
+
|
|
305
|
+
removed = LLMKeyManager.remove_key(provider)
|
|
306
|
+
if removed:
|
|
307
|
+
console.print(f"[green]{SYMBOL_OK}[/green] {provider} API key removed.")
|
|
308
|
+
else:
|
|
309
|
+
console.print(f"[red]{SYMBOL_FAIL}[/red] Failed to remove {provider} key.")
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
# ---------------------------------------------------------------------------
|
|
313
|
+
# codedd config provider
|
|
314
|
+
# ---------------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@config_app.command("provider")
|
|
318
|
+
def set_provider(
|
|
319
|
+
value: str = typer.Argument(
|
|
320
|
+
help="Preferred LLM provider: 'anthropic', 'openai', or 'both'.",
|
|
321
|
+
),
|
|
322
|
+
) -> None:
|
|
323
|
+
"""
|
|
324
|
+
Set the preferred LLM provider for audits.
|
|
325
|
+
|
|
326
|
+
- ``anthropic`` — use Anthropic only (claude-sonnet-4-6)
|
|
327
|
+
- ``openai`` — use OpenAI only (gpt-5.2)
|
|
328
|
+
- ``both`` — Anthropic primary, OpenAI fallback (recommended)
|
|
329
|
+
"""
|
|
330
|
+
value = value.lower().strip()
|
|
331
|
+
if value not in VALID_PROVIDERS:
|
|
332
|
+
console.print(
|
|
333
|
+
f"[red]{SYMBOL_FAIL}[/red] Invalid provider [bold]{value}[/bold]. "
|
|
334
|
+
f"Choose one of: {', '.join(VALID_PROVIDERS)}"
|
|
335
|
+
)
|
|
336
|
+
raise typer.Exit(code=1)
|
|
337
|
+
|
|
338
|
+
# Warn if the selected provider has no key stored
|
|
339
|
+
if value in ("anthropic", "openai") and not LLMKeyManager.has_key(value):
|
|
340
|
+
console.print(
|
|
341
|
+
f"[yellow]{SYMBOL_WARN}[/yellow] No {value} API key stored. "
|
|
342
|
+
f"Run [bold cyan]codedd config set-key {value}[/bold cyan] first."
|
|
343
|
+
)
|
|
344
|
+
elif value == "both":
|
|
345
|
+
missing = [p for p in ("anthropic", "openai") if not LLMKeyManager.has_key(p)]
|
|
346
|
+
if missing:
|
|
347
|
+
console.print(
|
|
348
|
+
f"[yellow]{SYMBOL_WARN}[/yellow] Missing key(s) for: "
|
|
349
|
+
f"{', '.join(missing)}. "
|
|
350
|
+
"Add them with [bold cyan]codedd config set-key[/bold cyan]."
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
cfg = ConfigManager()
|
|
354
|
+
cfg.llm_provider = value
|
|
355
|
+
cfg.save()
|
|
356
|
+
console.print(f"[green]{SYMBOL_OK}[/green] LLM provider preference set to [bold]{value}[/bold].")
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
# ---------------------------------------------------------------------------
|
|
360
|
+
# codedd config concurrency
|
|
361
|
+
# ---------------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
@config_app.command("concurrency")
|
|
365
|
+
def set_concurrency(
|
|
366
|
+
value: int = typer.Argument(
|
|
367
|
+
help="Max parallel LLM calls during file auditing (1–32).",
|
|
368
|
+
),
|
|
369
|
+
) -> None:
|
|
370
|
+
"""
|
|
371
|
+
Set the maximum number of concurrent LLM API calls during audits.
|
|
372
|
+
|
|
373
|
+
Higher values speed up audits but may trigger rate-limit errors
|
|
374
|
+
depending on your API tier:
|
|
375
|
+
|
|
376
|
+
\b
|
|
377
|
+
1–2 Free / starter tier (conservative)
|
|
378
|
+
4 Default — works well for most paid tiers
|
|
379
|
+
8–16 High-throughput tiers (Anthropic Scale, OpenAI Tier 4+)
|
|
380
|
+
32 Maximum — only if your provider contract allows it
|
|
381
|
+
"""
|
|
382
|
+
if value < 1 or value > 32:
|
|
383
|
+
console.print(f"[red]{SYMBOL_FAIL}[/red] Value must be between 1 and 32 (got {value}).")
|
|
384
|
+
raise typer.Exit(code=1)
|
|
385
|
+
|
|
386
|
+
cfg = ConfigManager()
|
|
387
|
+
cfg.llm_concurrency = value
|
|
388
|
+
cfg.save()
|
|
389
|
+
console.print(f"[green]{SYMBOL_OK}[/green] LLM concurrency set to [bold]{value}[/bold] parallel calls.")
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
# ---------------------------------------------------------------------------
|
|
393
|
+
# Internal helpers
|
|
394
|
+
# ---------------------------------------------------------------------------
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def _prompt_keep_update_delete() -> int:
|
|
398
|
+
"""Prompt user to Keep (1), Update (2), or Delete (3) the existing key."""
|
|
399
|
+
console.print(
|
|
400
|
+
" [bold cyan]1[/bold cyan] Keep existing key\n"
|
|
401
|
+
" [bold cyan]2[/bold cyan] Update with a new key\n"
|
|
402
|
+
" [bold cyan]3[/bold cyan] Delete stored key\n"
|
|
403
|
+
)
|
|
404
|
+
return IntPrompt.ask(" Enter 1, 2, or 3", choices=["1", "2", "3"], default=1)
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _prompt_provider_choice() -> str:
|
|
408
|
+
"""Interactive prompt to choose a provider when none was supplied."""
|
|
409
|
+
console.print(
|
|
410
|
+
"\n[bold]Select LLM provider:[/bold]\n"
|
|
411
|
+
"[dim]CodeDD uses LLMs to analyze your source code during the audit. "
|
|
412
|
+
"Your API key is stored in your system keychain (or equivalent) and is "
|
|
413
|
+
"never written to config files.[/dim]\n"
|
|
414
|
+
)
|
|
415
|
+
console.print(" [bold cyan]1[/bold cyan] Anthropic [dim](claude-sonnet-4-6)[/dim]")
|
|
416
|
+
console.print(" [bold cyan]2[/bold cyan] OpenAI [dim](gpt-5.2)[/dim]")
|
|
417
|
+
console.print()
|
|
418
|
+
choice = typer.prompt(" Enter 1 or 2", type=int, default=1)
|
|
419
|
+
if choice == 2:
|
|
420
|
+
return "openai"
|
|
421
|
+
return "anthropic"
|