htmlgraph 0.24.2__py3-none-any.whl → 0.25.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- htmlgraph/__init__.py +20 -1
- htmlgraph/agent_detection.py +26 -10
- htmlgraph/analytics/cross_session.py +4 -3
- htmlgraph/analytics/work_type.py +52 -16
- htmlgraph/analytics_index.py +51 -19
- htmlgraph/api/__init__.py +3 -0
- htmlgraph/api/main.py +2115 -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 +812 -0
- htmlgraph/api/templates/dashboard.html +783 -0
- htmlgraph/api/templates/partials/activity-feed-hierarchical.html +326 -0
- htmlgraph/api/templates/partials/activity-feed.html +570 -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 +509 -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 +163 -0
- htmlgraph/api/templates/partials/spawners.html +375 -0
- htmlgraph/atomic_ops.py +560 -0
- htmlgraph/builders/base.py +55 -1
- htmlgraph/builders/bug.py +17 -2
- htmlgraph/builders/chore.py +17 -2
- htmlgraph/builders/epic.py +17 -2
- htmlgraph/builders/feature.py +25 -2
- htmlgraph/builders/phase.py +17 -2
- htmlgraph/builders/spike.py +27 -2
- htmlgraph/builders/track.py +14 -0
- htmlgraph/cigs/__init__.py +4 -0
- htmlgraph/cigs/reporter.py +818 -0
- htmlgraph/cli.py +1427 -401
- htmlgraph/cli_commands/__init__.py +1 -0
- htmlgraph/cli_commands/feature.py +195 -0
- htmlgraph/cli_framework.py +115 -0
- htmlgraph/collections/__init__.py +2 -0
- htmlgraph/collections/base.py +21 -0
- htmlgraph/collections/session.py +189 -0
- htmlgraph/collections/spike.py +7 -1
- htmlgraph/collections/task_delegation.py +236 -0
- htmlgraph/collections/traces.py +482 -0
- htmlgraph/config.py +113 -0
- htmlgraph/converter.py +41 -0
- htmlgraph/cost_analysis/__init__.py +5 -0
- htmlgraph/cost_analysis/analyzer.py +438 -0
- htmlgraph/dashboard.html +3315 -492
- htmlgraph-0.24.2.data/data/htmlgraph/dashboard.html → htmlgraph/dashboard.html.backup +2246 -248
- 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 +1334 -0
- htmlgraph/deploy.py +26 -27
- 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 +710 -0
- htmlgraph/docs/README.md +533 -0
- htmlgraph/docs/version_check.py +3 -1
- htmlgraph/error_handler.py +544 -0
- htmlgraph/event_log.py +2 -0
- htmlgraph/hooks/__init__.py +8 -0
- htmlgraph/hooks/bootstrap.py +169 -0
- htmlgraph/hooks/context.py +271 -0
- htmlgraph/hooks/drift_handler.py +521 -0
- htmlgraph/hooks/event_tracker.py +405 -15
- htmlgraph/hooks/post_tool_use_handler.py +257 -0
- htmlgraph/hooks/pretooluse.py +476 -6
- htmlgraph/hooks/prompt_analyzer.py +648 -0
- htmlgraph/hooks/session_handler.py +583 -0
- htmlgraph/hooks/state_manager.py +501 -0
- htmlgraph/hooks/subagent_stop.py +309 -0
- htmlgraph/hooks/task_enforcer.py +39 -0
- htmlgraph/models.py +111 -15
- htmlgraph/operations/fastapi_server.py +230 -0
- htmlgraph/orchestration/headless_spawner.py +22 -14
- htmlgraph/pydantic_models.py +476 -0
- htmlgraph/quality_gates.py +350 -0
- htmlgraph/repo_hash.py +511 -0
- htmlgraph/sdk.py +348 -10
- htmlgraph/server.py +194 -0
- htmlgraph/session_hooks.py +300 -0
- htmlgraph/session_manager.py +131 -1
- htmlgraph/session_registry.py +587 -0
- htmlgraph/session_state.py +436 -0
- htmlgraph/system_prompts.py +449 -0
- htmlgraph/templates/orchestration-view.html +350 -0
- htmlgraph/track_builder.py +19 -0
- htmlgraph/validation.py +115 -0
- htmlgraph-0.25.0.data/data/htmlgraph/dashboard.html +7417 -0
- {htmlgraph-0.24.2.dist-info → htmlgraph-0.25.0.dist-info}/METADATA +91 -64
- {htmlgraph-0.24.2.dist-info → htmlgraph-0.25.0.dist-info}/RECORD +103 -42
- {htmlgraph-0.24.2.data → htmlgraph-0.25.0.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.24.2.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.24.2.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.24.2.data → htmlgraph-0.25.0.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.24.2.dist-info → htmlgraph-0.25.0.dist-info}/WHEEL +0 -0
- {htmlgraph-0.24.2.dist-info → htmlgraph-0.25.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
"""FastAPI-based server for HtmlGraph dashboard with real-time observability."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from htmlgraph.mcp_server import _resolve_project_dir
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class FastAPIServerHandle:
|
|
17
|
+
"""Handle to a running FastAPI server."""
|
|
18
|
+
|
|
19
|
+
url: str
|
|
20
|
+
port: int
|
|
21
|
+
host: str
|
|
22
|
+
server: Any | None = None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class FastAPIServerStartResult:
|
|
27
|
+
"""Result of starting FastAPI server."""
|
|
28
|
+
|
|
29
|
+
handle: FastAPIServerHandle
|
|
30
|
+
warnings: list[str]
|
|
31
|
+
config_used: dict[str, Any]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class FastAPIServerError(RuntimeError):
|
|
35
|
+
"""FastAPI server error."""
|
|
36
|
+
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class PortInUseError(FastAPIServerError):
|
|
41
|
+
"""Requested port is already in use."""
|
|
42
|
+
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def start_fastapi_server(
|
|
47
|
+
*,
|
|
48
|
+
port: int = 8000,
|
|
49
|
+
host: str = "127.0.0.1",
|
|
50
|
+
db_path: str | None = None,
|
|
51
|
+
auto_port: bool = False,
|
|
52
|
+
reload: bool = False,
|
|
53
|
+
) -> FastAPIServerStartResult:
|
|
54
|
+
"""
|
|
55
|
+
Start FastAPI-based HtmlGraph dashboard server.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
port: Port to listen on (default: 8000)
|
|
59
|
+
host: Host to bind to (default: 127.0.0.1)
|
|
60
|
+
db_path: Path to SQLite database file
|
|
61
|
+
auto_port: Automatically find available port if in use
|
|
62
|
+
reload: Enable auto-reload on file changes (development mode)
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
FastAPIServerStartResult with handle, warnings, and config used
|
|
66
|
+
|
|
67
|
+
Raises:
|
|
68
|
+
PortInUseError: If port is in use and auto_port=False
|
|
69
|
+
FastAPIServerError: If server fails to start
|
|
70
|
+
"""
|
|
71
|
+
import uvicorn
|
|
72
|
+
|
|
73
|
+
from htmlgraph.api.main import create_app
|
|
74
|
+
|
|
75
|
+
warnings: list[str] = []
|
|
76
|
+
original_port = port
|
|
77
|
+
|
|
78
|
+
# Default database path - prefer project-local database if available
|
|
79
|
+
if db_path is None:
|
|
80
|
+
# Check for project-local database first
|
|
81
|
+
project_dir = _resolve_project_dir()
|
|
82
|
+
project_db = Path(project_dir) / ".htmlgraph" / "index.sqlite"
|
|
83
|
+
if project_db.exists():
|
|
84
|
+
db_path = str(project_db) # Use project-local database
|
|
85
|
+
else:
|
|
86
|
+
db_path = str(
|
|
87
|
+
Path.home() / ".htmlgraph" / "index.sqlite"
|
|
88
|
+
) # Fall back to home
|
|
89
|
+
|
|
90
|
+
# Ensure database exists
|
|
91
|
+
db_path_obj = Path(db_path)
|
|
92
|
+
db_path_obj.parent.mkdir(parents=True, exist_ok=True)
|
|
93
|
+
|
|
94
|
+
# Handle auto-port selection
|
|
95
|
+
if auto_port and _check_port_in_use(port, host):
|
|
96
|
+
port = _find_available_port(port + 1)
|
|
97
|
+
warnings.append(f"Port {original_port} is in use, using {port} instead")
|
|
98
|
+
|
|
99
|
+
# Check if port is in use
|
|
100
|
+
if not auto_port and _check_port_in_use(port, host):
|
|
101
|
+
raise PortInUseError(
|
|
102
|
+
f"Port {port} is already in use. Use auto_port=True or choose a different port."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Create FastAPI app
|
|
106
|
+
app = create_app(db_path=db_path)
|
|
107
|
+
|
|
108
|
+
# Create server config
|
|
109
|
+
config = uvicorn.Config(
|
|
110
|
+
app,
|
|
111
|
+
host=host,
|
|
112
|
+
port=port,
|
|
113
|
+
log_level="info",
|
|
114
|
+
reload=reload,
|
|
115
|
+
reload_dirs=None, # Disable file watching for now
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Create server instance
|
|
119
|
+
server = uvicorn.Server(config)
|
|
120
|
+
|
|
121
|
+
# Create handle
|
|
122
|
+
handle = FastAPIServerHandle(
|
|
123
|
+
url=f"http://{host}:{port}",
|
|
124
|
+
port=port,
|
|
125
|
+
host=host,
|
|
126
|
+
server=server,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Configuration used
|
|
130
|
+
config_used = {
|
|
131
|
+
"port": port,
|
|
132
|
+
"original_port": original_port,
|
|
133
|
+
"host": host,
|
|
134
|
+
"db_path": db_path,
|
|
135
|
+
"auto_port": auto_port,
|
|
136
|
+
"reload": reload,
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return FastAPIServerStartResult(
|
|
140
|
+
handle=handle,
|
|
141
|
+
warnings=warnings,
|
|
142
|
+
config_used=config_used,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
async def run_fastapi_server(handle: FastAPIServerHandle) -> None:
|
|
147
|
+
"""
|
|
148
|
+
Run FastAPI server (async).
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
handle: FastAPIServerHandle from start_fastapi_server()
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
FastAPIServerError: If server fails
|
|
155
|
+
"""
|
|
156
|
+
if handle.server is None:
|
|
157
|
+
raise FastAPIServerError("Invalid server handle")
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
await handle.server.serve()
|
|
161
|
+
except Exception as e:
|
|
162
|
+
raise FastAPIServerError(f"Server error: {e}") from e
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def stop_fastapi_server(handle: FastAPIServerHandle) -> None:
|
|
166
|
+
"""
|
|
167
|
+
Stop FastAPI server.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
handle: FastAPIServerHandle from start_fastapi_server()
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
FastAPIServerError: If shutdown fails
|
|
174
|
+
"""
|
|
175
|
+
if handle.server is None:
|
|
176
|
+
return
|
|
177
|
+
|
|
178
|
+
try:
|
|
179
|
+
handle.server.should_exit = True
|
|
180
|
+
except Exception as e:
|
|
181
|
+
raise FastAPIServerError(f"Failed to stop server: {e}") from e
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _check_port_in_use(port: int, host: str = "localhost") -> bool:
|
|
185
|
+
"""
|
|
186
|
+
Check if a port is already in use.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
port: Port number to check
|
|
190
|
+
host: Host to check on
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
True if port is in use, False otherwise
|
|
194
|
+
"""
|
|
195
|
+
import socket
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
199
|
+
s.bind((host, port))
|
|
200
|
+
return False
|
|
201
|
+
except OSError:
|
|
202
|
+
return True
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _find_available_port(start_port: int = 8000, max_attempts: int = 10) -> int:
|
|
206
|
+
"""
|
|
207
|
+
Find an available port starting from start_port.
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
start_port: Port to start searching from
|
|
211
|
+
max_attempts: Maximum number of ports to try
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Available port number
|
|
215
|
+
|
|
216
|
+
Raises:
|
|
217
|
+
FastAPIServerError: If no available port found
|
|
218
|
+
"""
|
|
219
|
+
import socket
|
|
220
|
+
|
|
221
|
+
for port in range(start_port, start_port + max_attempts):
|
|
222
|
+
try:
|
|
223
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
224
|
+
s.bind(("", port))
|
|
225
|
+
return port
|
|
226
|
+
except OSError:
|
|
227
|
+
continue
|
|
228
|
+
raise FastAPIServerError(
|
|
229
|
+
f"No available ports found in range {start_port}-{start_port + max_attempts}"
|
|
230
|
+
)
|
|
@@ -382,14 +382,14 @@ class HeadlessSpawner:
|
|
|
382
382
|
|
|
383
383
|
Args:
|
|
384
384
|
prompt: Task description for Gemini
|
|
385
|
-
output_format: "json" or "stream-json" (
|
|
386
|
-
model: Model selection (e.g., "gemini-2.0-flash"). Default: None
|
|
387
|
-
include_directories:
|
|
385
|
+
output_format: "json" or "stream-json" (enables real-time tracking)
|
|
386
|
+
model: Model selection (e.g., "gemini-2.0-flash"). Default: None
|
|
387
|
+
include_directories: Directories to include for context. Default: None
|
|
388
388
|
track_in_htmlgraph: Enable HtmlGraph activity tracking. Default: True
|
|
389
389
|
timeout: Max seconds to wait
|
|
390
390
|
|
|
391
391
|
Returns:
|
|
392
|
-
AIResult with response
|
|
392
|
+
AIResult with response, error, and tracked events if tracking enabled
|
|
393
393
|
"""
|
|
394
394
|
# Initialize tracking if enabled
|
|
395
395
|
sdk: SDK | None = None
|
|
@@ -585,17 +585,17 @@ class HeadlessSpawner:
|
|
|
585
585
|
|
|
586
586
|
Args:
|
|
587
587
|
prompt: Task description for Codex
|
|
588
|
-
output_json:
|
|
588
|
+
output_json: JSONL output flag (enables real-time tracking)
|
|
589
589
|
model: Model selection (e.g., "gpt-4-turbo"). Default: None
|
|
590
|
-
sandbox: Sandbox mode ("read-only", "workspace-write",
|
|
591
|
-
full_auto: Enable full auto mode
|
|
590
|
+
sandbox: Sandbox mode ("read-only", "workspace-write", or full)
|
|
591
|
+
full_auto: Enable full auto mode. Default: True (required headless)
|
|
592
592
|
images: List of image paths (--image). Default: None
|
|
593
|
-
output_last_message: Write last message to file
|
|
594
|
-
output_schema: JSON schema for validation
|
|
595
|
-
skip_git_check: Skip git repo check
|
|
593
|
+
output_last_message: Write last message to file. Default: None
|
|
594
|
+
output_schema: JSON schema for validation. Default: None
|
|
595
|
+
skip_git_check: Skip git repo check. Default: False
|
|
596
596
|
working_directory: Workspace directory (--cd). Default: None
|
|
597
597
|
use_oss: Use local Ollama provider (--oss). Default: False
|
|
598
|
-
bypass_approvals:
|
|
598
|
+
bypass_approvals: Bypass approval checks. Default: False
|
|
599
599
|
track_in_htmlgraph: Enable HtmlGraph activity tracking. Default: True
|
|
600
600
|
timeout: Max seconds to wait
|
|
601
601
|
|
|
@@ -794,9 +794,9 @@ class HeadlessSpawner:
|
|
|
794
794
|
|
|
795
795
|
Args:
|
|
796
796
|
prompt: Task description for Copilot
|
|
797
|
-
allow_tools:
|
|
798
|
-
allow_all_tools: Auto-approve all tools
|
|
799
|
-
deny_tools:
|
|
797
|
+
allow_tools: Tools to auto-approve (e.g., ["shell(git)"])
|
|
798
|
+
allow_all_tools: Auto-approve all tools. Default: False
|
|
799
|
+
deny_tools: Tools to deny (--deny-tool). Default: None
|
|
800
800
|
track_in_htmlgraph: Enable HtmlGraph activity tracking. Default: True
|
|
801
801
|
timeout: Max seconds to wait
|
|
802
802
|
|
|
@@ -924,6 +924,7 @@ class HeadlessSpawner:
|
|
|
924
924
|
resume: str | None = None,
|
|
925
925
|
verbose: bool = False,
|
|
926
926
|
timeout: int = 300,
|
|
927
|
+
extra_args: list[str] | None = None,
|
|
927
928
|
) -> AIResult:
|
|
928
929
|
"""
|
|
929
930
|
Spawn Claude in headless mode.
|
|
@@ -948,6 +949,7 @@ class HeadlessSpawner:
|
|
|
948
949
|
resume: Resume from previous session (--resume). Default: None
|
|
949
950
|
verbose: Enable verbose output (--verbose). Default: False
|
|
950
951
|
timeout: Max seconds (default: 300, Claude can be slow with initialization)
|
|
952
|
+
extra_args: Additional arguments to pass to Claude CLI
|
|
951
953
|
|
|
952
954
|
Returns:
|
|
953
955
|
AIResult with response or error
|
|
@@ -975,6 +977,12 @@ class HeadlessSpawner:
|
|
|
975
977
|
if verbose:
|
|
976
978
|
cmd.append("--verbose")
|
|
977
979
|
|
|
980
|
+
# Add extra args
|
|
981
|
+
if extra_args:
|
|
982
|
+
cmd.extend(extra_args)
|
|
983
|
+
|
|
984
|
+
# Use -- separator to ensure prompt isn't consumed by variadic args
|
|
985
|
+
cmd.append("--")
|
|
978
986
|
cmd.append(prompt)
|
|
979
987
|
|
|
980
988
|
try:
|