Initial commit

This commit is contained in:
Johan Jongsma 2026-02-01 08:03:51 +00:00
commit 32839ef049
12 changed files with 2167 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules/
.env
*.log
.DS_Store

BIN
dashboard-server Executable file

Binary file not shown.

53
data/briefing.json Normal file
View File

@ -0,0 +1,53 @@
{
"timestamp": "2026-01-28T23:30:00Z",
"type": "evening",
"markets": {
"indices": [
{ "symbol": "S&P 500", "value": "7,000", "change": "+0.3%", "direction": "up", "note": "Historic first!" },
{ "symbol": "NASDAQ", "value": "23,857", "change": "+0.17%", "direction": "up" },
{ "symbol": "NVDA", "value": "+1.5%", "change": "up", "direction": "up", "note": "China AI chip hopes" }
],
"stocks": [],
"commodities": [],
"forex": []
},
"weather": {
"status": "clear",
"headline": "St. Pete — Clear",
"temp": "43°F 71°F",
"details": "No tropical threats. Hurricane season starts June. Thunderstorms in Panhandle — you're clear.",
"alerts": []
},
"techNews": [
{
"headline": "🦞 Clawdbot → Moltbot (Rename)",
"source": "Forbes / The Register",
"summary": "Anthropic asked for name change (trademark). Now 'Moltbot' + 'Molty'. Same lobster soul, new shell. Security vuln (proxy misconfig) also fixed."
},
{
"headline": "Claude = Best AI Coding Assistant",
"source": "Wikipedia",
"summary": "As of Jan 2026, widely considered #1 for coding (with GPT-5.2 close). Went viral over winter holidays."
},
{
"headline": "DeepSeek: Manifold-Constrained Hyper-Connections",
"source": "Times of India",
"summary": "New training method scales AI without ballooning compute. Direct challenge to 'bigger = smarter' consensus."
},
{
"headline": "Chinese AI Models Hit 15% Global Share",
"source": "TrendForce",
"summary": "DeepSeek + Alibaba Qwen driving adoption. R1 model (Jan 2025) still disrupting with high performance at low cost."
},
{
"headline": "Anthropic: 'Wake Up to AI Risks'",
"source": "The Guardian",
"summary": "Dario Amodei warns humanity entering phase that will 'test who we are as a species'. World needs to wake up."
},
{
"headline": "ServiceNow + Anthropic Partnership",
"source": "Morningstar",
"summary": "Claude is now default model powering ServiceNow Build Agent. Enterprise AI push."
}
]
}

89
data/briefings.json Normal file
View File

@ -0,0 +1,89 @@
{
"briefings": [
{
"id": "172a3946",
"date": "2025-01-28",
"title": "Afternoon Brief - Jan 28",
"weather": "St. Petersburg FL — mild evening",
"markets": "Not checked this cycle",
"news": "ClawdNode gateway working, notifications + calls flowing",
"tasks": "✅ Git user hardened on Zurich VPS\n✅ ClawdNode v0.1 built (10 Kotlin files)\n✅ Gateway protocol fix\n✅ Memory review system\n✅ Claude Code updated (2.1.20 → 2.1.22)\n🔲 ClawdNode: rebuild + test\n🔲 OpenVAS vuln scan\n🔲 Uptime Kuma setup",
"summary": "ClawdNode breakthrough — notifications and calls flowing through custom gateway. Phone answered command OK (TelecomManager implementation pending). Flutter styleguide blocking deployment — priority for 7pm sprint.",
"created_at": "2026-01-28T22:41:56.65781346Z"
},
{
"id": "f5c12407",
"date": "2026-01-29",
"title": "Morning Brief - Jan 29, 2026",
"weather": "St. Petersburg FL: ☀️ Clear, 47°F now. High 64°F / Low 39°F. No rain expected.",
"markets": "/ES 7,023 (+0.2%) | /CL $62.76 (+0.6%) | Gold $5,550 (+3.9% 🚀) | TSLA $431 (+0.1%, pre-mkt +1.8%) | NABL ~$7.50",
"news": "• Anthropic revises Claude constitution, hints at AI consciousness (Fortune/TechCrunch)\n• Anthropic seeking $350B valuation (Vice)\n• Moltbot (fka Clawdbot) goes viral - WIRED: \"Taking Over Silicon Valley\"\n• TechCrunch deep-dive on Moltbot published 2 days ago",
"summary": "Gold surging nearly 4% on dollar weakness. Fed decision today. Clawdbot/Moltbot getting serious mainstream press coverage.",
"created_at": "2026-01-29T14:01:29.389328336Z"
},
{
"id": "bd403f5d",
"date": "2026-01-29",
"title": "Afternoon Brief - Jan 29",
"weather": "n/a",
"markets": "n/a",
"news": "n/a",
"tasks": "✅ Flutter InouText style library complete\n✅ Sora fonts (7 weights) installed\n✅ Azure Files backup POC files created\n✅ Morning brief delivered\n🔲 OpenVAS scan still running\n🔲 Top menu behavior verification pending",
"summary": "Productive day. inou Flutter now has centralized typography (InouText.*). Azure Files POC scaffolded but needs Go install to test. OpenVAS scan on home IP in progress.",
"created_at": "2026-01-29T22:00:14.776990456Z"
},
{
"id": "8e79d9bb",
"date": "2026-01-30",
"title": "Morning Brief - Jan 30, 2026",
"weather": "St. Pete FL: Weather service timeout - check locally",
"markets": "/ES 7,023 (+0.2%) recovering from -0.9% overnight | /CL $62.76 (+0.6%) | Gold $5,050 (-5.7% crash from ATH!) | TSLA $416.56 (-3.2%) | NABL $7.48 (-0.8%)",
"news": "🦞 MAJOR: Clawdbot officially rebranded to Moltbot after Anthropic trademark request - 60k+ GitHub stars, NYT Hard Fork podcast coverage today | 📉 Gold crashes 5.7% to $5,000 as Warsh Fed pick strengthens dollar | ⚠️ Security researchers flagging Moltbot exposed admin ports",
"summary": "Moltbot rebrand is THE story - viral growth continues despite (or because of?) security concerns. Gold selloff after hitting ATH. Markets mixed on Fed chair pick.",
"created_at": "2026-01-30T14:01:29.27006189Z"
},
{
"id": "57f78885",
"date": "2026-01-30",
"title": "Morning Brief — Jan 30, 2026",
"weather": "Service timed out",
"markets": "/ES: 7,023 (+0.2%), /CL: $62.76 (+0.6%), Gold: $5,050 (-5.7% crash from ATH), TSLA: $416.56 (-3.2%), NABL: $7.48 (-0.8%)",
"news": "🦞 Clawdbot → Moltbot rebrand (Anthropic trademark request). 60k+ GitHub stars, NYT Hard Fork podcast today. Security researchers flagging exposed admin ports and poisoned skills.\n\n📉 Gold -5.7% selloff as Kevin Warsh Fed chair pick strengthens dollar.",
"tasks": "S short position winning (fell 5.7% yesterday, Goldman cut PT to $16.50)",
"summary": "Gold crashed 5.7% on Fed chair news. Moltbot rebrand is official. Your S short continues to print.",
"created_at": "2026-01-30T15:01:11.886794741Z"
},
{
"id": "5dcba631",
"date": "2026-01-30",
"title": "Afternoon Brief - Jan 30",
"weather": "n/a",
"markets": "NABL $7.49 (+2.67%)",
"news": "Dutch coalition formed (D66/VVD/CDA), PVV in crisis",
"tasks": "✅ Chrome relay + K2.5 browser agent working\n✅ Browser SKILL.md + TOOLS.md updated\n✅ Screensaver disabled on james\n🔲 Proton Mail skill (potential project)",
"summary": "Browser optimization day. Chrome relay working for authenticated sites, K2.5 agent created for cheap browsing tasks. Email triage discussed - may build proton-mail skill using IMAP to Bridge.",
"created_at": "2026-01-30T22:00:11.549426164Z"
},
{
"id": "fe6d3974",
"date": "2026-01-31",
"title": "Morning Brief - January 31, 2026",
"weather": "🥶 COLD SNAP: 49-58°F today, low 36°F tonight (wind chill 27°F!). Windy gusts to 39mph. Unusual for Florida.",
"markets": "📉 Fri close: S\u0026P -0.4% (6,039), Nasdaq -0.9%, Dow -0.4% (but Jan positive). TSLA $430 (+3.3%). Gold CRASHED 9% to $4,880 after hitting $5,625 record. Silver -28%. Oil $65.85. BTC ~$83,900. 10Y: 4.25%",
"news": "🔥 Trump nominates Kevin Warsh to replace Powell as Fed Chair. OpenClaw rebrand getting major press (Forbes, CNET, IBM, HN frontpage). Anthropic: Claude constitution revamp, seeking $350B valuation.",
"summary": "Gold/silver record highs → massive pullback. Fed Chair bombshell. OpenClaw making waves. Cold weekend ahead.",
"created_at": "2026-01-31T14:01:07.575988996Z"
},
{
"id": "90f4436a",
"date": "2026-01-31",
"title": "Afternoon Brief - Jan 31",
"weather": "n/a",
"markets": "n/a",
"news": "OpenClaw 2026.1.30 released",
"tasks": "• Mail agent refactored (pure API)\n• inou-mobile project created (6 features)\n• Nuclei scan: 34 info, 0 critical\n• Email review: 5 action items",
"summary": "Productive Saturday. Mail agent is now a clean IMAP/SMTP API — I handle all triage. inou-mobile Flutter project bootstrapped with OCR, voice, biometrics. Security scan on inou.com passed clean. 5 emails need your attention (Barclays fraud alert, UPS fees, DigiKey, Cryo-Cell renewal, Health Link invoice).",
"created_at": "2026-01-31T22:00:18.214044416Z"
}
]
}

15
data/deliveries.json Normal file
View File

@ -0,0 +1,15 @@
{
"deliveries": [
{
"id": "20a209a9",
"carrier": "UPS",
"retailer": "Tri-Med",
"description": "Tri-Med Medical Supplies Order #3386",
"expected_date": "2026-01-27",
"status": "delivered",
"notes": "Sophia medical supplies. Import fee $43.25 paid.",
"created_at": "2026-02-01T06:30:58.42184085Z",
"updated_at": "2026-02-01T06:30:58.421840934Z"
}
]
}

137
data/news.json Normal file
View File

@ -0,0 +1,137 @@
{
"items": [
{
"id": "be53de27",
"title": "📧 Mail Agent Complete",
"body": "IMAP triage service fully operational. Service running, webhook to /hooks/mail configured, code pushed to git@zurich.inou.com:mail-agent.git",
"type": "success",
"source": "james",
"timestamp": "2026-01-31T12:18:34.029948077Z"
},
{
"id": "258d00e1",
"title": "🛡️ SentinelOne (S) Stock",
"body": "$14.06 (-0.07%). Near 52-week low ($13.46). YTD +6.3%, 1yr +41.7%. Short position underwater.",
"type": "error",
"source": "Yahoo Finance",
"timestamp": "2026-01-30T16:44:08.903372981Z"
},
{
"id": "eb38d258",
"title": "📉 Markets Down",
"body": "Dow -400pts, S\u0026P 500 and Nasdaq falling. Warsh expected to get Fed chair nod. Inflation report weighing on investors. 10-yr Treasury yields rising, gold slumping.",
"type": "warning",
"source": "MarketWatch",
"timestamp": "2026-01-30T16:44:08.883164076Z"
},
{
"id": "3bd7d16f",
"title": "🥶 Weekend Cold Snap Alert",
"body": "St. Petersburg FL: Sat 36-56°F, Sun 35-46°F. Lows in mid-30s. Protect plants/pets.",
"type": "warning",
"source": "Open-Meteo",
"timestamp": "2026-01-30T16:43:56.172216406Z"
},
{
"id": "d9d3de6d",
"title": "🦞 OpenClaw Rebrand",
"body": "Project formerly known as Clawdbot → Moltbot → now OpenClaw. Hacker News thread active (posted 4 min ago). IBM Think coverage. Wikipedia updated.",
"type": "info",
"source": "Hacker News/IBM",
"timestamp": "2026-01-30T16:43:56.152204714Z"
},
{
"id": "ed66fd37",
"title": "🦞 Moltbot → OpenClaw",
"body": "Another rebrand! Now openclaw.ai. 100k+ GitHub stars, 2M visitors/week. \"The lobster has molted into its final form.\"",
"type": "info",
"source": "@openclaw",
"timestamp": "2026-01-30T16:08:10.873278893Z"
},
{
"id": "9678ef17",
"title": "❄️ Cold Snap This Weekend",
"body": "Unusually cold for Florida. Sat Feb 1: 35-46°F. Sun Feb 2: 41-54°F. Bundle up.",
"type": "warning",
"source": "Weather",
"timestamp": "2026-01-30T15:51:08.892460168Z"
},
{
"id": "ff3fa10f",
"title": "Moltbot on NYT Hard Fork",
"body": "Podcast feature today. 60k+ GitHub stars. Security researchers flagging exposed admin ports and poisoned skills.",
"type": "info",
"source": "Tech",
"timestamp": "2026-01-30T15:45:17.539675069Z"
},
{
"id": "b45e3ba0",
"title": "S (SentinelOne) -5.7%",
"body": "Goldman cut PT to $16.50 from $19. Weak Q4 guidance ($271M vs $273.2M expected). Your short winning.",
"type": "success",
"source": "Markets",
"timestamp": "2026-01-30T15:45:17.51939529Z"
},
{
"id": "0b604cce",
"title": "Gold Crashes -5.7%",
"body": "Dropped from ATH ($5,050) as Kevin Warsh Fed chair pick firms up dollar.",
"type": "warning",
"source": "Markets",
"timestamp": "2026-01-30T15:45:17.495471762Z"
},
{
"id": "3a0a49bd",
"title": "MoltMobile Ready",
"body": "Android app renamed \u0026 restructured. Audio playback, SUPER ATTENTION MODE 🚨, and Remote Browser implemented. Gateway ready — just needs MiniMax API key.",
"type": "success",
"source": "james",
"timestamp": "2026-01-29T23:00:01.092986645Z"
},
{
"id": "e60edc2a",
"title": "🦞 Clawdbot → Moltbot",
"body": "Anthropic trademark request. Rebrand happened Jan 27. Security vuln (proxy misconfig) also patched. npm install -g moltbot@latest",
"type": "warning",
"source": "Forbes/Register",
"timestamp": "2026-01-28T23:31:04.72383776Z"
},
{
"id": "93f95d6d",
"title": "Memory Features Enabled",
"body": "memoryFlush + sessionMemory now active. Auto-save before compaction, search all past sessions.",
"type": "info",
"source": "Config",
"timestamp": "2026-01-28T23:28:10.385545581Z"
},
{
"id": "cce42405",
"title": "S\u0026P 500 Hits 7,000",
"body": "Historic milestone — first time ever. Tech concentration at 34%.",
"type": "success",
"source": "Markets",
"timestamp": "2026-01-28T23:28:10.369853409Z"
},
{
"id": "9315a907",
"title": "Dashboard Restructured",
"body": "News on left, tasks on right. News API added.",
"type": "success",
"timestamp": "2026-01-28T23:16:12.826580181Z"
},
{
"id": "1",
"title": "Dashboard Created",
"body": "Built this dashboard so we can track tasks and I can post updates. You get a Signal ping with a link when something's new.",
"type": "info",
"timestamp": "2026-01-26T19:50:00Z"
},
{
"id": "2",
"title": "Screenshot Skill Working",
"body": "Screenshot server deployed on Mac Mini. Both instances (James \u0026 CC) can now pull screenshots via HTTP.",
"type": "success",
"timestamp": "2026-01-26T19:45:00Z"
}
]
}

190
data/tasks.json Normal file
View File

@ -0,0 +1,190 @@
{
"tasks": [
{
"id": "1",
"title": "Flutter Styleguide",
"priority": "high",
"status": "in-progress",
"owner": "johan",
"domain": "inou",
"notes": "Typography section updated. Still need: Settings, Genetics, Notes, Supplements, Peptides, Empty State sections.",
"created_at": "0001-01-01T00:00:00Z",
"updated": "2026-01-28T09:25:23Z"
},
{
"id": "2",
"title": "Screenshot Skill",
"priority": "medium",
"status": "done",
"owner": "james",
"domain": "Infrastructure",
"notes": "Go server running on Mac Mini :9123. Both James and CC updated.",
"created_at": "0001-01-01T00:00:00Z",
"updated": "2026-01-28T09:25:23Z"
},
{
"id": "65381e9b",
"title": "Flutter Styleguide - match original",
"priority": "high",
"status": "done",
"owner": "james",
"domain": "inou",
"notes": "Typography fixed to match CSS. Added Settings, Genetics, Supplements, Peptides, Empty State sections.",
"created_at": "2026-01-28T09:38:30.886723188Z",
"updated": "2026-01-30T01:04:17Z"
},
{
"id": "7e51cdd9",
"title": "Apply new styleguide to all pages",
"priority": "high",
"status": "done",
"owner": "james",
"domain": "inou",
"notes": "Added missing sections to app styleguide:\n- Settings (LLM selector, Units dropdown)\n- Genetics (variants with Ask AI)\n- Notes (photos, timeline entries)\n- Supplements (timing, dosage)\n- Peptides (active/completed cycles)\n- Empty State pattern\n\nBoth design/flutter/ and app/lib/design/ now have complete styleguides matching web.",
"created_at": "2026-01-28T09:38:35.046193253Z",
"updated": "2026-01-30T01:06:21Z"
},
{
"id": "b31a2233",
"title": "Azure Files Backup - new project",
"priority": "high",
"status": "in-progress",
"owner": "james",
"domain": "Personal",
"notes": "Feature complete! Latest: commit 18ce1fa.\n\n✅ Go 1.22/1.24 installed\n✅ FlatBuffer schema + generated code\n✅ FlatBuffer serializer (full round-trip, ~3μs serialize, ~2μs deserialize)\n✅ Azure SDK integration (real client + upload methods)\n✅ Web UI (Go + htmx + Tailwind)\n✅ Postgres integration (TreeStore methods, connection helper, web UI wired)\n✅ Integration tests (CRUD, tree ops, stats)\n✅ Bug fixes: dirname, scanner path concatenation\n✅ Tree differ: addition detection implemented\n✅ Backup handler: complete (chunking, dedup, XOR hash)\n✅ Restore handler: complete (reassemble, upload to Azure)\n✅ Azure client upload methods (PutFile, CreateDirectory)\n✅ Comprehensive test suite\n✅ All tests passing\n✅ Pushed to git@zurich.inou.com:azure-backup.git\n\n🔲 Azure free trial account (needs Johan)\n🔲 End-to-end test (blocked by Azure)\n\nRun: PATH=/usr/local/go/bin:$PATH go run ./cmd/web --addr=:8080",
"created_at": "2026-01-28T09:38:40.823760714Z",
"updated": "2026-02-01T01:07:12Z"
},
{
"id": "63b96b35",
"text": "Set up OpenVAS vulnerability scanner",
"title": "OpenVAS",
"priority": "medium",
"status": "pending",
"owner": "johan",
"domain": "Infrastructure",
"created_at": "2026-01-28T10:12:17.26905489Z"
},
{
"id": "d5fcdd3e",
"text": "Set up Uptime Kuma monitoring",
"title": "Uptime Kuma",
"priority": "medium",
"status": "pending",
"owner": "johan",
"domain": "Infrastructure",
"created_at": "2026-01-28T10:12:17.286579243Z"
},
{
"id": "69acd59d",
"title": "Get browsing working for Moltbot",
"priority": "high",
"status": "done",
"owner": "james",
"domain": "Infrastructure",
"notes": "Browser was not running. Started clawd profile successfully.\n- browser:start works\n- browser:open works\n- browser:snapshot works\n- Tested on example.com and news.ycombinator.com\n\nBrowser runs headless with Playwright Chromium at /home/johan/.cache/ms-playwright/chromium-1208/\nUser data stored at /home/johan/.clawdbot/browser/clawd/user-data",
"created_at": "2026-01-28T23:31:51.295564296Z",
"updated": "2026-01-30T01:07:02Z"
},
{
"id": "8d01b5df",
"title": "Add Let's Encrypt cert for OpenVAS (Zurich)",
"priority": "medium",
"status": "done",
"owner": "james",
"domain": "Infrastructure",
"notes": "Installed Caddy 2.10.2 as reverse proxy on Zurich server.\n\nSetup:\n- Caddy auto-obtains LE cert for zurich.inou.com\n- Reverse proxies HTTPS :443 → localhost:9392\n- Certificate auto-renews\n\nNew URL: https://zurich.inou.com (no port needed)\nOld URL: https://zurich.inou.com:9392 (still works with cert warning)",
"created_at": "2026-01-28T23:36:09.91271142Z",
"updated": "2026-01-30T01:08:50Z"
},
{
"id": "13283514",
"title": "Configure OpenVAS SOC2 scan for inou.com",
"priority": "high",
"status": "done",
"owner": "james",
"domain": "inou",
"notes": "Scan started via Task Wizard. Target: inou.com. Task: Immediate scan of IP inou.com (Status: Requested). Full scan running in OpenVAS at https://zurich.inou.com:9392",
"created_at": "2026-01-28T23:38:17.837376332Z",
"updated": "2026-01-29T10:18:13Z"
},
{
"id": "690de572",
"title": "Mail Agent - IMAP triage service",
"priority": "medium",
"status": "done",
"owner": "james",
"domain": "Infrastructure",
"notes": "✅ COMPLETE\n\n- IMAP API running (port 8025)\n- IMAP IDLE watching INBOX\n- Webhook to /hooks/mail configured\n- I receive new mail and decide: archive, delete, reply, or escalate\n- Systemd service: mail-agent.service\n\nNo L1 model needed — I handle all triage directly.",
"created_at": "2026-01-31T01:09:07.185371312Z",
"updated": "2026-01-31T12:16:18Z"
},
{
"id": "3d3e6483",
"text": "Mikhail FiL plan. Do NOT renew - he is leaving in ~1 month.",
"title": "Mint Mobile renewal coming up",
"priority": "low",
"status": "pending",
"owner": "johan",
"domain": "Personal",
"notes": "Email received Jan 26. This is an upsell for 12-month plan. Let it lapse.",
"created_at": "2026-01-31T10:13:26.640600581Z"
},
{
"id": "bdedb0c2",
"text": "Shipment has import fees that need payment",
"title": "UPS Import Fees Due",
"priority": "high",
"status": "pending",
"owner": "johan",
"domain": "Personal",
"created_at": "2026-01-31T10:14:40.918414184Z"
},
{
"id": "ad5b1211",
"text": "Mobile phlebotomy invoice received - needs payment",
"title": "Health Link Phlebotomy Invoice #000046",
"priority": "medium",
"status": "pending",
"owner": "johan",
"domain": "Personal",
"created_at": "2026-01-31T10:14:40.926185677Z"
},
{
"id": "25f4e8ed",
"text": "Add missing headers: X-Content-Type-Options, X-Frame-Options, X-XSS-Protection, HSTS, CSP, Referrer-Policy, Permissions-Policy",
"title": "Add security headers to Caddy",
"priority": "high",
"status": "done",
"owner": "james",
"domain": "Infrastructure",
"notes": "Added security headers via Tailscale SSH. Verified with curl.",
"created_at": "2026-01-31T23:50:16.714091775Z",
"updated": "2026-02-01T00:00:45Z"
},
{
"id": "6aeed4ac",
"text": "Set up ~/dev/docs/soc2/nuclei-scans/ with baseline report from Jan 31",
"title": "Create nuclei reporting structure",
"priority": "medium",
"status": "done",
"owner": "james",
"domain": "Infrastructure",
"notes": "Created ~/dev/docs/soc2/nuclei-scans/2026-01/ with baseline report",
"created_at": "2026-01-31T23:50:16.731454991Z",
"updated": "2026-01-31T23:51:53Z"
},
{
"id": "270424d3",
"text": "Cron job for Sundays, critical/high templates only",
"title": "Add weekly nuclei light scan",
"priority": "low",
"status": "done",
"owner": "james",
"domain": "Infrastructure",
"notes": "Added nuclei-weekly cron job (Sundays 10am ET)",
"created_at": "2026-01-31T23:50:16.74671578Z",
"updated": "2026-01-31T23:51:53Z"
}
]
}

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module james-dashboard
go 1.22.5
require github.com/google/uuid v1.6.0 // indirect

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=

606
index.html Normal file
View File

@ -0,0 +1,606 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>James Dashboard</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Sora:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg: #F8F7F6;
--bg-card: #FFFFFF;
--border: #E5E2DE;
--text: #1C1917;
--text-muted: #78716C;
--accent: #B45309;
--accent-light: #FEF3C7;
--success: #059669;
--success-light: #ECFDF5;
--danger: #DC2626;
--danger-light: #FEF2F2;
--info: #6366f1;
--info-light: #EEF2FF;
--james: #B45309;
--johan: #2563EB;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: "Sora", -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--bg);
color: var(--text);
line-height: 1.5;
padding: 24px;
}
.container { max-width: 1400px; margin: 0 auto; }
.three-column {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 24px;
}
@media (max-width: 1100px) {
.three-column { grid-template-columns: 1fr 1fr; }
.column-done { grid-column: span 2; }
}
@media (max-width: 700px) {
.three-column { grid-template-columns: 1fr; }
.column-done { grid-column: span 1; }
}
header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid var(--border);
}
h1 { font-size: 1.75rem; font-weight: 700; }
h1 .accent { color: var(--accent); }
.header-right {
display: flex;
align-items: center;
gap: 16px;
}
.updated { font-size: 0.8rem; color: var(--text-muted); }
/* Agents Bar */
.agents-bar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 20px;
padding: 12px 16px;
background: var(--bg-card);
border-radius: 10px;
border: 1px solid var(--border);
}
.agents-label {
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--text-muted);
}
.agents-links {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.agent-link {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
background: var(--bg);
border-radius: 8px;
text-decoration: none;
color: var(--text);
font-size: 0.85rem;
font-weight: 500;
transition: background 0.15s, transform 0.1s;
border: 1px solid var(--border);
}
.agent-link:hover {
background: var(--accent-light);
transform: translateY(-1px);
}
.agent-link.default {
background: var(--accent-light);
border-color: var(--accent);
}
.agent-emoji { font-size: 1rem; }
.section { margin-bottom: 20px; }
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.section-header-left {
display: flex;
align-items: center;
gap: 10px;
}
.section-indicator {
width: 4px;
height: 20px;
border-radius: 2px;
}
.section-indicator.alerts { background: var(--danger); }
.section-indicator.james { background: var(--james); }
.section-indicator.johan { background: var(--johan); }
.section-indicator.progress { background: var(--info); }
.section-indicator.done { background: var(--success); }
.section-title {
font-size: 0.7rem;
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text-muted);
}
.section-count {
font-size: 0.7rem;
color: var(--text-muted);
background: var(--bg);
padding: 2px 8px;
border-radius: 10px;
}
.card {
background: var(--bg-card);
border-radius: 12px;
border: 1px solid var(--border);
overflow: hidden;
}
.card-collapsed .card-content { display: none; }
.card-collapsed .collapse-toggle::after { content: '▶'; }
.collapse-toggle {
cursor: pointer;
user-select: none;
font-size: 0.65rem;
color: var(--text-muted);
}
.collapse-toggle::after { content: '▼'; margin-left: 4px; }
/* Alerts/News */
.alert-item {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
}
.alert-item:last-child { border-bottom: none; }
.alert-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 4px;
}
.alert-title {
font-weight: 500;
font-size: 0.9rem;
display: flex;
align-items: center;
gap: 8px;
}
.alert-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.alert-dot.error { background: var(--danger); }
.alert-dot.warning { background: var(--accent); }
.alert-dot.success { background: var(--success); }
.alert-dot.info { background: var(--info); }
.alert-time {
font-size: 0.75rem;
color: var(--text-muted);
white-space: nowrap;
}
.alert-body {
font-size: 0.8rem;
color: var(--text-muted);
margin-left: 16px;
}
.alert-source {
font-size: 0.7rem;
color: var(--text-muted);
margin-left: 16px;
margin-top: 4px;
font-style: italic;
}
/* Tasks */
.task-item {
padding: 12px 16px;
border-bottom: 1px solid var(--border);
}
.task-item:last-child { border-bottom: none; }
.task-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 8px;
}
.task-title {
font-weight: 500;
font-size: 0.85rem;
flex: 1;
}
.task-domain {
font-size: 0.7rem;
color: var(--text-muted);
background: var(--bg);
padding: 2px 6px;
border-radius: 4px;
}
.task-notes {
font-size: 0.75rem;
color: var(--text-muted);
margin-top: 6px;
line-height: 1.4;
}
.task-meta {
display: flex;
gap: 6px;
margin-top: 8px;
}
.badge {
font-size: 0.65rem;
font-weight: 500;
padding: 2px 8px;
border-radius: 4px;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.badge-high { background: var(--danger-light); color: var(--danger); }
.badge-medium { background: var(--accent-light); color: var(--accent); }
.badge-low { background: var(--bg); color: var(--text-muted); }
.badge-done { background: var(--success-light); color: var(--success); }
.badge-in-progress { background: var(--info-light); color: var(--info); }
.badge-pending { background: var(--bg); color: var(--text-muted); }
.empty {
padding: 24px;
text-align: center;
color: var(--text-muted);
font-size: 0.85rem;
}
/* Latest Briefing Banner */
.briefing-banner {
background: linear-gradient(135deg, #8B5CF6 0%, #6366f1 100%);
color: white;
padding: 16px 20px;
border-radius: 12px;
margin-bottom: 24px;
}
.briefing-banner-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.briefing-banner-title {
font-weight: 600;
font-size: 0.9rem;
}
.briefing-banner-time {
font-size: 0.75rem;
opacity: 0.8;
}
.briefing-banner-summary {
font-size: 0.8rem;
opacity: 0.9;
line-height: 1.5;
}
@media (max-width: 600px) {
body { padding: 16px; }
h1 { font-size: 1.25rem; }
}
</style>
</head>
<body>
<div class="container">
<header>
<h1><span class="accent">James</span> Dashboard</h1>
<div class="header-right">
<span class="updated" id="last-updated">Loading...</span>
</div>
</header>
<!-- Latest Briefing Banner -->
<div class="briefing-banner" id="briefing-banner">
<div class="briefing-banner-header">
<span class="briefing-banner-title">Loading briefing...</span>
</div>
</div>
<!-- Agents Quick Links -->
<div class="agents-bar" id="agents-bar">
<span class="agents-label">Agents:</span>
<div class="agents-links" id="agents-links">
<span class="loading">Loading...</span>
</div>
</div>
<div class="three-column">
<!-- Column 1: Alerts + James Tasks -->
<div class="column">
<div class="section">
<div class="section-header">
<div class="section-header-left">
<div class="section-indicator alerts"></div>
<span class="section-title">Alerts</span>
</div>
<span class="section-count" id="alerts-count">0</span>
</div>
<div class="card" id="alerts-container">
<div class="empty">No alerts</div>
</div>
</div>
<div class="section">
<div class="section-header">
<div class="section-header-left">
<div class="section-indicator james"></div>
<span class="section-title">James — Pending</span>
</div>
<span class="section-count" id="james-pending-count">0</span>
</div>
<div class="card" id="james-pending-container">
<div class="empty">No pending tasks</div>
</div>
</div>
</div>
<!-- Column 2: Johan Tasks + In Progress -->
<div class="column">
<div class="section">
<div class="section-header">
<div class="section-header-left">
<div class="section-indicator johan"></div>
<span class="section-title">Johan — Pending</span>
</div>
<span class="section-count" id="johan-pending-count">0</span>
</div>
<div class="card" id="johan-pending-container">
<div class="empty">No pending tasks</div>
</div>
</div>
<div class="section">
<div class="section-header">
<div class="section-header-left">
<div class="section-indicator progress"></div>
<span class="section-title">In Progress</span>
</div>
<span class="section-count" id="progress-count">0</span>
</div>
<div class="card" id="progress-container">
<div class="empty">Nothing in progress</div>
</div>
</div>
</div>
<!-- Column 3: Done -->
<div class="column column-done">
<div class="section">
<div class="section-header">
<div class="section-header-left">
<div class="section-indicator done"></div>
<span class="section-title">Done</span>
<span class="collapse-toggle" onclick="toggleDone()"></span>
</div>
<span class="section-count" id="done-count">0</span>
</div>
<div class="card" id="done-container">
<div class="empty">No completed tasks</div>
</div>
</div>
</div>
</div>
</div>
<script>
let doneCollapsed = false;
function toggleDone() {
doneCollapsed = !doneCollapsed;
document.getElementById('done-container').parentElement.classList.toggle('card-collapsed', doneCollapsed);
}
function formatTime(iso) {
const d = new Date(iso);
const now = new Date();
const diff = now - d;
if (diff < 3600000) return Math.floor(diff / 60000) + 'm ago';
if (diff < 86400000) return Math.floor(diff / 3600000) + 'h ago';
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
}
function priorityBadge(priority) {
return `<span class="badge badge-${priority}">${priority}</span>`;
}
function ownerBadge(owner) {
const color = owner === 'james' ? 'var(--james)' : 'var(--johan)';
return `<span class="badge" style="background: ${color}20; color: ${color}">${owner}</span>`;
}
function renderTask(t, showOwner = false) {
return `
<div class="task-item">
<div class="task-header">
<div class="task-title">${t.title}</div>
${t.domain ? `<span class="task-domain">${t.domain}</span>` : ''}
</div>
${t.notes ? `<div class="task-notes">${t.notes}</div>` : ''}
<div class="task-meta">
${priorityBadge(t.priority)}
${showOwner ? ownerBadge(t.owner) : ''}
</div>
</div>
`;
}
function renderAlert(n) {
return `
<div class="alert-item">
<div class="alert-header">
<div class="alert-title">
<span class="alert-dot ${n.type}"></span>
${n.title}
</div>
<span class="alert-time">${formatTime(n.timestamp)}</span>
</div>
<div class="alert-body">${n.body}</div>
${n.source ? `<div class="alert-source">— ${n.source}</div>` : ''}
</div>
`;
}
async function loadData() {
try {
const [tasksRes, newsRes, briefingsRes] = await Promise.all([
fetch('/api/tasks'),
fetch('/api/news'),
fetch('/api/briefings')
]);
const tasksData = await tasksRes.json();
const newsData = await newsRes.json();
const briefingsData = await briefingsRes.json();
const tasks = tasksData.tasks || [];
const news = newsData.items || [];
const briefings = briefingsData.briefings || [];
// Filter tasks by owner and status
const jamesPending = tasks.filter(t => t.owner === 'james' && t.status === 'pending');
const johanPending = tasks.filter(t => t.owner === 'johan' && t.status === 'pending');
const inProgress = tasks.filter(t => t.status === 'in-progress');
const done = tasks.filter(t => t.status === 'done').slice(0, 10); // Last 10
// Filter news to last 24h only
const oneDayAgo = Date.now() - 86400000;
const recentNews = news.filter(n => new Date(n.timestamp).getTime() > oneDayAgo);
// Render alerts
document.getElementById('alerts-count').textContent = recentNews.length;
document.getElementById('alerts-container').innerHTML = recentNews.length
? recentNews.map(renderAlert).join('')
: '<div class="empty">No alerts in last 24h</div>';
// Render task groups
document.getElementById('james-pending-count').textContent = jamesPending.length;
document.getElementById('james-pending-container').innerHTML = jamesPending.length
? jamesPending.map(t => renderTask(t)).join('')
: '<div class="empty">No pending tasks</div>';
document.getElementById('johan-pending-count').textContent = johanPending.length;
document.getElementById('johan-pending-container').innerHTML = johanPending.length
? johanPending.map(t => renderTask(t)).join('')
: '<div class="empty">No pending tasks</div>';
document.getElementById('progress-count').textContent = inProgress.length;
document.getElementById('progress-container').innerHTML = inProgress.length
? inProgress.map(t => renderTask(t, true)).join('')
: '<div class="empty">Nothing in progress</div>';
document.getElementById('done-count').textContent = done.length;
document.getElementById('done-container').innerHTML = done.length
? done.map(t => renderTask(t, true)).join('')
: '<div class="empty">No completed tasks</div>';
// Render latest briefing banner
if (briefings.length > 0) {
const latest = briefings[0];
document.getElementById('briefing-banner').innerHTML = `
<div class="briefing-banner-header">
<span class="briefing-banner-title">📋 ${latest.title}</span>
<span class="briefing-banner-time">${formatTime(latest.created_at)}</span>
</div>
<div class="briefing-banner-summary">${latest.summary || latest.weather + ' • ' + (latest.markets || '').substring(0, 80) + '...'}</div>
`;
}
document.getElementById('last-updated').textContent = 'Updated ' + formatTime(new Date().toISOString());
} catch (e) {
console.error('Failed to load data:', e);
}
}
async function loadAgents() {
try {
const res = await fetch('/api/agents');
const data = await res.json();
const agents = data.agents || [];
if (agents.length === 0) {
document.getElementById('agents-links').innerHTML = '<span class="loading">No agents configured</span>';
return;
}
document.getElementById('agents-links').innerHTML = agents.map(a => `
<a href="${a.url}" target="_blank" class="agent-link ${a.default ? 'default' : ''}">
<span class="agent-emoji">${a.emoji}</span>
<span>${a.name}</span>
</a>
`).join('');
} catch (e) {
console.error('Failed to load agents:', e);
document.getElementById('agents-links').innerHTML = '<span class="loading">Failed to load</span>';
}
}
loadAgents();
loadData();
setInterval(loadData, 30000);
</script>
</body>
</html>

BIN
james-dashboard Executable file

Binary file not shown.

1065
server.go Normal file

File diff suppressed because it is too large Load Diff