generated from coulomb/repo-seed
Add state-hub v0.1 — local-first state service for the Custodian
Implements the first live layer of the Custodian cognitive infrastructure: PostgreSQL schema, FastAPI REST API, FastMCP stdio server, and Observable Framework telemetry dashboard. - state-hub/: full stack (docker-compose, FastAPI, Alembic, MCP server, dashboard) - 5 DB tables: topics, workstreams, tasks, decisions, progress_events - 11 MCP tools + 5 resources registered in .mcp.json - Observable dashboard: Overview, Workstreams, Decisions, Progress pages - CLAUDE.md: session protocol (get_state_summary / add_progress_event ritual) - ~/.claude/CLAUDE.md: global cross-project reference to the hub - scripts/pull_image.py: WSL2 TLS-resilient Docker image downloader Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
15
dashboard/src/data/decisions.json.py
Normal file
15
dashboard/src/data/decisions.json.py
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Observable data loader: all decisions."""
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
API_BASE = os.environ.get("API_BASE", "http://127.0.0.1:8000").rstrip("/")
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(f"{API_BASE}/decisions", timeout=10) as resp:
|
||||
data = json.loads(resp.read())
|
||||
print(json.dumps(data))
|
||||
except urllib.error.URLError as e:
|
||||
print(json.dumps({"error": str(e), "decisions": []}))
|
||||
16
dashboard/src/data/progress.json.py
Normal file
16
dashboard/src/data/progress.json.py
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Observable data loader: recent progress events (last 200)."""
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
API_BASE = os.environ.get("API_BASE", "http://127.0.0.1:8000").rstrip("/")
|
||||
|
||||
try:
|
||||
url = f"{API_BASE}/progress?limit=200"
|
||||
with urllib.request.urlopen(url, timeout=10) as resp:
|
||||
data = json.loads(resp.read())
|
||||
print(json.dumps(data))
|
||||
except urllib.error.URLError as e:
|
||||
print(json.dumps({"error": str(e), "events": []}))
|
||||
31
dashboard/src/data/summary.json.py
Normal file
31
dashboard/src/data/summary.json.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Observable data loader: fetches /state/summary from the API."""
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
API_BASE = os.environ.get("API_BASE", "http://127.0.0.1:8000").rstrip("/")
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(f"{API_BASE}/state/summary", timeout=10) as resp:
|
||||
data = json.loads(resp.read())
|
||||
print(json.dumps(data))
|
||||
except urllib.error.URLError as e:
|
||||
# Return empty structure so the dashboard can show an error state
|
||||
print(json.dumps({
|
||||
"error": str(e),
|
||||
"generated_at": None,
|
||||
"totals": {
|
||||
"topics": {"active": 0, "paused": 0, "archived": 0, "total": 0},
|
||||
"workstreams": {"active": 0, "blocked": 0, "completed": 0, "archived": 0, "total": 0},
|
||||
"tasks": {"todo": 0, "in_progress": 0, "blocked": 0, "done": 0, "cancelled": 0, "total": 0},
|
||||
"decisions": {"open": 0, "resolved": 0, "escalated": 0, "superseded": 0, "total": 0},
|
||||
},
|
||||
"topics": [],
|
||||
"blocking_decisions": [],
|
||||
"blocked_tasks": [],
|
||||
"recent_progress": [],
|
||||
"open_workstreams": [],
|
||||
}))
|
||||
15
dashboard/src/data/workstreams.json.py
Normal file
15
dashboard/src/data/workstreams.json.py
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Observable data loader: all workstreams."""
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
|
||||
API_BASE = os.environ.get("API_BASE", "http://127.0.0.1:8000").rstrip("/")
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(f"{API_BASE}/workstreams", timeout=10) as resp:
|
||||
data = json.loads(resp.read())
|
||||
print(json.dumps(data))
|
||||
except urllib.error.URLError as e:
|
||||
print(json.dumps({"error": str(e), "workstreams": []}))
|
||||
Reference in New Issue
Block a user