claude-dev-cli 0.6.0__tar.gz → 0.7.0__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.6.0/src/claude_dev_cli.egg-info → claude_dev_cli-0.7.0}/PKG-INFO +14 -1
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/README.md +12 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/pyproject.toml +2 -1
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/__init__.py +1 -1
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/cli.py +364 -10
- claude_dev_cli-0.7.0/src/claude_dev_cli/warp_integration.py +243 -0
- claude_dev_cli-0.7.0/src/claude_dev_cli/workflows.py +340 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0/src/claude_dev_cli.egg-info}/PKG-INFO +14 -1
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli.egg-info/SOURCES.txt +2 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli.egg-info/requires.txt +1 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/LICENSE +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/MANIFEST.in +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/setup.cfg +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/commands.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/config.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/core.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/history.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/plugins/__init__.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/plugins/base.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/plugins/diff_editor/__init__.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/plugins/diff_editor/plugin.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/plugins/diff_editor/viewer.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/secure_storage.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/template_manager.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/templates.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/toon_utils.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/usage.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli.egg-info/dependency_links.txt +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli.egg-info/entry_points.txt +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli.egg-info/top_level.txt +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/tests/test_cli.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/tests/test_commands.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/tests/test_config.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/tests/test_core.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/tests/test_diff_editor.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/tests/test_secure_storage.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/tests/test_template_manager.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/tests/test_toon_utils.py +0 -0
- {claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/tests/test_usage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: claude-dev-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.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
|
|
@@ -145,18 +146,30 @@ cdc ask -a client "generate tests for this function"
|
|
|
145
146
|
# Generate tests
|
|
146
147
|
cdc generate tests mymodule.py -o tests/test_mymodule.py
|
|
147
148
|
|
|
149
|
+
# Generate tests with interactive refinement
|
|
150
|
+
cdc generate tests mymodule.py --interactive
|
|
151
|
+
|
|
148
152
|
# Code review
|
|
149
153
|
cdc review mymodule.py
|
|
150
154
|
|
|
155
|
+
# Code review with interactive follow-up questions
|
|
156
|
+
cdc review mymodule.py --interactive
|
|
157
|
+
|
|
151
158
|
# Debug errors
|
|
152
159
|
python script.py 2>&1 | cdc debug
|
|
153
160
|
|
|
154
161
|
# Generate documentation
|
|
155
162
|
cdc generate docs mymodule.py
|
|
156
163
|
|
|
164
|
+
# Generate docs with interactive refinement
|
|
165
|
+
cdc generate docs mymodule.py --interactive
|
|
166
|
+
|
|
157
167
|
# Refactor suggestions
|
|
158
168
|
cdc refactor legacy_code.py
|
|
159
169
|
|
|
170
|
+
# Refactor with interactive refinement
|
|
171
|
+
cdc refactor legacy_code.py --interactive
|
|
172
|
+
|
|
160
173
|
# Git commit message
|
|
161
174
|
git add .
|
|
162
175
|
cdc git commit
|
|
@@ -102,18 +102,30 @@ cdc ask -a client "generate tests for this function"
|
|
|
102
102
|
# Generate tests
|
|
103
103
|
cdc generate tests mymodule.py -o tests/test_mymodule.py
|
|
104
104
|
|
|
105
|
+
# Generate tests with interactive refinement
|
|
106
|
+
cdc generate tests mymodule.py --interactive
|
|
107
|
+
|
|
105
108
|
# Code review
|
|
106
109
|
cdc review mymodule.py
|
|
107
110
|
|
|
111
|
+
# Code review with interactive follow-up questions
|
|
112
|
+
cdc review mymodule.py --interactive
|
|
113
|
+
|
|
108
114
|
# Debug errors
|
|
109
115
|
python script.py 2>&1 | cdc debug
|
|
110
116
|
|
|
111
117
|
# Generate documentation
|
|
112
118
|
cdc generate docs mymodule.py
|
|
113
119
|
|
|
120
|
+
# Generate docs with interactive refinement
|
|
121
|
+
cdc generate docs mymodule.py --interactive
|
|
122
|
+
|
|
114
123
|
# Refactor suggestions
|
|
115
124
|
cdc refactor legacy_code.py
|
|
116
125
|
|
|
126
|
+
# Refactor with interactive refinement
|
|
127
|
+
cdc refactor legacy_code.py --interactive
|
|
128
|
+
|
|
117
129
|
# Git commit message
|
|
118
130
|
git add .
|
|
119
131
|
cdc git commit
|
|
@@ -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.7.0"
|
|
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"
|
|
@@ -34,6 +34,7 @@ dependencies = [
|
|
|
34
34
|
"pydantic>=2.0.0",
|
|
35
35
|
"keyring>=24.0.0",
|
|
36
36
|
"cryptography>=41.0.0",
|
|
37
|
+
"pyyaml>=6.0.0",
|
|
37
38
|
]
|
|
38
39
|
|
|
39
40
|
[project.optional-dependencies]
|
|
@@ -447,12 +447,14 @@ def generate() -> None:
|
|
|
447
447
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
448
448
|
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
449
449
|
@click.option('-a', '--api', help='API config to use')
|
|
450
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
450
451
|
@click.pass_context
|
|
451
452
|
def gen_tests(
|
|
452
453
|
ctx: click.Context,
|
|
453
454
|
file_path: str,
|
|
454
455
|
output: Optional[str],
|
|
455
|
-
api: Optional[str]
|
|
456
|
+
api: Optional[str],
|
|
457
|
+
interactive: bool
|
|
456
458
|
) -> None:
|
|
457
459
|
"""Generate pytest tests for a Python file."""
|
|
458
460
|
console = ctx.obj['console']
|
|
@@ -461,11 +463,48 @@ def gen_tests(
|
|
|
461
463
|
with console.status("[bold blue]Generating tests..."):
|
|
462
464
|
result = generate_tests(file_path, api_config_name=api)
|
|
463
465
|
|
|
466
|
+
if interactive:
|
|
467
|
+
# Show initial result
|
|
468
|
+
console.print("\n[bold]Initial Tests:[/bold]\n")
|
|
469
|
+
console.print(result)
|
|
470
|
+
|
|
471
|
+
# Interactive refinement loop
|
|
472
|
+
client = ClaudeClient(api_config_name=api)
|
|
473
|
+
conversation_context = [result]
|
|
474
|
+
|
|
475
|
+
while True:
|
|
476
|
+
console.print("\n[dim]Commands: 'save' to save and exit, 'exit' to discard, or ask for changes[/dim]")
|
|
477
|
+
user_input = console.input("[cyan]You:[/cyan] ").strip()
|
|
478
|
+
|
|
479
|
+
if user_input.lower() == 'exit':
|
|
480
|
+
console.print("[yellow]Discarded changes[/yellow]")
|
|
481
|
+
return
|
|
482
|
+
|
|
483
|
+
if user_input.lower() == 'save':
|
|
484
|
+
result = conversation_context[-1]
|
|
485
|
+
break
|
|
486
|
+
|
|
487
|
+
if not user_input:
|
|
488
|
+
continue
|
|
489
|
+
|
|
490
|
+
# Get refinement
|
|
491
|
+
refinement_prompt = f"Previous tests:\n\n{conversation_context[-1]}\n\nUser request: {user_input}\n\nProvide the updated tests."
|
|
492
|
+
|
|
493
|
+
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
494
|
+
response_parts = []
|
|
495
|
+
for chunk in client.call_streaming(refinement_prompt):
|
|
496
|
+
console.print(chunk, end='')
|
|
497
|
+
response_parts.append(chunk)
|
|
498
|
+
console.print()
|
|
499
|
+
|
|
500
|
+
result = ''.join(response_parts)
|
|
501
|
+
conversation_context.append(result)
|
|
502
|
+
|
|
464
503
|
if output:
|
|
465
504
|
with open(output, 'w') as f:
|
|
466
505
|
f.write(result)
|
|
467
|
-
console.print(f"[green]✓[/green] Tests saved to: {output}")
|
|
468
|
-
|
|
506
|
+
console.print(f"\n[green]✓[/green] Tests saved to: {output}")
|
|
507
|
+
elif not interactive:
|
|
469
508
|
console.print(result)
|
|
470
509
|
|
|
471
510
|
except Exception as e:
|
|
@@ -477,12 +516,14 @@ def gen_tests(
|
|
|
477
516
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
478
517
|
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
479
518
|
@click.option('-a', '--api', help='API config to use')
|
|
519
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
480
520
|
@click.pass_context
|
|
481
521
|
def gen_docs(
|
|
482
522
|
ctx: click.Context,
|
|
483
523
|
file_path: str,
|
|
484
524
|
output: Optional[str],
|
|
485
|
-
api: Optional[str]
|
|
525
|
+
api: Optional[str],
|
|
526
|
+
interactive: bool
|
|
486
527
|
) -> None:
|
|
487
528
|
"""Generate documentation for a Python file."""
|
|
488
529
|
console = ctx.obj['console']
|
|
@@ -491,11 +532,46 @@ def gen_docs(
|
|
|
491
532
|
with console.status("[bold blue]Generating documentation..."):
|
|
492
533
|
result = generate_docs(file_path, api_config_name=api)
|
|
493
534
|
|
|
535
|
+
if interactive:
|
|
536
|
+
console.print("\n[bold]Initial Documentation:[/bold]\n")
|
|
537
|
+
md = Markdown(result)
|
|
538
|
+
console.print(md)
|
|
539
|
+
|
|
540
|
+
client = ClaudeClient(api_config_name=api)
|
|
541
|
+
conversation_context = [result]
|
|
542
|
+
|
|
543
|
+
while True:
|
|
544
|
+
console.print("\n[dim]Commands: 'save' to save and exit, 'exit' to discard, or ask for changes[/dim]")
|
|
545
|
+
user_input = console.input("[cyan]You:[/cyan] ").strip()
|
|
546
|
+
|
|
547
|
+
if user_input.lower() == 'exit':
|
|
548
|
+
console.print("[yellow]Discarded changes[/yellow]")
|
|
549
|
+
return
|
|
550
|
+
|
|
551
|
+
if user_input.lower() == 'save':
|
|
552
|
+
result = conversation_context[-1]
|
|
553
|
+
break
|
|
554
|
+
|
|
555
|
+
if not user_input:
|
|
556
|
+
continue
|
|
557
|
+
|
|
558
|
+
refinement_prompt = f"Previous documentation:\n\n{conversation_context[-1]}\n\nUser request: {user_input}\n\nProvide the updated documentation."
|
|
559
|
+
|
|
560
|
+
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
561
|
+
response_parts = []
|
|
562
|
+
for chunk in client.call_streaming(refinement_prompt):
|
|
563
|
+
console.print(chunk, end='')
|
|
564
|
+
response_parts.append(chunk)
|
|
565
|
+
console.print()
|
|
566
|
+
|
|
567
|
+
result = ''.join(response_parts)
|
|
568
|
+
conversation_context.append(result)
|
|
569
|
+
|
|
494
570
|
if output:
|
|
495
571
|
with open(output, 'w') as f:
|
|
496
572
|
f.write(result)
|
|
497
|
-
console.print(f"[green]✓[/green] Documentation saved to: {output}")
|
|
498
|
-
|
|
573
|
+
console.print(f"\n[green]✓[/green] Documentation saved to: {output}")
|
|
574
|
+
elif not interactive:
|
|
499
575
|
md = Markdown(result)
|
|
500
576
|
console.print(md)
|
|
501
577
|
|
|
@@ -507,11 +583,13 @@ def gen_docs(
|
|
|
507
583
|
@main.command('review')
|
|
508
584
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
509
585
|
@click.option('-a', '--api', help='API config to use')
|
|
586
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive follow-up questions')
|
|
510
587
|
@click.pass_context
|
|
511
588
|
def review(
|
|
512
589
|
ctx: click.Context,
|
|
513
590
|
file_path: str,
|
|
514
|
-
api: Optional[str]
|
|
591
|
+
api: Optional[str],
|
|
592
|
+
interactive: bool
|
|
515
593
|
) -> None:
|
|
516
594
|
"""Review code for bugs and improvements."""
|
|
517
595
|
console = ctx.obj['console']
|
|
@@ -522,6 +600,29 @@ def review(
|
|
|
522
600
|
|
|
523
601
|
md = Markdown(result)
|
|
524
602
|
console.print(md)
|
|
603
|
+
|
|
604
|
+
if interactive:
|
|
605
|
+
client = ClaudeClient(api_config_name=api)
|
|
606
|
+
with open(file_path, 'r') as f:
|
|
607
|
+
file_content = f.read()
|
|
608
|
+
|
|
609
|
+
console.print("\n[dim]Ask follow-up questions about the review, or 'exit' to quit[/dim]")
|
|
610
|
+
|
|
611
|
+
while True:
|
|
612
|
+
user_input = console.input("\n[cyan]You:[/cyan] ").strip()
|
|
613
|
+
|
|
614
|
+
if user_input.lower() == 'exit':
|
|
615
|
+
break
|
|
616
|
+
|
|
617
|
+
if not user_input:
|
|
618
|
+
continue
|
|
619
|
+
|
|
620
|
+
follow_up_prompt = f"Code review:\n\n{result}\n\nOriginal code:\n\n{file_content}\n\nUser question: {user_input}"
|
|
621
|
+
|
|
622
|
+
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
623
|
+
for chunk in client.call_streaming(follow_up_prompt):
|
|
624
|
+
console.print(chunk, end='')
|
|
625
|
+
console.print()
|
|
525
626
|
|
|
526
627
|
except Exception as e:
|
|
527
628
|
console.print(f"[red]Error: {e}[/red]")
|
|
@@ -567,12 +668,14 @@ def debug(
|
|
|
567
668
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
568
669
|
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
569
670
|
@click.option('-a', '--api', help='API config to use')
|
|
671
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
570
672
|
@click.pass_context
|
|
571
673
|
def refactor(
|
|
572
674
|
ctx: click.Context,
|
|
573
675
|
file_path: str,
|
|
574
676
|
output: Optional[str],
|
|
575
|
-
api: Optional[str]
|
|
677
|
+
api: Optional[str],
|
|
678
|
+
interactive: bool
|
|
576
679
|
) -> None:
|
|
577
680
|
"""Suggest refactoring improvements."""
|
|
578
681
|
console = ctx.obj['console']
|
|
@@ -581,11 +684,46 @@ def refactor(
|
|
|
581
684
|
with console.status("[bold blue]Analyzing code..."):
|
|
582
685
|
result = refactor_code(file_path, api_config_name=api)
|
|
583
686
|
|
|
687
|
+
if interactive:
|
|
688
|
+
console.print("\n[bold]Initial Refactoring:[/bold]\n")
|
|
689
|
+
md = Markdown(result)
|
|
690
|
+
console.print(md)
|
|
691
|
+
|
|
692
|
+
client = ClaudeClient(api_config_name=api)
|
|
693
|
+
conversation_context = [result]
|
|
694
|
+
|
|
695
|
+
while True:
|
|
696
|
+
console.print("\n[dim]Commands: 'save' to save and exit, 'exit' to discard, or ask for changes[/dim]")
|
|
697
|
+
user_input = console.input("[cyan]You:[/cyan] ").strip()
|
|
698
|
+
|
|
699
|
+
if user_input.lower() == 'exit':
|
|
700
|
+
console.print("[yellow]Discarded changes[/yellow]")
|
|
701
|
+
return
|
|
702
|
+
|
|
703
|
+
if user_input.lower() == 'save':
|
|
704
|
+
result = conversation_context[-1]
|
|
705
|
+
break
|
|
706
|
+
|
|
707
|
+
if not user_input:
|
|
708
|
+
continue
|
|
709
|
+
|
|
710
|
+
refinement_prompt = f"Previous refactoring:\n\n{conversation_context[-1]}\n\nUser request: {user_input}\n\nProvide the updated refactoring suggestions."
|
|
711
|
+
|
|
712
|
+
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
713
|
+
response_parts = []
|
|
714
|
+
for chunk in client.call_streaming(refinement_prompt):
|
|
715
|
+
console.print(chunk, end='')
|
|
716
|
+
response_parts.append(chunk)
|
|
717
|
+
console.print()
|
|
718
|
+
|
|
719
|
+
result = ''.join(response_parts)
|
|
720
|
+
conversation_context.append(result)
|
|
721
|
+
|
|
584
722
|
if output:
|
|
585
723
|
with open(output, 'w') as f:
|
|
586
724
|
f.write(result)
|
|
587
|
-
console.print(f"[green]✓[/green] Refactored code saved to: {output}")
|
|
588
|
-
|
|
725
|
+
console.print(f"\n[green]✓[/green] Refactored code saved to: {output}")
|
|
726
|
+
elif not interactive:
|
|
589
727
|
md = Markdown(result)
|
|
590
728
|
console.print(md)
|
|
591
729
|
|
|
@@ -957,5 +1095,221 @@ def template_use(ctx: click.Context, name: str, api: Optional[str], model: Optio
|
|
|
957
1095
|
sys.exit(1)
|
|
958
1096
|
|
|
959
1097
|
|
|
1098
|
+
@main.group()
|
|
1099
|
+
def warp() -> None:
|
|
1100
|
+
"""Warp terminal integration."""
|
|
1101
|
+
pass
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
@warp.command('export-workflows')
|
|
1105
|
+
@click.option('-o', '--output', type=click.Path(), help='Output directory')
|
|
1106
|
+
@click.pass_context
|
|
1107
|
+
def warp_export_workflows(ctx: click.Context, output: Optional[str]) -> None:
|
|
1108
|
+
"""Export Warp workflows for claude-dev-cli commands."""
|
|
1109
|
+
console = ctx.obj['console']
|
|
1110
|
+
|
|
1111
|
+
try:
|
|
1112
|
+
from claude_dev_cli.warp_integration import export_builtin_workflows
|
|
1113
|
+
|
|
1114
|
+
config = Config()
|
|
1115
|
+
output_dir = Path(output) if output else config.config_dir / "warp" / "workflows"
|
|
1116
|
+
|
|
1117
|
+
created_files = export_builtin_workflows(output_dir)
|
|
1118
|
+
|
|
1119
|
+
console.print(f"[green]✓[/green] Exported {len(created_files)} Warp workflows to:")
|
|
1120
|
+
console.print(f" {output_dir}")
|
|
1121
|
+
console.print("\n[bold]Workflows:[/bold]")
|
|
1122
|
+
for file in created_files:
|
|
1123
|
+
console.print(f" • {file.name}")
|
|
1124
|
+
|
|
1125
|
+
except Exception as e:
|
|
1126
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1127
|
+
sys.exit(1)
|
|
1128
|
+
|
|
1129
|
+
|
|
1130
|
+
@warp.command('export-launch-configs')
|
|
1131
|
+
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
1132
|
+
@click.pass_context
|
|
1133
|
+
def warp_export_launch_configs(ctx: click.Context, output: Optional[str]) -> None:
|
|
1134
|
+
"""Export Warp launch configurations."""
|
|
1135
|
+
console = ctx.obj['console']
|
|
1136
|
+
|
|
1137
|
+
try:
|
|
1138
|
+
from claude_dev_cli.warp_integration import export_launch_configs
|
|
1139
|
+
|
|
1140
|
+
config = Config()
|
|
1141
|
+
output_path = Path(output) if output else config.config_dir / "warp" / "launch_configs.json"
|
|
1142
|
+
|
|
1143
|
+
export_launch_configs(output_path)
|
|
1144
|
+
|
|
1145
|
+
console.print(f"[green]✓[/green] Exported Warp launch configurations to:")
|
|
1146
|
+
console.print(f" {output_path}")
|
|
1147
|
+
|
|
1148
|
+
except Exception as e:
|
|
1149
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1150
|
+
sys.exit(1)
|
|
1151
|
+
|
|
1152
|
+
|
|
1153
|
+
@main.group()
|
|
1154
|
+
def workflow() -> None:
|
|
1155
|
+
"""Manage and run workflows."""
|
|
1156
|
+
pass
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
@workflow.command('run')
|
|
1160
|
+
@click.argument('workflow_file', type=click.Path(exists=True))
|
|
1161
|
+
@click.option('--var', '-v', multiple=True, help='Set variables (key=value)')
|
|
1162
|
+
@click.pass_context
|
|
1163
|
+
def workflow_run(
|
|
1164
|
+
ctx: click.Context,
|
|
1165
|
+
workflow_file: str,
|
|
1166
|
+
var: tuple
|
|
1167
|
+
) -> None:
|
|
1168
|
+
"""Run a workflow from YAML file."""
|
|
1169
|
+
console = ctx.obj['console']
|
|
1170
|
+
|
|
1171
|
+
# Parse variables
|
|
1172
|
+
variables = {}
|
|
1173
|
+
for v in var:
|
|
1174
|
+
if '=' in v:
|
|
1175
|
+
key, value = v.split('=', 1)
|
|
1176
|
+
variables[key] = value
|
|
1177
|
+
|
|
1178
|
+
try:
|
|
1179
|
+
from claude_dev_cli.workflows import WorkflowEngine
|
|
1180
|
+
|
|
1181
|
+
engine = WorkflowEngine(console=console)
|
|
1182
|
+
workflow_path = Path(workflow_file)
|
|
1183
|
+
|
|
1184
|
+
context = engine.execute(workflow_path, initial_vars=variables)
|
|
1185
|
+
|
|
1186
|
+
# Show summary
|
|
1187
|
+
if context.step_results:
|
|
1188
|
+
console.print("\n[bold]Results Summary:[/bold]")
|
|
1189
|
+
for step_name, result in context.step_results.items():
|
|
1190
|
+
status = "[green]✓[/green]" if result.success else "[red]✗[/red]"
|
|
1191
|
+
console.print(f"{status} {step_name}")
|
|
1192
|
+
|
|
1193
|
+
except Exception as e:
|
|
1194
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1195
|
+
sys.exit(1)
|
|
1196
|
+
|
|
1197
|
+
|
|
1198
|
+
@workflow.command('list')
|
|
1199
|
+
@click.pass_context
|
|
1200
|
+
def workflow_list(ctx: click.Context) -> None:
|
|
1201
|
+
"""List available workflows."""
|
|
1202
|
+
console = ctx.obj['console']
|
|
1203
|
+
config = Config()
|
|
1204
|
+
workflow_dir = config.config_dir / "workflows"
|
|
1205
|
+
|
|
1206
|
+
from claude_dev_cli.workflows import list_workflows
|
|
1207
|
+
workflows = list_workflows(workflow_dir)
|
|
1208
|
+
|
|
1209
|
+
if not workflows:
|
|
1210
|
+
console.print("[yellow]No workflows found.[/yellow]")
|
|
1211
|
+
console.print(f"\nCreate workflows in: {workflow_dir}")
|
|
1212
|
+
return
|
|
1213
|
+
|
|
1214
|
+
from rich.table import Table
|
|
1215
|
+
|
|
1216
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
1217
|
+
table.add_column("Name", style="cyan")
|
|
1218
|
+
table.add_column("Steps", style="yellow")
|
|
1219
|
+
table.add_column("Description")
|
|
1220
|
+
|
|
1221
|
+
for wf in workflows:
|
|
1222
|
+
table.add_row(
|
|
1223
|
+
wf['name'],
|
|
1224
|
+
str(wf['steps']),
|
|
1225
|
+
wf['description']
|
|
1226
|
+
)
|
|
1227
|
+
|
|
1228
|
+
console.print(table)
|
|
1229
|
+
console.print(f"\n[dim]Workflow directory: {workflow_dir}[/dim]")
|
|
1230
|
+
|
|
1231
|
+
|
|
1232
|
+
@workflow.command('show')
|
|
1233
|
+
@click.argument('workflow_file', type=click.Path(exists=True))
|
|
1234
|
+
@click.pass_context
|
|
1235
|
+
def workflow_show(ctx: click.Context, workflow_file: str) -> None:
|
|
1236
|
+
"""Show workflow details."""
|
|
1237
|
+
console = ctx.obj['console']
|
|
1238
|
+
|
|
1239
|
+
try:
|
|
1240
|
+
from claude_dev_cli.workflows import WorkflowEngine
|
|
1241
|
+
|
|
1242
|
+
engine = WorkflowEngine(console=console)
|
|
1243
|
+
workflow = engine.load_workflow(Path(workflow_file))
|
|
1244
|
+
|
|
1245
|
+
console.print(Panel(
|
|
1246
|
+
f"[bold]{workflow.get('name', 'Unnamed')}[/bold]\n\n"
|
|
1247
|
+
f"[dim]{workflow.get('description', 'No description')}[/dim]\n\n"
|
|
1248
|
+
f"Steps: [yellow]{len(workflow.get('steps', []))}[/yellow]",
|
|
1249
|
+
title="Workflow Info",
|
|
1250
|
+
border_style="blue"
|
|
1251
|
+
))
|
|
1252
|
+
|
|
1253
|
+
# Show steps
|
|
1254
|
+
steps = workflow.get('steps', [])
|
|
1255
|
+
if steps:
|
|
1256
|
+
console.print("\n[bold]Steps:[/bold]\n")
|
|
1257
|
+
for i, step in enumerate(steps, 1):
|
|
1258
|
+
step_name = step.get('name', f'step-{i}')
|
|
1259
|
+
step_type = 'command' if 'command' in step else 'shell' if 'shell' in step else 'set'
|
|
1260
|
+
console.print(f" {i}. [cyan]{step_name}[/cyan] ({step_type})")
|
|
1261
|
+
|
|
1262
|
+
if step.get('approval_required'):
|
|
1263
|
+
console.print(f" [yellow]⚠ Requires approval[/yellow]")
|
|
1264
|
+
if 'if' in step:
|
|
1265
|
+
console.print(f" [dim]Condition: {step['if']}[/dim]")
|
|
1266
|
+
|
|
1267
|
+
except Exception as e:
|
|
1268
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1269
|
+
sys.exit(1)
|
|
1270
|
+
|
|
1271
|
+
|
|
1272
|
+
@workflow.command('validate')
|
|
1273
|
+
@click.argument('workflow_file', type=click.Path(exists=True))
|
|
1274
|
+
@click.pass_context
|
|
1275
|
+
def workflow_validate(ctx: click.Context, workflow_file: str) -> None:
|
|
1276
|
+
"""Validate workflow syntax."""
|
|
1277
|
+
console = ctx.obj['console']
|
|
1278
|
+
|
|
1279
|
+
try:
|
|
1280
|
+
from claude_dev_cli.workflows import WorkflowEngine
|
|
1281
|
+
|
|
1282
|
+
engine = WorkflowEngine(console=console)
|
|
1283
|
+
workflow = engine.load_workflow(Path(workflow_file))
|
|
1284
|
+
|
|
1285
|
+
# Basic validation
|
|
1286
|
+
errors = []
|
|
1287
|
+
|
|
1288
|
+
if 'name' not in workflow:
|
|
1289
|
+
errors.append("Missing 'name' field")
|
|
1290
|
+
|
|
1291
|
+
if 'steps' not in workflow:
|
|
1292
|
+
errors.append("Missing 'steps' field")
|
|
1293
|
+
elif not isinstance(workflow['steps'], list):
|
|
1294
|
+
errors.append("'steps' must be a list")
|
|
1295
|
+
else:
|
|
1296
|
+
for i, step in enumerate(workflow['steps'], 1):
|
|
1297
|
+
if not any(k in step for k in ['command', 'shell', 'set']):
|
|
1298
|
+
errors.append(f"Step {i}: Must have 'command', 'shell', or 'set'")
|
|
1299
|
+
|
|
1300
|
+
if errors:
|
|
1301
|
+
console.print("[red]✗ Validation failed:[/red]\n")
|
|
1302
|
+
for error in errors:
|
|
1303
|
+
console.print(f" • {error}")
|
|
1304
|
+
sys.exit(1)
|
|
1305
|
+
else:
|
|
1306
|
+
console.print(f"[green]✓[/green] Workflow is valid: {workflow.get('name')}")
|
|
1307
|
+
console.print(f" Steps: {len(workflow.get('steps', []))}")
|
|
1308
|
+
|
|
1309
|
+
except Exception as e:
|
|
1310
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1311
|
+
sys.exit(1)
|
|
1312
|
+
|
|
1313
|
+
|
|
960
1314
|
if __name__ == '__main__':
|
|
961
1315
|
main(obj={})
|
|
@@ -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.7.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
|
|
@@ -145,18 +146,30 @@ cdc ask -a client "generate tests for this function"
|
|
|
145
146
|
# Generate tests
|
|
146
147
|
cdc generate tests mymodule.py -o tests/test_mymodule.py
|
|
147
148
|
|
|
149
|
+
# Generate tests with interactive refinement
|
|
150
|
+
cdc generate tests mymodule.py --interactive
|
|
151
|
+
|
|
148
152
|
# Code review
|
|
149
153
|
cdc review mymodule.py
|
|
150
154
|
|
|
155
|
+
# Code review with interactive follow-up questions
|
|
156
|
+
cdc review mymodule.py --interactive
|
|
157
|
+
|
|
151
158
|
# Debug errors
|
|
152
159
|
python script.py 2>&1 | cdc debug
|
|
153
160
|
|
|
154
161
|
# Generate documentation
|
|
155
162
|
cdc generate docs mymodule.py
|
|
156
163
|
|
|
164
|
+
# Generate docs with interactive refinement
|
|
165
|
+
cdc generate docs mymodule.py --interactive
|
|
166
|
+
|
|
157
167
|
# Refactor suggestions
|
|
158
168
|
cdc refactor legacy_code.py
|
|
159
169
|
|
|
170
|
+
# Refactor with interactive refinement
|
|
171
|
+
cdc refactor legacy_code.py --interactive
|
|
172
|
+
|
|
160
173
|
# Git commit message
|
|
161
174
|
git add .
|
|
162
175
|
cdc git commit
|
|
@@ -13,6 +13,8 @@ src/claude_dev_cli/template_manager.py
|
|
|
13
13
|
src/claude_dev_cli/templates.py
|
|
14
14
|
src/claude_dev_cli/toon_utils.py
|
|
15
15
|
src/claude_dev_cli/usage.py
|
|
16
|
+
src/claude_dev_cli/warp_integration.py
|
|
17
|
+
src/claude_dev_cli/workflows.py
|
|
16
18
|
src/claude_dev_cli.egg-info/PKG-INFO
|
|
17
19
|
src/claude_dev_cli.egg-info/SOURCES.txt
|
|
18
20
|
src/claude_dev_cli.egg-info/dependency_links.txt
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/plugins/diff_editor/__init__.py
RENAMED
|
File without changes
|
{claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/plugins/diff_editor/plugin.py
RENAMED
|
File without changes
|
{claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli/plugins/diff_editor/viewer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{claude_dev_cli-0.6.0 → claude_dev_cli-0.7.0}/src/claude_dev_cli.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|