htmlgraph 0.25.0__py3-none-any.whl → 0.26.2__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 (41) 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 +1 -1
  5. htmlgraph/api/main.py +252 -47
  6. htmlgraph/api/templates/dashboard.html +11 -0
  7. htmlgraph/api/templates/partials/activity-feed.html +517 -8
  8. htmlgraph/cli.py +1 -1
  9. htmlgraph/config.py +173 -96
  10. htmlgraph/dashboard.html +632 -7237
  11. htmlgraph/db/schema.py +258 -9
  12. htmlgraph/hooks/.htmlgraph/.session-warning-state.json +6 -0
  13. htmlgraph/hooks/.htmlgraph/agents.json +72 -0
  14. htmlgraph/hooks/.htmlgraph/index.sqlite +0 -0
  15. htmlgraph/hooks/cigs_pretool_enforcer.py +2 -2
  16. htmlgraph/hooks/concurrent_sessions.py +208 -0
  17. htmlgraph/hooks/context.py +88 -10
  18. htmlgraph/hooks/drift_handler.py +24 -20
  19. htmlgraph/hooks/event_tracker.py +264 -189
  20. htmlgraph/hooks/orchestrator.py +6 -4
  21. htmlgraph/hooks/orchestrator_reflector.py +4 -4
  22. htmlgraph/hooks/pretooluse.py +63 -36
  23. htmlgraph/hooks/prompt_analyzer.py +14 -25
  24. htmlgraph/hooks/session_handler.py +123 -69
  25. htmlgraph/hooks/state_manager.py +7 -4
  26. htmlgraph/hooks/subagent_stop.py +3 -2
  27. htmlgraph/hooks/validator.py +15 -11
  28. htmlgraph/operations/fastapi_server.py +2 -2
  29. htmlgraph/orchestration/headless_spawner.py +489 -16
  30. htmlgraph/orchestration/live_events.py +377 -0
  31. htmlgraph/server.py +100 -203
  32. htmlgraph-0.26.2.data/data/htmlgraph/dashboard.html +812 -0
  33. {htmlgraph-0.25.0.dist-info → htmlgraph-0.26.2.dist-info}/METADATA +1 -1
  34. {htmlgraph-0.25.0.dist-info → htmlgraph-0.26.2.dist-info}/RECORD +40 -32
  35. htmlgraph-0.25.0.data/data/htmlgraph/dashboard.html +0 -7417
  36. {htmlgraph-0.25.0.data → htmlgraph-0.26.2.data}/data/htmlgraph/styles.css +0 -0
  37. {htmlgraph-0.25.0.data → htmlgraph-0.26.2.data}/data/htmlgraph/templates/AGENTS.md.template +0 -0
  38. {htmlgraph-0.25.0.data → htmlgraph-0.26.2.data}/data/htmlgraph/templates/CLAUDE.md.template +0 -0
  39. {htmlgraph-0.25.0.data → htmlgraph-0.26.2.data}/data/htmlgraph/templates/GEMINI.md.template +0 -0
  40. {htmlgraph-0.25.0.dist-info → htmlgraph-0.26.2.dist-info}/WHEEL +0 -0
  41. {htmlgraph-0.25.0.dist-info → htmlgraph-0.26.2.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 HTTPServer, SimpleHTTPRequestHandler
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
- """Serve the bundled dashboard HTML if available."""
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
- Auto-syncs dashboard.html to index.html before starting.
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
- from contextlib import nullcontext
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
- def status_context(message: str) -> Any:
1425
- if progress_console is None:
1426
- return nullcontext()
1427
- return progress_console.status(message)
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
- def sync_dashboard() -> None:
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
- sys.exit(1)
1507
- # Re-raise other OSErrors
1508
- raise
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
- def start_watcher() -> None:
1511
- nonlocal watcher
1512
- if not watch:
1513
- return
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
- def get_graph(collection: str) -> HtmlGraph:
1516
- """Callback to get graph instance for a collection."""
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
- watcher = GraphWatcher(
1526
- graph_dir=graph_dir,
1527
- collections=HtmlGraphAPIHandler.COLLECTIONS,
1528
- get_graph_callback=get_graph,
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
- steps: list[tuple[str, Any]] = [
1533
- ("Sync dashboard files", sync_dashboard),
1534
- ("Create graph directories", create_graph_dirs),
1535
- ("Copy default stylesheet", copy_stylesheet),
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
- def run_steps(step_list: list[tuple[str, Any]]) -> None:
1548
- if progress is None:
1549
- for _, fn in step_list:
1550
- fn()
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
- HtmlGraph Server
1444
+ HtmlGraph Server (FastAPI)
1567
1445
  ╠══════════════════════════════════════════════════════════════╣
1568
- ║ Dashboard: http://{host}:{port}/
1569
- ║ API: http://{host}:{port}/api/
1570
- Graph Dir: {graph_dir}
1571
- Auto-reload: {watch_status}
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/status - Overall status
1576
- GET /api/collections - List collections
1577
- GET /api/query?status=todo - Query across collections
1578
- GET /api/analytics/overview - Analytics overview (requires index)
1579
- GET /api/analytics/features - Top features (requires index)
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
- try:
1596
- if server is not None:
1597
- server.serve_forever()
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__":