myaiforone 1.0.0

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 (315) hide show
  1. package/README.md +113 -0
  2. package/agents/_template/CLAUDE.md +18 -0
  3. package/agents/_template/agent.json +7 -0
  4. package/agents/platform/agentcreator/CLAUDE.md +300 -0
  5. package/agents/platform/appcreator/CLAUDE.md +158 -0
  6. package/agents/platform/gym/CLAUDE.md +486 -0
  7. package/agents/platform/gym/agent.json +40 -0
  8. package/agents/platform/gym/programs/agent-building/program.json +160 -0
  9. package/agents/platform/gym/programs/automations-mastery/program.json +129 -0
  10. package/agents/platform/gym/programs/getting-started/program.json +124 -0
  11. package/agents/platform/gym/programs/mcp-integrations/program.json +116 -0
  12. package/agents/platform/gym/programs/multi-model-strategy/program.json +115 -0
  13. package/agents/platform/gym/programs/prompt-engineering/program.json +136 -0
  14. package/agents/platform/gym/souls/alex.md +12 -0
  15. package/agents/platform/gym/souls/jordan.md +12 -0
  16. package/agents/platform/gym/souls/morgan.md +12 -0
  17. package/agents/platform/gym/souls/riley.md +12 -0
  18. package/agents/platform/gym/souls/sam.md +12 -0
  19. package/agents/platform/hub/CLAUDE.md +372 -0
  20. package/agents/platform/promptcreator/CLAUDE.md +130 -0
  21. package/agents/platform/skillcreator/CLAUDE.md +163 -0
  22. package/bin/cli.js +566 -0
  23. package/config.example.json +310 -0
  24. package/dist/agent-registry.d.ts +32 -0
  25. package/dist/agent-registry.d.ts.map +1 -0
  26. package/dist/agent-registry.js +144 -0
  27. package/dist/agent-registry.js.map +1 -0
  28. package/dist/channels/discord.d.ts +17 -0
  29. package/dist/channels/discord.d.ts.map +1 -0
  30. package/dist/channels/discord.js +114 -0
  31. package/dist/channels/discord.js.map +1 -0
  32. package/dist/channels/imessage.d.ts +23 -0
  33. package/dist/channels/imessage.d.ts.map +1 -0
  34. package/dist/channels/imessage.js +214 -0
  35. package/dist/channels/imessage.js.map +1 -0
  36. package/dist/channels/slack.d.ts +19 -0
  37. package/dist/channels/slack.d.ts.map +1 -0
  38. package/dist/channels/slack.js +167 -0
  39. package/dist/channels/slack.js.map +1 -0
  40. package/dist/channels/telegram.d.ts +19 -0
  41. package/dist/channels/telegram.d.ts.map +1 -0
  42. package/dist/channels/telegram.js +274 -0
  43. package/dist/channels/telegram.js.map +1 -0
  44. package/dist/channels/types.d.ts +44 -0
  45. package/dist/channels/types.d.ts.map +1 -0
  46. package/dist/channels/types.js +18 -0
  47. package/dist/channels/types.js.map +1 -0
  48. package/dist/channels/whatsapp.d.ts +23 -0
  49. package/dist/channels/whatsapp.d.ts.map +1 -0
  50. package/dist/channels/whatsapp.js +189 -0
  51. package/dist/channels/whatsapp.js.map +1 -0
  52. package/dist/config.d.ts +134 -0
  53. package/dist/config.d.ts.map +1 -0
  54. package/dist/config.js +127 -0
  55. package/dist/config.js.map +1 -0
  56. package/dist/cron.d.ts +8 -0
  57. package/dist/cron.d.ts.map +1 -0
  58. package/dist/cron.js +35 -0
  59. package/dist/cron.js.map +1 -0
  60. package/dist/decrypt-keys.d.ts +7 -0
  61. package/dist/decrypt-keys.d.ts.map +1 -0
  62. package/dist/decrypt-keys.js +53 -0
  63. package/dist/decrypt-keys.js.map +1 -0
  64. package/dist/encrypt-keys.d.ts +8 -0
  65. package/dist/encrypt-keys.d.ts.map +1 -0
  66. package/dist/encrypt-keys.js +62 -0
  67. package/dist/encrypt-keys.js.map +1 -0
  68. package/dist/executor.d.ts +31 -0
  69. package/dist/executor.d.ts.map +1 -0
  70. package/dist/executor.js +2009 -0
  71. package/dist/executor.js.map +1 -0
  72. package/dist/gemini-executor.d.ts +27 -0
  73. package/dist/gemini-executor.d.ts.map +1 -0
  74. package/dist/gemini-executor.js +160 -0
  75. package/dist/gemini-executor.js.map +1 -0
  76. package/dist/goals.d.ts +24 -0
  77. package/dist/goals.d.ts.map +1 -0
  78. package/dist/goals.js +189 -0
  79. package/dist/goals.js.map +1 -0
  80. package/dist/gym/activity-digest.d.ts +30 -0
  81. package/dist/gym/activity-digest.d.ts.map +1 -0
  82. package/dist/gym/activity-digest.js +506 -0
  83. package/dist/gym/activity-digest.js.map +1 -0
  84. package/dist/gym/dimension-scorer.d.ts +76 -0
  85. package/dist/gym/dimension-scorer.d.ts.map +1 -0
  86. package/dist/gym/dimension-scorer.js +236 -0
  87. package/dist/gym/dimension-scorer.js.map +1 -0
  88. package/dist/gym/gym-router.d.ts +7 -0
  89. package/dist/gym/gym-router.d.ts.map +1 -0
  90. package/dist/gym/gym-router.js +718 -0
  91. package/dist/gym/gym-router.js.map +1 -0
  92. package/dist/gym/index.d.ts +11 -0
  93. package/dist/gym/index.d.ts.map +1 -0
  94. package/dist/gym/index.js +11 -0
  95. package/dist/gym/index.js.map +1 -0
  96. package/dist/heartbeat.d.ts +21 -0
  97. package/dist/heartbeat.d.ts.map +1 -0
  98. package/dist/heartbeat.js +163 -0
  99. package/dist/heartbeat.js.map +1 -0
  100. package/dist/index.d.ts +2 -0
  101. package/dist/index.d.ts.map +1 -0
  102. package/dist/index.js +254 -0
  103. package/dist/index.js.map +1 -0
  104. package/dist/keystore.d.ts +22 -0
  105. package/dist/keystore.d.ts.map +1 -0
  106. package/dist/keystore.js +178 -0
  107. package/dist/keystore.js.map +1 -0
  108. package/dist/logger.d.ts +9 -0
  109. package/dist/logger.d.ts.map +1 -0
  110. package/dist/logger.js +45 -0
  111. package/dist/logger.js.map +1 -0
  112. package/dist/memory/daily.d.ts +22 -0
  113. package/dist/memory/daily.d.ts.map +1 -0
  114. package/dist/memory/daily.js +82 -0
  115. package/dist/memory/daily.js.map +1 -0
  116. package/dist/memory/embeddings.d.ts +15 -0
  117. package/dist/memory/embeddings.d.ts.map +1 -0
  118. package/dist/memory/embeddings.js +154 -0
  119. package/dist/memory/embeddings.js.map +1 -0
  120. package/dist/memory/index.d.ts +32 -0
  121. package/dist/memory/index.d.ts.map +1 -0
  122. package/dist/memory/index.js +159 -0
  123. package/dist/memory/index.js.map +1 -0
  124. package/dist/memory/search.d.ts +21 -0
  125. package/dist/memory/search.d.ts.map +1 -0
  126. package/dist/memory/search.js +77 -0
  127. package/dist/memory/search.js.map +1 -0
  128. package/dist/memory/store.d.ts +23 -0
  129. package/dist/memory/store.d.ts.map +1 -0
  130. package/dist/memory/store.js +144 -0
  131. package/dist/memory/store.js.map +1 -0
  132. package/dist/ollama-executor.d.ts +17 -0
  133. package/dist/ollama-executor.d.ts.map +1 -0
  134. package/dist/ollama-executor.js +112 -0
  135. package/dist/ollama-executor.js.map +1 -0
  136. package/dist/openai-executor.d.ts +38 -0
  137. package/dist/openai-executor.d.ts.map +1 -0
  138. package/dist/openai-executor.js +197 -0
  139. package/dist/openai-executor.js.map +1 -0
  140. package/dist/router.d.ts +11 -0
  141. package/dist/router.d.ts.map +1 -0
  142. package/dist/router.js +185 -0
  143. package/dist/router.js.map +1 -0
  144. package/dist/test-message.d.ts +2 -0
  145. package/dist/test-message.d.ts.map +1 -0
  146. package/dist/test-message.js +60 -0
  147. package/dist/test-message.js.map +1 -0
  148. package/dist/utils/imsg-db-reader.d.ts +24 -0
  149. package/dist/utils/imsg-db-reader.d.ts.map +1 -0
  150. package/dist/utils/imsg-db-reader.js +92 -0
  151. package/dist/utils/imsg-db-reader.js.map +1 -0
  152. package/dist/utils/imsg-rpc.d.ts +25 -0
  153. package/dist/utils/imsg-rpc.d.ts.map +1 -0
  154. package/dist/utils/imsg-rpc.js +149 -0
  155. package/dist/utils/imsg-rpc.js.map +1 -0
  156. package/dist/utils/message-formatter.d.ts +3 -0
  157. package/dist/utils/message-formatter.d.ts.map +1 -0
  158. package/dist/utils/message-formatter.js +69 -0
  159. package/dist/utils/message-formatter.js.map +1 -0
  160. package/dist/web-ui.d.ts +12 -0
  161. package/dist/web-ui.d.ts.map +1 -0
  162. package/dist/web-ui.js +5784 -0
  163. package/dist/web-ui.js.map +1 -0
  164. package/dist/whatsapp-chats.d.ts +2 -0
  165. package/dist/whatsapp-chats.d.ts.map +1 -0
  166. package/dist/whatsapp-chats.js +76 -0
  167. package/dist/whatsapp-chats.js.map +1 -0
  168. package/dist/whatsapp-login.d.ts +2 -0
  169. package/dist/whatsapp-login.d.ts.map +1 -0
  170. package/dist/whatsapp-login.js +90 -0
  171. package/dist/whatsapp-login.js.map +1 -0
  172. package/dist/wiki-sync.d.ts +21 -0
  173. package/dist/wiki-sync.d.ts.map +1 -0
  174. package/dist/wiki-sync.js +147 -0
  175. package/dist/wiki-sync.js.map +1 -0
  176. package/docs/AddNewAgentGuide.md +100 -0
  177. package/docs/AddNewMcpGuide.md +72 -0
  178. package/docs/Architecture.md +795 -0
  179. package/docs/CLAUDE-AI-SETUP.md +166 -0
  180. package/docs/Setup.md +297 -0
  181. package/docs/ai-gym-architecture.md +1040 -0
  182. package/docs/ai-gym-build-plan.md +343 -0
  183. package/docs/ai-gym-onboarding.md +122 -0
  184. package/docs/appcreator_plan.md +348 -0
  185. package/docs/platform-mcp-audit.md +320 -0
  186. package/docs/server-deployment-plan.md +503 -0
  187. package/docs/superpowers/plans/2026-03-25-marketplace.md +1281 -0
  188. package/docs/superpowers/specs/2026-03-25-marketplace-design.md +287 -0
  189. package/docs/user-guide.md +2016 -0
  190. package/mcp-catalog.json +628 -0
  191. package/package.json +63 -0
  192. package/public/MyAIforOne-logomark-512.svg +16 -0
  193. package/public/MyAIforOne-logomark-transparent.svg +15 -0
  194. package/public/activity.html +314 -0
  195. package/public/admin.html +1674 -0
  196. package/public/agent-dashboard.html +670 -0
  197. package/public/api-docs.html +1106 -0
  198. package/public/automations.html +722 -0
  199. package/public/canvas.css +223 -0
  200. package/public/canvas.js +588 -0
  201. package/public/changelog.html +231 -0
  202. package/public/gym.html +2766 -0
  203. package/public/home.html +1930 -0
  204. package/public/index.html +2809 -0
  205. package/public/lab.html +1643 -0
  206. package/public/library.html +1442 -0
  207. package/public/marketplace.html +1101 -0
  208. package/public/mcp-docs.html +441 -0
  209. package/public/mini.html +390 -0
  210. package/public/monitor.html +584 -0
  211. package/public/org.html +4304 -0
  212. package/public/projects.html +734 -0
  213. package/public/settings.html +645 -0
  214. package/public/tasks.html +932 -0
  215. package/public/trainers/alex.svg +12 -0
  216. package/public/trainers/jordan.svg +12 -0
  217. package/public/trainers/morgan.svg +12 -0
  218. package/public/trainers/riley.svg +12 -0
  219. package/public/trainers/sam.svg +12 -0
  220. package/public/user-guide.html +218 -0
  221. package/registry/agents.json +3 -0
  222. package/registry/apps.json +20 -0
  223. package/registry/installed-drafts.json +3 -0
  224. package/registry/mcps.json +1084 -0
  225. package/registry/prompts/personal/mcp-test-prompt.md +6 -0
  226. package/registry/prompts/personal/memory-recall.md +6 -0
  227. package/registry/prompts/platform/brainstorm.md +15 -0
  228. package/registry/prompts/platform/code-review.md +16 -0
  229. package/registry/prompts/platform/explain.md +16 -0
  230. package/registry/prompts.json +58 -0
  231. package/registry/skills/external/brainstorming.md +5 -0
  232. package/registry/skills/external/code-review.md +40 -0
  233. package/registry/skills/external/frontend-patterns.md +642 -0
  234. package/registry/skills/external/frontend-slides.md +184 -0
  235. package/registry/skills/external/systematic-debugging.md +5 -0
  236. package/registry/skills/external/tdd.md +328 -0
  237. package/registry/skills/external/verification-before-completion.md +5 -0
  238. package/registry/skills/external/writing-plans.md +5 -0
  239. package/registry/skills/platform/ai41_app_build.md +930 -0
  240. package/registry/skills/platform/ai41_app_deploy.md +168 -0
  241. package/registry/skills/platform/ai41_app_orchestrator.md +239 -0
  242. package/registry/skills/platform/ai41_app_patterns.md +359 -0
  243. package/registry/skills/platform/ai41_app_register.md +85 -0
  244. package/registry/skills/platform/ai41_app_scaffold.md +421 -0
  245. package/registry/skills/platform/ai41_app_verify.md +107 -0
  246. package/registry/skills/platform/opProjectCreate.md +239 -0
  247. package/registry/skills/platform/op_devbrowser.md +136 -0
  248. package/registry/skills/platform/sop_brandguidelines.md +103 -0
  249. package/registry/skills/platform/sop_docx.md +117 -0
  250. package/registry/skills/platform/sop_frontenddesign.md +44 -0
  251. package/registry/skills/platform/sop_frontenddesign_v2.md +659 -0
  252. package/registry/skills/platform/sop_mcpbuilder.md +133 -0
  253. package/registry/skills/platform/sop_pdf.md +172 -0
  254. package/registry/skills/platform/sop_pptx.md +133 -0
  255. package/registry/skills/platform/sop_skillcreator.md +104 -0
  256. package/registry/skills/platform/sop_themefactory.md +128 -0
  257. package/registry/skills/platform/sop_webapptesting.md +75 -0
  258. package/registry/skills/platform/sop_webartifactsbuilder.md +97 -0
  259. package/registry/skills/platform/sop_xlsx.md +134 -0
  260. package/registry/skills.json +1055 -0
  261. package/scripts/discover-chats.sh +11 -0
  262. package/scripts/install-service-windows.ps1 +87 -0
  263. package/scripts/install-service.sh +52 -0
  264. package/scripts/seed-registry.ts +195 -0
  265. package/scripts/test-send.sh +5 -0
  266. package/scripts/tray-indicator.ps1 +35 -0
  267. package/scripts/uninstall-service-windows.ps1 +23 -0
  268. package/scripts/uninstall-service.sh +15 -0
  269. package/scripts/xbar-myagent.5s.sh +32 -0
  270. package/server/mcp-server/dist/index.d.ts +11 -0
  271. package/server/mcp-server/dist/index.js +1332 -0
  272. package/server/mcp-server/dist/lib/api-client.d.ts +165 -0
  273. package/server/mcp-server/dist/lib/api-client.js +241 -0
  274. package/server/mcp-server/index.ts +1545 -0
  275. package/server/mcp-server/lib/api-client.ts +366 -0
  276. package/server/mcp-server/tsconfig.json +14 -0
  277. package/src/agent-registry.ts +180 -0
  278. package/src/channels/discord.ts +129 -0
  279. package/src/channels/imessage.ts +261 -0
  280. package/src/channels/slack.ts +208 -0
  281. package/src/channels/telegram.ts +307 -0
  282. package/src/channels/types.ts +62 -0
  283. package/src/channels/whatsapp.ts +227 -0
  284. package/src/config.ts +281 -0
  285. package/src/cron.ts +43 -0
  286. package/src/decrypt-keys.ts +60 -0
  287. package/src/encrypt-keys.ts +70 -0
  288. package/src/executor.ts +2190 -0
  289. package/src/gemini-executor.ts +212 -0
  290. package/src/goals.ts +240 -0
  291. package/src/gym/activity-digest.ts +546 -0
  292. package/src/gym/dimension-scorer.ts +297 -0
  293. package/src/gym/gym-router.ts +801 -0
  294. package/src/gym/index.ts +19 -0
  295. package/src/heartbeat.ts +220 -0
  296. package/src/index.ts +275 -0
  297. package/src/keystore.ts +190 -0
  298. package/src/logger.ts +51 -0
  299. package/src/memory/daily.ts +101 -0
  300. package/src/memory/embeddings.ts +185 -0
  301. package/src/memory/index.ts +218 -0
  302. package/src/memory/search.ts +124 -0
  303. package/src/memory/store.ts +189 -0
  304. package/src/ollama-executor.ts +126 -0
  305. package/src/openai-executor.ts +259 -0
  306. package/src/router.ts +230 -0
  307. package/src/test-message.ts +72 -0
  308. package/src/utils/imsg-db-reader.ts +109 -0
  309. package/src/utils/imsg-rpc.ts +178 -0
  310. package/src/utils/message-formatter.ts +90 -0
  311. package/src/web-ui.ts +5778 -0
  312. package/src/whatsapp-chats.ts +91 -0
  313. package/src/whatsapp-login.ts +110 -0
  314. package/src/wiki-sync.ts +199 -0
  315. package/tsconfig.json +19 -0
@@ -0,0 +1,670 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Agent Dashboard — MyAIforOne</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&family=Syne:wght@600;700;800&display=swap" rel="stylesheet">
8
+ <style>
9
+ *,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
10
+
11
+ :root{
12
+ --bg-deep:#060a13;
13
+ --bg-surface:rgba(12,18,33,0.92);
14
+ --bg-card:rgba(16,22,40,0.85);
15
+ --bg-input:rgba(0,0,0,0.35);
16
+ --border-dim:rgba(56,189,248,0.08);
17
+ --border-glow:rgba(56,189,248,0.18);
18
+ --border-active:rgba(56,189,248,0.45);
19
+ --text-primary:rgba(255,255,255,0.92);
20
+ --text-secondary:rgba(255,255,255,0.68);
21
+ --text-muted:rgba(148,163,184,0.55);
22
+ --accent:#22d3ee;
23
+ --accent-soft:#38bdf8;
24
+ --accent-bg:rgba(6,182,212,0.15);
25
+ --accent-glow:rgba(34,211,238,0.12);
26
+ --purple:rgba(139,92,246,0.7);
27
+ --purple-bg:rgba(139,92,246,0.12);
28
+ --green:#4ade80;
29
+ --green-bg:rgba(74,222,128,0.1);
30
+ --amber:#fbbf24;
31
+ --amber-bg:rgba(251,191,36,0.1);
32
+ --red:#ef4444;
33
+ --red-bg:rgba(239,68,68,0.1);
34
+ --shadow:0 2px 12px rgba(0,0,0,0.3);
35
+ --shadow-glow:0 0 20px rgba(34,211,238,0.08);
36
+ --radius:12px;
37
+ --font-sans:'DM Sans',system-ui,-apple-system,sans-serif;
38
+ --font-mono:'IBM Plex Mono',monospace;
39
+ --font-display:'Syne',sans-serif;
40
+ }
41
+
42
+ [data-theme="light"]{
43
+ --bg-deep:#f4f6f9;
44
+ --bg-surface:rgba(255,255,255,0.95);
45
+ --bg-card:rgba(255,255,255,0.9);
46
+ --bg-input:rgba(0,0,0,0.04);
47
+ --border-dim:rgba(0,0,0,0.08);
48
+ --border-glow:rgba(14,116,144,0.18);
49
+ --border-active:rgba(14,116,144,0.45);
50
+ --text-primary:rgba(15,23,42,0.92);
51
+ --text-secondary:rgba(51,65,85,0.8);
52
+ --text-muted:rgba(100,116,139,0.6);
53
+ --accent:#0891b2;
54
+ --accent-soft:#0e7490;
55
+ --accent-bg:rgba(14,116,144,0.08);
56
+ --accent-glow:rgba(14,116,144,0.06);
57
+ --purple:rgba(109,40,217,0.75);
58
+ --purple-bg:rgba(139,92,246,0.08);
59
+ --green:#16a34a;
60
+ --green-bg:rgba(22,163,74,0.08);
61
+ --amber:#d97706;
62
+ --amber-bg:rgba(217,119,6,0.08);
63
+ --red:#dc2626;
64
+ --red-bg:rgba(220,38,38,0.08);
65
+ --shadow:0 1px 8px rgba(0,0,0,0.06);
66
+ --shadow-glow:none;
67
+ }
68
+
69
+ html,body{
70
+ width:100%;height:100%;overflow-y:auto;
71
+ background:var(--bg-deep);
72
+ font-family:var(--font-sans);
73
+ color:var(--text-primary);
74
+ transition:background .3s,color .3s;
75
+ }
76
+
77
+ /* ── Topbar ── */
78
+ .topbar{height:48px;display:flex;align-items:center;padding:0 20px;background:var(--bg-surface);border-bottom:1px solid var(--border-dim);backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);position:fixed;top:0;left:0;right:0;z-index:200;flex-shrink:0;overflow:hidden;max-width:100vw}
79
+ .logo-mark{width:28px;height:28px;border-radius:8px;object-fit:contain}
80
+ .logo-text{font-family:var(--font-display);font-size:15px;font-weight:700;color:var(--accent)}
81
+ .tab-group{display:flex;gap:0;flex-shrink:1;min-width:0;overflow-x:auto}
82
+ .tab-btn{font-family:var(--font-sans);font-size:13px;font-weight:600;color:var(--text-muted);background:none;border:none;padding:14px 14px;cursor:pointer;position:relative;transition:color .2s;text-decoration:none;display:block;white-space:nowrap;flex-shrink:0}
83
+ .tab-btn:hover{color:var(--text-secondary)}
84
+ .tab-btn.active{color:var(--accent)}
85
+ .tab-btn.active::after{content:'';position:absolute;bottom:0;left:10px;right:10px;height:2px;background:var(--accent);border-radius:1px}
86
+ .topbar-right{margin-left:auto;display:flex;align-items:center;gap:8px}
87
+ .gym-nav-btn{font-family:var(--font-sans);font-size:12px;font-weight:700;color:#fff;background:var(--accent);border:none;border-radius:8px;padding:6px 14px;cursor:pointer;text-decoration:none;letter-spacing:.02em;transition:opacity .2s,transform .2s;white-space:nowrap}
88
+ .gym-nav-btn:hover{opacity:.88;transform:translateY(-1px)}
89
+ .theme-toggle,.gear-btn{width:34px;height:34px;border-radius:8px;border:1px solid var(--border-dim);background:transparent;color:var(--text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;transition:all .2stext-decoration:none;}
90
+ .theme-toggle:hover,.gear-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
91
+ .docs-dropdown{position:relative;display:inline-block}
92
+ .docs-menu{display:none;position:absolute;top:40px;right:0;min-width:160px;background:var(--bg-surface);border:1px solid var(--border-glow);border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,0.4);z-index:200;padding:4px;backdrop-filter:blur(20px)}
93
+ .docs-menu.open{display:block}
94
+ .docs-menu a{display:flex;align-items:center;gap:8px;padding:7px 12px;font-size:12px;font-weight:500;color:var(--text-secondary);text-decoration:none;border-radius:6px;transition:all .15s}
95
+ .docs-menu a:hover{background:var(--accent-bg);color:var(--accent)}
96
+ .docs-menu a .dm-icon{font-size:14px;width:18px;text-align:center}
97
+
98
+ /* ── Page ── */
99
+ .page{padding-top:48px;min-height:100vh}
100
+
101
+ /* ── Agent Header ── */
102
+ .agent-header{
103
+ padding:24px 32px;border-bottom:1px solid var(--border-dim);
104
+ background:var(--bg-surface);backdrop-filter:blur(20px);
105
+ display:flex;align-items:center;gap:16px;
106
+ }
107
+ .agent-avatar{
108
+ width:48px;height:48px;border-radius:12px;
109
+ background:var(--accent-bg);border:1px solid var(--accent);
110
+ display:flex;align-items:center;justify-content:center;
111
+ font-family:var(--font-mono);font-size:16px;font-weight:700;color:var(--accent);flex-shrink:0;
112
+ }
113
+ .agent-info{flex:1;min-width:0}
114
+ .agent-name{font-family:var(--font-display);font-size:20px;font-weight:800}
115
+ .agent-meta{display:flex;align-items:center;gap:10px;margin-top:4px}
116
+ .agent-alias{font-family:var(--font-mono);font-size:12px;color:var(--accent)}
117
+ .agent-desc{font-size:12px;color:var(--text-muted);max-width:400px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
118
+ .status-dot{width:8px;height:8px;border-radius:50%;background:var(--green);box-shadow:0 0 8px rgba(74,222,128,0.4)}
119
+ .header-actions{display:flex;gap:8px}
120
+ .header-btn{
121
+ font-family:var(--font-mono);font-size:11px;font-weight:500;
122
+ padding:8px 14px;border-radius:8px;
123
+ border:1px solid var(--border-dim);background:transparent;
124
+ color:var(--text-muted);cursor:pointer;transition:all .2s;text-decoration:none;
125
+ }
126
+ .header-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
127
+ .heartbeat-btn{
128
+ font-family:var(--font-mono);font-size:11px;font-weight:600;
129
+ padding:8px 16px;border-radius:8px;
130
+ border:1px solid var(--accent);background:var(--accent-bg);
131
+ color:var(--accent);cursor:pointer;transition:all .2s;
132
+ }
133
+ .heartbeat-btn:hover{background:var(--accent);color:#000}
134
+ .heartbeat-btn:disabled{opacity:.4;cursor:default}
135
+
136
+ /* ── Dashboard Grid ── */
137
+ .dashboard{display:grid;grid-template-columns:1fr 1fr;gap:20px;padding:24px 32px}
138
+ @media(max-width:900px){.dashboard{grid-template-columns:1fr}}
139
+
140
+ /* ── Section Card ── */
141
+ .section{
142
+ background:var(--bg-card);border:1px solid var(--border-dim);border-radius:var(--radius);
143
+ backdrop-filter:blur(16px);box-shadow:var(--shadow);overflow:hidden;
144
+ }
145
+ .section-header{
146
+ padding:14px 18px;border-bottom:1px solid var(--border-dim);
147
+ display:flex;align-items:center;justify-content:space-between;
148
+ }
149
+ .section-title{font-family:var(--font-mono);font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em}
150
+ .section-count{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);background:var(--bg-input);padding:2px 8px;border-radius:4px}
151
+ .section-body{padding:14px 18px;max-height:400px;overflow-y:auto}
152
+ .section-body::-webkit-scrollbar{width:4px}
153
+ .section-body::-webkit-scrollbar-thumb{background:var(--border-glow);border-radius:2px}
154
+ .section-empty{padding:24px;text-align:center;color:var(--text-muted);font-size:12px}
155
+
156
+ /* ── File Browser ── */
157
+ .file-row{
158
+ display:flex;align-items:center;gap:10px;padding:8px 12px;border-radius:8px;
159
+ border:1px solid var(--border-dim);background:var(--bg-input);margin-bottom:6px;
160
+ cursor:pointer;transition:all .15s;font-size:12px;
161
+ }
162
+ .file-row:hover{border-color:var(--border-glow);background:rgba(56,189,248,0.04)}
163
+ .file-icon{font-size:16px;flex-shrink:0;width:22px;text-align:center}
164
+ .file-info{flex:1;min-width:0}
165
+ .file-name{font-family:var(--font-mono);font-weight:600;color:var(--text-primary);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
166
+ .file-meta{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);margin-top:2px;display:flex;gap:10px}
167
+ .file-source{font-size:9px;font-weight:600;padding:1px 6px;border-radius:3px;text-transform:uppercase;letter-spacing:.04em}
168
+ .file-source.temp{color:var(--amber);background:rgba(251,191,36,0.1);border:1px solid rgba(251,191,36,0.2)}
169
+ .file-source.permanent{color:var(--green);background:rgba(74,222,128,0.08);border:1px solid rgba(74,222,128,0.2)}
170
+ .file-actions{display:flex;gap:4px;opacity:0;transition:opacity .15s}
171
+ .file-row:hover .file-actions{opacity:1}
172
+ .file-action-btn{
173
+ width:26px;height:26px;border-radius:6px;border:1px solid var(--border-dim);
174
+ background:transparent;color:var(--text-muted);cursor:pointer;font-size:12px;
175
+ display:flex;align-items:center;justify-content:center;transition:all .2s;
176
+ }
177
+ .file-action-btn:hover{border-color:var(--border-glow);color:var(--text-primary)}
178
+ .file-filter-bar{display:flex;gap:6px;margin-bottom:10px}
179
+ .file-filter-btn{
180
+ font-family:var(--font-mono);font-size:10px;font-weight:500;
181
+ padding:4px 10px;border-radius:6px;border:1px solid var(--border-dim);
182
+ background:transparent;color:var(--text-muted);cursor:pointer;transition:all .15s;
183
+ }
184
+ .file-filter-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
185
+ .file-filter-btn.active{border-color:var(--accent);color:var(--accent);background:var(--accent-bg)}
186
+
187
+ /* ── Task Cards ── */
188
+ .task-card{
189
+ padding:10px 14px;border-radius:8px;
190
+ border:1px solid var(--border-dim);background:var(--bg-input);
191
+ margin-bottom:8px;transition:all .15s;
192
+ }
193
+ .task-card:hover{border-color:var(--border-glow)}
194
+ .task-card.cross-agent{border-left:2px solid var(--accent);opacity:.85}
195
+ .cross-agent-label{font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:.5px;color:var(--text-muted);margin:12px 0 6px;padding-left:2px}
196
+ .task-top{display:flex;align-items:center;gap:8px;margin-bottom:4px}
197
+ .task-dot{width:6px;height:6px;border-radius:50%;flex-shrink:0}
198
+ .task-dot.proposed{background:var(--text-muted)}
199
+ .task-dot.approved{background:var(--accent)}
200
+ .task-dot.in_progress{background:var(--amber)}
201
+ .task-dot.review{background:var(--purple)}
202
+ .task-dot.done{background:var(--green)}
203
+ .task-title{font-size:13px;font-weight:500;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
204
+ .priority-badge{font-family:var(--font-mono);font-size:9px;font-weight:600;padding:1px 6px;border-radius:3px;text-transform:uppercase}
205
+ .priority-badge.high{background:var(--red-bg);color:var(--red)}
206
+ .priority-badge.medium{background:var(--amber-bg);color:var(--amber)}
207
+ .priority-badge.low{background:var(--green-bg);color:var(--green)}
208
+ .task-meta{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);display:flex;gap:8px}
209
+ .task-actions{display:flex;gap:4px;margin-top:6px}
210
+ .task-action-btn{
211
+ font-family:var(--font-mono);font-size:9px;font-weight:500;
212
+ padding:3px 8px;border-radius:4px;
213
+ border:1px solid var(--border-dim);background:transparent;
214
+ color:var(--text-muted);cursor:pointer;transition:all .15s;
215
+ }
216
+ .task-action-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
217
+
218
+ /* ── Activity Timeline ── */
219
+ .activity-item{
220
+ display:flex;gap:10px;padding:8px 0;
221
+ border-bottom:1px solid var(--border-dim);
222
+ font-size:12px;
223
+ }
224
+ .activity-item:last-child{border-bottom:none}
225
+ .activity-time{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);flex-shrink:0;width:50px}
226
+ .activity-dir{font-family:var(--font-mono);font-size:9px;padding:1px 5px;border-radius:3px;flex-shrink:0}
227
+ .activity-dir.in{background:var(--accent-bg);color:var(--accent)}
228
+ .activity-dir.out{background:var(--purple-bg);color:var(--purple)}
229
+ .activity-text{color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
230
+
231
+ /* ── Stat Cards ── */
232
+ .stat-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:10px}
233
+ .stat-card{
234
+ padding:14px;border-radius:8px;
235
+ border:1px solid var(--border-dim);background:var(--bg-input);
236
+ text-align:center;
237
+ }
238
+ .stat-value{font-family:var(--font-display);font-size:22px;font-weight:800;color:var(--text-primary)}
239
+ .stat-label{font-family:var(--font-mono);font-size:9px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;margin-top:4px}
240
+
241
+ /* ── Heartbeat History ── */
242
+ .hb-item{
243
+ display:flex;align-items:center;gap:10px;padding:8px 0;
244
+ border-bottom:1px solid var(--border-dim);font-size:12px;
245
+ }
246
+ .hb-item:last-child{border-bottom:none}
247
+ .hb-status{width:8px;height:8px;border-radius:50%;flex-shrink:0}
248
+ .hb-status.success{background:var(--green)}
249
+ .hb-status.error{background:var(--red)}
250
+ .hb-status.timeout{background:var(--amber)}
251
+ .hb-time{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);flex-shrink:0}
252
+ .hb-duration{font-family:var(--font-mono);font-size:10px;color:var(--text-muted);flex-shrink:0}
253
+ .hb-summary{color:var(--text-secondary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
254
+ .hb-trigger{font-family:var(--font-mono);font-size:9px;padding:1px 5px;border-radius:3px;background:var(--bg-input);color:var(--text-muted);flex-shrink:0}
255
+ </style>
256
+ </head>
257
+ <body>
258
+
259
+ <div class="topbar">
260
+ <a href="/" style="display:flex;align-items:center;gap:10px;margin-right:28px;text-decoration:none">
261
+ <img class="logo-mark" src="/MyAIforOne-logomark-transparent.svg" alt="MyAIforOne">
262
+ <span class="logo-text">MyAIforOne</span>
263
+ </a>
264
+ <nav class="tab-group">
265
+ <a class="tab-btn" href="/">Home</a>
266
+ <a class="tab-btn" href="/org">Agents</a>
267
+ <a class="tab-btn" href="/ui">Chat</a>
268
+ <a class="tab-btn" href="/library">Library</a>
269
+ <a class="tab-btn" href="/lab">Lab</a>
270
+ </nav>
271
+ <div class="topbar-right">
272
+ <a class="gym-nav-btn gym-tab-link" href="/gym" style="display:none">Gym</a>
273
+ <a href="/marketplace" class="gear-btn" title="Marketplace" style="font-size:11px;font-weight:700;letter-spacing:.03em;padding:0 10px;width:auto">Marketplace</a>
274
+ <a href="/monitor" class="gear-btn" title="Monitor" style="font-size:11px;font-weight:700;letter-spacing:.03em;padding:0 10px;width:auto">Monitor</a>
275
+ <a href="/admin" class="gear-btn" title="Admin" style="font-size:11px;font-weight:700;letter-spacing:.03em;padding:0 10px;width:auto">Admin</a>
276
+ <a href="/user-guide" class="gear-btn" title="User Guide" style="font-size:11px;font-weight:700;letter-spacing:.03em;padding:0 10px;width:auto">User Guide</a>
277
+ <button onclick="window.open('/mini','minibar','width=440,height=460,resizable=yes,scrollbars=no')" title="Open Mini Bar" style="display:flex;align-items:center;justify-content:center;width:34px;height:34px;border-radius:8px;border:1px solid var(--border-dim);background:transparent;color:var(--text-muted);cursor:pointer;font-size:15px;transition:all .2s" onmouseover="this.style.borderColor='var(--border-glow)'" onmouseout="this.style.borderColor='var(--border-dim)'">⊡</button>
278
+ <button class="theme-toggle" onclick="toggleTheme()" title="Toggle theme">☀</button>
279
+ </div>
280
+ </div>
281
+
282
+ <div class="page">
283
+ <div class="agent-header" id="agentHeader">
284
+ <div class="agent-avatar" id="agentAvatar">?</div>
285
+ <div class="agent-info">
286
+ <div class="agent-name" id="agentName">Loading...</div>
287
+ <div class="agent-meta">
288
+ <div class="status-dot"></div>
289
+ <span class="agent-alias" id="agentAlias"></span>
290
+ <span class="agent-desc" id="agentDesc"></span>
291
+ </div>
292
+ </div>
293
+ <div class="header-actions">
294
+ <a class="header-btn" id="backBtn" href="/org">← Org</a>
295
+ <a class="header-btn" id="chatBtn" href="/ui">Chat</a>
296
+ <button class="heartbeat-btn" id="heartbeatBtn" onclick="triggerHeartbeat()">♥ Heartbeat</button>
297
+ </div>
298
+ </div>
299
+
300
+ <div class="dashboard">
301
+ <!-- Tasks -->
302
+ <div class="section">
303
+ <div class="section-header">
304
+ <span class="section-title">Tasks</span>
305
+ <span class="section-count" id="taskCount">0</span>
306
+ </div>
307
+ <div class="section-body" id="taskList">
308
+ <div class="section-empty">Loading tasks...</div>
309
+ </div>
310
+ </div>
311
+
312
+ <!-- Recent Activity -->
313
+ <div class="section">
314
+ <div class="section-header">
315
+ <span class="section-title">Recent Activity</span>
316
+ <a class="header-btn" id="openChatLink" href="/ui" style="font-size:10px;padding:4px 10px">Open Chat →</a>
317
+ </div>
318
+ <div class="section-body" id="activityList">
319
+ <div class="section-empty">Loading activity...</div>
320
+ </div>
321
+ </div>
322
+
323
+ <!-- Cost Stats -->
324
+ <div class="section">
325
+ <div class="section-header">
326
+ <span class="section-title">Cost</span>
327
+ </div>
328
+ <div class="section-body" id="costSection">
329
+ <div class="section-empty">Loading cost data...</div>
330
+ </div>
331
+ </div>
332
+
333
+ <!-- Heartbeat History -->
334
+ <div class="section">
335
+ <div class="section-header">
336
+ <span class="section-title">Heartbeat History</span>
337
+ <span class="section-count" id="hbCount">0</span>
338
+ </div>
339
+ <div class="section-body" id="hbList">
340
+ <div class="section-empty">No heartbeats yet. Trigger one above.</div>
341
+ </div>
342
+ </div>
343
+
344
+ <!-- File Storage -->
345
+ <div class="section" style="grid-column:1/-1">
346
+ <div class="section-header">
347
+ <span class="section-title">File Storage</span>
348
+ <span class="section-count" id="fileCount">0</span>
349
+ </div>
350
+ <div class="section-body" id="fileList" style="max-height:500px">
351
+ <div class="section-empty">Loading files...</div>
352
+ </div>
353
+ </div>
354
+ </div>
355
+ </div>
356
+
357
+ <script>
358
+ function toggleTheme(){
359
+ const t=document.documentElement.dataset.theme==='light'?'':'light';
360
+ document.documentElement.dataset.theme=t;
361
+ localStorage.setItem('theme',t);
362
+ }
363
+ (function(){const t=localStorage.getItem('theme');if(t)document.documentElement.dataset.theme=t})();
364
+
365
+ const params=new URLSearchParams(window.location.search);
366
+ const agentId=params.get('id');
367
+ if(!agentId)window.location='/org';
368
+
369
+ function esc(s){return String(s||'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}
370
+ function timeAgo(ts){
371
+ const d=Date.now()-new Date(ts).getTime();
372
+ if(d<60000)return 'just now';
373
+ if(d<3600000)return Math.floor(d/60000)+'m ago';
374
+ if(d<86400000)return Math.floor(d/3600000)+'h ago';
375
+ return Math.floor(d/86400000)+'d ago';
376
+ }
377
+ function fmtDuration(ms){
378
+ if(ms<1000)return ms+'ms';
379
+ if(ms<60000)return Math.round(ms/1000)+'s';
380
+ return Math.round(ms/60000)+'m';
381
+ }
382
+
383
+ // ── Load all data ──
384
+ async function loadDashboard(){
385
+ document.getElementById('chatBtn').href=`/ui#${agentId}`;
386
+ document.getElementById('openChatLink').href=`/ui#${agentId}`;
387
+
388
+ const [agentRes,tasksRes,costRes,hbRes,filesRes]=await Promise.all([
389
+ fetch(`/api/agents/${agentId}`).catch(()=>null),
390
+ fetch(`/api/agents/${agentId}/tasks`).catch(()=>null),
391
+ fetch(`/api/agents/${agentId}/cost`).catch(()=>null),
392
+ fetch(`/api/agents/${agentId}/heartbeat-history`).catch(()=>null),
393
+ fetch(`/api/agents/${agentId}/files`).catch(()=>null),
394
+ ]);
395
+
396
+ // Agent info
397
+ if(agentRes?.ok){
398
+ const data=await agentRes.json();
399
+ const raw=data.agent||data;
400
+ const a=raw.config?{...raw,...raw.config}:raw;
401
+ const aliases=a.aliases||a.mentionAliases||[];
402
+ document.getElementById('agentName').textContent=a.name||agentId;
403
+ const displayAlias=aliases[0]||(a.mentionAlias)||`@${agentId}`;
404
+ document.getElementById('agentAlias').textContent=displayAlias.startsWith('@')?displayAlias:`@${displayAlias}`;
405
+ document.getElementById('agentDesc').textContent=a.description||'';
406
+ document.getElementById('agentAvatar').textContent=(a.name||agentId).slice(0,2).toUpperCase();
407
+ document.title=`${a.name||agentId} — Dashboard`;
408
+
409
+ // Render activity from recentMessages
410
+ renderActivity(data.recentMessages||[]);
411
+ }
412
+
413
+ // Tasks
414
+ if(tasksRes?.ok){
415
+ const data=await tasksRes.json();
416
+ renderTasks(data.tasks||[],data.crossAgentTasks||[]);
417
+ }else{
418
+ document.getElementById('taskList').innerHTML='<div class="section-empty">No tasks</div>';
419
+ }
420
+
421
+ // Cost
422
+ if(costRes?.ok){
423
+ const data=await costRes.json();
424
+ renderCost(data);
425
+ }else{
426
+ document.getElementById('costSection').innerHTML='<div class="section-empty">No cost data</div>';
427
+ }
428
+
429
+ // Heartbeat
430
+ if(hbRes?.ok){
431
+ const data=await hbRes.json();
432
+ renderHeartbeats(data.history||[]);
433
+ }
434
+
435
+ // Files
436
+ if(filesRes?.ok){
437
+ const data=await filesRes.json();
438
+ renderFiles(data.files||[]);
439
+ }else{
440
+ document.getElementById('fileList').innerHTML='<div class="section-empty">No files</div>';
441
+ }
442
+ }
443
+
444
+ // ── Render Tasks ──
445
+ function renderTaskCard(t,isCross){
446
+ const priorityCls=t.priority||'medium';
447
+ return `<div class="task-card${isCross?' cross-agent':''}">
448
+ <div class="task-top">
449
+ <div class="task-dot ${t.status}"></div>
450
+ <div class="task-title">${esc(t.title)}</div>
451
+ <span class="priority-badge ${priorityCls}">${priorityCls}</span>
452
+ </div>
453
+ <div class="task-meta">
454
+ <span>${t.status}</span>
455
+ <span>${timeAgo(t.createdAt)}</span>
456
+ ${t.assignedBy?`<span>by ${esc(t.assignedBy)}</span>`:''}
457
+ ${isCross&&t._sourceAgent?`<span>on @${esc(t._sourceAgent)}</span>`:''}
458
+ </div>
459
+ ${!isCross&&t.status!=='done'&&t.status!=='closed'?`<div class="task-actions">
460
+ ${t.status==='proposed'||t.status==='approved'?`<button class="task-action-btn" onclick="updateTask('${t.id}','in_progress')">Start</button>`:''}
461
+ <button class="task-action-btn" onclick="updateTask('${t.id}','done')">Done</button>
462
+ </div>`:''}
463
+ </div>`;
464
+ }
465
+
466
+ function renderTasks(tasks,crossTasks){
467
+ const el=document.getElementById('taskList');
468
+ const active=tasks.filter(t=>t.status!=='done'&&t.status!=='closed');
469
+ const done=tasks.filter(t=>t.status==='done'||t.status==='closed');
470
+ const crossActive=crossTasks.filter(t=>t.status!=='done'&&t.status!=='closed');
471
+ const crossDone=crossTasks.filter(t=>t.status==='done'||t.status==='closed');
472
+ const totalCount=tasks.length+crossTasks.length;
473
+ const activeCount=active.length+crossActive.length;
474
+ document.getElementById('taskCount').textContent=totalCount;
475
+
476
+ if(!tasks.length&&!crossTasks.length){el.innerHTML='<div class="section-empty">No tasks assigned</div>';return}
477
+
478
+ let html='';
479
+ const ownSorted=[...active,...done.slice(0,5)];
480
+ if(ownSorted.length){
481
+ html+=ownSorted.map(t=>renderTaskCard(t,false)).join('');
482
+ }
483
+ const crossSorted=[...crossActive,...crossDone.slice(0,5)];
484
+ if(crossSorted.length){
485
+ html+=`<div class="cross-agent-label">On other agents</div>`;
486
+ html+=crossSorted.map(t=>renderTaskCard(t,true)).join('');
487
+ }
488
+ el.innerHTML=html;
489
+ }
490
+
491
+ async function updateTask(taskId,status){
492
+ await fetch(`/api/agents/${agentId}/tasks/${taskId}`,{
493
+ method:'PUT',
494
+ headers:{'Content-Type':'application/json'},
495
+ body:JSON.stringify({status}),
496
+ });
497
+ loadDashboard();
498
+ }
499
+
500
+ // ── Render Activity ──
501
+ function renderActivity(messages){
502
+ const el=document.getElementById('activityList');
503
+ const recent=messages.slice(-10).reverse();
504
+ if(!recent.length){el.innerHTML='<div class="section-empty">No recent activity</div>';return}
505
+
506
+ el.innerHTML=recent.map(m=>{
507
+ const isUser=m.role==='user'||m.sender==='web-user';
508
+ const time=m.timestamp?new Date(m.timestamp).toLocaleTimeString([],{hour:'2-digit',minute:'2-digit'}):'';
509
+ const text=(m.text||m.response||'').slice(0,120);
510
+ return `<div class="activity-item">
511
+ <span class="activity-time">${time}</span>
512
+ <span class="activity-dir ${isUser?'in':'out'}">${isUser?'IN':'OUT'}</span>
513
+ <span class="activity-text">${esc(text)}</span>
514
+ </div>`;
515
+ }).join('');
516
+ }
517
+
518
+ // ── Render Cost ──
519
+ function renderCost(data){
520
+ const el=document.getElementById('costSection');
521
+ const totalCost=(data.totalCost||0).toFixed(4);
522
+ const totalMsgs=data.totalMessages||0;
523
+ const avgCost=totalMsgs>0?(data.totalCost/totalMsgs).toFixed(4):'0';
524
+
525
+ el.innerHTML=`<div class="stat-grid">
526
+ <div class="stat-card">
527
+ <div class="stat-value">$${totalCost}</div>
528
+ <div class="stat-label">Total Cost</div>
529
+ </div>
530
+ <div class="stat-card">
531
+ <div class="stat-value">${totalMsgs}</div>
532
+ <div class="stat-label">Messages</div>
533
+ </div>
534
+ <div class="stat-card">
535
+ <div class="stat-value">$${avgCost}</div>
536
+ <div class="stat-label">Avg / Msg</div>
537
+ </div>
538
+ </div>`;
539
+ }
540
+
541
+ // ── Render Heartbeats ──
542
+ function renderHeartbeats(history){
543
+ const el=document.getElementById('hbList');
544
+ document.getElementById('hbCount').textContent=history.length;
545
+ if(!history.length){el.innerHTML='<div class="section-empty">No heartbeats yet. Trigger one above.</div>';return}
546
+
547
+ el.innerHTML=history.slice(0,10).map(h=>`<div class="hb-item">
548
+ <div class="hb-status ${h.status}"></div>
549
+ <span class="hb-time">${timeAgo(h.triggeredAt)}</span>
550
+ <span class="hb-duration">${fmtDuration(h.durationMs)}</span>
551
+ <span class="hb-summary" title="${esc(h.summary)}">${esc((h.summary||'').slice(0,100))}</span>
552
+ <span class="hb-trigger">${h.triggeredBy}</span>
553
+ </div>`).join('');
554
+ }
555
+
556
+ // ── Trigger Heartbeat ──
557
+ async function triggerHeartbeat(){
558
+ const btn=document.getElementById('heartbeatBtn');
559
+ btn.disabled=true;
560
+ btn.textContent='Running...';
561
+
562
+ try{
563
+ await fetch(`/api/agents/${agentId}/heartbeat`,{
564
+ method:'POST',
565
+ headers:{'Content-Type':'application/json'},
566
+ body:JSON.stringify({triggeredBy:'manual'}),
567
+ });
568
+ btn.textContent='Triggered!';
569
+ // Refresh heartbeat history after a delay (agent takes time to execute)
570
+ setTimeout(()=>{
571
+ loadDashboard();
572
+ btn.disabled=false;
573
+ btn.textContent='♥ Heartbeat';
574
+ },5000);
575
+ }catch{
576
+ btn.textContent='Failed';
577
+ setTimeout(()=>{btn.disabled=false;btn.textContent='♥ Heartbeat'},3000);
578
+ }
579
+ }
580
+
581
+ // ── File Browser ──
582
+ let allFiles = [];
583
+ let fileFilter = 'all'; // 'all' | 'temp' | 'permanent'
584
+
585
+ const FILE_ICONS = {
586
+ pdf:'📄',csv:'📊',tsv:'📊',xlsx:'📊',xls:'📊',json:'📋',md:'📝',txt:'📝',
587
+ png:'🖼️',jpg:'🖼️',jpeg:'🖼️',gif:'🖼️',svg:'🖼️',webp:'🖼️',
588
+ html:'🌐',htm:'🌐',docx:'📃',doc:'📃',pptx:'📽️',ppt:'📽️',zip:'📦',
589
+ js:'⚡',ts:'⚡',py:'🐍',css:'🎨',
590
+ };
591
+
592
+ function fileIcon(name) {
593
+ const ext = (name.split('.').pop() || '').toLowerCase();
594
+ return FILE_ICONS[ext] || '📁';
595
+ }
596
+
597
+ function fmtSize(bytes) {
598
+ if (bytes < 1024) return bytes + ' B';
599
+ if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
600
+ return (bytes / 1048576).toFixed(1) + ' MB';
601
+ }
602
+
603
+ function setFileFilter(f) {
604
+ fileFilter = f;
605
+ renderFileList();
606
+ }
607
+
608
+ function renderFiles(files) {
609
+ allFiles = files;
610
+ document.getElementById('fileCount').textContent = files.length;
611
+ renderFileList();
612
+ }
613
+
614
+ function renderFileList() {
615
+ const el = document.getElementById('fileList');
616
+ const filtered = fileFilter === 'all' ? allFiles : allFiles.filter(f => f.source === fileFilter);
617
+ const tempCount = allFiles.filter(f => f.source === 'temp').length;
618
+ const permCount = allFiles.filter(f => f.source === 'permanent').length;
619
+
620
+ let html = `<div class="file-filter-bar">
621
+ <button class="file-filter-btn${fileFilter==='all'?' active':''}" onclick="setFileFilter('all')">All (${allFiles.length})</button>
622
+ <button class="file-filter-btn${fileFilter==='permanent'?' active':''}" onclick="setFileFilter('permanent')">Permanent (${permCount})</button>
623
+ <button class="file-filter-btn${fileFilter==='temp'?' active':''}" onclick="setFileFilter('temp')">Temp (${tempCount})</button>
624
+ </div>`;
625
+
626
+ if (filtered.length === 0) {
627
+ html += '<div class="section-empty">No files in this category</div>';
628
+ el.innerHTML = html;
629
+ return;
630
+ }
631
+
632
+ for (const f of filtered) {
633
+ const enc = encodeURIComponent(f.path);
634
+ html += `<div class="file-row" onclick="downloadAgentFile('${enc}')">
635
+ <span class="file-icon">${fileIcon(f.name)}</span>
636
+ <div class="file-info">
637
+ <div class="file-name" title="${esc(f.path)}">${esc(f.name)}</div>
638
+ <div class="file-meta">
639
+ <span>${fmtSize(f.size)}</span>
640
+ <span>${timeAgo(f.modified)}</span>
641
+ <span class="file-source ${f.source}">${f.source}</span>
642
+ </div>
643
+ </div>
644
+ <div class="file-actions">
645
+ <button class="file-action-btn" onclick="event.stopPropagation();downloadAgentFile('${enc}')" title="Download">⬇</button>
646
+ </div>
647
+ </div>`;
648
+ }
649
+ el.innerHTML = html;
650
+ }
651
+
652
+ function downloadAgentFile(encodedPath) {
653
+ const a = document.createElement('a');
654
+ a.href = `/api/agents/${agentId}/download?path=${encodedPath}`;
655
+ a.download = decodeURIComponent(encodedPath).split('/').pop() || 'file';
656
+ document.body.appendChild(a);
657
+ a.click();
658
+ document.body.removeChild(a);
659
+ }
660
+
661
+ // ── Init ──
662
+ loadDashboard();
663
+ document.addEventListener('click', function(e){
664
+ const dd = document.getElementById('docsDropdown');
665
+ if(dd && !dd.contains(e.target)) document.getElementById('docsMenu')?.classList.remove('open');
666
+ });
667
+ </script>
668
+ <script>(function(){fetch("/api/config/service").then(function(r){return r.json()}).then(function(d){if(d.gymEnabled)document.querySelectorAll(".gym-tab-link").forEach(function(el){el.style.display=""});if(d.gymOnlyMode){var keep=["/gym","/org","/admin"];document.querySelectorAll(".tab-group .tab-btn").forEach(function(el){var h=el.getAttribute("href");if(h&&keep.indexOf(h)===-1&&!el.classList.contains("gym-tab-link"))el.style.display="none"});document.querySelectorAll(".topbar-right .gear-btn").forEach(function(el){var h=el.getAttribute("href");if(h&&["/marketplace","/monitor","/user-guide"].indexOf(h)!==-1)el.style.display="none"})}}).catch(function(){})})()</script>
669
+ </body>
670
+ </html>