claude-dev-cli 0.12.0__py3-none-any.whl → 0.12.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.
Potentially problematic release.
This version of claude-dev-cli might be problematic. Click here for more details.
- claude_dev_cli/__init__.py +1 -1
- claude_dev_cli/cli.py +8 -8
- claude_dev_cli/multi_file_handler.py +348 -0
- {claude_dev_cli-0.12.0.dist-info → claude_dev_cli-0.12.1.dist-info}/METADATA +1 -1
- {claude_dev_cli-0.12.0.dist-info → claude_dev_cli-0.12.1.dist-info}/RECORD +9 -8
- {claude_dev_cli-0.12.0.dist-info → claude_dev_cli-0.12.1.dist-info}/WHEEL +0 -0
- {claude_dev_cli-0.12.0.dist-info → claude_dev_cli-0.12.1.dist-info}/entry_points.txt +0 -0
- {claude_dev_cli-0.12.0.dist-info → claude_dev_cli-0.12.1.dist-info}/licenses/LICENSE +0 -0
- {claude_dev_cli-0.12.0.dist-info → claude_dev_cli-0.12.1.dist-info}/top_level.txt +0 -0
claude_dev_cli/__init__.py
CHANGED
claude_dev_cli/cli.py
CHANGED
|
@@ -1157,15 +1157,15 @@ def gen_code(
|
|
|
1157
1157
|
|
|
1158
1158
|
# Generate code
|
|
1159
1159
|
with console.status(f"[bold blue]Generating code..."):
|
|
1160
|
-
client = ClaudeClient(api_config_name=api
|
|
1161
|
-
result = client.call(prompt)
|
|
1160
|
+
client = ClaudeClient(api_config_name=api)
|
|
1161
|
+
result = client.call(prompt, model=model)
|
|
1162
1162
|
|
|
1163
1163
|
# Interactive refinement
|
|
1164
1164
|
if interactive:
|
|
1165
1165
|
console.print("\n[bold]Initial Code:[/bold]\n")
|
|
1166
1166
|
console.print(result)
|
|
1167
1167
|
|
|
1168
|
-
client = ClaudeClient(api_config_name=api
|
|
1168
|
+
client = ClaudeClient(api_config_name=api)
|
|
1169
1169
|
conversation_context = [result]
|
|
1170
1170
|
|
|
1171
1171
|
while True:
|
|
@@ -1188,7 +1188,7 @@ def gen_code(
|
|
|
1188
1188
|
|
|
1189
1189
|
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
1190
1190
|
response_parts = []
|
|
1191
|
-
for chunk in client.call_streaming(refinement_prompt):
|
|
1191
|
+
for chunk in client.call_streaming(refinement_prompt, model=model):
|
|
1192
1192
|
console.print(chunk, end='')
|
|
1193
1193
|
response_parts.append(chunk)
|
|
1194
1194
|
console.print()
|
|
@@ -1314,8 +1314,8 @@ def gen_feature(
|
|
|
1314
1314
|
|
|
1315
1315
|
# Generate feature implementation
|
|
1316
1316
|
with console.status(f"[bold blue]Analyzing codebase and generating feature implementation..."):
|
|
1317
|
-
client = ClaudeClient(api_config_name=api
|
|
1318
|
-
result = client.call(prompt)
|
|
1317
|
+
client = ClaudeClient(api_config_name=api)
|
|
1318
|
+
result = client.call(prompt, model=model)
|
|
1319
1319
|
|
|
1320
1320
|
# Show result
|
|
1321
1321
|
from rich.markdown import Markdown
|
|
@@ -1329,7 +1329,7 @@ def gen_feature(
|
|
|
1329
1329
|
|
|
1330
1330
|
# Interactive refinement
|
|
1331
1331
|
if interactive:
|
|
1332
|
-
client = ClaudeClient(api_config_name=api
|
|
1332
|
+
client = ClaudeClient(api_config_name=api)
|
|
1333
1333
|
conversation_context = [result]
|
|
1334
1334
|
|
|
1335
1335
|
while True:
|
|
@@ -1353,7 +1353,7 @@ def gen_feature(
|
|
|
1353
1353
|
|
|
1354
1354
|
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
1355
1355
|
response_parts = []
|
|
1356
|
-
for chunk in client.call_streaming(refinement_prompt):
|
|
1356
|
+
for chunk in client.call_streaming(refinement_prompt, model=model):
|
|
1357
1357
|
console.print(chunk, end='')
|
|
1358
1358
|
response_parts.append(chunk)
|
|
1359
1359
|
console.print()
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
"""Multi-file handler for parsing and applying AI-generated file changes."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import difflib
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Tuple, Optional, Literal
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.tree import Tree
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class FileChange:
|
|
15
|
+
"""Represents a single file change."""
|
|
16
|
+
path: str
|
|
17
|
+
content: str
|
|
18
|
+
change_type: Literal["create", "modify", "delete"]
|
|
19
|
+
original_content: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def line_count(self) -> int:
|
|
23
|
+
"""Count lines in content."""
|
|
24
|
+
return len(self.content.splitlines()) if self.content else 0
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def diff(self) -> Optional[str]:
|
|
28
|
+
"""Generate unified diff for modifications."""
|
|
29
|
+
if self.change_type != "modify" or not self.original_content:
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
original_lines = self.original_content.splitlines(keepends=True)
|
|
33
|
+
new_lines = self.content.splitlines(keepends=True)
|
|
34
|
+
|
|
35
|
+
diff_lines = list(difflib.unified_diff(
|
|
36
|
+
original_lines,
|
|
37
|
+
new_lines,
|
|
38
|
+
fromfile=f"a/{self.path}",
|
|
39
|
+
tofile=f"b/{self.path}",
|
|
40
|
+
lineterm=''
|
|
41
|
+
))
|
|
42
|
+
|
|
43
|
+
return ''.join(diff_lines) if diff_lines else None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MultiFileResponse:
|
|
47
|
+
"""Parses and handles multi-file AI responses."""
|
|
48
|
+
|
|
49
|
+
def __init__(self):
|
|
50
|
+
self.files: List[FileChange] = []
|
|
51
|
+
|
|
52
|
+
def parse_response(self, text: str, base_path: Optional[Path] = None) -> None:
|
|
53
|
+
"""Parse AI response to extract file changes.
|
|
54
|
+
|
|
55
|
+
Supports formats:
|
|
56
|
+
- ## File: path/to/file.ext
|
|
57
|
+
- ## Create: path/to/file.ext
|
|
58
|
+
- ## Modify: path/to/file.ext
|
|
59
|
+
- ## Delete: path/to/file.ext
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
text: AI response text
|
|
63
|
+
base_path: Base directory to check for existing files (for modifications)
|
|
64
|
+
"""
|
|
65
|
+
self.files = []
|
|
66
|
+
|
|
67
|
+
# Pattern to match file markers and code blocks
|
|
68
|
+
# Matches: ## File: path or ## Create: path or ## Modify: path or ## Delete: path
|
|
69
|
+
file_pattern = r'^##\s+(File|Create|Modify|Delete):\s*(.+?)$'
|
|
70
|
+
code_block_pattern = r'```(\w+)?\n(.*?)```'
|
|
71
|
+
|
|
72
|
+
lines = text.split('\n')
|
|
73
|
+
i = 0
|
|
74
|
+
|
|
75
|
+
while i < len(lines):
|
|
76
|
+
line = lines[i].strip()
|
|
77
|
+
match = re.match(file_pattern, line, re.IGNORECASE)
|
|
78
|
+
|
|
79
|
+
if match:
|
|
80
|
+
action = match.group(1).lower()
|
|
81
|
+
file_path = match.group(2).strip()
|
|
82
|
+
|
|
83
|
+
# Map actions to change types
|
|
84
|
+
if action in ('file', 'create'):
|
|
85
|
+
change_type = 'create'
|
|
86
|
+
elif action == 'modify':
|
|
87
|
+
change_type = 'modify'
|
|
88
|
+
elif action == 'delete':
|
|
89
|
+
change_type = 'delete'
|
|
90
|
+
else:
|
|
91
|
+
change_type = 'create'
|
|
92
|
+
|
|
93
|
+
# For delete, no content needed
|
|
94
|
+
if change_type == 'delete':
|
|
95
|
+
self.files.append(FileChange(
|
|
96
|
+
path=file_path,
|
|
97
|
+
content='',
|
|
98
|
+
change_type='delete'
|
|
99
|
+
))
|
|
100
|
+
i += 1
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# Extract code block following the file marker
|
|
104
|
+
remaining_text = '\n'.join(lines[i+1:])
|
|
105
|
+
code_match = re.search(code_block_pattern, remaining_text, re.DOTALL)
|
|
106
|
+
|
|
107
|
+
if code_match:
|
|
108
|
+
content = code_match.group(2).strip()
|
|
109
|
+
|
|
110
|
+
# Check if file exists for modifications
|
|
111
|
+
original_content = None
|
|
112
|
+
if change_type == 'modify' and base_path:
|
|
113
|
+
file_full_path = base_path / file_path
|
|
114
|
+
if file_full_path.exists():
|
|
115
|
+
original_content = file_full_path.read_text()
|
|
116
|
+
change_type = 'modify'
|
|
117
|
+
else:
|
|
118
|
+
# File doesn't exist, treat as create
|
|
119
|
+
change_type = 'create'
|
|
120
|
+
|
|
121
|
+
self.files.append(FileChange(
|
|
122
|
+
path=file_path,
|
|
123
|
+
content=content,
|
|
124
|
+
change_type=change_type,
|
|
125
|
+
original_content=original_content
|
|
126
|
+
))
|
|
127
|
+
|
|
128
|
+
# Skip past the code block
|
|
129
|
+
lines_used = code_match.group(0).count('\n')
|
|
130
|
+
i += lines_used + 1
|
|
131
|
+
else:
|
|
132
|
+
# No code block found, skip this line
|
|
133
|
+
i += 1
|
|
134
|
+
else:
|
|
135
|
+
i += 1
|
|
136
|
+
|
|
137
|
+
def validate_paths(self, base_path: Path) -> List[str]:
|
|
138
|
+
"""Validate file paths for security.
|
|
139
|
+
|
|
140
|
+
Returns list of validation errors, empty if all valid.
|
|
141
|
+
"""
|
|
142
|
+
errors = []
|
|
143
|
+
base_path = base_path.resolve()
|
|
144
|
+
|
|
145
|
+
for file_change in self.files:
|
|
146
|
+
# Check for absolute paths
|
|
147
|
+
if Path(file_change.path).is_absolute():
|
|
148
|
+
errors.append(f"Absolute path not allowed: {file_change.path}")
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
# Resolve the path
|
|
152
|
+
try:
|
|
153
|
+
full_path = (base_path / file_change.path).resolve()
|
|
154
|
+
|
|
155
|
+
# Check if resolved path is within base_path
|
|
156
|
+
if not str(full_path).startswith(str(base_path)):
|
|
157
|
+
errors.append(f"Path traversal detected: {file_change.path}")
|
|
158
|
+
|
|
159
|
+
except Exception as e:
|
|
160
|
+
errors.append(f"Invalid path {file_change.path}: {str(e)}")
|
|
161
|
+
|
|
162
|
+
return errors
|
|
163
|
+
|
|
164
|
+
def build_tree(self, base_path: Path) -> str:
|
|
165
|
+
"""Generate visual directory tree.
|
|
166
|
+
|
|
167
|
+
Returns formatted tree string with colors and status indicators.
|
|
168
|
+
"""
|
|
169
|
+
if not self.files:
|
|
170
|
+
return "No files"
|
|
171
|
+
|
|
172
|
+
# Build tree structure
|
|
173
|
+
tree = Tree(f"[bold cyan]{base_path.name or base_path}/[/bold cyan]")
|
|
174
|
+
|
|
175
|
+
# Group files by directory
|
|
176
|
+
dirs = {}
|
|
177
|
+
for file_change in self.files:
|
|
178
|
+
parts = Path(file_change.path).parts
|
|
179
|
+
current = dirs
|
|
180
|
+
|
|
181
|
+
for i, part in enumerate(parts[:-1]):
|
|
182
|
+
if part not in current:
|
|
183
|
+
current[part] = {}
|
|
184
|
+
current = current[part]
|
|
185
|
+
|
|
186
|
+
# Store file info at leaf
|
|
187
|
+
filename = parts[-1]
|
|
188
|
+
current[filename] = file_change
|
|
189
|
+
|
|
190
|
+
def add_to_tree(node, items):
|
|
191
|
+
"""Recursively add items to tree."""
|
|
192
|
+
for name, value in sorted(items.items()):
|
|
193
|
+
if isinstance(value, dict):
|
|
194
|
+
# Directory
|
|
195
|
+
branch = node.add(f"[bold blue]{name}/[/bold blue]")
|
|
196
|
+
add_to_tree(branch, value)
|
|
197
|
+
elif isinstance(value, FileChange):
|
|
198
|
+
# File
|
|
199
|
+
if value.change_type == 'create':
|
|
200
|
+
status = "[green](new)[/green]"
|
|
201
|
+
elif value.change_type == 'modify':
|
|
202
|
+
status = "[yellow](modified)[/yellow]"
|
|
203
|
+
elif value.change_type == 'delete':
|
|
204
|
+
status = "[red](deleted)[/red]"
|
|
205
|
+
else:
|
|
206
|
+
status = ""
|
|
207
|
+
|
|
208
|
+
lines = f"{value.line_count} lines" if value.line_count > 0 else ""
|
|
209
|
+
node.add(f"{name} {status} [dim]{lines}[/dim]")
|
|
210
|
+
|
|
211
|
+
add_to_tree(tree, dirs)
|
|
212
|
+
|
|
213
|
+
return tree
|
|
214
|
+
|
|
215
|
+
def preview(self, console: Console, base_path: Path) -> None:
|
|
216
|
+
"""Show formatted preview with tree and summary."""
|
|
217
|
+
if not self.files:
|
|
218
|
+
console.print("[yellow]No files to change[/yellow]")
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
# Show tree
|
|
222
|
+
console.print("\n[bold]File Structure:[/bold]")
|
|
223
|
+
console.print(self.build_tree(base_path))
|
|
224
|
+
|
|
225
|
+
# Summary
|
|
226
|
+
creates = sum(1 for f in self.files if f.change_type == 'create')
|
|
227
|
+
modifies = sum(1 for f in self.files if f.change_type == 'modify')
|
|
228
|
+
deletes = sum(1 for f in self.files if f.change_type == 'delete')
|
|
229
|
+
total_lines = sum(f.line_count for f in self.files)
|
|
230
|
+
|
|
231
|
+
summary_parts = []
|
|
232
|
+
if creates:
|
|
233
|
+
summary_parts.append(f"[green]{creates} created[/green]")
|
|
234
|
+
if modifies:
|
|
235
|
+
summary_parts.append(f"[yellow]{modifies} modified[/yellow]")
|
|
236
|
+
if deletes:
|
|
237
|
+
summary_parts.append(f"[red]{deletes} deleted[/red]")
|
|
238
|
+
|
|
239
|
+
summary = ", ".join(summary_parts)
|
|
240
|
+
console.print(f"\n[bold]Summary:[/bold] {summary} ({total_lines} total lines)\n")
|
|
241
|
+
|
|
242
|
+
def write_all(self, base_path: Path, dry_run: bool = False, console: Optional[Console] = None) -> None:
|
|
243
|
+
"""Write all file changes to disk.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
base_path: Base directory for file operations
|
|
247
|
+
dry_run: If True, don't actually write files
|
|
248
|
+
console: Rich console for output
|
|
249
|
+
"""
|
|
250
|
+
if console is None:
|
|
251
|
+
console = Console()
|
|
252
|
+
|
|
253
|
+
base_path = base_path.resolve()
|
|
254
|
+
base_path.mkdir(parents=True, exist_ok=True)
|
|
255
|
+
|
|
256
|
+
for file_change in self.files:
|
|
257
|
+
full_path = base_path / file_change.path
|
|
258
|
+
|
|
259
|
+
if dry_run:
|
|
260
|
+
if file_change.change_type == 'create':
|
|
261
|
+
console.print(f"[dim]Would create: {file_change.path}[/dim]")
|
|
262
|
+
elif file_change.change_type == 'modify':
|
|
263
|
+
console.print(f"[dim]Would modify: {file_change.path}[/dim]")
|
|
264
|
+
elif file_change.change_type == 'delete':
|
|
265
|
+
console.print(f"[dim]Would delete: {file_change.path}[/dim]")
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
# Actual file operations
|
|
269
|
+
if file_change.change_type == 'delete':
|
|
270
|
+
if full_path.exists():
|
|
271
|
+
full_path.unlink()
|
|
272
|
+
console.print(f"[red]✗[/red] Deleted: {file_change.path}")
|
|
273
|
+
else:
|
|
274
|
+
# Create parent directories
|
|
275
|
+
full_path.parent.mkdir(parents=True, exist_ok=True)
|
|
276
|
+
|
|
277
|
+
# Write file
|
|
278
|
+
full_path.write_text(file_change.content)
|
|
279
|
+
|
|
280
|
+
if file_change.change_type == 'create':
|
|
281
|
+
console.print(f"[green]✓[/green] Created: {file_change.path}")
|
|
282
|
+
elif file_change.change_type == 'modify':
|
|
283
|
+
console.print(f"[yellow]✓[/yellow] Modified: {file_change.path}")
|
|
284
|
+
|
|
285
|
+
def confirm(self, console: Console) -> bool:
|
|
286
|
+
"""Interactive confirmation prompt.
|
|
287
|
+
|
|
288
|
+
Returns True if user confirms, False otherwise.
|
|
289
|
+
"""
|
|
290
|
+
if not self.files:
|
|
291
|
+
return False
|
|
292
|
+
|
|
293
|
+
while True:
|
|
294
|
+
response = console.input("\n[cyan]Continue?[/cyan] [dim](Y/n/preview/help)[/dim] ").strip().lower()
|
|
295
|
+
|
|
296
|
+
if response in ('y', 'yes', ''):
|
|
297
|
+
return True
|
|
298
|
+
elif response in ('n', 'no'):
|
|
299
|
+
return False
|
|
300
|
+
elif response == 'preview':
|
|
301
|
+
# Show individual file contents
|
|
302
|
+
for i, file_change in enumerate(self.files, 1):
|
|
303
|
+
console.print(f"\n[bold]File {i}/{len(self.files)}:[/bold] {file_change.path}")
|
|
304
|
+
|
|
305
|
+
if file_change.change_type == 'delete':
|
|
306
|
+
console.print("[red]This file will be deleted[/red]")
|
|
307
|
+
elif file_change.change_type == 'modify' and file_change.diff:
|
|
308
|
+
console.print("[yellow]Diff:[/yellow]")
|
|
309
|
+
console.print(Panel(file_change.diff, border_style="yellow"))
|
|
310
|
+
else:
|
|
311
|
+
preview = file_change.content[:500]
|
|
312
|
+
if len(file_change.content) > 500:
|
|
313
|
+
preview += "\n... (truncated)"
|
|
314
|
+
console.print(Panel(preview, border_style="green"))
|
|
315
|
+
elif response == 'help':
|
|
316
|
+
console.print("""
|
|
317
|
+
[bold]Options:[/bold]
|
|
318
|
+
y, yes - Proceed with changes
|
|
319
|
+
n, no - Cancel
|
|
320
|
+
preview - Show file contents/diffs
|
|
321
|
+
help - Show this help
|
|
322
|
+
""")
|
|
323
|
+
else:
|
|
324
|
+
console.print("[red]Invalid response. Type 'help' for options.[/red]")
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def extract_code_blocks(text: str) -> List[Tuple[str, str, str]]:
|
|
328
|
+
"""Extract code blocks from markdown text.
|
|
329
|
+
|
|
330
|
+
Returns list of (file_marker, language, code) tuples.
|
|
331
|
+
"""
|
|
332
|
+
pattern = r'^##\s+(File|Create|Modify|Delete):\s*(.+?)$.*?```(\w+)?\n(.*?)```'
|
|
333
|
+
matches = re.findall(pattern, text, re.MULTILINE | re.DOTALL)
|
|
334
|
+
|
|
335
|
+
results = []
|
|
336
|
+
for match in matches:
|
|
337
|
+
action = match[0]
|
|
338
|
+
path = match[1].strip()
|
|
339
|
+
language = match[2] if match[2] else ''
|
|
340
|
+
code = match[3].strip()
|
|
341
|
+
results.append((f"{action}: {path}", language, code))
|
|
342
|
+
|
|
343
|
+
return results
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def count_lines(content: str) -> int:
|
|
347
|
+
"""Count non-empty lines in content."""
|
|
348
|
+
return len([line for line in content.splitlines() if line.strip()])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-dev-cli
|
|
3
|
-
Version: 0.12.
|
|
3
|
+
Version: 0.12.1
|
|
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
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
claude_dev_cli/__init__.py,sha256=
|
|
2
|
-
claude_dev_cli/cli.py,sha256=
|
|
1
|
+
claude_dev_cli/__init__.py,sha256=m4e1TLJ_BCtWjtXr5lJcRupHDnyE30CEI2yqfFmbcWo,470
|
|
2
|
+
claude_dev_cli/cli.py,sha256=GDURdznUC9Dy_B9x8MS-9J3JrMbwiVOofSWHalmsRaU,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
8
|
claude_dev_cli/input_sources.py,sha256=pFX5pU8uAUW_iujYdV3z1c_6F0KbKTWMNG0ChvKbxC8,7115
|
|
9
|
+
claude_dev_cli/multi_file_handler.py,sha256=3Rgy9NetKvd4tFhGyeY-44CYApnNShKAEErWOOARrJw,13331
|
|
9
10
|
claude_dev_cli/path_utils.py,sha256=FFwweSkXe9OiG2Dej_UDKcY8-ZCYjL89ow6c7LZGe80,5564
|
|
10
11
|
claude_dev_cli/secure_storage.py,sha256=KcZuQMLTbQpMAi2Cyh-_JkNcK9vHzAITOgjTcM9sr98,8161
|
|
11
12
|
claude_dev_cli/template_manager.py,sha256=wtcrNuxFoJLJIPmIxUzrPKrE8kUvdqEd53EnG3jARhg,9277
|
|
@@ -19,9 +20,9 @@ claude_dev_cli/plugins/base.py,sha256=H4HQet1I-a3WLCfE9F06Lp8NuFvVoIlou7sIgyJFK-
|
|
|
19
20
|
claude_dev_cli/plugins/diff_editor/__init__.py,sha256=gqR5S2TyIVuq-sK107fegsutQ7Z-sgAIEbtc71FhXIM,101
|
|
20
21
|
claude_dev_cli/plugins/diff_editor/plugin.py,sha256=M1bUoqpasD3ZNQo36Fu_8g92uySPZyG_ujMbj5UplsU,3073
|
|
21
22
|
claude_dev_cli/plugins/diff_editor/viewer.py,sha256=1IOXIKw_01ppJx5C1dQt9Kr6U1TdAHT8_iUT5r_q0NM,17169
|
|
22
|
-
claude_dev_cli-0.12.
|
|
23
|
-
claude_dev_cli-0.12.
|
|
24
|
-
claude_dev_cli-0.12.
|
|
25
|
-
claude_dev_cli-0.12.
|
|
26
|
-
claude_dev_cli-0.12.
|
|
27
|
-
claude_dev_cli-0.12.
|
|
23
|
+
claude_dev_cli-0.12.1.dist-info/licenses/LICENSE,sha256=DGueuJwMJtMwgLO5mWlS0TaeBrFwQuNpNZ22PU9J2bw,1062
|
|
24
|
+
claude_dev_cli-0.12.1.dist-info/METADATA,sha256=HGhP9cVZEsXsOH9hhtZXRhjfqWrQvsdlxUcWAvzEZgU,24018
|
|
25
|
+
claude_dev_cli-0.12.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
26
|
+
claude_dev_cli-0.12.1.dist-info/entry_points.txt,sha256=zymgUIIVpFTARkFmxAuW2A4BQsNITh_L0uU-XunytHg,85
|
|
27
|
+
claude_dev_cli-0.12.1.dist-info/top_level.txt,sha256=m7MF6LOIuTe41IT5Fgt0lc-DK1EgM4gUU_IZwWxK0pg,15
|
|
28
|
+
claude_dev_cli-0.12.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|