claude-dev-cli 0.1.0__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.

Potentially problematic release.


This version of claude-dev-cli might be problematic. Click here for more details.

@@ -0,0 +1,19 @@
1
+ """
2
+ Claude Dev CLI - A powerful CLI tool for developers using Claude AI.
3
+
4
+ Features:
5
+ - Multi-API key management and routing
6
+ - Test generation for Python projects
7
+ - Code review and analysis
8
+ - Usage tracking and cost monitoring
9
+ - Interactive and single-shot modes
10
+ """
11
+
12
+ __version__ = "0.1.0"
13
+ __author__ = "Julio"
14
+ __license__ = "MIT"
15
+
16
+ from claude_dev_cli.core import ClaudeClient
17
+ from claude_dev_cli.config import Config
18
+
19
+ __all__ = ["ClaudeClient", "Config", "__version__"]
claude_dev_cli/cli.py ADDED
@@ -0,0 +1,398 @@
1
+ """Command-line interface for Claude Dev CLI."""
2
+
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ import click
8
+ from rich.console import Console
9
+ from rich.markdown import Markdown
10
+ from rich.panel import Panel
11
+
12
+ from claude_dev_cli import __version__
13
+ from claude_dev_cli.config import Config
14
+ from claude_dev_cli.core import ClaudeClient
15
+ from claude_dev_cli.commands import (
16
+ generate_tests,
17
+ code_review,
18
+ debug_code,
19
+ generate_docs,
20
+ refactor_code,
21
+ git_commit_message,
22
+ )
23
+ from claude_dev_cli.usage import UsageTracker
24
+
25
+ console = Console()
26
+
27
+
28
+ @click.group()
29
+ @click.version_option(version=__version__)
30
+ @click.pass_context
31
+ def main(ctx: click.Context) -> None:
32
+ """Claude Dev CLI - AI-powered development assistant with multi-API routing."""
33
+ ctx.ensure_object(dict)
34
+ ctx.obj['console'] = console
35
+
36
+
37
+ @main.command()
38
+ @click.argument('prompt', required=False)
39
+ @click.option('-f', '--file', type=click.Path(exists=True), help='Include file content in prompt')
40
+ @click.option('-s', '--system', help='System prompt')
41
+ @click.option('-a', '--api', help='API config to use')
42
+ @click.option('-m', '--model', help='Claude model to use')
43
+ @click.option('--stream/--no-stream', default=True, help='Stream response')
44
+ @click.pass_context
45
+ def ask(
46
+ ctx: click.Context,
47
+ prompt: Optional[str],
48
+ file: Optional[str],
49
+ system: Optional[str],
50
+ api: Optional[str],
51
+ model: Optional[str],
52
+ stream: bool
53
+ ) -> None:
54
+ """Ask Claude a question (single-shot mode)."""
55
+ console = ctx.obj['console']
56
+
57
+ # Build prompt
58
+ prompt_parts = []
59
+
60
+ if file:
61
+ with open(file, 'r') as f:
62
+ file_content = f.read()
63
+ prompt_parts.append(f"File: {file}\n\n{file_content}\n\n")
64
+
65
+ # Read from stdin if available
66
+ if not sys.stdin.isatty():
67
+ stdin_content = sys.stdin.read().strip()
68
+ if stdin_content:
69
+ prompt_parts.append(f"{stdin_content}\n\n")
70
+
71
+ if prompt:
72
+ prompt_parts.append(prompt)
73
+ elif not prompt_parts:
74
+ console.print("[red]Error: No prompt provided[/red]")
75
+ sys.exit(1)
76
+
77
+ full_prompt = ''.join(prompt_parts)
78
+
79
+ try:
80
+ client = ClaudeClient(api_config_name=api)
81
+
82
+ if stream:
83
+ for chunk in client.call_streaming(
84
+ full_prompt,
85
+ system_prompt=system,
86
+ model=model
87
+ ):
88
+ console.print(chunk, end='')
89
+ console.print() # New line at end
90
+ else:
91
+ response = client.call(full_prompt, system_prompt=system, model=model)
92
+ md = Markdown(response)
93
+ console.print(md)
94
+
95
+ except Exception as e:
96
+ console.print(f"[red]Error: {e}[/red]")
97
+ sys.exit(1)
98
+
99
+
100
+ @main.command()
101
+ @click.option('-a', '--api', help='API config to use')
102
+ @click.pass_context
103
+ def interactive(ctx: click.Context, api: Optional[str]) -> None:
104
+ """Start interactive chat mode."""
105
+ console = ctx.obj['console']
106
+
107
+ console.print(Panel.fit(
108
+ "Claude Dev CLI - Interactive Mode\n"
109
+ "Type 'exit' or 'quit' to end\n"
110
+ "Type 'clear' to reset conversation",
111
+ title="Welcome",
112
+ border_style="blue"
113
+ ))
114
+
115
+ try:
116
+ client = ClaudeClient(api_config_name=api)
117
+
118
+ while True:
119
+ try:
120
+ user_input = console.input("\n[bold cyan]You:[/bold cyan] ").strip()
121
+
122
+ if user_input.lower() in ['exit', 'quit']:
123
+ break
124
+ if not user_input:
125
+ continue
126
+
127
+ console.print("\n[bold green]Claude:[/bold green] ", end='')
128
+ for chunk in client.call_streaming(user_input):
129
+ console.print(chunk, end='')
130
+ console.print()
131
+
132
+ except KeyboardInterrupt:
133
+ console.print("\n\n[yellow]Interrupted. Type 'exit' to quit.[/yellow]")
134
+ continue
135
+
136
+ except Exception as e:
137
+ console.print(f"[red]Error: {e}[/red]")
138
+ sys.exit(1)
139
+
140
+
141
+ @main.group()
142
+ def config() -> None:
143
+ """Manage configuration."""
144
+ pass
145
+
146
+
147
+ @config.command('add')
148
+ @click.argument('name')
149
+ @click.option('--api-key', help='API key (or set {NAME}_ANTHROPIC_API_KEY env var)')
150
+ @click.option('--description', help='Description of this API config')
151
+ @click.option('--default', is_flag=True, help='Set as default API config')
152
+ @click.pass_context
153
+ def config_add(
154
+ ctx: click.Context,
155
+ name: str,
156
+ api_key: Optional[str],
157
+ description: Optional[str],
158
+ default: bool
159
+ ) -> None:
160
+ """Add a new API configuration."""
161
+ console = ctx.obj['console']
162
+
163
+ try:
164
+ config = Config()
165
+ config.add_api_config(
166
+ name=name,
167
+ api_key=api_key,
168
+ description=description,
169
+ make_default=default
170
+ )
171
+ console.print(f"[green]✓[/green] Added API config: {name}")
172
+ except Exception as e:
173
+ console.print(f"[red]Error: {e}[/red]")
174
+ sys.exit(1)
175
+
176
+
177
+ @config.command('list')
178
+ @click.pass_context
179
+ def config_list(ctx: click.Context) -> None:
180
+ """List all API configurations."""
181
+ console = ctx.obj['console']
182
+
183
+ config = Config()
184
+ api_configs = config.list_api_configs()
185
+
186
+ if not api_configs:
187
+ console.print("[yellow]No API configurations found.[/yellow]")
188
+ console.print("Run 'cdc config add' to add one.")
189
+ return
190
+
191
+ for cfg in api_configs:
192
+ default_marker = " [bold green](default)[/bold green]" if cfg.default else ""
193
+ console.print(f"• {cfg.name}{default_marker}")
194
+ if cfg.description:
195
+ console.print(f" {cfg.description}")
196
+ console.print(f" API Key: {cfg.api_key[:15]}...")
197
+
198
+
199
+ @main.group()
200
+ def generate() -> None:
201
+ """Generate code, tests, and documentation."""
202
+ pass
203
+
204
+
205
+ @generate.command('tests')
206
+ @click.argument('file_path', type=click.Path(exists=True))
207
+ @click.option('-o', '--output', type=click.Path(), help='Output file path')
208
+ @click.option('-a', '--api', help='API config to use')
209
+ @click.pass_context
210
+ def gen_tests(
211
+ ctx: click.Context,
212
+ file_path: str,
213
+ output: Optional[str],
214
+ api: Optional[str]
215
+ ) -> None:
216
+ """Generate pytest tests for a Python file."""
217
+ console = ctx.obj['console']
218
+
219
+ try:
220
+ with console.status("[bold blue]Generating tests..."):
221
+ result = generate_tests(file_path, api_config_name=api)
222
+
223
+ if output:
224
+ with open(output, 'w') as f:
225
+ f.write(result)
226
+ console.print(f"[green]✓[/green] Tests saved to: {output}")
227
+ else:
228
+ console.print(result)
229
+
230
+ except Exception as e:
231
+ console.print(f"[red]Error: {e}[/red]")
232
+ sys.exit(1)
233
+
234
+
235
+ @generate.command('docs')
236
+ @click.argument('file_path', type=click.Path(exists=True))
237
+ @click.option('-o', '--output', type=click.Path(), help='Output file path')
238
+ @click.option('-a', '--api', help='API config to use')
239
+ @click.pass_context
240
+ def gen_docs(
241
+ ctx: click.Context,
242
+ file_path: str,
243
+ output: Optional[str],
244
+ api: Optional[str]
245
+ ) -> None:
246
+ """Generate documentation for a Python file."""
247
+ console = ctx.obj['console']
248
+
249
+ try:
250
+ with console.status("[bold blue]Generating documentation..."):
251
+ result = generate_docs(file_path, api_config_name=api)
252
+
253
+ if output:
254
+ with open(output, 'w') as f:
255
+ f.write(result)
256
+ console.print(f"[green]✓[/green] Documentation saved to: {output}")
257
+ else:
258
+ md = Markdown(result)
259
+ console.print(md)
260
+
261
+ except Exception as e:
262
+ console.print(f"[red]Error: {e}[/red]")
263
+ sys.exit(1)
264
+
265
+
266
+ @main.command('review')
267
+ @click.argument('file_path', type=click.Path(exists=True))
268
+ @click.option('-a', '--api', help='API config to use')
269
+ @click.pass_context
270
+ def review(
271
+ ctx: click.Context,
272
+ file_path: str,
273
+ api: Optional[str]
274
+ ) -> None:
275
+ """Review code for bugs and improvements."""
276
+ console = ctx.obj['console']
277
+
278
+ try:
279
+ with console.status("[bold blue]Reviewing code..."):
280
+ result = code_review(file_path, api_config_name=api)
281
+
282
+ md = Markdown(result)
283
+ console.print(md)
284
+
285
+ except Exception as e:
286
+ console.print(f"[red]Error: {e}[/red]")
287
+ sys.exit(1)
288
+
289
+
290
+ @main.command('debug')
291
+ @click.option('-f', '--file', type=click.Path(exists=True), help='File to debug')
292
+ @click.option('-e', '--error', help='Error message to analyze')
293
+ @click.option('-a', '--api', help='API config to use')
294
+ @click.pass_context
295
+ def debug(
296
+ ctx: click.Context,
297
+ file: Optional[str],
298
+ error: Optional[str],
299
+ api: Optional[str]
300
+ ) -> None:
301
+ """Debug code and analyze errors."""
302
+ console = ctx.obj['console']
303
+
304
+ # Read from stdin if available
305
+ stdin_content = None
306
+ if not sys.stdin.isatty():
307
+ stdin_content = sys.stdin.read().strip()
308
+
309
+ try:
310
+ with console.status("[bold blue]Analyzing error..."):
311
+ result = debug_code(
312
+ file_path=file,
313
+ error_message=error or stdin_content,
314
+ api_config_name=api
315
+ )
316
+
317
+ md = Markdown(result)
318
+ console.print(md)
319
+
320
+ except Exception as e:
321
+ console.print(f"[red]Error: {e}[/red]")
322
+ sys.exit(1)
323
+
324
+
325
+ @main.command('refactor')
326
+ @click.argument('file_path', type=click.Path(exists=True))
327
+ @click.option('-o', '--output', type=click.Path(), help='Output file path')
328
+ @click.option('-a', '--api', help='API config to use')
329
+ @click.pass_context
330
+ def refactor(
331
+ ctx: click.Context,
332
+ file_path: str,
333
+ output: Optional[str],
334
+ api: Optional[str]
335
+ ) -> None:
336
+ """Suggest refactoring improvements."""
337
+ console = ctx.obj['console']
338
+
339
+ try:
340
+ with console.status("[bold blue]Analyzing code..."):
341
+ result = refactor_code(file_path, api_config_name=api)
342
+
343
+ if output:
344
+ with open(output, 'w') as f:
345
+ f.write(result)
346
+ console.print(f"[green]✓[/green] Refactored code saved to: {output}")
347
+ else:
348
+ md = Markdown(result)
349
+ console.print(md)
350
+
351
+ except Exception as e:
352
+ console.print(f"[red]Error: {e}[/red]")
353
+ sys.exit(1)
354
+
355
+
356
+ @main.group()
357
+ def git() -> None:
358
+ """Git workflow helpers."""
359
+ pass
360
+
361
+
362
+ @git.command('commit')
363
+ @click.option('-a', '--api', help='API config to use')
364
+ @click.pass_context
365
+ def git_commit(ctx: click.Context, api: Optional[str]) -> None:
366
+ """Generate commit message from staged changes."""
367
+ console = ctx.obj['console']
368
+
369
+ try:
370
+ with console.status("[bold blue]Analyzing changes..."):
371
+ result = git_commit_message(api_config_name=api)
372
+
373
+ console.print("\n[bold green]Suggested commit message:[/bold green]")
374
+ console.print(Panel(result, border_style="green"))
375
+
376
+ except Exception as e:
377
+ console.print(f"[red]Error: {e}[/red]")
378
+ sys.exit(1)
379
+
380
+
381
+ @main.command('usage')
382
+ @click.option('--days', type=int, help='Filter by days')
383
+ @click.option('--api', help='Filter by API config')
384
+ @click.pass_context
385
+ def usage(ctx: click.Context, days: Optional[int], api: Optional[str]) -> None:
386
+ """Show API usage statistics."""
387
+ console = ctx.obj['console']
388
+
389
+ try:
390
+ tracker = UsageTracker()
391
+ tracker.display_usage(console, days=days, api_config=api)
392
+ except Exception as e:
393
+ console.print(f"[red]Error: {e}[/red]")
394
+ sys.exit(1)
395
+
396
+
397
+ if __name__ == '__main__':
398
+ main(obj={})
@@ -0,0 +1,133 @@
1
+ """Developer-specific commands for Claude Dev CLI."""
2
+
3
+ import subprocess
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from claude_dev_cli.core import ClaudeClient
8
+ from claude_dev_cli.templates import (
9
+ TEST_GENERATION_PROMPT,
10
+ CODE_REVIEW_PROMPT,
11
+ DEBUG_PROMPT,
12
+ DOCS_GENERATION_PROMPT,
13
+ REFACTOR_PROMPT,
14
+ GIT_COMMIT_PROMPT,
15
+ )
16
+
17
+
18
+ def generate_tests(file_path: str, api_config_name: Optional[str] = None) -> str:
19
+ """Generate pytest tests for a Python file."""
20
+ with open(file_path, 'r') as f:
21
+ code = f.read()
22
+
23
+ prompt = TEST_GENERATION_PROMPT.format(
24
+ filename=Path(file_path).name,
25
+ code=code
26
+ )
27
+
28
+ client = ClaudeClient(api_config_name=api_config_name)
29
+ return client.call(prompt, system_prompt="You are a Python testing expert.")
30
+
31
+
32
+ def code_review(file_path: str, api_config_name: Optional[str] = None) -> str:
33
+ """Review code for bugs and improvements."""
34
+ with open(file_path, 'r') as f:
35
+ code = f.read()
36
+
37
+ prompt = CODE_REVIEW_PROMPT.format(
38
+ filename=Path(file_path).name,
39
+ code=code
40
+ )
41
+
42
+ client = ClaudeClient(api_config_name=api_config_name)
43
+ return client.call(
44
+ prompt,
45
+ system_prompt="You are a senior code reviewer focused on security, performance, and best practices."
46
+ )
47
+
48
+
49
+ def debug_code(
50
+ file_path: Optional[str] = None,
51
+ error_message: Optional[str] = None,
52
+ api_config_name: Optional[str] = None
53
+ ) -> str:
54
+ """Debug code and analyze errors."""
55
+ code = ""
56
+ if file_path:
57
+ with open(file_path, 'r') as f:
58
+ code = f.read()
59
+
60
+ prompt = DEBUG_PROMPT.format(
61
+ filename=Path(file_path).name if file_path else "unknown",
62
+ code=code,
63
+ error=error_message or "No error message provided"
64
+ )
65
+
66
+ client = ClaudeClient(api_config_name=api_config_name)
67
+ return client.call(
68
+ prompt,
69
+ system_prompt="You are a debugging expert. Analyze errors and provide fixes."
70
+ )
71
+
72
+
73
+ def generate_docs(file_path: str, api_config_name: Optional[str] = None) -> str:
74
+ """Generate documentation for a Python file."""
75
+ with open(file_path, 'r') as f:
76
+ code = f.read()
77
+
78
+ prompt = DOCS_GENERATION_PROMPT.format(
79
+ filename=Path(file_path).name,
80
+ code=code
81
+ )
82
+
83
+ client = ClaudeClient(api_config_name=api_config_name)
84
+ return client.call(
85
+ prompt,
86
+ system_prompt="You are a technical documentation expert."
87
+ )
88
+
89
+
90
+ def refactor_code(file_path: str, api_config_name: Optional[str] = None) -> str:
91
+ """Suggest refactoring improvements."""
92
+ with open(file_path, 'r') as f:
93
+ code = f.read()
94
+
95
+ prompt = REFACTOR_PROMPT.format(
96
+ filename=Path(file_path).name,
97
+ code=code
98
+ )
99
+
100
+ client = ClaudeClient(api_config_name=api_config_name)
101
+ return client.call(
102
+ prompt,
103
+ system_prompt="You are a refactoring expert focused on code maintainability and readability."
104
+ )
105
+
106
+
107
+ def git_commit_message(api_config_name: Optional[str] = None) -> str:
108
+ """Generate commit message from staged changes."""
109
+ try:
110
+ # Get staged changes
111
+ result = subprocess.run(
112
+ ['git', '--no-pager', 'diff', '--cached'],
113
+ capture_output=True,
114
+ text=True,
115
+ check=True
116
+ )
117
+ diff = result.stdout
118
+
119
+ if not diff:
120
+ raise ValueError("No staged changes found. Run 'git add' first.")
121
+
122
+ prompt = GIT_COMMIT_PROMPT.format(diff=diff)
123
+
124
+ client = ClaudeClient(api_config_name=api_config_name)
125
+ return client.call(
126
+ prompt,
127
+ system_prompt="You are a git commit message expert. Write clear, conventional commit messages."
128
+ )
129
+
130
+ except subprocess.CalledProcessError as e:
131
+ raise ValueError(f"Git command failed: {e}")
132
+ except FileNotFoundError:
133
+ raise ValueError("Git is not installed or not in PATH")