htmlgraph 0.23.5__py3-none-any.whl → 0.24.1__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.
Files changed (29) hide show
  1. htmlgraph/__init__.py +5 -1
  2. htmlgraph/cigs/__init__.py +77 -0
  3. htmlgraph/cigs/autonomy.py +385 -0
  4. htmlgraph/cigs/cost.py +475 -0
  5. htmlgraph/cigs/messages_basic.py +472 -0
  6. htmlgraph/cigs/messaging.py +365 -0
  7. htmlgraph/cigs/models.py +771 -0
  8. htmlgraph/cigs/pattern_storage.py +427 -0
  9. htmlgraph/cigs/patterns.py +503 -0
  10. htmlgraph/cigs/posttool_analyzer.py +234 -0
  11. htmlgraph/cigs/tracker.py +317 -0
  12. htmlgraph/cli.py +413 -11
  13. htmlgraph/hooks/cigs_pretool_enforcer.py +350 -0
  14. htmlgraph/hooks/posttooluse.py +50 -2
  15. htmlgraph/hooks/task_enforcer.py +60 -4
  16. htmlgraph/models.py +14 -1
  17. htmlgraph/orchestration/headless_spawner.py +519 -21
  18. htmlgraph/orchestrator-system-prompt-optimized.txt +259 -53
  19. htmlgraph/reflection.py +442 -0
  20. htmlgraph/sdk.py +26 -9
  21. {htmlgraph-0.23.5.dist-info → htmlgraph-0.24.1.dist-info}/METADATA +2 -1
  22. {htmlgraph-0.23.5.dist-info → htmlgraph-0.24.1.dist-info}/RECORD +29 -17
  23. {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/dashboard.html +0 -0
  24. {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/styles.css +0 -0
  25. {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  26. {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  27. {htmlgraph-0.23.5.data → htmlgraph-0.24.1.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  28. {htmlgraph-0.23.5.dist-info → htmlgraph-0.24.1.dist-info}/WHEEL +0 -0
  29. {htmlgraph-0.23.5.dist-info → htmlgraph-0.24.1.dist-info}/entry_points.txt +0 -0
htmlgraph/cli.py CHANGED
@@ -1209,16 +1209,17 @@ def cmd_session_start_info(args: argparse.Namespace) -> None:
1209
1209
  print(json.dumps(info, indent=2, default=str))
1210
1210
  else:
1211
1211
  # Human-readable format
1212
- status = info["status"]
1212
+ status: dict = info["status"] # type: ignore
1213
1213
  print("=" * 80)
1214
1214
  print("SESSION START INFO")
1215
1215
  print("=" * 80)
1216
1216
 
1217
1217
  # Project status
1218
1218
  print(f"\nProject: {status.get('project_name', 'HtmlGraph')}")
1219
- print(f"Total nodes: {status.get('total_nodes', 0)}")
1220
- print(f"In progress: {status.get('in_progress_count', 0)}")
1221
- print(f"Completed: {status.get('done_count', 0)}")
1219
+ print(f"Total features: {status.get('total_features', 0)}")
1220
+ print(f"In progress: {status.get('wip_count', 0)}")
1221
+ by_status = status.get("by_status", {})
1222
+ print(f"Completed: {by_status.get('done', 0)}")
1222
1223
 
1223
1224
  # Active work item (validation status)
1224
1225
  active_work = info.get("active_work")
@@ -3012,6 +3013,201 @@ def cmd_orchestrator_reset_violations(args: argparse.Namespace) -> None:
3012
3013
  print("You can now continue with delegation workflow")
3013
3014
 
3014
3015
 
3016
+ def cmd_orchestrator_acknowledge_violation(args: argparse.Namespace) -> None:
3017
+ """Acknowledge circuit breaker violation and reset state."""
3018
+ from htmlgraph.orchestrator_mode import OrchestratorModeManager
3019
+
3020
+ manager = OrchestratorModeManager(args.graph_dir)
3021
+
3022
+ # Check if circuit breaker is triggered
3023
+ if not manager.is_circuit_breaker_triggered():
3024
+ print("ℹ️ No circuit breaker to acknowledge")
3025
+ print("Current violations:", manager.get_violation_count())
3026
+ return
3027
+
3028
+ # Reset violations and circuit breaker
3029
+ manager.reset_violations()
3030
+
3031
+ print("✓ Circuit breaker acknowledged and reset")
3032
+ print("Violation counter: cleared")
3033
+ print("You can now continue with delegation workflow")
3034
+
3035
+
3036
+ def cmd_cigs_status(args: argparse.Namespace) -> None:
3037
+ """Show current session CIGS status."""
3038
+ from pathlib import Path
3039
+
3040
+ from htmlgraph.cigs.autonomy import AutonomyRecommender
3041
+ from htmlgraph.cigs.pattern_storage import PatternStorage
3042
+ from htmlgraph.cigs.tracker import ViolationTracker
3043
+
3044
+ graph_dir = Path(args.graph_dir or ".htmlgraph")
3045
+
3046
+ # Get violation tracker
3047
+ tracker = ViolationTracker(graph_dir)
3048
+ summary = tracker.get_session_violations()
3049
+
3050
+ # Get pattern storage
3051
+ pattern_storage = PatternStorage(graph_dir)
3052
+ patterns = pattern_storage.get_anti_patterns()
3053
+
3054
+ # Get autonomy recommendation
3055
+ recommender = AutonomyRecommender()
3056
+ autonomy = recommender.recommend(summary, patterns)
3057
+
3058
+ # Print formatted status
3059
+ print("=== CIGS Status ===\n")
3060
+ print(f"Session: {summary.session_id}")
3061
+ print(f"Violations: {summary.total_violations}/3")
3062
+ print(f"Compliance Rate: {summary.compliance_rate:.1%}")
3063
+ print(f"Total Waste: {summary.total_waste_tokens} tokens")
3064
+ print(
3065
+ f"Circuit Breaker: {'🚨 TRIGGERED' if summary.circuit_breaker_triggered else 'Not triggered'}"
3066
+ )
3067
+
3068
+ if summary.violations_by_type:
3069
+ print("\nViolation Breakdown:")
3070
+ for vtype, count in summary.violations_by_type.items():
3071
+ print(f" • {vtype}: {count}")
3072
+
3073
+ print(f"\nAutonomy Level: {autonomy.level.upper()}")
3074
+ print(f"Messaging Intensity: {autonomy.messaging_intensity}")
3075
+ print(f"Enforcement Mode: {autonomy.enforcement_mode}")
3076
+
3077
+ if patterns:
3078
+ print(f"\nAnti-Patterns Detected: {len(patterns)}")
3079
+ for pattern in patterns[:3]:
3080
+ print(f" • {pattern.name} ({pattern.occurrence_count}x)")
3081
+
3082
+
3083
+ def cmd_cigs_summary(args: argparse.Namespace) -> None:
3084
+ """Show detailed session summary."""
3085
+ from pathlib import Path
3086
+
3087
+ from htmlgraph.cigs.tracker import ViolationTracker
3088
+
3089
+ graph_dir = Path(args.graph_dir or ".htmlgraph")
3090
+ tracker = ViolationTracker(graph_dir)
3091
+
3092
+ # Get session ID (default to current)
3093
+ session_id = args.session_id or tracker._session_id
3094
+
3095
+ if not session_id:
3096
+ print("⚠️ No active session. Specify --session-id to view past sessions.")
3097
+ return
3098
+
3099
+ summary = tracker.get_session_violations(session_id)
3100
+
3101
+ # Print detailed summary
3102
+ print("=== CIGS Session Summary ===\n")
3103
+ print(f"Session ID: {summary.session_id}")
3104
+ print("─" * 50)
3105
+ print(f"Total Violations: {summary.total_violations}")
3106
+ print(f"Compliance Rate: {summary.compliance_rate:.1%}")
3107
+ print(f"Total Waste: {summary.total_waste_tokens} tokens")
3108
+ print(
3109
+ f"Circuit Breaker: {'🚨 TRIGGERED' if summary.circuit_breaker_triggered else 'Not triggered'}"
3110
+ )
3111
+
3112
+ if summary.violations_by_type:
3113
+ print("\nViolation Breakdown:")
3114
+ for vtype, count in summary.violations_by_type.items():
3115
+ print(f" • {vtype}: {count}")
3116
+
3117
+ if summary.violations:
3118
+ print(f"\nRecent Violations ({len(summary.violations)}):")
3119
+ for v in summary.violations[-5:]:
3120
+ print(f" • {v.tool} - {v.violation_type} - {v.waste_tokens} tokens wasted")
3121
+ print(f" Should have: {v.should_have_delegated_to}")
3122
+
3123
+
3124
+ def cmd_cigs_patterns(args: argparse.Namespace) -> None:
3125
+ """List all detected patterns with occurrence counts."""
3126
+ from pathlib import Path
3127
+
3128
+ from htmlgraph.cigs.pattern_storage import PatternStorage
3129
+
3130
+ graph_dir = Path(args.graph_dir or ".htmlgraph")
3131
+ pattern_storage = PatternStorage(graph_dir)
3132
+
3133
+ # Get all patterns
3134
+ anti_patterns = pattern_storage.get_anti_patterns()
3135
+ good_patterns = pattern_storage.get_good_patterns()
3136
+
3137
+ # Print anti-patterns
3138
+ print("=== CIGS Pattern Analysis ===\n")
3139
+
3140
+ if anti_patterns:
3141
+ print("Anti-Patterns Detected:")
3142
+ print("─" * 50)
3143
+ for pattern in sorted(
3144
+ anti_patterns, key=lambda p: p.occurrence_count, reverse=True
3145
+ ):
3146
+ print(f"\n{pattern.name}")
3147
+ print(f" Occurrences: {pattern.occurrence_count}")
3148
+ print(f" Description: {pattern.description}")
3149
+ print(f" Sessions Affected: {len(pattern.sessions_affected)}")
3150
+
3151
+ if pattern.correct_approach:
3152
+ print(f" ✓ Fix: {pattern.correct_approach}")
3153
+
3154
+ if pattern.delegation_suggestion:
3155
+ print(f" 💡 Instead: {pattern.delegation_suggestion}")
3156
+ else:
3157
+ print("No anti-patterns detected yet.")
3158
+
3159
+ # Print good patterns
3160
+ if good_patterns:
3161
+ print("\n\nGood Patterns Observed:")
3162
+ print("─" * 50)
3163
+ for pattern in sorted(
3164
+ good_patterns, key=lambda p: p.occurrence_count, reverse=True
3165
+ ):
3166
+ print(f"\n{pattern.name}")
3167
+ print(f" Occurrences: {pattern.occurrence_count}")
3168
+ print(f" Description: {pattern.description}")
3169
+ print(f" Sessions Affected: {len(pattern.sessions_affected)}")
3170
+
3171
+
3172
+ def cmd_cigs_reset_violations(args: argparse.Namespace) -> None:
3173
+ """Reset violation count for current session."""
3174
+ from pathlib import Path
3175
+
3176
+ from htmlgraph.cigs.tracker import ViolationTracker
3177
+
3178
+ graph_dir = Path(args.graph_dir or ".htmlgraph")
3179
+ tracker = ViolationTracker(graph_dir)
3180
+
3181
+ # Get current session
3182
+ session_id = tracker._session_id
3183
+ if not session_id:
3184
+ print("⚠️ No active session")
3185
+ return
3186
+
3187
+ # Check current violations
3188
+ summary = tracker.get_session_violations()
3189
+
3190
+ if summary.total_violations == 0:
3191
+ print("ℹ️ No violations to reset")
3192
+ return
3193
+
3194
+ # Confirm reset
3195
+ if not args.yes:
3196
+ print(f"Current violations: {summary.total_violations}")
3197
+ print(f"Total waste: {summary.total_waste_tokens} tokens")
3198
+ response = input("\nReset violations for current session? [y/N]: ")
3199
+ if response.lower() not in ("y", "yes"):
3200
+ print("Reset cancelled")
3201
+ return
3202
+
3203
+ # Clear violations file
3204
+ tracker.clear_session_file()
3205
+
3206
+ print("✓ Violations reset for current session")
3207
+ print("Circuit breaker: cleared")
3208
+ print("Starting fresh for this session")
3209
+
3210
+
3015
3211
  def cmd_publish(args: argparse.Namespace) -> None:
3016
3212
  """Build and publish the package to PyPI (Interoperable)."""
3017
3213
  import shutil
@@ -3586,6 +3782,88 @@ def create_default_index(path: Path) -> None:
3586
3782
  )
3587
3783
 
3588
3784
 
3785
+ def install_htmlgraph_plugin(args: argparse.Namespace) -> None:
3786
+ """Install/upgrade HtmlGraph plugin using documented Claude Code commands."""
3787
+ verbose = not (args.quiet or args.format == "json")
3788
+
3789
+ if verbose:
3790
+ print("\n📦 Installing/upgrading HtmlGraph plugin...\n")
3791
+
3792
+ # Step 1: Update marketplace
3793
+ try:
3794
+ if verbose:
3795
+ print(" Updating marketplace...")
3796
+ result = subprocess.run(
3797
+ ["claude", "plugin", "marketplace", "update", "htmlgraph"],
3798
+ capture_output=True,
3799
+ text=True,
3800
+ check=False,
3801
+ )
3802
+ if result.returncode == 0:
3803
+ if verbose:
3804
+ print(" ✓ Marketplace updated")
3805
+ else:
3806
+ # Marketplace errors are non-blocking
3807
+ if (
3808
+ "not found" in result.stderr.lower()
3809
+ or "no marketplace" in result.stderr.lower()
3810
+ ):
3811
+ if verbose:
3812
+ print(" ℹ Marketplace not configured (OK, continuing)")
3813
+ elif verbose:
3814
+ print(f" ⚠ Marketplace update: {result.stderr.strip()}")
3815
+ except FileNotFoundError:
3816
+ if verbose:
3817
+ print(" ⚠ 'claude' command not found")
3818
+ except Exception as e:
3819
+ if verbose:
3820
+ print(f" ⚠ Error updating marketplace: {e}")
3821
+
3822
+ # Step 2: Try update first (documented command for latest version)
3823
+ try:
3824
+ if verbose:
3825
+ print(" Updating plugin to latest version...")
3826
+ result = subprocess.run(
3827
+ ["claude", "plugin", "update", "htmlgraph"],
3828
+ capture_output=True,
3829
+ text=True,
3830
+ check=False,
3831
+ )
3832
+ if result.returncode == 0:
3833
+ if verbose:
3834
+ print(" ✓ Plugin updated successfully")
3835
+ else:
3836
+ # If update fails (plugin not installed), fallback to install
3837
+ if (
3838
+ "not installed" in result.stderr.lower()
3839
+ or "not found" in result.stderr.lower()
3840
+ ):
3841
+ if verbose:
3842
+ print(" ℹ Plugin not yet installed, installing...")
3843
+ install_result = subprocess.run(
3844
+ ["claude", "plugin", "install", "htmlgraph"],
3845
+ capture_output=True,
3846
+ text=True,
3847
+ check=False,
3848
+ )
3849
+ if install_result.returncode == 0:
3850
+ if verbose:
3851
+ print(" ✓ Plugin installed successfully")
3852
+ elif verbose:
3853
+ print(f" ⚠ Plugin install: {install_result.stderr.strip()}")
3854
+ elif verbose:
3855
+ print(f" ⚠ Plugin update: {result.stderr.strip()}")
3856
+ except FileNotFoundError:
3857
+ if verbose:
3858
+ print(" ⚠ 'claude' command not found")
3859
+ except Exception as e:
3860
+ if verbose:
3861
+ print(f" ⚠ Error updating plugin: {e}")
3862
+
3863
+ if verbose:
3864
+ print("\n✓ Plugin installation complete\n")
3865
+
3866
+
3589
3867
  def cmd_claude(args: argparse.Namespace) -> None:
3590
3868
  """Start Claude Code with orchestrator prompt."""
3591
3869
  import textwrap
@@ -3605,8 +3883,13 @@ def cmd_claude(args: argparse.Namespace) -> None:
3605
3883
 
3606
3884
  try:
3607
3885
  if args.init:
3886
+ # Install/upgrade plugin first
3887
+ install_htmlgraph_plugin(args)
3888
+
3608
3889
  # Load optimized orchestrator system prompt
3609
- prompt_file = Path(__file__).parent / "orchestrator_system_prompt.txt"
3890
+ prompt_file = (
3891
+ Path(__file__).parent / "orchestrator-system-prompt-optimized.txt"
3892
+ )
3610
3893
 
3611
3894
  if prompt_file.exists():
3612
3895
  system_prompt = prompt_file.read_text(encoding="utf-8")
@@ -3670,6 +3953,9 @@ def cmd_claude(args: argparse.Namespace) -> None:
3670
3953
  sys.exit(1)
3671
3954
 
3672
3955
  elif args.continue_session:
3956
+ # Install/upgrade plugin first
3957
+ install_htmlgraph_plugin(args)
3958
+
3673
3959
  # Resume last Claude Code session
3674
3960
  # Find plugin directory relative to project root
3675
3961
  plugin_dir = (
@@ -3726,22 +4012,69 @@ def cmd_claude(args: argparse.Namespace) -> None:
3726
4012
  )
3727
4013
  sys.exit(1)
3728
4014
 
4015
+ # Load optimized orchestrator system prompt
4016
+ prompt_file = (
4017
+ Path(__file__).parent / "orchestrator-system-prompt-optimized.txt"
4018
+ )
4019
+
4020
+ if prompt_file.exists():
4021
+ system_prompt = prompt_file.read_text(encoding="utf-8")
4022
+ else:
4023
+ # Fallback: provide minimal orchestrator guidance
4024
+ system_prompt = textwrap.dedent(
4025
+ """
4026
+ You are an AI orchestrator for HtmlGraph project development.
4027
+
4028
+ CRITICAL DIRECTIVES:
4029
+ 1. DELEGATE to subagents - do not implement directly
4030
+ 2. CREATE work items before delegating (features, bugs, spikes)
4031
+ 3. USE SDK for tracking - all work must be tracked in .htmlgraph/
4032
+ 4. RESPECT dependencies - check blockers before starting
4033
+
4034
+ Key Rules:
4035
+ - Implementation work → delegate to general-purpose subagent
4036
+ - Research/exploration → delegate to explorer subagent
4037
+ - Testing/validation → delegate to test-runner subagent
4038
+ - Complex analysis → delegate to appropriate specialist
4039
+
4040
+ Always use:
4041
+ from htmlgraph import SDK
4042
+ sdk = SDK(agent='orchestrator')
4043
+
4044
+ See CLAUDE.md for complete orchestrator directives.
4045
+ """
4046
+ )
4047
+
4048
+ # Combine orchestrator prompt with orchestration rules
4049
+ combined_prompt = system_prompt
4050
+ if orchestration_rules:
4051
+ combined_prompt = f"{system_prompt}\n\n---\n\n{orchestration_rules}"
4052
+
3729
4053
  if args.quiet or args.format == "json":
3730
- cmd = ["claude", "--plugin-dir", str(plugin_dir)]
4054
+ cmd = [
4055
+ "claude",
4056
+ "--plugin-dir",
4057
+ str(plugin_dir),
4058
+ "--append-system-prompt",
4059
+ combined_prompt,
4060
+ ]
3731
4061
  else:
3732
4062
  print("=" * 60)
3733
4063
  print("🔧 HtmlGraph Development Mode")
3734
4064
  print("=" * 60)
3735
4065
  print(f"\nLoading plugin from: {plugin_dir}")
3736
4066
  print(" ✓ Skills, agents, and hooks will be loaded from local files")
4067
+ print(" ✓ Orchestrator system prompt will be appended")
3737
4068
  print(" ✓ Multi-AI delegation rules will be injected")
3738
4069
  print(" ✓ Changes to plugin files will take effect after restart")
3739
4070
  print()
3740
- cmd = ["claude", "--plugin-dir", str(plugin_dir)]
3741
-
3742
- # Add orchestration rules if available
3743
- if orchestration_rules:
3744
- cmd.extend(["--append-system-prompt", orchestration_rules])
4071
+ cmd = [
4072
+ "claude",
4073
+ "--plugin-dir",
4074
+ str(plugin_dir),
4075
+ "--append-system-prompt",
4076
+ combined_prompt,
4077
+ ]
3745
4078
 
3746
4079
  try:
3747
4080
  subprocess.run(cmd, check=False)
@@ -5177,6 +5510,61 @@ For more help: https://github.com/Shakes-tzd/htmlgraph
5177
5510
  "--graph-dir", "-g", default=".htmlgraph", help="Graph directory"
5178
5511
  )
5179
5512
 
5513
+ # orchestrator acknowledge-violation
5514
+ orchestrator_acknowledge_violation = orchestrator_subparsers.add_parser(
5515
+ "acknowledge-violation",
5516
+ help="Acknowledge circuit breaker violation and reset state",
5517
+ )
5518
+ orchestrator_acknowledge_violation.add_argument(
5519
+ "--graph-dir", "-g", default=".htmlgraph", help="Graph directory"
5520
+ )
5521
+
5522
+ # cigs (Computational Imperative Guidance System) with subcommands
5523
+ cigs_parser = subparsers.add_parser(
5524
+ "cigs", help="CIGS (Computational Imperative Guidance System) management"
5525
+ )
5526
+ cigs_subparsers = cigs_parser.add_subparsers(
5527
+ dest="cigs_command", help="CIGS command"
5528
+ )
5529
+
5530
+ # cigs status
5531
+ cigs_status = cigs_subparsers.add_parser(
5532
+ "status", help="Show current session CIGS status"
5533
+ )
5534
+ cigs_status.add_argument(
5535
+ "--graph-dir", "-g", default=".htmlgraph", help="Graph directory"
5536
+ )
5537
+
5538
+ # cigs summary
5539
+ cigs_summary = cigs_subparsers.add_parser(
5540
+ "summary", help="Show detailed session summary"
5541
+ )
5542
+ cigs_summary.add_argument(
5543
+ "--session-id", "-s", help="Session ID (defaults to current session)"
5544
+ )
5545
+ cigs_summary.add_argument(
5546
+ "--graph-dir", "-g", default=".htmlgraph", help="Graph directory"
5547
+ )
5548
+
5549
+ # cigs patterns
5550
+ cigs_patterns = cigs_subparsers.add_parser(
5551
+ "patterns", help="List all detected patterns"
5552
+ )
5553
+ cigs_patterns.add_argument(
5554
+ "--graph-dir", "-g", default=".htmlgraph", help="Graph directory"
5555
+ )
5556
+
5557
+ # cigs reset-violations
5558
+ cigs_reset_violations = cigs_subparsers.add_parser(
5559
+ "reset-violations", help="Reset violation count for current session"
5560
+ )
5561
+ cigs_reset_violations.add_argument(
5562
+ "--yes", "-y", action="store_true", help="Skip confirmation prompt"
5563
+ )
5564
+ cigs_reset_violations.add_argument(
5565
+ "--graph-dir", "-g", default=".htmlgraph", help="Graph directory"
5566
+ )
5567
+
5180
5568
  # install-gemini-extension
5181
5569
  subparsers.add_parser(
5182
5570
  "install-gemini-extension",
@@ -5438,9 +5826,23 @@ For more help: https://github.com/Shakes-tzd/htmlgraph
5438
5826
  cmd_orchestrator_set_level(args)
5439
5827
  elif args.orchestrator_command == "reset-violations":
5440
5828
  cmd_orchestrator_reset_violations(args)
5829
+ elif args.orchestrator_command == "acknowledge-violation":
5830
+ cmd_orchestrator_acknowledge_violation(args)
5441
5831
  else:
5442
5832
  orchestrator_parser.print_help()
5443
5833
  sys.exit(1)
5834
+ elif args.command == "cigs":
5835
+ if args.cigs_command == "status":
5836
+ cmd_cigs_status(args)
5837
+ elif args.cigs_command == "summary":
5838
+ cmd_cigs_summary(args)
5839
+ elif args.cigs_command == "patterns":
5840
+ cmd_cigs_patterns(args)
5841
+ elif args.cigs_command == "reset-violations":
5842
+ cmd_cigs_reset_violations(args)
5843
+ else:
5844
+ cigs_parser.print_help()
5845
+ sys.exit(1)
5444
5846
  elif args.command == "install-gemini-extension":
5445
5847
  cmd_install_gemini_extension(args)
5446
5848
  elif args.command == "claude":