htmlgraph 0.26.4__py3-none-any.whl → 0.26.6__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 (69) 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 +189 -35
  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/validator.py +192 -79
  38. htmlgraph/operations/__init__.py +18 -0
  39. htmlgraph/operations/initialization.py +596 -0
  40. htmlgraph/operations/initialization.py.backup +228 -0
  41. htmlgraph/orchestration/__init__.py +16 -1
  42. htmlgraph/orchestration/claude_launcher.py +185 -0
  43. htmlgraph/orchestration/command_builder.py +71 -0
  44. htmlgraph/orchestration/headless_spawner.py +72 -1332
  45. htmlgraph/orchestration/plugin_manager.py +136 -0
  46. htmlgraph/orchestration/prompts.py +137 -0
  47. htmlgraph/orchestration/spawners/__init__.py +16 -0
  48. htmlgraph/orchestration/spawners/base.py +194 -0
  49. htmlgraph/orchestration/spawners/claude.py +170 -0
  50. htmlgraph/orchestration/spawners/codex.py +442 -0
  51. htmlgraph/orchestration/spawners/copilot.py +299 -0
  52. htmlgraph/orchestration/spawners/gemini.py +478 -0
  53. htmlgraph/orchestration/subprocess_runner.py +33 -0
  54. htmlgraph/orchestration.md +563 -0
  55. htmlgraph/orchestrator-system-prompt-optimized.txt +620 -55
  56. htmlgraph/orchestrator_config.py +357 -0
  57. htmlgraph/orchestrator_mode.py +45 -12
  58. htmlgraph/transcript.py +16 -4
  59. htmlgraph-0.26.6.data/data/htmlgraph/dashboard.html +6592 -0
  60. {htmlgraph-0.26.4.dist-info → htmlgraph-0.26.6.dist-info}/METADATA +1 -1
  61. {htmlgraph-0.26.4.dist-info → htmlgraph-0.26.6.dist-info}/RECORD +67 -33
  62. {htmlgraph-0.26.4.dist-info → htmlgraph-0.26.6.dist-info}/entry_points.txt +1 -1
  63. htmlgraph/cli.py +0 -7256
  64. htmlgraph-0.26.4.data/data/htmlgraph/dashboard.html +0 -812
  65. {htmlgraph-0.26.4.data → htmlgraph-0.26.6.data}/data/htmlgraph/styles.css +0 -0
  66. {htmlgraph-0.26.4.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  67. {htmlgraph-0.26.4.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  68. {htmlgraph-0.26.4.data → htmlgraph-0.26.6.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  69. {htmlgraph-0.26.4.dist-info → htmlgraph-0.26.6.dist-info}/WHEEL +0 -0
@@ -0,0 +1,465 @@
1
+ """HtmlGraph CLI - Session management commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from typing import TYPE_CHECKING
7
+
8
+ from rich import box
9
+ from rich.console import Console
10
+ from rich.panel import Panel
11
+ from rich.table import Table
12
+
13
+ from htmlgraph.cli.base import BaseCommand, CommandError, CommandResult
14
+ from htmlgraph.cli.constants import DEFAULT_GRAPH_DIR
15
+
16
+ if TYPE_CHECKING:
17
+ from argparse import _SubParsersAction
18
+
19
+ console = Console()
20
+
21
+
22
+ def register_session_commands(subparsers: _SubParsersAction) -> None:
23
+ """Register session management commands."""
24
+ session_parser = subparsers.add_parser("session", help="Session management")
25
+ session_subparsers = session_parser.add_subparsers(
26
+ dest="session_command", help="Session command"
27
+ )
28
+
29
+ # session start
30
+ session_start = session_subparsers.add_parser("start", help="Start a new session")
31
+ session_start.add_argument(
32
+ "--id", help="Session ID (auto-generated if not provided)"
33
+ )
34
+ session_start.add_argument("--agent", default="claude-code", help="Agent name")
35
+ session_start.add_argument("--title", help="Session title")
36
+ session_start.add_argument(
37
+ "--graph-dir", "-g", default=DEFAULT_GRAPH_DIR, help="Graph directory"
38
+ )
39
+ session_start.add_argument(
40
+ "--format", choices=["json", "text"], default="text", help="Output format"
41
+ )
42
+ session_start.set_defaults(func=SessionStartCommand.from_args)
43
+
44
+ # session end
45
+ session_end = session_subparsers.add_parser("end", help="End a session")
46
+ session_end.add_argument("id", help="Session ID to end")
47
+ session_end.add_argument("--notes", help="Handoff notes for the next session")
48
+ session_end.add_argument("--recommend", help="Recommended next steps")
49
+ session_end.add_argument(
50
+ "--blocker", action="append", default=[], help="Blocker to record"
51
+ )
52
+ session_end.add_argument(
53
+ "--graph-dir", "-g", default=DEFAULT_GRAPH_DIR, help="Graph directory"
54
+ )
55
+ session_end.add_argument(
56
+ "--format", choices=["json", "text"], default="text", help="Output format"
57
+ )
58
+ session_end.set_defaults(func=SessionEndCommand.from_args)
59
+
60
+ # session list
61
+ session_list = session_subparsers.add_parser("list", help="List all sessions")
62
+ session_list.add_argument(
63
+ "--graph-dir", "-g", default=DEFAULT_GRAPH_DIR, help="Graph directory"
64
+ )
65
+ session_list.add_argument(
66
+ "--format", choices=["json", "text"], default="text", help="Output format"
67
+ )
68
+ session_list.set_defaults(func=SessionListCommand.from_args)
69
+
70
+ # session handoff
71
+ session_handoff = session_subparsers.add_parser(
72
+ "handoff", help="Get or set handoff context"
73
+ )
74
+ session_handoff.add_argument(
75
+ "--session-id", help="Session ID (defaults to last ended)"
76
+ )
77
+ session_handoff.add_argument("--notes", help="Handoff notes")
78
+ session_handoff.add_argument("--recommend", help="Recommended next steps")
79
+ session_handoff.add_argument(
80
+ "--blocker", action="append", default=[], help="Blocker to record"
81
+ )
82
+ session_handoff.add_argument(
83
+ "--show", action="store_true", help="Show handoff context"
84
+ )
85
+ session_handoff.add_argument("--agent", default="claude-code", help="Agent name")
86
+ session_handoff.add_argument(
87
+ "--graph-dir", "-g", default=DEFAULT_GRAPH_DIR, help="Graph directory"
88
+ )
89
+ session_handoff.add_argument(
90
+ "--format", choices=["json", "text"], default="text", help="Output format"
91
+ )
92
+ session_handoff.set_defaults(func=SessionHandoffCommand.from_args)
93
+
94
+ # session start-info
95
+ session_start_info = session_subparsers.add_parser(
96
+ "start-info", help="Get session start information"
97
+ )
98
+ session_start_info.add_argument("--agent", default="claude-code", help="Agent name")
99
+ session_start_info.add_argument(
100
+ "--no-git", action="store_true", help="Exclude git log"
101
+ )
102
+ session_start_info.add_argument(
103
+ "--git-count", type=int, default=5, help="Number of git commits"
104
+ )
105
+ session_start_info.add_argument(
106
+ "--top-n", type=int, default=3, help="Top N analytics items"
107
+ )
108
+ session_start_info.add_argument(
109
+ "--max-agents", type=int, default=3, help="Max agents in analytics"
110
+ )
111
+ session_start_info.add_argument(
112
+ "--graph-dir", "-g", default=DEFAULT_GRAPH_DIR, help="Graph directory"
113
+ )
114
+ session_start_info.add_argument(
115
+ "--format", choices=["json", "text"], default="text", help="Output format"
116
+ )
117
+ session_start_info.set_defaults(func=SessionStartInfoCommand.from_args)
118
+
119
+
120
+ # ============================================================================
121
+ # Session Commands
122
+ # ============================================================================
123
+
124
+
125
+ class SessionStartCommand(BaseCommand):
126
+ """Start a new session."""
127
+
128
+ def __init__(
129
+ self, *, session_id: str | None, agent: str, title: str | None
130
+ ) -> None:
131
+ super().__init__()
132
+ self.session_id = session_id
133
+ self.agent_name = agent
134
+ self.title = title
135
+
136
+ @classmethod
137
+ def from_args(cls, args: argparse.Namespace) -> SessionStartCommand:
138
+ return cls(
139
+ session_id=getattr(args, "id", None),
140
+ agent=args.agent,
141
+ title=getattr(args, "title", None),
142
+ )
143
+
144
+ def execute(self) -> CommandResult:
145
+ """Start a new session."""
146
+ from htmlgraph.cli.base import TextOutputBuilder
147
+ from htmlgraph.converter import session_to_dict
148
+
149
+ sdk = self.get_sdk()
150
+
151
+ with console.status("[blue]Starting session...", spinner="dots"):
152
+ session = sdk.start_session(
153
+ session_id=self.session_id,
154
+ title=self.title,
155
+ agent=self.agent_name,
156
+ )
157
+
158
+ output = TextOutputBuilder()
159
+ output.add_success(f"Session started: {session.id}")
160
+ output.add_field("Agent", session.agent)
161
+ output.add_field("Started", session.started_at.isoformat())
162
+ if session.title:
163
+ output.add_field("Title", session.title)
164
+
165
+ return CommandResult(
166
+ data=session_to_dict(session),
167
+ text=output.build(),
168
+ json_data=session_to_dict(session),
169
+ )
170
+
171
+
172
+ class SessionEndCommand(BaseCommand):
173
+ """End a session."""
174
+
175
+ def __init__(
176
+ self,
177
+ *,
178
+ session_id: str,
179
+ notes: str | None,
180
+ recommend: str | None,
181
+ blockers: list[str],
182
+ ) -> None:
183
+ super().__init__()
184
+ self.session_id = session_id
185
+ self.notes = notes
186
+ self.recommend = recommend
187
+ self.blockers = blockers
188
+
189
+ @classmethod
190
+ def from_args(cls, args: argparse.Namespace) -> SessionEndCommand:
191
+ return cls(
192
+ session_id=args.id,
193
+ notes=args.notes,
194
+ recommend=args.recommend,
195
+ blockers=args.blocker,
196
+ )
197
+
198
+ def execute(self) -> CommandResult:
199
+ """End a session."""
200
+ from htmlgraph.cli.base import TextOutputBuilder
201
+ from htmlgraph.converter import session_to_dict
202
+
203
+ sdk = self.get_sdk()
204
+ session = sdk.end_session(
205
+ self.session_id,
206
+ handoff_notes=self.notes,
207
+ recommended_next=self.recommend,
208
+ blockers=self.blockers,
209
+ )
210
+
211
+ self.require_node(session, "session", self.session_id)
212
+
213
+ duration = session.ended_at - session.started_at if session.ended_at else None
214
+ output = TextOutputBuilder()
215
+ output.add_success(f"Session ended: {session.id}")
216
+ output.add_field("Duration", duration)
217
+ output.add_field("Events", session.event_count)
218
+ if session.worked_on:
219
+ output.add_field("Worked on", ", ".join(session.worked_on))
220
+
221
+ return CommandResult(
222
+ data=session_to_dict(session),
223
+ text=output.build(),
224
+ json_data=session_to_dict(session),
225
+ )
226
+
227
+
228
+ class SessionListCommand(BaseCommand):
229
+ """List all sessions."""
230
+
231
+ def __init__(self, *, status: str | None = None, agent: str | None = None) -> None:
232
+ super().__init__()
233
+ self.status = status
234
+ self.agent_filter = agent
235
+
236
+ @classmethod
237
+ def from_args(cls, args: argparse.Namespace) -> SessionListCommand:
238
+ # Validate inputs using SessionFilter model
239
+ from htmlgraph.cli.models import SessionFilter
240
+
241
+ # Get optional filter arguments
242
+ status = getattr(args, "status", None)
243
+ agent = getattr(args, "agent", None)
244
+
245
+ try:
246
+ filter_model = SessionFilter(status=status, agent=agent)
247
+ except ValueError as e:
248
+ raise CommandError(str(e))
249
+
250
+ return cls(status=filter_model.status, agent=filter_model.agent)
251
+
252
+ def execute(self) -> CommandResult:
253
+ """List all sessions."""
254
+ from pathlib import Path
255
+
256
+ from htmlgraph.converter import SessionConverter, session_to_dict
257
+
258
+ if self.graph_dir is None:
259
+ raise CommandError("Missing graph directory")
260
+
261
+ sessions_dir = Path(self.graph_dir) / "sessions"
262
+ if not sessions_dir.exists():
263
+ from htmlgraph.cli.base import TextOutputBuilder
264
+
265
+ output = TextOutputBuilder()
266
+ output.add_warning("No sessions found.")
267
+ return CommandResult(
268
+ text=output.build(),
269
+ json_data={"sessions": []},
270
+ )
271
+
272
+ converter = SessionConverter(sessions_dir)
273
+
274
+ with console.status("[blue]Loading sessions...", spinner="dots"):
275
+ sessions = converter.load_all()
276
+
277
+ # Convert to display models for type-safe filtering and sorting
278
+ from htmlgraph.cli.models import SessionDisplay
279
+
280
+ display_sessions = [SessionDisplay.from_node(s) for s in sessions]
281
+
282
+ # Apply filters if provided
283
+ if self.status:
284
+ display_sessions = [
285
+ s for s in display_sessions if s.status == self.status
286
+ ]
287
+ if self.agent_filter:
288
+ display_sessions = [
289
+ s for s in display_sessions if s.agent == self.agent_filter
290
+ ]
291
+
292
+ # Sort by started_at descending using display model's sort_key
293
+ display_sessions.sort(key=lambda s: s.sort_key(), reverse=True)
294
+
295
+ if not display_sessions:
296
+ from htmlgraph.cli.base import TextOutputBuilder
297
+
298
+ output = TextOutputBuilder()
299
+ output.add_warning("No sessions found.")
300
+ return CommandResult(
301
+ text=output.build(),
302
+ json_data={"sessions": []},
303
+ )
304
+
305
+ # Create Rich table
306
+ table = Table(
307
+ title="Sessions",
308
+ show_header=True,
309
+ header_style="bold magenta",
310
+ box=box.ROUNDED,
311
+ )
312
+ table.add_column("ID", style="cyan", no_wrap=False, max_width=30)
313
+ table.add_column("Status", style="green", width=10)
314
+ table.add_column("Agent", style="blue", width=15)
315
+ table.add_column("Events", justify="right", style="yellow", width=8)
316
+ table.add_column("Started", style="white")
317
+
318
+ for session in display_sessions:
319
+ table.add_row(
320
+ session.id,
321
+ session.status,
322
+ session.agent,
323
+ str(session.event_count),
324
+ session.started_str,
325
+ )
326
+
327
+ # Return table object directly - TextFormatter will print it properly
328
+ return CommandResult(
329
+ data=table,
330
+ json_data=[session_to_dict(s) for s in sessions],
331
+ )
332
+
333
+
334
+ class SessionHandoffCommand(BaseCommand):
335
+ """Get or set session handoff context."""
336
+
337
+ def __init__(
338
+ self,
339
+ *,
340
+ session_id: str | None,
341
+ notes: str | None,
342
+ recommend: str | None,
343
+ blockers: list[str],
344
+ show: bool,
345
+ agent: str,
346
+ ) -> None:
347
+ super().__init__()
348
+ self.session_id = session_id
349
+ self.notes = notes
350
+ self.recommend = recommend
351
+ self.blockers = blockers
352
+ self.show = show
353
+ self.agent_name = agent
354
+
355
+ @classmethod
356
+ def from_args(cls, args: argparse.Namespace) -> SessionHandoffCommand:
357
+ return cls(
358
+ session_id=args.session_id,
359
+ notes=args.notes,
360
+ recommend=args.recommend,
361
+ blockers=args.blocker,
362
+ show=args.show,
363
+ agent=args.agent,
364
+ )
365
+
366
+ def execute(self) -> CommandResult:
367
+ """Get or set session handoff context."""
368
+ from htmlgraph.converter import session_to_dict
369
+
370
+ sdk = self.get_sdk()
371
+
372
+ if self.show:
373
+ # Show handoff context
374
+ if self.session_id:
375
+ session = sdk.session_manager.get_session(self.session_id)
376
+ else:
377
+ session = sdk.session_manager.get_last_ended_session(
378
+ agent=self.agent_name
379
+ )
380
+
381
+ if not session:
382
+ return CommandResult(
383
+ text="No handoff context found.",
384
+ json_data={},
385
+ )
386
+
387
+ from htmlgraph.cli.base import TextOutputBuilder
388
+
389
+ output = TextOutputBuilder()
390
+ output.add_line(f"Session: {session.id}")
391
+ if session.handoff_notes:
392
+ output.add_field("Notes", session.handoff_notes)
393
+ if session.recommended_next:
394
+ output.add_field("Recommended next", session.recommended_next)
395
+ if session.blockers:
396
+ output.add_field("Blockers", ", ".join(session.blockers))
397
+
398
+ return CommandResult(
399
+ data=session_to_dict(session),
400
+ text=output.build(),
401
+ json_data=session_to_dict(session),
402
+ )
403
+
404
+ # Set handoff context (not implemented in old CLI, just return error)
405
+ raise CommandError("Setting handoff context is not yet implemented")
406
+
407
+
408
+ class SessionStartInfoCommand(BaseCommand):
409
+ """Get comprehensive session start information."""
410
+
411
+ def __init__(
412
+ self,
413
+ *,
414
+ agent: str,
415
+ no_git: bool,
416
+ git_count: int,
417
+ top_n: int,
418
+ max_agents: int,
419
+ ) -> None:
420
+ super().__init__()
421
+ self.agent_name = agent
422
+ self.no_git = no_git
423
+ self.git_count = git_count
424
+ self.top_n = top_n
425
+ self.max_agents = max_agents
426
+
427
+ @classmethod
428
+ def from_args(cls, args: argparse.Namespace) -> SessionStartInfoCommand:
429
+ return cls(
430
+ agent=args.agent,
431
+ no_git=args.no_git,
432
+ git_count=args.git_count,
433
+ top_n=args.top_n,
434
+ max_agents=args.max_agents,
435
+ )
436
+
437
+ def execute(self) -> CommandResult:
438
+ """Get comprehensive session start information."""
439
+ sdk = self.get_sdk()
440
+
441
+ info = sdk.get_session_start_info(
442
+ include_git_log=not self.no_git,
443
+ git_log_count=self.git_count,
444
+ analytics_top_n=self.top_n,
445
+ analytics_max_agents=self.max_agents,
446
+ )
447
+
448
+ # Human-readable format
449
+ status: dict = info["status"] # type: ignore
450
+ by_status = status.get("by_status", {})
451
+
452
+ project_info = (
453
+ f"Project: {status.get('project_name', 'HtmlGraph')}\n"
454
+ f"Total features: {status.get('total_features', 0)}\n"
455
+ f"In progress: {status.get('wip_count', 0)}\n"
456
+ f"Completed: {by_status.get('done', 0)}"
457
+ )
458
+
459
+ panel = Panel(project_info, title="SESSION START INFO", border_style="cyan")
460
+
461
+ # Return panel object directly - TextFormatter will print it properly
462
+ return CommandResult(
463
+ data=panel,
464
+ json_data=info,
465
+ )