htmlgraph 0.20.1__py3-none-any.whl → 0.27.5__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 (304) hide show
  1. htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
  2. htmlgraph/.htmlgraph/agents.json +72 -0
  3. htmlgraph/.htmlgraph/htmlgraph.db +0 -0
  4. htmlgraph/__init__.py +51 -1
  5. htmlgraph/__init__.pyi +123 -0
  6. htmlgraph/agent_detection.py +26 -10
  7. htmlgraph/agent_registry.py +2 -1
  8. htmlgraph/analytics/__init__.py +8 -1
  9. htmlgraph/analytics/cli.py +86 -20
  10. htmlgraph/analytics/cost_analyzer.py +391 -0
  11. htmlgraph/analytics/cost_monitor.py +664 -0
  12. htmlgraph/analytics/cost_reporter.py +675 -0
  13. htmlgraph/analytics/cross_session.py +617 -0
  14. htmlgraph/analytics/dependency.py +10 -6
  15. htmlgraph/analytics/pattern_learning.py +771 -0
  16. htmlgraph/analytics/session_graph.py +707 -0
  17. htmlgraph/analytics/strategic/__init__.py +80 -0
  18. htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
  19. htmlgraph/analytics/strategic/pattern_detector.py +876 -0
  20. htmlgraph/analytics/strategic/preference_manager.py +709 -0
  21. htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
  22. htmlgraph/analytics/work_type.py +67 -27
  23. htmlgraph/analytics_index.py +53 -20
  24. htmlgraph/api/__init__.py +3 -0
  25. htmlgraph/api/cost_alerts_websocket.py +416 -0
  26. htmlgraph/api/main.py +2498 -0
  27. htmlgraph/api/static/htmx.min.js +1 -0
  28. htmlgraph/api/static/style-redesign.css +1344 -0
  29. htmlgraph/api/static/style.css +1079 -0
  30. htmlgraph/api/templates/dashboard-redesign.html +1366 -0
  31. htmlgraph/api/templates/dashboard.html +794 -0
  32. htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
  33. htmlgraph/api/templates/partials/activity-feed.html +1100 -0
  34. htmlgraph/api/templates/partials/agents-redesign.html +317 -0
  35. htmlgraph/api/templates/partials/agents.html +317 -0
  36. htmlgraph/api/templates/partials/event-traces.html +373 -0
  37. htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
  38. htmlgraph/api/templates/partials/features.html +578 -0
  39. htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
  40. htmlgraph/api/templates/partials/metrics.html +346 -0
  41. htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
  42. htmlgraph/api/templates/partials/orchestration.html +198 -0
  43. htmlgraph/api/templates/partials/spawners.html +375 -0
  44. htmlgraph/api/templates/partials/work-items.html +613 -0
  45. htmlgraph/api/websocket.py +538 -0
  46. htmlgraph/archive/__init__.py +24 -0
  47. htmlgraph/archive/bloom.py +234 -0
  48. htmlgraph/archive/fts.py +297 -0
  49. htmlgraph/archive/manager.py +583 -0
  50. htmlgraph/archive/search.py +244 -0
  51. htmlgraph/atomic_ops.py +560 -0
  52. htmlgraph/attribute_index.py +2 -1
  53. htmlgraph/bounded_paths.py +539 -0
  54. htmlgraph/builders/base.py +57 -2
  55. htmlgraph/builders/bug.py +19 -3
  56. htmlgraph/builders/chore.py +19 -3
  57. htmlgraph/builders/epic.py +19 -3
  58. htmlgraph/builders/feature.py +27 -3
  59. htmlgraph/builders/insight.py +2 -1
  60. htmlgraph/builders/metric.py +2 -1
  61. htmlgraph/builders/pattern.py +2 -1
  62. htmlgraph/builders/phase.py +19 -3
  63. htmlgraph/builders/spike.py +29 -3
  64. htmlgraph/builders/track.py +42 -1
  65. htmlgraph/cigs/__init__.py +81 -0
  66. htmlgraph/cigs/autonomy.py +385 -0
  67. htmlgraph/cigs/cost.py +475 -0
  68. htmlgraph/cigs/messages_basic.py +472 -0
  69. htmlgraph/cigs/messaging.py +365 -0
  70. htmlgraph/cigs/models.py +771 -0
  71. htmlgraph/cigs/pattern_storage.py +427 -0
  72. htmlgraph/cigs/patterns.py +503 -0
  73. htmlgraph/cigs/posttool_analyzer.py +234 -0
  74. htmlgraph/cigs/reporter.py +818 -0
  75. htmlgraph/cigs/tracker.py +317 -0
  76. htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
  77. htmlgraph/cli/.htmlgraph/agents.json +72 -0
  78. htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
  79. htmlgraph/cli/__init__.py +42 -0
  80. htmlgraph/cli/__main__.py +6 -0
  81. htmlgraph/cli/analytics.py +1424 -0
  82. htmlgraph/cli/base.py +685 -0
  83. htmlgraph/cli/constants.py +206 -0
  84. htmlgraph/cli/core.py +954 -0
  85. htmlgraph/cli/main.py +147 -0
  86. htmlgraph/cli/models.py +475 -0
  87. htmlgraph/cli/templates/__init__.py +1 -0
  88. htmlgraph/cli/templates/cost_dashboard.py +399 -0
  89. htmlgraph/cli/work/__init__.py +239 -0
  90. htmlgraph/cli/work/browse.py +115 -0
  91. htmlgraph/cli/work/features.py +568 -0
  92. htmlgraph/cli/work/orchestration.py +676 -0
  93. htmlgraph/cli/work/report.py +728 -0
  94. htmlgraph/cli/work/sessions.py +466 -0
  95. htmlgraph/cli/work/snapshot.py +559 -0
  96. htmlgraph/cli/work/tracks.py +486 -0
  97. htmlgraph/cli_commands/__init__.py +1 -0
  98. htmlgraph/cli_commands/feature.py +195 -0
  99. htmlgraph/cli_framework.py +115 -0
  100. htmlgraph/collections/__init__.py +2 -0
  101. htmlgraph/collections/base.py +197 -14
  102. htmlgraph/collections/bug.py +2 -1
  103. htmlgraph/collections/chore.py +2 -1
  104. htmlgraph/collections/epic.py +2 -1
  105. htmlgraph/collections/feature.py +2 -1
  106. htmlgraph/collections/insight.py +2 -1
  107. htmlgraph/collections/metric.py +2 -1
  108. htmlgraph/collections/pattern.py +2 -1
  109. htmlgraph/collections/phase.py +2 -1
  110. htmlgraph/collections/session.py +194 -0
  111. htmlgraph/collections/spike.py +13 -2
  112. htmlgraph/collections/task_delegation.py +241 -0
  113. htmlgraph/collections/todo.py +14 -1
  114. htmlgraph/collections/traces.py +487 -0
  115. htmlgraph/config/cost_models.json +56 -0
  116. htmlgraph/config.py +190 -0
  117. htmlgraph/context_analytics.py +2 -1
  118. htmlgraph/converter.py +116 -7
  119. htmlgraph/cost_analysis/__init__.py +5 -0
  120. htmlgraph/cost_analysis/analyzer.py +438 -0
  121. htmlgraph/dashboard.html +2246 -248
  122. htmlgraph/dashboard.html.backup +6592 -0
  123. htmlgraph/dashboard.html.bak +7181 -0
  124. htmlgraph/dashboard.html.bak2 +7231 -0
  125. htmlgraph/dashboard.html.bak3 +7232 -0
  126. htmlgraph/db/__init__.py +38 -0
  127. htmlgraph/db/queries.py +790 -0
  128. htmlgraph/db/schema.py +1788 -0
  129. htmlgraph/decorators.py +317 -0
  130. htmlgraph/dependency_models.py +2 -1
  131. htmlgraph/deploy.py +26 -27
  132. htmlgraph/docs/API_REFERENCE.md +841 -0
  133. htmlgraph/docs/HTTP_API.md +750 -0
  134. htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
  135. htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
  136. htmlgraph/docs/README.md +532 -0
  137. htmlgraph/docs/__init__.py +77 -0
  138. htmlgraph/docs/docs_version.py +55 -0
  139. htmlgraph/docs/metadata.py +93 -0
  140. htmlgraph/docs/migrations.py +232 -0
  141. htmlgraph/docs/template_engine.py +143 -0
  142. htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
  143. htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
  144. htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
  145. htmlgraph/docs/templates/base_agents.md.j2 +78 -0
  146. htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
  147. htmlgraph/docs/version_check.py +163 -0
  148. htmlgraph/edge_index.py +2 -1
  149. htmlgraph/error_handler.py +544 -0
  150. htmlgraph/event_log.py +86 -37
  151. htmlgraph/event_migration.py +2 -1
  152. htmlgraph/file_watcher.py +12 -8
  153. htmlgraph/find_api.py +2 -1
  154. htmlgraph/git_events.py +67 -9
  155. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  156. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  157. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  158. htmlgraph/hooks/__init__.py +8 -0
  159. htmlgraph/hooks/bootstrap.py +169 -0
  160. htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
  161. htmlgraph/hooks/concurrent_sessions.py +208 -0
  162. htmlgraph/hooks/context.py +350 -0
  163. htmlgraph/hooks/drift_handler.py +525 -0
  164. htmlgraph/hooks/event_tracker.py +790 -99
  165. htmlgraph/hooks/git_commands.py +175 -0
  166. htmlgraph/hooks/installer.py +5 -1
  167. htmlgraph/hooks/orchestrator.py +327 -76
  168. htmlgraph/hooks/orchestrator_reflector.py +31 -4
  169. htmlgraph/hooks/post_tool_use_failure.py +32 -7
  170. htmlgraph/hooks/post_tool_use_handler.py +257 -0
  171. htmlgraph/hooks/posttooluse.py +92 -19
  172. htmlgraph/hooks/pretooluse.py +527 -7
  173. htmlgraph/hooks/prompt_analyzer.py +637 -0
  174. htmlgraph/hooks/session_handler.py +668 -0
  175. htmlgraph/hooks/session_summary.py +395 -0
  176. htmlgraph/hooks/state_manager.py +504 -0
  177. htmlgraph/hooks/subagent_detection.py +202 -0
  178. htmlgraph/hooks/subagent_stop.py +369 -0
  179. htmlgraph/hooks/task_enforcer.py +99 -4
  180. htmlgraph/hooks/validator.py +212 -91
  181. htmlgraph/ids.py +2 -1
  182. htmlgraph/learning.py +125 -100
  183. htmlgraph/mcp_server.py +2 -1
  184. htmlgraph/models.py +217 -18
  185. htmlgraph/operations/README.md +62 -0
  186. htmlgraph/operations/__init__.py +79 -0
  187. htmlgraph/operations/analytics.py +339 -0
  188. htmlgraph/operations/bootstrap.py +289 -0
  189. htmlgraph/operations/events.py +244 -0
  190. htmlgraph/operations/fastapi_server.py +231 -0
  191. htmlgraph/operations/hooks.py +350 -0
  192. htmlgraph/operations/initialization.py +597 -0
  193. htmlgraph/operations/initialization.py.backup +228 -0
  194. htmlgraph/operations/server.py +303 -0
  195. htmlgraph/orchestration/__init__.py +58 -0
  196. htmlgraph/orchestration/claude_launcher.py +179 -0
  197. htmlgraph/orchestration/command_builder.py +72 -0
  198. htmlgraph/orchestration/headless_spawner.py +281 -0
  199. htmlgraph/orchestration/live_events.py +377 -0
  200. htmlgraph/orchestration/model_selection.py +327 -0
  201. htmlgraph/orchestration/plugin_manager.py +140 -0
  202. htmlgraph/orchestration/prompts.py +137 -0
  203. htmlgraph/orchestration/spawner_event_tracker.py +383 -0
  204. htmlgraph/orchestration/spawners/__init__.py +16 -0
  205. htmlgraph/orchestration/spawners/base.py +194 -0
  206. htmlgraph/orchestration/spawners/claude.py +173 -0
  207. htmlgraph/orchestration/spawners/codex.py +435 -0
  208. htmlgraph/orchestration/spawners/copilot.py +294 -0
  209. htmlgraph/orchestration/spawners/gemini.py +471 -0
  210. htmlgraph/orchestration/subprocess_runner.py +36 -0
  211. htmlgraph/{orchestration.py → orchestration/task_coordination.py} +16 -8
  212. htmlgraph/orchestration.md +563 -0
  213. htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
  214. htmlgraph/orchestrator.py +2 -1
  215. htmlgraph/orchestrator_config.py +357 -0
  216. htmlgraph/orchestrator_mode.py +115 -4
  217. htmlgraph/parallel.py +2 -1
  218. htmlgraph/parser.py +86 -6
  219. htmlgraph/path_query.py +608 -0
  220. htmlgraph/pattern_matcher.py +636 -0
  221. htmlgraph/pydantic_models.py +476 -0
  222. htmlgraph/quality_gates.py +350 -0
  223. htmlgraph/query_builder.py +2 -1
  224. htmlgraph/query_composer.py +509 -0
  225. htmlgraph/reflection.py +443 -0
  226. htmlgraph/refs.py +344 -0
  227. htmlgraph/repo_hash.py +512 -0
  228. htmlgraph/repositories/__init__.py +292 -0
  229. htmlgraph/repositories/analytics_repository.py +455 -0
  230. htmlgraph/repositories/analytics_repository_standard.py +628 -0
  231. htmlgraph/repositories/feature_repository.py +581 -0
  232. htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
  233. htmlgraph/repositories/feature_repository_memory.py +607 -0
  234. htmlgraph/repositories/feature_repository_sqlite.py +858 -0
  235. htmlgraph/repositories/filter_service.py +620 -0
  236. htmlgraph/repositories/filter_service_standard.py +445 -0
  237. htmlgraph/repositories/shared_cache.py +621 -0
  238. htmlgraph/repositories/shared_cache_memory.py +395 -0
  239. htmlgraph/repositories/track_repository.py +552 -0
  240. htmlgraph/repositories/track_repository_htmlfile.py +619 -0
  241. htmlgraph/repositories/track_repository_memory.py +508 -0
  242. htmlgraph/repositories/track_repository_sqlite.py +711 -0
  243. htmlgraph/sdk/__init__.py +398 -0
  244. htmlgraph/sdk/__init__.pyi +14 -0
  245. htmlgraph/sdk/analytics/__init__.py +19 -0
  246. htmlgraph/sdk/analytics/engine.py +155 -0
  247. htmlgraph/sdk/analytics/helpers.py +178 -0
  248. htmlgraph/sdk/analytics/registry.py +109 -0
  249. htmlgraph/sdk/base.py +484 -0
  250. htmlgraph/sdk/constants.py +216 -0
  251. htmlgraph/sdk/core.pyi +308 -0
  252. htmlgraph/sdk/discovery.py +120 -0
  253. htmlgraph/sdk/help/__init__.py +12 -0
  254. htmlgraph/sdk/help/mixin.py +699 -0
  255. htmlgraph/sdk/mixins/__init__.py +15 -0
  256. htmlgraph/sdk/mixins/attribution.py +113 -0
  257. htmlgraph/sdk/mixins/mixin.py +410 -0
  258. htmlgraph/sdk/operations/__init__.py +12 -0
  259. htmlgraph/sdk/operations/mixin.py +427 -0
  260. htmlgraph/sdk/orchestration/__init__.py +17 -0
  261. htmlgraph/sdk/orchestration/coordinator.py +203 -0
  262. htmlgraph/sdk/orchestration/spawner.py +204 -0
  263. htmlgraph/sdk/planning/__init__.py +19 -0
  264. htmlgraph/sdk/planning/bottlenecks.py +93 -0
  265. htmlgraph/sdk/planning/mixin.py +211 -0
  266. htmlgraph/sdk/planning/parallel.py +186 -0
  267. htmlgraph/sdk/planning/queue.py +210 -0
  268. htmlgraph/sdk/planning/recommendations.py +87 -0
  269. htmlgraph/sdk/planning/smart_planning.py +319 -0
  270. htmlgraph/sdk/session/__init__.py +19 -0
  271. htmlgraph/sdk/session/continuity.py +57 -0
  272. htmlgraph/sdk/session/handoff.py +110 -0
  273. htmlgraph/sdk/session/info.py +309 -0
  274. htmlgraph/sdk/session/manager.py +103 -0
  275. htmlgraph/sdk/strategic/__init__.py +26 -0
  276. htmlgraph/sdk/strategic/mixin.py +563 -0
  277. htmlgraph/server.py +295 -107
  278. htmlgraph/session_hooks.py +300 -0
  279. htmlgraph/session_manager.py +285 -3
  280. htmlgraph/session_registry.py +587 -0
  281. htmlgraph/session_state.py +436 -0
  282. htmlgraph/session_warning.py +2 -1
  283. htmlgraph/sessions/__init__.py +23 -0
  284. htmlgraph/sessions/handoff.py +756 -0
  285. htmlgraph/system_prompts.py +450 -0
  286. htmlgraph/templates/orchestration-view.html +350 -0
  287. htmlgraph/track_builder.py +33 -1
  288. htmlgraph/track_manager.py +38 -0
  289. htmlgraph/transcript.py +18 -5
  290. htmlgraph/validation.py +115 -0
  291. htmlgraph/watch.py +2 -1
  292. htmlgraph/work_type_utils.py +2 -1
  293. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2246 -248
  294. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +95 -64
  295. htmlgraph-0.27.5.dist-info/RECORD +337 -0
  296. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
  297. htmlgraph/cli.py +0 -4839
  298. htmlgraph/sdk.py +0 -2359
  299. htmlgraph-0.20.1.dist-info/RECORD +0 -118
  300. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
  301. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  302. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  303. {htmlgraph-0.20.1.data → htmlgraph-0.27.5.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  304. {htmlgraph-0.20.1.dist-info → htmlgraph-0.27.5.dist-info}/WHEEL +0 -0
htmlgraph/cli/main.py ADDED
@@ -0,0 +1,147 @@
1
+ from __future__ import annotations
2
+
3
+ """HtmlGraph CLI - Main entry point.
4
+
5
+ Entry point with argument parsing and command routing.
6
+ Keeps main() thin by delegating to command modules.
7
+ """
8
+
9
+
10
+ import argparse
11
+ import sys
12
+
13
+ from rich.console import Console
14
+
15
+ from htmlgraph.cli.constants import (
16
+ DEFAULT_GRAPH_DIR,
17
+ DEFAULT_OUTPUT_FORMAT,
18
+ OUTPUT_FORMATS,
19
+ )
20
+
21
+
22
+ def create_parser() -> argparse.ArgumentParser:
23
+ """Create and configure the argument parser.
24
+
25
+ Returns:
26
+ Configured ArgumentParser with all subcommands
27
+ """
28
+ parser = argparse.ArgumentParser(
29
+ description="HtmlGraph - HTML is All You Need",
30
+ formatter_class=argparse.RawDescriptionHelpFormatter,
31
+ epilog="""
32
+ Quick Start:
33
+ htmlgraph bootstrap # One-command setup (< 60 seconds)
34
+
35
+ Examples:
36
+ htmlgraph init # Initialize .htmlgraph in current dir
37
+ htmlgraph serve # Start server on port 8080
38
+ htmlgraph status # Show graph status
39
+ htmlgraph query "[data-status='todo']" # Query nodes
40
+
41
+ Session Management:
42
+ htmlgraph session start # Start a new session (auto-ID)
43
+ htmlgraph session end my-session # End a session
44
+ htmlgraph session list # List all sessions
45
+
46
+ Feature Management:
47
+ htmlgraph feature list # List all features
48
+ htmlgraph feature start feat-001 # Start working on a feature
49
+ htmlgraph feature complete feat-001 # Mark feature as done
50
+
51
+ Track Management:
52
+ htmlgraph track new "User Auth" # Create a new track
53
+ htmlgraph track list # List all tracks
54
+
55
+ Analytics:
56
+ htmlgraph analytics # Project-wide analytics
57
+ htmlgraph analytics --recent 10 # Analyze last 10 sessions
58
+
59
+ For more help: https://github.com/Shakes-tzd/htmlgraph
60
+ """,
61
+ )
62
+
63
+ # Global output control flags
64
+ parser.add_argument(
65
+ "--format",
66
+ choices=OUTPUT_FORMATS,
67
+ default=DEFAULT_OUTPUT_FORMAT,
68
+ help="Output format: text (default), json, or plain",
69
+ )
70
+ parser.add_argument(
71
+ "--quiet",
72
+ "-q",
73
+ action="store_true",
74
+ help="Suppress progress messages and non-essential output",
75
+ )
76
+ parser.add_argument(
77
+ "--verbose",
78
+ "-v",
79
+ action="count",
80
+ default=0,
81
+ help="Increase verbosity (can be used multiple times: -v, -vv, -vvv)",
82
+ )
83
+
84
+ subparsers = parser.add_subparsers(dest="command", help="Command to run")
85
+
86
+ # Import command registration functions
87
+ from htmlgraph.cli import analytics, core, work
88
+
89
+ # Register commands from each module
90
+ core.register_commands(subparsers)
91
+ work.register_commands(subparsers)
92
+ analytics.register_commands(subparsers)
93
+
94
+ return parser
95
+
96
+
97
+ def main() -> None:
98
+ """Main entry point for the CLI."""
99
+ parser = create_parser()
100
+ args = parser.parse_args()
101
+
102
+ # If no command specified, show help
103
+ if not args.command:
104
+ parser.print_help()
105
+ sys.exit(0)
106
+
107
+ # Get the command handler (set by register_commands via set_defaults)
108
+ if not hasattr(args, "func"):
109
+ parser.print_help()
110
+ sys.exit(1)
111
+
112
+ # Determine graph directory and agent
113
+ graph_dir = getattr(args, "graph_dir", DEFAULT_GRAPH_DIR)
114
+ agent = getattr(args, "agent", None)
115
+
116
+ # Get output format from args
117
+ output_format = args.format
118
+
119
+ # Execute the command
120
+ try:
121
+ # Create command instance from args
122
+ command = args.func(args)
123
+
124
+ # Run command with context
125
+ command.run(
126
+ graph_dir=graph_dir,
127
+ agent=agent,
128
+ output_format=output_format,
129
+ )
130
+ except KeyboardInterrupt:
131
+ err_console = Console(stderr=True)
132
+ err_console.print("\n\n[yellow]Interrupted by user[/yellow]")
133
+ sys.exit(130)
134
+ except Exception as e:
135
+ from htmlgraph.cli.base import save_traceback
136
+
137
+ err_console = Console(stderr=True)
138
+ log_file = save_traceback(
139
+ e, context={"command": args.command, "cwd": graph_dir}
140
+ )
141
+ err_console.print(f"[red]Error:[/red] {e}")
142
+ err_console.print(f"[dim]Full traceback saved to:[/dim] {log_file}")
143
+ sys.exit(1)
144
+
145
+
146
+ if __name__ == "__main__":
147
+ main()
@@ -0,0 +1,475 @@
1
+ from __future__ import annotations
2
+
3
+ """HtmlGraph CLI - Pydantic models for command filters and configuration.
4
+
5
+ This module provides type-safe models for validating command inputs:
6
+ - Filter models for list commands (features, sessions, tracks)
7
+ - Configuration models for infrastructure commands (init, serve)
8
+ """
9
+
10
+
11
+ from datetime import datetime
12
+ from typing import Literal
13
+
14
+ from pydantic import BaseModel, Field, field_validator
15
+
16
+ # ============================================================================
17
+ # Filter Models
18
+ # ============================================================================
19
+
20
+
21
+ class FeatureFilter(BaseModel):
22
+ """Filter options for feature listing.
23
+
24
+ Attributes:
25
+ status: Filter by status (todo, in_progress, completed, blocked, all)
26
+ priority: Filter by priority (high, medium, low, critical, all)
27
+ agent: Filter by agent name
28
+ collection: Collection name to query (default: features)
29
+ """
30
+
31
+ status: Literal["todo", "in_progress", "completed", "blocked", "all"] | None = None
32
+ priority: Literal["high", "medium", "low", "critical", "all"] | None = None
33
+ agent: str | None = None
34
+ collection: str = Field(default="features")
35
+
36
+ @field_validator("status")
37
+ @classmethod
38
+ def validate_status(cls, v: str | None) -> str | None:
39
+ """Validate status value."""
40
+ if v and v not in ["todo", "in_progress", "completed", "blocked", "all"]:
41
+ raise ValueError(
42
+ f"Invalid status: {v}. "
43
+ f"Valid values: todo, in_progress, completed, blocked, all"
44
+ )
45
+ return v
46
+
47
+ @field_validator("priority")
48
+ @classmethod
49
+ def validate_priority(cls, v: str | None) -> str | None:
50
+ """Validate priority value."""
51
+ if v and v not in ["high", "medium", "low", "critical", "all"]:
52
+ raise ValueError(
53
+ f"Invalid priority: {v}. Valid values: high, medium, low, critical, all"
54
+ )
55
+ return v
56
+
57
+
58
+ class SessionFilter(BaseModel):
59
+ """Filter options for session listing.
60
+
61
+ Attributes:
62
+ status: Filter by status (active, ended, all)
63
+ agent: Filter by agent name
64
+ since: Only show sessions since this date
65
+ """
66
+
67
+ status: Literal["active", "ended", "all"] | None = None
68
+ agent: str | None = None
69
+ since: datetime | None = None
70
+
71
+ @field_validator("status")
72
+ @classmethod
73
+ def validate_status(cls, v: str | None) -> str | None:
74
+ """Validate status value."""
75
+ if v and v not in ["active", "ended", "all"]:
76
+ raise ValueError(f"Invalid status: {v}. Valid values: active, ended, all")
77
+ return v
78
+
79
+
80
+ class TrackFilter(BaseModel):
81
+ """Filter options for track listing.
82
+
83
+ Attributes:
84
+ status: Filter by status (todo, in_progress, completed, all)
85
+ priority: Filter by priority (high, medium, low, all)
86
+ has_spec: Filter for tracks with specs
87
+ has_plan: Filter for tracks with plans
88
+ """
89
+
90
+ status: Literal["todo", "in_progress", "completed", "all"] | None = None
91
+ priority: Literal["high", "medium", "low", "all"] | None = None
92
+ has_spec: bool | None = None
93
+ has_plan: bool | None = None
94
+
95
+ @field_validator("status")
96
+ @classmethod
97
+ def validate_status(cls, v: str | None) -> str | None:
98
+ """Validate status value."""
99
+ if v and v not in ["todo", "in_progress", "completed", "all"]:
100
+ raise ValueError(
101
+ f"Invalid status: {v}. Valid values: todo, in_progress, completed, all"
102
+ )
103
+ return v
104
+
105
+ @field_validator("priority")
106
+ @classmethod
107
+ def validate_priority(cls, v: str | None) -> str | None:
108
+ """Validate priority value."""
109
+ if v and v not in ["high", "medium", "low", "all"]:
110
+ raise ValueError(
111
+ f"Invalid priority: {v}. Valid values: high, medium, low, all"
112
+ )
113
+ return v
114
+
115
+
116
+ # ============================================================================
117
+ # Configuration Models
118
+ # ============================================================================
119
+
120
+
121
+ class InitConfig(BaseModel):
122
+ """Configuration for htmlgraph init command.
123
+
124
+ Attributes:
125
+ dir: Directory to initialize (default: .)
126
+ install_hooks: Install Git hooks for event logging
127
+ interactive: Interactive setup wizard
128
+ no_index: Do not create the analytics cache (index.sqlite)
129
+ no_update_gitignore: Do not update/create .gitignore for cache files
130
+ no_events_keep: Do not create .htmlgraph/events/.gitkeep
131
+ """
132
+
133
+ dir: str = Field(default=".")
134
+ install_hooks: bool = Field(default=False)
135
+ interactive: bool = Field(default=False)
136
+ no_index: bool = Field(default=False)
137
+ no_update_gitignore: bool = Field(default=False)
138
+ no_events_keep: bool = Field(default=False)
139
+
140
+
141
+ class ServeConfig(BaseModel):
142
+ """Configuration for htmlgraph serve command.
143
+
144
+ Attributes:
145
+ port: Port to bind to (must be between 1024-65535)
146
+ host: Host to bind to (default: 0.0.0.0)
147
+ graph_dir: Graph directory path
148
+ static_dir: Static files directory
149
+ no_watch: Disable file watching (auto-reload disabled)
150
+ auto_port: Automatically find available port if default is occupied
151
+ """
152
+
153
+ port: int = Field(default=8080, ge=1024, le=65535)
154
+ host: str = Field(default="0.0.0.0")
155
+ graph_dir: str = Field(default=".htmlgraph")
156
+ static_dir: str = Field(default=".")
157
+ no_watch: bool = Field(default=False)
158
+ auto_port: bool = Field(default=False)
159
+
160
+ @field_validator("port")
161
+ @classmethod
162
+ def validate_port(cls, v: int) -> int:
163
+ """Validate port is in valid range."""
164
+ if not 1024 <= v <= 65535:
165
+ raise ValueError(f"Port must be between 1024 and 65535, got {v}")
166
+ return v
167
+
168
+ @field_validator("host")
169
+ @classmethod
170
+ def validate_host(cls, v: str) -> str:
171
+ """Validate host is not empty."""
172
+ if not v or not v.strip():
173
+ raise ValueError("Host cannot be empty")
174
+ return v.strip()
175
+
176
+
177
+ class ServeApiConfig(BaseModel):
178
+ """Configuration for htmlgraph serve-api command.
179
+
180
+ Attributes:
181
+ port: Port to bind to (must be between 1024-65535)
182
+ host: Host to bind to (default: 127.0.0.1)
183
+ db: Path to SQLite database file
184
+ auto_port: Automatically find available port if default is occupied
185
+ reload: Enable auto-reload on file changes (development mode)
186
+ """
187
+
188
+ port: int = Field(default=8000, ge=1024, le=65535)
189
+ host: str = Field(default="127.0.0.1")
190
+ db: str | None = None
191
+ auto_port: bool = Field(default=False)
192
+ reload: bool = Field(default=False)
193
+
194
+ @field_validator("port")
195
+ @classmethod
196
+ def validate_port(cls, v: int) -> int:
197
+ """Validate port is in valid range."""
198
+ if not 1024 <= v <= 65535:
199
+ raise ValueError(f"Port must be between 1024 and 65535, got {v}")
200
+ return v
201
+
202
+ @field_validator("host")
203
+ @classmethod
204
+ def validate_host(cls, v: str) -> str:
205
+ """Validate host is not empty."""
206
+ if not v or not v.strip():
207
+ raise ValueError("Host cannot be empty")
208
+ return v.strip()
209
+
210
+
211
+ # ============================================================================
212
+ # Result Models
213
+ # ============================================================================
214
+
215
+
216
+ class InitResult(BaseModel):
217
+ """Result from htmlgraph init command.
218
+
219
+ Attributes:
220
+ success: Whether initialization succeeded
221
+ graph_dir: Path to initialized .htmlgraph directory
222
+ directories_created: List of directories created
223
+ files_created: List of files created
224
+ hooks_installed: Whether Git hooks were installed
225
+ warnings: List of warning messages
226
+ errors: List of error messages
227
+ """
228
+
229
+ success: bool = Field(default=True)
230
+ graph_dir: str = Field(...)
231
+ directories_created: list[str] = Field(default_factory=list)
232
+ files_created: list[str] = Field(default_factory=list)
233
+ hooks_installed: bool = Field(default=False)
234
+ warnings: list[str] = Field(default_factory=list)
235
+ errors: list[str] = Field(default_factory=list)
236
+
237
+ @property
238
+ def summary(self) -> str:
239
+ """Human-readable summary of initialization."""
240
+ lines = [f"Initialized {self.graph_dir}"]
241
+ if self.directories_created:
242
+ lines.append(f" • Created {len(self.directories_created)} directories")
243
+ if self.files_created:
244
+ lines.append(f" • Created {len(self.files_created)} files")
245
+ if self.hooks_installed:
246
+ lines.append(" • Installed Git hooks")
247
+ if self.warnings:
248
+ lines.append(f" • {len(self.warnings)} warnings")
249
+ if self.errors:
250
+ lines.append(f" • {len(self.errors)} errors")
251
+ return "\n".join(lines)
252
+
253
+
254
+ class ValidationResult(BaseModel):
255
+ """Result from directory validation.
256
+
257
+ Attributes:
258
+ valid: Whether directory is valid for initialization
259
+ exists: Whether directory already exists
260
+ is_initialized: Whether directory is already initialized
261
+ has_git: Whether directory is in a Git repository
262
+ errors: List of validation errors
263
+ """
264
+
265
+ valid: bool = Field(default=True)
266
+ exists: bool = Field(default=False)
267
+ is_initialized: bool = Field(default=False)
268
+ has_git: bool = Field(default=False)
269
+ errors: list[str] = Field(default_factory=list)
270
+
271
+
272
+ # ============================================================================
273
+ # Display Models
274
+ # ============================================================================
275
+
276
+
277
+ class SessionDisplay(BaseModel):
278
+ """Validated session data for CLI display."""
279
+
280
+ id: str = Field(..., description="Session identifier")
281
+ status: str = Field(default="active", description="Session status")
282
+ agent: str = Field(default="unknown", description="Agent name")
283
+ event_count: int = Field(default=0, ge=0, description="Number of events")
284
+ started_at: datetime = Field(..., description="Session start time")
285
+ ended_at: datetime | None = Field(None, description="Session end time")
286
+ title: str | None = Field(None, description="Session title")
287
+
288
+ @classmethod
289
+ def from_node(cls, node: object) -> SessionDisplay:
290
+ """
291
+ Create SessionDisplay from graph node.
292
+
293
+ Args:
294
+ node: Session node from graph
295
+
296
+ Returns:
297
+ SessionDisplay instance
298
+ """
299
+ return cls(
300
+ id=getattr(node, "id"),
301
+ status=getattr(node, "status", "active"),
302
+ agent=getattr(node, "agent", "unknown"),
303
+ event_count=getattr(node, "event_count", 0),
304
+ started_at=getattr(node, "started_at"),
305
+ ended_at=getattr(node, "ended_at", None),
306
+ title=getattr(node, "title", None),
307
+ )
308
+
309
+ @property
310
+ def started_str(self) -> str:
311
+ """Formatted start time for display."""
312
+ return self.started_at.strftime("%Y-%m-%d %H:%M")
313
+
314
+ @property
315
+ def ended_str(self) -> str | None:
316
+ """Formatted end time for display."""
317
+ if self.ended_at:
318
+ return self.ended_at.strftime("%Y-%m-%d %H:%M")
319
+ return None
320
+
321
+ def sort_key(self) -> datetime:
322
+ """
323
+ Return sort key for session ordering.
324
+
325
+ Returns timezone-naive datetime for consistent sorting.
326
+ """
327
+ ts = self.started_at
328
+ if ts.tzinfo is None:
329
+ return ts
330
+ return ts.replace(tzinfo=None)
331
+
332
+
333
+ class FeatureDisplay(BaseModel):
334
+ """Validated feature data for CLI display."""
335
+
336
+ id: str = Field(..., description="Feature identifier")
337
+ title: str = Field(default="Untitled", description="Feature title")
338
+ status: str = Field(default="unknown", description="Feature status")
339
+ priority: Literal["low", "medium", "high", "critical"] = Field(
340
+ default="medium", description="Feature priority"
341
+ )
342
+ updated: datetime = Field(..., description="Last update time")
343
+ created: datetime | None = Field(None, description="Creation time")
344
+ track_id: str | None = Field(None, description="Associated track ID")
345
+ agent_assigned: str | None = Field(None, description="Assigned agent")
346
+
347
+ @classmethod
348
+ def from_node(cls, node: object) -> FeatureDisplay:
349
+ """
350
+ Create FeatureDisplay from graph node.
351
+
352
+ Args:
353
+ node: Feature node from graph
354
+
355
+ Returns:
356
+ FeatureDisplay instance
357
+ """
358
+ return cls(
359
+ id=getattr(node, "id"),
360
+ title=getattr(node, "title", "Untitled"),
361
+ status=getattr(node, "status", "unknown"),
362
+ priority=getattr(node, "priority", "medium"),
363
+ updated=getattr(node, "updated"),
364
+ created=getattr(node, "created", None),
365
+ track_id=getattr(node, "track_id", None),
366
+ agent_assigned=getattr(node, "agent_assigned", None),
367
+ )
368
+
369
+ @property
370
+ def updated_str(self) -> str:
371
+ """Formatted update time for display."""
372
+ return self.updated.strftime("%Y-%m-%d %H:%M")
373
+
374
+ @property
375
+ def created_str(self) -> str | None:
376
+ """Formatted creation time for display."""
377
+ if self.created:
378
+ return self.created.strftime("%Y-%m-%d %H:%M")
379
+ return None
380
+
381
+ def sort_key(self) -> tuple[int, datetime]:
382
+ """
383
+ Return sort key for feature ordering (priority, then updated time).
384
+
385
+ Returns:
386
+ Tuple of (priority_rank, timezone_naive_updated_time)
387
+ """
388
+ priority_order = {"critical": 0, "high": 1, "medium": 2, "low": 3}
389
+ priority_rank = priority_order.get(self.priority, 99)
390
+
391
+ updated_ts = self.updated
392
+ if updated_ts.tzinfo is None:
393
+ updated_naive = updated_ts
394
+ else:
395
+ updated_naive = updated_ts.replace(tzinfo=None)
396
+
397
+ return (priority_rank, updated_naive)
398
+
399
+
400
+ class TrackDisplay(BaseModel):
401
+ """Validated track data for CLI display."""
402
+
403
+ id: str = Field(..., description="Track identifier")
404
+ title: str = Field(default="Untitled", description="Track title")
405
+ status: str = Field(default="planning", description="Track status")
406
+ priority: Literal["low", "medium", "high"] = Field(
407
+ default="medium", description="Track priority"
408
+ )
409
+ has_spec: bool = Field(default=False, description="Has specification")
410
+ has_plan: bool = Field(default=False, description="Has plan")
411
+ format_type: str = Field(
412
+ default="consolidated", description="File format (consolidated or directory)"
413
+ )
414
+ feature_count: int = Field(default=0, ge=0, description="Number of features")
415
+
416
+ @classmethod
417
+ def from_track_id(
418
+ cls,
419
+ track_id: str,
420
+ has_spec: bool = False,
421
+ has_plan: bool = False,
422
+ format_type: str = "consolidated",
423
+ ) -> TrackDisplay:
424
+ """
425
+ Create TrackDisplay from track ID and metadata.
426
+
427
+ Args:
428
+ track_id: Track identifier
429
+ has_spec: Whether track has specification
430
+ has_plan: Whether track has plan
431
+ format_type: File format type
432
+
433
+ Returns:
434
+ TrackDisplay instance
435
+ """
436
+ return cls(
437
+ id=track_id,
438
+ title=track_id, # Default to ID if title not available
439
+ has_spec=has_spec,
440
+ has_plan=has_plan,
441
+ format_type=format_type,
442
+ )
443
+
444
+ @property
445
+ def components_str(self) -> str:
446
+ """Formatted components list for display."""
447
+ components = []
448
+ if self.has_spec:
449
+ components.append("spec")
450
+ if self.has_plan:
451
+ components.append("plan")
452
+ return ", ".join(components) if components else "empty"
453
+
454
+ def sort_key(self) -> tuple[int, str]:
455
+ """
456
+ Return sort key for track ordering (priority, then ID).
457
+
458
+ Returns:
459
+ Tuple of (priority_rank, track_id)
460
+ """
461
+ priority_order = {"high": 0, "medium": 1, "low": 2}
462
+ priority_rank = priority_order.get(self.priority, 99)
463
+ return (priority_rank, self.id)
464
+
465
+
466
+ class BootstrapConfig(BaseModel):
467
+ """Configuration for htmlgraph bootstrap command.
468
+
469
+ Attributes:
470
+ project_path: Directory to bootstrap (default: .)
471
+ no_plugins: Skip plugin installation
472
+ """
473
+
474
+ project_path: str = Field(default=".")
475
+ no_plugins: bool = Field(default=False)
@@ -0,0 +1 @@
1
+ """HTML templates for CLI output."""