htmlgraph 0.26.24__py3-none-any.whl → 0.27.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.
Files changed (155) hide show
  1. htmlgraph/__init__.py +23 -1
  2. htmlgraph/__init__.pyi +123 -0
  3. htmlgraph/agent_registry.py +2 -1
  4. htmlgraph/analytics/cli.py +3 -3
  5. htmlgraph/analytics/cost_analyzer.py +5 -1
  6. htmlgraph/analytics/cross_session.py +13 -9
  7. htmlgraph/analytics/dependency.py +10 -6
  8. htmlgraph/analytics/work_type.py +15 -11
  9. htmlgraph/analytics_index.py +2 -1
  10. htmlgraph/api/main.py +114 -51
  11. htmlgraph/api/templates/dashboard-redesign.html +3 -3
  12. htmlgraph/api/templates/dashboard.html +3 -3
  13. htmlgraph/api/templates/partials/work-items.html +613 -0
  14. htmlgraph/attribute_index.py +2 -1
  15. htmlgraph/builders/base.py +2 -1
  16. htmlgraph/builders/bug.py +2 -1
  17. htmlgraph/builders/chore.py +2 -1
  18. htmlgraph/builders/epic.py +2 -1
  19. htmlgraph/builders/feature.py +2 -1
  20. htmlgraph/builders/insight.py +2 -1
  21. htmlgraph/builders/metric.py +2 -1
  22. htmlgraph/builders/pattern.py +2 -1
  23. htmlgraph/builders/phase.py +2 -1
  24. htmlgraph/builders/spike.py +2 -1
  25. htmlgraph/builders/track.py +28 -1
  26. htmlgraph/cli/analytics.py +2 -1
  27. htmlgraph/cli/base.py +33 -8
  28. htmlgraph/cli/core.py +2 -1
  29. htmlgraph/cli/main.py +2 -1
  30. htmlgraph/cli/models.py +2 -1
  31. htmlgraph/cli/templates/cost_dashboard.py +2 -1
  32. htmlgraph/cli/work/__init__.py +76 -1
  33. htmlgraph/cli/work/browse.py +115 -0
  34. htmlgraph/cli/work/features.py +2 -1
  35. htmlgraph/cli/work/orchestration.py +2 -1
  36. htmlgraph/cli/work/report.py +2 -1
  37. htmlgraph/cli/work/sessions.py +2 -1
  38. htmlgraph/cli/work/snapshot.py +559 -0
  39. htmlgraph/cli/work/tracks.py +2 -1
  40. htmlgraph/collections/base.py +43 -4
  41. htmlgraph/collections/bug.py +2 -1
  42. htmlgraph/collections/chore.py +2 -1
  43. htmlgraph/collections/epic.py +2 -1
  44. htmlgraph/collections/feature.py +2 -1
  45. htmlgraph/collections/insight.py +2 -1
  46. htmlgraph/collections/metric.py +2 -1
  47. htmlgraph/collections/pattern.py +2 -1
  48. htmlgraph/collections/phase.py +2 -1
  49. htmlgraph/collections/session.py +12 -7
  50. htmlgraph/collections/spike.py +6 -1
  51. htmlgraph/collections/task_delegation.py +7 -2
  52. htmlgraph/collections/todo.py +14 -1
  53. htmlgraph/collections/traces.py +15 -10
  54. htmlgraph/context_analytics.py +2 -1
  55. htmlgraph/converter.py +11 -0
  56. htmlgraph/dependency_models.py +2 -1
  57. htmlgraph/edge_index.py +2 -1
  58. htmlgraph/event_log.py +81 -66
  59. htmlgraph/event_migration.py +2 -1
  60. htmlgraph/file_watcher.py +12 -8
  61. htmlgraph/find_api.py +2 -1
  62. htmlgraph/git_events.py +6 -2
  63. htmlgraph/hooks/cigs_pretool_enforcer.py +5 -1
  64. htmlgraph/hooks/drift_handler.py +3 -3
  65. htmlgraph/hooks/event_tracker.py +40 -61
  66. htmlgraph/hooks/installer.py +5 -1
  67. htmlgraph/hooks/orchestrator.py +92 -14
  68. htmlgraph/hooks/orchestrator_reflector.py +4 -0
  69. htmlgraph/hooks/post_tool_use_failure.py +7 -3
  70. htmlgraph/hooks/posttooluse.py +4 -0
  71. htmlgraph/hooks/prompt_analyzer.py +5 -5
  72. htmlgraph/hooks/session_handler.py +5 -2
  73. htmlgraph/hooks/session_summary.py +6 -2
  74. htmlgraph/hooks/validator.py +8 -4
  75. htmlgraph/ids.py +2 -1
  76. htmlgraph/learning.py +2 -1
  77. htmlgraph/mcp_server.py +2 -1
  78. htmlgraph/models.py +18 -1
  79. htmlgraph/operations/analytics.py +2 -1
  80. htmlgraph/operations/bootstrap.py +2 -1
  81. htmlgraph/operations/events.py +2 -1
  82. htmlgraph/operations/fastapi_server.py +2 -1
  83. htmlgraph/operations/hooks.py +2 -1
  84. htmlgraph/operations/initialization.py +2 -1
  85. htmlgraph/operations/server.py +2 -1
  86. htmlgraph/orchestration/__init__.py +4 -0
  87. htmlgraph/orchestration/claude_launcher.py +23 -20
  88. htmlgraph/orchestration/command_builder.py +2 -1
  89. htmlgraph/orchestration/headless_spawner.py +6 -2
  90. htmlgraph/orchestration/model_selection.py +7 -3
  91. htmlgraph/orchestration/plugin_manager.py +25 -21
  92. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  93. htmlgraph/orchestration/spawners/claude.py +5 -2
  94. htmlgraph/orchestration/spawners/codex.py +12 -19
  95. htmlgraph/orchestration/spawners/copilot.py +13 -18
  96. htmlgraph/orchestration/spawners/gemini.py +12 -19
  97. htmlgraph/orchestration/subprocess_runner.py +6 -3
  98. htmlgraph/orchestration/task_coordination.py +16 -8
  99. htmlgraph/orchestrator.py +2 -1
  100. htmlgraph/parallel.py +2 -1
  101. htmlgraph/query_builder.py +2 -1
  102. htmlgraph/reflection.py +2 -1
  103. htmlgraph/refs.py +344 -0
  104. htmlgraph/repo_hash.py +2 -1
  105. htmlgraph/sdk/__init__.py +398 -0
  106. htmlgraph/sdk/__init__.pyi +14 -0
  107. htmlgraph/sdk/analytics/__init__.py +19 -0
  108. htmlgraph/sdk/analytics/engine.py +155 -0
  109. htmlgraph/sdk/analytics/helpers.py +178 -0
  110. htmlgraph/sdk/analytics/registry.py +109 -0
  111. htmlgraph/sdk/base.py +484 -0
  112. htmlgraph/sdk/constants.py +216 -0
  113. htmlgraph/sdk/core.pyi +308 -0
  114. htmlgraph/sdk/discovery.py +120 -0
  115. htmlgraph/sdk/help/__init__.py +12 -0
  116. htmlgraph/sdk/help/mixin.py +699 -0
  117. htmlgraph/sdk/mixins/__init__.py +15 -0
  118. htmlgraph/sdk/mixins/attribution.py +113 -0
  119. htmlgraph/sdk/mixins/mixin.py +410 -0
  120. htmlgraph/sdk/operations/__init__.py +12 -0
  121. htmlgraph/sdk/operations/mixin.py +427 -0
  122. htmlgraph/sdk/orchestration/__init__.py +17 -0
  123. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  124. htmlgraph/sdk/orchestration/spawner.py +204 -0
  125. htmlgraph/sdk/planning/__init__.py +19 -0
  126. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  127. htmlgraph/sdk/planning/mixin.py +211 -0
  128. htmlgraph/sdk/planning/parallel.py +186 -0
  129. htmlgraph/sdk/planning/queue.py +210 -0
  130. htmlgraph/sdk/planning/recommendations.py +87 -0
  131. htmlgraph/sdk/planning/smart_planning.py +319 -0
  132. htmlgraph/sdk/session/__init__.py +19 -0
  133. htmlgraph/sdk/session/continuity.py +57 -0
  134. htmlgraph/sdk/session/handoff.py +110 -0
  135. htmlgraph/sdk/session/info.py +309 -0
  136. htmlgraph/sdk/session/manager.py +103 -0
  137. htmlgraph/server.py +21 -17
  138. htmlgraph/session_manager.py +1 -7
  139. htmlgraph/session_warning.py +2 -1
  140. htmlgraph/sessions/handoff.py +10 -3
  141. htmlgraph/system_prompts.py +2 -1
  142. htmlgraph/track_builder.py +14 -1
  143. htmlgraph/transcript.py +2 -1
  144. htmlgraph/watch.py +2 -1
  145. htmlgraph/work_type_utils.py +2 -1
  146. {htmlgraph-0.26.24.dist-info → htmlgraph-0.27.0.dist-info}/METADATA +15 -1
  147. {htmlgraph-0.26.24.dist-info → htmlgraph-0.27.0.dist-info}/RECORD +154 -117
  148. htmlgraph/sdk.py +0 -3430
  149. {htmlgraph-0.26.24.data → htmlgraph-0.27.0.data}/data/htmlgraph/dashboard.html +0 -0
  150. {htmlgraph-0.26.24.data → htmlgraph-0.27.0.data}/data/htmlgraph/styles.css +0 -0
  151. {htmlgraph-0.26.24.data → htmlgraph-0.27.0.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  152. {htmlgraph-0.26.24.data → htmlgraph-0.27.0.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  153. {htmlgraph-0.26.24.data → htmlgraph-0.27.0.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  154. {htmlgraph-0.26.24.dist-info → htmlgraph-0.27.0.dist-info}/WHEEL +0 -0
  155. {htmlgraph-0.26.24.dist-info → htmlgraph-0.27.0.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Insight builder for creating session insight nodes.
3
5
 
@@ -5,7 +7,6 @@ Extends BaseBuilder with insight-specific methods for
5
7
  session health scoring and pattern detection.
6
8
  """
7
9
 
8
- from __future__ import annotations
9
10
 
10
11
  from typing import TYPE_CHECKING, Any
11
12
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Metric builder for creating aggregated metric nodes.
3
5
 
@@ -5,7 +7,6 @@ Extends BaseBuilder with metric-specific methods for
5
7
  time-series aggregation and trend analysis.
6
8
  """
7
9
 
8
- from __future__ import annotations
9
10
 
10
11
  from datetime import datetime
11
12
  from typing import TYPE_CHECKING, Any
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Pattern builder for creating workflow pattern nodes.
3
5
 
@@ -5,7 +7,6 @@ Extends BaseBuilder with pattern-specific methods for
5
7
  tracking optimal and anti-pattern tool sequences.
6
8
  """
7
9
 
8
- from __future__ import annotations
9
10
 
10
11
  from datetime import datetime
11
12
  from typing import TYPE_CHECKING, Any
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Phase builder for creating project phase nodes.
3
5
 
@@ -5,7 +7,6 @@ Extends BaseBuilder with phase-specific methods like
5
7
  phase ordering and dependencies.
6
8
  """
7
9
 
8
- from __future__ import annotations
9
10
 
10
11
  from datetime import date
11
12
  from typing import TYPE_CHECKING, Any
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Spike builder for creating spike investigation nodes.
3
5
 
@@ -5,7 +7,6 @@ Extends BaseBuilder with spike-specific methods like
5
7
  spike_type and timebox_hours.
6
8
  """
7
9
 
8
- from __future__ import annotations
9
10
 
10
11
  from typing import TYPE_CHECKING, Any
11
12
 
@@ -1,8 +1,9 @@
1
+ from __future__ import annotations
2
+
1
3
  """
2
4
  Track Builder for agent-friendly track creation.
3
5
  """
4
6
 
5
- from __future__ import annotations
6
7
 
7
8
  import re
8
9
  from datetime import datetime
@@ -617,6 +618,30 @@ class TrackBuilder:
617
618
  Phase(id=f"phase-{i + 1}", name=phase_name, tasks=phase_tasks)
618
619
  )
619
620
 
621
+ # Persist features to database from plan phases
622
+ features_created = 0
623
+ if phases:
624
+ for phase in phases:
625
+ for task in phase.tasks:
626
+ # Generate feature ID from task description
627
+ feature_id = generate_id(node_type="feat", title=task.description)
628
+
629
+ # Insert feature into database
630
+ success = self.sdk._db.insert_feature(
631
+ feature_id=feature_id,
632
+ feature_type="task", # Tasks from tracks are features of type "task"
633
+ title=task.description,
634
+ status="todo", # All new tasks start as "todo"
635
+ priority=self._priority, # Inherit priority from track
636
+ assigned_to=None, # No assignment initially
637
+ track_id=track_id,
638
+ description=f"Task from {phase.name}",
639
+ steps_total=0,
640
+ tags=None,
641
+ )
642
+ if success:
643
+ features_created += 1
644
+
620
645
  if self._consolidated:
621
646
  # Single-file format: everything in one index.html
622
647
  track_file = self.sdk._directory / "tracks" / f"{track_id}.html"
@@ -677,6 +702,8 @@ class TrackBuilder:
677
702
  if self._plan_phases:
678
703
  total_tasks = sum(len(tasks) for _, tasks in self._plan_phases)
679
704
  print(f" - Plan with {len(self._plan_phases)} phases, {total_tasks} tasks")
705
+ if features_created > 0:
706
+ print(f" - Persisted {features_created} features to database")
680
707
 
681
708
  return track
682
709
 
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """HtmlGraph CLI - Analytics and reporting commands.
2
4
 
3
5
  Commands for analytics and reporting:
@@ -7,7 +9,6 @@ Commands for analytics and reporting:
7
9
  - sync-docs: Documentation synchronization
8
10
  """
9
11
 
10
- from __future__ import annotations
11
12
 
12
13
  import argparse
13
14
  import json
htmlgraph/cli/base.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """Base classes and utilities for CLI commands.
2
4
 
3
5
  Provides:
@@ -10,7 +12,6 @@ Provides:
10
12
  - save_traceback: Save full tracebacks to log files instead of console
11
13
  """
12
14
 
13
- from __future__ import annotations
14
15
 
15
16
  import argparse
16
17
  import json
@@ -464,11 +465,22 @@ class Formatter(Protocol):
464
465
 
465
466
 
466
467
  def _serialize_json(value: Any) -> Any:
467
- """Recursively serialize value to JSON-compatible types."""
468
+ """Recursively serialize value to JSON-compatible types.
469
+
470
+ Sanitizes strings to remove control characters (newlines, tabs) that
471
+ would break JSON validity when using json.dumps().
472
+ """
468
473
  if value is None:
469
474
  return None
470
475
  if isinstance(value, (datetime, date)):
471
476
  return value.isoformat()
477
+ if isinstance(value, str):
478
+ # Sanitize string: replace control characters with spaces
479
+ # This prevents newlines/tabs in JSON string values from breaking JSON validity
480
+ sanitized = value.replace("\n", " ").replace("\r", " ").replace("\t", " ")
481
+ # Collapse multiple spaces to single space
482
+ sanitized = " ".join(sanitized.split())
483
+ return sanitized
472
484
  if hasattr(value, "model_dump") and callable(getattr(value, "model_dump")):
473
485
  return _serialize_json(value.model_dump())
474
486
  if hasattr(value, "to_dict") and callable(getattr(value, "to_dict")):
@@ -485,7 +497,9 @@ class JsonFormatter:
485
497
 
486
498
  def output(self, result: CommandResult) -> None:
487
499
  payload = result.json_data if result.json_data is not None else result.data
488
- _console.print(json.dumps(_serialize_json(payload), indent=2))
500
+ # Use sys.stdout.write instead of _console.print to avoid Rich's line-wrapping
501
+ # which inserts literal newlines into JSON string values, breaking JSON validity
502
+ sys.stdout.write(json.dumps(_serialize_json(payload), indent=2) + "\n")
489
503
 
490
504
 
491
505
  class TextFormatter:
@@ -507,16 +521,21 @@ class TextFormatter:
507
521
  _console.print(result.data)
508
522
  return
509
523
  if isinstance(result.text, str):
510
- _console.print(result.text)
524
+ # Use sys.stdout.write() for ANSI-formatted text to preserve colors when piped
525
+ # This bypasses Rich's reprocessing and ensures ANSI codes are preserved
526
+ sys.stdout.write(result.text)
527
+ if not result.text.endswith("\n"):
528
+ sys.stdout.write("\n")
511
529
  return
512
- _console.print("\n".join(str(line) for line in result.text))
530
+ # For text as list/iterable, write directly to preserve ANSI codes
531
+ sys.stdout.write("\n".join(str(line) for line in result.text) + "\n")
513
532
 
514
533
 
515
534
  def get_formatter(format_name: str) -> Formatter:
516
- """Get formatter by name (json, text, plain)."""
535
+ """Get formatter by name (json, text, plain, refs)."""
517
536
  if format_name == "json":
518
537
  return JsonFormatter()
519
- if format_name in ("text", "plain", ""):
538
+ if format_name in ("text", "plain", "refs", ""):
520
539
  return TextFormatter()
521
540
  raise CommandError(f"Unknown output format '{format_name}'")
522
541
 
@@ -539,6 +558,9 @@ class BaseCommand(ABC):
539
558
  self.graph_dir: str | None = None
540
559
  self.agent: str | None = None
541
560
  self._sdk: SDK | None = None
561
+ self.override_output_format: str | None = (
562
+ None # Allow commands to override formatter
563
+ )
542
564
 
543
565
  @classmethod
544
566
  @abstractmethod
@@ -648,7 +670,10 @@ class BaseCommand(ABC):
648
670
  try:
649
671
  self.validate()
650
672
  result = self.execute()
651
- formatter = get_formatter(output_format)
673
+ # Allow commands to override output format
674
+ # (e.g., snapshot command's --output-format flag overrides global --format)
675
+ actual_format = self.override_output_format or output_format
676
+ formatter = get_formatter(actual_format)
652
677
  formatter.output(result)
653
678
  except CommandError as exc:
654
679
  error_console = Console(file=sys.stderr)
htmlgraph/cli/core.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """HtmlGraph CLI - Infrastructure commands.
2
4
 
3
5
  Commands for core infrastructure operations:
@@ -11,7 +13,6 @@ Commands for core infrastructure operations:
11
13
  - Other utilities
12
14
  """
13
15
 
14
- from __future__ import annotations
15
16
 
16
17
  import argparse
17
18
  import sys
htmlgraph/cli/main.py CHANGED
@@ -1,10 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  """HtmlGraph CLI - Main entry point.
2
4
 
3
5
  Entry point with argument parsing and command routing.
4
6
  Keeps main() thin by delegating to command modules.
5
7
  """
6
8
 
7
- from __future__ import annotations
8
9
 
9
10
  import argparse
10
11
  import sys
htmlgraph/cli/models.py CHANGED
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """HtmlGraph CLI - Pydantic models for command filters and configuration.
2
4
 
3
5
  This module provides type-safe models for validating command inputs:
@@ -5,7 +7,6 @@ This module provides type-safe models for validating command inputs:
5
7
  - Configuration models for infrastructure commands (init, serve)
6
8
  """
7
9
 
8
- from __future__ import annotations
9
10
 
10
11
  from datetime import datetime
11
12
  from typing import Literal
@@ -1,10 +1,11 @@
1
+ from __future__ import annotations
2
+
1
3
  """HTML template for cost dashboard.
2
4
 
3
5
  This module generates a beautiful, interactive HTML dashboard for cost analysis.
4
6
  Separated from main CLI code for maintainability and testability.
5
7
  """
6
8
 
7
- from __future__ import annotations
8
9
 
9
10
  from datetime import datetime
10
11
  from typing import TYPE_CHECKING, Any
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """HtmlGraph CLI - Work management commands.
2
4
 
3
5
  Commands for managing work items:
@@ -9,7 +11,6 @@ Commands for managing work items:
9
11
  - Other work-related operations
10
12
  """
11
13
 
12
- from __future__ import annotations
13
14
 
14
15
  from typing import TYPE_CHECKING
15
16
 
@@ -23,6 +24,7 @@ def register_commands(subparsers: _SubParsersAction) -> None:
23
24
  Args:
24
25
  subparsers: Subparser action from ArgumentParser.add_subparsers()
25
26
  """
27
+ from htmlgraph.cli.work.browse import BrowseCommand
26
28
  from htmlgraph.cli.work.features import register_feature_commands
27
29
  from htmlgraph.cli.work.orchestration import (
28
30
  register_archive_commands,
@@ -31,6 +33,7 @@ def register_commands(subparsers: _SubParsersAction) -> None:
31
33
  )
32
34
  from htmlgraph.cli.work.report import register_report_commands
33
35
  from htmlgraph.cli.work.sessions import register_session_commands
36
+ from htmlgraph.cli.work.snapshot import SnapshotCommand
34
37
  from htmlgraph.cli.work.tracks import register_track_commands
35
38
 
36
39
  # Register all command groups
@@ -42,8 +45,75 @@ def register_commands(subparsers: _SubParsersAction) -> None:
42
45
  register_claude_commands(subparsers)
43
46
  register_report_commands(subparsers)
44
47
 
48
+ # Snapshot command
49
+ snapshot_parser = subparsers.add_parser(
50
+ "snapshot",
51
+ help="Output current graph state with refs",
52
+ )
53
+ snapshot_parser.add_argument(
54
+ "--output-format",
55
+ choices=["refs", "json", "text"],
56
+ default="refs",
57
+ help="Output format (default: refs)",
58
+ )
59
+ snapshot_parser.add_argument(
60
+ "--type",
61
+ help="Filter by type (feature, track, bug, spike, chore, epic, all)",
62
+ )
63
+ snapshot_parser.add_argument(
64
+ "--status",
65
+ help="Filter by status (todo, in_progress, blocked, done, all)",
66
+ )
67
+ snapshot_parser.add_argument(
68
+ "--track",
69
+ help="Show only items in a specific track (by track ID or ref)",
70
+ )
71
+ snapshot_parser.add_argument(
72
+ "--active",
73
+ action="store_true",
74
+ help="Show only TODO/IN_PROGRESS items (filters out metadata spikes)",
75
+ )
76
+ snapshot_parser.add_argument(
77
+ "--blockers",
78
+ action="store_true",
79
+ help="Show only critical/blocked items",
80
+ )
81
+ snapshot_parser.add_argument(
82
+ "--summary",
83
+ action="store_true",
84
+ help="Show counts and progress summary instead of listing all items",
85
+ )
86
+ snapshot_parser.add_argument(
87
+ "--my-work",
88
+ action="store_true",
89
+ help="Show items assigned to current agent",
90
+ )
91
+ snapshot_parser.set_defaults(func=SnapshotCommand.from_args)
92
+
93
+ # Browse command
94
+ browse_parser = subparsers.add_parser(
95
+ "browse",
96
+ help="Open dashboard in browser",
97
+ )
98
+ browse_parser.add_argument(
99
+ "--port",
100
+ type=int,
101
+ default=8080,
102
+ help="Server port (default: 8080)",
103
+ )
104
+ browse_parser.add_argument(
105
+ "--query-type",
106
+ help="Filter by type (feature, track, bug, spike, chore, epic)",
107
+ )
108
+ browse_parser.add_argument(
109
+ "--query-status",
110
+ help="Filter by status (todo, in_progress, blocked, done)",
111
+ )
112
+ browse_parser.set_defaults(func=BrowseCommand.from_args)
113
+
45
114
 
46
115
  # Re-export all command classes for backward compatibility
116
+ from htmlgraph.cli.work.browse import BrowseCommand
47
117
  from htmlgraph.cli.work.features import (
48
118
  FeatureClaimCommand,
49
119
  FeatureCompleteCommand,
@@ -71,6 +141,7 @@ from htmlgraph.cli.work.sessions import (
71
141
  SessionStartCommand,
72
142
  SessionStartInfoCommand,
73
143
  )
144
+ from htmlgraph.cli.work.snapshot import SnapshotCommand
74
145
  from htmlgraph.cli.work.tracks import (
75
146
  TrackDeleteCommand,
76
147
  TrackListCommand,
@@ -89,6 +160,10 @@ __all__ = [
89
160
  "SessionStartInfoCommand",
90
161
  # Report commands
91
162
  "SessionReportCommand",
163
+ # Snapshot commands
164
+ "SnapshotCommand",
165
+ # Browse commands
166
+ "BrowseCommand",
92
167
  # Feature commands
93
168
  "FeatureListCommand",
94
169
  "FeatureCreateCommand",
@@ -0,0 +1,115 @@
1
+ from __future__ import annotations
2
+
3
+ """HtmlGraph CLI - Browse command for opening dashboard in browser."""
4
+
5
+
6
+ import argparse
7
+ import webbrowser
8
+ from typing import TYPE_CHECKING
9
+
10
+ from htmlgraph.cli.base import BaseCommand, CommandResult
11
+
12
+ if TYPE_CHECKING:
13
+ pass
14
+
15
+
16
+ class BrowseCommand(BaseCommand):
17
+ """Open the HtmlGraph dashboard in your default browser.
18
+
19
+ Usage:
20
+ htmlgraph browse # Open dashboard
21
+ htmlgraph browse --port 8080 # Custom port
22
+ htmlgraph browse --query-type feature # Show only features
23
+ htmlgraph browse --query-status todo # Show only todo items
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ *,
29
+ port: int = 8080,
30
+ query_type: str | None = None,
31
+ query_status: str | None = None,
32
+ ) -> None:
33
+ """Initialize BrowseCommand.
34
+
35
+ Args:
36
+ port: Server port (default: 8080)
37
+ query_type: Filter by type (feature, track, bug, spike, chore, epic)
38
+ query_status: Filter by status (todo, in_progress, blocked, done)
39
+ """
40
+ super().__init__()
41
+ self.port = port
42
+ self.query_type = query_type
43
+ self.query_status = query_status
44
+
45
+ @classmethod
46
+ def from_args(cls, args: argparse.Namespace) -> BrowseCommand:
47
+ """Create BrowseCommand from argparse arguments.
48
+
49
+ Args:
50
+ args: Argparse namespace with command arguments
51
+
52
+ Returns:
53
+ BrowseCommand instance
54
+ """
55
+ return cls(
56
+ port=args.port,
57
+ query_type=args.query_type,
58
+ query_status=args.query_status,
59
+ )
60
+
61
+ def execute(self) -> CommandResult:
62
+ """Execute the browse command.
63
+
64
+ Opens the dashboard in the default browser with optional query parameters.
65
+
66
+ Returns:
67
+ CommandResult with success status and URL
68
+ """
69
+ # Build URL with query params
70
+ url = f"http://localhost:{self.port}"
71
+
72
+ params = []
73
+ if self.query_type:
74
+ params.append(f"type={self.query_type}")
75
+ if self.query_status:
76
+ params.append(f"status={self.query_status}")
77
+
78
+ if params:
79
+ url += "?" + "&".join(params)
80
+
81
+ # Check if server is running
82
+ try:
83
+ import requests # type: ignore[import-untyped]
84
+
85
+ response = requests.head(f"http://localhost:{self.port}", timeout=1)
86
+ response.raise_for_status()
87
+ except ImportError:
88
+ # requests module not available - try to open anyway with a warning
89
+ webbrowser.open(url)
90
+ return CommandResult(
91
+ data={"url": url},
92
+ text=f"Opening dashboard at {url}\n(Note: Could not verify server is running - install 'requests' for server checks)",
93
+ exit_code=0,
94
+ )
95
+ except Exception:
96
+ # Server not running or not responding
97
+ return CommandResult(
98
+ text=f"Dashboard server not running on port {self.port}.\nStart with: htmlgraph serve --port {self.port}",
99
+ exit_code=1,
100
+ )
101
+
102
+ # Open browser
103
+ try:
104
+ webbrowser.open(url)
105
+ except Exception as e:
106
+ return CommandResult(
107
+ text=f"Failed to open browser: {e}\nYou can manually visit: {url}",
108
+ exit_code=1,
109
+ )
110
+
111
+ return CommandResult(
112
+ data={"url": url},
113
+ text=f"Opening dashboard at {url}",
114
+ exit_code=0,
115
+ )
@@ -1,6 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  """HtmlGraph CLI - Feature management commands."""
2
4
 
3
- from __future__ import annotations
4
5
 
5
6
  import argparse
6
7
  from typing import TYPE_CHECKING
@@ -1,6 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  """HtmlGraph CLI - Orchestration commands (Archive, Orchestrator, Claude)."""
2
4
 
3
- from __future__ import annotations
4
5
 
5
6
  import argparse
6
7
  from datetime import datetime
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  """HtmlGraph CLI - Session report commands.
2
4
 
3
5
  Commands for generating "What Did Claude Do?" reports:
@@ -7,7 +9,6 @@ THE killer feature that differentiates HtmlGraph - complete observability
7
9
  of AI agent activities with cost attribution and tool usage analysis.
8
10
  """
9
11
 
10
- from __future__ import annotations
11
12
 
12
13
  import argparse
13
14
  import sqlite3
@@ -1,6 +1,7 @@
1
+ from __future__ import annotations
2
+
1
3
  """HtmlGraph CLI - Session management commands."""
2
4
 
3
- from __future__ import annotations
4
5
 
5
6
  import argparse
6
7
  from typing import TYPE_CHECKING