diff --git a/dashboard-server b/dashboard-server index f27d0d0..f4ebd49 100755 Binary files a/dashboard-server and b/dashboard-server differ diff --git a/data/briefings.json b/data/briefings.json index b9afb65..8dabe24 100644 --- a/data/briefings.json +++ b/data/briefings.json @@ -84,6 +84,80 @@ "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" + }, + { + "id": "b6e48580", + "date": "2026-02-01", + "title": "Morning Brief - February 1, 2026", + "weather": "⚠️ 38°F — Unusually cold for Florida (normally 60-70°F this time of year)", + "markets": "/ES 7,023 (+0.2%) | /CL $62.76 (+0.6%) | Gold $4,745 (-11.4% 📉 CRASH) | TSLA $430 (+3.3%) | NABL $7.49 (+2.7%)", + "news": "• GOLD CRASH: -11% in one day, Chinese speculators blamed for setting stage\n• OpenClaw: Now has Wikipedia page, HN discussion on real users, Fortune warns of \"next AI security crisis\", Yahoo Finance covers hosted platform launch\n• Anthropic: Opening Bengaluru office (India expansion), reportedly raising at $350B valuation, 40% enterprise market share (up from 24%)\n• Trump: Picked Kevin Warsh as Fed chair candidate (markets digesting)", + "summary": "Gold crashed 11% (biggest story). OpenClaw getting mainstream press attention. Cold snap in Florida.", + "created_at": "2026-02-01T14:01:35.363628739Z" + }, + { + "id": "164feb83", + "date": "2026-02-01", + "title": "Afternoon Brief - Feb 1", + "weather": "n/a", + "markets": "n/a", + "news": "Scanner out for delivery", + "tasks": "✅ Weekly memory synthesis\n✅ Doc management system\n✅ Monthly Nuclei scan (clean)\n✅ inou-mobile theme\n✅ SOC2 security headers\n🔲 Azure Backup (needs az login)\n🔲 inou-mobile backend", + "summary": "Productive Sunday. Doc pipeline complete, security scan passed, scanner arriving. Azure needs your MFA for az login when ready.", + "created_at": "2026-02-01T22:00:15.233371588Z" + }, + { + "id": "465a1970", + "date": "2026-02-02", + "title": "Morning Brief - Feb 2, 2026", + "weather": "☀️ St. Pete FL: Typical February (60s-70s). No alerts.", + "markets": "/ES: 6,965.75 (-0.4%) | /CL: $65.21 | Gold: $4,754 (-2.7%) | TSLA: $429.63 | NABL: Earnings Feb 19, RBC Buy", + "news": "🦞 OpenClaw: Forbes reports exposed interfaces found (security concerns growing) | 🤖 Anthropic: Updated Claude constitution, seeking $350B valuation, Bengaluru office | 🏛️ Trump: 7 EOs in 2026, latest on Long Island Rail Road labor", + "summary": "Markets slightly red. Gold pulling back after recent highs. OpenClaw security story gaining traction. Anthropic constitution update notable.", + "created_at": "2026-02-02T14:01:22.100459473Z" + }, + { + "id": "cb710c1d", + "date": "2026-02-02", + "title": "Afternoon Brief - Feb 2", + "weather": "n/a", + "markets": "n/a", + "news": "n/a", + "tasks": "✅ Azure Backup: Docker + K8s + CI/CD complete\n✅ Inbox zero maintained\n✅ K2.5 watchdog clear\n🔲 Azure testing (blocked: needs az login MFA)", + "summary": "Productive day. Azure Backup now has full deployment pipeline (Docker, K8s manifests, GitHub Actions). Ready for real Azure testing once you do az login.", + "created_at": "2026-02-02T22:00:12.505497949Z" + }, + { + "id": "70c615eb", + "date": "2026-02-03", + "title": "Morning Brief - Feb 3, 2026", + "weather": "☀️ 48-68°F — Sunny, breezy (NW 9mph). Nothing exceptional.", + "markets": "📈 EQUITIES: Dow +515 (+1.05%) to 49,408 | S\u0026P 500 +0.54% to 6,976 (touched 7,000 intraday!) | Nasdaq +0.56% to 23,592. Futures Tue: S\u0026P +0.2%, Dow +130pts. Palantir +11% (blowout earnings). NVDA -3% (OpenAI investment stalled).\n\n🪙 GOLD: Wild ride. Crashed Friday on Warsh Fed pick → $4,600s. Now rebounding hard +5.5% to ~$4,914. Biggest daily gain since Nov 2008. Silver also recovering +9%.\n\n🛢️ OIL: WTI crashed -4.7% to $62.14/bbl on Monday. Now hovering ~$56-62 range.\n\n📉 NABL: $6.15 — down 18.4% in 30 days, 15.8% YTD. B. Riley initiated Buy rating. Analysts target $9-10. Still sliding.\n\n🚗 TSLA: ~$418-421 range. Down 3.5% Monday (BYD competition, Europe sales stalling). Slightly up premarket Tue on cheaper Model Y launch.", + "news": "🏛️ FED: Trump picked Kevin Warsh as next Fed Chair (replacing Powell in May). Warsh is an inflation hawk → triggered massive precious metals selloff Friday. Gold crashed from highs, now recovering.\n\n🤖 ANTHROPIC/CLAUDE:\n• Claude Sonnet 5 'Fennec' leaked via Vertex AI logs — internal date string Feb 3, 2026. May drop during Super Bowl week.\n• Anthropic struck F1 deal with Williams Racing — Claude branding on cars, named 'Official Thinking Partner'. Livery reveal TODAY (Feb 3).\n• Anthropic launching legal tech plugins for Cowork platform.\n• Wikipedia notes Opus 4.5 widely considered best AI coding assistant.\n\n📊 EARNINGS WEEK: 100+ S\u0026P 500 companies reporting. Alphabet Wed, Amazon later this week. AI efficiency/profit growth is the theme.\n\n🌍 TARIFFS: One-year anniversary analysis pieces running. Trump threatening Canada/Mexico again. Trade patterns surprisingly resilient so far per PIIE.", + "summary": "Big week: Warsh Fed pick shook metals (now recovering), S\u0026P flirting with 7K, massive earnings week ahead. Claude Sonnet 5 may drop today. NABL continues bleeding at $6.15.", + "created_at": "2026-02-03T14:01:47.091442183Z" + }, + { + "id": "5f0f68af", + "date": "2026-02-03", + "title": "Afternoon Brief - Feb 3", + "weather": "n/a", + "markets": "n/a", + "news": "n/a", + "tasks": "✅ Done: Dashboard cleanup (11 tasks), security patches + reboot Zurich VPS, 3 new Uptime Kuma monitors, service health script, inou caddy fix script ready, email/WhatsApp triage\n🔲 Open: Azure Backup (blocked: az login MFA), inou.com indexing (blocked: caddy SSH), Flutter Styleguide, Mint Mobile renewal\n⚠️ Blockers: az login needs Johan MFA, caddy SSH for www redirect\n📊 Claude: 68% weekly", + "summary": "Productive infra day. Zurich VPS patched \u0026 rebooted, monitoring expanded. Two items blocked on Johan. OpenClaw update available (2026.2.1). Inbox clear.", + "created_at": "2026-02-03T22:00:15.809207534Z" + }, + { + "id": "a4d4aacc", + "date": "2026-02-04", + "title": "Morning Brief — Wed Feb 4, 2026", + "weather": "Florida Severe Weather Awareness Week (statewide tornado drill tomorrow). No active threats — just the usual.", + "markets": "📉 S\u0026P 500: 6,894 (-0.34%) | NASDAQ: 22,984 (-1.17%) | Dow: 49,529 (+0.58%)\n💰 BTC: $73,880 (-2.3%) | ETH: $2,152 (-3.4%)\n🏆 Gold/Silver: recovering after Friday crash, manipulation talk\n\n🎯 SentinelOne (S): NEW 52-WEEK LOW at $13.27 — your short is printing. Penserra Capital dumped 524K shares. 50-day MA: $14.90, 200-day MA: $16.62. Insider buying ($600K by director Mark Peek) but price action says sellers winning.\n\n📊 NABL: B. Riley initiated Buy coverage, PT $10. New board member Patrick Pulvermueller. No PE/going-private rumors detected.", + "news": "🤖 AI: Claude Sonnet 5 release IMMINENT — found gated on Google Vertex AI alongside claude-opus-4-6. Anthropic had 4 outages yesterday (prep?). AlexFinn says drop everything when it lands.\n\n🦞 OpenClaw 2026.2.2 released: 169 commits, 25 contributors. Feishu/Lark support, tsdown migration, security hardening, QMD memory plugin.\n\n🇺🇸 Trump: Met with Colombian president on narcotrafficking cooperation. Otherwise focused on Melania documentary promotion.\n\n🇳🇱 Dutch: Box 3 debate continues — analysis shows Spaar BV yields 428% more than Box 3 reform path, but 26.9% exit tax is the catch.\n\n📈 Tech rotation: Dow up while NASDAQ -1.17%. Capital moving from tech/AI to industrials, healthcare, financials. Google earnings tonight — key catalyst.", + "tasks": "⚠️ Claude usage fetch script broken (missing curl_cffi module) — needs pip install", + "summary": "Markets mixed with clear tech rotation. Your S short is winning big — new 52-week low. Sonnet 5 imminent (huge for us). OpenClaw shipped a solid release. No urgent action needed.", + "created_at": "2026-02-04T11:09:52.126683565-05:00" } ] } \ No newline at end of file diff --git a/data/deliveries.json b/data/deliveries.json index 59b98a7..bdebf69 100644 --- a/data/deliveries.json +++ b/data/deliveries.json @@ -10,6 +10,120 @@ "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" + }, + { + "id": "ef54b72b", + "carrier": "Amazon", + "retailer": "Amazon", + "description": "Brother ADS-4300N Professional Desktop Scanner", + "expected_date": "2026-02-02", + "status": "delivered", + "notes": "Order #112-7614761-1552234 - $427.99 | Delivered Feb 2 - left near front door", + "created_at": "2026-02-01T19:30:52.407998449Z", + "updated_at": "2026-02-02T22:54:22.634574588Z" + }, + { + "id": "627e730e", + "carrier": "eBay", + "retailer": "eBay", + "description": "Intel Xeon E3 SR14S CPU", + "expected_date": "2026-02-02", + "status": "delivered", + "notes": "Delivered Mon Feb 2, 4:42 PM. E3-1275v3 for james server upgrade.", + "created_at": "2026-02-02T12:40:49.133341067Z", + "updated_at": "2026-02-02T21:58:07.178775258Z" + }, + { + "id": "7e90d6e0", + "carrier": "Amazon", + "retailer": "Amazon", + "description": "World Organic - Chlorophyll Liquid", + "expected_date": "2026-02-08", + "status": "shipped", + "notes": "Order #112-4979107-5850639 — $13.42", + "created_at": "2026-02-04T12:50:16.021108279-05:00", + "updated_at": "2026-02-04T12:50:16.021108323-05:00" + }, + { + "id": "5559792b", + "carrier": "Pediatric Home Service", + "retailer": "All About Pediatrics", + "description": "Sophia medical supplies (pulse ox wraps, etc.)", + "tracking_number": "75175", + "expected_date": "2026-02-07", + "status": "shipped", + "notes": "4 boxes shipped Feb 3. Order #75175.", + "created_at": "2026-02-04T13:11:29.306713737-05:00", + "updated_at": "2026-02-04T13:11:29.306713781-05:00" + }, + { + "id": "0e67333b", + "carrier": "Pediatric Home Service", + "retailer": "All About Pediatrics", + "description": "Sophia monthly supplies (Feb 2026) - 4 boxes", + "tracking_number": "75175", + "expected_date": "2026-02-06", + "status": "in_transit", + "notes": "Shipped Feb 3. Trach ties, saline, gauze, suction tubes, filters, gloves, H2O2, trach, pulse-ox wraps, cleaning kits, collars, alcohol wipes. Still need updated Rx from Dr Lastra for pulse-ox wraps.", + "created_at": "2026-02-04T14:36:17.040575584-05:00", + "updated_at": "2026-02-04T14:36:17.040575636-05:00" + }, + { + "id": "4d72150d", + "carrier": "Amazon", + "retailer": "Amazon", + "description": "World Organic Chlorophyll Liquid", + "tracking_number": "112-4979107-5850639", + "expected_date": "2026-02-08", + "status": "shipped", + "notes": "Order #112-4979107-5850639, $13.42. Arriving Sunday.", + "created_at": "2026-02-04T14:36:17.076836995-05:00", + "updated_at": "2026-02-04T14:36:17.076837035-05:00" + }, + { + "id": "0f28675d", + "carrier": "Pediatric Home Service", + "retailer": "All About Pediatrics", + "description": "Sophia monthly supplies (Feb 2026) - Pediatric Home Service", + "expected_date": "2026-02-07", + "status": "shipped", + "notes": "Order #75175, 4 boxes, shipped Feb 3. Trach ties, saline, gauze, suction tubes, filters, gloves, H2O2, trach, cleaning kits, collars, alcohol wipes.", + "created_at": "2026-02-04T15:24:15.704405892-05:00", + "updated_at": "2026-02-04T15:24:15.704405942-05:00" + }, + { + "id": "1c3a76f8", + "carrier": "Pediatric Home Service", + "retailer": "Pediatric Home Service", + "description": "Sophia supplies (4 boxes)", + "tracking_number": "75175", + "expected_date": "2026-02-07", + "status": "shipped", + "notes": "Shipped Feb 3, 2026. Order #75175. Pulse-ox wraps prescription issue - expired, needs renewal.", + "created_at": "2026-02-04T16:31:59.598579883-05:00", + "updated_at": "2026-02-04T16:31:59.598579924-05:00" + }, + { + "id": "0882ab83", + "carrier": "Pediatric Home Service", + "retailer": "Pediatric Home Service", + "description": "Sophia medical supplies (4 boxes)", + "expected_date": "2026-02-06", + "status": "shipped", + "notes": "Order #75175, shipped Feb 3", + "created_at": "2026-02-04T20:18:45.540488089-05:00", + "updated_at": "2026-02-04T20:18:45.540488134-05:00" + }, + { + "id": "3cbb090f", + "carrier": "Amazon", + "retailer": "Amazon", + "description": "World Organic Chlorophyll", + "expected_date": "2026-02-09", + "status": "shipped", + "notes": "Order #112-4979107-5850639, $13.42", + "created_at": "2026-02-04T20:19:33.429595282-05:00", + "updated_at": "2026-02-04T20:19:33.429595321-05:00" } ] } \ No newline at end of file diff --git a/data/email-triage.json b/data/email-triage.json new file mode 100644 index 0000000..b50a4d8 --- /dev/null +++ b/data/email-triage.json @@ -0,0 +1,58 @@ +{ + "entries": [ + { + "id": "bd98a53e", + "account": "johan", + "from": "Your Teacher (via Canva) \u003cno-reply@canva.com\u003e", + "subject": "You have been invited to join a class on Canva", + "action": "flagged", + "reason": "PHISHING - fake $769.68 payment scam with phone number. Preserved for review. Original misclassified as spam.", + "timestamp": "2026-02-02T16:42:56.217471102Z" + }, + { + "id": "cc62a7a9", + "account": "proton", + "from": "eBay", + "subject": "Scanner - Out for delivery", + "action": "archived", + "reason": "Delivery notification - updated dashboard", + "timestamp": "2026-02-02T16:34:07.290459206Z" + }, + { + "id": "1a08464e", + "account": "proton", + "from": "Canva (no-reply@canva.com)", + "subject": "You have been invited to join a class", + "action": "deleted", + "reason": "Educational marketing spam - generic sender", + "timestamp": "2026-02-02T16:34:07.282979849Z" + }, + { + "id": "91ec6481", + "account": "proton", + "from": "Proton", + "subject": "Charitable donations newsletter", + "action": "deleted", + "reason": "Promotional newsletter", + "timestamp": "2026-02-02T16:34:07.275913358Z" + }, + { + "id": "dbd24ff4", + "account": "proton", + "from": "eBay", + "subject": "Intel Xeon E3 CPU - Out for delivery", + "action": "archived", + "reason": "Delivery notification - added to dashboard deliveries", + "timestamp": "2026-02-02T16:34:07.268341407Z" + }, + { + "id": "fac9cdb7", + "account": "proton", + "from": "GLAMUSE \u003cjulia@news.glamuse.com\u003e", + "subject": "Lingerie Sale", + "action": "deleted", + "reason": "Marketing spam", + "timestamp": "2026-02-02T10:11:32.989544816Z" + } + ] +} \ No newline at end of file diff --git a/data/news.json b/data/news.json index befafd9..dd8a6b1 100644 --- a/data/news.json +++ b/data/news.json @@ -1,137 +1,68 @@ { "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.", + "id": "6c76816c", + "title": "🇺🇸 Trump: Colombia Meeting on Narco Cooperation", + "body": "Trump met with Colombian president in DC on bilateral narcotrafficking cooperation. Otherwise mostly promoting Melania movie/documentary.", "type": "info", - "source": "Hacker News/IBM", - "timestamp": "2026-01-30T16:43:56.152204714Z" + "source": "X/@realDonaldTrump", + "timestamp": "2026-02-04T11:09:35.285136417-05:00" }, { - "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.\"", + "id": "07246f3e", + "title": "💰 Gold \u0026 Silver: Post-Crash Manipulation Talk", + "body": "Gold and silver recovering after Friday crash. Analysts discussing potential market manipulation. Metals still in broader uptrend.", "type": "info", - "source": "@openclaw", - "timestamp": "2026-01-30T16:08:10.873278893Z" + "source": "X/Metals", + "timestamp": "2026-02-04T11:09:35.275342825-05:00" }, { - "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.", + "id": "a890e4b1", + "title": "🇳🇱 Dutch Box 3: Spaar BV vs Box 3 Debate", + "body": "Analysis shows Spaar BV yields 428% more wealth than Box 3 reform (2028 sim). Exit tax 26.9% is the catch. Vermogensbelasting discussions continue.", "type": "info", - "source": "Tech", - "timestamp": "2026-01-30T15:45:17.539675069Z" + "source": "X/Dutch", + "timestamp": "2026-02-04T11:09:35.265271373-05:00" }, { - "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.", + "id": "f32c7d4b", + "title": "📊 NABL: B. Riley Initiates Buy, PT $10", + "body": "B. Riley initiated coverage with Buy rating and $10 price target. Patrick Pulvermueller appointed to board. No PE takeover/going-private signals detected.", + "type": "info", + "source": "X/Markets", + "timestamp": "2026-02-04T11:09:35.255235299-05:00" + }, + { + "id": "b1ecb026", + "title": "🦞 OpenClaw 2026.2.2 Released", + "body": "169 commits, 25 contributors. New: Feishu/Lark Chinese chat client, faster builds (tsdown), security hardening, QMD memory plugin.", + "type": "info", + "source": "X/@openclaw", + "timestamp": "2026-02-04T11:09:35.245325884-05:00" + }, + { + "id": "107834e3", + "title": "🤖 Claude Sonnet 5 Release Imminent", + "body": "API checks found claude-sonnet-5 and claude-opus-4-6 gated on Google Vertex AI. Anthropic had 4 outages yesterday (likely prep). AlexFinn calling it landscape-shifting. Expected today/this week.", + "type": "info", + "source": "X/AI", + "timestamp": "2026-02-04T11:09:35.234901551-05:00" + }, + { + "id": "cb47e956", + "title": "🎯 SentinelOne (S) Hits 52-Week Low — $13.27", + "body": "S hit new 52-week low at $13.27 yesterday, now ~$13.46. Penserra Capital dumped 524K shares. CFO concerns + growth worries. Short position looking excellent.", "type": "success", "source": "Markets", - "timestamp": "2026-01-30T15:45:17.51939529Z" + "timestamp": "2026-02-04T11:09:35.224087874-05:00" }, { - "id": "0b604cce", - "title": "Gold Crashes -5.7%", - "body": "Dropped from ATH ($5,050) as Kevin Warsh Fed chair pick firms up dollar.", + "id": "9206df40", + "title": "📉 S\u0026P 500 \u0026 NASDAQ Down — AI Worries", + "body": "S\u0026P 500: 6,894 (-0.34%), NASDAQ: 22,984 (-1.17%), Dow: 49,529 (+0.58%). Tech selling off on AI concerns ahead of Google earnings. BTC: $73,880 (-2.3%).", "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" + "source": "X/Markets", + "timestamp": "2026-02-04T11:09:35.211541428-05:00" } ] } \ No newline at end of file diff --git a/data/status.json b/data/status.json new file mode 100644 index 0000000..b8bccb2 --- /dev/null +++ b/data/status.json @@ -0,0 +1,28 @@ +{ + "items": { + "claude_weekly": { + "key": "claude_weekly", + "value": "87% used · 10:00 PM", + "type": "error", + "updated_at": "2026-02-04T22:00:03.932195929-05:00" + }, + "openclaw-update": { + "key": "openclaw-update", + "value": "2026.2.2-3 (latest) ✅", + "type": "info", + "updated_at": "2026-02-04T13:26:44.866210785-05:00" + }, + "services": { + "key": "services", + "value": "All healthy ✅", + "type": "info", + "updated_at": "2026-02-04T13:26:44.871721208-05:00" + }, + "zurich": { + "key": "zurich", + "value": "VPS OK ✅", + "type": "info", + "updated_at": "2026-02-04T13:26:44.877121921-05:00" + } + } +} \ No newline at end of file diff --git a/data/tasks.json b/data/tasks.json index 6c1eb7f..6fcecc6 100644 --- a/data/tasks.json +++ b/data/tasks.json @@ -11,39 +11,6 @@ "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", @@ -51,73 +18,9 @@ "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", + "notes": "Feature complete! Latest: commit 0645037.\n\nNEW (Feb 4): Postgres job queue, FS object storage, fully wired worker binary.\n\n🔲 Configure Azure Files share for testing\n🔲 End-to-end test with real Azure\n\n31 tests passing. All code pushed to zurich.", "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" + "updated": "2026-02-04T01:05:39Z" }, { "id": "3d3e6483", @@ -131,60 +34,48 @@ "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", + "id": "bdb04496", + "text": "Google Search Console: \"Alternate page with proper canonical tag\" preventing pages from being indexed. Check GSC indexing report, identify affected pages, determine if intentional or needs fix.", + "title": "inou.com indexing issue - GSC alert", "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" + "domain": "inou", + "notes": "FIXED 2026-02-04: Separated www.inou.com from main inou.com block in Caddy. www now returns 301 permanent redirect to inou.com (preserves URI path). GSC should clear the \"Alternate page with proper canonical tag\" warnings within a few days as Googlebot recrawls.", + "created_at": "2026-02-02T17:39:41.240766963Z", + "updated": "2026-02-04T12:10:46-05:00" }, { - "id": "270424d3", - "text": "Cron job for Sundays, critical/high templates only", - "title": "Add weekly nuclei light scan", + "id": "8d9b10c5", + "text": "Hostkey VPS 50304 exceeded 70% CPU sustained. Investigate what caused the spike and optimize.", + "title": "Zurich VPS CPU throttled", + "priority": "medium", + "status": "pending", + "owner": "james", + "domain": "Infrastructure", + "created_at": "2026-02-04T10:52:52.153366786-05:00" + }, + { + "id": "f0eee732", + "text": "Google Search Console: New reasons prevent pages from being indexed. Investigate and fix.", + "title": "inou.com indexing issues", + "priority": "medium", + "status": "done", + "owner": "james", + "domain": "inou", + "notes": "Same root cause as bdb04496 (www.inou.com serving 200 instead of 301). Fixed 2026-02-04 by adding permanent redirect in Caddy config.", + "created_at": "2026-02-04T10:53:02.693108324-05:00", + "updated": "2026-02-04T12:10:46-05:00" + }, + { + "id": "8b9a3456", + "text": "New release available. Bug fixes: editor reset states, upload progress flickering, metadata race condition, backspace delete modal prevention. Update during Sunday Docker maintenance.", + "title": "Update Immich to v2.5.3", "priority": "low", - "status": "done", + "status": "pending", "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" + "created_at": "2026-02-04T15:24:24.083887926-05:00" } ] } \ No newline at end of file diff --git a/index.html b/index.html index 8be86b2..9e45b3a 100644 --- a/index.html +++ b/index.html @@ -126,6 +126,25 @@ .agent-emoji { font-size: 1rem; } + /* Status (inline in header) */ + .status-inline { + display: flex; + gap: 12px; + font-size: 0.8rem; + color: var(--text-muted); + padding-right: 12px; + border-right: 1px solid var(--border); + margin-right: 4px; + } + + .status-inline:empty { + display: none; + } + + .status-inline .value.ok { color: var(--success); } + .status-inline .value.warning { color: var(--accent); } + .status-inline .value.error { color: var(--danger); } + .section { margin-bottom: 20px; } .section-header { @@ -148,6 +167,7 @@ } .section-indicator.alerts { background: var(--danger); } + .section-indicator.deliveries { background: #8B5CF6; } .section-indicator.james { background: var(--james); } .section-indicator.johan { background: var(--johan); } .section-indicator.progress { background: var(--info); } @@ -240,20 +260,108 @@ font-style: italic; } + /* Deliveries */ + .delivery-item { + padding: 12px 16px; + border-bottom: 1px solid var(--border); + } + .delivery-item:last-child { border-bottom: none; } + + .delivery-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 4px; + } + + .delivery-title { + font-weight: 500; + font-size: 0.9rem; + display: flex; + align-items: center; + gap: 8px; + } + + .delivery-status { + font-size: 0.7rem; + padding: 2px 8px; + border-radius: 4px; + font-weight: 500; + } + .delivery-status.out_for_delivery { background: #FEF3C7; color: #B45309; } + .delivery-status.in_transit { background: #EEF2FF; color: #6366f1; } + .delivery-status.shipped { background: var(--bg); color: var(--text-muted); } + .delivery-status.delayed { background: #FEF2F2; color: #DC2626; } + + .delivery-meta { + font-size: 0.75rem; + color: var(--text-muted); + margin-top: 4px; + } + /* Tasks */ .task-item { padding: 12px 16px; border-bottom: 1px solid var(--border); } .task-item:last-child { border-bottom: none; } + .task-item:hover .task-actions { opacity: 1; } .task-header { display: flex; justify-content: space-between; - align-items: flex-start; + align-items: center; gap: 8px; } + .task-checkbox { + width: 22px; + height: 22px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + font-size: 14px; + color: var(--text-muted); + border-radius: 50%; + transition: all 0.15s; + flex-shrink: 0; + } + .task-checkbox:hover { + background: var(--accent); + color: white; + } + + .task-actions { + display: flex; + gap: 4px; + opacity: 0; + transition: opacity 0.15s; + flex-shrink: 0; + } + + .task-btn { + width: 22px; + height: 22px; + border: none; + background: var(--bg-secondary); + color: var(--text-muted); + border-radius: 4px; + cursor: pointer; + font-size: 12px; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.15s; + } + .task-btn:hover { + background: var(--accent); + color: white; + } + .task-btn.delete:hover { + background: #ef4444; + } + .task-title { font-weight: 500; font-size: 0.85rem; @@ -347,6 +455,7 @@

James Dashboard

+ Loading...
@@ -382,6 +491,19 @@ +
+
+
+
+ 📦 Deliveries +
+ 0 +
+
+
No active deliveries
+
+
+
@@ -472,11 +594,21 @@ } function renderTask(t, showOwner = false) { + const isDone = t.status === 'done'; + const isInProgress = t.status === 'in-progress'; return ` -
+
-
${t.title}
+
+ ${isDone ? '✓' : (isInProgress ? '◐' : '○')} +
+
${t.title}
${t.domain ? `${t.domain}` : ''} +
+ ${!isDone && !isInProgress ? `` : ''} + ${isInProgress ? `` : ''} + +
${t.notes ? `
${t.notes}
` : ''}
@@ -487,6 +619,34 @@ `; } + async function toggleTaskDone(id, currentStatus) { + const newStatus = currentStatus === 'done' ? 'pending' : 'done'; + await setTaskStatus(id, newStatus); + } + + async function setTaskStatus(id, status) { + try { + await fetch(`/api/tasks/${id}`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ status }) + }); + loadData(); + } catch (e) { + console.error('Failed to update task:', e); + } + } + + async function deleteTask(id) { + if (!confirm('Delete this task?')) return; + try { + await fetch(`/api/tasks/${id}`, { method: 'DELETE' }); + loadData(); + } catch (e) { + console.error('Failed to delete task:', e); + } + } + function renderAlert(n) { return `
@@ -503,21 +663,42 @@ `; } + function renderDelivery(d) { + const statusLabel = d.status.replace(/_/g, ' '); + const icon = d.status === 'out_for_delivery' ? '🚚' : (d.status === 'delayed' ? '⚠️' : '📦'); + return ` +
+
+
+ ${icon} ${d.description} +
+ ${statusLabel} +
+
+ ${d.carrier}${d.expected_date ? ' • Expected: ' + d.expected_date : ''} +
+
+ `; + } + async function loadData() { try { - const [tasksRes, newsRes, briefingsRes] = await Promise.all([ + const [tasksRes, newsRes, briefingsRes, deliveriesRes] = await Promise.all([ fetch('/api/tasks'), fetch('/api/news'), - fetch('/api/briefings') + fetch('/api/briefings'), + fetch('/api/deliveries') ]); const tasksData = await tasksRes.json(); const newsData = await newsRes.json(); const briefingsData = await briefingsRes.json(); + const deliveriesData = await deliveriesRes.json(); const tasks = tasksData.tasks || []; const news = newsData.items || []; const briefings = briefingsData.briefings || []; + const deliveries = deliveriesData.deliveries || []; // Filter tasks by owner and status const jamesPending = tasks.filter(t => t.owner === 'james' && t.status === 'pending'); @@ -535,6 +716,12 @@ ? recentNews.map(renderAlert).join('') : '
No alerts in last 24h
'; + // Render deliveries (active only - API already filters out delivered) + document.getElementById('deliveries-count').textContent = deliveries.length; + document.getElementById('deliveries-container').innerHTML = deliveries.length + ? deliveries.map(renderDelivery).join('') + : '
No active deliveries
'; + // Render task groups document.getElementById('james-pending-count').textContent = jamesPending.length; document.getElementById('james-pending-container').innerHTML = jamesPending.length @@ -598,9 +785,46 @@ } } + async function loadStatus() { + try { + const res = await fetch('/api/status'); + const data = await res.json(); + const items = data.status || {}; + + if (Object.keys(items).length === 0) { + document.getElementById('status-items').innerHTML = ''; + return; + } + + // Sort by key for consistent display + const sorted = Object.entries(items).sort((a, b) => a[0].localeCompare(b[0])); + + document.getElementById('status-items').innerHTML = sorted.map(([key, item]) => { + // Determine color class based on type or value + let valueClass = 'ok'; + if (item.type === 'warning' || (item.value && item.value.includes('%') && parseInt(item.value) > 70)) { + valueClass = 'warning'; + } + if (item.type === 'error' || (item.value && item.value.includes('%') && parseInt(item.value) > 85)) { + valueClass = 'error'; + } + + // Compact format for header: "Claude: 46%" + const label = key.replace('_weekly', '').replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); + const shortValue = item.value.replace(/ \([^)]+\)/, ''); // Remove parenthetical + + return `${label}: ${shortValue}`; + }).join(' · '); + } catch (e) { + console.error('Failed to load status:', e); + } + } + loadAgents(); + loadStatus(); loadData(); setInterval(loadData, 30000); + setInterval(loadStatus, 60000); // Refresh status every minute diff --git a/james-dashboard b/james-dashboard index 88d7ab7..6acd10c 100755 Binary files a/james-dashboard and b/james-dashboard differ diff --git a/james-dashboard-new b/james-dashboard-new new file mode 100755 index 0000000..d9e3cd3 Binary files /dev/null and b/james-dashboard-new differ diff --git a/multi-chat.html b/multi-chat.html new file mode 100644 index 0000000..8b09389 --- /dev/null +++ b/multi-chat.html @@ -0,0 +1,405 @@ + + + + + + Multi-Agent Chat + + + + + +
+ +
+
Select an agent from the sidebar
+
+
+ + +
+
+ + + + diff --git a/server.go b/server.go index 1dc0273..fd539a8 100644 --- a/server.go +++ b/server.go @@ -568,6 +568,221 @@ func (s *DeliveryStore) FindByTracking(tracking string) (Delivery, bool) { return Delivery{}, false } +// ============================================ +// STATUS (key-value status indicators) +// ============================================ + +type StatusItem struct { + Key string `json:"key"` + Value string `json:"value"` + Type string `json:"type,omitempty"` // info, success, warning, error + UpdatedAt time.Time `json:"updated_at"` +} + +type StatusStore struct { + Items map[string]StatusItem `json:"items"` + mu sync.RWMutex + path string +} + +func NewStatusStore(path string) (*StatusStore, error) { + store := &StatusStore{path: path, Items: make(map[string]StatusItem)} + + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, err + } + + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return store, store.save() + } + return nil, err + } + + var wrapper struct { + Items map[string]StatusItem `json:"items"` + } + if err := json.Unmarshal(data, &wrapper); err == nil && wrapper.Items != nil { + store.Items = wrapper.Items + return store, nil + } + + return store, store.save() +} + +func (s *StatusStore) save() error { + wrapper := struct { + Items map[string]StatusItem `json:"items"` + }{Items: s.Items} + data, err := json.MarshalIndent(wrapper, "", " ") + if err != nil { + return err + } + return os.WriteFile(s.path, data, 0644) +} + +func (s *StatusStore) Set(key, value, itemType string) StatusItem { + s.mu.Lock() + defer s.mu.Unlock() + + item := StatusItem{ + Key: key, + Value: value, + Type: itemType, + UpdatedAt: time.Now(), + } + if item.Type == "" { + item.Type = "info" + } + s.Items[key] = item + s.save() + return item +} + +func (s *StatusStore) Get(key string) (StatusItem, bool) { + s.mu.RLock() + defer s.mu.RUnlock() + item, ok := s.Items[key] + return item, ok +} + +func (s *StatusStore) List() map[string]StatusItem { + s.mu.RLock() + defer s.mu.RUnlock() + return s.Items +} + +func (s *StatusStore) Delete(key string) bool { + s.mu.Lock() + defer s.mu.Unlock() + if _, ok := s.Items[key]; ok { + delete(s.Items, key) + s.save() + return true + } + return false +} + +// ============================================ +// EMAIL TRIAGE +// ============================================ + +type EmailTriageEntry struct { + ID string `json:"id"` + Account string `json:"account"` // proton, johan + From string `json:"from"` + Subject string `json:"subject"` + Action string `json:"action"` // archived, deleted, flagged, kept + Reason string `json:"reason,omitempty"` + Timestamp time.Time `json:"timestamp"` +} + +type EmailTriageStore struct { + Entries []EmailTriageEntry `json:"entries"` + mu sync.RWMutex + path string +} + +func NewEmailTriageStore(path string) (*EmailTriageStore, error) { + store := &EmailTriageStore{path: path, Entries: []EmailTriageEntry{}} + + dir := filepath.Dir(path) + if err := os.MkdirAll(dir, 0755); err != nil { + return nil, err + } + + data, err := os.ReadFile(path) + if err != nil { + if os.IsNotExist(err) { + return store, store.save() + } + return nil, err + } + + var wrapper struct { + Entries []EmailTriageEntry `json:"entries"` + } + if err := json.Unmarshal(data, &wrapper); err == nil && wrapper.Entries != nil { + store.Entries = wrapper.Entries + return store, nil + } + + return store, store.save() +} + +func (s *EmailTriageStore) save() error { + wrapper := struct { + Entries []EmailTriageEntry `json:"entries"` + }{Entries: s.Entries} + data, err := json.MarshalIndent(wrapper, "", " ") + if err != nil { + return err + } + return os.WriteFile(s.path, data, 0644) +} + +func (s *EmailTriageStore) Add(entry EmailTriageEntry) (EmailTriageEntry, error) { + s.mu.Lock() + defer s.mu.Unlock() + + entry.ID = uuid.New().String()[:8] + entry.Timestamp = time.Now() + + // Prepend (newest first) + s.Entries = append([]EmailTriageEntry{entry}, s.Entries...) + + // Keep only last 500 entries + if len(s.Entries) > 500 { + s.Entries = s.Entries[:500] + } + + return entry, s.save() +} + +func (s *EmailTriageStore) List(account string, limit int) []EmailTriageEntry { + s.mu.RLock() + defer s.mu.RUnlock() + + if account == "" { + if limit > 0 && limit < len(s.Entries) { + return s.Entries[:limit] + } + return s.Entries + } + + // Filter by account + var filtered []EmailTriageEntry + for _, e := range s.Entries { + if e.Account == account { + filtered = append(filtered, e) + if limit > 0 && len(filtered) >= limit { + break + } + } + } + return filtered +} + +func (s *EmailTriageStore) Clear(account string) error { + s.mu.Lock() + defer s.mu.Unlock() + + if account == "" { + s.Entries = []EmailTriageEntry{} + } else { + var filtered []EmailTriageEntry + for _, e := range s.Entries { + if e.Account != account { + filtered = append(filtered, e) + } + } + s.Entries = filtered + } + return s.save() +} + // ============================================ // AGENTS (reads from OpenClaw config) // ============================================ @@ -580,7 +795,7 @@ type Agent struct { Default bool `json:"default,omitempty"` } -func getAgents(gatewayIP string, gatewayPort string) []Agent { +func getAgents(gatewayIP string, gatewayPort string, gatewayToken string) []Agent { // Read OpenClaw config home, _ := os.UserHomeDir() configPath := filepath.Join(home, ".openclaw", "openclaw.json") @@ -630,12 +845,14 @@ func getAgents(gatewayIP string, gatewayPort string) []Agent { agent.Emoji = "🤖" } - // Build URL - main is default + // Build URL - use /chat?session= (OpenClaw Control UI format) + // Include token for auto-authentication if agentConfig.ID == "main" { agent.Default = true - agent.URL = baseURL + "/" + agent.URL = baseURL + "/chat?token=" + gatewayToken } else { - agent.URL = baseURL + "/agents/" + agentConfig.ID + // Control UI reads session param on /chat route + agent.URL = baseURL + "/chat?session=agent:" + agentConfig.ID + ":main&token=" + gatewayToken } agents = append(agents, agent) @@ -661,6 +878,7 @@ func main() { dir := flag.String("dir", ".", "directory to serve") gatewayIP := flag.String("gateway-ip", "192.168.1.16", "OpenClaw gateway IP") gatewayPort := flag.String("gateway-port", "18789", "OpenClaw gateway port") + gatewayToken := flag.String("gateway-token", "2dee57cc3ce2947c27ce9e848d5c3e95cc452f25a1477462", "OpenClaw gateway auth token") flag.Parse() // Initialize stores @@ -684,6 +902,16 @@ func main() { log.Fatalf("Failed to initialize delivery store: %v", err) } + statusStore, err := NewStatusStore(filepath.Join(*dir, "data", "status.json")) + if err != nil { + log.Fatalf("Failed to initialize status store: %v", err) + } + + emailTriageStore, err := NewEmailTriageStore(filepath.Join(*dir, "data", "email-triage.json")) + if err != nil { + log.Fatalf("Failed to initialize email triage store: %v", err) + } + mux := http.NewServeMux() // CORS middleware helper @@ -992,6 +1220,125 @@ func main() { } }) + // ========== STATUS API ========== + mux.HandleFunc("/api/status", func(w http.ResponseWriter, r *http.Request) { + cors(w) + if r.Method == "OPTIONS" { + return + } + + switch r.Method { + case "GET": + json.NewEncoder(w).Encode(map[string]interface{}{ + "status": statusStore.List(), + }) + + case "POST", "PUT": + var item struct { + Key string `json:"key"` + Value string `json:"value"` + Type string `json:"type"` + } + if err := json.NewDecoder(r.Body).Decode(&item); err != nil { + http.Error(w, `{"error": "invalid JSON"}`, http.StatusBadRequest) + return + } + if item.Key == "" || item.Value == "" { + http.Error(w, `{"error": "key and value required"}`, http.StatusBadRequest) + return + } + result := statusStore.Set(item.Key, item.Value, item.Type) + json.NewEncoder(w).Encode(result) + + default: + http.Error(w, `{"error": "method not allowed"}`, http.StatusMethodNotAllowed) + } + }) + + // Status by key + mux.HandleFunc("/api/status/", func(w http.ResponseWriter, r *http.Request) { + cors(w) + if r.Method == "OPTIONS" { + return + } + + key := strings.TrimPrefix(r.URL.Path, "/api/status/") + if key == "" { + http.Error(w, `{"error": "status key required"}`, http.StatusBadRequest) + return + } + + switch r.Method { + case "GET": + if item, ok := statusStore.Get(key); ok { + json.NewEncoder(w).Encode(item) + } else { + http.Error(w, `{"error": "status not found"}`, http.StatusNotFound) + } + + case "DELETE": + if statusStore.Delete(key) { + json.NewEncoder(w).Encode(map[string]string{"status": "deleted", "key": key}) + } else { + http.Error(w, `{"error": "status not found"}`, http.StatusNotFound) + } + + default: + http.Error(w, `{"error": "method not allowed"}`, http.StatusMethodNotAllowed) + } + }) + + // ========== EMAIL TRIAGE API ========== + mux.HandleFunc("/api/email-triage", func(w http.ResponseWriter, r *http.Request) { + cors(w) + if r.Method == "OPTIONS" { + return + } + + switch r.Method { + case "GET": + account := r.URL.Query().Get("account") + limit := 100 + if l := r.URL.Query().Get("limit"); l != "" { + if n, err := json.Number(l).Int64(); err == nil { + limit = int(n) + } + } + json.NewEncoder(w).Encode(map[string]interface{}{ + "entries": emailTriageStore.List(account, limit), + }) + + case "POST": + var entry EmailTriageEntry + if err := json.NewDecoder(r.Body).Decode(&entry); err != nil { + http.Error(w, `{"error": "invalid JSON"}`, http.StatusBadRequest) + return + } + if entry.Account == "" || entry.Action == "" { + http.Error(w, `{"error": "account and action required"}`, http.StatusBadRequest) + return + } + newEntry, err := emailTriageStore.Add(entry) + if err != nil { + http.Error(w, `{"error": "failed to save entry"}`, http.StatusInternalServerError) + return + } + w.WriteHeader(http.StatusCreated) + json.NewEncoder(w).Encode(newEntry) + + case "DELETE": + account := r.URL.Query().Get("account") + if err := emailTriageStore.Clear(account); err != nil { + http.Error(w, `{"error": "failed to clear"}`, http.StatusInternalServerError) + return + } + json.NewEncoder(w).Encode(map[string]string{"status": "cleared", "account": account}) + + default: + http.Error(w, `{"error": "method not allowed"}`, http.StatusMethodNotAllowed) + } + }) + // ========== AGENTS API ========== mux.HandleFunc("/api/agents", func(w http.ResponseWriter, r *http.Request) { cors(w) @@ -1000,7 +1347,7 @@ func main() { } if r.Method == "GET" { - agents := getAgents(*gatewayIP, *gatewayPort) + agents := getAgents(*gatewayIP, *gatewayPort, *gatewayToken) json.NewEncoder(w).Encode(map[string]interface{}{ "agents": agents, "gateway_ip": *gatewayIP, @@ -1055,6 +1402,12 @@ func main() { log.Printf(" GET /api/deliveries/:id - get single delivery\n") log.Printf(" PATCH /api/deliveries/:id - update delivery\n") log.Printf(" DELETE /api/deliveries/:id - remove delivery\n") + log.Printf(" Email Triage:\n") + log.Printf(" GET /api/email-triage - list email triage log (newest first)\n") + log.Printf(" GET /api/email-triage?account=X - filter by account (proton, johan)\n") + log.Printf(" POST /api/email-triage - add entry {account, from, subject, action, reason}\n") + log.Printf(" DELETE /api/email-triage - clear all entries\n") + log.Printf(" DELETE /api/email-triage?account=X - clear entries for account\n") log.Printf(" Agents:\n") log.Printf(" GET /api/agents - list OpenClaw agents (reads from config)\n") log.Printf(" Gateway: http://%s:%s\n", *gatewayIP, *gatewayPort)