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.
Files changed (70) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +1 -1
  2. htmlgraph/__init__.py +1 -1
  3. htmlgraph/api/main.py +50 -10
  4. htmlgraph/api/templates/dashboard-redesign.html +608 -54
  5. htmlgraph/api/templates/partials/activity-feed.html +21 -0
  6. htmlgraph/api/templates/partials/features.html +81 -12
  7. htmlgraph/api/templates/partials/orchestration.html +35 -0
  8. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  9. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  10. htmlgraph/cli/__init__.py +42 -0
  11. htmlgraph/cli/__main__.py +6 -0
  12. htmlgraph/cli/analytics.py +939 -0
  13. htmlgraph/cli/base.py +660 -0
  14. htmlgraph/cli/constants.py +206 -0
  15. htmlgraph/cli/core.py +856 -0
  16. htmlgraph/cli/main.py +143 -0
  17. htmlgraph/cli/models.py +462 -0
  18. htmlgraph/cli/templates/__init__.py +1 -0
  19. htmlgraph/cli/templates/cost_dashboard.py +398 -0
  20. htmlgraph/cli/work/__init__.py +159 -0
  21. htmlgraph/cli/work/features.py +567 -0
  22. htmlgraph/cli/work/orchestration.py +675 -0
  23. htmlgraph/cli/work/sessions.py +465 -0
  24. htmlgraph/cli/work/tracks.py +485 -0
  25. htmlgraph/dashboard.html +6414 -634
  26. htmlgraph/db/schema.py +8 -3
  27. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +20 -13
  28. htmlgraph/docs/README.md +2 -3
  29. htmlgraph/hooks/event_tracker.py +355 -26
  30. htmlgraph/hooks/git_commands.py +175 -0
  31. htmlgraph/hooks/orchestrator.py +137 -71
  32. htmlgraph/hooks/orchestrator_reflector.py +23 -0
  33. htmlgraph/hooks/pretooluse.py +29 -6
  34. htmlgraph/hooks/session_handler.py +28 -0
  35. htmlgraph/hooks/session_summary.py +391 -0
  36. htmlgraph/hooks/subagent_detection.py +202 -0
  37. htmlgraph/hooks/subagent_stop.py +71 -12
  38. htmlgraph/hooks/validator.py +192 -79
  39. htmlgraph/operations/__init__.py +18 -0
  40. htmlgraph/operations/initialization.py +596 -0
  41. htmlgraph/operations/initialization.py.backup +228 -0
  42. htmlgraph/orchestration/__init__.py +16 -1
  43. htmlgraph/orchestration/claude_launcher.py +185 -0
  44. htmlgraph/orchestration/command_builder.py +71 -0
  45. htmlgraph/orchestration/headless_spawner.py +72 -1332
  46. htmlgraph/orchestration/plugin_manager.py +136 -0
  47. htmlgraph/orchestration/prompts.py +137 -0
  48. htmlgraph/orchestration/spawners/__init__.py +16 -0
  49. htmlgraph/orchestration/spawners/base.py +194 -0
  50. htmlgraph/orchestration/spawners/claude.py +170 -0
  51. htmlgraph/orchestration/spawners/codex.py +442 -0
  52. htmlgraph/orchestration/spawners/copilot.py +299 -0
  53. htmlgraph/orchestration/spawners/gemini.py +478 -0
  54. htmlgraph/orchestration/subprocess_runner.py +33 -0
  55. htmlgraph/orchestration.md +563 -0
  56. htmlgraph/orchestrator-system-prompt-optimized.txt +620 -55
  57. htmlgraph/orchestrator_config.py +357 -0
  58. htmlgraph/orchestrator_mode.py +45 -12
  59. htmlgraph/transcript.py +16 -4
  60. htmlgraph-0.26.7.data/data/htmlgraph/dashboard.html +6592 -0
  61. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.7.dist-info}/METADATA +1 -1
  62. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.7.dist-info}/RECORD +68 -34
  63. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.7.dist-info}/entry_points.txt +1 -1
  64. htmlgraph/cli.py +0 -7256
  65. htmlgraph-0.26.5.data/data/htmlgraph/dashboard.html +0 -812
  66. {htmlgraph-0.26.5.data → htmlgraph-0.26.7.data}/data/htmlgraph/styles.css +0 -0
  67. {htmlgraph-0.26.5.data → htmlgraph-0.26.7.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  68. {htmlgraph-0.26.5.data → htmlgraph-0.26.7.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  69. {htmlgraph-0.26.5.data → htmlgraph-0.26.7.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  70. {htmlgraph-0.26.5.dist-info → htmlgraph-0.26.7.dist-info}/WHEEL +0 -0
@@ -0,0 +1,398 @@
1
+ """HTML template for cost dashboard.
2
+
3
+ This module generates a beautiful, interactive HTML dashboard for cost analysis.
4
+ Separated from main CLI code for maintainability and testability.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from datetime import datetime
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ if TYPE_CHECKING:
13
+ from htmlgraph.cli.analytics import CostSummary
14
+
15
+
16
+ def generate_html(summary: CostSummary) -> str:
17
+ """Generate HTML dashboard from cost summary.
18
+
19
+ Args:
20
+ summary: Validated cost summary data
21
+
22
+ Returns:
23
+ Complete HTML document as string
24
+ """
25
+ # Calculate derived metrics
26
+ total_cost = summary.total_cost_tokens
27
+ avg_cost = summary.avg_cost_per_event
28
+ delegation_pct = summary.delegation_percentage
29
+ cost_usd = summary.estimated_cost_usd
30
+
31
+ # Sort tools by cost
32
+ sorted_tools = sorted(
33
+ summary.tool_costs.items(),
34
+ key=lambda x: x[1].total_tokens,
35
+ reverse=True,
36
+ )
37
+
38
+ # Sort sessions by cost
39
+ sorted_sessions = sorted(
40
+ summary.session_costs.items(),
41
+ key=lambda x: x[1].total_tokens,
42
+ reverse=True,
43
+ )
44
+
45
+ # Build tool cost rows
46
+ tool_rows = _build_tool_rows(sorted_tools, total_cost)
47
+
48
+ # Build session cost rows
49
+ session_rows = _build_session_rows(sorted_sessions, total_cost)
50
+
51
+ # Build insights
52
+ insights = _build_insights_html(delegation_pct, sorted_tools, total_cost)
53
+
54
+ # Generate complete HTML
55
+ return f"""<!DOCTYPE html>
56
+ <html lang="en">
57
+ <head>
58
+ <meta charset="UTF-8">
59
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
60
+ <title>HtmlGraph Cost Dashboard</title>
61
+ <style>
62
+ {_get_css_styles()}
63
+ </style>
64
+ </head>
65
+ <body>
66
+ <div class="container">
67
+ <header>
68
+ <h1>💰 HtmlGraph Cost Dashboard</h1>
69
+ <p class="timestamp">Generated: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</p>
70
+ <div class="metrics">
71
+ <div class="metric">
72
+ <div class="metric-label">Total Cost</div>
73
+ <div class="metric-value">{total_cost:,}<span class="metric-unit">tokens</span></div>
74
+ </div>
75
+ <div class="metric success">
76
+ <div class="metric-label">Estimated USD</div>
77
+ <div class="metric-value">${cost_usd:.2f}</div>
78
+ </div>
79
+ <div class="metric">
80
+ <div class="metric-label">Average Cost</div>
81
+ <div class="metric-value">{avg_cost:,.0f}<span class="metric-unit">tokens</span></div>
82
+ </div>
83
+ <div class="metric success">
84
+ <div class="metric-label">Delegation Rate</div>
85
+ <div class="metric-value">{delegation_pct:.1f}%</div>
86
+ </div>
87
+ </div>
88
+ </header>
89
+
90
+ <section>
91
+ <h2>📊 Cost by Tool</h2>
92
+ <table>
93
+ <thead>
94
+ <tr>
95
+ <th>Tool</th>
96
+ <th>Count</th>
97
+ <th>Total Tokens</th>
98
+ <th>% of Total</th>
99
+ </tr>
100
+ </thead>
101
+ <tbody>
102
+ {tool_rows}
103
+ </tbody>
104
+ </table>
105
+ </section>
106
+
107
+ <section>
108
+ <h2>🔄 Cost by Session</h2>
109
+ <table>
110
+ <thead>
111
+ <tr>
112
+ <th>Session ID</th>
113
+ <th>Count</th>
114
+ <th>Total Tokens</th>
115
+ <th>% of Total</th>
116
+ </tr>
117
+ </thead>
118
+ <tbody>
119
+ {session_rows}
120
+ </tbody>
121
+ </table>
122
+ </section>
123
+
124
+ <section>
125
+ <h2>💡 Insights & Recommendations</h2>
126
+ <div class="insights">
127
+ {insights}
128
+ </div>
129
+ </section>
130
+
131
+ <div class="footer">
132
+ <p>HtmlGraph Cost Attribution Dashboard | Real-time cost tracking and optimization</p>
133
+ </div>
134
+ </div>
135
+ </body>
136
+ </html>"""
137
+
138
+
139
+ def _build_tool_rows(sorted_tools: list[tuple[str, Any]], total_cost: int) -> str:
140
+ """Build HTML table rows for tool costs."""
141
+ if not sorted_tools or total_cost == 0:
142
+ return '<tr><td colspan="4" class="cell">No data available</td></tr>'
143
+
144
+ rows = []
145
+ for tool, data in sorted_tools[:20]: # Top 20 tools
146
+ pct = data.total_tokens / total_cost * 100
147
+ rows.append(
148
+ f"""
149
+ <tr>
150
+ <td class="cell">{tool}</td>
151
+ <td class="cell number">{data.count}</td>
152
+ <td class="cell number">{data.total_tokens:,}</td>
153
+ <td class="cell number">{pct:.1f}%</td>
154
+ </tr>"""
155
+ )
156
+ return "".join(rows)
157
+
158
+
159
+ def _build_session_rows(sorted_sessions: list[tuple[str, Any]], total_cost: int) -> str:
160
+ """Build HTML table rows for session costs."""
161
+ if not sorted_sessions or total_cost == 0:
162
+ return '<tr><td colspan="4" class="cell">No data available</td></tr>'
163
+
164
+ rows = []
165
+ for session, data in sorted_sessions[:20]: # Top 20 sessions
166
+ pct = data.total_tokens / total_cost * 100
167
+ session_display = session[:12] + "..." if len(session) > 12 else session
168
+ rows.append(
169
+ f"""
170
+ <tr>
171
+ <td class="cell">{session_display}</td>
172
+ <td class="cell number">{data.count}</td>
173
+ <td class="cell number">{data.total_tokens:,}</td>
174
+ <td class="cell number">{pct:.1f}%</td>
175
+ </tr>"""
176
+ )
177
+ return "".join(rows)
178
+
179
+
180
+ def _build_insights_html(
181
+ delegation_pct: float, sorted_tools: list[tuple[str, Any]], total_cost: int
182
+ ) -> str:
183
+ """Build insights and recommendations section."""
184
+ insights = []
185
+
186
+ # Delegation insight
187
+ delegation_msg = (
188
+ "Continue delegation for cost efficiency!"
189
+ if delegation_pct > 50
190
+ else "Consider delegating more operations to reduce costs."
191
+ )
192
+ insights.append(
193
+ f"""
194
+ <div class="insight">
195
+ <div class="insight-title">✓ Delegation Usage</div>
196
+ <div class="insight-text">
197
+ You're delegating {delegation_pct:.1f}% of operations.
198
+ {delegation_msg}
199
+ </div>
200
+ </div>"""
201
+ )
202
+
203
+ # Top cost driver insight
204
+ if sorted_tools and total_cost > 0:
205
+ top_tool, top_data = sorted_tools[0]
206
+ top_pct = top_data.total_tokens / total_cost * 100
207
+ insights.append(
208
+ f"""
209
+ <div class="insight">
210
+ <div class="insight-title">🎯 Top Cost Driver</div>
211
+ <div class="insight-text">
212
+ {top_tool} accounts for {top_pct:.1f}% of total cost.
213
+ Review if this tool usage is optimal.
214
+ </div>
215
+ </div>"""
216
+ )
217
+
218
+ # Parallelization insight
219
+ insights.append(
220
+ """
221
+ <div class="insight">
222
+ <div class="insight-title">📈 Parallelization Opportunity</div>
223
+ <div class="insight-text">
224
+ Parallel Task() calls can reduce overall execution time by ~40%.
225
+ Look for independent operations that can run simultaneously.
226
+ </div>
227
+ </div>"""
228
+ )
229
+
230
+ return "".join(insights)
231
+
232
+
233
+ def _get_css_styles() -> str:
234
+ """Get CSS styles for the dashboard.
235
+
236
+ Extracted to separate function for maintainability.
237
+ """
238
+ return """ * {
239
+ margin: 0;
240
+ padding: 0;
241
+ box-sizing: border-box;
242
+ }
243
+
244
+ body {
245
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
246
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
247
+ min-height: 100vh;
248
+ padding: 40px 20px;
249
+ }
250
+
251
+ .container {
252
+ max-width: 1400px;
253
+ margin: 0 auto;
254
+ }
255
+
256
+ header {
257
+ background: white;
258
+ border-radius: 12px;
259
+ padding: 30px;
260
+ margin-bottom: 30px;
261
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
262
+ }
263
+
264
+ h1 {
265
+ color: #667eea;
266
+ margin-bottom: 10px;
267
+ font-size: 28px;
268
+ }
269
+
270
+ .timestamp {
271
+ color: #999;
272
+ font-size: 12px;
273
+ }
274
+
275
+ .metrics {
276
+ display: grid;
277
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
278
+ gap: 20px;
279
+ margin-top: 20px;
280
+ }
281
+
282
+ .metric {
283
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
284
+ color: white;
285
+ padding: 20px;
286
+ border-radius: 8px;
287
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
288
+ }
289
+
290
+ .metric-label {
291
+ font-size: 12px;
292
+ opacity: 0.9;
293
+ margin-bottom: 8px;
294
+ text-transform: uppercase;
295
+ letter-spacing: 1px;
296
+ }
297
+
298
+ .metric-value {
299
+ font-size: 32px;
300
+ font-weight: bold;
301
+ }
302
+
303
+ .metric-unit {
304
+ font-size: 14px;
305
+ opacity: 0.8;
306
+ margin-left: 8px;
307
+ }
308
+
309
+ .metric.warning {
310
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
311
+ }
312
+
313
+ .metric.success {
314
+ background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
315
+ }
316
+
317
+ section {
318
+ background: white;
319
+ border-radius: 12px;
320
+ padding: 30px;
321
+ margin-bottom: 30px;
322
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
323
+ }
324
+
325
+ h2 {
326
+ color: #333;
327
+ margin-bottom: 20px;
328
+ font-size: 20px;
329
+ border-bottom: 2px solid #667eea;
330
+ padding-bottom: 10px;
331
+ }
332
+
333
+ table {
334
+ width: 100%;
335
+ border-collapse: collapse;
336
+ }
337
+
338
+ th {
339
+ background: #f5f5f5;
340
+ padding: 12px;
341
+ text-align: left;
342
+ font-weight: 600;
343
+ color: #333;
344
+ border-bottom: 2px solid #ddd;
345
+ }
346
+
347
+ td {
348
+ padding: 12px;
349
+ border-bottom: 1px solid #eee;
350
+ }
351
+
352
+ td.cell {
353
+ color: #333;
354
+ }
355
+
356
+ td.number {
357
+ text-align: right;
358
+ font-family: 'Monaco', 'Courier New', monospace;
359
+ color: #667eea;
360
+ font-weight: 500;
361
+ }
362
+
363
+ tr:hover {
364
+ background: #f9f9f9;
365
+ }
366
+
367
+ .insights {
368
+ display: grid;
369
+ grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
370
+ gap: 20px;
371
+ margin-top: 20px;
372
+ }
373
+
374
+ .insight {
375
+ background: #f0f4ff;
376
+ border-left: 4px solid #667eea;
377
+ padding: 16px;
378
+ border-radius: 4px;
379
+ }
380
+
381
+ .insight-title {
382
+ font-weight: 600;
383
+ color: #333;
384
+ margin-bottom: 8px;
385
+ }
386
+
387
+ .insight-text {
388
+ color: #666;
389
+ font-size: 14px;
390
+ line-height: 1.6;
391
+ }
392
+
393
+ .footer {
394
+ text-align: center;
395
+ color: #999;
396
+ font-size: 12px;
397
+ margin-top: 40px;
398
+ }"""
@@ -0,0 +1,159 @@
1
+ """HtmlGraph CLI - Work management commands.
2
+
3
+ Commands for managing work items:
4
+ - Features: Work item tracking
5
+ - Sessions: Session management
6
+ - Tracks: Multi-feature planning
7
+ - Archives: Archival management
8
+ - Orchestrator: Claude Code integration
9
+ - Other work-related operations
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from typing import TYPE_CHECKING
15
+
16
+ if TYPE_CHECKING:
17
+ from argparse import _SubParsersAction
18
+
19
+
20
+ def register_commands(subparsers: _SubParsersAction) -> None:
21
+ """Register work management commands with the argument parser.
22
+
23
+ Args:
24
+ subparsers: Subparser action from ArgumentParser.add_subparsers()
25
+ """
26
+ from htmlgraph.cli.work.features import register_feature_commands
27
+ from htmlgraph.cli.work.orchestration import (
28
+ register_archive_commands,
29
+ register_claude_commands,
30
+ register_orchestrator_commands,
31
+ )
32
+ from htmlgraph.cli.work.sessions import register_session_commands
33
+ from htmlgraph.cli.work.tracks import register_track_commands
34
+
35
+ # Register all command groups
36
+ register_session_commands(subparsers)
37
+ register_feature_commands(subparsers)
38
+ register_track_commands(subparsers)
39
+ register_archive_commands(subparsers)
40
+ register_orchestrator_commands(subparsers)
41
+ register_claude_commands(subparsers)
42
+
43
+
44
+ # Re-export all command classes for backward compatibility
45
+ from htmlgraph.cli.work.features import (
46
+ FeatureClaimCommand,
47
+ FeatureCompleteCommand,
48
+ FeatureCreateCommand,
49
+ FeatureListCommand,
50
+ FeaturePrimaryCommand,
51
+ FeatureReleaseCommand,
52
+ FeatureStartCommand,
53
+ )
54
+ from htmlgraph.cli.work.orchestration import (
55
+ ArchiveCreateCommand,
56
+ ArchiveListCommand,
57
+ ClaudeCommand,
58
+ OrchestratorDisableCommand,
59
+ OrchestratorEnableCommand,
60
+ OrchestratorResetViolationsCommand,
61
+ OrchestratorSetLevelCommand,
62
+ OrchestratorStatusCommand,
63
+ )
64
+ from htmlgraph.cli.work.sessions import (
65
+ SessionEndCommand,
66
+ SessionHandoffCommand,
67
+ SessionListCommand,
68
+ SessionStartCommand,
69
+ SessionStartInfoCommand,
70
+ )
71
+ from htmlgraph.cli.work.tracks import (
72
+ TrackDeleteCommand,
73
+ TrackListCommand,
74
+ TrackNewCommand,
75
+ TrackPlanCommand,
76
+ TrackSpecCommand,
77
+ )
78
+
79
+ __all__ = [
80
+ "register_commands",
81
+ # Session commands
82
+ "SessionStartCommand",
83
+ "SessionEndCommand",
84
+ "SessionListCommand",
85
+ "SessionHandoffCommand",
86
+ "SessionStartInfoCommand",
87
+ # Feature commands
88
+ "FeatureListCommand",
89
+ "FeatureCreateCommand",
90
+ "FeatureStartCommand",
91
+ "FeatureCompleteCommand",
92
+ "FeatureClaimCommand",
93
+ "FeatureReleaseCommand",
94
+ "FeaturePrimaryCommand",
95
+ # Track commands
96
+ "TrackNewCommand",
97
+ "TrackListCommand",
98
+ "TrackSpecCommand",
99
+ "TrackPlanCommand",
100
+ "TrackDeleteCommand",
101
+ # Orchestration commands
102
+ "ArchiveCreateCommand",
103
+ "ArchiveListCommand",
104
+ "OrchestratorStatusCommand",
105
+ "OrchestratorEnableCommand",
106
+ "OrchestratorDisableCommand",
107
+ "OrchestratorResetViolationsCommand",
108
+ "OrchestratorSetLevelCommand",
109
+ "ClaudeCommand",
110
+ ]
111
+
112
+
113
+ # Convenience functions for backward compatibility with tests
114
+ def cmd_orchestrator_reset_violations(args: object) -> None:
115
+ """Reset violations command."""
116
+ from argparse import Namespace
117
+
118
+ if isinstance(args, Namespace):
119
+ cmd = OrchestratorResetViolationsCommand.from_args(args)
120
+ cmd.graph_dir = (
121
+ str(args.graph_dir) if hasattr(args, "graph_dir") else ".htmlgraph"
122
+ )
123
+ result = cmd.execute()
124
+ from htmlgraph.cli.base import TextFormatter
125
+
126
+ formatter = TextFormatter()
127
+ formatter.output(result)
128
+
129
+
130
+ def cmd_orchestrator_set_level(args: object) -> None:
131
+ """Set level command."""
132
+ from argparse import Namespace
133
+
134
+ if isinstance(args, Namespace):
135
+ cmd = OrchestratorSetLevelCommand.from_args(args)
136
+ cmd.graph_dir = (
137
+ str(args.graph_dir) if hasattr(args, "graph_dir") else ".htmlgraph"
138
+ )
139
+ result = cmd.execute()
140
+ from htmlgraph.cli.base import TextFormatter
141
+
142
+ formatter = TextFormatter()
143
+ formatter.output(result)
144
+
145
+
146
+ def cmd_orchestrator_status(args: object) -> None:
147
+ """Status command."""
148
+ from argparse import Namespace
149
+
150
+ if isinstance(args, Namespace):
151
+ cmd = OrchestratorStatusCommand.from_args(args)
152
+ cmd.graph_dir = (
153
+ str(args.graph_dir) if hasattr(args, "graph_dir") else ".htmlgraph"
154
+ )
155
+ result = cmd.execute()
156
+ from htmlgraph.cli.base import TextFormatter
157
+
158
+ formatter = TextFormatter()
159
+ formatter.output(result)