claude-dev-cli 0.2.0__tar.gz → 0.3.1__tar.gz
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-0.2.0/src/claude_dev_cli.egg-info → claude_dev_cli-0.3.1}/PKG-INFO +4 -1
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/pyproject.toml +5 -1
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli/cli.py +12 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli/config.py +7 -7
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli/core.py +13 -5
- claude_dev_cli-0.3.1/src/claude_dev_cli/plugins/__init__.py +42 -0
- claude_dev_cli-0.3.1/src/claude_dev_cli/plugins/base.py +53 -0
- claude_dev_cli-0.3.1/src/claude_dev_cli/plugins/diff_editor/__init__.py +3 -0
- claude_dev_cli-0.3.1/src/claude_dev_cli/plugins/diff_editor/plugin.py +98 -0
- claude_dev_cli-0.3.1/src/claude_dev_cli/plugins/diff_editor/viewer.py +367 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1/src/claude_dev_cli.egg-info}/PKG-INFO +4 -1
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli.egg-info/SOURCES.txt +12 -1
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli.egg-info/requires.txt +4 -0
- claude_dev_cli-0.3.1/tests/test_cli.py +262 -0
- claude_dev_cli-0.3.1/tests/test_commands.py +314 -0
- claude_dev_cli-0.3.1/tests/test_config.py +255 -0
- claude_dev_cli-0.3.1/tests/test_core.py +240 -0
- claude_dev_cli-0.3.1/tests/test_toon_utils.py +119 -0
- claude_dev_cli-0.3.1/tests/test_usage.py +222 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/LICENSE +0 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/MANIFEST.in +0 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/README.md +0 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/setup.cfg +0 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli/__init__.py +0 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli/commands.py +0 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli/templates.py +0 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli/toon_utils.py +0 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli/usage.py +0 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli.egg-info/dependency_links.txt +0 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli.egg-info/entry_points.txt +0 -0
- {claude_dev_cli-0.2.0 → claude_dev_cli-0.3.1}/src/claude_dev_cli.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-dev-cli
|
|
3
|
-
Version: 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
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "claude-dev-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.1"
|
|
8
8
|
description = "A powerful CLI tool for developers using Claude AI with multi-API routing, test generation, code review, and usage tracking"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -38,12 +38,16 @@ dependencies = [
|
|
|
38
38
|
toon = [
|
|
39
39
|
"toon-format>=0.9.0",
|
|
40
40
|
]
|
|
41
|
+
plugins = [
|
|
42
|
+
"pygments>=2.0.0",
|
|
43
|
+
]
|
|
41
44
|
dev = [
|
|
42
45
|
"pytest>=7.0.0",
|
|
43
46
|
"black>=23.0.0",
|
|
44
47
|
"ruff>=0.1.0",
|
|
45
48
|
"mypy>=1.0.0",
|
|
46
49
|
"toon-format>=0.9.0",
|
|
50
|
+
"pygments>=2.0.0",
|
|
47
51
|
]
|
|
48
52
|
|
|
49
53
|
[project.urls]
|
|
@@ -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')
|
|
@@ -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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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,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()
|