htmlgraph 0.9.3__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.
- htmlgraph/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/.htmlgraph/agents.json +72 -0
- htmlgraph/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/__init__.py +173 -17
- htmlgraph/__init__.pyi +123 -0
- htmlgraph/agent_detection.py +127 -0
- htmlgraph/agent_registry.py +45 -30
- htmlgraph/agents.py +160 -107
- htmlgraph/analytics/__init__.py +9 -2
- htmlgraph/analytics/cli.py +190 -51
- htmlgraph/analytics/cost_analyzer.py +391 -0
- htmlgraph/analytics/cost_monitor.py +664 -0
- htmlgraph/analytics/cost_reporter.py +675 -0
- htmlgraph/analytics/cross_session.py +617 -0
- htmlgraph/analytics/dependency.py +192 -100
- htmlgraph/analytics/pattern_learning.py +771 -0
- htmlgraph/analytics/session_graph.py +707 -0
- htmlgraph/analytics/strategic/__init__.py +80 -0
- htmlgraph/analytics/strategic/cost_optimizer.py +611 -0
- htmlgraph/analytics/strategic/pattern_detector.py +876 -0
- htmlgraph/analytics/strategic/preference_manager.py +709 -0
- htmlgraph/analytics/strategic/suggestion_engine.py +747 -0
- htmlgraph/analytics/work_type.py +190 -14
- htmlgraph/analytics_index.py +135 -51
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/cost_alerts_websocket.py +416 -0
- htmlgraph/api/main.py +2498 -0
- htmlgraph/api/static/htmx.min.js +1 -0
- htmlgraph/api/static/style-redesign.css +1344 -0
- htmlgraph/api/static/style.css +1079 -0
- htmlgraph/api/templates/dashboard-redesign.html +1366 -0
- htmlgraph/api/templates/dashboard.html +794 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +1100 -0
- htmlgraph/api/templates/partials/agents-redesign.html +317 -0
- htmlgraph/api/templates/partials/agents.html +317 -0
- htmlgraph/api/templates/partials/event-traces.html +373 -0
- htmlgraph/api/templates/partials/features-kanban-redesign.html +509 -0
- htmlgraph/api/templates/partials/features.html +578 -0
- htmlgraph/api/templates/partials/metrics-redesign.html +346 -0
- htmlgraph/api/templates/partials/metrics.html +346 -0
- htmlgraph/api/templates/partials/orchestration-redesign.html +443 -0
- htmlgraph/api/templates/partials/orchestration.html +198 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/api/templates/partials/work-items.html +613 -0
- htmlgraph/api/websocket.py +538 -0
- htmlgraph/archive/__init__.py +24 -0
- htmlgraph/archive/bloom.py +234 -0
- htmlgraph/archive/fts.py +297 -0
- htmlgraph/archive/manager.py +583 -0
- htmlgraph/archive/search.py +244 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/attribute_index.py +208 -0
- htmlgraph/bounded_paths.py +539 -0
- htmlgraph/builders/__init__.py +14 -0
- htmlgraph/builders/base.py +118 -29
- htmlgraph/builders/bug.py +150 -0
- htmlgraph/builders/chore.py +119 -0
- htmlgraph/builders/epic.py +150 -0
- htmlgraph/builders/feature.py +31 -6
- htmlgraph/builders/insight.py +195 -0
- htmlgraph/builders/metric.py +217 -0
- htmlgraph/builders/pattern.py +202 -0
- htmlgraph/builders/phase.py +162 -0
- htmlgraph/builders/spike.py +52 -19
- htmlgraph/builders/track.py +148 -72
- htmlgraph/cigs/__init__.py +81 -0
- htmlgraph/cigs/autonomy.py +385 -0
- htmlgraph/cigs/cost.py +475 -0
- htmlgraph/cigs/messages_basic.py +472 -0
- htmlgraph/cigs/messaging.py +365 -0
- htmlgraph/cigs/models.py +771 -0
- htmlgraph/cigs/pattern_storage.py +427 -0
- htmlgraph/cigs/patterns.py +503 -0
- htmlgraph/cigs/posttool_analyzer.py +234 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cigs/tracker.py +317 -0
- htmlgraph/cli/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/cli/.htmlgraph/agents.json +72 -0
- htmlgraph/cli/.htmlgraph/htmlgraph.db +0 -0
- htmlgraph/cli/__init__.py +42 -0
- htmlgraph/cli/__main__.py +6 -0
- htmlgraph/cli/analytics.py +1424 -0
- htmlgraph/cli/base.py +685 -0
- htmlgraph/cli/constants.py +206 -0
- htmlgraph/cli/core.py +954 -0
- htmlgraph/cli/main.py +147 -0
- htmlgraph/cli/models.py +475 -0
- htmlgraph/cli/templates/__init__.py +1 -0
- htmlgraph/cli/templates/cost_dashboard.py +399 -0
- htmlgraph/cli/work/__init__.py +239 -0
- htmlgraph/cli/work/browse.py +115 -0
- htmlgraph/cli/work/features.py +568 -0
- htmlgraph/cli/work/orchestration.py +676 -0
- htmlgraph/cli/work/report.py +728 -0
- htmlgraph/cli/work/sessions.py +466 -0
- htmlgraph/cli/work/snapshot.py +559 -0
- htmlgraph/cli/work/tracks.py +486 -0
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +18 -0
- htmlgraph/collections/base.py +415 -98
- htmlgraph/collections/bug.py +53 -0
- htmlgraph/collections/chore.py +53 -0
- htmlgraph/collections/epic.py +53 -0
- htmlgraph/collections/feature.py +12 -26
- htmlgraph/collections/insight.py +100 -0
- htmlgraph/collections/metric.py +92 -0
- htmlgraph/collections/pattern.py +97 -0
- htmlgraph/collections/phase.py +53 -0
- htmlgraph/collections/session.py +194 -0
- htmlgraph/collections/spike.py +56 -16
- htmlgraph/collections/task_delegation.py +241 -0
- htmlgraph/collections/todo.py +511 -0
- htmlgraph/collections/traces.py +487 -0
- htmlgraph/config/cost_models.json +56 -0
- htmlgraph/config.py +190 -0
- htmlgraph/context_analytics.py +344 -0
- htmlgraph/converter.py +216 -28
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +2406 -307
- htmlgraph/dashboard.html.backup +6592 -0
- htmlgraph/dashboard.html.bak +7181 -0
- htmlgraph/dashboard.html.bak2 +7231 -0
- htmlgraph/dashboard.html.bak3 +7232 -0
- htmlgraph/db/__init__.py +38 -0
- htmlgraph/db/queries.py +790 -0
- htmlgraph/db/schema.py +1788 -0
- htmlgraph/decorators.py +317 -0
- htmlgraph/dependency_models.py +19 -2
- htmlgraph/deploy.py +142 -125
- htmlgraph/deployment_models.py +474 -0
- htmlgraph/docs/API_REFERENCE.md +841 -0
- htmlgraph/docs/HTTP_API.md +750 -0
- htmlgraph/docs/INTEGRATION_GUIDE.md +752 -0
- htmlgraph/docs/ORCHESTRATION_PATTERNS.md +717 -0
- htmlgraph/docs/README.md +532 -0
- htmlgraph/docs/__init__.py +77 -0
- htmlgraph/docs/docs_version.py +55 -0
- htmlgraph/docs/metadata.py +93 -0
- htmlgraph/docs/migrations.py +232 -0
- htmlgraph/docs/template_engine.py +143 -0
- htmlgraph/docs/templates/_sections/cli_reference.md.j2 +52 -0
- htmlgraph/docs/templates/_sections/core_concepts.md.j2 +29 -0
- htmlgraph/docs/templates/_sections/sdk_basics.md.j2 +69 -0
- htmlgraph/docs/templates/base_agents.md.j2 +78 -0
- htmlgraph/docs/templates/example_user_override.md.j2 +47 -0
- htmlgraph/docs/version_check.py +163 -0
- htmlgraph/edge_index.py +182 -27
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +100 -52
- htmlgraph/event_migration.py +13 -4
- htmlgraph/exceptions.py +49 -0
- htmlgraph/file_watcher.py +101 -28
- htmlgraph/find_api.py +75 -63
- htmlgraph/git_events.py +145 -63
- htmlgraph/graph.py +1122 -106
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
- htmlgraph/hooks/.htmlgraph/agents.json +72 -0
- htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
- htmlgraph/hooks/__init__.py +45 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/cigs_pretool_enforcer.py +354 -0
- htmlgraph/hooks/concurrent_sessions.py +208 -0
- htmlgraph/hooks/context.py +350 -0
- htmlgraph/hooks/drift_handler.py +525 -0
- htmlgraph/hooks/event_tracker.py +1314 -0
- htmlgraph/hooks/git_commands.py +175 -0
- htmlgraph/hooks/hooks-config.example.json +12 -0
- htmlgraph/hooks/installer.py +343 -0
- htmlgraph/hooks/orchestrator.py +674 -0
- htmlgraph/hooks/orchestrator_reflector.py +223 -0
- htmlgraph/hooks/post-checkout.sh +28 -0
- htmlgraph/hooks/post-commit.sh +24 -0
- htmlgraph/hooks/post-merge.sh +26 -0
- htmlgraph/hooks/post_tool_use_failure.py +273 -0
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/posttooluse.py +408 -0
- htmlgraph/hooks/pre-commit.sh +94 -0
- htmlgraph/hooks/pre-push.sh +28 -0
- htmlgraph/hooks/pretooluse.py +819 -0
- htmlgraph/hooks/prompt_analyzer.py +637 -0
- htmlgraph/hooks/session_handler.py +668 -0
- htmlgraph/hooks/session_summary.py +395 -0
- htmlgraph/hooks/state_manager.py +504 -0
- htmlgraph/hooks/subagent_detection.py +202 -0
- htmlgraph/hooks/subagent_stop.py +369 -0
- htmlgraph/hooks/task_enforcer.py +255 -0
- htmlgraph/hooks/task_validator.py +177 -0
- htmlgraph/hooks/validator.py +628 -0
- htmlgraph/ids.py +41 -27
- htmlgraph/index.d.ts +286 -0
- htmlgraph/learning.py +767 -0
- htmlgraph/mcp_server.py +69 -23
- htmlgraph/models.py +1586 -87
- htmlgraph/operations/README.md +62 -0
- htmlgraph/operations/__init__.py +79 -0
- htmlgraph/operations/analytics.py +339 -0
- htmlgraph/operations/bootstrap.py +289 -0
- htmlgraph/operations/events.py +244 -0
- htmlgraph/operations/fastapi_server.py +231 -0
- htmlgraph/operations/hooks.py +350 -0
- htmlgraph/operations/initialization.py +597 -0
- htmlgraph/operations/initialization.py.backup +228 -0
- htmlgraph/operations/server.py +303 -0
- htmlgraph/orchestration/__init__.py +58 -0
- htmlgraph/orchestration/claude_launcher.py +179 -0
- htmlgraph/orchestration/command_builder.py +72 -0
- htmlgraph/orchestration/headless_spawner.py +281 -0
- htmlgraph/orchestration/live_events.py +377 -0
- htmlgraph/orchestration/model_selection.py +327 -0
- htmlgraph/orchestration/plugin_manager.py +140 -0
- htmlgraph/orchestration/prompts.py +137 -0
- htmlgraph/orchestration/spawner_event_tracker.py +383 -0
- htmlgraph/orchestration/spawners/__init__.py +16 -0
- htmlgraph/orchestration/spawners/base.py +194 -0
- htmlgraph/orchestration/spawners/claude.py +173 -0
- htmlgraph/orchestration/spawners/codex.py +435 -0
- htmlgraph/orchestration/spawners/copilot.py +294 -0
- htmlgraph/orchestration/spawners/gemini.py +471 -0
- htmlgraph/orchestration/subprocess_runner.py +36 -0
- htmlgraph/orchestration/task_coordination.py +343 -0
- htmlgraph/orchestration.md +563 -0
- htmlgraph/orchestrator-system-prompt-optimized.txt +863 -0
- htmlgraph/orchestrator.py +669 -0
- htmlgraph/orchestrator_config.py +357 -0
- htmlgraph/orchestrator_mode.py +328 -0
- htmlgraph/orchestrator_validator.py +133 -0
- htmlgraph/parallel.py +646 -0
- htmlgraph/parser.py +160 -35
- htmlgraph/path_query.py +608 -0
- htmlgraph/pattern_matcher.py +636 -0
- htmlgraph/planning.py +147 -52
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/query_builder.py +109 -72
- htmlgraph/query_composer.py +509 -0
- htmlgraph/reflection.py +443 -0
- htmlgraph/refs.py +344 -0
- htmlgraph/repo_hash.py +512 -0
- htmlgraph/repositories/__init__.py +292 -0
- htmlgraph/repositories/analytics_repository.py +455 -0
- htmlgraph/repositories/analytics_repository_standard.py +628 -0
- htmlgraph/repositories/feature_repository.py +581 -0
- htmlgraph/repositories/feature_repository_htmlfile.py +668 -0
- htmlgraph/repositories/feature_repository_memory.py +607 -0
- htmlgraph/repositories/feature_repository_sqlite.py +858 -0
- htmlgraph/repositories/filter_service.py +620 -0
- htmlgraph/repositories/filter_service_standard.py +445 -0
- htmlgraph/repositories/shared_cache.py +621 -0
- htmlgraph/repositories/shared_cache_memory.py +395 -0
- htmlgraph/repositories/track_repository.py +552 -0
- htmlgraph/repositories/track_repository_htmlfile.py +619 -0
- htmlgraph/repositories/track_repository_memory.py +508 -0
- htmlgraph/repositories/track_repository_sqlite.py +711 -0
- htmlgraph/routing.py +8 -19
- htmlgraph/scripts/deploy.py +1 -2
- htmlgraph/sdk/__init__.py +398 -0
- htmlgraph/sdk/__init__.pyi +14 -0
- htmlgraph/sdk/analytics/__init__.py +19 -0
- htmlgraph/sdk/analytics/engine.py +155 -0
- htmlgraph/sdk/analytics/helpers.py +178 -0
- htmlgraph/sdk/analytics/registry.py +109 -0
- htmlgraph/sdk/base.py +484 -0
- htmlgraph/sdk/constants.py +216 -0
- htmlgraph/sdk/core.pyi +308 -0
- htmlgraph/sdk/discovery.py +120 -0
- htmlgraph/sdk/help/__init__.py +12 -0
- htmlgraph/sdk/help/mixin.py +699 -0
- htmlgraph/sdk/mixins/__init__.py +15 -0
- htmlgraph/sdk/mixins/attribution.py +113 -0
- htmlgraph/sdk/mixins/mixin.py +410 -0
- htmlgraph/sdk/operations/__init__.py +12 -0
- htmlgraph/sdk/operations/mixin.py +427 -0
- htmlgraph/sdk/orchestration/__init__.py +17 -0
- htmlgraph/sdk/orchestration/coordinator.py +203 -0
- htmlgraph/sdk/orchestration/spawner.py +204 -0
- htmlgraph/sdk/planning/__init__.py +19 -0
- htmlgraph/sdk/planning/bottlenecks.py +93 -0
- htmlgraph/sdk/planning/mixin.py +211 -0
- htmlgraph/sdk/planning/parallel.py +186 -0
- htmlgraph/sdk/planning/queue.py +210 -0
- htmlgraph/sdk/planning/recommendations.py +87 -0
- htmlgraph/sdk/planning/smart_planning.py +319 -0
- htmlgraph/sdk/session/__init__.py +19 -0
- htmlgraph/sdk/session/continuity.py +57 -0
- htmlgraph/sdk/session/handoff.py +110 -0
- htmlgraph/sdk/session/info.py +309 -0
- htmlgraph/sdk/session/manager.py +103 -0
- htmlgraph/sdk/strategic/__init__.py +26 -0
- htmlgraph/sdk/strategic/mixin.py +563 -0
- htmlgraph/server.py +685 -180
- htmlgraph/services/__init__.py +10 -0
- htmlgraph/services/claiming.py +199 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +1392 -175
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/session_warning.py +201 -0
- htmlgraph/sessions/__init__.py +23 -0
- htmlgraph/sessions/handoff.py +756 -0
- htmlgraph/setup.py +34 -17
- htmlgraph/spike_index.py +143 -0
- htmlgraph/sync_docs.py +12 -15
- htmlgraph/system_prompts.py +450 -0
- htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph/templates/GEMINI.md.template +87 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +146 -15
- htmlgraph/track_manager.py +69 -21
- htmlgraph/transcript.py +890 -0
- htmlgraph/transcript_analytics.py +699 -0
- htmlgraph/types.py +323 -0
- htmlgraph/validation.py +115 -0
- htmlgraph/watch.py +8 -5
- htmlgraph/work_type_utils.py +3 -2
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/dashboard.html +2406 -307
- htmlgraph-0.27.5.data/data/htmlgraph/templates/AGENTS.md.template +366 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/CLAUDE.md.template +97 -0
- htmlgraph-0.27.5.data/data/htmlgraph/templates/GEMINI.md.template +87 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/METADATA +97 -64
- htmlgraph-0.27.5.dist-info/RECORD +337 -0
- {htmlgraph-0.9.3.dist-info → htmlgraph-0.27.5.dist-info}/entry_points.txt +1 -1
- htmlgraph/cli.py +0 -2688
- htmlgraph/sdk.py +0 -709
- htmlgraph-0.9.3.dist-info/RECORD +0 -61
- {htmlgraph-0.9.3.data → htmlgraph-0.27.5.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.9.3.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
|
+
)
|