htmlgraph 0.26.5__py3-none-any.whl → 0.26.7__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.
- htmlgraph/.htmlgraph/.session-warning-state.json +1 -1
- htmlgraph/__init__.py +1 -1
- htmlgraph/api/main.py +50 -10
- htmlgraph/api/templates/dashboard-redesign.html +608 -54
- htmlgraph/api/templates/partials/activity-feed.html +21 -0
- htmlgraph/api/templates/partials/features.html +81 -12
- htmlgraph/api/templates/partials/orchestration.html +35 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +939 -0
- htmlgraph/cli/base.py +660 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +856 -0
- htmlgraph/cli/main.py +143 -0
- htmlgraph/cli/models.py +462 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +398 -0
- htmlgraph/cli/work/__init__.py +159 -0
- htmlgraph/cli/work/features.py +567 -0
- htmlgraph/cli/work/orchestration.py +675 -0
- htmlgraph/cli/work/sessions.py +465 -0
- htmlgraph/cli/work/tracks.py +485 -0
- htmlgraph/dashboard.html +6414 -634
- htmlgraph/db/schema.py +8 -3
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +20 -13
- htmlgraph/docs/README.md +2 -3
- htmlgraph/hooks/event_tracker.py +355 -26
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/orchestrator.py +137 -71
- htmlgraph/hooks/orchestrator_reflector.py +23 -0
- htmlgraph/hooks/pretooluse.py +29 -6
- htmlgraph/hooks/session_handler.py +28 -0
- htmlgraph/hooks/session_summary.py +391 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +71 -12
- htmlgraph/hooks/validator.py +192 -79
- htmlgraph/operations/__init__.py +18 -0
- htmlgraph/operations/initialization.py +596 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/orchestration/__init__.py +16 -1
- htmlgraph/orchestration/claude_launcher.py +185 -0
- htmlgraph/orchestration/command_builder.py +71 -0
- htmlgraph/orchestration/headless_spawner.py +72 -1332
- htmlgraph/orchestration/plugin_manager.py +136 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +170 -0
- htmlgraph/orchestration/spawners/codex.py +442 -0
- htmlgraph/orchestration/spawners/copilot.py +299 -0
- htmlgraph/orchestration/spawners/gemini.py +478 -0
- htmlgraph/orchestration/subprocess_runner.py +33 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +620 -55
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +45 -12
- htmlgraph/transcript.py +16 -4
- htmlgraph-0.26.7.data/data/htmlgraph/dashboard.html +6592 -0
- {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.7.dist-info}/METADATA +1 -1
- {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.7.dist-info}/RECORD +68 -34
- {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.7.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -7256
- htmlgraph-0.26.5.data/data/htmlgraph/dashboard.html +0 -812
- {htmlgraph-0.26.5.data → htmlgraph-0.26.7.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.26.5.data → htmlgraph-0.26.7.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.26.5.data → htmlgraph-0.26.7.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.26.5.data → htmlgraph-0.26.7.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.7.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""HtmlGraph initialization operations.
|
|
2
|
+
|
|
3
|
+
This module provides functions for initializing the .htmlgraph directory structure,
|
|
4
|
+
creating necessary files, and optionally installing Git hooks.
|
|
5
|
+
|
|
6
|
+
The initialization process includes:
|
|
7
|
+
1. Directory structure creation (.htmlgraph with subdirectories)
|
|
8
|
+
2. Database initialization (htmlgraph.db)
|
|
9
|
+
3. Index creation (index.sqlite)
|
|
10
|
+
4. Configuration files
|
|
11
|
+
5. Optional Git hooks installation
|
|
12
|
+
|
|
13
|
+
Extracted from monolithic cmd_init() implementation for better maintainability.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import json
|
|
19
|
+
import sqlite3
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import TYPE_CHECKING
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from htmlgraph.cli.models import InitConfig, InitResult, ValidationResult
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Default collections to create
|
|
30
|
+
DEFAULT_COLLECTIONS = [
|
|
31
|
+
"features",
|
|
32
|
+
"bugs",
|
|
33
|
+
"chores",
|
|
34
|
+
"spikes",
|
|
35
|
+
"epics",
|
|
36
|
+
"phases",
|
|
37
|
+
"tracks",
|
|
38
|
+
"sessions",
|
|
39
|
+
"insights",
|
|
40
|
+
"metrics",
|
|
41
|
+
"agents",
|
|
42
|
+
"cigs",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# Additional directories
|
|
46
|
+
ADDITIONAL_DIRECTORIES = [
|
|
47
|
+
"events",
|
|
48
|
+
"logs",
|
|
49
|
+
"archive-index",
|
|
50
|
+
"archives",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def initialize_htmlgraph(config: InitConfig) -> InitResult:
|
|
55
|
+
"""Initialize .htmlgraph directory structure.
|
|
56
|
+
|
|
57
|
+
Creates the standard HtmlGraph directory structure:
|
|
58
|
+
- .htmlgraph/
|
|
59
|
+
- features/
|
|
60
|
+
- sessions/
|
|
61
|
+
- spikes/
|
|
62
|
+
- bugs/
|
|
63
|
+
- tracks/
|
|
64
|
+
- events/
|
|
65
|
+
- logs/
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
config: Initialization configuration
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
InitResult with success status and details
|
|
72
|
+
"""
|
|
73
|
+
try:
|
|
74
|
+
# Resolve directory path
|
|
75
|
+
base_dir = Path(config.dir).resolve()
|
|
76
|
+
htmlgraph_dir = base_dir / ".htmlgraph"
|
|
77
|
+
|
|
78
|
+
created_dirs: list[str] = []
|
|
79
|
+
updated_files: list[str] = []
|
|
80
|
+
|
|
81
|
+
# Create main directory
|
|
82
|
+
if not htmlgraph_dir.exists():
|
|
83
|
+
htmlgraph_dir.mkdir(parents=True, exist_ok=True)
|
|
84
|
+
created_dirs.append(str(htmlgraph_dir))
|
|
85
|
+
|
|
86
|
+
# Create standard subdirectories
|
|
87
|
+
subdirs = [
|
|
88
|
+
"features",
|
|
89
|
+
"sessions",
|
|
90
|
+
"spikes",
|
|
91
|
+
"bugs",
|
|
92
|
+
"tracks",
|
|
93
|
+
"events",
|
|
94
|
+
"logs",
|
|
95
|
+
"logs/errors",
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
for subdir in subdirs:
|
|
99
|
+
subdir_path = htmlgraph_dir / subdir
|
|
100
|
+
if not subdir_path.exists():
|
|
101
|
+
subdir_path.mkdir(parents=True, exist_ok=True)
|
|
102
|
+
created_dirs.append(str(subdir_path))
|
|
103
|
+
|
|
104
|
+
# Create .gitkeep in events directory (unless disabled)
|
|
105
|
+
if not config.no_events_keep:
|
|
106
|
+
events_dir = htmlgraph_dir / "events"
|
|
107
|
+
gitkeep_file = events_dir / ".gitkeep"
|
|
108
|
+
if not gitkeep_file.exists():
|
|
109
|
+
gitkeep_file.touch()
|
|
110
|
+
updated_files.append(str(gitkeep_file))
|
|
111
|
+
|
|
112
|
+
# Update .gitignore (unless disabled)
|
|
113
|
+
if not config.no_update_gitignore:
|
|
114
|
+
gitignore_path = base_dir / ".gitignore"
|
|
115
|
+
gitignore_entries = [
|
|
116
|
+
"# HtmlGraph cache files",
|
|
117
|
+
".htmlgraph/index.sqlite",
|
|
118
|
+
".htmlgraph/htmlgraph.db",
|
|
119
|
+
".htmlgraph/sessions/*.jsonl",
|
|
120
|
+
".htmlgraph/events/*.jsonl",
|
|
121
|
+
".htmlgraph/parent-activity.json",
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
# Read existing .gitignore
|
|
125
|
+
existing_entries: set[str] = set()
|
|
126
|
+
if gitignore_path.exists():
|
|
127
|
+
existing_entries = set(
|
|
128
|
+
line.strip() for line in gitignore_path.read_text().splitlines()
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
# Add missing entries
|
|
132
|
+
new_entries = [
|
|
133
|
+
entry for entry in gitignore_entries if entry not in existing_entries
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
if new_entries:
|
|
137
|
+
mode = "a" if gitignore_path.exists() else "w"
|
|
138
|
+
with gitignore_path.open(mode) as f:
|
|
139
|
+
if mode == "a":
|
|
140
|
+
f.write("\n") # Add newline before appending
|
|
141
|
+
f.write("\n".join(new_entries) + "\n")
|
|
142
|
+
updated_files.append(str(gitignore_path))
|
|
143
|
+
|
|
144
|
+
# Initialize analytics index (unless disabled)
|
|
145
|
+
if not config.no_index:
|
|
146
|
+
from htmlgraph.analytics_index import AnalyticsIndex
|
|
147
|
+
|
|
148
|
+
index_path = htmlgraph_dir / "index.sqlite"
|
|
149
|
+
if not index_path.exists():
|
|
150
|
+
# Create empty index (will be populated on first use)
|
|
151
|
+
index = AnalyticsIndex(str(index_path))
|
|
152
|
+
index.ensure_schema()
|
|
153
|
+
created_dirs.append(str(index_path))
|
|
154
|
+
|
|
155
|
+
# Install Git hooks (if requested)
|
|
156
|
+
if config.install_hooks:
|
|
157
|
+
from htmlgraph.operations.hooks import install_hooks
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
hooks_result = install_hooks(project_dir=base_dir, use_copy=False)
|
|
161
|
+
if hooks_result.installed:
|
|
162
|
+
updated_files.extend(hooks_result.installed)
|
|
163
|
+
if hooks_result.warnings:
|
|
164
|
+
for warning in hooks_result.warnings:
|
|
165
|
+
print(f"Warning: {warning}", file=sys.stderr)
|
|
166
|
+
except Exception as e:
|
|
167
|
+
# Non-fatal: hooks installation failed but init succeeded
|
|
168
|
+
print(f"Warning: Failed to install hooks: {e}", file=sys.stderr)
|
|
169
|
+
|
|
170
|
+
# Interactive setup wizard (if requested)
|
|
171
|
+
if config.interactive:
|
|
172
|
+
_run_interactive_setup(htmlgraph_dir)
|
|
173
|
+
|
|
174
|
+
# Build success message
|
|
175
|
+
message_parts = []
|
|
176
|
+
if created_dirs:
|
|
177
|
+
message_parts.append(f"Created {len(created_dirs)} directories")
|
|
178
|
+
if updated_files:
|
|
179
|
+
message_parts.append(f"Updated {len(updated_files)} files")
|
|
180
|
+
|
|
181
|
+
message = ", ".join(message_parts) if message_parts else "Already initialized"
|
|
182
|
+
|
|
183
|
+
return InitResult(
|
|
184
|
+
success=True,
|
|
185
|
+
directory=htmlgraph_dir,
|
|
186
|
+
message=message,
|
|
187
|
+
created_dirs=created_dirs,
|
|
188
|
+
updated_files=updated_files,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
except Exception as e:
|
|
192
|
+
return InitResult(
|
|
193
|
+
success=False,
|
|
194
|
+
directory=Path(config.dir).resolve() / ".htmlgraph",
|
|
195
|
+
error=f"Initialization failed: {e}",
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _run_interactive_setup(htmlgraph_dir: Path) -> None:
|
|
200
|
+
"""Run interactive setup wizard.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
htmlgraph_dir: Path to .htmlgraph directory
|
|
204
|
+
"""
|
|
205
|
+
print("\n=== HtmlGraph Interactive Setup ===\n")
|
|
206
|
+
|
|
207
|
+
# Ask about project name
|
|
208
|
+
project_name = input("Project name (optional, press Enter to skip): ").strip()
|
|
209
|
+
|
|
210
|
+
# Ask about default agent
|
|
211
|
+
default_agent = input("Default agent name (default: claude): ").strip()
|
|
212
|
+
if not default_agent:
|
|
213
|
+
default_agent = "claude"
|
|
214
|
+
|
|
215
|
+
# Create config file
|
|
216
|
+
config_file = htmlgraph_dir / "config.json"
|
|
217
|
+
if not config_file.exists():
|
|
218
|
+
import json
|
|
219
|
+
|
|
220
|
+
config_data = {}
|
|
221
|
+
if project_name:
|
|
222
|
+
config_data["project_name"] = project_name
|
|
223
|
+
config_data["default_agent"] = default_agent
|
|
224
|
+
|
|
225
|
+
config_file.write_text(json.dumps(config_data, indent=2) + "\n")
|
|
226
|
+
print(f"\n✓ Created config file: {config_file}")
|
|
227
|
+
|
|
228
|
+
print("\n✓ Interactive setup complete!\n")
|
|
@@ -9,6 +9,15 @@ from .model_selection import (
|
|
|
9
9
|
get_fallback_chain,
|
|
10
10
|
select_model,
|
|
11
11
|
)
|
|
12
|
+
|
|
13
|
+
# Export modular spawners for advanced usage
|
|
14
|
+
from .spawners import (
|
|
15
|
+
BaseSpawner,
|
|
16
|
+
ClaudeSpawner,
|
|
17
|
+
CodexSpawner,
|
|
18
|
+
CopilotSpawner,
|
|
19
|
+
GeminiSpawner,
|
|
20
|
+
)
|
|
12
21
|
from .task_coordination import (
|
|
13
22
|
delegate_with_id,
|
|
14
23
|
generate_task_id,
|
|
@@ -19,9 +28,15 @@ from .task_coordination import (
|
|
|
19
28
|
)
|
|
20
29
|
|
|
21
30
|
__all__ = [
|
|
22
|
-
# Headless AI spawning
|
|
31
|
+
# Headless AI spawning (unified interface)
|
|
23
32
|
"HeadlessSpawner",
|
|
24
33
|
"AIResult",
|
|
34
|
+
# Modular spawner implementations
|
|
35
|
+
"BaseSpawner",
|
|
36
|
+
"GeminiSpawner",
|
|
37
|
+
"CodexSpawner",
|
|
38
|
+
"CopilotSpawner",
|
|
39
|
+
"ClaudeSpawner",
|
|
25
40
|
# Model selection
|
|
26
41
|
"ModelSelection",
|
|
27
42
|
"TaskType",
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Claude Code launcher with multiple integration modes.
|
|
2
|
+
|
|
3
|
+
Coordinates launching Claude Code with various HtmlGraph integration options.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from htmlgraph.orchestration.command_builder import ClaudeCommandBuilder
|
|
13
|
+
from htmlgraph.orchestration.plugin_manager import PluginManager
|
|
14
|
+
from htmlgraph.orchestration.prompts import get_orchestrator_prompt
|
|
15
|
+
from htmlgraph.orchestration.subprocess_runner import SubprocessRunner
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ClaudeLauncher:
|
|
19
|
+
"""Launch Claude Code with various HtmlGraph integration modes.
|
|
20
|
+
|
|
21
|
+
Supports four launch scenarios:
|
|
22
|
+
1. --init: Orchestrator mode with plugin installation
|
|
23
|
+
2. --continue: Resume last session with orchestrator rules
|
|
24
|
+
3. --dev: Development mode with local plugin
|
|
25
|
+
4. default: Minimal orchestrator rules
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, args: argparse.Namespace) -> None:
|
|
29
|
+
"""Initialize launcher with parsed arguments.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
args: Parsed command-line arguments
|
|
33
|
+
"""
|
|
34
|
+
self.args = args
|
|
35
|
+
self.interactive = not (args.quiet or args.format == "json")
|
|
36
|
+
|
|
37
|
+
def launch(self) -> None:
|
|
38
|
+
"""Main entry point - routes to appropriate scenario.
|
|
39
|
+
|
|
40
|
+
Raises:
|
|
41
|
+
SystemExit: On error during launch
|
|
42
|
+
"""
|
|
43
|
+
try:
|
|
44
|
+
if self.args.init:
|
|
45
|
+
self._launch_orchestrator_mode()
|
|
46
|
+
elif self.args.continue_session:
|
|
47
|
+
self._launch_resume_mode()
|
|
48
|
+
elif self.args.dev:
|
|
49
|
+
self._launch_dev_mode()
|
|
50
|
+
else:
|
|
51
|
+
self._launch_default_mode()
|
|
52
|
+
except Exception as e:
|
|
53
|
+
print(f"Error: Failed to start Claude Code: {e}", file=sys.stderr)
|
|
54
|
+
sys.exit(1)
|
|
55
|
+
|
|
56
|
+
def _launch_orchestrator_mode(self) -> None:
|
|
57
|
+
"""Launch with orchestrator prompt (--init).
|
|
58
|
+
|
|
59
|
+
Installs plugin, loads orchestrator system prompt, and starts Claude Code
|
|
60
|
+
in orchestrator mode with multi-AI delegation rules.
|
|
61
|
+
"""
|
|
62
|
+
# Install plugin
|
|
63
|
+
PluginManager.install_or_update(verbose=self.interactive)
|
|
64
|
+
|
|
65
|
+
# Load prompt
|
|
66
|
+
prompt = get_orchestrator_prompt(include_dev_mode=False)
|
|
67
|
+
|
|
68
|
+
# Show banner
|
|
69
|
+
if self.interactive:
|
|
70
|
+
self._print_orchestrator_banner()
|
|
71
|
+
|
|
72
|
+
# Build command
|
|
73
|
+
cmd = ClaudeCommandBuilder().with_system_prompt(prompt).build()
|
|
74
|
+
|
|
75
|
+
# Execute
|
|
76
|
+
SubprocessRunner.run_claude_command(cmd)
|
|
77
|
+
|
|
78
|
+
def _launch_resume_mode(self) -> None:
|
|
79
|
+
"""Resume last session with orchestrator rules (--continue).
|
|
80
|
+
|
|
81
|
+
Installs plugin, loads plugin directory, and resumes the last Claude Code
|
|
82
|
+
session with orchestrator system prompt.
|
|
83
|
+
"""
|
|
84
|
+
# Install plugin
|
|
85
|
+
PluginManager.install_or_update(verbose=self.interactive)
|
|
86
|
+
|
|
87
|
+
# Get plugin directory
|
|
88
|
+
plugin_dir = PluginManager.get_plugin_dir()
|
|
89
|
+
|
|
90
|
+
# Load prompt
|
|
91
|
+
prompt = get_orchestrator_prompt(include_dev_mode=False)
|
|
92
|
+
|
|
93
|
+
# Show status
|
|
94
|
+
if self.interactive:
|
|
95
|
+
print("Resuming last Claude Code session...")
|
|
96
|
+
print(" ✓ Multi-AI delegation rules injected")
|
|
97
|
+
|
|
98
|
+
# Build command
|
|
99
|
+
builder = ClaudeCommandBuilder().with_resume().with_system_prompt(prompt)
|
|
100
|
+
|
|
101
|
+
# Add plugin directory if exists
|
|
102
|
+
if plugin_dir.exists():
|
|
103
|
+
builder.with_plugin_dir(str(plugin_dir))
|
|
104
|
+
if self.interactive:
|
|
105
|
+
print(f" ✓ Loading plugin from: {plugin_dir}")
|
|
106
|
+
|
|
107
|
+
cmd = builder.build()
|
|
108
|
+
|
|
109
|
+
# Execute
|
|
110
|
+
SubprocessRunner.run_claude_command(cmd)
|
|
111
|
+
|
|
112
|
+
def _launch_dev_mode(self) -> None:
|
|
113
|
+
"""Launch with local plugin for development (--dev).
|
|
114
|
+
|
|
115
|
+
Loads plugin from local source directory for development/testing.
|
|
116
|
+
Changes to plugin files take effect after restart.
|
|
117
|
+
"""
|
|
118
|
+
# Get and validate plugin directory
|
|
119
|
+
plugin_dir = PluginManager.get_plugin_dir()
|
|
120
|
+
PluginManager.validate_plugin_dir(plugin_dir)
|
|
121
|
+
|
|
122
|
+
# Load prompt with dev mode
|
|
123
|
+
prompt = get_orchestrator_prompt(include_dev_mode=True)
|
|
124
|
+
|
|
125
|
+
# Show banner
|
|
126
|
+
if self.interactive:
|
|
127
|
+
self._print_dev_mode_banner(plugin_dir)
|
|
128
|
+
|
|
129
|
+
# Build command
|
|
130
|
+
cmd = (
|
|
131
|
+
ClaudeCommandBuilder()
|
|
132
|
+
.with_plugin_dir(str(plugin_dir))
|
|
133
|
+
.with_system_prompt(prompt)
|
|
134
|
+
.build()
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Execute
|
|
138
|
+
SubprocessRunner.run_claude_command(cmd)
|
|
139
|
+
|
|
140
|
+
def _launch_default_mode(self) -> None:
|
|
141
|
+
"""Launch with minimal orchestrator rules (default).
|
|
142
|
+
|
|
143
|
+
Starts Claude Code with basic multi-AI delegation rules but no plugin.
|
|
144
|
+
"""
|
|
145
|
+
# Load prompt
|
|
146
|
+
prompt = get_orchestrator_prompt(include_dev_mode=False)
|
|
147
|
+
|
|
148
|
+
# Show status
|
|
149
|
+
if self.interactive:
|
|
150
|
+
print("Starting Claude Code with multi-AI delegation rules...")
|
|
151
|
+
|
|
152
|
+
# Build command
|
|
153
|
+
cmd = ClaudeCommandBuilder().with_system_prompt(prompt).build()
|
|
154
|
+
|
|
155
|
+
# Execute
|
|
156
|
+
SubprocessRunner.run_claude_command(cmd)
|
|
157
|
+
|
|
158
|
+
def _print_orchestrator_banner(self) -> None:
|
|
159
|
+
"""Print orchestrator mode banner."""
|
|
160
|
+
print("=" * 60)
|
|
161
|
+
print("🤖 HtmlGraph Orchestrator Mode")
|
|
162
|
+
print("=" * 60)
|
|
163
|
+
print("\nStarting Claude Code with orchestrator system prompt...")
|
|
164
|
+
print("Key directives:")
|
|
165
|
+
print(" ✓ Delegate to Gemini (FREE), Codex, Copilot first")
|
|
166
|
+
print(" ✓ Use Task() only as fallback")
|
|
167
|
+
print(" ✓ Create work items before delegating")
|
|
168
|
+
print(" ✓ Track all work in .htmlgraph/")
|
|
169
|
+
print()
|
|
170
|
+
|
|
171
|
+
def _print_dev_mode_banner(self, plugin_dir: Path) -> None:
|
|
172
|
+
"""Print development mode banner.
|
|
173
|
+
|
|
174
|
+
Args:
|
|
175
|
+
plugin_dir: Path to local plugin directory
|
|
176
|
+
"""
|
|
177
|
+
print("=" * 60)
|
|
178
|
+
print("🔧 HtmlGraph Development Mode")
|
|
179
|
+
print("=" * 60)
|
|
180
|
+
print(f"\nLoading plugin from: {plugin_dir}")
|
|
181
|
+
print(" ✓ Skills, agents, and hooks will be loaded from local files")
|
|
182
|
+
print(" ✓ Orchestrator system prompt will be appended")
|
|
183
|
+
print(" ✓ Multi-AI delegation rules will be injected")
|
|
184
|
+
print(" ✓ Changes to plugin files will take effect after restart")
|
|
185
|
+
print()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Command builder for Claude Code CLI invocations.
|
|
2
|
+
|
|
3
|
+
Provides fluent interface for constructing Claude CLI commands.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ClaudeCommandBuilder:
|
|
15
|
+
"""Fluent builder for Claude Code CLI commands.
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
>>> builder = ClaudeCommandBuilder()
|
|
19
|
+
>>> cmd = builder.with_resume() \\
|
|
20
|
+
... .with_plugin_dir("/path/to/plugin") \\
|
|
21
|
+
... .with_system_prompt("System prompt text") \\
|
|
22
|
+
... .build()
|
|
23
|
+
>>> # Result: ["claude", "--resume", "--plugin-dir", "/path/to/plugin",
|
|
24
|
+
... # "--append-system-prompt", "System prompt text"]
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self) -> None:
|
|
28
|
+
"""Initialize with base command."""
|
|
29
|
+
self._cmd: list[str] = ["claude"]
|
|
30
|
+
|
|
31
|
+
def with_resume(self) -> ClaudeCommandBuilder:
|
|
32
|
+
"""Add --resume flag to resume last session.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Self for method chaining
|
|
36
|
+
"""
|
|
37
|
+
self._cmd.append("--resume")
|
|
38
|
+
return self
|
|
39
|
+
|
|
40
|
+
def with_plugin_dir(self, plugin_dir: str | Path) -> ClaudeCommandBuilder:
|
|
41
|
+
"""Add --plugin-dir flag.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
plugin_dir: Path to plugin directory
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Self for method chaining
|
|
48
|
+
"""
|
|
49
|
+
self._cmd.extend(["--plugin-dir", str(plugin_dir)])
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
def with_system_prompt(self, prompt: str) -> ClaudeCommandBuilder:
|
|
53
|
+
"""Add --append-system-prompt flag.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
prompt: System prompt text to append
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Self for method chaining
|
|
60
|
+
"""
|
|
61
|
+
if prompt: # Only add if prompt is not empty
|
|
62
|
+
self._cmd.extend(["--append-system-prompt", prompt])
|
|
63
|
+
return self
|
|
64
|
+
|
|
65
|
+
def build(self) -> list[str]:
|
|
66
|
+
"""Build the final command list.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
List of command arguments ready for subprocess.run()
|
|
70
|
+
"""
|
|
71
|
+
return self._cmd
|