claude-dev-cli 0.2.0__py3-none-any.whl → 0.3.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/cli.py CHANGED
@@ -22,6 +22,7 @@ from claude_dev_cli.commands import (
22
22
  )
23
23
  from claude_dev_cli.usage import UsageTracker
24
24
  from claude_dev_cli import toon_utils
25
+ from claude_dev_cli.plugins import load_plugins
25
26
 
26
27
  console = Console()
27
28
 
@@ -35,6 +36,17 @@ def main(ctx: click.Context) -> None:
35
36
  ctx.obj['console'] = console
36
37
 
37
38
 
39
+ # Load plugins after main group is defined
40
+ # Silently load plugins - they'll register their commands
41
+ try:
42
+ plugins = load_plugins()
43
+ for plugin in plugins:
44
+ plugin.register_commands(main)
45
+ except Exception:
46
+ # Don't fail if plugins can't load - continue without them
47
+ pass
48
+
49
+
38
50
  @main.command()
39
51
  @click.argument('prompt', required=False)
40
52
  @click.option('-f', '--file', type=click.Path(exists=True), help='Include file content in prompt')
claude_dev_cli/config.py CHANGED
@@ -28,15 +28,15 @@ class ProjectProfile(BaseModel):
28
28
  class Config:
29
29
  """Manages configuration for Claude Dev CLI."""
30
30
 
31
- CONFIG_DIR = Path.home() / ".claude-dev-cli"
32
- CONFIG_FILE = CONFIG_DIR / "config.json"
33
- USAGE_LOG = CONFIG_DIR / "usage.jsonl"
34
-
35
31
  def __init__(self) -> None:
36
32
  """Initialize configuration."""
37
- self.config_dir = self.CONFIG_DIR
38
- self.config_file = self.CONFIG_FILE
39
- self.usage_log = self.USAGE_LOG
33
+ # Determine home directory (respects HOME env var for testing)
34
+ home = Path(os.environ.get("HOME", str(Path.home())))
35
+
36
+ self.config_dir = home / ".claude-dev-cli"
37
+ self.config_file = self.config_dir / "config.json"
38
+ self.usage_log = self.config_dir / "usage.jsonl"
39
+
40
40
  self._ensure_config_dir()
41
41
  self._data: Dict = self._load_config()
42
42
 
claude_dev_cli/core.py CHANGED
@@ -13,13 +13,21 @@ class ClaudeClient:
13
13
  """Claude API client with multi-key routing and usage tracking."""
14
14
 
15
15
  def __init__(self, config: Optional[Config] = None, api_config_name: Optional[str] = None):
16
- """Initialize Claude client."""
16
+ """Initialize Claude client.
17
+
18
+ API routing hierarchy (highest to lowest priority):
19
+ 1. Explicit api_config_name parameter
20
+ 2. Project-specific .claude-dev-cli file
21
+ 3. Default API config
22
+ """
17
23
  self.config = config or Config()
18
24
 
19
- # Determine which API config to use
20
- project_profile = self.config.get_project_profile()
21
- if project_profile:
22
- api_config_name = project_profile.api_config
25
+ # Determine which API config to use based on hierarchy
26
+ if not api_config_name:
27
+ # Check for project profile if no explicit config provided
28
+ project_profile = self.config.get_project_profile()
29
+ if project_profile:
30
+ api_config_name = project_profile.api_config
23
31
 
24
32
  self.api_config = self.config.get_api_config(api_config_name)
25
33
  if not self.api_config:
@@ -0,0 +1,42 @@
1
+ """Plugin system for Claude Dev CLI."""
2
+
3
+ from typing import List
4
+ from pathlib import Path
5
+ import importlib
6
+ import importlib.util
7
+
8
+ from .base import Plugin
9
+
10
+
11
+ def discover_plugins() -> List[Plugin]:
12
+ """Discover and load all plugins from the plugins directory.
13
+
14
+ Returns:
15
+ List of loaded plugin instances
16
+ """
17
+ plugins = []
18
+ plugin_dir = Path(__file__).parent
19
+
20
+ # Look for plugin directories (those with plugin.py)
21
+ for item in plugin_dir.iterdir():
22
+ if item.is_dir() and (item / "plugin.py").exists() and item.name != "__pycache__":
23
+ try:
24
+ # Use proper import instead of spec loading to handle relative imports
25
+ plugin_module_name = f"claude_dev_cli.plugins.{item.name}.plugin"
26
+ module = importlib.import_module(plugin_module_name)
27
+
28
+ # Look for plugin registration function
29
+ if hasattr(module, "register_plugin"):
30
+ plugin = module.register_plugin()
31
+ plugins.append(plugin)
32
+ except Exception as e:
33
+ # Silently skip plugins that fail to load
34
+ pass
35
+
36
+ return plugins
37
+
38
+
39
+ # Alias for backwards compatibility and clearer intent
40
+ load_plugins = discover_plugins
41
+
42
+ __all__ = ["Plugin", "discover_plugins", "load_plugins"]
@@ -0,0 +1,53 @@
1
+ """Base plugin interface for Claude Dev CLI."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Optional
5
+ import click
6
+
7
+
8
+ class Plugin(ABC):
9
+ """Base class for all plugins."""
10
+
11
+ def __init__(self, name: str, version: str, description: str = ""):
12
+ """Initialize plugin.
13
+
14
+ Args:
15
+ name: Plugin name
16
+ version: Plugin version
17
+ description: Plugin description
18
+ """
19
+ self.name = name
20
+ self.version = version
21
+ self.description = description
22
+
23
+ @abstractmethod
24
+ def register_commands(self, cli: click.Group) -> None:
25
+ """Register plugin commands with the CLI.
26
+
27
+ Args:
28
+ cli: Click group to register commands to
29
+ """
30
+ pass
31
+
32
+ def before_apply(self, original: str, proposed: str) -> Optional[str]:
33
+ """Hook called before applying changes.
34
+
35
+ Args:
36
+ original: Original content
37
+ proposed: Proposed changes
38
+
39
+ Returns:
40
+ Modified proposed content or None to keep original
41
+ """
42
+ return None
43
+
44
+ def after_apply(self, result: str) -> Optional[str]:
45
+ """Hook called after applying changes.
46
+
47
+ Args:
48
+ result: Result after changes applied
49
+
50
+ Returns:
51
+ Modified result or None to keep original
52
+ """
53
+ return None
@@ -0,0 +1,3 @@
1
+ """Interactive diff editor plugin for reviewing AI-generated code changes."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,98 @@
1
+ """Diff editor plugin registration."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ import click
7
+ from rich.console import Console
8
+ from rich.panel import Panel
9
+ from rich.syntax import Syntax
10
+
11
+ from claude_dev_cli.plugins.base import Plugin
12
+ from .viewer import DiffViewer
13
+
14
+
15
+ class DiffEditorPlugin(Plugin):
16
+ """Interactive diff editor plugin."""
17
+
18
+ def __init__(self):
19
+ super().__init__(
20
+ name="diff-editor",
21
+ version="0.1.0",
22
+ description="Interactive diff viewer for reviewing code changes"
23
+ )
24
+ self.console = Console()
25
+
26
+ def register_commands(self, cli: click.Group) -> None:
27
+ """Register diff editor commands."""
28
+
29
+ @cli.command("diff")
30
+ @click.argument("original", type=click.Path(exists=True))
31
+ @click.argument("proposed", type=click.Path(exists=True))
32
+ @click.option(
33
+ "--keybindings",
34
+ "-k",
35
+ type=click.Choice(["nvim", "fresh", "auto"]),
36
+ default="auto",
37
+ help="Keybinding mode (nvim, fresh, or auto-detect)"
38
+ )
39
+ @click.option(
40
+ "--output",
41
+ "-o",
42
+ type=click.Path(),
43
+ help="Output file path for accepted changes"
44
+ )
45
+ def diff_command(
46
+ original: str,
47
+ proposed: str,
48
+ keybindings: str,
49
+ output: Optional[str]
50
+ ) -> None:
51
+ """Interactively review differences between two files."""
52
+ viewer = DiffViewer(
53
+ original_path=Path(original),
54
+ proposed_path=Path(proposed),
55
+ keybinding_mode=keybindings,
56
+ console=self.console
57
+ )
58
+
59
+ result = viewer.run()
60
+
61
+ if result and output:
62
+ with open(output, "w") as f:
63
+ f.write(result)
64
+ self.console.print(f"\n[green]✓[/green] Changes saved to: {output}")
65
+ elif result:
66
+ self.console.print("\n[bold]Final result:[/bold]")
67
+ self.console.print(result)
68
+
69
+ @cli.command("apply-diff")
70
+ @click.argument("file_path", type=click.Path(exists=True))
71
+ @click.option(
72
+ "--keybindings",
73
+ "-k",
74
+ type=click.Choice(["nvim", "fresh", "auto"]),
75
+ default="auto",
76
+ help="Keybinding mode"
77
+ )
78
+ @click.option(
79
+ "--in-place",
80
+ "-i",
81
+ is_flag=True,
82
+ help="Edit file in place"
83
+ )
84
+ def apply_diff_command(
85
+ file_path: str,
86
+ keybindings: str,
87
+ in_place: bool
88
+ ) -> None:
89
+ """Apply AI-suggested changes to a file interactively."""
90
+ self.console.print(
91
+ f"[yellow]This would apply changes to {file_path} interactively[/yellow]"
92
+ )
93
+ self.console.print("Feature coming soon!")
94
+
95
+
96
+ def register_plugin() -> Plugin:
97
+ """Register the diff editor plugin."""
98
+ return DiffEditorPlugin()
@@ -0,0 +1,367 @@
1
+ """Interactive diff viewer with multiple keybinding modes."""
2
+
3
+ import difflib
4
+ from pathlib import Path
5
+ from typing import List, Optional, Tuple
6
+ import os
7
+
8
+ from rich.console import Console
9
+ from rich.panel import Panel
10
+ from rich.syntax import Syntax
11
+ from rich.table import Table
12
+ from rich.text import Text
13
+
14
+ try:
15
+ from pygments import lexers
16
+ from pygments.util import ClassNotFound
17
+ PYGMENTS_AVAILABLE = True
18
+ except ImportError:
19
+ PYGMENTS_AVAILABLE = False
20
+
21
+
22
+ class Hunk:
23
+ """Represents a single diff hunk."""
24
+
25
+ def __init__(
26
+ self,
27
+ original_lines: List[str],
28
+ proposed_lines: List[str],
29
+ original_start: int,
30
+ proposed_start: int
31
+ ):
32
+ self.original_lines = original_lines
33
+ self.proposed_lines = proposed_lines
34
+ self.original_start = original_start
35
+ self.proposed_start = proposed_start
36
+ self.accepted = None # None = undecided, True = accepted, False = rejected
37
+
38
+ def get_context(self) -> str:
39
+ """Get a brief context description of this hunk."""
40
+ if self.proposed_lines:
41
+ first_line = self.proposed_lines[0].strip()
42
+ return first_line[:50] if len(first_line) > 50 else first_line
43
+ return "deletion"
44
+
45
+
46
+ class DiffViewer:
47
+ """Interactive diff viewer with multiple keybinding modes."""
48
+
49
+ def __init__(
50
+ self,
51
+ original_path: Path,
52
+ proposed_path: Path,
53
+ keybinding_mode: str = "auto",
54
+ console: Optional[Console] = None
55
+ ):
56
+ self.original_path = original_path
57
+ self.proposed_path = proposed_path
58
+ self.console = console or Console()
59
+
60
+ # Detect keybinding mode
61
+ if keybinding_mode == "auto":
62
+ self.keybinding_mode = self._detect_keybinding_mode()
63
+ else:
64
+ self.keybinding_mode = keybinding_mode
65
+
66
+ # Load files
67
+ with open(original_path) as f:
68
+ self.original_content = f.read()
69
+ self.original_lines = self.original_content.splitlines(keepends=True)
70
+
71
+ with open(proposed_path) as f:
72
+ self.proposed_content = f.read()
73
+ self.proposed_lines = self.proposed_content.splitlines(keepends=True)
74
+
75
+ # Generate hunks
76
+ self.hunks = self._generate_hunks()
77
+ self.current_hunk_idx = 0
78
+ self.filename = proposed_path.name
79
+
80
+ # Detect lexer for syntax highlighting
81
+ self.lexer_name = self._detect_lexer()
82
+
83
+ # History stack for undo support
84
+ self.history: List[Tuple[int, Optional[bool]]] = []
85
+
86
+ def _detect_keybinding_mode(self) -> str:
87
+ """Auto-detect keybinding preference from environment."""
88
+ # Check if user has vim/nvim in their environment
89
+ editor = os.environ.get("EDITOR", "").lower()
90
+ visual = os.environ.get("VISUAL", "").lower()
91
+
92
+ if "vim" in editor or "vim" in visual or "nvim" in editor or "nvim" in visual:
93
+ return "nvim"
94
+ return "fresh"
95
+
96
+ def _detect_lexer(self) -> Optional[str]:
97
+ """Detect appropriate lexer for syntax highlighting based on filename."""
98
+ if not PYGMENTS_AVAILABLE:
99
+ return None
100
+
101
+ try:
102
+ lexer = lexers.get_lexer_for_filename(str(self.proposed_path))
103
+ return lexer.name
104
+ except ClassNotFound:
105
+ return None
106
+
107
+ def _generate_hunks(self) -> List[Hunk]:
108
+ """Generate hunks from diff."""
109
+ hunks = []
110
+ differ = difflib.Differ()
111
+ diff = list(differ.compare(self.original_lines, self.proposed_lines))
112
+
113
+ i = 0
114
+ while i < len(diff):
115
+ # Find start of a hunk (lines that differ)
116
+ while i < len(diff) and diff[i].startswith(" "):
117
+ i += 1
118
+
119
+ if i >= len(diff):
120
+ break
121
+
122
+ # Collect the hunk
123
+ original_lines = []
124
+ proposed_lines = []
125
+ original_start = i
126
+ proposed_start = i
127
+
128
+ while i < len(diff) and not diff[i].startswith(" "):
129
+ line = diff[i]
130
+ if line.startswith("- "):
131
+ original_lines.append(line[2:])
132
+ elif line.startswith("+ "):
133
+ proposed_lines.append(line[2:])
134
+ elif line.startswith("? "):
135
+ # Skip hint lines
136
+ pass
137
+ i += 1
138
+
139
+ if original_lines or proposed_lines:
140
+ hunks.append(Hunk(
141
+ original_lines,
142
+ proposed_lines,
143
+ original_start,
144
+ proposed_start
145
+ ))
146
+
147
+ return hunks
148
+
149
+ def _get_keybindings(self) -> dict:
150
+ """Get keybindings based on mode."""
151
+ if self.keybinding_mode == "nvim":
152
+ return {
153
+ "accept": ["y", "a"],
154
+ "reject": ["n", "d"],
155
+ "edit": ["e", "c"],
156
+ "split": ["s"],
157
+ "quit": ["q", "ZZ"],
158
+ "accept_all": ["A"],
159
+ "reject_all": ["D"],
160
+ "undo": ["u"],
161
+ "help": ["?", "h"],
162
+ "next": ["j", "n"],
163
+ "prev": ["k", "p"],
164
+ "goto_first": ["gg"],
165
+ "goto_last": ["G"],
166
+ }
167
+ else: # fresh mode
168
+ return {
169
+ "accept": ["y", "Enter"],
170
+ "reject": ["n", "Backspace"],
171
+ "edit": ["e"],
172
+ "split": ["s"],
173
+ "quit": ["q", "Esc"],
174
+ "accept_all": ["Ctrl-A"],
175
+ "reject_all": ["Ctrl-R"],
176
+ "undo": ["Ctrl-Z"],
177
+ "help": ["F1", "?"],
178
+ "next": ["Down", "j"],
179
+ "prev": ["Up", "k"],
180
+ "goto_first": ["Home"],
181
+ "goto_last": ["End"],
182
+ }
183
+
184
+ def _show_help(self) -> None:
185
+ """Display help panel."""
186
+ kb = self._get_keybindings()
187
+ mode_name = "Neovim" if self.keybinding_mode == "nvim" else "Fresh"
188
+
189
+ table = Table(title=f"{mode_name} Keybindings", show_header=True)
190
+ table.add_column("Action", style="cyan")
191
+ table.add_column("Keys", style="green")
192
+
193
+ table.add_row("Accept hunk", " or ".join(kb["accept"]))
194
+ table.add_row("Reject hunk", " or ".join(kb["reject"]))
195
+ table.add_row("Edit hunk", " or ".join(kb["edit"]))
196
+ table.add_row("Split hunk", " or ".join(kb["split"]))
197
+ table.add_row("Next hunk", " or ".join(kb["next"]))
198
+ table.add_row("Previous hunk", " or ".join(kb["prev"]))
199
+ table.add_row("Accept all", " or ".join(kb["accept_all"]))
200
+ table.add_row("Reject all", " or ".join(kb["reject_all"]))
201
+ table.add_row("Undo", " or ".join(kb["undo"]))
202
+ table.add_row("Quit", " or ".join(kb["quit"]))
203
+ table.add_row("Help", " or ".join(kb["help"]))
204
+
205
+ self.console.print(table)
206
+ self.console.print("\n[dim]Press any key to continue...[/dim]")
207
+ self.console.input()
208
+
209
+ def _display_hunk(self, hunk: Hunk) -> None:
210
+ """Display a single hunk with syntax highlighting."""
211
+ self.console.clear()
212
+
213
+ # Title
214
+ mode_indicator = "🎹 Neovim" if self.keybinding_mode == "nvim" else "✨ Fresh"
215
+ self.console.print(
216
+ Panel(
217
+ f"{mode_indicator} Mode | {self.filename}",
218
+ title=f"Hunk {self.current_hunk_idx + 1}/{len(self.hunks)}",
219
+ border_style="blue"
220
+ )
221
+ )
222
+
223
+ # Show original (if any deletions)
224
+ if hunk.original_lines:
225
+ self.console.print("\n[bold red]━━━ Original (-):[/bold red]")
226
+ code = "".join(hunk.original_lines)
227
+ if self.lexer_name and code.strip():
228
+ syntax = Syntax(code, self.lexer_name, theme="monokai", line_numbers=False)
229
+ # Wrap in red for deletions
230
+ for line in code.splitlines():
231
+ self.console.print(f"[red]- {line}[/red]")
232
+ else:
233
+ for line in hunk.original_lines:
234
+ self.console.print(f"[red]- {line}[/red]", end="")
235
+
236
+ # Show proposed (if any additions)
237
+ if hunk.proposed_lines:
238
+ self.console.print("\n[bold green]━━━ Proposed (+):[/bold green]")
239
+ code = "".join(hunk.proposed_lines)
240
+ if self.lexer_name and code.strip():
241
+ syntax = Syntax(code, self.lexer_name, theme="monokai", line_numbers=False)
242
+ # Wrap in green for additions
243
+ for line in code.splitlines():
244
+ self.console.print(f"[green]+ {line}[/green]")
245
+ else:
246
+ for line in hunk.proposed_lines:
247
+ self.console.print(f"[green]+ {line}[/green]", end="")
248
+
249
+ # Context
250
+ context = hunk.get_context()
251
+ self.console.print(f"\n[dim]Context: {context}[/dim]")
252
+
253
+ # Status
254
+ status = "✓ Accepted" if hunk.accepted is True else "✗ Rejected" if hunk.accepted is False else "? Undecided"
255
+ self.console.print(f"Status: {status}\n")
256
+
257
+ def _show_prompt(self) -> None:
258
+ """Show action prompt based on keybinding mode."""
259
+ kb = self._get_keybindings()
260
+
261
+ if self.keybinding_mode == "nvim":
262
+ prompt = (
263
+ f"[cyan][y]es [n]o [e]dit [s]plit | "
264
+ f"[j]next [k]prev [q]uit [?]help[/cyan]"
265
+ )
266
+ else: # fresh
267
+ prompt = (
268
+ f"[cyan][y]es [n]o [e]dit [s]plit | "
269
+ f"[↓]next [↑]prev [q]uit [?]help[/cyan]"
270
+ )
271
+
272
+ self.console.print(prompt)
273
+
274
+ def run(self) -> Optional[str]:
275
+ """Run the interactive diff viewer.
276
+
277
+ Returns:
278
+ Final content with accepted changes or None if cancelled
279
+ """
280
+ if not self.hunks:
281
+ self.console.print("[yellow]No differences found[/yellow]")
282
+ return self.original_content
283
+
284
+ # Show initial help
285
+ self._show_help()
286
+
287
+ while self.current_hunk_idx < len(self.hunks):
288
+ hunk = self.hunks[self.current_hunk_idx]
289
+ self._display_hunk(hunk)
290
+ self._show_prompt()
291
+
292
+ # Get user input
293
+ choice = self.console.input("\nYour choice: ").strip().lower()
294
+
295
+ kb = self._get_keybindings()
296
+
297
+ # Process choice
298
+ if choice in kb["accept"]:
299
+ # Save history before changing
300
+ self.history.append((self.current_hunk_idx, hunk.accepted))
301
+ hunk.accepted = True
302
+ self.current_hunk_idx += 1
303
+ elif choice in kb["reject"]:
304
+ # Save history before changing
305
+ self.history.append((self.current_hunk_idx, hunk.accepted))
306
+ hunk.accepted = False
307
+ self.current_hunk_idx += 1
308
+ elif choice in kb["edit"]:
309
+ self.console.print("[yellow]Edit mode not yet implemented[/yellow]")
310
+ self.console.input("Press Enter to continue...")
311
+ elif choice in kb["split"]:
312
+ self.console.print("[yellow]Split mode not yet implemented[/yellow]")
313
+ self.console.input("Press Enter to continue...")
314
+ elif choice in kb["next"]:
315
+ if self.current_hunk_idx < len(self.hunks) - 1:
316
+ self.current_hunk_idx += 1
317
+ elif choice in kb["prev"]:
318
+ if self.current_hunk_idx > 0:
319
+ self.current_hunk_idx -= 1
320
+ elif choice in kb["accept_all"]:
321
+ for h in self.hunks[self.current_hunk_idx:]:
322
+ h.accepted = True
323
+ break
324
+ elif choice in kb["reject_all"]:
325
+ for h in self.hunks[self.current_hunk_idx:]:
326
+ h.accepted = False
327
+ break
328
+ elif choice in kb["undo"]:
329
+ if self.history:
330
+ # Restore previous state
331
+ hunk_idx, prev_state = self.history.pop()
332
+ self.hunks[hunk_idx].accepted = prev_state
333
+ self.current_hunk_idx = hunk_idx
334
+ self.console.print("[yellow]↶ Undone last action[/yellow]")
335
+ self.console.input("Press Enter to continue...")
336
+ else:
337
+ self.console.print("[yellow]No actions to undo[/yellow]")
338
+ self.console.input("Press Enter to continue...")
339
+ elif choice in kb["quit"]:
340
+ self.console.print("[yellow]Quitting without applying changes[/yellow]")
341
+ return None
342
+ elif choice in kb["help"]:
343
+ self._show_help()
344
+ else:
345
+ self.console.print(f"[red]Unknown command: {choice}[/red]")
346
+ self.console.input("Press Enter to continue...")
347
+
348
+ # Apply accepted changes
349
+ return self._apply_changes()
350
+
351
+ def _apply_changes(self) -> str:
352
+ """Apply accepted changes and return final content."""
353
+ result_lines = list(self.original_lines)
354
+
355
+ # Apply hunks in reverse order to maintain line numbers
356
+ for hunk in reversed(self.hunks):
357
+ if hunk.accepted:
358
+ # Remove original lines
359
+ for _ in hunk.original_lines:
360
+ if hunk.original_start < len(result_lines):
361
+ result_lines.pop(hunk.original_start)
362
+
363
+ # Insert proposed lines
364
+ for i, line in enumerate(hunk.proposed_lines):
365
+ result_lines.insert(hunk.original_start + i, line)
366
+
367
+ return "".join(result_lines)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: claude-dev-cli
3
- Version: 0.2.0
3
+ Version: 0.3.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
@@ -28,12 +28,15 @@ Requires-Dist: rich>=13.0.0
28
28
  Requires-Dist: pydantic>=2.0.0
29
29
  Provides-Extra: toon
30
30
  Requires-Dist: toon-format>=0.9.0; extra == "toon"
31
+ Provides-Extra: plugins
32
+ Requires-Dist: pygments>=2.0.0; extra == "plugins"
31
33
  Provides-Extra: dev
32
34
  Requires-Dist: pytest>=7.0.0; extra == "dev"
33
35
  Requires-Dist: black>=23.0.0; extra == "dev"
34
36
  Requires-Dist: ruff>=0.1.0; extra == "dev"
35
37
  Requires-Dist: mypy>=1.0.0; extra == "dev"
36
38
  Requires-Dist: toon-format>=0.9.0; extra == "dev"
39
+ Requires-Dist: pygments>=2.0.0; extra == "dev"
37
40
  Dynamic: license-file
38
41
 
39
42
  # Claude Dev CLI
@@ -0,0 +1,19 @@
1
+ claude_dev_cli/__init__.py,sha256=2ulyIQ3E-s6wBTKyeXAlqHMVA73zUGdaaNUsFiJ-nqs,469
2
+ claude_dev_cli/cli.py,sha256=ekJpBU3d0k9DrE5VoJdKGNgPBsmdB4415up_Yhx_fw0,16174
3
+ claude_dev_cli/commands.py,sha256=RKGx2rv56PM6eErvA2uoQ20hY8babuI5jav8nCUyUOk,3964
4
+ claude_dev_cli/config.py,sha256=-AESk_3Oxq7AoMxvnBIbgplAU9AXQGtlpihEwgtww2s,5826
5
+ claude_dev_cli/core.py,sha256=yaLjEixDvPzvUy4fJ2UB7nMpPPLyKACjR-RuM-1OQBY,4780
6
+ claude_dev_cli/templates.py,sha256=lKxH943ySfUKgyHaWa4W3LVv91SgznKgajRtSRp_4UY,2260
7
+ claude_dev_cli/toon_utils.py,sha256=S3px2UvmNEaltmTa5K-h21n2c0CPvYjZc9mc7kHGqNQ,2828
8
+ claude_dev_cli/usage.py,sha256=32rs0_dUn6ihha3vCfT3rwnvel_-sED7jvLpO7gu-KQ,7446
9
+ claude_dev_cli/plugins/__init__.py,sha256=BdiZlylBzEgnwK2tuEdn8cITxhAZRVbTnDbWhdDhgqs,1340
10
+ claude_dev_cli/plugins/base.py,sha256=H4HQet1I-a3WLCfE9F06Lp8NuFvVoIlou7sIgyJFK-c,1417
11
+ claude_dev_cli/plugins/diff_editor/__init__.py,sha256=gqR5S2TyIVuq-sK107fegsutQ7Z-sgAIEbtc71FhXIM,101
12
+ claude_dev_cli/plugins/diff_editor/plugin.py,sha256=M1bUoqpasD3ZNQo36Fu_8g92uySPZyG_ujMbj5UplsU,3073
13
+ claude_dev_cli/plugins/diff_editor/viewer.py,sha256=LoS6MD48AIQM_9aeBhIuGOYJN1-D6TTxozPV-hkvnTc,13951
14
+ claude_dev_cli-0.3.1.dist-info/licenses/LICENSE,sha256=DGueuJwMJtMwgLO5mWlS0TaeBrFwQuNpNZ22PU9J2bw,1062
15
+ claude_dev_cli-0.3.1.dist-info/METADATA,sha256=oYmNCiM1y__QsoXnp3Cbvw3R21-SpUrepCpQ461v6eg,10447
16
+ claude_dev_cli-0.3.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
17
+ claude_dev_cli-0.3.1.dist-info/entry_points.txt,sha256=zymgUIIVpFTARkFmxAuW2A4BQsNITh_L0uU-XunytHg,85
18
+ claude_dev_cli-0.3.1.dist-info/top_level.txt,sha256=m7MF6LOIuTe41IT5Fgt0lc-DK1EgM4gUU_IZwWxK0pg,15
19
+ claude_dev_cli-0.3.1.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- claude_dev_cli/__init__.py,sha256=2ulyIQ3E-s6wBTKyeXAlqHMVA73zUGdaaNUsFiJ-nqs,469
2
- claude_dev_cli/cli.py,sha256=vWjUgtYljbzutTcEQxWU6mITtboUWA3E17UynuHrXm0,15833
3
- claude_dev_cli/commands.py,sha256=RKGx2rv56PM6eErvA2uoQ20hY8babuI5jav8nCUyUOk,3964
4
- claude_dev_cli/config.py,sha256=YwJjVkW9S1O_iq_2O6YCjYtuFWUCmP18zA7esKDwkKU,5776
5
- claude_dev_cli/core.py,sha256=97rR9BuNfnhJxFrd7dTdApGyPh6MeGNArcRmaiOY69I,4443
6
- claude_dev_cli/templates.py,sha256=lKxH943ySfUKgyHaWa4W3LVv91SgznKgajRtSRp_4UY,2260
7
- claude_dev_cli/toon_utils.py,sha256=S3px2UvmNEaltmTa5K-h21n2c0CPvYjZc9mc7kHGqNQ,2828
8
- claude_dev_cli/usage.py,sha256=32rs0_dUn6ihha3vCfT3rwnvel_-sED7jvLpO7gu-KQ,7446
9
- claude_dev_cli-0.2.0.dist-info/licenses/LICENSE,sha256=DGueuJwMJtMwgLO5mWlS0TaeBrFwQuNpNZ22PU9J2bw,1062
10
- claude_dev_cli-0.2.0.dist-info/METADATA,sha256=uaSOqYhfDsIt3dEAh1iBtJHS8MUM6dWTC_aBJZF8m2M,10325
11
- claude_dev_cli-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
- claude_dev_cli-0.2.0.dist-info/entry_points.txt,sha256=zymgUIIVpFTARkFmxAuW2A4BQsNITh_L0uU-XunytHg,85
13
- claude_dev_cli-0.2.0.dist-info/top_level.txt,sha256=m7MF6LOIuTe41IT5Fgt0lc-DK1EgM4gUU_IZwWxK0pg,15
14
- claude_dev_cli-0.2.0.dist-info/RECORD,,