claude-dev-cli 0.6.0__py3-none-any.whl → 0.8.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.
- claude_dev_cli/__init__.py +1 -1
- claude_dev_cli/cli.py +452 -22
- claude_dev_cli/config.py +6 -0
- claude_dev_cli/context.py +494 -0
- claude_dev_cli/warp_integration.py +243 -0
- claude_dev_cli/workflows.py +340 -0
- {claude_dev_cli-0.6.0.dist-info → claude_dev_cli-0.8.0.dist-info}/METADATA +50 -5
- {claude_dev_cli-0.6.0.dist-info → claude_dev_cli-0.8.0.dist-info}/RECORD +12 -9
- {claude_dev_cli-0.6.0.dist-info → claude_dev_cli-0.8.0.dist-info}/WHEEL +0 -0
- {claude_dev_cli-0.6.0.dist-info → claude_dev_cli-0.8.0.dist-info}/entry_points.txt +0 -0
- {claude_dev_cli-0.6.0.dist-info → claude_dev_cli-0.8.0.dist-info}/licenses/LICENSE +0 -0
- {claude_dev_cli-0.6.0.dist-info → claude_dev_cli-0.8.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""Warp terminal integration for enhanced output formatting."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def format_as_warp_block(
|
|
11
|
+
content: str,
|
|
12
|
+
title: Optional[str] = None,
|
|
13
|
+
language: Optional[str] = None,
|
|
14
|
+
actions: Optional[List[Dict[str, str]]] = None
|
|
15
|
+
) -> str:
|
|
16
|
+
"""Format content as a Warp block with optional actions.
|
|
17
|
+
|
|
18
|
+
Warp blocks support special formatting and click-to-run actions.
|
|
19
|
+
"""
|
|
20
|
+
block_parts = []
|
|
21
|
+
|
|
22
|
+
if title:
|
|
23
|
+
block_parts.append(f"### {title}")
|
|
24
|
+
block_parts.append("")
|
|
25
|
+
|
|
26
|
+
# Add content with language if code block
|
|
27
|
+
if language:
|
|
28
|
+
block_parts.append(f"```{language}")
|
|
29
|
+
block_parts.append(content)
|
|
30
|
+
block_parts.append("```")
|
|
31
|
+
else:
|
|
32
|
+
block_parts.append(content)
|
|
33
|
+
|
|
34
|
+
# Add actions if provided
|
|
35
|
+
if actions:
|
|
36
|
+
block_parts.append("")
|
|
37
|
+
block_parts.append("**Actions:**")
|
|
38
|
+
for action in actions:
|
|
39
|
+
label = action.get('label', 'Run')
|
|
40
|
+
command = action.get('command', '')
|
|
41
|
+
block_parts.append(f"- [{label}](`{command}`)")
|
|
42
|
+
|
|
43
|
+
return "\n".join(block_parts)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def generate_warp_workflow(
|
|
47
|
+
workflow_name: str,
|
|
48
|
+
commands: List[Dict[str, str]],
|
|
49
|
+
output_path: Optional[Path] = None
|
|
50
|
+
) -> str:
|
|
51
|
+
"""Generate a Warp workflow file from command list.
|
|
52
|
+
|
|
53
|
+
Warp workflows allow users to execute predefined command sequences.
|
|
54
|
+
"""
|
|
55
|
+
workflow = {
|
|
56
|
+
"name": workflow_name,
|
|
57
|
+
"command": "://" + workflow_name.lower().replace(" ", "-"),
|
|
58
|
+
"tags": ["claude-dev-cli"],
|
|
59
|
+
"description": f"Generated from claude-dev-cli",
|
|
60
|
+
"arguments": [],
|
|
61
|
+
"source_specs": [
|
|
62
|
+
{
|
|
63
|
+
"type": "command",
|
|
64
|
+
"command": cmd.get('command', ''),
|
|
65
|
+
"description": cmd.get('description', '')
|
|
66
|
+
}
|
|
67
|
+
for cmd in commands
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
workflow_yaml = yaml.dump(workflow, default_flow_style=False, sort_keys=False)
|
|
72
|
+
|
|
73
|
+
if output_path:
|
|
74
|
+
output_path.write_text(workflow_yaml)
|
|
75
|
+
|
|
76
|
+
return workflow_yaml
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def export_builtin_workflows(output_dir: Path) -> List[Path]:
|
|
80
|
+
"""Export built-in Warp workflows for common claude-dev-cli tasks."""
|
|
81
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
82
|
+
|
|
83
|
+
workflows = [
|
|
84
|
+
{
|
|
85
|
+
"name": "Code Review Workflow",
|
|
86
|
+
"commands": [
|
|
87
|
+
{
|
|
88
|
+
"command": "cdc review {{file}}",
|
|
89
|
+
"description": "Review code for issues"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"command": "cdc review {{file}} --interactive",
|
|
93
|
+
"description": "Review with follow-up questions"
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "Test Generation Workflow",
|
|
99
|
+
"commands": [
|
|
100
|
+
{
|
|
101
|
+
"command": "cdc generate tests {{file}} -o tests/test_{{file}}",
|
|
102
|
+
"description": "Generate tests"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"command": "pytest tests/test_{{file}}",
|
|
106
|
+
"description": "Run tests"
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "Refactor Workflow",
|
|
112
|
+
"commands": [
|
|
113
|
+
{
|
|
114
|
+
"command": "cdc refactor {{file}} --interactive",
|
|
115
|
+
"description": "Get refactoring suggestions"
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"command": "cdc review {{file}}",
|
|
119
|
+
"description": "Review changes"
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
"command": "git add {{file}} && cdc git commit",
|
|
123
|
+
"description": "Commit changes"
|
|
124
|
+
}
|
|
125
|
+
]
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"name": "Debug Workflow",
|
|
129
|
+
"commands": [
|
|
130
|
+
{
|
|
131
|
+
"command": "python {{file}} 2>&1 | cdc debug",
|
|
132
|
+
"description": "Run and debug errors"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"command": "cdc debug -f {{file}} -e \"{{error}}\"",
|
|
136
|
+
"description": "Debug specific error"
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
}
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
created_files = []
|
|
143
|
+
for workflow in workflows:
|
|
144
|
+
filename = workflow['name'].lower().replace(' ', '-') + '.yaml'
|
|
145
|
+
filepath = output_dir / filename
|
|
146
|
+
generate_warp_workflow(
|
|
147
|
+
workflow['name'],
|
|
148
|
+
workflow['commands'],
|
|
149
|
+
filepath
|
|
150
|
+
)
|
|
151
|
+
created_files.append(filepath)
|
|
152
|
+
|
|
153
|
+
return created_files
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def format_code_review_for_warp(review_output: str, file_path: str) -> str:
|
|
157
|
+
"""Format code review output as Warp block with actions."""
|
|
158
|
+
actions = [
|
|
159
|
+
{
|
|
160
|
+
"label": "Review with follow-up",
|
|
161
|
+
"command": f"cdc review {file_path} --interactive"
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
"label": "Refactor",
|
|
165
|
+
"command": f"cdc refactor {file_path} --interactive"
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
return format_as_warp_block(
|
|
170
|
+
content=review_output,
|
|
171
|
+
title="Code Review",
|
|
172
|
+
actions=actions
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def format_test_generation_for_warp(test_output: str, file_path: str) -> str:
|
|
177
|
+
"""Format test generation output as Warp block with actions."""
|
|
178
|
+
test_file = f"tests/test_{Path(file_path).name}"
|
|
179
|
+
|
|
180
|
+
actions = [
|
|
181
|
+
{
|
|
182
|
+
"label": "Save tests",
|
|
183
|
+
"command": f"cdc generate tests {file_path} -o {test_file}"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"label": "Run tests",
|
|
187
|
+
"command": f"pytest {test_file}"
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
return format_as_warp_block(
|
|
192
|
+
content=test_output,
|
|
193
|
+
title="Generated Tests",
|
|
194
|
+
language="python",
|
|
195
|
+
actions=actions
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def create_warp_launch_config(
|
|
200
|
+
name: str,
|
|
201
|
+
command: str,
|
|
202
|
+
cwd: Optional[str] = None,
|
|
203
|
+
env: Optional[Dict[str, str]] = None
|
|
204
|
+
) -> Dict[str, Any]:
|
|
205
|
+
"""Create a Warp launch configuration.
|
|
206
|
+
|
|
207
|
+
Launch configs allow quick environment setup in Warp.
|
|
208
|
+
"""
|
|
209
|
+
config = {
|
|
210
|
+
"name": name,
|
|
211
|
+
"command": command,
|
|
212
|
+
"type": "terminal"
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if cwd:
|
|
216
|
+
config["cwd"] = cwd
|
|
217
|
+
|
|
218
|
+
if env:
|
|
219
|
+
config["env"] = env
|
|
220
|
+
|
|
221
|
+
return config
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def export_launch_configs(output_path: Path) -> None:
|
|
225
|
+
"""Export Warp launch configurations for claude-dev-cli."""
|
|
226
|
+
configs = [
|
|
227
|
+
create_warp_launch_config(
|
|
228
|
+
name="Claude Dev CLI - Interactive",
|
|
229
|
+
command="cdc interactive"
|
|
230
|
+
),
|
|
231
|
+
create_warp_launch_config(
|
|
232
|
+
name="Claude Dev CLI - Review Mode",
|
|
233
|
+
command="cdc review"
|
|
234
|
+
),
|
|
235
|
+
create_warp_launch_config(
|
|
236
|
+
name="Claude Dev CLI - Test Generation",
|
|
237
|
+
command="cdc generate tests"
|
|
238
|
+
)
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
242
|
+
with open(output_path, 'w') as f:
|
|
243
|
+
json.dump({"launch_configurations": configs}, f, indent=2)
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""Workflow execution engine for chaining AI operations."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any, Dict, List, Optional, Union
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
from rich.console import Console
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class StepResult:
|
|
15
|
+
"""Result from executing a workflow step."""
|
|
16
|
+
success: bool
|
|
17
|
+
output: str
|
|
18
|
+
error: Optional[str] = None
|
|
19
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class WorkflowContext:
|
|
24
|
+
"""Context passed between workflow steps."""
|
|
25
|
+
variables: Dict[str, Any] = field(default_factory=dict)
|
|
26
|
+
step_results: Dict[str, StepResult] = field(default_factory=dict)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class WorkflowEngine:
|
|
30
|
+
"""Execute workflow definitions with step chaining."""
|
|
31
|
+
|
|
32
|
+
def __init__(self, console: Optional[Console] = None):
|
|
33
|
+
self.console = console or Console()
|
|
34
|
+
|
|
35
|
+
def load_workflow(self, path: Path) -> Dict[str, Any]:
|
|
36
|
+
"""Load workflow from YAML file."""
|
|
37
|
+
with open(path, 'r') as f:
|
|
38
|
+
return yaml.safe_load(f)
|
|
39
|
+
|
|
40
|
+
def execute(
|
|
41
|
+
self,
|
|
42
|
+
workflow: Union[Dict[str, Any], Path],
|
|
43
|
+
initial_vars: Optional[Dict[str, Any]] = None
|
|
44
|
+
) -> WorkflowContext:
|
|
45
|
+
"""Execute a workflow definition."""
|
|
46
|
+
# Load from file if path provided
|
|
47
|
+
if isinstance(workflow, Path):
|
|
48
|
+
workflow = self.load_workflow(workflow)
|
|
49
|
+
|
|
50
|
+
# Initialize context
|
|
51
|
+
context = WorkflowContext(variables=initial_vars or {})
|
|
52
|
+
|
|
53
|
+
# Extract workflow metadata
|
|
54
|
+
name = workflow.get('name', 'Unnamed Workflow')
|
|
55
|
+
description = workflow.get('description', '')
|
|
56
|
+
steps = workflow.get('steps', [])
|
|
57
|
+
|
|
58
|
+
self.console.print(f"\n[bold cyan]Running workflow:[/bold cyan] {name}")
|
|
59
|
+
if description:
|
|
60
|
+
self.console.print(f"[dim]{description}[/dim]")
|
|
61
|
+
|
|
62
|
+
# Execute steps
|
|
63
|
+
for i, step in enumerate(steps, 1):
|
|
64
|
+
step_name = step.get('name', f'step-{i}')
|
|
65
|
+
|
|
66
|
+
# Check conditional
|
|
67
|
+
if 'if' in step:
|
|
68
|
+
condition = self._evaluate_condition(step['if'], context)
|
|
69
|
+
if not condition:
|
|
70
|
+
self.console.print(f"[yellow]↷[/yellow] Skipping step {i}: {step_name} (condition false)")
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
self.console.print(f"\n[bold]Step {i}:[/bold] {step_name}")
|
|
74
|
+
|
|
75
|
+
# Check for approval gate
|
|
76
|
+
if step.get('approval_required', False):
|
|
77
|
+
if not self._request_approval(step_name):
|
|
78
|
+
self.console.print("[yellow]Workflow stopped by user[/yellow]")
|
|
79
|
+
break
|
|
80
|
+
|
|
81
|
+
# Execute step
|
|
82
|
+
try:
|
|
83
|
+
result = self._execute_step(step, context)
|
|
84
|
+
context.step_results[step_name] = result
|
|
85
|
+
|
|
86
|
+
if result.success:
|
|
87
|
+
self.console.print(f"[green]✓[/green] {step_name} completed")
|
|
88
|
+
else:
|
|
89
|
+
self.console.print(f"[red]✗[/red] {step_name} failed: {result.error}")
|
|
90
|
+
|
|
91
|
+
# Check if workflow should continue on error
|
|
92
|
+
if not step.get('continue_on_error', False):
|
|
93
|
+
self.console.print("[red]Workflow stopped due to error[/red]")
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
except Exception as e:
|
|
97
|
+
self.console.print(f"[red]Error in step {step_name}: {e}[/red]")
|
|
98
|
+
if not step.get('continue_on_error', False):
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
self.console.print("\n[bold green]Workflow completed[/bold green]")
|
|
102
|
+
return context
|
|
103
|
+
|
|
104
|
+
def _execute_step(self, step: Dict[str, Any], context: WorkflowContext) -> StepResult:
|
|
105
|
+
"""Execute a single workflow step."""
|
|
106
|
+
# Determine step type
|
|
107
|
+
if 'command' in step:
|
|
108
|
+
return self._execute_command_step(step, context)
|
|
109
|
+
elif 'shell' in step:
|
|
110
|
+
return self._execute_shell_step(step, context)
|
|
111
|
+
elif 'set' in step:
|
|
112
|
+
return self._execute_set_step(step, context)
|
|
113
|
+
else:
|
|
114
|
+
return StepResult(
|
|
115
|
+
success=False,
|
|
116
|
+
output="",
|
|
117
|
+
error="Unknown step type"
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
def _execute_command_step(self, step: Dict[str, Any], context: WorkflowContext) -> StepResult:
|
|
121
|
+
"""Execute a cdc command step."""
|
|
122
|
+
command = step['command']
|
|
123
|
+
args = step.get('args', {})
|
|
124
|
+
|
|
125
|
+
# Interpolate variables in args
|
|
126
|
+
interpolated_args = self._interpolate_variables(args, context)
|
|
127
|
+
|
|
128
|
+
# Import here to avoid circular dependency
|
|
129
|
+
from claude_dev_cli.commands import (
|
|
130
|
+
generate_tests, code_review, debug_code,
|
|
131
|
+
generate_docs, refactor_code, git_commit_message
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Map commands to functions
|
|
135
|
+
command_map = {
|
|
136
|
+
'generate tests': generate_tests,
|
|
137
|
+
'review': code_review,
|
|
138
|
+
'debug': debug_code,
|
|
139
|
+
'generate docs': generate_docs,
|
|
140
|
+
'refactor': refactor_code,
|
|
141
|
+
'git commit': git_commit_message,
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if command not in command_map:
|
|
145
|
+
return StepResult(
|
|
146
|
+
success=False,
|
|
147
|
+
output="",
|
|
148
|
+
error=f"Unknown command: {command}"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
try:
|
|
152
|
+
func = command_map[command]
|
|
153
|
+
|
|
154
|
+
# Build function arguments
|
|
155
|
+
func_args = {}
|
|
156
|
+
if 'file' in interpolated_args:
|
|
157
|
+
func_args['file_path'] = interpolated_args['file']
|
|
158
|
+
if 'error' in interpolated_args:
|
|
159
|
+
func_args['error_message'] = interpolated_args['error']
|
|
160
|
+
if 'api' in interpolated_args:
|
|
161
|
+
func_args['api_config_name'] = interpolated_args['api']
|
|
162
|
+
|
|
163
|
+
# Execute
|
|
164
|
+
result = func(**func_args)
|
|
165
|
+
|
|
166
|
+
# Store output in context for next steps
|
|
167
|
+
if 'output_var' in step:
|
|
168
|
+
context.variables[step['output_var']] = result
|
|
169
|
+
|
|
170
|
+
return StepResult(
|
|
171
|
+
success=True,
|
|
172
|
+
output=result,
|
|
173
|
+
metadata={'command': command}
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
except Exception as e:
|
|
177
|
+
return StepResult(
|
|
178
|
+
success=False,
|
|
179
|
+
output="",
|
|
180
|
+
error=str(e)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def _execute_shell_step(self, step: Dict[str, Any], context: WorkflowContext) -> StepResult:
|
|
184
|
+
"""Execute a shell command step."""
|
|
185
|
+
command = step['shell']
|
|
186
|
+
|
|
187
|
+
# Interpolate variables
|
|
188
|
+
command = self._interpolate_string(command, context)
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
result = subprocess.run(
|
|
192
|
+
command,
|
|
193
|
+
shell=True,
|
|
194
|
+
capture_output=True,
|
|
195
|
+
text=True,
|
|
196
|
+
check=False
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
success = result.returncode == 0
|
|
200
|
+
output = result.stdout or result.stderr
|
|
201
|
+
|
|
202
|
+
# Store output in context
|
|
203
|
+
if 'output_var' in step:
|
|
204
|
+
context.variables[step['output_var']] = output.strip()
|
|
205
|
+
|
|
206
|
+
return StepResult(
|
|
207
|
+
success=success,
|
|
208
|
+
output=output,
|
|
209
|
+
error=result.stderr if not success else None,
|
|
210
|
+
metadata={'returncode': result.returncode}
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
return StepResult(
|
|
215
|
+
success=False,
|
|
216
|
+
output="",
|
|
217
|
+
error=str(e)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
def _execute_set_step(self, step: Dict[str, Any], context: WorkflowContext) -> StepResult:
|
|
221
|
+
"""Execute a variable assignment step."""
|
|
222
|
+
var_name = step['set']
|
|
223
|
+
value = step.get('value', '')
|
|
224
|
+
|
|
225
|
+
# Interpolate value
|
|
226
|
+
value = self._interpolate_value(value, context)
|
|
227
|
+
|
|
228
|
+
context.variables[var_name] = value
|
|
229
|
+
|
|
230
|
+
return StepResult(
|
|
231
|
+
success=True,
|
|
232
|
+
output=str(value),
|
|
233
|
+
metadata={'variable': var_name}
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def _interpolate_variables(
|
|
237
|
+
self,
|
|
238
|
+
data: Union[Dict, List, str, Any],
|
|
239
|
+
context: WorkflowContext
|
|
240
|
+
) -> Any:
|
|
241
|
+
"""Recursively interpolate variables in data structures."""
|
|
242
|
+
if isinstance(data, dict):
|
|
243
|
+
return {k: self._interpolate_variables(v, context) for k, v in data.items()}
|
|
244
|
+
elif isinstance(data, list):
|
|
245
|
+
return [self._interpolate_variables(item, context) for item in data]
|
|
246
|
+
elif isinstance(data, str):
|
|
247
|
+
return self._interpolate_string(data, context)
|
|
248
|
+
else:
|
|
249
|
+
return data
|
|
250
|
+
|
|
251
|
+
def _interpolate_string(self, text: str, context: WorkflowContext) -> str:
|
|
252
|
+
"""Interpolate {{variable}} placeholders in string."""
|
|
253
|
+
def replace(match: re.Match) -> str:
|
|
254
|
+
var_path = match.group(1)
|
|
255
|
+
return str(self._resolve_variable(var_path, context))
|
|
256
|
+
|
|
257
|
+
return re.sub(r'\{\{([^}]+)\}\}', replace, text)
|
|
258
|
+
|
|
259
|
+
def _interpolate_value(self, value: Any, context: WorkflowContext) -> Any:
|
|
260
|
+
"""Interpolate a single value."""
|
|
261
|
+
if isinstance(value, str):
|
|
262
|
+
return self._interpolate_string(value, context)
|
|
263
|
+
return value
|
|
264
|
+
|
|
265
|
+
def _resolve_variable(self, var_path: str, context: WorkflowContext) -> Any:
|
|
266
|
+
"""Resolve a variable path like 'step1.output' or 'vars.filename'."""
|
|
267
|
+
parts = var_path.strip().split('.')
|
|
268
|
+
|
|
269
|
+
# Check step results first
|
|
270
|
+
if len(parts) >= 2 and parts[0] in context.step_results:
|
|
271
|
+
step_result = context.step_results[parts[0]]
|
|
272
|
+
if parts[1] == 'output':
|
|
273
|
+
return step_result.output
|
|
274
|
+
elif parts[1] == 'success':
|
|
275
|
+
return step_result.success
|
|
276
|
+
elif parts[1] == 'error':
|
|
277
|
+
return step_result.error or ''
|
|
278
|
+
|
|
279
|
+
# Check variables
|
|
280
|
+
if parts[0] in context.variables:
|
|
281
|
+
value = context.variables[parts[0]]
|
|
282
|
+
# Support nested access for dicts
|
|
283
|
+
for part in parts[1:]:
|
|
284
|
+
if isinstance(value, dict):
|
|
285
|
+
value = value.get(part, '')
|
|
286
|
+
else:
|
|
287
|
+
return ''
|
|
288
|
+
return value
|
|
289
|
+
|
|
290
|
+
return f'{{{{{var_path}}}}}' # Return placeholder if not found
|
|
291
|
+
|
|
292
|
+
def _evaluate_condition(self, condition: str, context: WorkflowContext) -> bool:
|
|
293
|
+
"""Evaluate a simple condition expression."""
|
|
294
|
+
# Interpolate variables
|
|
295
|
+
evaluated = self._interpolate_string(condition, context)
|
|
296
|
+
|
|
297
|
+
# Simple boolean evaluation
|
|
298
|
+
if evaluated.lower() in ('true', '1', 'yes'):
|
|
299
|
+
return True
|
|
300
|
+
if evaluated.lower() in ('false', '0', 'no', ''):
|
|
301
|
+
return False
|
|
302
|
+
|
|
303
|
+
# Try to evaluate as Python expression (limited to safe operations)
|
|
304
|
+
try:
|
|
305
|
+
# Only allow simple comparisons
|
|
306
|
+
if any(op in evaluated for op in ['==', '!=', '>', '<', '>=', '<=']):
|
|
307
|
+
return eval(evaluated, {"__builtins__": {}}, {})
|
|
308
|
+
except Exception:
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
return bool(evaluated)
|
|
312
|
+
|
|
313
|
+
def _request_approval(self, step_name: str) -> bool:
|
|
314
|
+
"""Request user approval to proceed."""
|
|
315
|
+
self.console.print(f"\n[yellow]⚠ Approval required for:[/yellow] {step_name}")
|
|
316
|
+
response = self.console.input("[cyan]Proceed? (y/n):[/cyan] ").strip().lower()
|
|
317
|
+
return response in ('y', 'yes')
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def list_workflows(workflow_dir: Path) -> List[Dict[str, str]]:
|
|
321
|
+
"""List available workflows in a directory."""
|
|
322
|
+
if not workflow_dir.exists():
|
|
323
|
+
return []
|
|
324
|
+
|
|
325
|
+
workflows = []
|
|
326
|
+
for file in workflow_dir.glob('*.yaml'):
|
|
327
|
+
try:
|
|
328
|
+
with open(file, 'r') as f:
|
|
329
|
+
data = yaml.safe_load(f)
|
|
330
|
+
|
|
331
|
+
workflows.append({
|
|
332
|
+
'name': data.get('name', file.stem),
|
|
333
|
+
'description': data.get('description', ''),
|
|
334
|
+
'file': str(file),
|
|
335
|
+
'steps': len(data.get('steps', []))
|
|
336
|
+
})
|
|
337
|
+
except Exception:
|
|
338
|
+
continue
|
|
339
|
+
|
|
340
|
+
return workflows
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-dev-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
|
@@ -28,6 +28,7 @@ Requires-Dist: rich>=13.0.0
|
|
|
28
28
|
Requires-Dist: pydantic>=2.0.0
|
|
29
29
|
Requires-Dist: keyring>=24.0.0
|
|
30
30
|
Requires-Dist: cryptography>=41.0.0
|
|
31
|
+
Requires-Dist: pyyaml>=6.0.0
|
|
31
32
|
Provides-Extra: toon
|
|
32
33
|
Requires-Dist: toon-format>=0.9.0; extra == "toon"
|
|
33
34
|
Provides-Extra: plugins
|
|
@@ -78,6 +79,13 @@ A powerful command-line tool for developers using Claude AI with multi-API routi
|
|
|
78
79
|
- **Variable Substitution**: Use {{variable}} placeholders for dynamic content
|
|
79
80
|
- **Categories**: Organize templates by category (review, testing, debugging, etc.)
|
|
80
81
|
|
|
82
|
+
### 🧠 Context Intelligence (NEW in v0.8.0)
|
|
83
|
+
- **Auto-Context**: `--auto-context` flag for intelligent context gathering
|
|
84
|
+
- **Git Integration**: Automatically include branch, commits, modified files
|
|
85
|
+
- **Dependency Analysis**: Parse imports and include related files
|
|
86
|
+
- **Error Parsing**: Structured Python traceback parsing
|
|
87
|
+
- **Project Memory**: Remember preferences per project
|
|
88
|
+
|
|
81
89
|
### 🎒 TOON Format Support (Optional)
|
|
82
90
|
- **Token Reduction**: 30-60% fewer tokens than JSON
|
|
83
91
|
- **Cost Savings**: Reduce API costs significantly
|
|
@@ -145,23 +153,60 @@ cdc ask -a client "generate tests for this function"
|
|
|
145
153
|
# Generate tests
|
|
146
154
|
cdc generate tests mymodule.py -o tests/test_mymodule.py
|
|
147
155
|
|
|
156
|
+
# Generate tests with interactive refinement
|
|
157
|
+
cdc generate tests mymodule.py --interactive
|
|
158
|
+
|
|
148
159
|
# Code review
|
|
149
160
|
cdc review mymodule.py
|
|
150
161
|
|
|
151
|
-
#
|
|
152
|
-
|
|
162
|
+
# Code review with auto-context (includes git, dependencies, tests)
|
|
163
|
+
cdc review mymodule.py --auto-context
|
|
164
|
+
|
|
165
|
+
# Code review with interactive follow-up questions
|
|
166
|
+
cdc review mymodule.py --interactive
|
|
167
|
+
|
|
168
|
+
# Debug errors with intelligent error parsing
|
|
169
|
+
python script.py 2>&1 | cdc debug --auto-context
|
|
153
170
|
|
|
154
171
|
# Generate documentation
|
|
155
172
|
cdc generate docs mymodule.py
|
|
156
173
|
|
|
157
|
-
#
|
|
158
|
-
cdc
|
|
174
|
+
# Generate docs with interactive refinement
|
|
175
|
+
cdc generate docs mymodule.py --interactive
|
|
176
|
+
|
|
177
|
+
# Refactor with context (includes related files)
|
|
178
|
+
cdc refactor legacy_code.py --auto-context
|
|
179
|
+
|
|
180
|
+
# Refactor with interactive refinement
|
|
181
|
+
cdc refactor legacy_code.py --interactive
|
|
159
182
|
|
|
160
183
|
# Git commit message
|
|
161
184
|
git add .
|
|
162
185
|
cdc git commit
|
|
163
186
|
```
|
|
164
187
|
|
|
188
|
+
### 4. Context-Aware Operations (NEW in v0.8.0)
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
# Auto-context includes: git info, dependencies, related files
|
|
192
|
+
|
|
193
|
+
# Review with full project context
|
|
194
|
+
cdc review mymodule.py --auto-context
|
|
195
|
+
# ✓ Context gathered (git, dependencies, tests)
|
|
196
|
+
|
|
197
|
+
# Debug with parsed error details
|
|
198
|
+
python broken.py 2>&1 | cdc debug -f broken.py --auto-context
|
|
199
|
+
# ✓ Context gathered (error details, git context)
|
|
200
|
+
|
|
201
|
+
# Ask questions with file context
|
|
202
|
+
cdc ask -f mycode.py --auto-context "how can I improve this?"
|
|
203
|
+
# ✓ Context gathered
|
|
204
|
+
|
|
205
|
+
# Refactor with related files
|
|
206
|
+
cdc refactor app.py --auto-context
|
|
207
|
+
# Automatically includes imported modules and dependencies
|
|
208
|
+
```
|
|
209
|
+
|
|
165
210
|
### 5. Custom Templates
|
|
166
211
|
|
|
167
212
|
```bash
|