claude-dev-cli 0.10.1__py3-none-any.whl → 0.12.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.
- claude_dev_cli/__init__.py +1 -1
- claude_dev_cli/cli.py +617 -53
- claude_dev_cli/input_sources.py +231 -0
- claude_dev_cli/path_utils.py +174 -0
- {claude_dev_cli-0.10.1.dist-info → claude_dev_cli-0.12.0.dist-info}/METADATA +114 -8
- {claude_dev_cli-0.10.1.dist-info → claude_dev_cli-0.12.0.dist-info}/RECORD +10 -8
- {claude_dev_cli-0.10.1.dist-info → claude_dev_cli-0.12.0.dist-info}/WHEEL +0 -0
- {claude_dev_cli-0.10.1.dist-info → claude_dev_cli-0.12.0.dist-info}/entry_points.txt +0 -0
- {claude_dev_cli-0.10.1.dist-info → claude_dev_cli-0.12.0.dist-info}/licenses/LICENSE +0 -0
- {claude_dev_cli-0.10.1.dist-info → claude_dev_cli-0.12.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
"""Input source handlers for reading specifications from various sources."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional, Tuple
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def read_text_input(text: str) -> str:
|
|
9
|
+
"""Read text input directly."""
|
|
10
|
+
return text
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def read_file_input(file_path: str) -> str:
|
|
14
|
+
"""Read input from a file.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
file_path: Path to the file
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
File contents as string
|
|
21
|
+
|
|
22
|
+
Raises:
|
|
23
|
+
FileNotFoundError: If file doesn't exist
|
|
24
|
+
IOError: If file can't be read
|
|
25
|
+
"""
|
|
26
|
+
path = Path(file_path)
|
|
27
|
+
if not path.exists():
|
|
28
|
+
raise FileNotFoundError(f"File not found: {file_path}")
|
|
29
|
+
|
|
30
|
+
return path.read_text(encoding='utf-8')
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def read_pdf_input(pdf_path: str) -> str:
|
|
34
|
+
"""Read text content from a PDF file.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
pdf_path: Path to the PDF file
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Extracted text from PDF
|
|
41
|
+
|
|
42
|
+
Raises:
|
|
43
|
+
ImportError: If pypdf is not installed
|
|
44
|
+
FileNotFoundError: If PDF doesn't exist
|
|
45
|
+
Exception: If PDF can't be parsed
|
|
46
|
+
"""
|
|
47
|
+
try:
|
|
48
|
+
from pypdf import PdfReader
|
|
49
|
+
except ImportError:
|
|
50
|
+
raise ImportError(
|
|
51
|
+
"PDF support requires pypdf. Install with: "
|
|
52
|
+
"pip install 'claude-dev-cli[generation]'"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
path = Path(pdf_path)
|
|
56
|
+
if not path.exists():
|
|
57
|
+
raise FileNotFoundError(f"PDF not found: {pdf_path}")
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
reader = PdfReader(path)
|
|
61
|
+
text_parts = []
|
|
62
|
+
for page in reader.pages:
|
|
63
|
+
text = page.extract_text()
|
|
64
|
+
if text:
|
|
65
|
+
text_parts.append(text)
|
|
66
|
+
|
|
67
|
+
if not text_parts:
|
|
68
|
+
raise ValueError(f"No text could be extracted from PDF: {pdf_path}")
|
|
69
|
+
|
|
70
|
+
return "\n\n".join(text_parts)
|
|
71
|
+
except Exception as e:
|
|
72
|
+
raise Exception(f"Failed to read PDF {pdf_path}: {str(e)}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def read_url_input(url: str) -> str:
|
|
76
|
+
"""Fetch and extract text content from a URL.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
url: URL to fetch
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Extracted text content
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
ImportError: If requests or beautifulsoup4 are not installed
|
|
86
|
+
Exception: If URL can't be fetched or parsed
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
import requests
|
|
90
|
+
from bs4 import BeautifulSoup
|
|
91
|
+
except ImportError:
|
|
92
|
+
raise ImportError(
|
|
93
|
+
"URL support requires requests and beautifulsoup4. Install with: "
|
|
94
|
+
"pip install 'claude-dev-cli[generation]'"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Validate URL format
|
|
98
|
+
if not url.startswith(('http://', 'https://')):
|
|
99
|
+
raise ValueError(f"Invalid URL format: {url}")
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
# Fetch content
|
|
103
|
+
response = requests.get(url, timeout=30, headers={
|
|
104
|
+
'User-Agent': 'claude-dev-cli/0.11.0'
|
|
105
|
+
})
|
|
106
|
+
response.raise_for_status()
|
|
107
|
+
|
|
108
|
+
# Determine content type
|
|
109
|
+
content_type = response.headers.get('Content-Type', '').lower()
|
|
110
|
+
|
|
111
|
+
if 'text/plain' in content_type:
|
|
112
|
+
# Plain text - return as-is
|
|
113
|
+
return response.text
|
|
114
|
+
|
|
115
|
+
elif 'text/html' in content_type or 'application/xhtml' in content_type:
|
|
116
|
+
# HTML - extract text
|
|
117
|
+
soup = BeautifulSoup(response.content, 'html.parser')
|
|
118
|
+
|
|
119
|
+
# Remove script and style elements
|
|
120
|
+
for script in soup(['script', 'style', 'nav', 'footer', 'header']):
|
|
121
|
+
script.decompose()
|
|
122
|
+
|
|
123
|
+
# Get text
|
|
124
|
+
text = soup.get_text(separator='\n', strip=True)
|
|
125
|
+
|
|
126
|
+
# Clean up extra whitespace
|
|
127
|
+
lines = [line.strip() for line in text.splitlines() if line.strip()]
|
|
128
|
+
return '\n'.join(lines)
|
|
129
|
+
|
|
130
|
+
elif 'application/json' in content_type:
|
|
131
|
+
# JSON - return formatted
|
|
132
|
+
import json
|
|
133
|
+
return json.dumps(response.json(), indent=2)
|
|
134
|
+
|
|
135
|
+
else:
|
|
136
|
+
# Unknown content type - try to decode as text
|
|
137
|
+
return response.text
|
|
138
|
+
|
|
139
|
+
except requests.RequestException as e:
|
|
140
|
+
raise Exception(f"Failed to fetch URL {url}: {str(e)}")
|
|
141
|
+
except Exception as e:
|
|
142
|
+
raise Exception(f"Failed to parse content from {url}: {str(e)}")
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def get_input_content(
|
|
146
|
+
description: Optional[str] = None,
|
|
147
|
+
file_path: Optional[str] = None,
|
|
148
|
+
pdf_path: Optional[str] = None,
|
|
149
|
+
url: Optional[str] = None,
|
|
150
|
+
console: Optional[Console] = None
|
|
151
|
+
) -> Tuple[str, str]:
|
|
152
|
+
"""Get input content from one of the available sources.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
description: Direct text description
|
|
156
|
+
file_path: Path to file
|
|
157
|
+
pdf_path: Path to PDF
|
|
158
|
+
url: URL to fetch
|
|
159
|
+
console: Rich console for output
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Tuple of (content, source_description)
|
|
163
|
+
|
|
164
|
+
Raises:
|
|
165
|
+
ValueError: If multiple sources or no sources provided
|
|
166
|
+
Various exceptions from individual read functions
|
|
167
|
+
"""
|
|
168
|
+
if console is None:
|
|
169
|
+
console = Console()
|
|
170
|
+
|
|
171
|
+
# Count how many sources are provided
|
|
172
|
+
sources = [
|
|
173
|
+
('description', description),
|
|
174
|
+
('file', file_path),
|
|
175
|
+
('pdf', pdf_path),
|
|
176
|
+
('url', url)
|
|
177
|
+
]
|
|
178
|
+
provided_sources = [(name, value) for name, value in sources if value]
|
|
179
|
+
|
|
180
|
+
if len(provided_sources) == 0:
|
|
181
|
+
raise ValueError(
|
|
182
|
+
"No input source provided. Use one of:\n"
|
|
183
|
+
" --description TEXT\n"
|
|
184
|
+
" -f/--file PATH\n"
|
|
185
|
+
" --pdf PATH\n"
|
|
186
|
+
" --url URL"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if len(provided_sources) > 1:
|
|
190
|
+
source_names = [name for name, _ in provided_sources]
|
|
191
|
+
raise ValueError(
|
|
192
|
+
f"Multiple input sources provided: {', '.join(source_names)}. "
|
|
193
|
+
f"Please use only one."
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
source_type, source_value = provided_sources[0]
|
|
197
|
+
|
|
198
|
+
# Read from the appropriate source
|
|
199
|
+
if source_type == 'description':
|
|
200
|
+
content = read_text_input(source_value)
|
|
201
|
+
source_desc = "text description"
|
|
202
|
+
|
|
203
|
+
elif source_type == 'file':
|
|
204
|
+
console.print(f"[cyan]Reading from file:[/cyan] {source_value}")
|
|
205
|
+
content = read_file_input(source_value)
|
|
206
|
+
source_desc = f"file: {source_value}"
|
|
207
|
+
|
|
208
|
+
elif source_type == 'pdf':
|
|
209
|
+
console.print(f"[cyan]Extracting text from PDF:[/cyan] {source_value}")
|
|
210
|
+
try:
|
|
211
|
+
content = read_pdf_input(source_value)
|
|
212
|
+
console.print(f"[green]✓[/green] Extracted {len(content)} characters from PDF")
|
|
213
|
+
source_desc = f"PDF: {source_value}"
|
|
214
|
+
except ImportError as e:
|
|
215
|
+
console.print(f"[red]Error:[/red] {str(e)}")
|
|
216
|
+
raise
|
|
217
|
+
|
|
218
|
+
elif source_type == 'url':
|
|
219
|
+
console.print(f"[cyan]Fetching content from URL:[/cyan] {source_value}")
|
|
220
|
+
try:
|
|
221
|
+
content = read_url_input(source_value)
|
|
222
|
+
console.print(f"[green]✓[/green] Fetched {len(content)} characters from URL")
|
|
223
|
+
source_desc = f"URL: {source_value}"
|
|
224
|
+
except ImportError as e:
|
|
225
|
+
console.print(f"[red]Error:[/red] {str(e)}")
|
|
226
|
+
raise
|
|
227
|
+
|
|
228
|
+
else:
|
|
229
|
+
raise ValueError(f"Unknown source type: {source_type}")
|
|
230
|
+
|
|
231
|
+
return content, source_desc
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Path expansion and git change detection utilities."""
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Set, Optional
|
|
6
|
+
|
|
7
|
+
# Common code file extensions
|
|
8
|
+
CODE_EXTENSIONS = {
|
|
9
|
+
'.py', '.js', '.ts', '.jsx', '.tsx', '.go', '.rs', '.java', '.cpp', '.c',
|
|
10
|
+
'.h', '.hpp', '.cs', '.rb', '.php', '.swift', '.kt', '.scala', '.r',
|
|
11
|
+
'.m', '.mm', '.sh', '.bash', '.zsh', '.fish', '.lua', '.pl', '.sql',
|
|
12
|
+
'.html', '.css', '.scss', '.sass', '.less', '.vue', '.svelte'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def is_code_file(path: Path) -> bool:
|
|
17
|
+
"""Check if file is a code file based on extension."""
|
|
18
|
+
return path.suffix.lower() in CODE_EXTENSIONS
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def expand_paths(
|
|
22
|
+
paths: List[str],
|
|
23
|
+
max_files: Optional[int] = None,
|
|
24
|
+
recursive: bool = True
|
|
25
|
+
) -> List[Path]:
|
|
26
|
+
"""Expand paths (files, directories, globs) to list of code files.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
paths: List of file/directory paths
|
|
30
|
+
max_files: Maximum number of files to return (None = unlimited)
|
|
31
|
+
recursive: Whether to recursively search directories
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
List of Path objects for code files
|
|
35
|
+
"""
|
|
36
|
+
result_files: Set[Path] = set()
|
|
37
|
+
|
|
38
|
+
for path_str in paths:
|
|
39
|
+
path = Path(path_str).resolve()
|
|
40
|
+
|
|
41
|
+
if not path.exists():
|
|
42
|
+
continue
|
|
43
|
+
|
|
44
|
+
if path.is_file():
|
|
45
|
+
# Add single file
|
|
46
|
+
result_files.add(path)
|
|
47
|
+
elif path.is_dir():
|
|
48
|
+
# Expand directory
|
|
49
|
+
if recursive:
|
|
50
|
+
# Recursively find all code files
|
|
51
|
+
for file_path in path.rglob('*'):
|
|
52
|
+
if file_path.is_file() and is_code_file(file_path):
|
|
53
|
+
result_files.add(file_path)
|
|
54
|
+
if max_files and len(result_files) >= max_files:
|
|
55
|
+
break
|
|
56
|
+
else:
|
|
57
|
+
# Only direct children
|
|
58
|
+
for file_path in path.glob('*'):
|
|
59
|
+
if file_path.is_file() and is_code_file(file_path):
|
|
60
|
+
result_files.add(file_path)
|
|
61
|
+
if max_files and len(result_files) >= max_files:
|
|
62
|
+
break
|
|
63
|
+
|
|
64
|
+
if max_files and len(result_files) >= max_files:
|
|
65
|
+
break
|
|
66
|
+
|
|
67
|
+
# Sort for consistent ordering
|
|
68
|
+
return sorted(result_files)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def get_git_changes(
|
|
72
|
+
staged_only: bool = False,
|
|
73
|
+
include_untracked: bool = False,
|
|
74
|
+
commit_range: Optional[str] = None
|
|
75
|
+
) -> List[Path]:
|
|
76
|
+
"""Get list of changed files from git.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
staged_only: Only return staged files
|
|
80
|
+
include_untracked: Include untracked files
|
|
81
|
+
commit_range: Git commit range (e.g., "main..HEAD")
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
List of Path objects for changed files
|
|
85
|
+
"""
|
|
86
|
+
files: Set[Path] = set()
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
if commit_range:
|
|
90
|
+
# Get files changed in commit range
|
|
91
|
+
result = subprocess.run(
|
|
92
|
+
['git', 'diff', '--name-only', commit_range],
|
|
93
|
+
capture_output=True,
|
|
94
|
+
text=True,
|
|
95
|
+
check=True
|
|
96
|
+
)
|
|
97
|
+
for line in result.stdout.strip().split('\n'):
|
|
98
|
+
if line:
|
|
99
|
+
path = Path(line)
|
|
100
|
+
if path.exists():
|
|
101
|
+
files.add(path)
|
|
102
|
+
elif staged_only:
|
|
103
|
+
# Get only staged files
|
|
104
|
+
result = subprocess.run(
|
|
105
|
+
['git', 'diff', '--cached', '--name-only'],
|
|
106
|
+
capture_output=True,
|
|
107
|
+
text=True,
|
|
108
|
+
check=True
|
|
109
|
+
)
|
|
110
|
+
for line in result.stdout.strip().split('\n'):
|
|
111
|
+
if line:
|
|
112
|
+
path = Path(line)
|
|
113
|
+
if path.exists():
|
|
114
|
+
files.add(path)
|
|
115
|
+
else:
|
|
116
|
+
# Get all modified files (staged + unstaged)
|
|
117
|
+
result = subprocess.run(
|
|
118
|
+
['git', 'diff', '--name-only', 'HEAD'],
|
|
119
|
+
capture_output=True,
|
|
120
|
+
text=True,
|
|
121
|
+
check=True
|
|
122
|
+
)
|
|
123
|
+
for line in result.stdout.strip().split('\n'):
|
|
124
|
+
if line:
|
|
125
|
+
path = Path(line)
|
|
126
|
+
if path.exists():
|
|
127
|
+
files.add(path)
|
|
128
|
+
|
|
129
|
+
if include_untracked:
|
|
130
|
+
# Add untracked files
|
|
131
|
+
result = subprocess.run(
|
|
132
|
+
['git', 'ls-files', '--others', '--exclude-standard'],
|
|
133
|
+
capture_output=True,
|
|
134
|
+
text=True,
|
|
135
|
+
check=True
|
|
136
|
+
)
|
|
137
|
+
for line in result.stdout.strip().split('\n'):
|
|
138
|
+
if line:
|
|
139
|
+
path = Path(line)
|
|
140
|
+
if path.exists():
|
|
141
|
+
files.add(path)
|
|
142
|
+
except subprocess.CalledProcessError:
|
|
143
|
+
# Not a git repo or git command failed
|
|
144
|
+
return []
|
|
145
|
+
|
|
146
|
+
return sorted(files)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def auto_detect_files(cwd: Optional[Path] = None) -> List[Path]:
|
|
150
|
+
"""Auto-detect files to process based on git status.
|
|
151
|
+
|
|
152
|
+
Priority:
|
|
153
|
+
1. Staged files
|
|
154
|
+
2. Modified files (staged + unstaged)
|
|
155
|
+
3. All code files in current directory
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
List of Path objects, empty list if none found
|
|
159
|
+
"""
|
|
160
|
+
if cwd is None:
|
|
161
|
+
cwd = Path.cwd()
|
|
162
|
+
|
|
163
|
+
# Try staged files first
|
|
164
|
+
files = get_git_changes(staged_only=True)
|
|
165
|
+
if files:
|
|
166
|
+
return files
|
|
167
|
+
|
|
168
|
+
# Try all modified files
|
|
169
|
+
files = get_git_changes(staged_only=False)
|
|
170
|
+
if files:
|
|
171
|
+
return files
|
|
172
|
+
|
|
173
|
+
# Fallback: all code files in current directory (non-recursive)
|
|
174
|
+
return expand_paths([str(cwd)], recursive=False)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-dev-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.12.0
|
|
4
4
|
Summary: A powerful CLI tool for developers using Claude AI with multi-API routing, test generation, code review, and usage tracking
|
|
5
5
|
Author-email: Julio <thinmanj@users.noreply.github.com>
|
|
6
6
|
License: MIT
|
|
@@ -33,6 +33,10 @@ Provides-Extra: toon
|
|
|
33
33
|
Requires-Dist: toon-format>=0.1.0; extra == "toon"
|
|
34
34
|
Provides-Extra: plugins
|
|
35
35
|
Requires-Dist: pygments>=2.0.0; extra == "plugins"
|
|
36
|
+
Provides-Extra: generation
|
|
37
|
+
Requires-Dist: pypdf>=3.0.0; extra == "generation"
|
|
38
|
+
Requires-Dist: requests>=2.28.0; extra == "generation"
|
|
39
|
+
Requires-Dist: beautifulsoup4>=4.0.0; extra == "generation"
|
|
36
40
|
Provides-Extra: dev
|
|
37
41
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
38
42
|
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
@@ -40,13 +44,16 @@ Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
|
40
44
|
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
41
45
|
Requires-Dist: toon-format>=0.1.0; extra == "dev"
|
|
42
46
|
Requires-Dist: pygments>=2.0.0; extra == "dev"
|
|
47
|
+
Requires-Dist: pypdf>=3.0.0; extra == "dev"
|
|
48
|
+
Requires-Dist: requests>=2.28.0; extra == "dev"
|
|
49
|
+
Requires-Dist: beautifulsoup4>=4.0.0; extra == "dev"
|
|
43
50
|
Dynamic: license-file
|
|
44
51
|
|
|
45
52
|
# Claude Dev CLI
|
|
46
53
|
|
|
47
54
|
[](https://badge.fury.io/py/claude-dev-cli)
|
|
48
55
|
[](https://www.python.org/downloads/)
|
|
49
|
-
[](https://github.com/thinmanj/claude-dev-cli)
|
|
50
57
|
[](https://opensource.org/licenses/MIT)
|
|
51
58
|
[](https://github.com/thinmanj/homebrew-tap)
|
|
52
59
|
[](https://github.com/psf/black)
|
|
@@ -72,6 +79,39 @@ A powerful command-line tool for developers using Claude AI with multi-API routi
|
|
|
72
79
|
- `smart`: Claude Sonnet 4 ($3.00/$15.00 per Mtok) - default
|
|
73
80
|
- `powerful`: Claude Opus 4 ($15.00/$75.00 per Mtok)
|
|
74
81
|
|
|
82
|
+
### 🚀 Code Generation (v0.12.0+)
|
|
83
|
+
- **Generate Code from Specs**: Create new code from descriptions, files, PDFs, or URLs
|
|
84
|
+
- `cdc generate code --description "REST API client" -o client.py`
|
|
85
|
+
- Multiple input sources: text, files (.md, .txt), PDFs, URLs
|
|
86
|
+
- Auto-detects target language from file extension
|
|
87
|
+
- Interactive refinement mode
|
|
88
|
+
- **Add Features to Projects**: Analyze existing code and generate implementation plans
|
|
89
|
+
- `cdc generate feature --description "Add authentication" src/`
|
|
90
|
+
- Multi-file analysis and modification
|
|
91
|
+
- Preview mode to review changes before applying
|
|
92
|
+
- Supports same input sources as code generation
|
|
93
|
+
- **Multiple Input Sources**:
|
|
94
|
+
- `--description TEXT`: Inline specification
|
|
95
|
+
- `-f/--file PATH`: Read from file
|
|
96
|
+
- `--pdf PATH`: Extract from PDF
|
|
97
|
+
- `--url URL`: Fetch from URL
|
|
98
|
+
- **Optional Dependencies**: Install with `pip install 'claude-dev-cli[generation]'`
|
|
99
|
+
- Enables PDF and URL support
|
|
100
|
+
- Graceful fallback if not installed
|
|
101
|
+
|
|
102
|
+
### 📁 Multi-File Support (v0.11.0+)
|
|
103
|
+
- **Batch Processing**: Review, refactor, test, or document multiple files at once
|
|
104
|
+
- **Directory Support**: Process all code files in a directory with `--max-files` limit
|
|
105
|
+
- **Auto-Detection**: Commands auto-detect git changes when no files specified
|
|
106
|
+
- `cdc review` → reviews staged files, falls back to modified files, then current directory
|
|
107
|
+
- **Git Integration**: New `cdc git review` command for reviewing changes
|
|
108
|
+
- `--staged`: Review only staged changes
|
|
109
|
+
- `--branch <range>`: Review branch changes (e.g., `main..HEAD`)
|
|
110
|
+
- **Multi-Language**: Supports 25+ file extensions (Python, JS/TS, Go, Rust, Java, C++, etc.)
|
|
111
|
+
- **Smart Display**: Shows file list preview (first 5-10 files, then "... and N more")
|
|
112
|
+
- Commands with multi-file support:
|
|
113
|
+
- `review`, `refactor`, `generate tests`, `generate docs`
|
|
114
|
+
|
|
75
115
|
### 🧪 Developer Tools
|
|
76
116
|
- **Test Generation**: Automatic pytest test generation for Python code
|
|
77
117
|
- **Code Review**: Comprehensive code reviews with security, performance, and best practice checks
|
|
@@ -136,8 +176,14 @@ brew install thinmanj/tap/claude-dev-cli
|
|
|
136
176
|
# Basic installation
|
|
137
177
|
pip install claude-dev-cli
|
|
138
178
|
|
|
179
|
+
# With code generation support (PDF & URL input)
|
|
180
|
+
pip install claude-dev-cli[generation]
|
|
181
|
+
|
|
139
182
|
# With TOON support (30-60% token reduction)
|
|
140
183
|
pip install claude-dev-cli[toon]
|
|
184
|
+
|
|
185
|
+
# With all optional features
|
|
186
|
+
pip install claude-dev-cli[generation,toon]
|
|
141
187
|
```
|
|
142
188
|
|
|
143
189
|
### Via pipx (Recommended for CLI tools)
|
|
@@ -146,8 +192,14 @@ pip install claude-dev-cli[toon]
|
|
|
146
192
|
# Isolated installation
|
|
147
193
|
pipx install claude-dev-cli
|
|
148
194
|
|
|
195
|
+
# With code generation support
|
|
196
|
+
pipx install claude-dev-cli[generation]
|
|
197
|
+
|
|
149
198
|
# With TOON support
|
|
150
199
|
pipx install claude-dev-cli[toon]
|
|
200
|
+
|
|
201
|
+
# With all optional features
|
|
202
|
+
pipx install claude-dev-cli[generation,toon]
|
|
151
203
|
```
|
|
152
204
|
|
|
153
205
|
## Quick Start
|
|
@@ -229,39 +281,93 @@ cdc review -m powerful complex_file.py # More thorough review
|
|
|
229
281
|
cdc generate tests -m smart mymodule.py # Balanced approach
|
|
230
282
|
```
|
|
231
283
|
|
|
232
|
-
### 3.
|
|
284
|
+
### 3. Code Generation Commands (NEW in v0.12.0)
|
|
285
|
+
|
|
286
|
+
```bash
|
|
287
|
+
# Generate code from specification
|
|
288
|
+
cdc generate code --description "REST API client for weather data" -o client.py
|
|
289
|
+
cdc generate code --file spec.md -o implementation.go
|
|
290
|
+
cdc generate code --pdf requirements.pdf -o app.js
|
|
291
|
+
cdc generate code --url https://example.com/api-spec -o service.py
|
|
292
|
+
|
|
293
|
+
# Generate code with interactive refinement
|
|
294
|
+
cdc generate code --description "Database ORM" -o orm.py --interactive
|
|
295
|
+
|
|
296
|
+
# Generate code with project context
|
|
297
|
+
cdc generate code --file spec.md -o service.py --auto-context
|
|
298
|
+
|
|
299
|
+
# Add features to existing project
|
|
300
|
+
cdc generate feature --description "Add user authentication with JWT" src/
|
|
301
|
+
cdc generate feature --file feature-spec.md
|
|
302
|
+
cdc generate feature --pdf product-requirements.pdf --preview
|
|
303
|
+
cdc generate feature --url https://example.com/feature-spec
|
|
304
|
+
|
|
305
|
+
# Preview feature changes before applying
|
|
306
|
+
cdc generate feature --description "Add caching layer" src/ --preview
|
|
307
|
+
|
|
308
|
+
# Interactive feature implementation
|
|
309
|
+
cdc generate feature --description "Add logging" src/ --interactive
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
### 4. Developer Commands
|
|
233
313
|
|
|
234
314
|
```bash
|
|
235
|
-
# Generate tests
|
|
315
|
+
# Generate tests (single file)
|
|
236
316
|
cdc generate tests mymodule.py -o tests/test_mymodule.py
|
|
237
317
|
|
|
318
|
+
# Generate tests for multiple files (NEW in v0.11.0)
|
|
319
|
+
cdc generate tests file1.py file2.py file3.py
|
|
320
|
+
cdc generate tests src/ --max-files 10
|
|
321
|
+
|
|
238
322
|
# Generate tests with interactive refinement
|
|
239
323
|
cdc generate tests mymodule.py --interactive
|
|
240
324
|
|
|
241
325
|
# Generate tests with context (includes dependencies, related files) - NEW in v0.8.1
|
|
242
326
|
cdc generate tests mymodule.py --auto-context
|
|
243
327
|
|
|
244
|
-
# Code review
|
|
328
|
+
# Code review (single file)
|
|
245
329
|
cdc review mymodule.py
|
|
246
330
|
|
|
331
|
+
# Code review multiple files (NEW in v0.11.0)
|
|
332
|
+
cdc review file1.py file2.py file3.py
|
|
333
|
+
cdc review src/ # Review entire directory
|
|
334
|
+
cdc review # Auto-detect git changes (staged → modified → current dir)
|
|
335
|
+
|
|
247
336
|
# Code review with auto-context (includes git, dependencies, tests)
|
|
248
337
|
cdc review mymodule.py --auto-context
|
|
249
338
|
|
|
250
339
|
# Code review with interactive follow-up questions
|
|
251
340
|
cdc review mymodule.py --interactive
|
|
252
341
|
|
|
342
|
+
# Review git changes (NEW in v0.11.0)
|
|
343
|
+
cdc git review --staged # Review only staged changes
|
|
344
|
+
cdc git review --branch main..HEAD # Review branch changes
|
|
345
|
+
cdc git review # Review all modified files
|
|
346
|
+
|
|
253
347
|
# Debug errors with intelligent error parsing
|
|
254
348
|
python script.py 2>&1 | cdc debug --auto-context
|
|
255
349
|
|
|
256
|
-
# Generate documentation
|
|
350
|
+
# Generate documentation (single file)
|
|
257
351
|
cdc generate docs mymodule.py
|
|
258
352
|
|
|
353
|
+
# Generate docs for multiple files (NEW in v0.11.0)
|
|
354
|
+
cdc generate docs file1.py file2.py file3.py
|
|
355
|
+
cdc generate docs src/ --max-files 10
|
|
356
|
+
|
|
259
357
|
# Generate docs with interactive refinement
|
|
260
358
|
cdc generate docs mymodule.py --interactive
|
|
261
359
|
|
|
262
360
|
# Generate docs with context (includes dependencies) - NEW in v0.8.1
|
|
263
361
|
cdc generate docs mymodule.py --auto-context
|
|
264
362
|
|
|
363
|
+
# Refactor (single file)
|
|
364
|
+
cdc refactor legacy_code.py
|
|
365
|
+
|
|
366
|
+
# Refactor multiple files (NEW in v0.11.0)
|
|
367
|
+
cdc refactor file1.py file2.py file3.py
|
|
368
|
+
cdc refactor src/
|
|
369
|
+
cdc refactor # Auto-detect git changes
|
|
370
|
+
|
|
265
371
|
# Refactor with context (includes related files)
|
|
266
372
|
cdc refactor legacy_code.py --auto-context
|
|
267
373
|
|
|
@@ -277,7 +383,7 @@ git add .
|
|
|
277
383
|
cdc git commit --auto-context
|
|
278
384
|
```
|
|
279
385
|
|
|
280
|
-
###
|
|
386
|
+
### 5. Context-Aware Operations (v0.8.0+)
|
|
281
387
|
|
|
282
388
|
```bash
|
|
283
389
|
# Auto-context includes: git info, dependencies, related files
|
|
@@ -306,7 +412,7 @@ cdc refactor app.py --auto-context
|
|
|
306
412
|
# Automatically includes imported modules and dependencies
|
|
307
413
|
```
|
|
308
414
|
|
|
309
|
-
###
|
|
415
|
+
### 6. Custom Templates
|
|
310
416
|
|
|
311
417
|
```bash
|
|
312
418
|
# List all templates (built-in and user)
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
claude_dev_cli/__init__.py,sha256=
|
|
2
|
-
claude_dev_cli/cli.py,sha256=
|
|
1
|
+
claude_dev_cli/__init__.py,sha256=Qaoj-1FmqkHY4Lz4nzM6MhKNF3sooMTTKgLyBq-aiM8,470
|
|
2
|
+
claude_dev_cli/cli.py,sha256=SKL0Sy1fyHziPoK3FQOVgJ6cwJK8cmmBWoAAP3oP720,91797
|
|
3
3
|
claude_dev_cli/commands.py,sha256=RKGx2rv56PM6eErvA2uoQ20hY8babuI5jav8nCUyUOk,3964
|
|
4
4
|
claude_dev_cli/config.py,sha256=ZnPvzwlXsoY9YhqTl4S__fwY1MzJXKIaYK0nIIelNXk,19978
|
|
5
5
|
claude_dev_cli/context.py,sha256=1TlLzpREFZDEIuU7RAtlkjxARKWZpnxHHvK283sUAZE,26714
|
|
6
6
|
claude_dev_cli/core.py,sha256=4tKBgPQzvhM-jtlHaIy2K54vc2yIb4ycNDPLpoIoqN0,6621
|
|
7
7
|
claude_dev_cli/history.py,sha256=26EjNW68JuFQJhUp1j8UdB19S-eYz3eqevkpCOATwP0,10510
|
|
8
|
+
claude_dev_cli/input_sources.py,sha256=pFX5pU8uAUW_iujYdV3z1c_6F0KbKTWMNG0ChvKbxC8,7115
|
|
9
|
+
claude_dev_cli/path_utils.py,sha256=FFwweSkXe9OiG2Dej_UDKcY8-ZCYjL89ow6c7LZGe80,5564
|
|
8
10
|
claude_dev_cli/secure_storage.py,sha256=KcZuQMLTbQpMAi2Cyh-_JkNcK9vHzAITOgjTcM9sr98,8161
|
|
9
11
|
claude_dev_cli/template_manager.py,sha256=wtcrNuxFoJLJIPmIxUzrPKrE8kUvdqEd53EnG3jARhg,9277
|
|
10
12
|
claude_dev_cli/templates.py,sha256=lKxH943ySfUKgyHaWa4W3LVv91SgznKgajRtSRp_4UY,2260
|
|
@@ -17,9 +19,9 @@ claude_dev_cli/plugins/base.py,sha256=H4HQet1I-a3WLCfE9F06Lp8NuFvVoIlou7sIgyJFK-
|
|
|
17
19
|
claude_dev_cli/plugins/diff_editor/__init__.py,sha256=gqR5S2TyIVuq-sK107fegsutQ7Z-sgAIEbtc71FhXIM,101
|
|
18
20
|
claude_dev_cli/plugins/diff_editor/plugin.py,sha256=M1bUoqpasD3ZNQo36Fu_8g92uySPZyG_ujMbj5UplsU,3073
|
|
19
21
|
claude_dev_cli/plugins/diff_editor/viewer.py,sha256=1IOXIKw_01ppJx5C1dQt9Kr6U1TdAHT8_iUT5r_q0NM,17169
|
|
20
|
-
claude_dev_cli-0.
|
|
21
|
-
claude_dev_cli-0.
|
|
22
|
-
claude_dev_cli-0.
|
|
23
|
-
claude_dev_cli-0.
|
|
24
|
-
claude_dev_cli-0.
|
|
25
|
-
claude_dev_cli-0.
|
|
22
|
+
claude_dev_cli-0.12.0.dist-info/licenses/LICENSE,sha256=DGueuJwMJtMwgLO5mWlS0TaeBrFwQuNpNZ22PU9J2bw,1062
|
|
23
|
+
claude_dev_cli-0.12.0.dist-info/METADATA,sha256=S6ijTS_zClkYYgGuHO_hcCtTRO4TmXZS6rE_JCCjYzc,24018
|
|
24
|
+
claude_dev_cli-0.12.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
25
|
+
claude_dev_cli-0.12.0.dist-info/entry_points.txt,sha256=zymgUIIVpFTARkFmxAuW2A4BQsNITh_L0uU-XunytHg,85
|
|
26
|
+
claude_dev_cli-0.12.0.dist-info/top_level.txt,sha256=m7MF6LOIuTe41IT5Fgt0lc-DK1EgM4gUU_IZwWxK0pg,15
|
|
27
|
+
claude_dev_cli-0.12.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|