htmlgraph 0.26.1__py3-none-any.whl → 0.26.3__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 +1 -1
- htmlgraph/api/main.py +66 -9
- htmlgraph/api/templates/partials/activity-feed.html +59 -0
- htmlgraph/cli.py +1 -1
- htmlgraph/config.py +173 -96
- htmlgraph/dashboard.html +631 -7277
- htmlgraph/db/schema.py +4 -5
- htmlgraph/hooks/.htmlgraph/.session-warning-state.json +1 -1
- htmlgraph/hooks/context.py +40 -8
- htmlgraph/hooks/event_tracker.py +60 -12
- htmlgraph/hooks/pretooluse.py +60 -30
- htmlgraph/hooks/subagent_stop.py +3 -2
- htmlgraph/operations/fastapi_server.py +2 -2
- htmlgraph/orchestration/headless_spawner.py +167 -1
- htmlgraph/server.py +100 -203
- htmlgraph-0.26.3.data/data/htmlgraph/dashboard.html +812 -0
- {htmlgraph-0.26.1.dist-info → htmlgraph-0.26.3.dist-info}/METADATA +1 -1
- {htmlgraph-0.26.1.dist-info → htmlgraph-0.26.3.dist-info}/RECORD +27 -24
- htmlgraph-0.26.1.data/data/htmlgraph/dashboard.html +0 -7458
- {htmlgraph-0.26.1.data → htmlgraph-0.26.3.data}/data/htmlgraph/styles.css +0 -0
- {htmlgraph-0.26.1.data → htmlgraph-0.26.3.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
- {htmlgraph-0.26.1.data → htmlgraph-0.26.3.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
- {htmlgraph-0.26.1.data → htmlgraph-0.26.3.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
- {htmlgraph-0.26.1.dist-info → htmlgraph-0.26.3.dist-info}/WHEEL +0 -0
- {htmlgraph-0.26.1.dist-info → htmlgraph-0.26.3.dist-info}/entry_points.txt +0 -0
htmlgraph/server.py
CHANGED
|
@@ -17,14 +17,13 @@ import socket
|
|
|
17
17
|
import sys
|
|
18
18
|
import urllib.parse
|
|
19
19
|
from datetime import datetime, timezone
|
|
20
|
-
from http.server import
|
|
20
|
+
from http.server import SimpleHTTPRequestHandler
|
|
21
21
|
from pathlib import Path
|
|
22
22
|
from typing import Any, Literal, cast
|
|
23
23
|
|
|
24
24
|
from htmlgraph.analytics_index import AnalyticsIndex
|
|
25
25
|
from htmlgraph.converter import dict_to_node, node_to_dict
|
|
26
26
|
from htmlgraph.event_log import JsonlEventLog
|
|
27
|
-
from htmlgraph.file_watcher import GraphWatcher
|
|
28
27
|
from htmlgraph.graph import HtmlGraph
|
|
29
28
|
from htmlgraph.ids import generate_id
|
|
30
29
|
from htmlgraph.models import Node
|
|
@@ -211,7 +210,15 @@ class HtmlGraphAPIHandler(SimpleHTTPRequestHandler):
|
|
|
211
210
|
return "api", collection, node_id, query_params
|
|
212
211
|
|
|
213
212
|
def _serve_packaged_dashboard(self) -> bool:
|
|
214
|
-
"""
|
|
213
|
+
"""
|
|
214
|
+
DEPRECATED: Serve the bundled dashboard HTML if available.
|
|
215
|
+
|
|
216
|
+
NOTE: This server is LEGACY. The active server is FastAPI-based.
|
|
217
|
+
See: src/python/htmlgraph/operations/fastapi_server.py
|
|
218
|
+
|
|
219
|
+
The dashboard.html file was archived to .archived-templates/
|
|
220
|
+
Active templates are in src/python/htmlgraph/api/templates/
|
|
221
|
+
"""
|
|
215
222
|
dashboard_path = Path(__file__).parent / "dashboard.html"
|
|
216
223
|
if not dashboard_path.exists():
|
|
217
224
|
return False
|
|
@@ -1372,235 +1379,125 @@ def serve(
|
|
|
1372
1379
|
quiet: bool = False,
|
|
1373
1380
|
) -> None:
|
|
1374
1381
|
"""
|
|
1375
|
-
Start the HtmlGraph server.
|
|
1376
|
-
|
|
1382
|
+
Start the HtmlGraph server (FastAPI-based with WebSocket support).
|
|
1383
|
+
|
|
1384
|
+
This function launches the FastAPI server which provides:
|
|
1385
|
+
- REST API for CRUD operations on graph nodes
|
|
1386
|
+
- WebSocket endpoint at /ws/events for real-time event streaming
|
|
1387
|
+
- HTMX-powered dashboard for agent observability
|
|
1377
1388
|
|
|
1378
1389
|
Args:
|
|
1379
|
-
port: Port to listen on
|
|
1390
|
+
port: Port to listen on (default: 8080)
|
|
1380
1391
|
graph_dir: Directory containing graph data (.htmlgraph/)
|
|
1381
|
-
static_dir: Directory for static files (index.html, etc.)
|
|
1382
|
-
host: Host to bind to
|
|
1383
|
-
watch: Enable file watching for auto-reload (default: True)
|
|
1392
|
+
static_dir: Directory for static files (index.html, etc.) - preserved for compatibility
|
|
1393
|
+
host: Host to bind to (default: localhost)
|
|
1394
|
+
watch: Enable file watching for auto-reload (default: True) - maps to reload in FastAPI
|
|
1384
1395
|
auto_port: Automatically find available port if specified port is in use
|
|
1385
|
-
show_progress: Show Rich progress during startup
|
|
1396
|
+
show_progress: Show Rich progress during startup (not used with FastAPI)
|
|
1386
1397
|
quiet: Suppress progress output when true
|
|
1387
1398
|
"""
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
graph_dir = Path(graph_dir)
|
|
1391
|
-
static_dir = Path(static_dir)
|
|
1392
|
-
|
|
1393
|
-
# Handle auto-port selection
|
|
1394
|
-
if auto_port and check_port_in_use(port, host):
|
|
1395
|
-
original_port = port
|
|
1396
|
-
port = find_available_port(port + 1)
|
|
1397
|
-
print(f"⚠️ Port {original_port} is in use, using {port} instead\n")
|
|
1398
|
-
|
|
1399
|
-
progress = None
|
|
1400
|
-
progress_console = None
|
|
1401
|
-
if show_progress and not quiet:
|
|
1402
|
-
try:
|
|
1403
|
-
from rich.console import Console
|
|
1404
|
-
from rich.progress import (
|
|
1405
|
-
BarColumn,
|
|
1406
|
-
Progress,
|
|
1407
|
-
SpinnerColumn,
|
|
1408
|
-
TextColumn,
|
|
1409
|
-
TimeElapsedColumn,
|
|
1410
|
-
)
|
|
1411
|
-
except Exception:
|
|
1412
|
-
progress = None
|
|
1413
|
-
else:
|
|
1414
|
-
progress_console = Console()
|
|
1415
|
-
progress = Progress(
|
|
1416
|
-
SpinnerColumn(),
|
|
1417
|
-
TextColumn("{task.description}"),
|
|
1418
|
-
BarColumn(),
|
|
1419
|
-
TimeElapsedColumn(),
|
|
1420
|
-
console=progress_console,
|
|
1421
|
-
transient=True,
|
|
1422
|
-
)
|
|
1399
|
+
import asyncio
|
|
1423
1400
|
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
server = None
|
|
1430
|
-
watcher = None
|
|
1431
|
-
|
|
1432
|
-
events_dir = graph_dir / "events"
|
|
1433
|
-
db_path = graph_dir / "index.sqlite"
|
|
1434
|
-
index_needs_build = (
|
|
1435
|
-
not db_path.exists() and events_dir.exists() and any(events_dir.glob("*.jsonl"))
|
|
1401
|
+
from htmlgraph.operations.fastapi_server import (
|
|
1402
|
+
FastAPIServerError,
|
|
1403
|
+
PortInUseError,
|
|
1404
|
+
run_fastapi_server,
|
|
1405
|
+
start_fastapi_server,
|
|
1436
1406
|
)
|
|
1437
1407
|
|
|
1438
|
-
|
|
1439
|
-
# Auto-sync dashboard files
|
|
1440
|
-
try:
|
|
1441
|
-
if sync_dashboard_files(static_dir):
|
|
1442
|
-
print("⚠️ Dashboard files out of sync")
|
|
1443
|
-
print("✅ Synced dashboard.html → index.html\n")
|
|
1444
|
-
except PermissionError as e:
|
|
1445
|
-
print(f"⚠️ Warning: Unable to sync dashboard files: {e}")
|
|
1446
|
-
print(
|
|
1447
|
-
f" Run: cp src/python/htmlgraph/dashboard.html {static_dir / 'index.html'}\n"
|
|
1448
|
-
)
|
|
1449
|
-
except Exception as e:
|
|
1450
|
-
print(f"⚠️ Warning: Error during dashboard sync: {e}\n")
|
|
1451
|
-
|
|
1452
|
-
def create_graph_dirs() -> None:
|
|
1453
|
-
graph_dir.mkdir(parents=True, exist_ok=True)
|
|
1454
|
-
for collection in HtmlGraphAPIHandler.COLLECTIONS:
|
|
1455
|
-
(graph_dir / collection).mkdir(exist_ok=True)
|
|
1456
|
-
|
|
1457
|
-
def copy_stylesheet() -> None:
|
|
1458
|
-
styles_dest = graph_dir / "styles.css"
|
|
1459
|
-
if not styles_dest.exists():
|
|
1460
|
-
styles_src = Path(__file__).parent / "styles.css"
|
|
1461
|
-
if styles_src.exists():
|
|
1462
|
-
styles_dest.write_text(styles_src.read_text())
|
|
1463
|
-
|
|
1464
|
-
def build_analytics_index() -> None:
|
|
1465
|
-
if not index_needs_build:
|
|
1466
|
-
return
|
|
1467
|
-
with status_context("Building analytics index..."):
|
|
1468
|
-
try:
|
|
1469
|
-
log = JsonlEventLog(events_dir)
|
|
1470
|
-
index = AnalyticsIndex(db_path)
|
|
1471
|
-
events = (event for _, event in log.iter_events())
|
|
1472
|
-
index.rebuild_from_events(events)
|
|
1473
|
-
except Exception as e:
|
|
1474
|
-
print(f"⚠️ Warning: Failed to build analytics index: {e}")
|
|
1475
|
-
|
|
1476
|
-
def configure_handler() -> None:
|
|
1477
|
-
HtmlGraphAPIHandler.graph_dir = graph_dir
|
|
1478
|
-
HtmlGraphAPIHandler.static_dir = static_dir
|
|
1479
|
-
HtmlGraphAPIHandler.graphs = {}
|
|
1480
|
-
HtmlGraphAPIHandler.analytics_db = None
|
|
1481
|
-
|
|
1482
|
-
def create_server() -> None:
|
|
1483
|
-
nonlocal server
|
|
1484
|
-
try:
|
|
1485
|
-
server = HTTPServer((host, port), HtmlGraphAPIHandler)
|
|
1486
|
-
except OSError as e:
|
|
1487
|
-
# Handle "Address already in use" error
|
|
1488
|
-
if e.errno == 48 or "Address already in use" in str(e):
|
|
1489
|
-
print(f"\n❌ Port {port} is already in use\n")
|
|
1490
|
-
print("Solutions:")
|
|
1491
|
-
print(" 1. Use a different port:")
|
|
1492
|
-
print(f" htmlgraph serve --port {port + 1}\n")
|
|
1493
|
-
print(" 2. Let htmlgraph automatically find an available port:")
|
|
1494
|
-
print(" htmlgraph serve --auto-port\n")
|
|
1495
|
-
print(f" 3. Find and kill the process using port {port}:")
|
|
1496
|
-
print(f" lsof -ti:{port} | xargs kill -9\n")
|
|
1497
|
-
|
|
1498
|
-
# Try to find and suggest an available port
|
|
1499
|
-
try:
|
|
1500
|
-
alt_port = find_available_port(port + 1)
|
|
1501
|
-
print(f"💡 Found available port: {alt_port}")
|
|
1502
|
-
print(f" Run: htmlgraph serve --port {alt_port}\n")
|
|
1503
|
-
except OSError:
|
|
1504
|
-
pass
|
|
1408
|
+
graph_dir = Path(graph_dir)
|
|
1505
1409
|
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1410
|
+
# Ensure graph directory exists
|
|
1411
|
+
graph_dir.mkdir(parents=True, exist_ok=True)
|
|
1412
|
+
for collection in HtmlGraphAPIHandler.COLLECTIONS:
|
|
1413
|
+
(graph_dir / collection).mkdir(exist_ok=True)
|
|
1509
1414
|
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1415
|
+
# Copy default stylesheet if not present
|
|
1416
|
+
styles_dest = graph_dir / "styles.css"
|
|
1417
|
+
if not styles_dest.exists():
|
|
1418
|
+
styles_src = Path(__file__).parent / "styles.css"
|
|
1419
|
+
if styles_src.exists():
|
|
1420
|
+
styles_dest.write_text(styles_src.read_text())
|
|
1514
1421
|
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
handler = HtmlGraphAPIHandler
|
|
1518
|
-
if collection not in handler.graphs:
|
|
1519
|
-
collection_dir = handler.graph_dir / collection
|
|
1520
|
-
handler.graphs[collection] = HtmlGraph(
|
|
1521
|
-
collection_dir, stylesheet_path="../styles.css", auto_load=True
|
|
1522
|
-
)
|
|
1523
|
-
return handler.graphs[collection]
|
|
1422
|
+
# Database path - use htmlgraph.db in the graph directory
|
|
1423
|
+
db_path = str(graph_dir / "htmlgraph.db")
|
|
1524
1424
|
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1425
|
+
try:
|
|
1426
|
+
result = start_fastapi_server(
|
|
1427
|
+
port=port,
|
|
1428
|
+
host=host,
|
|
1429
|
+
db_path=db_path,
|
|
1430
|
+
auto_port=auto_port,
|
|
1431
|
+
reload=watch, # Map watch to reload for FastAPI
|
|
1529
1432
|
)
|
|
1530
|
-
watcher.start()
|
|
1531
1433
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
]
|
|
1537
|
-
if index_needs_build:
|
|
1538
|
-
steps.append(("Build analytics index", build_analytics_index))
|
|
1539
|
-
steps.extend(
|
|
1540
|
-
[
|
|
1541
|
-
("Configure server", configure_handler),
|
|
1542
|
-
("Start HTTP server", create_server),
|
|
1543
|
-
("Start file watcher", start_watcher),
|
|
1544
|
-
]
|
|
1545
|
-
)
|
|
1434
|
+
# Print warnings if any
|
|
1435
|
+
for warning in result.warnings:
|
|
1436
|
+
if not quiet:
|
|
1437
|
+
print(f"⚠️ {warning}")
|
|
1546
1438
|
|
|
1547
|
-
|
|
1548
|
-
if
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
return
|
|
1552
|
-
with progress:
|
|
1553
|
-
task_id = progress.add_task(
|
|
1554
|
-
"Starting HtmlGraph server...", total=len(step_list)
|
|
1555
|
-
)
|
|
1556
|
-
for description, fn in step_list:
|
|
1557
|
-
progress.update(task_id, description=description)
|
|
1558
|
-
fn()
|
|
1559
|
-
progress.advance(task_id)
|
|
1560
|
-
|
|
1561
|
-
run_steps(steps)
|
|
1562
|
-
|
|
1563
|
-
watch_status = "Enabled" if watch else "Disabled"
|
|
1564
|
-
print(f"""
|
|
1439
|
+
# Print server info
|
|
1440
|
+
if not quiet:
|
|
1441
|
+
actual_port = result.config_used["port"]
|
|
1442
|
+
print(f"""
|
|
1565
1443
|
╔══════════════════════════════════════════════════════════════╗
|
|
1566
|
-
║
|
|
1444
|
+
║ HtmlGraph Server (FastAPI) ║
|
|
1567
1445
|
╠══════════════════════════════════════════════════════════════╣
|
|
1568
|
-
║ Dashboard:
|
|
1569
|
-
║ API:
|
|
1570
|
-
║
|
|
1571
|
-
║
|
|
1446
|
+
║ Dashboard: http://{host}:{actual_port}/
|
|
1447
|
+
║ API: http://{host}:{actual_port}/api/
|
|
1448
|
+
║ WebSocket: ws://{host}:{actual_port}/ws/events
|
|
1449
|
+
║ Graph Dir: {graph_dir}
|
|
1450
|
+
║ Database: {db_path}
|
|
1451
|
+
║ Auto-reload: {"Enabled" if watch else "Disabled"}
|
|
1572
1452
|
╚══════════════════════════════════════════════════════════════╝
|
|
1573
1453
|
|
|
1454
|
+
Features:
|
|
1455
|
+
• Real-time agent activity feed (HTMX + WebSocket)
|
|
1456
|
+
• Orchestration chains visualization
|
|
1457
|
+
• Feature tracker with Kanban view
|
|
1458
|
+
• Session metrics & performance analytics
|
|
1459
|
+
|
|
1574
1460
|
API Endpoints:
|
|
1575
|
-
GET /api/
|
|
1576
|
-
GET /api/
|
|
1577
|
-
GET /api/
|
|
1578
|
-
GET /api/
|
|
1579
|
-
|
|
1580
|
-
GET /api/analytics/continuity?feature_id=... - Feature continuity (requires index)
|
|
1581
|
-
GET /api/analytics/transitions - Tool transitions (requires index)
|
|
1582
|
-
|
|
1583
|
-
GET /api/{{collection}} - List nodes
|
|
1584
|
-
POST /api/{{collection}} - Create node
|
|
1585
|
-
GET /api/{{collection}}/{{id}} - Get node
|
|
1586
|
-
PUT /api/{{collection}}/{{id}} - Replace node
|
|
1587
|
-
PATCH /api/{{collection}}/{{id}} - Update node
|
|
1588
|
-
DELETE /api/{{collection}}/{{id}} - Delete node
|
|
1461
|
+
GET /api/events - List events
|
|
1462
|
+
GET /api/sessions - List sessions
|
|
1463
|
+
GET /api/orchestration - Orchestration data
|
|
1464
|
+
GET /api/initial-stats - Dashboard statistics
|
|
1465
|
+
WS /ws/events - Real-time event stream
|
|
1589
1466
|
|
|
1590
1467
|
Collections: {", ".join(HtmlGraphAPIHandler.COLLECTIONS)}
|
|
1591
1468
|
|
|
1592
1469
|
Press Ctrl+C to stop.
|
|
1593
1470
|
""")
|
|
1594
1471
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1472
|
+
# Run the server
|
|
1473
|
+
asyncio.run(run_fastapi_server(result.handle))
|
|
1474
|
+
|
|
1475
|
+
except PortInUseError:
|
|
1476
|
+
print(f"\n❌ Port {port} is already in use\n")
|
|
1477
|
+
print("Solutions:")
|
|
1478
|
+
print(" 1. Use a different port:")
|
|
1479
|
+
print(f" htmlgraph serve --port {port + 1}\n")
|
|
1480
|
+
print(" 2. Let htmlgraph automatically find an available port:")
|
|
1481
|
+
print(" htmlgraph serve --auto-port\n")
|
|
1482
|
+
print(f" 3. Find and kill the process using port {port}:")
|
|
1483
|
+
print(f" lsof -ti:{port} | xargs kill -9\n")
|
|
1484
|
+
|
|
1485
|
+
# Try to find and suggest an available port
|
|
1486
|
+
try:
|
|
1487
|
+
alt_port = find_available_port(port + 1)
|
|
1488
|
+
print(f"💡 Found available port: {alt_port}")
|
|
1489
|
+
print(f" Run: htmlgraph serve --port {alt_port}\n")
|
|
1490
|
+
except OSError:
|
|
1491
|
+
pass
|
|
1492
|
+
|
|
1493
|
+
sys.exit(1)
|
|
1494
|
+
|
|
1495
|
+
except FastAPIServerError as e:
|
|
1496
|
+
print(f"\n❌ Server error: {e}\n")
|
|
1497
|
+
sys.exit(1)
|
|
1498
|
+
|
|
1598
1499
|
except KeyboardInterrupt:
|
|
1599
1500
|
print("\nShutting down...")
|
|
1600
|
-
if watcher:
|
|
1601
|
-
watcher.stop()
|
|
1602
|
-
if server is not None:
|
|
1603
|
-
server.shutdown()
|
|
1604
1501
|
|
|
1605
1502
|
|
|
1606
1503
|
if __name__ == "__main__":
|