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/core.py ADDED
@@ -0,0 +1,954 @@
1
+ from __future__ import annotations
2
+
3
+ """HtmlGraph CLI - Infrastructure commands.
4
+
5
+ Commands for core infrastructure operations:
6
+ - serve: Start FastAPI server
7
+ - serve-api: Start API dashboard
8
+ - init: Initialize .htmlgraph directory
9
+ - status: Show graph status
10
+ - query: CSS selector query
11
+ - debug: Debug mode
12
+ - install-hooks: Install Git hooks
13
+ - Other utilities
14
+ """
15
+
16
+
17
+ import argparse
18
+ import sys
19
+ from typing import TYPE_CHECKING
20
+
21
+ from htmlgraph.cli.base import BaseCommand, CommandError, CommandResult
22
+ from htmlgraph.cli.constants import (
23
+ COLLECTIONS,
24
+ DEFAULT_DATABASE_NAME,
25
+ DEFAULT_GRAPH_DIR,
26
+ DEFAULT_SERVER_HOST,
27
+ DEFAULT_SERVER_PORT,
28
+ get_error_message,
29
+ )
30
+
31
+ if TYPE_CHECKING:
32
+ from argparse import _SubParsersAction
33
+
34
+
35
+ def register_commands(subparsers: _SubParsersAction) -> None:
36
+ """Register infrastructure commands with the argument parser.
37
+
38
+ Args:
39
+ subparsers: Subparser action from ArgumentParser.add_subparsers()
40
+ """
41
+ # bootstrap
42
+ bootstrap_parser = subparsers.add_parser(
43
+ "bootstrap", help="One-command setup: Initialize HtmlGraph in under 60 seconds"
44
+ )
45
+ bootstrap_parser.add_argument(
46
+ "--project-path",
47
+ default=".",
48
+ help="Directory to bootstrap (default: current directory)",
49
+ )
50
+ bootstrap_parser.add_argument(
51
+ "--no-plugins",
52
+ action="store_true",
53
+ help="Skip plugin installation",
54
+ )
55
+ bootstrap_parser.set_defaults(func=BootstrapCommand.from_args)
56
+
57
+ # serve
58
+ serve_parser = subparsers.add_parser("serve", help="Start the HtmlGraph server")
59
+ serve_parser.add_argument(
60
+ "--port",
61
+ "-p",
62
+ type=int,
63
+ default=DEFAULT_SERVER_PORT,
64
+ help="Port (default: 8080)",
65
+ )
66
+ serve_parser.add_argument(
67
+ "--host", default=DEFAULT_SERVER_HOST, help="Host to bind to (default: 0.0.0.0)"
68
+ )
69
+ serve_parser.add_argument(
70
+ "--graph-dir", "-g", default=DEFAULT_GRAPH_DIR, help="Graph directory"
71
+ )
72
+ serve_parser.add_argument(
73
+ "--static-dir", "-s", default=".", help="Static files directory"
74
+ )
75
+ serve_parser.add_argument(
76
+ "--no-watch",
77
+ action="store_true",
78
+ help="Disable file watching (auto-reload disabled)",
79
+ )
80
+ serve_parser.add_argument(
81
+ "--auto-port",
82
+ action="store_true",
83
+ help="Automatically find an available port if default is occupied",
84
+ )
85
+ serve_parser.set_defaults(func=ServeCommand.from_args)
86
+
87
+ # serve-api
88
+ serve_api_parser = subparsers.add_parser(
89
+ "serve-api",
90
+ help="Start the FastAPI-based observability dashboard",
91
+ )
92
+ serve_api_parser.add_argument(
93
+ "--port", "-p", type=int, default=8000, help="Port (default: 8000)"
94
+ )
95
+ serve_api_parser.add_argument(
96
+ "--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)"
97
+ )
98
+ serve_api_parser.add_argument(
99
+ "--db", default=None, help="Path to SQLite database file"
100
+ )
101
+ serve_api_parser.add_argument(
102
+ "--auto-port",
103
+ action="store_true",
104
+ help="Automatically find an available port if default is occupied",
105
+ )
106
+ serve_api_parser.add_argument(
107
+ "--reload",
108
+ action="store_true",
109
+ help="Enable auto-reload on file changes (development mode)",
110
+ )
111
+ serve_api_parser.set_defaults(func=ServeApiCommand.from_args)
112
+
113
+ # init
114
+ init_parser = subparsers.add_parser("init", help="Initialize .htmlgraph directory")
115
+ init_parser.add_argument(
116
+ "dir", nargs="?", default=".", help="Directory to initialize"
117
+ )
118
+ init_parser.add_argument(
119
+ "--install-hooks",
120
+ action="store_true",
121
+ help="Install Git hooks for event logging",
122
+ )
123
+ init_parser.add_argument(
124
+ "--interactive", "-i", action="store_true", help="Interactive setup wizard"
125
+ )
126
+ init_parser.add_argument(
127
+ "--no-index",
128
+ action="store_true",
129
+ help="Do not create the analytics cache (index.sqlite)",
130
+ )
131
+ init_parser.add_argument(
132
+ "--no-update-gitignore",
133
+ action="store_true",
134
+ help="Do not update/create .gitignore for HtmlGraph cache files",
135
+ )
136
+ init_parser.add_argument(
137
+ "--no-events-keep",
138
+ action="store_true",
139
+ help="Do not create .htmlgraph/events/.gitkeep",
140
+ )
141
+ init_parser.set_defaults(func=InitCommand.from_args)
142
+
143
+ # status
144
+ status_parser = subparsers.add_parser("status", help="Show graph status")
145
+ status_parser.add_argument(
146
+ "--graph-dir", "-g", default=DEFAULT_GRAPH_DIR, help="Graph directory"
147
+ )
148
+ status_parser.set_defaults(func=StatusCommand.from_args)
149
+
150
+ # debug
151
+ debug_parser = subparsers.add_parser(
152
+ "debug", help="Show debugging resources and system diagnostics"
153
+ )
154
+ debug_parser.add_argument(
155
+ "--graph-dir", "-g", default=DEFAULT_GRAPH_DIR, help="Graph directory"
156
+ )
157
+ debug_parser.set_defaults(func=DebugCommand.from_args)
158
+
159
+ # query
160
+ query_parser = subparsers.add_parser("query", help="Query nodes with CSS selector")
161
+ query_parser.add_argument(
162
+ "selector", help="CSS selector (e.g. [data-status='todo'])"
163
+ )
164
+ query_parser.add_argument(
165
+ "--graph-dir", "-g", default=DEFAULT_GRAPH_DIR, help="Graph directory"
166
+ )
167
+ query_parser.set_defaults(func=QueryCommand.from_args)
168
+
169
+ # install-hooks
170
+ install_hooks_parser = subparsers.add_parser(
171
+ "install-hooks", help="Install Git hooks for event logging"
172
+ )
173
+ install_hooks_parser.add_argument(
174
+ "--graph-dir", "-g", default=DEFAULT_GRAPH_DIR, help="Graph directory"
175
+ )
176
+ install_hooks_parser.add_argument(
177
+ "--force",
178
+ action="store_true",
179
+ help="Force installation, overwriting existing hooks",
180
+ )
181
+ install_hooks_parser.add_argument(
182
+ "--dry-run",
183
+ action="store_true",
184
+ help="Show what would be installed without making changes",
185
+ )
186
+ install_hooks_parser.set_defaults(func=InstallHooksCommand.from_args)
187
+
188
+
189
+ # ============================================================================
190
+ # Command Implementations
191
+ # ============================================================================
192
+
193
+
194
+ class ServeCommand(BaseCommand):
195
+ """Start the HtmlGraph server."""
196
+
197
+ def __init__(
198
+ self,
199
+ *,
200
+ port: int,
201
+ host: str,
202
+ static_dir: str,
203
+ no_watch: bool,
204
+ auto_port: bool,
205
+ ) -> None:
206
+ super().__init__()
207
+ self.port = port
208
+ self.host = host
209
+ self.static_dir = static_dir
210
+ self.no_watch = no_watch
211
+ self.auto_port = auto_port
212
+
213
+ @classmethod
214
+ def from_args(cls, args: argparse.Namespace) -> ServeCommand:
215
+ return cls(
216
+ port=args.port,
217
+ host=args.host,
218
+ static_dir=args.static_dir,
219
+ no_watch=args.no_watch,
220
+ auto_port=args.auto_port,
221
+ )
222
+
223
+ def execute(self) -> CommandResult:
224
+ """Start the FastAPI server."""
225
+ import asyncio
226
+ from pathlib import Path
227
+
228
+ from rich.console import Console
229
+ from rich.panel import Panel
230
+
231
+ from htmlgraph.operations.fastapi_server import (
232
+ run_fastapi_server,
233
+ start_fastapi_server,
234
+ )
235
+
236
+ console = Console()
237
+
238
+ try:
239
+ # Default to database in graph dir if not specified
240
+ db_path = str(
241
+ Path(self.graph_dir or DEFAULT_GRAPH_DIR) / DEFAULT_DATABASE_NAME
242
+ )
243
+
244
+ result = start_fastapi_server(
245
+ port=self.port,
246
+ host=self.host,
247
+ db_path=db_path,
248
+ auto_port=self.auto_port,
249
+ reload=False, # Not supported for cmd_serve
250
+ )
251
+
252
+ # Display server info using Rich
253
+ console.print()
254
+ console.print(
255
+ Panel.fit(
256
+ f"[bold blue]{result.handle.url}[/bold blue]",
257
+ title="[bold cyan]HtmlGraph Server (FastAPI)[/bold cyan]",
258
+ border_style="cyan",
259
+ )
260
+ )
261
+
262
+ console.print(
263
+ f"[dim]Graph directory:[/dim] {self.graph_dir or DEFAULT_GRAPH_DIR}"
264
+ )
265
+ console.print(f"[dim]Database:[/dim] {result.config_used['db_path']}")
266
+
267
+ # Show warnings if any
268
+ if result.warnings:
269
+ console.print()
270
+ for warning in result.warnings:
271
+ console.print(f"[yellow]⚠️ {warning}[/yellow]")
272
+
273
+ # Show available features
274
+ console.print()
275
+ console.print("[cyan]Features:[/cyan]")
276
+ console.print(" • Real-time agent activity feed (HTMX)")
277
+ console.print(" • Orchestration chains visualization")
278
+ console.print(" • Feature tracker with Kanban view")
279
+ console.print(" • Session metrics & performance analytics")
280
+
281
+ console.print()
282
+ console.print("[cyan]Press Ctrl+C to stop.[/cyan]")
283
+ console.print()
284
+
285
+ # Run server (blocking)
286
+ asyncio.run(run_fastapi_server(result.handle))
287
+
288
+ except KeyboardInterrupt:
289
+ console.print("\n[yellow]Shutting down...[/yellow]")
290
+ except Exception as e:
291
+ from htmlgraph.cli.base import save_traceback
292
+
293
+ log_file = save_traceback(
294
+ e, context={"command": "serve", "port": self.port}
295
+ )
296
+ console.print(f"\n[red]Error:[/red] {e}")
297
+ console.print(f"[dim]Full traceback saved to:[/dim] {log_file}")
298
+ sys.exit(1)
299
+
300
+ return CommandResult(text="Server stopped")
301
+
302
+
303
+ class ServeApiCommand(BaseCommand):
304
+ """Start the FastAPI-based dashboard."""
305
+
306
+ def __init__(
307
+ self,
308
+ *,
309
+ port: int,
310
+ host: str,
311
+ db: str | None,
312
+ auto_port: bool,
313
+ reload: bool,
314
+ ) -> None:
315
+ super().__init__()
316
+ self.port = port
317
+ self.host = host
318
+ self.db = db
319
+ self.auto_port = auto_port
320
+ self.reload = reload
321
+
322
+ @classmethod
323
+ def from_args(cls, args: argparse.Namespace) -> ServeApiCommand:
324
+ return cls(
325
+ port=args.port,
326
+ host=args.host,
327
+ db=args.db,
328
+ auto_port=args.auto_port,
329
+ reload=args.reload,
330
+ )
331
+
332
+ def execute(self) -> CommandResult:
333
+ """Start the FastAPI dashboard server."""
334
+ import asyncio
335
+
336
+ from rich.console import Console
337
+ from rich.panel import Panel
338
+
339
+ from htmlgraph.operations.fastapi_server import (
340
+ run_fastapi_server,
341
+ start_fastapi_server,
342
+ )
343
+
344
+ console = Console()
345
+
346
+ try:
347
+ result = start_fastapi_server(
348
+ port=self.port,
349
+ host=self.host,
350
+ db_path=self.db,
351
+ auto_port=self.auto_port,
352
+ reload=self.reload,
353
+ )
354
+
355
+ # Display server info using Rich
356
+ console.print()
357
+ console.print(
358
+ Panel.fit(
359
+ f"[bold blue]{result.handle.url}[/bold blue]",
360
+ title="[bold cyan]HtmlGraph FastAPI Dashboard[/bold cyan]",
361
+ border_style="green",
362
+ )
363
+ )
364
+
365
+ console.print("[bold green]✓[/bold green] Started observability dashboard")
366
+ console.print(f"[dim]Database:[/dim] {result.config_used['db_path']}")
367
+
368
+ # Show warnings if any
369
+ if result.warnings:
370
+ console.print()
371
+ for warning in result.warnings:
372
+ console.print(f"[yellow]⚠️ {warning}[/yellow]")
373
+
374
+ # Show available features
375
+ console.print()
376
+ console.print("[cyan]Features:[/cyan]")
377
+ console.print(" • Real-time agent activity feed")
378
+ console.print(" • Orchestration chains visualization")
379
+ console.print(" • Feature tracker with Kanban view")
380
+ console.print(" • Session metrics & performance analytics")
381
+ console.print(" • WebSocket live event streaming")
382
+
383
+ console.print()
384
+ console.print("[cyan]Press Ctrl+C to stop.[/cyan]")
385
+ console.print()
386
+
387
+ # Run server (blocking)
388
+ asyncio.run(run_fastapi_server(result.handle))
389
+
390
+ except KeyboardInterrupt:
391
+ console.print("\n[yellow]Shutting down...[/yellow]")
392
+ except Exception as e:
393
+ from htmlgraph.cli.base import save_traceback
394
+
395
+ log_file = save_traceback(e, context={"command": "serve-api"})
396
+ console.print(f"\n[red]Error:[/red] {e}")
397
+ console.print(f"[dim]Full traceback saved to:[/dim] {log_file}")
398
+ sys.exit(1)
399
+
400
+ return CommandResult(text="Dashboard stopped")
401
+
402
+
403
+ class InitCommand(BaseCommand):
404
+ """Initialize .htmlgraph directory."""
405
+
406
+ def __init__(
407
+ self,
408
+ *,
409
+ dir: str,
410
+ install_hooks: bool,
411
+ interactive: bool,
412
+ no_index: bool,
413
+ no_update_gitignore: bool,
414
+ no_events_keep: bool,
415
+ ) -> None:
416
+ super().__init__()
417
+ self.dir = dir
418
+ self.install_hooks = install_hooks
419
+ self.interactive = interactive
420
+ self.no_index = no_index
421
+ self.no_update_gitignore = no_update_gitignore
422
+ self.no_events_keep = no_events_keep
423
+
424
+ @classmethod
425
+ def from_args(cls, args: argparse.Namespace) -> InitCommand:
426
+ return cls(
427
+ dir=args.dir,
428
+ install_hooks=args.install_hooks,
429
+ interactive=args.interactive,
430
+ no_index=args.no_index,
431
+ no_update_gitignore=args.no_update_gitignore,
432
+ no_events_keep=args.no_events_keep,
433
+ )
434
+
435
+ def execute(self) -> CommandResult:
436
+ """Initialize the .htmlgraph directory."""
437
+ from htmlgraph.cli.base import TextOutputBuilder
438
+ from htmlgraph.cli.models import InitConfig
439
+ from htmlgraph.operations.initialization import initialize_htmlgraph
440
+
441
+ # Create config from command parameters
442
+ config = InitConfig(
443
+ dir=self.dir,
444
+ install_hooks=self.install_hooks,
445
+ interactive=self.interactive,
446
+ no_index=self.no_index,
447
+ no_update_gitignore=self.no_update_gitignore,
448
+ no_events_keep=self.no_events_keep,
449
+ )
450
+
451
+ # Initialize using new module
452
+ result = initialize_htmlgraph(config)
453
+
454
+ # Return result
455
+ if result.success:
456
+ output = TextOutputBuilder()
457
+ output.add_success("Initialized .htmlgraph directory")
458
+ output.add_field("Location", result.graph_dir)
459
+
460
+ # Show what was created
461
+ if result.directories_created:
462
+ output.add_info(
463
+ f"Created {len(result.directories_created)} directories"
464
+ )
465
+ if result.files_created:
466
+ output.add_info(f"Created/updated {len(result.files_created)} files")
467
+ if result.hooks_installed:
468
+ output.add_info("Git hooks installed")
469
+
470
+ # Show any warnings
471
+ for warning in result.warnings:
472
+ output.add_warning(warning)
473
+
474
+ return CommandResult(text=output.build(), json_data=result.dict())
475
+ else:
476
+ # Build error message from all errors
477
+ error_msg = (
478
+ "\n".join(result.errors) if result.errors else "Initialization failed"
479
+ )
480
+ raise CommandError(error_msg)
481
+
482
+
483
+ class StatusCommand(BaseCommand):
484
+ """Show graph status."""
485
+
486
+ @classmethod
487
+ def from_args(cls, args: argparse.Namespace) -> StatusCommand:
488
+ return cls()
489
+
490
+ def execute(self) -> CommandResult:
491
+ """Show the current graph status."""
492
+ from collections import Counter
493
+
494
+ from rich.console import Console
495
+ from rich.progress import Progress, SpinnerColumn, TextColumn
496
+
497
+ console = Console()
498
+
499
+ # Initialize SDK
500
+ with console.status("[blue]Initializing SDK...", spinner="dots"):
501
+ sdk = self.get_sdk()
502
+
503
+ total = 0
504
+ by_status: Counter[str] = Counter()
505
+ by_collection: dict[str, int] = {}
506
+
507
+ # Scan all collections
508
+ with Progress(
509
+ SpinnerColumn(),
510
+ TextColumn("[progress.description]{task.description}"),
511
+ console=console,
512
+ transient=True,
513
+ ) as progress:
514
+ task = progress.add_task("Scanning collections...", total=len(COLLECTIONS))
515
+
516
+ for coll_name in COLLECTIONS:
517
+ progress.update(task, description=f"Scanning {coll_name}...")
518
+ try:
519
+ coll = getattr(sdk, coll_name)
520
+ nodes = coll.all()
521
+ count = len(nodes)
522
+
523
+ if count > 0:
524
+ by_collection[coll_name] = count
525
+ total += count
526
+
527
+ # Count by status
528
+ for node in nodes:
529
+ status = getattr(node, "status", "unknown")
530
+ by_status[status] += 1
531
+
532
+ except Exception:
533
+ # Collection might not exist yet
534
+ pass
535
+
536
+ progress.update(task, advance=1)
537
+
538
+ # Build status table
539
+ from htmlgraph.cli.base import TableBuilder
540
+
541
+ builder = TableBuilder.create_list_table(f"HtmlGraph Status: {self.graph_dir}")
542
+ builder.add_column("Collection", style="cyan")
543
+ builder.add_numeric_column("Count", style="green")
544
+
545
+ for coll_name in sorted(by_collection.keys()):
546
+ builder.add_row(coll_name, str(by_collection[coll_name]))
547
+
548
+ builder.add_separator()
549
+ builder.add_row("[bold]Total", f"[bold]{total}")
550
+ table = builder.table
551
+
552
+ # Display results
553
+ console.print()
554
+ console.print(table)
555
+
556
+ # Show status breakdown
557
+ if by_status:
558
+ console.print()
559
+ console.print("[cyan]By Status:[/cyan]")
560
+ for status, count in sorted(by_status.items()):
561
+ console.print(f" {status}: {count}")
562
+
563
+ return CommandResult(
564
+ data={
565
+ "total_nodes": total,
566
+ "by_collection": dict(sorted(by_collection.items())),
567
+ "by_status": dict(sorted(by_status.items())),
568
+ },
569
+ text=f"Total nodes: {total}",
570
+ )
571
+
572
+
573
+ class DebugCommand(BaseCommand):
574
+ """Show debugging resources."""
575
+
576
+ @classmethod
577
+ def from_args(cls, args: argparse.Namespace) -> DebugCommand:
578
+ return cls()
579
+
580
+ def execute(self) -> CommandResult:
581
+ """Show debugging resources and diagnostics."""
582
+ import os
583
+ import sys
584
+ from pathlib import Path
585
+
586
+ from rich.console import Console
587
+ from rich.panel import Panel
588
+
589
+ console = Console()
590
+
591
+ # Header
592
+ console.print()
593
+ console.print(
594
+ Panel.fit(
595
+ "[bold cyan]HtmlGraph Debugging Resources[/bold cyan]",
596
+ border_style="cyan",
597
+ )
598
+ )
599
+
600
+ # Documentation section
601
+ console.print("\n[bold yellow]Documentation:[/bold yellow]")
602
+ console.print(" • DEBUGGING.md - Complete debugging guide")
603
+ console.print(" • AGENTS.md - SDK and agent documentation")
604
+ console.print(" • CLAUDE.md - Project workflow")
605
+
606
+ # Debugging Agents section
607
+ console.print("\n[bold yellow]Debugging Agents:[/bold yellow]")
608
+ agents_dir = Path("packages/claude-plugin/agents")
609
+ if agents_dir.exists():
610
+ console.print(f" • {agents_dir}/researcher.md")
611
+ console.print(f" • {agents_dir}/debugger.md")
612
+ console.print(f" • {agents_dir}/test-runner.md")
613
+ else:
614
+ console.print(
615
+ " • researcher.md - Research documentation before implementing"
616
+ )
617
+ console.print(" • debugger.md - Systematic error analysis")
618
+ console.print(" • test-runner.md - Quality gates and validation")
619
+
620
+ # Diagnostic Commands section
621
+ from htmlgraph.cli.base import TableBuilder
622
+
623
+ console.print("\n[bold yellow]Diagnostic Commands:[/bold yellow]")
624
+ cmd_builder = TableBuilder.create_compact_table()
625
+ cmd_builder.add_column("Command", style="cyan")
626
+ cmd_builder.add_column("Description", style="dim")
627
+ cmd_builder.add_row("htmlgraph status", "Show current graph state")
628
+ cmd_builder.add_row("htmlgraph feature list", "List all features")
629
+ cmd_builder.add_row("htmlgraph session list", "List all sessions")
630
+ cmd_builder.add_row("htmlgraph analytics", "Project analytics")
631
+ console.print(cmd_builder.table)
632
+
633
+ # Current Status section
634
+ console.print("\n[bold yellow]Current Status:[/bold yellow]")
635
+ graph_path = Path(self.graph_dir or DEFAULT_GRAPH_DIR)
636
+
637
+ status_builder = TableBuilder.create_compact_table()
638
+ status_builder.add_column("Item", style="dim")
639
+ status_builder.add_column("Value")
640
+
641
+ status_builder.add_row("Graph directory:", str(graph_path))
642
+
643
+ if graph_path.exists():
644
+ status_builder.add_row("Status:", "[green]✓ Initialized[/green]")
645
+
646
+ # Try to get quick stats
647
+ try:
648
+ sdk = self.get_sdk()
649
+
650
+ # Count features
651
+ features = sdk.features.all()
652
+ status_builder.add_row("Features:", str(len(features)))
653
+
654
+ # Count sessions
655
+ sessions = sdk.sessions.all()
656
+ status_builder.add_row("Sessions:", str(len(sessions)))
657
+
658
+ # Count other collections
659
+ for coll_name in [
660
+ "bugs",
661
+ "chores",
662
+ "spikes",
663
+ "epics",
664
+ "phases",
665
+ "tracks",
666
+ ]:
667
+ try:
668
+ coll = getattr(sdk, coll_name)
669
+ nodes = coll.all()
670
+ if len(nodes) > 0:
671
+ status_builder.add_row(
672
+ f"{coll_name.capitalize()}:", str(len(nodes))
673
+ )
674
+ except Exception:
675
+ pass
676
+
677
+ except Exception as e:
678
+ status_builder.add_row(
679
+ "Warning:", f"[yellow]Could not load graph data: {e}[/yellow]"
680
+ )
681
+ else:
682
+ status_builder.add_row("Status:", "[yellow]⚠️ Not initialized[/yellow]")
683
+ status_builder.add_row(
684
+ "", "[dim]Run 'htmlgraph init' to create .htmlgraph directory[/dim]"
685
+ )
686
+
687
+ console.print(status_builder.table)
688
+
689
+ # Environment Info section
690
+ console.print("\n[bold yellow]Environment:[/bold yellow]")
691
+ env_builder = TableBuilder.create_compact_table()
692
+ env_builder.add_column("Item", style="dim")
693
+ env_builder.add_column("Value")
694
+ env_builder.add_row("Python:", sys.version.split()[0])
695
+ env_builder.add_row("Working dir:", os.getcwd())
696
+ console.print(env_builder.table)
697
+
698
+ # Project Files section
699
+ console.print("\n[bold yellow]Project Files:[/bold yellow]")
700
+ files_builder = TableBuilder.create_compact_table()
701
+ files_builder.add_column("Status", justify="center")
702
+ files_builder.add_column("File")
703
+ for filename in ["pyproject.toml", "package.json", ".git", "README.md"]:
704
+ exists = "[green]✓[/green]" if Path(filename).exists() else "[red]✗[/red]"
705
+ files_builder.add_row(exists, filename)
706
+ console.print(files_builder.table)
707
+
708
+ # Footer
709
+ console.print()
710
+ console.print(
711
+ "[dim]For more help: https://github.com/Shakes-tzd/htmlgraph[/dim]"
712
+ )
713
+ console.print()
714
+
715
+ return CommandResult(text="Debug info displayed")
716
+
717
+
718
+ class QueryCommand(BaseCommand):
719
+ """Query nodes with CSS selector."""
720
+
721
+ def __init__(self, *, selector: str) -> None:
722
+ super().__init__()
723
+ self.selector = selector
724
+
725
+ @classmethod
726
+ def from_args(cls, args: argparse.Namespace) -> QueryCommand:
727
+ return cls(selector=args.selector)
728
+
729
+ def execute(self) -> CommandResult:
730
+ """Execute CSS selector query."""
731
+ from pathlib import Path
732
+ from typing import Any
733
+
734
+ from rich.console import Console
735
+ from rich.table import Table
736
+
737
+ from htmlgraph.converter import node_to_dict
738
+ from htmlgraph.graph import HtmlGraph
739
+
740
+ console = Console()
741
+
742
+ graph_dir = Path(self.graph_dir or DEFAULT_GRAPH_DIR)
743
+ if not graph_dir.exists():
744
+ raise CommandError(
745
+ get_error_message("missing_graph_dir", path=str(graph_dir))
746
+ )
747
+
748
+ # Query across all collections
749
+ results: list[dict[str, Any]] = []
750
+
751
+ with console.status(
752
+ f"[blue]Querying with selector '{self.selector}'...", spinner="dots"
753
+ ):
754
+ for collection_dir in graph_dir.iterdir():
755
+ if collection_dir.is_dir() and not collection_dir.name.startswith("."):
756
+ graph = HtmlGraph(collection_dir, auto_load=True)
757
+ for node in graph.query(self.selector):
758
+ data = node_to_dict(node)
759
+ data["_collection"] = collection_dir.name
760
+ results.append(data)
761
+
762
+ # Display results in table
763
+ if results:
764
+ table = Table(
765
+ title=f"Query Results: {self.selector}",
766
+ show_header=True,
767
+ header_style="bold cyan",
768
+ )
769
+ table.add_column("Collection", style="dim")
770
+ table.add_column("ID", style="cyan")
771
+ table.add_column("Title", style="white")
772
+ table.add_column("Status", style="blue")
773
+ table.add_column("Priority", style="yellow")
774
+
775
+ for result in results:
776
+ table.add_row(
777
+ result.get("_collection", "?"),
778
+ result.get("id", "?"),
779
+ result.get("title", "?"),
780
+ result.get("status", "?"),
781
+ result.get("priority", "?"),
782
+ )
783
+
784
+ console.print()
785
+ console.print(table)
786
+ console.print(f"\n[green]Found {len(results)} results[/green]")
787
+ else:
788
+ console.print(f"\n[yellow]No results found for '{self.selector}'[/yellow]")
789
+
790
+ return CommandResult(data=results, text=f"Found {len(results)} results")
791
+
792
+
793
+ class InstallHooksCommand(BaseCommand):
794
+ """Install Git hooks for event logging."""
795
+
796
+ def __init__(self, *, force: bool = False, dry_run: bool = False) -> None:
797
+ super().__init__()
798
+ self.force = force
799
+ self.dry_run = dry_run
800
+
801
+ @classmethod
802
+ def from_args(cls, args: argparse.Namespace) -> InstallHooksCommand:
803
+ return cls(
804
+ force=getattr(args, "force", False),
805
+ dry_run=getattr(args, "dry_run", False),
806
+ )
807
+
808
+ def execute(self) -> CommandResult:
809
+ """Install Git hooks."""
810
+ from pathlib import Path
811
+
812
+ from rich.console import Console
813
+
814
+ from htmlgraph.hooks.installer import HookConfig, HookInstaller
815
+
816
+ console = Console()
817
+
818
+ graph_dir = Path(self.graph_dir or DEFAULT_GRAPH_DIR).resolve()
819
+
820
+ # Validate environment
821
+ if not (graph_dir.parent / ".git").exists():
822
+ raise CommandError("Not a git repository (no .git directory found)")
823
+
824
+ if not graph_dir.exists():
825
+ raise CommandError(f"Graph directory not found: {graph_dir}")
826
+
827
+ # Create hook config and installer
828
+ config_path = graph_dir / "hooks-config.json"
829
+ config = HookConfig(config_path)
830
+ installer = HookInstaller(graph_dir.parent, config)
831
+
832
+ # Validate environment
833
+ is_valid, error_msg = installer.validate_environment()
834
+ if not is_valid:
835
+ raise CommandError(error_msg)
836
+
837
+ # Install hooks
838
+ with console.status("[blue]Installing Git hooks...", spinner="dots"):
839
+ results = installer.install_all_hooks(
840
+ dry_run=self.dry_run, force=self.force
841
+ )
842
+
843
+ # Build output
844
+ from htmlgraph.cli.base import TextOutputBuilder
845
+
846
+ output = TextOutputBuilder()
847
+
848
+ if self.dry_run:
849
+ output.add_info("DRY RUN - No changes made")
850
+
851
+ # Count results
852
+ success_count = sum(1 for success, _ in results.values() if success)
853
+ total = len(results)
854
+
855
+ output.add_success(f"Installed {success_count}/{total} hooks")
856
+
857
+ # Show individual results
858
+ for hook_name, (success, message) in sorted(results.items()):
859
+ status = "[green]✓[/green]" if success else "[yellow]✗[/yellow]"
860
+ output.add_line(f"{status} {hook_name}: {message}")
861
+
862
+ return CommandResult(
863
+ text=output.build(),
864
+ json_data={
865
+ "dry_run": self.dry_run,
866
+ "installed": success_count,
867
+ "total": total,
868
+ "results": {
869
+ name: {"success": success, "message": msg}
870
+ for name, (success, msg) in results.items()
871
+ },
872
+ },
873
+ )
874
+
875
+
876
+ class BootstrapCommand(BaseCommand):
877
+ """Bootstrap HtmlGraph in under 60 seconds."""
878
+
879
+ def __init__(self, *, project_path: str, no_plugins: bool) -> None:
880
+ super().__init__()
881
+ self.project_path = project_path
882
+ self.no_plugins = no_plugins
883
+
884
+ @classmethod
885
+ def from_args(cls, args: argparse.Namespace) -> BootstrapCommand:
886
+ return cls(
887
+ project_path=args.project_path,
888
+ no_plugins=args.no_plugins,
889
+ )
890
+
891
+ def execute(self) -> CommandResult:
892
+ """Bootstrap HtmlGraph setup."""
893
+ from rich.console import Console
894
+ from rich.panel import Panel
895
+
896
+ from htmlgraph.cli.models import BootstrapConfig
897
+ from htmlgraph.operations.bootstrap import bootstrap_htmlgraph
898
+
899
+ console = Console()
900
+
901
+ # Create config
902
+ config = BootstrapConfig(
903
+ project_path=self.project_path,
904
+ no_plugins=self.no_plugins,
905
+ )
906
+
907
+ # Run bootstrap
908
+ console.print()
909
+ console.print("[bold cyan]Bootstrapping HtmlGraph...[/bold cyan]")
910
+ console.print()
911
+
912
+ result = bootstrap_htmlgraph(config)
913
+
914
+ if not result["success"]:
915
+ raise CommandError(result.get("message", "Bootstrap failed"))
916
+
917
+ # Display success message
918
+ console.print()
919
+ console.print(
920
+ Panel.fit(
921
+ "[bold green]✓ HtmlGraph initialized successfully![/bold green]",
922
+ border_style="green",
923
+ )
924
+ )
925
+ console.print()
926
+
927
+ # Show project info
928
+ console.print(f"[cyan]Project type:[/cyan] {result['project_type']}")
929
+ console.print(f"[cyan]Location:[/cyan] {result['graph_dir']}")
930
+ console.print()
931
+
932
+ # Show next steps
933
+ console.print("[bold yellow]Next steps:[/bold yellow]")
934
+ for step in result["next_steps"]:
935
+ console.print(f" {step}")
936
+ console.print()
937
+
938
+ # Show documentation link
939
+ console.print(
940
+ "[dim]📚 Learn more: https://github.com/Shakes-tzd/htmlgraph[/dim]"
941
+ )
942
+ console.print()
943
+
944
+ return CommandResult(
945
+ text="Bootstrap completed successfully",
946
+ json_data={
947
+ "project_type": result["project_type"],
948
+ "graph_dir": result["graph_dir"],
949
+ "directories_created": len(result["directories_created"]),
950
+ "files_created": len(result["files_created"]),
951
+ "has_claude": result["has_claude"],
952
+ "plugin_installed": result["plugin_installed"],
953
+ },
954
+ )