claude-dev-cli 0.5.0__py3-none-any.whl → 0.7.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 +568 -10
- claude_dev_cli/template_manager.py +288 -0
- claude_dev_cli/warp_integration.py +243 -0
- claude_dev_cli/workflows.py +340 -0
- {claude_dev_cli-0.5.0.dist-info → claude_dev_cli-0.7.0.dist-info}/METADATA +69 -3
- {claude_dev_cli-0.5.0.dist-info → claude_dev_cli-0.7.0.dist-info}/RECORD +11 -8
- {claude_dev_cli-0.5.0.dist-info → claude_dev_cli-0.7.0.dist-info}/WHEEL +0 -0
- {claude_dev_cli-0.5.0.dist-info → claude_dev_cli-0.7.0.dist-info}/entry_points.txt +0 -0
- {claude_dev_cli-0.5.0.dist-info → claude_dev_cli-0.7.0.dist-info}/licenses/LICENSE +0 -0
- {claude_dev_cli-0.5.0.dist-info → claude_dev_cli-0.7.0.dist-info}/top_level.txt +0 -0
claude_dev_cli/__init__.py
CHANGED
claude_dev_cli/cli.py
CHANGED
|
@@ -25,6 +25,7 @@ from claude_dev_cli.usage import UsageTracker
|
|
|
25
25
|
from claude_dev_cli import toon_utils
|
|
26
26
|
from claude_dev_cli.plugins import load_plugins
|
|
27
27
|
from claude_dev_cli.history import ConversationHistory, Conversation
|
|
28
|
+
from claude_dev_cli.template_manager import TemplateManager, Template
|
|
28
29
|
|
|
29
30
|
console = Console()
|
|
30
31
|
|
|
@@ -446,12 +447,14 @@ def generate() -> None:
|
|
|
446
447
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
447
448
|
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
448
449
|
@click.option('-a', '--api', help='API config to use')
|
|
450
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
449
451
|
@click.pass_context
|
|
450
452
|
def gen_tests(
|
|
451
453
|
ctx: click.Context,
|
|
452
454
|
file_path: str,
|
|
453
455
|
output: Optional[str],
|
|
454
|
-
api: Optional[str]
|
|
456
|
+
api: Optional[str],
|
|
457
|
+
interactive: bool
|
|
455
458
|
) -> None:
|
|
456
459
|
"""Generate pytest tests for a Python file."""
|
|
457
460
|
console = ctx.obj['console']
|
|
@@ -460,11 +463,48 @@ def gen_tests(
|
|
|
460
463
|
with console.status("[bold blue]Generating tests..."):
|
|
461
464
|
result = generate_tests(file_path, api_config_name=api)
|
|
462
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
|
+
|
|
463
503
|
if output:
|
|
464
504
|
with open(output, 'w') as f:
|
|
465
505
|
f.write(result)
|
|
466
|
-
console.print(f"[green]✓[/green] Tests saved to: {output}")
|
|
467
|
-
|
|
506
|
+
console.print(f"\n[green]✓[/green] Tests saved to: {output}")
|
|
507
|
+
elif not interactive:
|
|
468
508
|
console.print(result)
|
|
469
509
|
|
|
470
510
|
except Exception as e:
|
|
@@ -476,12 +516,14 @@ def gen_tests(
|
|
|
476
516
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
477
517
|
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
478
518
|
@click.option('-a', '--api', help='API config to use')
|
|
519
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
479
520
|
@click.pass_context
|
|
480
521
|
def gen_docs(
|
|
481
522
|
ctx: click.Context,
|
|
482
523
|
file_path: str,
|
|
483
524
|
output: Optional[str],
|
|
484
|
-
api: Optional[str]
|
|
525
|
+
api: Optional[str],
|
|
526
|
+
interactive: bool
|
|
485
527
|
) -> None:
|
|
486
528
|
"""Generate documentation for a Python file."""
|
|
487
529
|
console = ctx.obj['console']
|
|
@@ -490,11 +532,46 @@ def gen_docs(
|
|
|
490
532
|
with console.status("[bold blue]Generating documentation..."):
|
|
491
533
|
result = generate_docs(file_path, api_config_name=api)
|
|
492
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
|
+
|
|
493
570
|
if output:
|
|
494
571
|
with open(output, 'w') as f:
|
|
495
572
|
f.write(result)
|
|
496
|
-
console.print(f"[green]✓[/green] Documentation saved to: {output}")
|
|
497
|
-
|
|
573
|
+
console.print(f"\n[green]✓[/green] Documentation saved to: {output}")
|
|
574
|
+
elif not interactive:
|
|
498
575
|
md = Markdown(result)
|
|
499
576
|
console.print(md)
|
|
500
577
|
|
|
@@ -506,11 +583,13 @@ def gen_docs(
|
|
|
506
583
|
@main.command('review')
|
|
507
584
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
508
585
|
@click.option('-a', '--api', help='API config to use')
|
|
586
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive follow-up questions')
|
|
509
587
|
@click.pass_context
|
|
510
588
|
def review(
|
|
511
589
|
ctx: click.Context,
|
|
512
590
|
file_path: str,
|
|
513
|
-
api: Optional[str]
|
|
591
|
+
api: Optional[str],
|
|
592
|
+
interactive: bool
|
|
514
593
|
) -> None:
|
|
515
594
|
"""Review code for bugs and improvements."""
|
|
516
595
|
console = ctx.obj['console']
|
|
@@ -521,6 +600,29 @@ def review(
|
|
|
521
600
|
|
|
522
601
|
md = Markdown(result)
|
|
523
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()
|
|
524
626
|
|
|
525
627
|
except Exception as e:
|
|
526
628
|
console.print(f"[red]Error: {e}[/red]")
|
|
@@ -566,12 +668,14 @@ def debug(
|
|
|
566
668
|
@click.argument('file_path', type=click.Path(exists=True))
|
|
567
669
|
@click.option('-o', '--output', type=click.Path(), help='Output file path')
|
|
568
670
|
@click.option('-a', '--api', help='API config to use')
|
|
671
|
+
@click.option('-i', '--interactive', is_flag=True, help='Interactive refinement mode')
|
|
569
672
|
@click.pass_context
|
|
570
673
|
def refactor(
|
|
571
674
|
ctx: click.Context,
|
|
572
675
|
file_path: str,
|
|
573
676
|
output: Optional[str],
|
|
574
|
-
api: Optional[str]
|
|
677
|
+
api: Optional[str],
|
|
678
|
+
interactive: bool
|
|
575
679
|
) -> None:
|
|
576
680
|
"""Suggest refactoring improvements."""
|
|
577
681
|
console = ctx.obj['console']
|
|
@@ -580,11 +684,46 @@ def refactor(
|
|
|
580
684
|
with console.status("[bold blue]Analyzing code..."):
|
|
581
685
|
result = refactor_code(file_path, api_config_name=api)
|
|
582
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
|
+
|
|
583
722
|
if output:
|
|
584
723
|
with open(output, 'w') as f:
|
|
585
724
|
f.write(result)
|
|
586
|
-
console.print(f"[green]✓[/green] Refactored code saved to: {output}")
|
|
587
|
-
|
|
725
|
+
console.print(f"\n[green]✓[/green] Refactored code saved to: {output}")
|
|
726
|
+
elif not interactive:
|
|
588
727
|
md = Markdown(result)
|
|
589
728
|
console.print(md)
|
|
590
729
|
|
|
@@ -753,5 +892,424 @@ def toon_info(ctx: click.Context) -> None:
|
|
|
753
892
|
console.print("• Same data, fewer tokens")
|
|
754
893
|
|
|
755
894
|
|
|
895
|
+
@main.group()
|
|
896
|
+
def template() -> None:
|
|
897
|
+
"""Manage custom prompt templates."""
|
|
898
|
+
pass
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
@template.command('list')
|
|
902
|
+
@click.option('-c', '--category', help='Filter by category')
|
|
903
|
+
@click.option('--builtin', is_flag=True, help='Show only built-in templates')
|
|
904
|
+
@click.option('--user', is_flag=True, help='Show only user templates')
|
|
905
|
+
@click.pass_context
|
|
906
|
+
def template_list(
|
|
907
|
+
ctx: click.Context,
|
|
908
|
+
category: Optional[str],
|
|
909
|
+
builtin: bool,
|
|
910
|
+
user: bool
|
|
911
|
+
) -> None:
|
|
912
|
+
"""List available templates."""
|
|
913
|
+
console = ctx.obj['console']
|
|
914
|
+
config = Config()
|
|
915
|
+
manager = TemplateManager(config.config_dir)
|
|
916
|
+
|
|
917
|
+
templates = manager.list_templates(
|
|
918
|
+
category=category,
|
|
919
|
+
builtin_only=builtin,
|
|
920
|
+
user_only=user
|
|
921
|
+
)
|
|
922
|
+
|
|
923
|
+
if not templates:
|
|
924
|
+
console.print("[yellow]No templates found.[/yellow]")
|
|
925
|
+
return
|
|
926
|
+
|
|
927
|
+
from rich.table import Table
|
|
928
|
+
|
|
929
|
+
table = Table(show_header=True, header_style="bold magenta")
|
|
930
|
+
table.add_column("Name", style="cyan")
|
|
931
|
+
table.add_column("Category", style="green")
|
|
932
|
+
table.add_column("Variables", style="yellow")
|
|
933
|
+
table.add_column("Type", style="blue")
|
|
934
|
+
table.add_column("Description")
|
|
935
|
+
|
|
936
|
+
for tmpl in templates:
|
|
937
|
+
vars_display = ", ".join(tmpl.variables) if tmpl.variables else "-"
|
|
938
|
+
type_display = "🔒 Built-in" if tmpl.builtin else "📝 User"
|
|
939
|
+
table.add_row(
|
|
940
|
+
tmpl.name,
|
|
941
|
+
tmpl.category,
|
|
942
|
+
vars_display,
|
|
943
|
+
type_display,
|
|
944
|
+
tmpl.description
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
console.print(table)
|
|
948
|
+
|
|
949
|
+
# Show categories
|
|
950
|
+
categories = manager.get_categories()
|
|
951
|
+
console.print(f"\n[dim]Categories: {', '.join(categories)}[/dim]")
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
@template.command('show')
|
|
955
|
+
@click.argument('name')
|
|
956
|
+
@click.pass_context
|
|
957
|
+
def template_show(ctx: click.Context, name: str) -> None:
|
|
958
|
+
"""Show template details."""
|
|
959
|
+
console = ctx.obj['console']
|
|
960
|
+
config = Config()
|
|
961
|
+
manager = TemplateManager(config.config_dir)
|
|
962
|
+
|
|
963
|
+
tmpl = manager.get_template(name)
|
|
964
|
+
if not tmpl:
|
|
965
|
+
console.print(f"[red]Template not found: {name}[/red]")
|
|
966
|
+
sys.exit(1)
|
|
967
|
+
|
|
968
|
+
console.print(Panel(
|
|
969
|
+
f"[bold]{tmpl.name}[/bold]\n\n"
|
|
970
|
+
f"[dim]{tmpl.description}[/dim]\n\n"
|
|
971
|
+
f"Category: [green]{tmpl.category}[/green]\n"
|
|
972
|
+
f"Type: {'🔒 Built-in' if tmpl.builtin else '📝 User'}\n"
|
|
973
|
+
f"Variables: [yellow]{', '.join(tmpl.variables) if tmpl.variables else 'None'}[/yellow]",
|
|
974
|
+
title="Template Info",
|
|
975
|
+
border_style="blue"
|
|
976
|
+
))
|
|
977
|
+
|
|
978
|
+
console.print("\n[bold]Content:[/bold]\n")
|
|
979
|
+
console.print(Panel(tmpl.content, border_style="dim"))
|
|
980
|
+
|
|
981
|
+
|
|
982
|
+
@template.command('add')
|
|
983
|
+
@click.argument('name')
|
|
984
|
+
@click.option('-c', '--content', help='Template content (or use stdin)')
|
|
985
|
+
@click.option('-d', '--description', help='Template description')
|
|
986
|
+
@click.option('--category', default='general', help='Template category')
|
|
987
|
+
@click.pass_context
|
|
988
|
+
def template_add(
|
|
989
|
+
ctx: click.Context,
|
|
990
|
+
name: str,
|
|
991
|
+
content: Optional[str],
|
|
992
|
+
description: Optional[str],
|
|
993
|
+
category: str
|
|
994
|
+
) -> None:
|
|
995
|
+
"""Add a new template."""
|
|
996
|
+
console = ctx.obj['console']
|
|
997
|
+
config = Config()
|
|
998
|
+
manager = TemplateManager(config.config_dir)
|
|
999
|
+
|
|
1000
|
+
# Get content from stdin if not provided
|
|
1001
|
+
if not content:
|
|
1002
|
+
if sys.stdin.isatty():
|
|
1003
|
+
console.print("[yellow]Enter template content (Ctrl+D to finish):[/yellow]")
|
|
1004
|
+
content = sys.stdin.read().strip()
|
|
1005
|
+
|
|
1006
|
+
if not content:
|
|
1007
|
+
console.print("[red]Error: No content provided[/red]")
|
|
1008
|
+
sys.exit(1)
|
|
1009
|
+
|
|
1010
|
+
try:
|
|
1011
|
+
tmpl = Template(
|
|
1012
|
+
name=name,
|
|
1013
|
+
content=content,
|
|
1014
|
+
description=description,
|
|
1015
|
+
category=category
|
|
1016
|
+
)
|
|
1017
|
+
manager.add_template(tmpl)
|
|
1018
|
+
|
|
1019
|
+
console.print(f"[green]✓[/green] Template added: {name}")
|
|
1020
|
+
if tmpl.variables:
|
|
1021
|
+
console.print(f"[dim]Variables: {', '.join(tmpl.variables)}[/dim]")
|
|
1022
|
+
|
|
1023
|
+
except ValueError as e:
|
|
1024
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1025
|
+
sys.exit(1)
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
@template.command('delete')
|
|
1029
|
+
@click.argument('name')
|
|
1030
|
+
@click.pass_context
|
|
1031
|
+
def template_delete(ctx: click.Context, name: str) -> None:
|
|
1032
|
+
"""Delete a user template."""
|
|
1033
|
+
console = ctx.obj['console']
|
|
1034
|
+
config = Config()
|
|
1035
|
+
manager = TemplateManager(config.config_dir)
|
|
1036
|
+
|
|
1037
|
+
try:
|
|
1038
|
+
if manager.delete_template(name):
|
|
1039
|
+
console.print(f"[green]✓[/green] Template deleted: {name}")
|
|
1040
|
+
else:
|
|
1041
|
+
console.print(f"[red]Template not found: {name}[/red]")
|
|
1042
|
+
sys.exit(1)
|
|
1043
|
+
|
|
1044
|
+
except ValueError as e:
|
|
1045
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
1046
|
+
sys.exit(1)
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
@template.command('use')
|
|
1050
|
+
@click.argument('name')
|
|
1051
|
+
@click.option('-a', '--api', help='API config to use')
|
|
1052
|
+
@click.option('-m', '--model', help='Claude model to use')
|
|
1053
|
+
@click.pass_context
|
|
1054
|
+
def template_use(ctx: click.Context, name: str, api: Optional[str], model: Optional[str]) -> None:
|
|
1055
|
+
"""Use a template with interactive variable input."""
|
|
1056
|
+
console = ctx.obj['console']
|
|
1057
|
+
config = Config()
|
|
1058
|
+
manager = TemplateManager(config.config_dir)
|
|
1059
|
+
|
|
1060
|
+
tmpl = manager.get_template(name)
|
|
1061
|
+
if not tmpl:
|
|
1062
|
+
console.print(f"[red]Template not found: {name}[/red]")
|
|
1063
|
+
sys.exit(1)
|
|
1064
|
+
|
|
1065
|
+
# Get variable values
|
|
1066
|
+
variables = {}
|
|
1067
|
+
if tmpl.variables:
|
|
1068
|
+
console.print(f"\n[bold]Template: {name}[/bold]")
|
|
1069
|
+
console.print(f"[dim]{tmpl.description}[/dim]\n")
|
|
1070
|
+
|
|
1071
|
+
for var in tmpl.variables:
|
|
1072
|
+
value = console.input(f"[cyan]{var}:[/cyan] ").strip()
|
|
1073
|
+
variables[var] = value
|
|
1074
|
+
|
|
1075
|
+
# Check for missing variables
|
|
1076
|
+
missing = tmpl.get_missing_variables(**variables)
|
|
1077
|
+
if missing:
|
|
1078
|
+
console.print(f"[red]Missing required variables: {', '.join(missing)}[/red]")
|
|
1079
|
+
sys.exit(1)
|
|
1080
|
+
|
|
1081
|
+
# Render template
|
|
1082
|
+
prompt = tmpl.render(**variables)
|
|
1083
|
+
|
|
1084
|
+
# Call Claude
|
|
1085
|
+
try:
|
|
1086
|
+
client = ClaudeClient(api_config_name=api)
|
|
1087
|
+
|
|
1088
|
+
console.print("\n[bold green]Claude:[/bold green] ", end='')
|
|
1089
|
+
for chunk in client.call_streaming(prompt, model=model):
|
|
1090
|
+
console.print(chunk, end='')
|
|
1091
|
+
console.print()
|
|
1092
|
+
|
|
1093
|
+
except Exception as e:
|
|
1094
|
+
console.print(f"\n[red]Error: {e}[/red]")
|
|
1095
|
+
sys.exit(1)
|
|
1096
|
+
|
|
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
|
+
|
|
756
1314
|
if __name__ == '__main__':
|
|
757
1315
|
main(obj={})
|