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,1643 @@
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>Lab — 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
+ /* ─── Theme Variables (matches main app exactly) ────────────── */
12
+ :root{
13
+ --bg-deep:#060a13;
14
+ --bg-surface:rgba(12,18,33,0.92);
15
+ --bg-card:rgba(16,22,40,0.85);
16
+ --bg-input:rgba(0,0,0,0.35);
17
+ --border-dim:rgba(56,189,248,0.08);
18
+ --border-glow:rgba(56,189,248,0.18);
19
+ --border-active:rgba(56,189,248,0.45);
20
+ --text-primary:rgba(255,255,255,0.92);
21
+ --text-secondary:rgba(255,255,255,0.68);
22
+ --text-muted:rgba(148,163,184,0.55);
23
+ --accent:#22d3ee;
24
+ --accent-soft:#38bdf8;
25
+ --accent-bg:rgba(6,182,212,0.15);
26
+ --accent-glow:rgba(34,211,238,0.12);
27
+ --purple:rgba(139,92,246,0.7);
28
+ --purple-bg:rgba(139,92,246,0.12);
29
+ --green:#4ade80;
30
+ --green-bg:rgba(74,222,128,0.1);
31
+ --amber:#fbbf24;
32
+ --amber-bg:rgba(251,191,36,0.1);
33
+ --msg-user:rgba(56,189,248,0.08);
34
+ --msg-agent:rgba(139,92,246,0.06);
35
+ --shadow:0 2px 12px rgba(0,0,0,0.3);
36
+ --shadow-glow:0 0 20px rgba(34,211,238,0.08);
37
+ --radius:12px;
38
+ --font-sans:'DM Sans',system-ui,-apple-system,sans-serif;
39
+ --font-mono:'IBM Plex Mono',monospace;
40
+ --font-display:'Syne',sans-serif;
41
+ }
42
+
43
+ [data-theme="light"]{
44
+ --bg-deep:#f4f6f9;
45
+ --bg-surface:rgba(255,255,255,0.95);
46
+ --bg-card:rgba(255,255,255,0.9);
47
+ --bg-input:rgba(0,0,0,0.04);
48
+ --border-dim:rgba(0,0,0,0.08);
49
+ --border-glow:rgba(14,116,144,0.18);
50
+ --border-active:rgba(14,116,144,0.45);
51
+ --text-primary:rgba(15,23,42,0.92);
52
+ --text-secondary:rgba(51,65,85,0.8);
53
+ --text-muted:rgba(100,116,139,0.6);
54
+ --accent:#0891b2;
55
+ --accent-soft:#0e7490;
56
+ --accent-bg:rgba(14,116,144,0.08);
57
+ --accent-glow:rgba(14,116,144,0.06);
58
+ --purple:rgba(109,40,217,0.75);
59
+ --purple-bg:rgba(139,92,246,0.08);
60
+ --green:#16a34a;
61
+ --green-bg:rgba(22,163,74,0.08);
62
+ --amber:#d97706;
63
+ --amber-bg:rgba(217,119,6,0.08);
64
+ --msg-user:rgba(14,116,144,0.06);
65
+ --msg-agent:rgba(139,92,246,0.04);
66
+ --shadow:0 1px 8px rgba(0,0,0,0.06);
67
+ --shadow-glow:none;
68
+ }
69
+
70
+ html,body{
71
+ width:100%;height:100%;overflow:hidden;
72
+ background:var(--bg-deep);
73
+ font-family:var(--font-sans);
74
+ color:var(--text-primary);
75
+ transition:background .3s,color .3s;
76
+ }
77
+
78
+ /* ─── Topbar (matches main app) ──────────────────────────────── */
79
+ .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}
80
+ .logo-mark{width:28px;height:28px;border-radius:8px;object-fit:contain}
81
+ .logo-text{font-family:var(--font-display);font-size:15px;font-weight:700;color:var(--accent)}
82
+ .tab-group{display:flex;gap:0;flex-shrink:1;min-width:0;overflow-x:auto}
83
+ .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}
84
+ .tab-btn:hover{color:var(--text-secondary)}
85
+ .tab-btn.active{color:var(--accent)}
86
+ .tab-btn.active::after{content:'';position:absolute;bottom:0;left:10px;right:10px;height:2px;background:var(--accent);border-radius:1px}
87
+ .topbar-right{margin-left:auto;display:flex;align-items:center;gap:8px}
88
+ .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}
89
+ .gym-nav-btn:hover{opacity:.88;transform:translateY(-1px)}
90
+ .docs-dropdown{position:relative;display:inline-block}
91
+ .docs-btn{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:14px;transition:all .2s}
92
+ .docs-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
93
+ .docs-menu{display:none;position:absolute;top:40px;right:0;min-width:140px;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)}
94
+ .docs-menu.open{display:block}
95
+ .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}
96
+ .docs-menu a:hover{background:var(--accent-bg);color:var(--accent)}
97
+ .docs-menu a .dm-icon{font-size:14px;width:18px;text-align:center}
98
+ .theme-toggle,.gear-btn{
99
+ width:34px;height:34px;border-radius:8px;border:1px solid var(--border-dim);
100
+ background:transparent;color:var(--text-muted);cursor:pointer;
101
+ display:flex;align-items:center;justify-content:center;font-size:16px;
102
+ transition:all .2s;
103
+ text-decoration:none;}
104
+ .theme-toggle:hover,.gear-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
105
+
106
+ /* ─── Page body ── */
107
+ .page{padding-top:48px;height:100vh;overflow:hidden;display:flex;flex-direction:column}
108
+
109
+ /* ─── Landing ── */
110
+ #landing{flex:1;overflow-y:auto;display:flex;flex-direction:column;align-items:center;padding:60px 24px 40px}
111
+ .lab-hero{text-align:center;margin-bottom:48px}
112
+ .lab-hero h1{font-family:var(--font-display);font-size:36px;font-weight:800;letter-spacing:-.5px}
113
+ .lab-hero p{margin-top:8px;font-size:14px;color:var(--text-secondary)}
114
+
115
+ .tile-grid{
116
+ display:grid;grid-template-columns:repeat(4,1fr);gap:16px;
117
+ width:100%;max-width:860px;margin-bottom:56px;
118
+ }
119
+ @media(max-width:700px){.tile-grid{grid-template-columns:repeat(2,1fr)}}
120
+
121
+ .tile{
122
+ background:var(--bg-card);
123
+ border:1px solid var(--border-dim);
124
+ border-radius:var(--radius);
125
+ padding:28px 20px 24px;
126
+ cursor:pointer;
127
+ transition:all .25s;
128
+ display:flex;flex-direction:column;align-items:center;gap:14px;
129
+ text-align:center;
130
+ backdrop-filter:blur(16px);-webkit-backdrop-filter:blur(16px);
131
+ box-shadow:var(--shadow);
132
+ }
133
+ .tile:hover{border-color:var(--border-glow);transform:translateY(-2px);box-shadow:var(--shadow),var(--shadow-glow)}
134
+ .tile-icon{font-size:28px;width:56px;height:56px;border-radius:12px;display:flex;align-items:center;justify-content:center}
135
+ .tile-icon.agent{background:var(--accent-bg)}
136
+ .tile-icon.skill{background:var(--green-bg)}
137
+ .tile-icon.app{background:var(--amber-bg)}
138
+ .tile-icon.prompt{background:var(--purple-bg)}
139
+ .tile-label{font-family:var(--font-display);font-size:15px;font-weight:700}
140
+ .tile-desc{font-size:12px;color:var(--text-muted);line-height:1.5}.tile-example{font-size:11px;color:#5c4400;line-height:1.6;margin-top:14px;font-style:italic;background:#fff9c4;border-radius:2px;padding:10px 12px;box-shadow:2px 3px 8px rgba(0,0,0,0.18),0 1px 2px rgba(0,0,0,0.10);transform:rotate(-0.6deg);position:relative;display:block}.tile-example::before{content:"eg.";display:block;font-size:9px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:#a07800;margin-bottom:4px;font-style:normal}
141
+
142
+ /* ─── Artifacts section ── */
143
+ .artifacts-section{width:100%;max-width:860px}
144
+ .section-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px}
145
+ .section-title{font-family:var(--font-mono);font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em}
146
+ .accordion{border:1px solid var(--border-dim);border-radius:10px;overflow:hidden;background:var(--bg-card)}
147
+ .accordion-header{display:flex;align-items:center;gap:10px;padding:10px 14px;cursor:pointer;transition:all .2s;user-select:none}
148
+ .accordion-header:hover{background:var(--bg-input)}
149
+ .accordion-arrow{font-size:10px;color:var(--text-muted);transition:transform .2s;width:14px}
150
+ .accordion.open .accordion-arrow{transform:rotate(90deg)}
151
+ .accordion-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
152
+ .accordion-label{font-size:13px;font-weight:600;flex:1}
153
+ .accordion-count{font-family:var(--font-mono);font-size:11px;color:var(--text-muted);background:var(--bg-input);padding:1px 8px;border-radius:10px}
154
+ .accordion:not(.open) .accordion-body{display:none}
155
+ .accordion.open .accordion-body{display:flex;flex-direction:column;gap:4px;padding:0 10px 10px}
156
+ .artifact-list{display:flex;flex-direction:column;gap:6px}
157
+ .artifact-card{
158
+ display:flex;align-items:center;gap:14px;
159
+ padding:14px 16px;border-radius:var(--radius);
160
+ background:var(--bg-card);border:1px solid var(--border-dim);
161
+ cursor:pointer;transition:all .2s;
162
+ backdrop-filter:blur(16px);box-shadow:var(--shadow);
163
+ }
164
+ .artifact-card:hover{border-color:var(--border-glow);box-shadow:var(--shadow),var(--shadow-glow);transform:translateY(-1px)}
165
+ .artifact-type-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}
166
+ .artifact-name{font-size:13px;font-weight:600;flex:1}
167
+ .artifact-meta{font-family:var(--font-mono);font-size:10px;color:var(--text-muted)}
168
+ .artifact-status{
169
+ font-family:var(--font-mono);
170
+ padding:2px 8px;border-radius:4px;font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.04em;
171
+ }
172
+ .artifact-status.live{background:var(--green-bg);color:var(--green)}
173
+ .artifact-status.draft{background:var(--amber-bg);color:var(--amber)}
174
+ .artifact-status.active{background:var(--accent-bg);color:var(--accent)}
175
+ .artifact-empty{padding:32px;text-align:center;color:var(--text-muted);font-size:13px}
176
+ .artifact-actions{display:flex;gap:4px;margin-left:8px;flex-shrink:0}
177
+ .artifact-actions .act-btn{padding:3px 8px;border-radius:6px;border:1px solid var(--border-dim);background:transparent;color:var(--text-muted);font-size:10px;font-weight:600;cursor:pointer;transition:all .2s;text-decoration:none;white-space:nowrap;display:inline-flex;align-items:center;gap:3px}
178
+ .artifact-actions .act-btn:hover{border-color:var(--accent);color:var(--accent);background:var(--accent-bg)}
179
+ .artifact-actions .act-btn.launch:hover{border-color:var(--green);color:var(--green);background:var(--green-bg)}
180
+ .artifact-actions .act-btn.deploy:hover{border-color:var(--amber);color:var(--amber);background:var(--amber-bg)}
181
+ .artifact-actions .act-btn:disabled,.artifact-actions .act-btn.disabled{opacity:.35;cursor:not-allowed;pointer-events:none}
182
+
183
+ /* ─── Search & Filters ── */
184
+ .search-row{display:flex;gap:8px;margin-bottom:14px;align-items:center}
185
+ .search-input{
186
+ flex:1;font-family:var(--font-sans);font-size:13px;
187
+ padding:8px 14px 8px 34px;border-radius:8px;
188
+ border:1px solid var(--border-dim);background:var(--bg-input);
189
+ color:var(--text-primary);outline:none;transition:border-color .2s;
190
+ }
191
+ .search-input:focus{border-color:var(--accent)}
192
+ .search-input::placeholder{color:var(--text-muted)}
193
+ .search-wrap{position:relative;flex:1}
194
+ .search-icon{position:absolute;left:11px;top:50%;transform:translateY(-50%);font-size:13px;color:var(--text-muted);pointer-events:none}
195
+ .filter-select{
196
+ font-family:var(--font-mono);font-size:10px;
197
+ padding:8px 10px;border-radius:8px;
198
+ border:1px solid var(--border-dim);background:var(--bg-input);
199
+ color:var(--text-primary);outline:none;cursor:pointer;
200
+ transition:border-color .2s;min-width:100px;
201
+ }
202
+ .filter-select:focus{border-color:var(--accent)}
203
+ .search-clear{
204
+ position:absolute;right:8px;top:50%;transform:translateY(-50%);
205
+ background:none;border:none;color:var(--text-muted);cursor:pointer;
206
+ font-size:14px;display:none;padding:2px 4px;
207
+ }
208
+ .search-clear.visible{display:block}
209
+ .search-clear:hover{color:var(--text-primary)}
210
+
211
+ /* ─── Intake Form ── */
212
+ #intake{display:none;flex:1;flex-direction:column;align-items:center;justify-content:center;padding:40px 24px}
213
+ .intake-card{
214
+ width:100%;max-width:560px;
215
+ background:var(--bg-card);border:1px solid var(--border-dim);border-radius:var(--radius);
216
+ padding:32px;backdrop-filter:blur(16px);box-shadow:var(--shadow);
217
+ }
218
+ .intake-header{display:flex;align-items:center;gap:14px;margin-bottom:28px}
219
+ .intake-icon{width:48px;height:48px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0}
220
+ .intake-title{font-family:var(--font-display);font-size:20px;font-weight:800}
221
+ .intake-sub{font-size:12px;color:var(--text-muted);margin-top:2px}
222
+ .intake-field{margin-bottom:20px}
223
+ .intake-label{font-family:var(--font-mono);font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px;display:block}
224
+ .intake-input{
225
+ width:100%;font-family:var(--font-sans);font-size:14px;
226
+ padding:10px 14px;border-radius:8px;
227
+ border:1px solid var(--border-dim);background:var(--bg-input);
228
+ color:var(--text-primary);outline:none;transition:border-color .2s;
229
+ }
230
+ .intake-input:focus{border-color:var(--accent)}
231
+ .intake-input::placeholder{color:var(--text-muted)}
232
+ .intake-textarea{
233
+ width:100%;font-family:var(--font-sans);font-size:14px;
234
+ padding:10px 14px;border-radius:8px;
235
+ border:1px solid var(--border-dim);background:var(--bg-input);
236
+ color:var(--text-primary);outline:none;transition:border-color .2s;
237
+ resize:vertical;min-height:100px;line-height:1.6;
238
+ }
239
+ .intake-textarea:focus{border-color:var(--accent)}
240
+ .intake-textarea::placeholder{color:var(--text-muted)}
241
+ .intake-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:24px}
242
+ .intake-cancel{
243
+ font-family:var(--font-mono);font-size:12px;font-weight:500;
244
+ padding:10px 20px;border-radius:8px;
245
+ border:1px solid var(--border-dim);background:transparent;
246
+ color:var(--text-muted);cursor:pointer;transition:all .2s;
247
+ }
248
+ .intake-cancel:hover{border-color:var(--border-glow);color:var(--text-secondary)}
249
+ .intake-create{
250
+ font-family:var(--font-mono);font-size:12px;font-weight:600;
251
+ padding:10px 24px;border-radius:8px;
252
+ border:1px solid var(--accent);background:var(--accent);
253
+ color:#000;cursor:pointer;transition:all .2s;
254
+ }
255
+ .intake-create:hover{filter:brightness(1.1)}
256
+ .intake-create:disabled{opacity:.4;cursor:default}
257
+ .intake-hint{font-size:11px;color:var(--text-muted);margin-top:4px}.intake-attach-btn{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;border-radius:7px;border:1px solid var(--border-dim);background:transparent;color:var(--text-muted);font-size:11px;cursor:pointer;transition:all .2s;margin-top:8px}.intake-attach-btn:hover,.intake-attach-btn.has-files{border-color:var(--accent);color:var(--accent)}.intake-file-list{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;min-height:0}
258
+
259
+ /* ─── Creation (split layout) ── */
260
+ #creation{display:none;flex:1;flex-direction:column;overflow:hidden}
261
+ .creation-header{
262
+ display:flex;align-items:center;gap:14px;
263
+ padding:10px 20px;border-bottom:1px solid var(--border-dim);
264
+ background:var(--bg-surface);backdrop-filter:blur(20px);
265
+ }
266
+ .back-btn{
267
+ font-family:var(--font-mono);font-size:10px;font-weight:500;
268
+ padding:6px 12px;border-radius:8px;border:1px solid var(--border-dim);
269
+ background:transparent;color:var(--text-muted);cursor:pointer;transition:all .2s;
270
+ }
271
+ .back-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
272
+ .creation-title{font-family:var(--font-display);font-size:14px;font-weight:700;flex:1}
273
+ .creation-actions{display:flex;gap:8px}
274
+ .header-btn{
275
+ font-family:var(--font-mono);font-size:10px;font-weight:500;
276
+ padding:6px 12px;border-radius:8px;
277
+ border:1px solid var(--border-dim);background:transparent;
278
+ color:var(--text-muted);cursor:pointer;transition:all .2s;
279
+ }
280
+ .header-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
281
+ .deploy-btn{
282
+ font-family:var(--font-mono);font-size:10px;font-weight:600;
283
+ padding:6px 16px;border-radius:8px;
284
+ border:1px solid var(--accent);background:var(--accent);
285
+ color:#000;cursor:pointer;transition:all .2s;
286
+ }
287
+ .deploy-btn:hover{filter:brightness(1.1)}
288
+ .deploy-btn:disabled{opacity:.4;cursor:default}
289
+
290
+ .split{display:flex;flex:1;overflow:hidden;position:relative}
291
+
292
+ /* ─── Chat panel ── */
293
+ .chat-panel{
294
+ flex:1;display:flex;flex-direction:column;
295
+ border-right:1px solid var(--border-dim);overflow:hidden;
296
+ }
297
+ .split.canvas-open .chat-panel{flex:0 0 55%}
298
+ .chat-header{
299
+ padding:14px 24px;
300
+ border-bottom:1px solid var(--border-dim);
301
+ background:var(--bg-surface);
302
+ backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px);
303
+ display:flex;align-items:center;gap:12px;
304
+ }
305
+ .chat-header-info{flex:1}
306
+ .chat-header-name{font-family:var(--font-display);font-size:15px;font-weight:700}
307
+ .chat-header-alias{font-family:var(--font-mono);font-size:11px;color:var(--accent)}
308
+ .chat-messages{
309
+ flex:1;overflow-y:auto;padding:20px 24px;
310
+ display:flex;flex-direction:column;gap:12px;
311
+ }
312
+ .chat-messages::-webkit-scrollbar{width:4px}
313
+ .chat-messages::-webkit-scrollbar-thumb{background:var(--border-glow);border-radius:2px}
314
+
315
+ .msg{
316
+ max-width:80%;padding:12px 16px;border-radius:14px;
317
+ font-size:13px;line-height:1.6;
318
+ animation:msgIn .3s ease-out;
319
+ }
320
+ @keyframes msgIn{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
321
+ .msg-user{
322
+ align-self:flex-end;
323
+ background:var(--msg-user);border:1px solid var(--border-glow);
324
+ border-bottom-right-radius:4px;
325
+ }
326
+ .msg-agent{
327
+ align-self:flex-start;
328
+ background:var(--msg-agent);border:1px solid rgba(139,92,246,0.1);
329
+ border-bottom-left-radius:4px;
330
+ }
331
+ .msg-agent pre{
332
+ background:var(--bg-input);border:1px solid var(--border-dim);
333
+ border-radius:8px;padding:10px 12px;margin:8px 0;
334
+ overflow-x:auto;font-family:var(--font-mono);font-size:12px;line-height:1.5;
335
+ }
336
+ .msg-agent code{font-family:var(--font-mono);font-size:12px;background:var(--bg-input);padding:1px 5px;border-radius:4px}
337
+ .msg-agent p{margin:0 0 .6em 0}.msg-agent p:last-child{margin-bottom:0}
338
+
339
+ .thinking{
340
+ align-self:flex-start;display:flex;align-items:center;gap:8px;
341
+ padding:12px 18px;background:var(--msg-agent);border:1px solid rgba(139,92,246,0.1);
342
+ border-radius:14px;border-bottom-left-radius:4px;
343
+ }
344
+ .thinking-dots{display:flex;gap:4px}
345
+ .thinking-dots span{
346
+ width:6px;height:6px;border-radius:50%;background:var(--purple);
347
+ animation:bounce 1.4s ease-in-out infinite;
348
+ }
349
+ .thinking-dots span:nth-child(2){animation-delay:.2s}
350
+ .thinking-dots span:nth-child(3){animation-delay:.4s}
351
+ .thinking-text{font-size:12px;color:var(--text-muted)}
352
+ @keyframes bounce{0%,80%,100%{transform:translateY(0)}40%{transform:translateY(-6px)}}
353
+
354
+ .tool-cards{margin-bottom:8px;display:flex;flex-direction:column;gap:4px}
355
+ .tool-card{
356
+ display:flex;align-items:center;gap:6px;
357
+ padding:4px 10px;border-radius:6px;
358
+ background:var(--purple-bg);border:1px solid rgba(139,92,246,0.15);
359
+ font-size:11px;color:var(--text-muted);
360
+ font-family:var(--font-mono);overflow:hidden;
361
+ }
362
+ .tool-name{color:var(--purple);font-weight:600;white-space:nowrap;flex-shrink:0}
363
+ .tool-cmd{
364
+ background:rgba(0,0,0,0.2);padding:1px 5px;border-radius:3px;
365
+ color:var(--text-secondary);font-size:10px;
366
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:400px;
367
+ }
368
+
369
+ /* ─── Chat input (matches main app) ── */
370
+ .chat-input-wrap{
371
+ padding:12px 16px;border-top:1px solid var(--border-dim);
372
+ background:var(--bg-surface);backdrop-filter:blur(20px);
373
+ }
374
+ .file-drop-zone{
375
+ display:none;padding:12px 16px;margin-bottom:10px;
376
+ border:2px dashed var(--border-glow);border-radius:12px;
377
+ background:var(--bg-input);text-align:center;
378
+ font-size:12px;color:var(--text-muted);cursor:pointer;
379
+ transition:all .2s;min-height:60px;
380
+ display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;
381
+ }
382
+ .file-drop-zone.hidden{display:none}
383
+ .file-drop-zone.dragover{border-color:var(--accent);background:var(--accent-bg);color:var(--accent)}
384
+ .file-drop-zone input[type=file]{display:none}
385
+ .chat-input-box{display:flex;align-items:flex-end;gap:6px}
386
+ .clip-btn,.mic-btn{
387
+ width:44px;height:44px;border-radius:10px;border:1px solid var(--border-dim);
388
+ background:transparent;color:var(--text-muted);cursor:pointer;
389
+ display:flex;align-items:center;justify-content:center;
390
+ font-size:18px;transition:all .2s;flex-shrink:0;
391
+ }
392
+ .clip-btn:hover,.mic-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
393
+ .clip-btn.has-files{border-color:var(--accent);color:var(--accent)}
394
+ .raw-logs-btn{
395
+ width:44px;height:44px;border-radius:10px;border:1px solid var(--border-dim);
396
+ background:transparent;color:var(--text-muted);cursor:pointer;
397
+ display:flex;align-items:center;justify-content:center;
398
+ font-size:14px;font-family:var(--font-mono);transition:all .2s;flex-shrink:0;
399
+ }
400
+ .raw-logs-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
401
+ .raw-logs-btn.active{border-color:var(--accent);color:var(--accent);background:rgba(139,92,246,0.1)}
402
+ .chat-textarea{
403
+ flex:1;resize:none;
404
+ font-family:var(--font-sans);font-size:14px;
405
+ padding:12px 16px;border-radius:12px;
406
+ border:1px solid var(--border-dim);
407
+ background:var(--bg-input);color:var(--text-primary);
408
+ outline:none;min-height:44px;max-height:140px;
409
+ transition:border-color .2s;line-height:1.5;
410
+ }
411
+ .chat-textarea::placeholder{color:var(--text-muted)}
412
+ .chat-textarea:focus{border-color:var(--accent)}
413
+ .send-btn{
414
+ width:44px;height:44px;border-radius:10px;border:none;
415
+ background:var(--accent);color:#fff;cursor:pointer;
416
+ display:flex;align-items:center;justify-content:center;
417
+ font-size:18px;transition:all .2s;flex-shrink:0;
418
+ }
419
+ .send-btn:hover{filter:brightness(1.1);transform:scale(1.05)}
420
+ .send-btn:disabled{opacity:.4;cursor:not-allowed;transform:none}
421
+ .send-btn.queue-mode{background:var(--amber);font-size:11px;font-weight:700;font-family:var(--font-mono);width:auto;padding:0 14px;letter-spacing:.02em}
422
+ .stop-btn{
423
+ width:44px;height:44px;border-radius:10px;border:1px solid rgba(239,68,68,.3);
424
+ background:rgba(239,68,68,.1);color:#ef4444;cursor:pointer;
425
+ display:flex;align-items:center;justify-content:center;
426
+ font-size:14px;transition:all .2s;flex-shrink:0;
427
+ }
428
+ .stop-btn:hover{background:rgba(239,68,68,.2)}
429
+ .raw-logs-drawer{
430
+ display:none;height:200px;background:#0a0a0a;border-top:1px solid var(--border-dim);
431
+ overflow:auto;font-family:var(--font-mono);font-size:11px;line-height:1.5;
432
+ color:#8b8b8b;padding:8px 12px;
433
+ }
434
+ .raw-logs-drawer.open{display:block}
435
+ .raw-logs-header{display:flex;justify-content:space-between;align-items:center;padding:4px 0;border-bottom:1px solid rgba(255,255,255,0.06);margin-bottom:4px}
436
+ .raw-logs-mode-btn{background:none;border:1px solid var(--border-dim);color:var(--text-muted);font-size:10px;padding:2px 8px;border-radius:4px;cursor:pointer;font-family:var(--font-mono)}
437
+ .raw-logs-mode-btn:hover{border-color:var(--accent);color:var(--accent)}
438
+ .raw-logs-drawer pre{margin:0;white-space:pre-wrap;word-break:break-all}
439
+ .raw-logs-drawer .log-line{opacity:0.7}
440
+ .raw-logs-drawer .log-line:last-child{opacity:1}
441
+ .raw-logs-drawer .log-stderr{color:#ef4444}
442
+ .raw-logs-drawer .log-dim{opacity:0.4}
443
+
444
+ /* ─── Canvas panel ── */
445
+ .canvas-panel{
446
+ display:none;flex-direction:column;overflow:hidden;background:var(--bg-deep);
447
+ position:relative;width:45%;
448
+ }
449
+ .split.canvas-open .canvas-panel{display:flex}
450
+ .canvas-resize-handle{
451
+ position:absolute;left:0;top:0;bottom:0;width:5px;
452
+ cursor:col-resize;z-index:10;
453
+ background:transparent;transition:background .15s;
454
+ }
455
+ .canvas-resize-handle:hover,
456
+ .canvas-resize-handle.dragging{background:var(--accent);opacity:.35}
457
+ .split.canvas-resizing{user-select:none}
458
+ .canvas-header{
459
+ padding:12px 20px;border-bottom:1px solid var(--border-dim);
460
+ display:flex;align-items:center;justify-content:space-between;
461
+ background:var(--bg-surface);backdrop-filter:blur(20px);
462
+ }
463
+ .canvas-toggle-btn{
464
+ font-family:var(--font-mono);font-size:10px;font-weight:500;
465
+ padding:6px 12px;border-radius:8px;
466
+ border:1px solid var(--border-dim);background:transparent;
467
+ color:var(--text-muted);cursor:pointer;transition:all .2s;
468
+ }
469
+ .canvas-toggle-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
470
+ .canvas-toggle-btn.active{border-color:rgba(139,92,246,0.3);color:var(--purple);background:var(--purple-bg)}
471
+ .canvas-close-btn{
472
+ font-family:var(--font-mono);font-size:12px;
473
+ padding:4px 8px;border-radius:6px;
474
+ border:1px solid var(--border-dim);background:transparent;
475
+ color:var(--text-muted);cursor:pointer;transition:all .2s;
476
+ }
477
+ .canvas-close-btn:hover{border-color:var(--border-glow);color:var(--text-secondary)}
478
+ .canvas-label{font-family:var(--font-mono);font-size:11px;font-weight:600;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em}
479
+ .canvas-body{flex:1;overflow-y:auto;padding:20px}
480
+ .canvas-body::-webkit-scrollbar{width:4px}
481
+ .canvas-body::-webkit-scrollbar-thumb{background:var(--border-glow);border-radius:2px}
482
+ .canvas-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;height:100%;gap:12px;color:var(--text-muted);text-align:center}
483
+ .canvas-empty-icon{font-size:32px;opacity:.3}
484
+ .canvas-empty-text{font-size:13px}
485
+ .canvas-empty-sub{font-size:11px;opacity:.7}
486
+ .canvas-block{
487
+ background:var(--bg-card);border:1px solid var(--border-dim);border-radius:var(--radius);
488
+ overflow:hidden;margin-bottom:16px;backdrop-filter:blur(16px);box-shadow:var(--shadow);
489
+ }
490
+ .canvas-block-header{
491
+ padding:8px 14px;border-bottom:1px solid var(--border-dim);
492
+ display:flex;align-items:center;justify-content:space-between;
493
+ }
494
+ .canvas-block-type{font-family:var(--font-mono);font-size:9px;font-weight:600;text-transform:uppercase;letter-spacing:.05em;color:var(--text-muted)}
495
+ .canvas-block-body{padding:14px;font-family:var(--font-mono);font-size:12px;line-height:1.6;color:var(--text-primary);white-space:pre-wrap;overflow-x:auto}
496
+ </style>
497
+ </head>
498
+ <body>
499
+
500
+ <div class="topbar">
501
+ <a href="/" style="display:flex;align-items:center;gap:10px;margin-right:28px;text-decoration:none">
502
+ <img class="logo-mark" src="/MyAIforOne-logomark-transparent.svg" alt="MyAIforOne">
503
+ <span class="logo-text">MyAIforOne</span>
504
+ </a>
505
+ <nav class="tab-group">
506
+ <a class="tab-btn" href="/">Home</a>
507
+ <a class="tab-btn" href="/org">Agents</a>
508
+ <a class="tab-btn" href="/ui">Chat</a>
509
+ <a class="tab-btn" href="/library">Library</a>
510
+ <a class="tab-btn active" href="/lab">Lab</a>
511
+ </nav>
512
+ <div class="topbar-right">
513
+ <a class="gym-nav-btn gym-tab-link" href="/gym" style="display:none">Gym</a>
514
+ <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>
515
+ <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>
516
+ <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>
517
+ <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>
518
+ <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>
519
+ <button class="theme-toggle" onclick="toggleTheme()" title="Toggle theme">☀</button>
520
+ </div>
521
+ </div>
522
+
523
+ <div class="page">
524
+
525
+ <!-- ── Landing ── -->
526
+ <div id="landing">
527
+ <div class="lab-hero">
528
+ <h1>Lab</h1>
529
+ <p>What do you want to build?</p>
530
+ </div>
531
+
532
+ <div class="tile-grid">
533
+ <div class="tile" onclick="showIntake('agent')">
534
+ <div class="tile-icon agent">&#x25C8;</div>
535
+ <div class="tile-label">Agent</div>
536
+ <div class="tile-desc">A purpose-built AI with memory, tools, and channels</div>
537
+ <div class="tile-example">An AP aging agent that tracks outstanding invoices, sends payment reminders to vendors, and escalates overdue items to the CFO</div>
538
+ </div>
539
+ <div class="tile" onclick="showIntake('skill')">
540
+ <div class="tile-icon skill">&#x26A1;</div>
541
+ <div class="tile-label">Skill</div>
542
+ <div class="tile-desc">A reusable instruction set any agent can invoke</div>
543
+ <div class="tile-example">Read a bank statement CSV and generate matching double-entry journal entries with correct GL account codes and cost centre allocations</div>
544
+ </div>
545
+ <div class="tile" onclick="showIntake('app')">
546
+ <div class="tile-icon app">&#x25A8;</div>
547
+ <div class="tile-label">App</div>
548
+ <div class="tile-desc">A deployed web application in your platform</div>
549
+ <div class="tile-example">A month-end close tracker that shows each task, owner, status, and days-to-deadline on a visual progress board</div>
550
+ </div>
551
+ <div class="tile" onclick="showIntake('prompt')">
552
+ <div class="tile-icon prompt">&#x2726;</div>
553
+ <div class="tile-label">Prompt</div>
554
+ <div class="tile-desc">A reusable prompt template triggered with !</div>
555
+ <div class="tile-example">Analyze this month's P&amp;L versus budget and write an executive variance commentary highlighting the top 3 drivers of the gap</div>
556
+ </div>
557
+ </div>
558
+
559
+ <div class="artifacts-section">
560
+ <div class="section-header">
561
+ <span class="section-title">Your Work</span>
562
+ </div>
563
+ <div class="search-row" style="margin-bottom:8px">
564
+ <div class="search-wrap">
565
+ <span class="search-icon">&#x1F50D;</span>
566
+ <input class="search-input" id="searchInput" type="text" placeholder="Search..." oninput="renderArtifacts()">
567
+ <button class="search-clear" id="searchClear" onclick="document.getElementById('searchInput').value='';renderArtifacts()">&#x2715;</button>
568
+ </div>
569
+ </div>
570
+ <div class="artifact-list" id="artifactList">
571
+ <div class="artifact-empty">Loading your work...</div>
572
+ </div>
573
+ </div>
574
+ </div>
575
+
576
+ <!-- ── Intake Form ── -->
577
+ <div id="intake">
578
+ <div class="intake-card">
579
+ <div class="intake-header">
580
+ <div class="intake-icon" id="intakeIcon">&#x25C8;</div>
581
+ <div>
582
+ <div class="intake-title" id="intakeTitle">Create Agent</div>
583
+ <div class="intake-sub" id="intakeSub">Describe what you want to build</div>
584
+ </div>
585
+ </div>
586
+ <div class="intake-field">
587
+ <label class="intake-label" for="intakeName">Name</label>
588
+ <input class="intake-input" id="intakeName" type="text" placeholder="My App">
589
+ </div>
590
+ <div class="intake-field">
591
+ <label class="intake-label" for="intakeDesc">Description</label>
592
+ <textarea class="intake-textarea" id="intakeDesc" placeholder="Describe what you'd like to build..."></textarea>
593
+ <div class="intake-hint" id="intakeHint">Be specific — the more detail you give, the better the result.</div>
594
+ </div>
595
+ <div class="intake-field" id="intakeWorkspaceField" style="display:none">
596
+ <label class="intake-label" for="intakeWorkspace">Project Directory</label>
597
+ <div style="display:flex;gap:8px">
598
+ <input class="intake-input" id="intakeWorkspace" type="text" placeholder="~/Desktop/APPs/my-app" style="flex:1">
599
+ <button type="button" class="intake-attach-btn" onclick="openDirPicker()" style="white-space:nowrap;padding:8px 14px">&#128193; Browse</button>
600
+ </div>
601
+ <div class="intake-hint">Where to create the project folder. Leave blank for default.</div>
602
+ </div>
603
+ <div>
604
+ <input type="file" id="intakeFileInput" multiple style="display:none" onchange="handleIntakeFileSelect(event)">
605
+ <button class="intake-attach-btn" id="intakeAttachBtn" onclick="document.getElementById('intakeFileInput').click()" type="button">&#x1F4CE; Attach files</button>
606
+ <div class="intake-file-list" id="intakeFileList"></div>
607
+ </div>
608
+ <div class="intake-actions">
609
+ <button class="intake-cancel" onclick="cancelIntake()">Cancel</button>
610
+ <button class="intake-create" id="intakeCreateBtn" onclick="submitIntake()">Create</button>
611
+ </div>
612
+ </div>
613
+ </div>
614
+
615
+ <!-- ── Creation ── -->
616
+ <div id="creation">
617
+ <div class="creation-header">
618
+ <button class="back-btn" onclick="backToLanding()">&#x25C0; Lab</button>
619
+ <span class="creation-title" id="creationTitle">Creating Agent</span>
620
+ <div class="creation-actions">
621
+ <button class="canvas-toggle-btn" id="canvasToggleBtn" onclick="toggleCanvas()" title="Toggle canvas panel">&#x25A8; Canvas</button>
622
+ <button class="deploy-btn" id="deployBtn" disabled onclick="handleDeploy()">Deploy</button>
623
+ </div>
624
+ </div>
625
+ <div class="split" id="splitWrap">
626
+ <div class="chat-panel">
627
+ <div class="chat-header" id="chatHeader">
628
+ <div style="width:36px;height:36px;border-radius:10px;background:var(--accent-bg);border:1px solid var(--accent);display:flex;align-items:center;justify-content:center;font-family:var(--font-mono);font-size:12px;font-weight:600;color:var(--accent);flex-shrink:0" id="chatAvatar">A</div>
629
+ <div class="chat-header-info">
630
+ <div class="chat-header-name" id="chatAgentName">Agent</div>
631
+ <div class="chat-header-alias" id="chatAgentAlias">@agent</div>
632
+ </div>
633
+ </div>
634
+ <div class="chat-messages" id="chatMessages"></div>
635
+ <div class="chat-input-wrap">
636
+ <div class="file-drop-zone hidden" id="fileDropZone"
637
+ onclick="document.getElementById('fileInput').click()"
638
+ ondragover="event.preventDefault();this.classList.add('dragover')"
639
+ ondragleave="this.classList.remove('dragover')"
640
+ ondrop="event.preventDefault();this.classList.remove('dragover');handleFileDrop(event)">
641
+ <input type="file" id="fileInput" multiple onchange="handleFileSelect(event)">
642
+ <div id="fileListArea"><span>Drop files here or click to browse</span></div>
643
+ </div>
644
+ <div class="chat-input-box">
645
+ <button class="clip-btn" id="clipBtn" onclick="toggleFileZone()" title="Attach files">&#x1F4CE;</button>
646
+ <button class="mic-btn" id="micBtn" onclick="toggleVoice()" title="Click to speak">&#x1F3A4;</button>
647
+ <button class="raw-logs-btn" id="rawLogsBtn" onclick="toggleRawLogs()" title="Raw logs (live feed)">&#x2318;</button>
648
+ <textarea class="chat-textarea" id="chatInput" placeholder="Describe what you want to build..." rows="1"
649
+ onkeydown="handleKey(event)" oninput="autoResize(this)"></textarea>
650
+ <button class="send-btn" id="sendBtn" onclick="sendMessage()" title="Send">&#x2192;</button>
651
+ </div>
652
+ <div class="raw-logs-drawer" id="rawLogsDrawer">
653
+ <div class="raw-logs-header">
654
+ <span style="font-size:10px;color:var(--text-muted);text-transform:uppercase;letter-spacing:.05em">Activity</span>
655
+ <button class="raw-logs-mode-btn" id="rawModeBtn" onclick="toggleRawMode()">&#x25CB; Clean</button>
656
+ </div>
657
+ <pre id="rawLogsContent"></pre>
658
+ </div>
659
+ </div>
660
+ </div>
661
+ <div class="canvas-panel" id="canvasPanel">
662
+ <div class="canvas-resize-handle" id="canvasResizeHandle"></div>
663
+ <div class="canvas-header">
664
+ <span class="canvas-label" id="canvasLabel">Canvas</span>
665
+ <div style="display:flex;gap:6px">
666
+ <button class="header-btn" onclick="clearCanvas()">Clear</button>
667
+ <button class="header-btn" onclick="copyCanvas()">Copy</button>
668
+ <button class="canvas-close-btn" onclick="toggleCanvas()" title="Close canvas">&#x2715;</button>
669
+ </div>
670
+ </div>
671
+ <div class="canvas-body" id="canvasBody">
672
+ <div class="canvas-empty" id="canvasEmpty">
673
+ <div class="canvas-empty-icon">&#x25C7;</div>
674
+ <div class="canvas-empty-text">Canvas is empty</div>
675
+ <div class="canvas-empty-sub">Artifacts will appear here as they're built</div>
676
+ </div>
677
+ </div>
678
+ </div>
679
+ </div>
680
+ </div>
681
+
682
+ </div>
683
+
684
+ <script>
685
+ // ── Theme ──
686
+ function toggleTheme(){
687
+ const t=document.documentElement.dataset.theme==='light'?'':'light';
688
+ document.documentElement.dataset.theme=t;
689
+ localStorage.setItem('theme',t);
690
+ }
691
+ (function(){const t=localStorage.getItem('theme');if(t)document.documentElement.dataset.theme=t})();
692
+
693
+ document.addEventListener('click',e=>{
694
+ const dd=document.getElementById('docsDropdown');
695
+ if(!dd.contains(e.target))document.getElementById('docsMenu').classList.remove('open');
696
+ });
697
+
698
+ // ── State ──
699
+ let currentType=null;
700
+ let currentAgentId=null;
701
+ let currentJobId=null;
702
+ let isStreaming=false;
703
+ let streamController=null;
704
+ let lastEventId=0;
705
+ let allArtifacts=[];
706
+ let canvasBlocks=[];
707
+ let rawLogsOpen=false;
708
+ let rawMode=false;
709
+ let pendingFiles=[];
710
+ let intakePendingFiles=[];
711
+ let messageQueue=[];
712
+ let canvasOpen=false;
713
+
714
+ const CREATOR_AGENTS={
715
+ agent:{id:'agentcreator',name:'Agent Creator',alias:'@agentcreator'},
716
+ skill:{id:'skillcreator',name:'Skill Creator',alias:'@skillcreator'},
717
+ app:{id:'appcreator',name:'App Creator',alias:'@appcreator'},
718
+ prompt:{id:'promptcreator',name:'Prompt Creator',alias:'@promptcreator'},
719
+ };
720
+
721
+ const TYPE_COLORS={
722
+ agent:'var(--accent)',
723
+ skill:'var(--green)',
724
+ app:'var(--amber)',
725
+ prompt:'var(--purple)',
726
+ };
727
+
728
+ const TYPE_LABELS={agent:'Agent',skill:'Skill',app:'App',prompt:'Prompt'};
729
+
730
+ // ── Landing: load artifacts ──
731
+ async function loadArtifacts(){
732
+ try{
733
+ const[agentsRes,appsRes,skillsRes,promptsRes]=await Promise.all([
734
+ fetch('/api/agents'),fetch('/api/apps'),
735
+ fetch('/api/marketplace/skills?source=personal'),fetch('/api/marketplace/prompts?source=personal'),
736
+ ]);
737
+ const{agents}=await agentsRes.json();
738
+ const apps=await appsRes.json();
739
+ const skillsData=await skillsRes.json();
740
+ const promptsData=await promptsRes.json();
741
+ const mySkills=(skillsData.items||[]).filter(s=>s.provider==='me');
742
+ const myPrompts=(promptsData.items||[]).filter(p=>p.provider==='me');
743
+ allArtifacts=[
744
+ ...agents.map(a=>({id:a.id,name:a.name,type:'agent',status:'active',meta:a.id})),
745
+ ...(Array.isArray(apps)?apps:[]).map(a=>({id:a.id,name:a.name,type:'app',status:a.status||'draft',meta:a.category||'',url:a.url||'',agentDeveloper:a.agentDeveloper||''})),
746
+ ...mySkills.map(s=>({id:s.id,name:s.name,type:'skill',status:'active',meta:s.category||''})),
747
+ ...myPrompts.map(p=>({id:p.id,name:p.name,type:'prompt',status:'active',meta:p.category||''})),
748
+ ];
749
+ renderArtifacts();
750
+ }catch(e){
751
+ document.getElementById('artifactList').innerHTML='<div class="artifact-empty">Could not load artifacts</div>';
752
+ }
753
+ }
754
+
755
+ function renderArtifacts(){
756
+ const list=document.getElementById('artifactList');
757
+ const query=(document.getElementById('searchInput')?.value||'').toLowerCase().trim();
758
+ const clearBtn=document.getElementById('searchClear');
759
+ if(clearBtn) clearBtn.classList.toggle('visible',query.length>0);
760
+
761
+ if(!allArtifacts.length){
762
+ list.innerHTML='<div class="artifact-empty">No artifacts yet. Create one above.</div>';
763
+ return;
764
+ }
765
+
766
+ const groups=[
767
+ {type:'app',label:'Apps',dot:TYPE_COLORS.app||'#fbbf24'},
768
+ {type:'agent',label:'Agents',dot:TYPE_COLORS.agent||'#22d3ee'},
769
+ {type:'skill',label:'Skills',dot:TYPE_COLORS.skill||'#4ade80'},
770
+ {type:'prompt',label:'Prompts',dot:TYPE_COLORS.prompt||'rgba(139,92,246,0.7)'},
771
+ ];
772
+
773
+ let html='';
774
+ for(const g of groups){
775
+ let items=allArtifacts.filter(a=>a.type===g.type).sort((a,b)=>(a.name||'').localeCompare(b.name||''));
776
+ if(query) items=items.filter(a=>(a.name||'').toLowerCase().includes(query)||(a.id||'').toLowerCase().includes(query)||(a.meta||'').toLowerCase().includes(query));
777
+ const autoOpen=query&&items.length>0?' open':'';
778
+ html+=`<div class="accordion${autoOpen}" id="acc-${g.type}">
779
+ <div class="accordion-header" onclick="this.parentElement.classList.toggle('open')">
780
+ <span class="accordion-arrow">&#x25B6;</span>
781
+ <div class="accordion-dot" style="background:${g.dot}"></div>
782
+ <span class="accordion-label">${g.label}</span>
783
+ <span class="accordion-count">${items.length}</span>
784
+ </div>
785
+ <div class="accordion-body">`;
786
+ const filtered=items;
787
+ html+=filtered.map(a=>`
788
+ <div class="artifact-card" onclick="editArtifact('${a.type}','${a.id}')">
789
+ <div style="flex:1;min-width:0">
790
+ <div class="artifact-name">${esc(a.name)}</div>
791
+ ${a.meta?`<div class="artifact-meta">${esc(a.meta)}</div>`:''}
792
+ </div>
793
+ <span class="artifact-status ${a.status}">${a.status}</span>
794
+ ${a.type==='app'?`<div class="artifact-actions" onclick="event.stopPropagation()">
795
+ <a class="act-btn launch ${a.url?'':'disabled'}" ${a.url?'href="'+esc(a.url)+'" target="_blank"':''} title="${a.url?'Open app':'No URL yet'}">&#x2197; Launch</a>
796
+ <button class="act-btn" onclick="appAction('modify','${esc(a.id)}','${esc(a.name)}','${esc(a.agentDeveloper)}')" title="Chat with developer agent">&#x270E; Modify</button>
797
+ <button class="act-btn deploy" onclick="appAction('deploy','${esc(a.id)}','${esc(a.name)}','${esc(a.agentDeveloper)}')" title="Deploy or redeploy">${a.status==='live'?'&#x21BB; Redeploy':'&#x2191; Deploy'}</button>
798
+ </div>`:''}
799
+ </div>
800
+ `).join('');
801
+ html+='</div></div>';
802
+ }
803
+ list.innerHTML=html;
804
+ }
805
+
806
+ function appAction(action, appId, appName, devAgent) {
807
+ const agent = devAgent || 'appcreator';
808
+ let msg = '';
809
+ if (action === 'modify') {
810
+ msg = 'I want to make changes to the app "' + appName + '" (id: ' + appId + '). What would you like me to update?';
811
+ } else if (action === 'deploy') {
812
+ msg = 'Deploy "' + appName + '" (id: ' + appId + ') to production. Run the full deploy pipeline — build, push to GitHub, deploy to Railway, verify health.';
813
+ }
814
+ window.location.href = '/ui#' + encodeURIComponent(agent) + '?msg=' + encodeURIComponent(msg);
815
+ }
816
+
817
+ function editArtifact(type,id){
818
+ startCreation(type,`Tell me about the existing ${type} "${id}" — I want to make some changes to it.`);
819
+ }
820
+
821
+ // ── Intake form ──
822
+ const INTAKE_CONFIG={
823
+ agent:{
824
+ icon:'&#x25C8;', iconClass:'agent',
825
+ title:'Create Agent',
826
+ sub:'Define a new AI agent with its own identity and capabilities',
827
+ namePlaceholder:'AP Aging Agent',
828
+ descPlaceholder:'e.g. An AP aging agent that tracks outstanding invoices, sends payment reminders to vendors, and escalates overdue items to the CFO',
829
+ hint:'The agent will be created with memory, tools, and channels based on your description.',
830
+ },
831
+ skill:{
832
+ icon:'&#x26A1;', iconClass:'skill',
833
+ title:'Create Skill',
834
+ sub:'Build a reusable instruction set for agents',
835
+ namePlaceholder:'bank-statement-to-journal',
836
+ descPlaceholder:'e.g. Read a bank statement CSV and generate matching double-entry journal entries with correct GL account codes and cost centre allocations',
837
+ hint:'Skills are markdown files that agents read and follow when a task matches.',
838
+ },
839
+ app:{
840
+ icon:'&#x25A8;', iconClass:'app',
841
+ title:'Create App',
842
+ sub:'Build and deploy a full-stack web application',
843
+ namePlaceholder:'Month-End Close Tracker',
844
+ descPlaceholder:'e.g. A month-end close tracker that shows each task, owner, status, and days-to-deadline on a visual progress board',
845
+ hint:'The app will be built with Express 5 + React 19 + Prisma + Tailwind and deployed to Railway.',
846
+ },
847
+ prompt:{
848
+ icon:'&#x2726;', iconClass:'prompt',
849
+ title:'Create Prompt',
850
+ sub:'Craft a reusable prompt template for agents',
851
+ namePlaceholder:'pl-variance-commentary',
852
+ descPlaceholder:'e.g. Analyze this month\'s P&L versus budget and write an executive variance commentary highlighting the top 3 drivers of the gap',
853
+ hint:'Prompts are invoked with ! followed by the name (e.g. !pl-variance-commentary).',
854
+ },
855
+ };
856
+
857
+ function showIntake(type){
858
+ currentType=type;
859
+ const cfg=INTAKE_CONFIG[type];
860
+ document.getElementById('intakeIcon').innerHTML=cfg.icon;
861
+ document.getElementById('intakeIcon').className='intake-icon '+cfg.iconClass;
862
+ document.getElementById('intakeTitle').textContent=cfg.title;
863
+ document.getElementById('intakeSub').textContent=cfg.sub;
864
+ document.getElementById('intakeName').placeholder=cfg.namePlaceholder;
865
+ document.getElementById('intakeDesc').placeholder=cfg.descPlaceholder;
866
+ document.getElementById('intakeHint').textContent=cfg.hint;
867
+ document.getElementById('intakeName').value='';
868
+ document.getElementById('intakeDesc').value='';
869
+ document.getElementById('intakeWorkspace').value='';
870
+ document.getElementById('intakeWorkspaceField').style.display = type==='app' ? '' : 'none';
871
+
872
+ document.getElementById('landing').style.display='none';
873
+ document.getElementById('intake').style.display='flex';
874
+ document.getElementById('intakeName').focus();
875
+ }
876
+
877
+ function cancelIntake(){
878
+ intakePendingFiles=[];renderIntakeFileList();
879
+ document.getElementById('intake').style.display='none';
880
+ document.getElementById('landing').style.display='flex';
881
+ currentType=null;
882
+ }
883
+
884
+ function handleIntakeFileSelect(e){
885
+ if(e.target.files) for(const f of e.target.files) intakePendingFiles.push(f);
886
+ renderIntakeFileList();
887
+ }
888
+ function renderIntakeFileList(){
889
+ const el=document.getElementById('intakeFileList');
890
+ const btn=document.getElementById('intakeAttachBtn');
891
+ if(!el)return;
892
+ if(!intakePendingFiles.length){el.innerHTML='';if(btn)btn.classList.remove('has-files');return;}
893
+ if(btn)btn.classList.add('has-files');
894
+ el.innerHTML=intakePendingFiles.map((f,i)=>`<span style="display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:6px;background:var(--bg-surface);border:1px solid var(--border-dim);font-family:var(--font-mono);font-size:10px">${esc(f.name)}<span onclick="intakePendingFiles.splice(${i},1);renderIntakeFileList()" style="cursor:pointer;color:var(--text-muted)">&times;</span></span>`).join('');
895
+ }
896
+ async function submitIntake(){
897
+ const name=document.getElementById('intakeName').value.trim();
898
+ const desc=document.getElementById('intakeDesc').value.trim();
899
+ if(!name||!desc)return;
900
+
901
+ const type=currentType;
902
+ const label=TYPE_LABELS[type];
903
+
904
+ // Build structured prompt for the creator agent — for apps, reference the skill directly
905
+ const workspace=document.getElementById('intakeWorkspace').value.trim();
906
+ let msg;
907
+ if(type==='app'){
908
+ msg=`Use the skill "ai41_app_build" to create an app called "${name}". Read the skill first and follow it phase by phase.\n\nDescription: ${desc}`;
909
+ if(workspace) msg+=`\n\nProject directory: ${workspace}`;
910
+ } else {
911
+ msg=`Create a new ${label.toLowerCase()} called "${name}".\n\nDescription: ${desc}`;
912
+ }
913
+
914
+ document.getElementById('intake').style.display='none';
915
+
916
+ // Upload attached files before starting creation
917
+ if(intakePendingFiles.length){
918
+ const creatorId=CREATOR_AGENTS[type]?.id;
919
+ if(creatorId){
920
+ const uploadedPaths=[];
921
+ for(const file of intakePendingFiles){
922
+ try{
923
+ const form=new FormData();form.append('file',file);
924
+ const r=await fetch(`/api/upload/${creatorId}`,{method:'POST',body:form});
925
+ if(r.ok){const d=await r.json();if(d.path)uploadedPaths.push(`${file.name}: ${d.path}`);}
926
+ }catch{}
927
+ }
928
+ if(uploadedPaths.length) msg+=`\n\nAttached files (you can Read these):\n${uploadedPaths.map(p=>`- ${p}`).join('\n')}`;
929
+ intakePendingFiles=[];
930
+ renderIntakeFileList();
931
+ }
932
+ }
933
+
934
+ startCreation(type,msg);
935
+ }
936
+
937
+ // Allow Enter in name field to focus description, Ctrl+Enter in description to submit
938
+ document.addEventListener('keydown',e=>{
939
+ if(document.getElementById('intake').style.display==='flex'){
940
+ if(e.target.id==='intakeName'&&e.key==='Enter'){
941
+ e.preventDefault();
942
+ document.getElementById('intakeDesc').focus();
943
+ }
944
+ if(e.target.id==='intakeDesc'&&e.key==='Enter'&&(e.metaKey||e.ctrlKey)){
945
+ e.preventDefault();
946
+ submitIntake();
947
+ }
948
+ }
949
+ });
950
+
951
+ // ── Creation flow ──
952
+ function startCreation(type,initialMsg=null){
953
+ currentType=type;
954
+ const creator=CREATOR_AGENTS[type];
955
+ currentAgentId=creator.id;
956
+ document.getElementById('chatAgentName').textContent=creator.name;
957
+ document.getElementById('chatAgentAlias').textContent=creator.alias;
958
+ document.getElementById('chatAvatar').textContent=creator.name.charAt(0);
959
+ canvasBlocks=[];
960
+ isStreaming=false;
961
+ currentJobId=null;
962
+ lastEventId=0;
963
+ rawLogsOpen=false;
964
+ pendingFiles=[];
965
+
966
+ document.getElementById('landing').style.display='none';
967
+ document.getElementById('creation').style.display='flex';
968
+ document.getElementById('creationTitle').textContent=`Creating ${TYPE_LABELS[type]}`;
969
+ document.getElementById('canvasLabel').textContent=`${TYPE_LABELS[type]} Canvas`;
970
+ document.getElementById('chatMessages').innerHTML='';
971
+ document.getElementById('chatInput').value='';
972
+ document.getElementById('deployBtn').disabled=true;
973
+ document.getElementById('rawLogsDrawer').classList.remove('open');
974
+ document.getElementById('rawLogsBtn').classList.remove('active');
975
+ renderCanvas();
976
+
977
+ if(initialMsg){
978
+ setTimeout(()=>{
979
+ document.getElementById('chatInput').value=initialMsg;
980
+ sendMessage();
981
+ },100);
982
+ }
983
+ document.getElementById('chatInput').focus();
984
+ }
985
+
986
+ function backToLanding(){
987
+ if(streamController)streamController.abort();
988
+ document.getElementById('creation').style.display='none';
989
+ document.getElementById('landing').style.display='flex';
990
+ currentType=null;currentAgentId=null;
991
+ loadArtifacts();
992
+ }
993
+
994
+ // ── File handling ──
995
+ function toggleFileZone(){
996
+ const zone=document.getElementById('fileDropZone');
997
+ zone.classList.toggle('hidden');
998
+ document.getElementById('clipBtn').classList.toggle('has-files',!zone.classList.contains('hidden'));
999
+ }
1000
+
1001
+ function handleFileSelect(e){
1002
+ if(e.target.files)for(const f of e.target.files)pendingFiles.push(f);
1003
+ renderFileList();
1004
+ }
1005
+
1006
+ function handleFileDrop(e){
1007
+ if(e.dataTransfer.files)for(const f of e.dataTransfer.files)pendingFiles.push(f);
1008
+ renderFileList();
1009
+ }
1010
+
1011
+ function renderFileList(){
1012
+ const area=document.getElementById('fileListArea');
1013
+ if(!pendingFiles.length){area.innerHTML='<span>Drop files here or click to browse</span>';return}
1014
+ area.innerHTML=pendingFiles.map((f,i)=>`<span style="display:inline-flex;align-items:center;gap:4px;padding:3px 8px;border-radius:6px;background:var(--bg-surface);border:1px solid var(--border-dim);font-family:var(--font-mono);font-size:10px;margin:2px">${esc(f.name)}<span onclick="pendingFiles.splice(${i},1);renderFileList()" style="cursor:pointer;color:var(--text-muted)">&times;</span></span>`).join('');
1015
+ document.getElementById('clipBtn').classList.add('has-files');
1016
+ }
1017
+
1018
+ // ── Voice (stub) ──
1019
+ function toggleVoice(){
1020
+ const btn=document.getElementById('micBtn');
1021
+ btn.classList.toggle('recording');
1022
+ // Voice input would go here
1023
+ }
1024
+
1025
+ // ── Raw logs (matches main chat exactly) ──
1026
+ let rawLogsReader=null;
1027
+
1028
+ function toggleRawLogs(){
1029
+ rawLogsOpen=!rawLogsOpen;
1030
+ const drawer=document.getElementById('rawLogsDrawer');
1031
+ const btn=document.getElementById('rawLogsBtn');
1032
+ if(drawer)drawer.classList.toggle('open',rawLogsOpen);
1033
+ if(btn)btn.classList.toggle('active',rawLogsOpen);
1034
+ if(rawLogsOpen)connectRawLogs();
1035
+ }
1036
+
1037
+ function connectRawLogs(){
1038
+ if(!currentJobId){
1039
+ const el=document.getElementById('rawLogsContent');
1040
+ if(el)el.textContent='No active job. Send a message to start streaming logs.\n';
1041
+ return;
1042
+ }
1043
+ streamRawLogs(currentJobId);
1044
+ }
1045
+
1046
+ function toggleRawMode(){
1047
+ rawMode=!rawMode;
1048
+ const btn=document.getElementById('rawModeBtn');
1049
+ if(btn)btn.textContent=rawMode?'● Raw':'○ Clean';
1050
+ }
1051
+
1052
+ async function streamRawLogs(jobId){
1053
+ const el=document.getElementById('rawLogsContent');
1054
+ if(!el)return;
1055
+ el.textContent='';
1056
+
1057
+ try{
1058
+ const res=await fetch(`/api/chat/jobs/${jobId}/raw?after=0`);
1059
+ if(!res.ok)return;
1060
+ const reader=res.body.getReader();
1061
+ rawLogsReader=reader;
1062
+ const decoder=new TextDecoder();
1063
+ let buffer='';
1064
+
1065
+ while(true){
1066
+ const{done,value}=await reader.read();
1067
+ if(done)break;
1068
+
1069
+ buffer+=decoder.decode(value,{stream:true});
1070
+ const lines=buffer.split('\n');
1071
+ buffer=lines.pop()||'';
1072
+
1073
+ for(const line of lines){
1074
+ if(!line.startsWith('data: '))continue;
1075
+ const data=line.slice(6);
1076
+ if(data==='[DONE]'){
1077
+ el.textContent+='\n--- Job complete ---\n';
1078
+ break;
1079
+ }
1080
+ if(rawMode){
1081
+ const span=document.createElement('span');
1082
+ span.className=data.startsWith('[stderr]')?'log-line log-stderr':'log-line';
1083
+ span.textContent=data+'\n';
1084
+ el.appendChild(span);
1085
+ }else{
1086
+ const fmt=formatLogLine(data);
1087
+ if(fmt){
1088
+ const span=document.createElement('span');
1089
+ span.className='log-line'+(fmt.cls?' '+fmt.cls:'');
1090
+ span.textContent=fmt.icon+' '+fmt.text+'\n';
1091
+ el.appendChild(span);
1092
+ }
1093
+ }
1094
+ const drawer=document.getElementById('rawLogsDrawer');
1095
+ if(drawer)drawer.scrollTop=drawer.scrollHeight;
1096
+ }
1097
+ }
1098
+ }catch{/* connection closed */}
1099
+ }
1100
+
1101
+ function formatLogLine(data){
1102
+ if(data.startsWith('[stderr]'))return{icon:'⚠',text:data.slice(9),cls:'log-stderr'};
1103
+ try{
1104
+ let evt=JSON.parse(data);
1105
+ if(evt.type==='stream_event'&&evt.event)evt=evt.event;
1106
+
1107
+ // Text output
1108
+ if(evt.type==='content_block_delta'&&evt.delta?.type==='text_delta')
1109
+ return{icon:'💬',text:evt.delta.text};
1110
+
1111
+ // Thinking
1112
+ if(evt.type==='content_block_delta'&&evt.delta?.type==='thinking_delta')
1113
+ return{icon:'🧠',text:evt.delta.thinking};
1114
+
1115
+ // Tool starting
1116
+ if(evt.type==='content_block_start'&&evt.content_block?.type==='tool_use')
1117
+ return{icon:'🔧',text:evt.content_block.name};
1118
+
1119
+ // Tool call complete
1120
+ if(evt.type==='assistant'&&evt.message?.content){
1121
+ const tools=evt.message.content.filter(b=>b.type==='tool_use');
1122
+ if(tools.length){
1123
+ return{icon:'📖',text:tools.map(t=>{
1124
+ const path=t.input?.file_path||t.input?.command?.slice(0,80)||t.input?.pattern||'';
1125
+ return t.name+(path?' '+path:'');
1126
+ }).join(', ')};
1127
+ }
1128
+ const texts=evt.message.content.filter(b=>b.type==='text'&&b.text);
1129
+ if(texts.length)return{icon:'💬',text:texts.map(t=>t.text).join(' ')};
1130
+ }
1131
+
1132
+ // Tool result
1133
+ if(evt.type==='user'&&evt.message?.content?.[0]?.type==='tool_result')
1134
+ return{icon:'✓',text:'tool_result',cls:'log-dim'};
1135
+
1136
+ // Skip noise
1137
+ if(evt.type==='content_block_delta'&&evt.delta?.type==='input_json_delta')return null;
1138
+ if(evt.type==='content_block_stop'||evt.type==='message_stop'||evt.type==='message_delta')return null;
1139
+ if(evt.type==='rate_limit_event')return null;
1140
+ if(evt.type==='content_block_delta'&&evt.delta?.type==='signature_delta')return null;
1141
+
1142
+ // System init
1143
+ if(evt.type==='system'&&evt.subtype==='init')
1144
+ return{icon:'●',text:'Session started — '+(evt.model||'')+' | '+(evt.mcp_servers?.length||0)+' MCPs'};
1145
+ if(evt.type==='system')return{icon:'⚙',text:evt.subtype||'system'};
1146
+
1147
+ // Result
1148
+ if(evt.type==='result')
1149
+ return{icon:'🏁',text:'Done — '+(evt.duration_ms?Math.round(evt.duration_ms/1000)+'s':'')};
1150
+
1151
+ // Message start
1152
+ if(evt.type==='message_start')return null;
1153
+
1154
+ // Fallback
1155
+ return{icon:'·',text:evt.type||data.slice(0,80),cls:'log-dim'};
1156
+ }catch{
1157
+ return{icon:'·',text:data.slice(0,120)};
1158
+ }
1159
+ }
1160
+
1161
+ // ── Chat ──
1162
+ function handleKey(e){
1163
+ if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendMessage()}
1164
+ }
1165
+
1166
+ function autoResize(el){
1167
+ el.style.height='auto';
1168
+ el.style.height=Math.min(el.scrollHeight,140)+'px';
1169
+ }
1170
+
1171
+ let streamText='';
1172
+ let streamTools=[];
1173
+
1174
+ async function sendMessage(){
1175
+ const input=document.getElementById('chatInput');
1176
+ const text=input.value.trim();
1177
+ if(!text)return;
1178
+ if(isStreaming){
1179
+ // Queue mode — queue the message
1180
+ messageQueue.push(text);
1181
+ input.value='';
1182
+ input.style.height='auto';
1183
+ return;
1184
+ }
1185
+
1186
+ input.value='';
1187
+ input.style.height='auto';
1188
+ appendMsg('user',text);
1189
+
1190
+ isStreaming=true;
1191
+ streamText='';
1192
+ streamTools=[];
1193
+ updateInputButtons();
1194
+
1195
+ // Show thinking with "Starting..." status
1196
+ appendThinking('Starting...');
1197
+
1198
+ try{
1199
+ // Upload pending files first
1200
+ if(pendingFiles.length&&currentAgentId){
1201
+ for(const file of pendingFiles){
1202
+ const form=new FormData();
1203
+ form.append('file',file);
1204
+ await fetch(`/api/upload/${currentAgentId}`,{method:'POST',body:form});
1205
+ }
1206
+ pendingFiles=[];
1207
+ renderFileList();
1208
+ document.getElementById('fileDropZone').classList.add('hidden');
1209
+ document.getElementById('clipBtn').classList.remove('has-files');
1210
+ }
1211
+
1212
+ const startRes=await fetch(`/api/chat/${currentAgentId}/stream`,{
1213
+ method:'POST',
1214
+ headers:{'Content-Type':'application/json'},
1215
+ body:JSON.stringify({text}),
1216
+ });
1217
+ if(!startRes.ok)throw new Error(`HTTP ${startRes.status}`);
1218
+ const{jobId}=await startRes.json();
1219
+ if(!jobId)throw new Error('No jobId');
1220
+ currentJobId=jobId;
1221
+ lastEventId=0;
1222
+ updateInputButtons();
1223
+ document.getElementById('rawLogsContent').textContent='';
1224
+ if(rawLogsOpen)streamRawLogs(jobId);
1225
+ await streamJob(jobId,text);
1226
+ }catch(err){
1227
+ removeThinking();
1228
+ appendMsg('agent',`Error: ${err.message}`);
1229
+ finishStreaming();
1230
+ }
1231
+ }
1232
+
1233
+ async function streamJob(jobId,userText){
1234
+ streamController=new AbortController();
1235
+ let currentMsgEl=null;
1236
+ let retries=0;
1237
+ const MAX_RETRIES=10;
1238
+ let isDone=false;
1239
+
1240
+ while(!isDone&&retries<MAX_RETRIES){
1241
+ try{
1242
+ const res=await fetch(`/api/chat/jobs/${jobId}/stream?after=${lastEventId}`,{signal:streamController.signal});
1243
+ if(!res.ok){
1244
+ if(res.status===404){
1245
+ // Service restarted — recover
1246
+ try{
1247
+ await fetch(`/api/agents/${currentAgentId}/recover`,{
1248
+ method:'POST',headers:{'Content-Type':'application/json'},
1249
+ body:JSON.stringify({userText,response:streamText}),
1250
+ });
1251
+ }catch{}
1252
+ isDone=true;break;
1253
+ }
1254
+ retries++;
1255
+ await new Promise(r=>setTimeout(r,1000));
1256
+ continue;
1257
+ }
1258
+ const reader=res.body.getReader();
1259
+ const decoder=new TextDecoder();
1260
+ let buf='';
1261
+ retries=0;
1262
+
1263
+ while(true){
1264
+ const{done,value}=await reader.read();
1265
+ if(done)break;
1266
+ buf+=decoder.decode(value,{stream:true});
1267
+ const lines=buf.split('\n');
1268
+ buf=lines.pop()||'';
1269
+
1270
+ for(const line of lines){
1271
+ if(line.startsWith('id: ')){
1272
+ lastEventId=parseInt(line.slice(4))+1;
1273
+ continue;
1274
+ }
1275
+ if(!line.startsWith('data: '))continue;
1276
+ const data=line.slice(6);
1277
+ if(data==='[DONE]'){isDone=true;continue}
1278
+
1279
+ try{
1280
+ const event=JSON.parse(data);
1281
+ if(event.type==='text'){
1282
+ removeThinking();
1283
+ streamText+=event.data;
1284
+ if(!currentMsgEl){
1285
+ currentMsgEl=appendMsgEl('agent');
1286
+ currentMsgEl.id='streamingContent';
1287
+ }
1288
+ currentMsgEl.innerHTML=renderMarkdown(streamText);
1289
+ scrollToBottom();
1290
+ extractCanvasBlocks(streamText);
1291
+ }else if(event.type==='tool'){
1292
+ if(event.tool){
1293
+ const hasInput=event.tool.input&&Object.keys(event.tool.input).length>0;
1294
+ const existingIdx=hasInput?streamTools.findLastIndex(t=>t.name===event.tool.name&&(!t.input||Object.keys(t.input).length===0)):-1;
1295
+ if(existingIdx>=0){
1296
+ streamTools[existingIdx].input=event.tool.input;
1297
+ }else{
1298
+ streamTools.push({name:event.tool.name,input:event.tool.input});
1299
+ }
1300
+ updateToolCards();
1301
+ }
1302
+ }else if(event.type==='status'){
1303
+ const thinkEl=document.getElementById('thinkingMsg');
1304
+ if(thinkEl&&!streamText){
1305
+ const statusText=thinkEl.querySelector('.thinking-text');
1306
+ if(statusText)statusText.textContent=event.data||'Thinking...';
1307
+ }
1308
+ }else if(event.type==='stopped'){
1309
+ isDone=true;
1310
+ }else if(event.type==='error'){
1311
+ removeThinking();
1312
+ appendMsg('agent',`Error: ${event.data}`);
1313
+ isDone=true;
1314
+ }
1315
+ }catch{}
1316
+ }
1317
+ }
1318
+
1319
+ if(!isDone){
1320
+ retries++;
1321
+ if(retries>=3){
1322
+ const thinkEl=document.getElementById('thinkingMsg');
1323
+ if(thinkEl){
1324
+ const statusText=thinkEl.querySelector('.thinking-text');
1325
+ if(statusText)statusText.textContent=`Reconnecting... (${retries}/${MAX_RETRIES})`;
1326
+ }
1327
+ }
1328
+ await new Promise(r=>setTimeout(r,500));
1329
+ }
1330
+ }catch(e){
1331
+ if(e.name==='AbortError')return;
1332
+ retries++;
1333
+ if(retries>=3&&retries<MAX_RETRIES){
1334
+ const thinkEl=document.getElementById('thinkingMsg');
1335
+ if(thinkEl){
1336
+ const statusText=thinkEl.querySelector('.thinking-text');
1337
+ if(statusText)statusText.textContent=`Reconnecting... (${retries}/${MAX_RETRIES})`;
1338
+ }
1339
+ }
1340
+ if(retries<MAX_RETRIES)await new Promise(r=>setTimeout(r,500*retries));
1341
+ }
1342
+ }
1343
+
1344
+ // Finalize
1345
+ removeThinking();
1346
+ currentJobId=null;
1347
+ finishStreaming();
1348
+
1349
+ // Auto-send next queued message
1350
+ if(messageQueue.length>0){
1351
+ const next=messageQueue.shift();
1352
+ document.getElementById('chatInput').value=next;
1353
+ sendMessage();
1354
+ }
1355
+ }
1356
+
1357
+ function stopStreaming(){
1358
+ if(currentJobId)fetch(`/api/chat/jobs/${currentJobId}/stop`,{method:'POST'});
1359
+ if(streamController)streamController.abort();
1360
+ currentJobId=null;
1361
+ finishStreaming();
1362
+ }
1363
+
1364
+ function updateInputButtons(){
1365
+ const sendBtn=document.getElementById('sendBtn');
1366
+ const inputBox=document.querySelector('.chat-input-box');
1367
+ // Add/remove stop button
1368
+ let stopBtn=document.getElementById('stopBtn');
1369
+ if(isStreaming&&currentJobId){
1370
+ sendBtn.innerHTML='Queue';
1371
+ sendBtn.classList.add('queue-mode');
1372
+ sendBtn.disabled=false;
1373
+ if(!stopBtn){
1374
+ stopBtn=document.createElement('button');
1375
+ stopBtn.id='stopBtn';
1376
+ stopBtn.className='stop-btn';
1377
+ stopBtn.onclick=stopStreaming;
1378
+ stopBtn.title='Stop (Esc)';
1379
+ stopBtn.innerHTML='&#x25A0;';
1380
+ inputBox.insertBefore(stopBtn,sendBtn);
1381
+ }
1382
+ }else{
1383
+ sendBtn.innerHTML='&#x2192;';
1384
+ sendBtn.classList.remove('queue-mode');
1385
+ sendBtn.disabled=false;
1386
+ if(stopBtn)stopBtn.remove();
1387
+ }
1388
+ }
1389
+
1390
+ function finishStreaming(){
1391
+ isStreaming=false;
1392
+ streamText='';
1393
+ streamTools=[];
1394
+ updateInputButtons();
1395
+ document.getElementById('chatInput').disabled=false;
1396
+ document.getElementById('chatInput').focus();
1397
+ document.getElementById('deployBtn').disabled=canvasBlocks.length===0;
1398
+ }
1399
+
1400
+ // ── Message rendering (matches main app) ──
1401
+ function appendMsg(role,text){
1402
+ const el=appendMsgEl(role);
1403
+ el.innerHTML=renderMarkdown(text);
1404
+ return el;
1405
+ }
1406
+
1407
+ function appendMsgEl(role){
1408
+ const div=document.createElement('div');
1409
+ div.className=`msg msg-${role}`;
1410
+ document.getElementById('chatMessages').appendChild(div);
1411
+ scrollToBottom();
1412
+ return div;
1413
+ }
1414
+
1415
+ function appendThinking(statusText){
1416
+ const div=document.createElement('div');
1417
+ div.className='thinking';
1418
+ div.id='thinkingMsg';
1419
+ div.innerHTML=`<div class="thinking-dots"><span></span><span></span><span></span></div><span class="thinking-text">${statusText||'Thinking...'}</span>`;
1420
+ document.getElementById('chatMessages').appendChild(div);
1421
+ scrollToBottom();
1422
+ }
1423
+
1424
+ function removeThinking(){
1425
+ const el=document.getElementById('thinkingMsg');
1426
+ if(el)el.remove();
1427
+ const toolsWrap=document.getElementById('streamingToolsWrap');
1428
+ if(toolsWrap)toolsWrap.remove();
1429
+ }
1430
+
1431
+ // Escape to stop
1432
+ document.addEventListener('keydown',e=>{
1433
+ if(e.key==='Escape'&&isStreaming)stopStreaming();
1434
+ });
1435
+
1436
+ function updateToolCards(){
1437
+ let wrap=document.getElementById('streamingToolsWrap');
1438
+ if(!wrap){
1439
+ wrap=document.createElement('div');
1440
+ wrap.id='streamingToolsWrap';
1441
+ wrap.className='tool-cards';
1442
+ // Insert before thinking indicator or at end
1443
+ const thinkEl=document.getElementById('thinkingMsg');
1444
+ const chatMsgs=document.getElementById('chatMessages');
1445
+ if(thinkEl)chatMsgs.insertBefore(wrap,thinkEl);
1446
+ else chatMsgs.appendChild(wrap);
1447
+ }
1448
+ wrap.innerHTML=streamTools.map(t=>{
1449
+ const inputStr=t.input?Object.entries(t.input).map(([k,v])=>`${k}=${typeof v==='string'?v:JSON.stringify(v)}`).join(' '):'';
1450
+ return `<div class="tool-card"><span class="tool-name">${esc(t.name)}</span>${inputStr?`<span class="tool-cmd">${esc(inputStr).slice(0,120)}</span>`:''}</div>`;
1451
+ }).join('');
1452
+ scrollToBottom();
1453
+ }
1454
+
1455
+ function appendToolCard(name){
1456
+ streamTools.push({name});
1457
+ updateToolCards();
1458
+ }
1459
+
1460
+ function scrollToBottom(){
1461
+ const el=document.getElementById('chatMessages');
1462
+ el.scrollTop=el.scrollHeight;
1463
+ }
1464
+
1465
+ // ── Markdown renderer ──
1466
+ function renderMarkdown(text){
1467
+ return text
1468
+ .replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
1469
+ .replace(/```(\w*)\n([\s\S]*?)```/g,(_,lang,code)=>
1470
+ `<pre><code>${code.trimEnd()}</code></pre>`)
1471
+ .replace(/`([^`]+)`/g,'<code>$1</code>')
1472
+ .replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>')
1473
+ .replace(/\*([^*]+)\*/g,'<em>$1</em>')
1474
+ .replace(/^#{1,3} (.+)$/gm,(_,t)=>`<strong>${t}</strong>`)
1475
+ .replace(/\n/g,'<br>');
1476
+ }
1477
+
1478
+ // ── Canvas ──
1479
+ function extractCanvasBlocks(text){
1480
+ const regex=/```(json|yaml|markdown|md|typescript|ts|javascript|js|bash|sh)?\n([\s\S]*?)```/g;
1481
+ const blocks=[];let m;
1482
+ while((m=regex.exec(text))!==null)blocks.push({lang:m[1]||'text',content:m[2].trimEnd()});
1483
+ if(blocks.length!==canvasBlocks.length){
1484
+ const hadBlocks=canvasBlocks.length>0;
1485
+ canvasBlocks=blocks;
1486
+ renderCanvas();
1487
+ document.getElementById('deployBtn').disabled=blocks.length===0;
1488
+ // Auto-open canvas when first artifact appears
1489
+ if(!hadBlocks&&blocks.length>0)openCanvas();
1490
+ }
1491
+ }
1492
+
1493
+ function renderCanvas(){
1494
+ const body=document.getElementById('canvasBody');
1495
+ if(!canvasBlocks.length){
1496
+ body.innerHTML=`<div class="canvas-empty"><div class="canvas-empty-icon">&#x25C7;</div><div class="canvas-empty-text">Canvas is empty</div><div class="canvas-empty-sub">Artifacts will appear here as they're built</div></div>`;
1497
+ return;
1498
+ }
1499
+ body.innerHTML=canvasBlocks.map((b,i)=>`
1500
+ <div class="canvas-block">
1501
+ <div class="canvas-block-header">
1502
+ <span class="canvas-block-type">${b.lang}</span>
1503
+ <button class="header-btn" onclick="copyBlock(${i})">Copy</button>
1504
+ </div>
1505
+ <div class="canvas-block-body">${esc(b.content)}</div>
1506
+ </div>
1507
+ `).join('');
1508
+ }
1509
+
1510
+ function clearCanvas(){canvasBlocks=[];renderCanvas();document.getElementById('deployBtn').disabled=true}
1511
+ function copyCanvas(){
1512
+ const text=canvasBlocks.map(b=>'```'+b.lang+'\n'+b.content+'\n```').join('\n\n');
1513
+ navigator.clipboard.writeText(text).catch(()=>{});
1514
+ }
1515
+ function copyBlock(i){navigator.clipboard.writeText(canvasBlocks[i]?.content||'').catch(()=>{})}
1516
+ function handleDeploy(){
1517
+ const input=document.getElementById('chatInput');
1518
+ input.value='Deploy this — write the files and register it.';
1519
+ sendMessage();
1520
+ }
1521
+
1522
+ // ── Canvas toggle & resize ──
1523
+ function toggleCanvas(){
1524
+ canvasOpen=!canvasOpen;
1525
+ const split=document.getElementById('splitWrap');
1526
+ const btn=document.getElementById('canvasToggleBtn');
1527
+ split.classList.toggle('canvas-open',canvasOpen);
1528
+ btn.classList.toggle('active',canvasOpen);
1529
+ // Reset width on close
1530
+ if(!canvasOpen){
1531
+ const panel=document.getElementById('canvasPanel');
1532
+ if(panel)panel.style.width='';
1533
+ }
1534
+ }
1535
+
1536
+ function openCanvas(){
1537
+ if(!canvasOpen)toggleCanvas();
1538
+ }
1539
+
1540
+ // Resize handle
1541
+ (function(){
1542
+ const handle=document.getElementById('canvasResizeHandle');
1543
+ if(!handle)return;
1544
+ handle.addEventListener('mousedown',e=>{
1545
+ e.preventDefault();
1546
+ const split=document.getElementById('splitWrap');
1547
+ const panel=document.getElementById('canvasPanel');
1548
+ if(!split||!panel)return;
1549
+ split.classList.add('canvas-resizing');
1550
+ handle.classList.add('dragging');
1551
+ const onMove=e=>{
1552
+ const rect=split.getBoundingClientRect();
1553
+ const pxFromRight=rect.right-e.clientX;
1554
+ const totalW=rect.width;
1555
+ const pct=Math.max(20,Math.min(80,(pxFromRight/totalW)*100));
1556
+ panel.style.width=pct+'%';
1557
+ };
1558
+ const onUp=()=>{
1559
+ split.classList.remove('canvas-resizing');
1560
+ handle.classList.remove('dragging');
1561
+ document.removeEventListener('mousemove',onMove);
1562
+ document.removeEventListener('mouseup',onUp);
1563
+ };
1564
+ document.addEventListener('mousemove',onMove);
1565
+ document.addEventListener('mouseup',onUp);
1566
+ });
1567
+ })();
1568
+
1569
+ // ── Utils ──
1570
+ function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')}
1571
+
1572
+ // ── Directory Picker ──
1573
+ let dirPickerPath='~';
1574
+ function openDirPicker(){ dirPickerPath='~'; renderDirPicker(); document.getElementById('dirPickerOverlay').classList.remove('hidden'); }
1575
+ function closeDirPicker(){ document.getElementById('dirPickerOverlay').classList.add('hidden'); }
1576
+ async function renderDirPicker(){
1577
+ const list=document.getElementById('dirPickerList');
1578
+ const pathEl=document.getElementById('dirPickerPath');
1579
+ list.innerHTML='<div style="padding:20px;text-align:center;color:var(--text-muted)">Loading...</div>';
1580
+ try{
1581
+ const res=await fetch(`/api/browse-dirs?path=${encodeURIComponent(dirPickerPath)}`);
1582
+ const data=await res.json();
1583
+ if(data.error){ list.innerHTML=`<div style="padding:20px;color:#f87171">${data.error}</div>`; return; }
1584
+ dirPickerPath=data.path;
1585
+ pathEl.textContent=data.path;
1586
+ let html='';
1587
+ if(data.path!=='~'&&data.path!=='/'){
1588
+ html+=`<div class="dir-item dir-parent" onclick="dirPickerUp()">&#x2B06; ..</div>`;
1589
+ }
1590
+ for(const d of data.dirs){
1591
+ html+=`<div class="dir-item" onclick="dirPickerInto('${d.replace(/'/g,"\\'")}')">${d}</div>`;
1592
+ }
1593
+ if(!data.dirs.length&&!html) html='<div style="padding:20px;text-align:center;color:var(--text-muted)">Empty directory</div>';
1594
+ list.innerHTML=html;
1595
+ }catch(e){ list.innerHTML=`<div style="padding:20px;color:#f87171">Error: ${e.message}</div>`; }
1596
+ }
1597
+ function dirPickerInto(name){ dirPickerPath=dirPickerPath==='/'?'/'+name:dirPickerPath+'/'+name; renderDirPicker(); }
1598
+ function dirPickerUp(){ const parts=dirPickerPath.split('/'); parts.pop(); dirPickerPath=parts.join('/')||'~'; renderDirPicker(); }
1599
+ function dirPickerSelect(){
1600
+ const name=document.getElementById('intakeName').value.trim().toLowerCase().replace(/\s+/g,'-').replace(/[^a-z0-9-]/g,'');
1601
+ const suffix=name?'/'+name:'';
1602
+ document.getElementById('intakeWorkspace').value=dirPickerPath+suffix;
1603
+ closeDirPicker();
1604
+ }
1605
+ function dirPickerNewFolder(){
1606
+ const name=prompt('New folder name:');
1607
+ if(!name||!name.trim())return;
1608
+ dirPickerInto(name.trim());
1609
+ }
1610
+
1611
+ // ── Init ──
1612
+ loadArtifacts();
1613
+ const _buildType = new URLSearchParams(window.location.search).get('build');
1614
+ if(_buildType) showIntake(_buildType);
1615
+ </script>
1616
+
1617
+ <!-- Directory Picker Modal -->
1618
+ <div class="hidden" id="dirPickerOverlay" onclick="closeDirPicker()" style="position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);backdrop-filter:blur(4px);z-index:3000;display:flex;align-items:center;justify-content:center">
1619
+ <div onclick="event.stopPropagation()" style="background:var(--bg-card);border:1px solid var(--border-dim);border-radius:16px;width:480px;max-height:70vh;display:flex;flex-direction:column;box-shadow:0 20px 60px rgba(0,0,0,0.5)">
1620
+ <div style="padding:16px 20px;border-bottom:1px solid var(--border-dim);display:flex;align-items:center;gap:10px">
1621
+ <span style="font-size:18px">&#128193;</span>
1622
+ <span style="font-weight:700;font-size:15px">Choose Directory</span>
1623
+ <span style="flex:1"></span>
1624
+ <button onclick="closeDirPicker()" style="background:none;border:none;color:var(--text-muted);cursor:pointer;font-size:18px">&times;</button>
1625
+ </div>
1626
+ <div id="dirPickerPath" style="padding:8px 20px;font-family:var(--font-mono);font-size:12px;color:var(--text-muted);background:var(--bg-input);border-bottom:1px solid var(--border-dim);word-break:break-all">~</div>
1627
+ <div id="dirPickerList" style="flex:1;overflow-y:auto;min-height:200px;max-height:400px"></div>
1628
+ <div style="padding:12px 20px;border-top:1px solid var(--border-dim);display:flex;gap:8px;justify-content:flex-end">
1629
+ <button onclick="dirPickerNewFolder()" style="padding:6px 14px;border-radius:6px;border:1px solid var(--border-dim);background:transparent;color:var(--text);cursor:pointer;font-size:12px">+ New Folder</button>
1630
+ <button onclick="closeDirPicker()" style="padding:6px 14px;border-radius:6px;border:1px solid var(--border-dim);background:transparent;color:var(--text-muted);cursor:pointer;font-size:12px">Cancel</button>
1631
+ <button onclick="dirPickerSelect()" style="padding:6px 18px;border-radius:6px;border:none;background:var(--accent);color:#000;cursor:pointer;font-size:12px;font-weight:600">Select</button>
1632
+ </div>
1633
+ </div>
1634
+ </div>
1635
+ <style>
1636
+ .dir-item{padding:10px 20px;cursor:pointer;font-size:13px;border-bottom:1px solid var(--border-dim);transition:background .15s}
1637
+ .dir-item:hover{background:var(--bg-input)}
1638
+ .dir-parent{color:var(--text-muted);font-style:italic}
1639
+ #dirPickerOverlay.hidden{display:none!important}
1640
+ </style>
1641
+ <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>
1642
+ </body>
1643
+ </html>