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
claude_dev_cli/__init__.py
CHANGED
claude_dev_cli/cli.py
CHANGED
|
@@ -57,6 +57,7 @@ except Exception:
|
|
|
57
57
|
@click.option('-a', '--api', help='API config to use')
|
|
58
58
|
@click.option('-m', '--model', help='Claude model to use')
|
|
59
59
|
@click.option('--stream/--no-stream', default=True, help='Stream response')
|
|
60
|
+
@click.option('--auto-context', is_flag=True, help='Automatically include git, dependencies, and related files')
|
|
60
61
|
@click.pass_context
|
|
61
62
|
def ask(
|
|
62
63
|
ctx: click.Context,
|
|
@@ -65,7 +66,8 @@ def ask(
|
|
|
65
66
|
system: Optional[str],
|
|
66
67
|
api: Optional[str],
|
|
67
68
|
model: Optional[str],
|
|
68
|
-
stream: bool
|
|
69
|
+
stream: bool,
|
|
70
|
+
auto_context: bool
|
|
69
71
|
) -> None:
|
|
70
72
|
"""Ask Claude a question (single-shot mode)."""
|
|
71
73
|
console = ctx.obj['console']
|
|
@@ -73,7 +75,18 @@ def ask(
|
|
|
73
75
|
# Build prompt
|
|
74
76
|
prompt_parts = []
|
|
75
77
|
|
|
76
|
-
if
|
|
78
|
+
# Gather context if requested
|
|
79
|
+
if auto_context and file:
|
|
80
|
+
from claude_dev_cli.context import ContextGatherer
|
|
81
|
+
|
|
82
|
+
with console.status("[bold blue]Gathering context..."):
|
|
83
|
+
gatherer = ContextGatherer()
|
|
84
|
+
context = gatherer.gather_for_file(Path(file))
|
|
85
|
+
context_info = context.format_for_prompt()
|
|
86
|
+
|
|
87
|
+
console.print("[dim]✓ Context gathered[/dim]")
|
|
88
|
+
prompt_parts.append(context_info)
|
|
89
|
+
elif file:
|
|
77
90
|
with open(file, 'r') as f:
|
|
78
91
|
file_content = f.read()
|
|
79
92
|
prompt_parts.append(f"File: {file}\n\n{file_content}\n\n")
|
|
@@ -447,12 +460,14 @@ def generate() -> None:
|
|
|
447
460
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
448
461
|
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
449
462
|
@click.option('-a', '--api', help='API config to use')
|
|
463
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
450
464
|
@click.pass_context
|
|
451
465
|
def gen_tests(
|
|
452
466
|
ctx: click.Context,
|
|
453
467
|
file_path: str,
|
|
454
468
|
output: Optional[str],
|
|
455
|
-
api: Optional[str]
|
|
469
|
+
api: Optional[str],
|
|
470
|
+
interactive: bool
|
|
456
471
|
) -> None:
|
|
457
472
|
"""Generate pytest tests for a Python file."""
|
|
458
473
|
console = ctx.obj['console']
|
|
@@ -461,11 +476,48 @@ def gen_tests(
|
|
|
461
476
|
with console.status("[bold blue]Generating tests..."):
|
|
462
477
|
result = generate_tests(file_path, api_config_name=api)
|
|
463
478
|
|
|
479
|
+
if interactive:
|
|
480
|
+
# Show initial result
|
|
481
|
+
console.print("\n[bold]Initial Tests:[/bold]\n")
|
|
482
|
+
console.print(result)
|
|
483
|
+
|
|
484
|
+
# Interactive refinement loop
|
|
485
|
+
client = ClaudeClient(api_config_name=api)
|
|
486
|
+
conversation_context = [result]
|
|
487
|
+
|
|
488
|
+
while True:
|
|
489
|
+
console.print("\n[dim]Commands: 'save' to save and exit, 'exit' to discard, or ask for changes[/dim]")
|
|
490
|
+
user_input = console.input("[cyan]You:[/cyan] ").strip()
|
|
491
|
+
|
|
492
|
+
if user_input.lower() == 'exit':
|
|
493
|
+
console.print("[yellow]Discarded changes[/yellow]")
|
|
494
|
+
return
|
|
495
|
+
|
|
496
|
+
if user_input.lower() == 'save':
|
|
497
|
+
result = conversation_context[-1]
|
|
498
|
+
break
|
|
499
|
+
|
|
500
|
+
if not user_input:
|
|
501
|
+
continue
|
|
502
|
+
|
|
503
|
+
# Get refinement
|
|
504
|
+
refinement_prompt = f"Previous tests:\n\n{conversation_context[-1]}\n\nUser request: {user_input}\n\nProvide the updated tests."
|
|
505
|
+
|
|
506
|
+
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
507
|
+
response_parts = []
|
|
508
|
+
for chunk in client.call_streaming(refinement_prompt):
|
|
509
|
+
console.print(chunk, end='')
|
|
510
|
+
response_parts.append(chunk)
|
|
511
|
+
console.print()
|
|
512
|
+
|
|
513
|
+
result = ''.join(response_parts)
|
|
514
|
+
conversation_context.append(result)
|
|
515
|
+
|
|
464
516
|
if output:
|
|
465
517
|
with open(output, 'w') as f:
|
|
466
518
|
f.write(result)
|
|
467
|
-
console.print(f"[green]✓[/green] Tests saved to: {output}")
|
|
468
|
-
|
|
519
|
+
console.print(f"\n[green]✓[/green] Tests saved to: {output}")
|
|
520
|
+
elif not interactive:
|
|
469
521
|
console.print(result)
|
|
470
522
|
|
|
471
523
|
except Exception as e:
|
|
@@ -477,12 +529,14 @@ def gen_tests(
|
|
|
477
529
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
478
530
|
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
479
531
|
@click.option('-a', '--api', help='API config to use')
|
|
532
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
480
533
|
@click.pass_context
|
|
481
534
|
def gen_docs(
|
|
482
535
|
ctx: click.Context,
|
|
483
536
|
file_path: str,
|
|
484
537
|
output: Optional[str],
|
|
485
|
-
api: Optional[str]
|
|
538
|
+
api: Optional[str],
|
|
539
|
+
interactive: bool
|
|
486
540
|
) -> None:
|
|
487
541
|
"""Generate documentation for a Python file."""
|
|
488
542
|
console = ctx.obj['console']
|
|
@@ -491,11 +545,46 @@ def gen_docs(
|
|
|
491
545
|
with console.status("[bold blue]Generating documentation..."):
|
|
492
546
|
result = generate_docs(file_path, api_config_name=api)
|
|
493
547
|
|
|
548
|
+
if interactive:
|
|
549
|
+
console.print("\n[bold]Initial Documentation:[/bold]\n")
|
|
550
|
+
md = Markdown(result)
|
|
551
|
+
console.print(md)
|
|
552
|
+
|
|
553
|
+
client = ClaudeClient(api_config_name=api)
|
|
554
|
+
conversation_context = [result]
|
|
555
|
+
|
|
556
|
+
while True:
|
|
557
|
+
console.print("\n[dim]Commands: 'save' to save and exit, 'exit' to discard, or ask for changes[/dim]")
|
|
558
|
+
user_input = console.input("[cyan]You:[/cyan] ").strip()
|
|
559
|
+
|
|
560
|
+
if user_input.lower() == 'exit':
|
|
561
|
+
console.print("[yellow]Discarded changes[/yellow]")
|
|
562
|
+
return
|
|
563
|
+
|
|
564
|
+
if user_input.lower() == 'save':
|
|
565
|
+
result = conversation_context[-1]
|
|
566
|
+
break
|
|
567
|
+
|
|
568
|
+
if not user_input:
|
|
569
|
+
continue
|
|
570
|
+
|
|
571
|
+
refinement_prompt = f"Previous documentation:\n\n{conversation_context[-1]}\n\nUser request: {user_input}\n\nProvide the updated documentation."
|
|
572
|
+
|
|
573
|
+
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
574
|
+
response_parts = []
|
|
575
|
+
for chunk in client.call_streaming(refinement_prompt):
|
|
576
|
+
console.print(chunk, end='')
|
|
577
|
+
response_parts.append(chunk)
|
|
578
|
+
console.print()
|
|
579
|
+
|
|
580
|
+
result = ''.join(response_parts)
|
|
581
|
+
conversation_context.append(result)
|
|
582
|
+
|
|
494
583
|
if output:
|
|
495
584
|
with open(output, 'w') as f:
|
|
496
585
|
f.write(result)
|
|
497
|
-
console.print(f"[green]✓[/green] Documentation saved to: {output}")
|
|
498
|
-
|
|
586
|
+
console.print(f"\n[green]✓[/green] Documentation saved to: {output}")
|
|
587
|
+
elif not interactive:
|
|
499
588
|
md = Markdown(result)
|
|
500
589
|
console.print(md)
|
|
501
590
|
|
|
@@ -507,21 +596,69 @@ def gen_docs(
|
|
|
507
596
|
@main.command('review')
|
|
508
597
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
509
598
|
@click.option('-a', '--api', help='API config to use')
|
|
599
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive follow-up questions')
|
|
600
|
+
@click.option('--auto-context', is_flag=True, help='Automatically include git, dependencies, and related files')
|
|
510
601
|
@click.pass_context
|
|
511
602
|
def review(
|
|
512
603
|
ctx: click.Context,
|
|
513
604
|
file_path: str,
|
|
514
|
-
api: Optional[str]
|
|
605
|
+
api: Optional[str],
|
|
606
|
+
interactive: bool,
|
|
607
|
+
auto_context: bool
|
|
515
608
|
) -> None:
|
|
516
609
|
"""Review code for bugs and improvements."""
|
|
517
610
|
console = ctx.obj['console']
|
|
518
611
|
|
|
519
612
|
try:
|
|
613
|
+
# Gather context if requested
|
|
614
|
+
context_info = ""
|
|
615
|
+
if auto_context:
|
|
616
|
+
from claude_dev_cli.context import ContextGatherer
|
|
617
|
+
|
|
618
|
+
with console.status("[bold blue]Gathering context..."):
|
|
619
|
+
gatherer = ContextGatherer()
|
|
620
|
+
context = gatherer.gather_for_review(Path(file_path))
|
|
621
|
+
context_info = context.format_for_prompt()
|
|
622
|
+
|
|
623
|
+
console.print("[dim]✓ Context gathered (git, dependencies, tests)[/dim]")
|
|
624
|
+
|
|
520
625
|
with console.status("[bold blue]Reviewing code..."):
|
|
521
|
-
|
|
626
|
+
# If we have context, prepend it to the file analysis
|
|
627
|
+
if context_info:
|
|
628
|
+
# Read file separately for context-aware review
|
|
629
|
+
result = code_review(file_path, api_config_name=api)
|
|
630
|
+
# The context module already includes the file, so we use it differently
|
|
631
|
+
client = ClaudeClient(api_config_name=api)
|
|
632
|
+
enhanced_prompt = f"{context_info}\n\nPlease review this code for bugs and improvements."
|
|
633
|
+
result = client.call(enhanced_prompt)
|
|
634
|
+
else:
|
|
635
|
+
result = code_review(file_path, api_config_name=api)
|
|
522
636
|
|
|
523
637
|
md = Markdown(result)
|
|
524
638
|
console.print(md)
|
|
639
|
+
|
|
640
|
+
if interactive:
|
|
641
|
+
client = ClaudeClient(api_config_name=api)
|
|
642
|
+
with open(file_path, 'r') as f:
|
|
643
|
+
file_content = f.read()
|
|
644
|
+
|
|
645
|
+
console.print("\n[dim]Ask follow-up questions about the review, or 'exit' to quit[/dim]")
|
|
646
|
+
|
|
647
|
+
while True:
|
|
648
|
+
user_input = console.input("\n[cyan]You:[/cyan] ").strip()
|
|
649
|
+
|
|
650
|
+
if user_input.lower() == 'exit':
|
|
651
|
+
break
|
|
652
|
+
|
|
653
|
+
if not user_input:
|
|
654
|
+
continue
|
|
655
|
+
|
|
656
|
+
follow_up_prompt = f"Code review:\n\n{result}\n\nOriginal code:\n\n{file_content}\n\nUser question: {user_input}"
|
|
657
|
+
|
|
658
|
+
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
659
|
+
for chunk in client.call_streaming(follow_up_prompt):
|
|
660
|
+
console.print(chunk, end='')
|
|
661
|
+
console.print()
|
|
525
662
|
|
|
526
663
|
except Exception as e:
|
|
527
664
|
console.print(f"[red]Error: {e}[/red]")
|
|
@@ -532,12 +669,14 @@ def review(
|
|
|
532
669
|
@click.option('-f', '--file', type=click.Path(exists=True), help='File to debug')
|
|
533
670
|
@click.option('-e', '--error', help='Error message to analyze')
|
|
534
671
|
@click.option('-a', '--api', help='API config to use')
|
|
672
|
+
@click.option('--auto-context', is_flag=True, help='Automatically include git context and parse error details')
|
|
535
673
|
@click.pass_context
|
|
536
674
|
def debug(
|
|
537
675
|
ctx: click.Context,
|
|
538
676
|
file: Optional[str],
|
|
539
677
|
error: Optional[str],
|
|
540
|
-
api: Optional[str]
|
|
678
|
+
api: Optional[str],
|
|
679
|
+
auto_context: bool
|
|
541
680
|
) -> None:
|
|
542
681
|
"""Debug code and analyze errors."""
|
|
543
682
|
console = ctx.obj['console']
|
|
@@ -547,13 +686,33 @@ def debug(
|
|
|
547
686
|
if not sys.stdin.isatty():
|
|
548
687
|
stdin_content = sys.stdin.read().strip()
|
|
549
688
|
|
|
689
|
+
error_text = error or stdin_content
|
|
690
|
+
|
|
550
691
|
try:
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
692
|
+
# Gather context if requested
|
|
693
|
+
if auto_context and error_text:
|
|
694
|
+
from claude_dev_cli.context import ContextGatherer
|
|
695
|
+
|
|
696
|
+
with console.status("[bold blue]Gathering context..."):
|
|
697
|
+
gatherer = ContextGatherer()
|
|
698
|
+
file_path = Path(file) if file else None
|
|
699
|
+
context = gatherer.gather_for_error(error_text, file_path=file_path)
|
|
700
|
+
context_info = context.format_for_prompt()
|
|
701
|
+
|
|
702
|
+
console.print("[dim]✓ Context gathered (error details, git context)[/dim]")
|
|
703
|
+
|
|
704
|
+
# Use context-aware analysis
|
|
705
|
+
client = ClaudeClient(api_config_name=api)
|
|
706
|
+
enhanced_prompt = f"{context_info}\n\nPlease analyze this error and suggest fixes."
|
|
707
|
+
result = client.call(enhanced_prompt)
|
|
708
|
+
else:
|
|
709
|
+
# Original behavior
|
|
710
|
+
with console.status("[bold blue]Analyzing error..."):
|
|
711
|
+
result = debug_code(
|
|
712
|
+
file_path=file,
|
|
713
|
+
error_message=error_text,
|
|
714
|
+
api_config_name=api
|
|
715
|
+
)
|
|
557
716
|
|
|
558
717
|
md = Markdown(result)
|
|
559
718
|
console.print(md)
|
|
@@ -567,25 +726,80 @@ def debug(
|
|
|
567
726
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
568
727
|
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
569
728
|
@click.option('-a', '--api', help='API config to use')
|
|
729
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
730
|
+
@click.option('--auto-context', is_flag=True, help='Automatically include git, dependencies, and related files')
|
|
570
731
|
@click.pass_context
|
|
571
732
|
def refactor(
|
|
572
733
|
ctx: click.Context,
|
|
573
734
|
file_path: str,
|
|
574
735
|
output: Optional[str],
|
|
575
|
-
api: Optional[str]
|
|
736
|
+
api: Optional[str],
|
|
737
|
+
interactive: bool,
|
|
738
|
+
auto_context: bool
|
|
576
739
|
) -> None:
|
|
577
740
|
"""Suggest refactoring improvements."""
|
|
578
741
|
console = ctx.obj['console']
|
|
579
742
|
|
|
580
743
|
try:
|
|
581
|
-
|
|
582
|
-
|
|
744
|
+
# Gather context if requested
|
|
745
|
+
if auto_context:
|
|
746
|
+
from claude_dev_cli.context import ContextGatherer
|
|
747
|
+
|
|
748
|
+
with console.status("[bold blue]Gathering context..."):
|
|
749
|
+
gatherer = ContextGatherer()
|
|
750
|
+
context = gatherer.gather_for_file(Path(file_path))
|
|
751
|
+
context_info = context.format_for_prompt()
|
|
752
|
+
|
|
753
|
+
console.print("[dim]✓ Context gathered[/dim]")
|
|
754
|
+
|
|
755
|
+
# Use context-aware refactoring
|
|
756
|
+
client = ClaudeClient(api_config_name=api)
|
|
757
|
+
enhanced_prompt = f"{context_info}\n\nPlease suggest refactoring improvements for the main file."
|
|
758
|
+
result = client.call(enhanced_prompt)
|
|
759
|
+
else:
|
|
760
|
+
with console.status("[bold blue]Analyzing code..."):
|
|
761
|
+
result = refactor_code(file_path, api_config_name=api)
|
|
762
|
+
|
|
763
|
+
if interactive:
|
|
764
|
+
console.print("\n[bold]Initial Refactoring:[/bold]\n")
|
|
765
|
+
md = Markdown(result)
|
|
766
|
+
console.print(md)
|
|
767
|
+
|
|
768
|
+
client = ClaudeClient(api_config_name=api)
|
|
769
|
+
conversation_context = [result]
|
|
770
|
+
|
|
771
|
+
while True:
|
|
772
|
+
console.print("\n[dim]Commands: 'save' to save and exit, 'exit' to discard, or ask for changes[/dim]")
|
|
773
|
+
user_input = console.input("[cyan]You:[/cyan] ").strip()
|
|
774
|
+
|
|
775
|
+
if user_input.lower() == 'exit':
|
|
776
|
+
console.print("[yellow]Discarded changes[/yellow]")
|
|
777
|
+
return
|
|
778
|
+
|
|
779
|
+
if user_input.lower() == 'save':
|
|
780
|
+
result = conversation_context[-1]
|
|
781
|
+
break
|
|
782
|
+
|
|
783
|
+
if not user_input:
|
|
784
|
+
continue
|
|
785
|
+
|
|
786
|
+
refinement_prompt = f"Previous refactoring:\n\n{conversation_context[-1]}\n\nUser request: {user_input}\n\nProvide the updated refactoring suggestions."
|
|
787
|
+
|
|
788
|
+
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
789
|
+
response_parts = []
|
|
790
|
+
for chunk in client.call_streaming(refinement_prompt):
|
|
791
|
+
console.print(chunk, end='')
|
|
792
|
+
response_parts.append(chunk)
|
|
793
|
+
console.print()
|
|
794
|
+
|
|
795
|
+
result = ''.join(response_parts)
|
|
796
|
+
conversation_context.append(result)
|
|
583
797
|
|
|
584
798
|
if output:
|
|
585
799
|
with open(output, 'w') as f:
|
|
586
800
|
f.write(result)
|
|
587
|
-
console.print(f"[green]✓[/green] Refactored code saved to: {output}")
|
|
588
|
-
|
|
801
|
+
console.print(f"\n[green]✓[/green] Refactored code saved to: {output}")
|
|
802
|
+
elif not interactive:
|
|
589
803
|
md = Markdown(result)
|
|
590
804
|
console.print(md)
|
|
591
805
|
|
|
@@ -957,5 +1171,221 @@ def template_use(ctx: click.Context, name: str, api: Optional[str], model: Optio
|
|
|
957
1171
|
sys.exit(1)
|
|
958
1172
|
|
|
959
1173
|
|
|
1174
|
+
@main.group()
|
|
1175
|
+
def warp() -> None:
|
|
1176
|
+
"""Warp terminal integration."""
|
|
1177
|
+
pass
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
@warp.command('export-workflows')
|
|
1181
|
+
@click.option('-o', '--output', type=click.Path(), help='Output directory')
|
|
1182
|
+
@click.pass_context
|
|
1183
|
+
def warp_export_workflows(ctx: click.Context, output: Optional[str]) -> None:
|
|
1184
|
+
"""Export Warp workflows for claude-dev-cli commands."""
|
|
1185
|
+
console = ctx.obj['console']
|
|
1186
|
+
|
|
1187
|
+
try:
|
|
1188
|
+
from claude_dev_cli.warp_integration import export_builtin_workflows
|
|
1189
|
+
|
|
1190
|
+
config = Config()
|
|
1191
|
+
output_dir = Path(output) if output else config.config_dir / "warp" / "workflows"
|
|
1192
|
+
|
|
1193
|
+
created_files = export_builtin_workflows(output_dir)
|
|
1194
|
+
|
|
1195
|
+
console.print(f"[green]✓[/green] Exported {len(created_files)} Warp workflows to:")
|
|
1196
|
+
console.print(f" {output_dir}")
|
|
1197
|
+
console.print("\n[bold]Workflows:[/bold]")
|
|
1198
|
+
for file in created_files:
|
|
1199
|
+
console.print(f" • {file.name}")
|
|
1200
|
+
|
|
1201
|
+
except Exception as e:
|
|
1202
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1203
|
+
sys.exit(1)
|
|
1204
|
+
|
|
1205
|
+
|
|
1206
|
+
@warp.command('export-launch-configs')
|
|
1207
|
+
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
1208
|
+
@click.pass_context
|
|
1209
|
+
def warp_export_launch_configs(ctx: click.Context, output: Optional[str]) -> None:
|
|
1210
|
+
"""Export Warp launch configurations."""
|
|
1211
|
+
console = ctx.obj['console']
|
|
1212
|
+
|
|
1213
|
+
try:
|
|
1214
|
+
from claude_dev_cli.warp_integration import export_launch_configs
|
|
1215
|
+
|
|
1216
|
+
config = Config()
|
|
1217
|
+
output_path = Path(output) if output else config.config_dir / "warp" / "launch_configs.json"
|
|
1218
|
+
|
|
1219
|
+
export_launch_configs(output_path)
|
|
1220
|
+
|
|
1221
|
+
console.print(f"[green]✓[/green] Exported Warp launch configurations to:")
|
|
1222
|
+
console.print(f" {output_path}")
|
|
1223
|
+
|
|
1224
|
+
except Exception as e:
|
|
1225
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1226
|
+
sys.exit(1)
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
@main.group()
|
|
1230
|
+
def workflow() -> None:
|
|
1231
|
+
"""Manage and run workflows."""
|
|
1232
|
+
pass
|
|
1233
|
+
|
|
1234
|
+
|
|
1235
|
+
@workflow.command('run')
|
|
1236
|
+
@click.argument('workflow_file', type=click.Path(exists=True))
|
|
1237
|
+
@click.option('--var', '-v', multiple=True, help='Set variables (key=value)')
|
|
1238
|
+
@click.pass_context
|
|
1239
|
+
def workflow_run(
|
|
1240
|
+
ctx: click.Context,
|
|
1241
|
+
workflow_file: str,
|
|
1242
|
+
var: tuple
|
|
1243
|
+
) -> None:
|
|
1244
|
+
"""Run a workflow from YAML file."""
|
|
1245
|
+
console = ctx.obj['console']
|
|
1246
|
+
|
|
1247
|
+
# Parse variables
|
|
1248
|
+
variables = {}
|
|
1249
|
+
for v in var:
|
|
1250
|
+
if '=' in v:
|
|
1251
|
+
key, value = v.split('=', 1)
|
|
1252
|
+
variables[key] = value
|
|
1253
|
+
|
|
1254
|
+
try:
|
|
1255
|
+
from claude_dev_cli.workflows import WorkflowEngine
|
|
1256
|
+
|
|
1257
|
+
engine = WorkflowEngine(console=console)
|
|
1258
|
+
workflow_path = Path(workflow_file)
|
|
1259
|
+
|
|
1260
|
+
context = engine.execute(workflow_path, initial_vars=variables)
|
|
1261
|
+
|
|
1262
|
+
# Show summary
|
|
1263
|
+
if context.step_results:
|
|
1264
|
+
console.print("\n[bold]Results Summary:[/bold]")
|
|
1265
|
+
for step_name, result in context.step_results.items():
|
|
1266
|
+
status = "[green]✓[/green]" if result.success else "[red]✗[/red]"
|
|
1267
|
+
console.print(f"{status} {step_name}")
|
|
1268
|
+
|
|
1269
|
+
except Exception as e:
|
|
1270
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1271
|
+
sys.exit(1)
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
@workflow.command('list')
|
|
1275
|
+
@click.pass_context
|
|
1276
|
+
def workflow_list(ctx: click.Context) -> None:
|
|
1277
|
+
"""List available workflows."""
|
|
1278
|
+
console = ctx.obj['console']
|
|
1279
|
+
config = Config()
|
|
1280
|
+
workflow_dir = config.config_dir / "workflows"
|
|
1281
|
+
|
|
1282
|
+
from claude_dev_cli.workflows import list_workflows
|
|
1283
|
+
workflows = list_workflows(workflow_dir)
|
|
1284
|
+
|
|
1285
|
+
if not workflows:
|
|
1286
|
+
console.print("[yellow]No workflows found.[/yellow]")
|
|
1287
|
+
console.print(f"\nCreate workflows in: {workflow_dir}")
|
|
1288
|
+
return
|
|
1289
|
+
|
|
1290
|
+
from rich.table import Table
|
|
1291
|
+
|
|
1292
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
1293
|
+
table.add_column("Name", style="cyan")
|
|
1294
|
+
table.add_column("Steps", style="yellow")
|
|
1295
|
+
table.add_column("Description")
|
|
1296
|
+
|
|
1297
|
+
for wf in workflows:
|
|
1298
|
+
table.add_row(
|
|
1299
|
+
wf['name'],
|
|
1300
|
+
str(wf['steps']),
|
|
1301
|
+
wf['description']
|
|
1302
|
+
)
|
|
1303
|
+
|
|
1304
|
+
console.print(table)
|
|
1305
|
+
console.print(f"\n[dim]Workflow directory: {workflow_dir}[/dim]")
|
|
1306
|
+
|
|
1307
|
+
|
|
1308
|
+
@workflow.command('show')
|
|
1309
|
+
@click.argument('workflow_file', type=click.Path(exists=True))
|
|
1310
|
+
@click.pass_context
|
|
1311
|
+
def workflow_show(ctx: click.Context, workflow_file: str) -> None:
|
|
1312
|
+
"""Show workflow details."""
|
|
1313
|
+
console = ctx.obj['console']
|
|
1314
|
+
|
|
1315
|
+
try:
|
|
1316
|
+
from claude_dev_cli.workflows import WorkflowEngine
|
|
1317
|
+
|
|
1318
|
+
engine = WorkflowEngine(console=console)
|
|
1319
|
+
workflow = engine.load_workflow(Path(workflow_file))
|
|
1320
|
+
|
|
1321
|
+
console.print(Panel(
|
|
1322
|
+
f"[bold]{workflow.get('name', 'Unnamed')}[/bold]\n\n"
|
|
1323
|
+
f"[dim]{workflow.get('description', 'No description')}[/dim]\n\n"
|
|
1324
|
+
f"Steps: [yellow]{len(workflow.get('steps', []))}[/yellow]",
|
|
1325
|
+
title="Workflow Info",
|
|
1326
|
+
border_style="blue"
|
|
1327
|
+
))
|
|
1328
|
+
|
|
1329
|
+
# Show steps
|
|
1330
|
+
steps = workflow.get('steps', [])
|
|
1331
|
+
if steps:
|
|
1332
|
+
console.print("\n[bold]Steps:[/bold]\n")
|
|
1333
|
+
for i, step in enumerate(steps, 1):
|
|
1334
|
+
step_name = step.get('name', f'step-{i}')
|
|
1335
|
+
step_type = 'command' if 'command' in step else 'shell' if 'shell' in step else 'set'
|
|
1336
|
+
console.print(f" {i}. [cyan]{step_name}[/cyan] ({step_type})")
|
|
1337
|
+
|
|
1338
|
+
if step.get('approval_required'):
|
|
1339
|
+
console.print(f" [yellow]⚠ Requires approval[/yellow]")
|
|
1340
|
+
if 'if' in step:
|
|
1341
|
+
console.print(f" [dim]Condition: {step['if']}[/dim]")
|
|
1342
|
+
|
|
1343
|
+
except Exception as e:
|
|
1344
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1345
|
+
sys.exit(1)
|
|
1346
|
+
|
|
1347
|
+
|
|
1348
|
+
@workflow.command('validate')
|
|
1349
|
+
@click.argument('workflow_file', type=click.Path(exists=True))
|
|
1350
|
+
@click.pass_context
|
|
1351
|
+
def workflow_validate(ctx: click.Context, workflow_file: str) -> None:
|
|
1352
|
+
"""Validate workflow syntax."""
|
|
1353
|
+
console = ctx.obj['console']
|
|
1354
|
+
|
|
1355
|
+
try:
|
|
1356
|
+
from claude_dev_cli.workflows import WorkflowEngine
|
|
1357
|
+
|
|
1358
|
+
engine = WorkflowEngine(console=console)
|
|
1359
|
+
workflow = engine.load_workflow(Path(workflow_file))
|
|
1360
|
+
|
|
1361
|
+
# Basic validation
|
|
1362
|
+
errors = []
|
|
1363
|
+
|
|
1364
|
+
if 'name' not in workflow:
|
|
1365
|
+
errors.append("Missing 'name' field")
|
|
1366
|
+
|
|
1367
|
+
if 'steps' not in workflow:
|
|
1368
|
+
errors.append("Missing 'steps' field")
|
|
1369
|
+
elif not isinstance(workflow['steps'], list):
|
|
1370
|
+
errors.append("'steps' must be a list")
|
|
1371
|
+
else:
|
|
1372
|
+
for i, step in enumerate(workflow['steps'], 1):
|
|
1373
|
+
if not any(k in step for k in ['command', 'shell', 'set']):
|
|
1374
|
+
errors.append(f"Step {i}: Must have 'command', 'shell', or 'set'")
|
|
1375
|
+
|
|
1376
|
+
if errors:
|
|
1377
|
+
console.print("[red]✗ Validation failed:[/red]\n")
|
|
1378
|
+
for error in errors:
|
|
1379
|
+
console.print(f" • {error}")
|
|
1380
|
+
sys.exit(1)
|
|
1381
|
+
else:
|
|
1382
|
+
console.print(f"[green]✓[/green] Workflow is valid: {workflow.get('name')}")
|
|
1383
|
+
console.print(f" Steps: {len(workflow.get('steps', []))}")
|
|
1384
|
+
|
|
1385
|
+
except Exception as e:
|
|
1386
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1387
|
+
sys.exit(1)
|
|
1388
|
+
|
|
1389
|
+
|
|
960
1390
|
if __name__ == '__main__':
|
|
961
1391
|
main(obj={})
|
claude_dev_cli/config.py
CHANGED
|
@@ -25,6 +25,12 @@ class ProjectProfile(BaseModel):
|
|
|
25
25
|
api_config: str # Name of the API config to use
|
|
26
26
|
system_prompt: Optional[str] = None
|
|
27
27
|
allowed_commands: List[str] = Field(default_factory=lambda: ["all"])
|
|
28
|
+
|
|
29
|
+
# Project memory - preferences and patterns
|
|
30
|
+
auto_context: bool = False # Default value for --auto-context flag
|
|
31
|
+
coding_style: Optional[str] = None # Preferred coding style
|
|
32
|
+
test_framework: Optional[str] = None # Preferred test framework
|
|
33
|
+
preferences: Dict[str, str] = Field(default_factory=dict) # Custom preferences
|
|
28
34
|
|
|
29
35
|
|
|
30
36
|
class Config:
|