@cluesmith/codev 2.0.0-rc.9 → 2.0.1

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 (537) hide show
  1. package/bin/af.js +2 -2
  2. package/bin/consult.js +1 -1
  3. package/dashboard/dist/assets/index-4n9zpWLY.css +32 -0
  4. package/dashboard/dist/assets/index-b38SaXk5.js +136 -0
  5. package/dashboard/dist/assets/index-b38SaXk5.js.map +1 -0
  6. package/dashboard/dist/index.html +14 -0
  7. package/dist/agent-farm/cli.d.ts.map +1 -1
  8. package/dist/agent-farm/cli.js +179 -104
  9. package/dist/agent-farm/cli.js.map +1 -1
  10. package/dist/agent-farm/commands/architect.d.ts +3 -3
  11. package/dist/agent-farm/commands/architect.d.ts.map +1 -1
  12. package/dist/agent-farm/commands/architect.js +20 -147
  13. package/dist/agent-farm/commands/architect.js.map +1 -1
  14. package/dist/agent-farm/commands/attach.d.ts +13 -0
  15. package/dist/agent-farm/commands/attach.d.ts.map +1 -0
  16. package/dist/agent-farm/commands/attach.js +144 -0
  17. package/dist/agent-farm/commands/attach.js.map +1 -0
  18. package/dist/agent-farm/commands/cleanup.d.ts.map +1 -1
  19. package/dist/agent-farm/commands/cleanup.js +35 -19
  20. package/dist/agent-farm/commands/cleanup.js.map +1 -1
  21. package/dist/agent-farm/commands/consult.d.ts +3 -4
  22. package/dist/agent-farm/commands/consult.d.ts.map +1 -1
  23. package/dist/agent-farm/commands/consult.js +27 -37
  24. package/dist/agent-farm/commands/consult.js.map +1 -1
  25. package/dist/agent-farm/commands/index.d.ts +2 -2
  26. package/dist/agent-farm/commands/index.d.ts.map +1 -1
  27. package/dist/agent-farm/commands/index.js +2 -2
  28. package/dist/agent-farm/commands/index.js.map +1 -1
  29. package/dist/agent-farm/commands/open.d.ts +4 -2
  30. package/dist/agent-farm/commands/open.d.ts.map +1 -1
  31. package/dist/agent-farm/commands/open.js +33 -83
  32. package/dist/agent-farm/commands/open.js.map +1 -1
  33. package/dist/agent-farm/commands/send.d.ts +1 -1
  34. package/dist/agent-farm/commands/send.d.ts.map +1 -1
  35. package/dist/agent-farm/commands/send.js +70 -79
  36. package/dist/agent-farm/commands/send.js.map +1 -1
  37. package/dist/agent-farm/commands/shell.d.ts +15 -0
  38. package/dist/agent-farm/commands/shell.d.ts.map +1 -0
  39. package/dist/agent-farm/commands/shell.js +50 -0
  40. package/dist/agent-farm/commands/shell.js.map +1 -0
  41. package/dist/agent-farm/commands/spawn-roles.d.ts +80 -0
  42. package/dist/agent-farm/commands/spawn-roles.d.ts.map +1 -0
  43. package/dist/agent-farm/commands/spawn-roles.js +278 -0
  44. package/dist/agent-farm/commands/spawn-roles.js.map +1 -0
  45. package/dist/agent-farm/commands/spawn-worktree.d.ts +96 -0
  46. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -0
  47. package/dist/agent-farm/commands/spawn-worktree.js +305 -0
  48. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -0
  49. package/dist/agent-farm/commands/spawn.d.ts +5 -1
  50. package/dist/agent-farm/commands/spawn.d.ts.map +1 -1
  51. package/dist/agent-farm/commands/spawn.js +241 -561
  52. package/dist/agent-farm/commands/spawn.js.map +1 -1
  53. package/dist/agent-farm/commands/start.d.ts +10 -20
  54. package/dist/agent-farm/commands/start.d.ts.map +1 -1
  55. package/dist/agent-farm/commands/start.js +45 -449
  56. package/dist/agent-farm/commands/start.js.map +1 -1
  57. package/dist/agent-farm/commands/status.d.ts +2 -0
  58. package/dist/agent-farm/commands/status.d.ts.map +1 -1
  59. package/dist/agent-farm/commands/status.js +75 -24
  60. package/dist/agent-farm/commands/status.js.map +1 -1
  61. package/dist/agent-farm/commands/stop.d.ts +6 -0
  62. package/dist/agent-farm/commands/stop.d.ts.map +1 -1
  63. package/dist/agent-farm/commands/stop.js +49 -109
  64. package/dist/agent-farm/commands/stop.js.map +1 -1
  65. package/dist/agent-farm/commands/tower-cloud.d.ts +48 -0
  66. package/dist/agent-farm/commands/tower-cloud.d.ts.map +1 -0
  67. package/dist/agent-farm/commands/tower-cloud.js +293 -0
  68. package/dist/agent-farm/commands/tower-cloud.js.map +1 -0
  69. package/dist/agent-farm/commands/tower.d.ts +9 -0
  70. package/dist/agent-farm/commands/tower.d.ts.map +1 -1
  71. package/dist/agent-farm/commands/tower.js +59 -19
  72. package/dist/agent-farm/commands/tower.js.map +1 -1
  73. package/dist/agent-farm/db/index.d.ts +6 -2
  74. package/dist/agent-farm/db/index.d.ts.map +1 -1
  75. package/dist/agent-farm/db/index.js +301 -19
  76. package/dist/agent-farm/db/index.js.map +1 -1
  77. package/dist/agent-farm/db/migrate.d.ts +0 -4
  78. package/dist/agent-farm/db/migrate.d.ts.map +1 -1
  79. package/dist/agent-farm/db/migrate.js +6 -55
  80. package/dist/agent-farm/db/migrate.js.map +1 -1
  81. package/dist/agent-farm/db/schema.d.ts +3 -3
  82. package/dist/agent-farm/db/schema.d.ts.map +1 -1
  83. package/dist/agent-farm/db/schema.js +25 -19
  84. package/dist/agent-farm/db/schema.js.map +1 -1
  85. package/dist/agent-farm/db/types.d.ts +3 -13
  86. package/dist/agent-farm/db/types.d.ts.map +1 -1
  87. package/dist/agent-farm/db/types.js +3 -11
  88. package/dist/agent-farm/db/types.js.map +1 -1
  89. package/dist/agent-farm/hq-connector.d.ts +2 -6
  90. package/dist/agent-farm/hq-connector.d.ts.map +1 -1
  91. package/dist/agent-farm/hq-connector.js +2 -17
  92. package/dist/agent-farm/hq-connector.js.map +1 -1
  93. package/dist/agent-farm/lib/cloud-config.d.ts +59 -0
  94. package/dist/agent-farm/lib/cloud-config.d.ts.map +1 -0
  95. package/dist/agent-farm/lib/cloud-config.js +143 -0
  96. package/dist/agent-farm/lib/cloud-config.js.map +1 -0
  97. package/dist/agent-farm/lib/device-name.d.ts +25 -0
  98. package/dist/agent-farm/lib/device-name.d.ts.map +1 -0
  99. package/dist/agent-farm/lib/device-name.js +46 -0
  100. package/dist/agent-farm/lib/device-name.js.map +1 -0
  101. package/dist/agent-farm/lib/nonce-store.d.ts +28 -0
  102. package/dist/agent-farm/lib/nonce-store.d.ts.map +1 -0
  103. package/dist/agent-farm/lib/nonce-store.js +60 -0
  104. package/dist/agent-farm/lib/nonce-store.js.map +1 -0
  105. package/dist/agent-farm/lib/token-exchange.d.ts +18 -0
  106. package/dist/agent-farm/lib/token-exchange.d.ts.map +1 -0
  107. package/dist/agent-farm/lib/token-exchange.js +48 -0
  108. package/dist/agent-farm/lib/token-exchange.js.map +1 -0
  109. package/dist/agent-farm/lib/tower-client.d.ts +163 -0
  110. package/dist/agent-farm/lib/tower-client.d.ts.map +1 -0
  111. package/dist/agent-farm/lib/tower-client.js +233 -0
  112. package/dist/agent-farm/lib/tower-client.js.map +1 -0
  113. package/dist/agent-farm/lib/tunnel-client.d.ts +117 -0
  114. package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -0
  115. package/dist/agent-farm/lib/tunnel-client.js +504 -0
  116. package/dist/agent-farm/lib/tunnel-client.js.map +1 -0
  117. package/dist/agent-farm/servers/tower-instances.d.ts +82 -0
  118. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -0
  119. package/dist/agent-farm/servers/tower-instances.js +454 -0
  120. package/dist/agent-farm/servers/tower-instances.js.map +1 -0
  121. package/dist/agent-farm/servers/tower-routes.d.ts +34 -0
  122. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -0
  123. package/dist/agent-farm/servers/tower-routes.js +1445 -0
  124. package/dist/agent-farm/servers/tower-routes.js.map +1 -0
  125. package/dist/agent-farm/servers/tower-server.d.ts +5 -2
  126. package/dist/agent-farm/servers/tower-server.d.ts.map +1 -1
  127. package/dist/agent-farm/servers/tower-server.js +157 -475
  128. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  129. package/dist/agent-farm/servers/tower-terminals.d.ts +119 -0
  130. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -0
  131. package/dist/agent-farm/servers/tower-terminals.js +629 -0
  132. package/dist/agent-farm/servers/tower-terminals.js.map +1 -0
  133. package/dist/agent-farm/servers/tower-tunnel.d.ts +34 -0
  134. package/dist/agent-farm/servers/tower-tunnel.d.ts.map +1 -0
  135. package/dist/agent-farm/servers/tower-tunnel.js +480 -0
  136. package/dist/agent-farm/servers/tower-tunnel.js.map +1 -0
  137. package/dist/agent-farm/servers/tower-types.d.ts +86 -0
  138. package/dist/agent-farm/servers/tower-types.d.ts.map +1 -0
  139. package/dist/agent-farm/servers/tower-types.js +6 -0
  140. package/dist/agent-farm/servers/tower-types.js.map +1 -0
  141. package/dist/agent-farm/servers/tower-utils.d.ts +58 -0
  142. package/dist/agent-farm/servers/tower-utils.d.ts.map +1 -0
  143. package/dist/agent-farm/servers/tower-utils.js +182 -0
  144. package/dist/agent-farm/servers/tower-utils.js.map +1 -0
  145. package/dist/agent-farm/servers/tower-websocket.d.ts +25 -0
  146. package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -0
  147. package/dist/agent-farm/servers/tower-websocket.js +171 -0
  148. package/dist/agent-farm/servers/tower-websocket.js.map +1 -0
  149. package/dist/agent-farm/state.d.ts +6 -2
  150. package/dist/agent-farm/state.d.ts.map +1 -1
  151. package/dist/agent-farm/state.js +34 -25
  152. package/dist/agent-farm/state.js.map +1 -1
  153. package/dist/agent-farm/types.d.ts +49 -26
  154. package/dist/agent-farm/types.d.ts.map +1 -1
  155. package/dist/agent-farm/utils/config.d.ts +0 -5
  156. package/dist/agent-farm/utils/config.d.ts.map +1 -1
  157. package/dist/agent-farm/utils/config.js +12 -44
  158. package/dist/agent-farm/utils/config.js.map +1 -1
  159. package/dist/agent-farm/utils/deps.d.ts.map +1 -1
  160. package/dist/agent-farm/utils/deps.js +0 -32
  161. package/dist/agent-farm/utils/deps.js.map +1 -1
  162. package/dist/agent-farm/utils/file-tabs.d.ts +27 -0
  163. package/dist/agent-farm/utils/file-tabs.d.ts.map +1 -0
  164. package/dist/agent-farm/utils/file-tabs.js +46 -0
  165. package/dist/agent-farm/utils/file-tabs.js.map +1 -0
  166. package/dist/agent-farm/utils/gate-status.d.ts +16 -0
  167. package/dist/agent-farm/utils/gate-status.d.ts.map +1 -0
  168. package/dist/agent-farm/utils/gate-status.js +79 -0
  169. package/dist/agent-farm/utils/gate-status.js.map +1 -0
  170. package/dist/agent-farm/utils/gate-watcher.d.ts +38 -0
  171. package/dist/agent-farm/utils/gate-watcher.d.ts.map +1 -0
  172. package/dist/agent-farm/utils/gate-watcher.js +122 -0
  173. package/dist/agent-farm/utils/gate-watcher.js.map +1 -0
  174. package/dist/agent-farm/utils/index.d.ts +0 -1
  175. package/dist/agent-farm/utils/index.d.ts.map +1 -1
  176. package/dist/agent-farm/utils/index.js +0 -1
  177. package/dist/agent-farm/utils/index.js.map +1 -1
  178. package/dist/agent-farm/utils/notifications.d.ts +30 -0
  179. package/dist/agent-farm/utils/notifications.d.ts.map +1 -0
  180. package/dist/agent-farm/utils/notifications.js +121 -0
  181. package/dist/agent-farm/utils/notifications.js.map +1 -0
  182. package/dist/agent-farm/utils/server-utils.d.ts +5 -5
  183. package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
  184. package/dist/agent-farm/utils/server-utils.js +5 -16
  185. package/dist/agent-farm/utils/server-utils.js.map +1 -1
  186. package/dist/agent-farm/utils/session.d.ts +32 -0
  187. package/dist/agent-farm/utils/session.d.ts.map +1 -0
  188. package/dist/agent-farm/utils/session.js +57 -0
  189. package/dist/agent-farm/utils/session.js.map +1 -0
  190. package/dist/agent-farm/utils/shell.d.ts +9 -22
  191. package/dist/agent-farm/utils/shell.d.ts.map +1 -1
  192. package/dist/agent-farm/utils/shell.js +34 -34
  193. package/dist/agent-farm/utils/shell.js.map +1 -1
  194. package/dist/cli.d.ts.map +1 -1
  195. package/dist/cli.js +6 -37
  196. package/dist/cli.js.map +1 -1
  197. package/dist/commands/adopt.d.ts.map +1 -1
  198. package/dist/commands/adopt.js +33 -4
  199. package/dist/commands/adopt.js.map +1 -1
  200. package/dist/commands/consult/index.d.ts +13 -2
  201. package/dist/commands/consult/index.d.ts.map +1 -1
  202. package/dist/commands/consult/index.js +244 -29
  203. package/dist/commands/consult/index.js.map +1 -1
  204. package/dist/commands/doctor.d.ts.map +1 -1
  205. package/dist/commands/doctor.js +96 -79
  206. package/dist/commands/doctor.js.map +1 -1
  207. package/dist/commands/init.d.ts.map +1 -1
  208. package/dist/commands/init.js +36 -3
  209. package/dist/commands/init.js.map +1 -1
  210. package/dist/commands/porch/build-counter.d.ts +5 -0
  211. package/dist/commands/porch/build-counter.d.ts.map +1 -0
  212. package/dist/commands/porch/build-counter.js +5 -0
  213. package/dist/commands/porch/build-counter.js.map +1 -0
  214. package/dist/commands/porch/checks.d.ts +3 -2
  215. package/dist/commands/porch/checks.d.ts.map +1 -1
  216. package/dist/commands/porch/checks.js +8 -2
  217. package/dist/commands/porch/checks.js.map +1 -1
  218. package/dist/commands/porch/index.d.ts +4 -0
  219. package/dist/commands/porch/index.d.ts.map +1 -1
  220. package/dist/commands/porch/index.js +109 -70
  221. package/dist/commands/porch/index.js.map +1 -1
  222. package/dist/commands/porch/next.d.ts +22 -0
  223. package/dist/commands/porch/next.d.ts.map +1 -0
  224. package/dist/commands/porch/next.js +571 -0
  225. package/dist/commands/porch/next.js.map +1 -0
  226. package/dist/commands/porch/plan.d.ts +11 -1
  227. package/dist/commands/porch/plan.d.ts.map +1 -1
  228. package/dist/commands/porch/plan.js +33 -5
  229. package/dist/commands/porch/plan.js.map +1 -1
  230. package/dist/commands/porch/prompts.d.ts.map +1 -1
  231. package/dist/commands/porch/prompts.js +44 -26
  232. package/dist/commands/porch/prompts.js.map +1 -1
  233. package/dist/commands/porch/protocol.d.ts +6 -4
  234. package/dist/commands/porch/protocol.d.ts.map +1 -1
  235. package/dist/commands/porch/protocol.js +59 -15
  236. package/dist/commands/porch/protocol.js.map +1 -1
  237. package/dist/commands/porch/state.d.ts +29 -2
  238. package/dist/commands/porch/state.d.ts.map +1 -1
  239. package/dist/commands/porch/state.js +71 -3
  240. package/dist/commands/porch/state.js.map +1 -1
  241. package/dist/commands/porch/types.d.ts +45 -2
  242. package/dist/commands/porch/types.d.ts.map +1 -1
  243. package/dist/commands/porch/verdict.d.ts +31 -0
  244. package/dist/commands/porch/verdict.d.ts.map +1 -0
  245. package/dist/commands/porch/verdict.js +59 -0
  246. package/dist/commands/porch/verdict.js.map +1 -0
  247. package/dist/commands/update.d.ts.map +1 -1
  248. package/dist/commands/update.js +18 -6
  249. package/dist/commands/update.js.map +1 -1
  250. package/dist/lib/scaffold.d.ts +13 -0
  251. package/dist/lib/scaffold.d.ts.map +1 -1
  252. package/dist/lib/scaffold.js +36 -0
  253. package/dist/lib/scaffold.js.map +1 -1
  254. package/dist/terminal/index.d.ts +8 -0
  255. package/dist/terminal/index.d.ts.map +1 -0
  256. package/dist/terminal/index.js +5 -0
  257. package/dist/terminal/index.js.map +1 -0
  258. package/dist/terminal/pty-manager.d.ts +69 -0
  259. package/dist/terminal/pty-manager.d.ts.map +1 -0
  260. package/dist/terminal/pty-manager.js +377 -0
  261. package/dist/terminal/pty-manager.js.map +1 -0
  262. package/dist/terminal/pty-session.d.ts +104 -0
  263. package/dist/terminal/pty-session.d.ts.map +1 -0
  264. package/dist/terminal/pty-session.js +327 -0
  265. package/dist/terminal/pty-session.js.map +1 -0
  266. package/dist/terminal/ring-buffer.d.ts +34 -0
  267. package/dist/terminal/ring-buffer.d.ts.map +1 -0
  268. package/dist/terminal/ring-buffer.js +94 -0
  269. package/dist/terminal/ring-buffer.js.map +1 -0
  270. package/dist/terminal/session-manager.d.ts +115 -0
  271. package/dist/terminal/session-manager.d.ts.map +1 -0
  272. package/dist/terminal/session-manager.js +582 -0
  273. package/dist/terminal/session-manager.js.map +1 -0
  274. package/dist/terminal/shellper-client.d.ts +66 -0
  275. package/dist/terminal/shellper-client.d.ts.map +1 -0
  276. package/dist/terminal/shellper-client.js +234 -0
  277. package/dist/terminal/shellper-client.js.map +1 -0
  278. package/dist/terminal/shellper-main.d.ts +19 -0
  279. package/dist/terminal/shellper-main.d.ts.map +1 -0
  280. package/dist/terminal/shellper-main.js +153 -0
  281. package/dist/terminal/shellper-main.js.map +1 -0
  282. package/dist/terminal/shellper-process.d.ts +75 -0
  283. package/dist/terminal/shellper-process.d.ts.map +1 -0
  284. package/dist/terminal/shellper-process.js +279 -0
  285. package/dist/terminal/shellper-process.js.map +1 -0
  286. package/dist/terminal/shellper-protocol.d.ts +115 -0
  287. package/dist/terminal/shellper-protocol.d.ts.map +1 -0
  288. package/dist/terminal/shellper-protocol.js +214 -0
  289. package/dist/terminal/shellper-protocol.js.map +1 -0
  290. package/dist/terminal/shellper-replay-buffer.d.ts +38 -0
  291. package/dist/terminal/shellper-replay-buffer.d.ts.map +1 -0
  292. package/dist/terminal/shellper-replay-buffer.js +94 -0
  293. package/dist/terminal/shellper-replay-buffer.js.map +1 -0
  294. package/dist/terminal/ws-protocol.d.ts +27 -0
  295. package/dist/terminal/ws-protocol.d.ts.map +1 -0
  296. package/dist/terminal/ws-protocol.js +44 -0
  297. package/dist/terminal/ws-protocol.js.map +1 -0
  298. package/package.json +17 -5
  299. package/skeleton/.claude/skills/af/SKILL.md +89 -0
  300. package/skeleton/.claude/skills/codev/SKILL.md +41 -0
  301. package/skeleton/.claude/skills/consult/SKILL.md +81 -0
  302. package/skeleton/.claude/skills/generate-image/SKILL.md +56 -0
  303. package/skeleton/DEPENDENCIES.md +4 -62
  304. package/skeleton/builders.md +1 -1
  305. package/skeleton/consult-types/impl-review.md +18 -9
  306. package/skeleton/consult-types/integration-review.md +1 -1
  307. package/skeleton/consult-types/plan-review.md +1 -1
  308. package/skeleton/consult-types/pr-ready.md +1 -1
  309. package/skeleton/consult-types/spec-review.md +1 -1
  310. package/skeleton/porch/prompts/defend.md +1 -1
  311. package/skeleton/porch/prompts/evaluate.md +2 -2
  312. package/skeleton/porch/prompts/implement.md +1 -1
  313. package/skeleton/porch/prompts/plan.md +1 -1
  314. package/skeleton/porch/prompts/review.md +4 -4
  315. package/skeleton/porch/prompts/specify.md +1 -1
  316. package/skeleton/porch/prompts/understand.md +2 -2
  317. package/skeleton/protocol-schema.json +282 -0
  318. package/skeleton/protocols/bugfix/builder-prompt.md +60 -0
  319. package/skeleton/protocols/bugfix/prompts/fix.md +77 -0
  320. package/skeleton/protocols/bugfix/prompts/investigate.md +77 -0
  321. package/skeleton/protocols/bugfix/prompts/pr.md +84 -0
  322. package/skeleton/protocols/bugfix/protocol.json +20 -33
  323. package/skeleton/protocols/experiment/builder-prompt.md +52 -0
  324. package/skeleton/protocols/experiment/protocol.json +101 -0
  325. package/skeleton/protocols/experiment/protocol.md +3 -3
  326. package/skeleton/protocols/experiment/templates/notes.md +1 -1
  327. package/skeleton/protocols/maintain/builder-prompt.md +46 -0
  328. package/skeleton/protocols/maintain/prompts/audit.md +111 -0
  329. package/skeleton/protocols/maintain/prompts/clean.md +91 -0
  330. package/skeleton/protocols/maintain/prompts/sync.md +113 -0
  331. package/skeleton/protocols/maintain/prompts/verify.md +110 -0
  332. package/skeleton/protocols/maintain/protocol.json +141 -0
  333. package/skeleton/protocols/maintain/protocol.md +17 -11
  334. package/skeleton/protocols/protocol-schema.json +54 -1
  335. package/skeleton/protocols/spir/builder-prompt.md +66 -0
  336. package/skeleton/protocols/spir/prompts/implement.md +208 -0
  337. package/skeleton/protocols/{spider → spir}/prompts/plan.md +6 -70
  338. package/skeleton/protocols/{spider → spir}/prompts/review.md +20 -39
  339. package/skeleton/protocols/{spider → spir}/prompts/specify.md +24 -59
  340. package/skeleton/protocols/{spider → spir}/protocol.json +30 -10
  341. package/skeleton/protocols/{spider → spir}/protocol.md +35 -21
  342. package/skeleton/protocols/spir/templates/review.md +89 -0
  343. package/skeleton/protocols/tick/builder-prompt.md +56 -0
  344. package/skeleton/protocols/tick/protocol.json +7 -2
  345. package/skeleton/protocols/tick/protocol.md +18 -18
  346. package/skeleton/protocols/tick/templates/review.md +1 -1
  347. package/skeleton/resources/commands/agent-farm.md +63 -46
  348. package/skeleton/resources/commands/codev.md +0 -2
  349. package/skeleton/resources/commands/overview.md +7 -17
  350. package/skeleton/resources/workflow-reference.md +4 -4
  351. package/skeleton/roles/architect.md +151 -306
  352. package/skeleton/roles/builder.md +115 -332
  353. package/skeleton/roles/consultant.md +6 -6
  354. package/skeleton/templates/AGENTS.md +2 -2
  355. package/skeleton/templates/CLAUDE.md +2 -2
  356. package/skeleton/templates/cheatsheet.md +7 -5
  357. package/skeleton/templates/projectlist.md +1 -1
  358. package/templates/dashboard/index.html +17 -16
  359. package/templates/dashboard/js/dialogs.js +7 -7
  360. package/templates/dashboard/js/files.js +2 -2
  361. package/templates/dashboard/js/main.js +4 -4
  362. package/templates/dashboard/js/projects.js +3 -3
  363. package/templates/dashboard/js/tabs.js +1 -1
  364. package/templates/dashboard/js/utils.js +22 -1
  365. package/templates/open.html +26 -0
  366. package/templates/tower.html +731 -91
  367. package/dist/agent-farm/commands/kickoff.d.ts +0 -20
  368. package/dist/agent-farm/commands/kickoff.d.ts.map +0 -1
  369. package/dist/agent-farm/commands/kickoff.js +0 -273
  370. package/dist/agent-farm/commands/kickoff.js.map +0 -1
  371. package/dist/agent-farm/commands/rename.d.ts +0 -13
  372. package/dist/agent-farm/commands/rename.d.ts.map +0 -1
  373. package/dist/agent-farm/commands/rename.js +0 -33
  374. package/dist/agent-farm/commands/rename.js.map +0 -1
  375. package/dist/agent-farm/commands/tutorial.d.ts +0 -10
  376. package/dist/agent-farm/commands/tutorial.d.ts.map +0 -1
  377. package/dist/agent-farm/commands/tutorial.js +0 -49
  378. package/dist/agent-farm/commands/tutorial.js.map +0 -1
  379. package/dist/agent-farm/commands/util.d.ts +0 -15
  380. package/dist/agent-farm/commands/util.d.ts.map +0 -1
  381. package/dist/agent-farm/commands/util.js +0 -108
  382. package/dist/agent-farm/commands/util.js.map +0 -1
  383. package/dist/agent-farm/servers/dashboard-server.d.ts +0 -7
  384. package/dist/agent-farm/servers/dashboard-server.d.ts.map +0 -1
  385. package/dist/agent-farm/servers/dashboard-server.js +0 -1858
  386. package/dist/agent-farm/servers/dashboard-server.js.map +0 -1
  387. package/dist/agent-farm/servers/open-server.d.ts +0 -7
  388. package/dist/agent-farm/servers/open-server.d.ts.map +0 -1
  389. package/dist/agent-farm/servers/open-server.js +0 -315
  390. package/dist/agent-farm/servers/open-server.js.map +0 -1
  391. package/dist/agent-farm/tutorial/index.d.ts +0 -8
  392. package/dist/agent-farm/tutorial/index.d.ts.map +0 -1
  393. package/dist/agent-farm/tutorial/index.js +0 -8
  394. package/dist/agent-farm/tutorial/index.js.map +0 -1
  395. package/dist/agent-farm/tutorial/prompts.d.ts +0 -57
  396. package/dist/agent-farm/tutorial/prompts.d.ts.map +0 -1
  397. package/dist/agent-farm/tutorial/prompts.js +0 -147
  398. package/dist/agent-farm/tutorial/prompts.js.map +0 -1
  399. package/dist/agent-farm/tutorial/runner.d.ts +0 -52
  400. package/dist/agent-farm/tutorial/runner.d.ts.map +0 -1
  401. package/dist/agent-farm/tutorial/runner.js +0 -204
  402. package/dist/agent-farm/tutorial/runner.js.map +0 -1
  403. package/dist/agent-farm/tutorial/state.d.ts +0 -26
  404. package/dist/agent-farm/tutorial/state.d.ts.map +0 -1
  405. package/dist/agent-farm/tutorial/state.js +0 -89
  406. package/dist/agent-farm/tutorial/state.js.map +0 -1
  407. package/dist/agent-farm/tutorial/steps/first-spec.d.ts +0 -7
  408. package/dist/agent-farm/tutorial/steps/first-spec.d.ts.map +0 -1
  409. package/dist/agent-farm/tutorial/steps/first-spec.js +0 -136
  410. package/dist/agent-farm/tutorial/steps/first-spec.js.map +0 -1
  411. package/dist/agent-farm/tutorial/steps/implementation.d.ts +0 -7
  412. package/dist/agent-farm/tutorial/steps/implementation.d.ts.map +0 -1
  413. package/dist/agent-farm/tutorial/steps/implementation.js +0 -76
  414. package/dist/agent-farm/tutorial/steps/implementation.js.map +0 -1
  415. package/dist/agent-farm/tutorial/steps/index.d.ts +0 -10
  416. package/dist/agent-farm/tutorial/steps/index.d.ts.map +0 -1
  417. package/dist/agent-farm/tutorial/steps/index.js +0 -10
  418. package/dist/agent-farm/tutorial/steps/index.js.map +0 -1
  419. package/dist/agent-farm/tutorial/steps/planning.d.ts +0 -7
  420. package/dist/agent-farm/tutorial/steps/planning.d.ts.map +0 -1
  421. package/dist/agent-farm/tutorial/steps/planning.js +0 -143
  422. package/dist/agent-farm/tutorial/steps/planning.js.map +0 -1
  423. package/dist/agent-farm/tutorial/steps/review.d.ts +0 -7
  424. package/dist/agent-farm/tutorial/steps/review.d.ts.map +0 -1
  425. package/dist/agent-farm/tutorial/steps/review.js +0 -78
  426. package/dist/agent-farm/tutorial/steps/review.js.map +0 -1
  427. package/dist/agent-farm/tutorial/steps/setup.d.ts +0 -7
  428. package/dist/agent-farm/tutorial/steps/setup.d.ts.map +0 -1
  429. package/dist/agent-farm/tutorial/steps/setup.js +0 -126
  430. package/dist/agent-farm/tutorial/steps/setup.js.map +0 -1
  431. package/dist/agent-farm/tutorial/steps/welcome.d.ts +0 -7
  432. package/dist/agent-farm/tutorial/steps/welcome.d.ts.map +0 -1
  433. package/dist/agent-farm/tutorial/steps/welcome.js +0 -50
  434. package/dist/agent-farm/tutorial/steps/welcome.js.map +0 -1
  435. package/dist/agent-farm/utils/orphan-handler.d.ts +0 -27
  436. package/dist/agent-farm/utils/orphan-handler.d.ts.map +0 -1
  437. package/dist/agent-farm/utils/orphan-handler.js +0 -149
  438. package/dist/agent-farm/utils/orphan-handler.js.map +0 -1
  439. package/dist/agent-farm/utils/port-registry.d.ts +0 -58
  440. package/dist/agent-farm/utils/port-registry.d.ts.map +0 -1
  441. package/dist/agent-farm/utils/port-registry.js +0 -166
  442. package/dist/agent-farm/utils/port-registry.js.map +0 -1
  443. package/dist/agent-farm/utils/terminal-ports.d.ts +0 -18
  444. package/dist/agent-farm/utils/terminal-ports.d.ts.map +0 -1
  445. package/dist/agent-farm/utils/terminal-ports.js +0 -35
  446. package/dist/agent-farm/utils/terminal-ports.js.map +0 -1
  447. package/dist/commands/pcheck/cache.d.ts +0 -48
  448. package/dist/commands/pcheck/cache.d.ts.map +0 -1
  449. package/dist/commands/pcheck/cache.js +0 -170
  450. package/dist/commands/pcheck/cache.js.map +0 -1
  451. package/dist/commands/pcheck/evaluator.d.ts +0 -15
  452. package/dist/commands/pcheck/evaluator.d.ts.map +0 -1
  453. package/dist/commands/pcheck/evaluator.js +0 -246
  454. package/dist/commands/pcheck/evaluator.js.map +0 -1
  455. package/dist/commands/pcheck/index.d.ts +0 -12
  456. package/dist/commands/pcheck/index.d.ts.map +0 -1
  457. package/dist/commands/pcheck/index.js +0 -249
  458. package/dist/commands/pcheck/index.js.map +0 -1
  459. package/dist/commands/pcheck/parser.d.ts +0 -39
  460. package/dist/commands/pcheck/parser.d.ts.map +0 -1
  461. package/dist/commands/pcheck/parser.js +0 -155
  462. package/dist/commands/pcheck/parser.js.map +0 -1
  463. package/dist/commands/pcheck/types.d.ts +0 -82
  464. package/dist/commands/pcheck/types.d.ts.map +0 -1
  465. package/dist/commands/pcheck/types.js +0 -5
  466. package/dist/commands/pcheck/types.js.map +0 -1
  467. package/dist/commands/porch/claude.d.ts +0 -29
  468. package/dist/commands/porch/claude.d.ts.map +0 -1
  469. package/dist/commands/porch/claude.js +0 -79
  470. package/dist/commands/porch/claude.js.map +0 -1
  471. package/dist/commands/porch/consultation.d.ts +0 -56
  472. package/dist/commands/porch/consultation.d.ts.map +0 -1
  473. package/dist/commands/porch/consultation.js +0 -330
  474. package/dist/commands/porch/consultation.js.map +0 -1
  475. package/dist/commands/porch/notifications.d.ts +0 -99
  476. package/dist/commands/porch/notifications.d.ts.map +0 -1
  477. package/dist/commands/porch/notifications.js +0 -223
  478. package/dist/commands/porch/notifications.js.map +0 -1
  479. package/dist/commands/porch/plan-parser.d.ts +0 -38
  480. package/dist/commands/porch/plan-parser.d.ts.map +0 -1
  481. package/dist/commands/porch/plan-parser.js +0 -166
  482. package/dist/commands/porch/plan-parser.js.map +0 -1
  483. package/dist/commands/porch/protocol-loader.d.ts +0 -46
  484. package/dist/commands/porch/protocol-loader.d.ts.map +0 -1
  485. package/dist/commands/porch/protocol-loader.js +0 -262
  486. package/dist/commands/porch/protocol-loader.js.map +0 -1
  487. package/dist/commands/porch/repl.d.ts +0 -33
  488. package/dist/commands/porch/repl.d.ts.map +0 -1
  489. package/dist/commands/porch/repl.js +0 -206
  490. package/dist/commands/porch/repl.js.map +0 -1
  491. package/dist/commands/porch/run.d.ts +0 -15
  492. package/dist/commands/porch/run.d.ts.map +0 -1
  493. package/dist/commands/porch/run.js +0 -551
  494. package/dist/commands/porch/run.js.map +0 -1
  495. package/dist/commands/porch/signal-parser.d.ts +0 -102
  496. package/dist/commands/porch/signal-parser.d.ts.map +0 -1
  497. package/dist/commands/porch/signal-parser.js +0 -199
  498. package/dist/commands/porch/signal-parser.js.map +0 -1
  499. package/dist/commands/porch/signals.d.ts +0 -35
  500. package/dist/commands/porch/signals.d.ts.map +0 -1
  501. package/dist/commands/porch/signals.js +0 -76
  502. package/dist/commands/porch/signals.js.map +0 -1
  503. package/dist/commands/porch2/checks.d.ts +0 -29
  504. package/dist/commands/porch2/checks.d.ts.map +0 -1
  505. package/dist/commands/porch2/checks.js +0 -141
  506. package/dist/commands/porch2/checks.js.map +0 -1
  507. package/dist/commands/porch2/index.d.ts +0 -38
  508. package/dist/commands/porch2/index.d.ts.map +0 -1
  509. package/dist/commands/porch2/index.js +0 -483
  510. package/dist/commands/porch2/index.js.map +0 -1
  511. package/dist/commands/porch2/plan.d.ts +0 -70
  512. package/dist/commands/porch2/plan.d.ts.map +0 -1
  513. package/dist/commands/porch2/plan.js +0 -227
  514. package/dist/commands/porch2/plan.js.map +0 -1
  515. package/dist/commands/porch2/protocol.d.ts +0 -37
  516. package/dist/commands/porch2/protocol.d.ts.map +0 -1
  517. package/dist/commands/porch2/protocol.js +0 -183
  518. package/dist/commands/porch2/protocol.js.map +0 -1
  519. package/dist/commands/porch2/state.d.ts +0 -35
  520. package/dist/commands/porch2/state.d.ts.map +0 -1
  521. package/dist/commands/porch2/state.js +0 -124
  522. package/dist/commands/porch2/state.js.map +0 -1
  523. package/dist/commands/porch2/types.d.ts +0 -79
  524. package/dist/commands/porch2/types.d.ts.map +0 -1
  525. package/dist/commands/porch2/types.js +0 -8
  526. package/dist/commands/porch2/types.js.map +0 -1
  527. package/dist/commands/tower.d.ts +0 -16
  528. package/dist/commands/tower.d.ts.map +0 -1
  529. package/dist/commands/tower.js +0 -21
  530. package/dist/commands/tower.js.map +0 -1
  531. package/skeleton/config.json +0 -7
  532. package/skeleton/protocols/spider/prompts/defend.md +0 -215
  533. package/skeleton/protocols/spider/prompts/evaluate.md +0 -241
  534. package/skeleton/protocols/spider/prompts/implement.md +0 -149
  535. package/skeleton/protocols/spider/templates/review.md +0 -207
  536. /package/skeleton/protocols/{spider → spir}/templates/plan.md +0 -0
  537. /package/skeleton/protocols/{spider → spir}/templates/spec.md +0 -0
@@ -2,8 +2,12 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>AF Control Tower</title>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
6
+ <meta name="apple-mobile-web-app-capable" content="yes">
7
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
8
+ <meta name="theme-color" content="#252525">
9
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🗼</text></svg>">
10
+ <title>Agent Farm Tower</title>
7
11
  <style>
8
12
  * {
9
13
  box-sizing: border-box;
@@ -199,6 +203,36 @@
199
203
  overflow: hidden;
200
204
  }
201
205
 
206
+ .instance.gate-pending {
207
+ border-color: #f59e0b;
208
+ animation: gate-pulse 2s infinite;
209
+ }
210
+
211
+ @keyframes gate-pulse {
212
+ 0%, 100% { border-color: #f59e0b; }
213
+ 50% { border-color: #d97706; }
214
+ }
215
+
216
+ .gate-indicator {
217
+ padding: 8px 20px;
218
+ background: rgba(245, 158, 11, 0.15);
219
+ border-bottom: 1px solid var(--border);
220
+ font-size: 14px;
221
+ color: #f59e0b;
222
+ display: flex;
223
+ align-items: center;
224
+ gap: 8px;
225
+ }
226
+
227
+ .gate-indicator .gate-name {
228
+ font-weight: 600;
229
+ }
230
+
231
+ .gate-indicator .gate-builder {
232
+ color: var(--text-muted);
233
+ font-size: 12px;
234
+ }
235
+
202
236
  .instance-header {
203
237
  display: flex;
204
238
  justify-content: space-between;
@@ -388,6 +422,11 @@
388
422
  gap: 24px;
389
423
  }
390
424
 
425
+ .new-shell-row {
426
+ margin-top: 8px;
427
+ padding-top: 8px;
428
+ }
429
+
391
430
  /* Recents section */
392
431
  .recents-section {
393
432
  margin-top: 32px;
@@ -603,6 +642,68 @@
603
642
  }
604
643
  }
605
644
 
645
+ /* Cloud status */
646
+ .cloud-status {
647
+ display: inline-flex;
648
+ align-items: center;
649
+ gap: 8px;
650
+ font-size: 14px;
651
+ color: var(--text-secondary);
652
+ }
653
+
654
+ .cloud-dot {
655
+ width: 8px;
656
+ height: 8px;
657
+ border-radius: 50%;
658
+ display: inline-block;
659
+ }
660
+
661
+ .cloud-dot--green { background: var(--status-running); }
662
+ .cloud-dot--yellow { background: #eab308; }
663
+ .cloud-dot--red { background: #ef4444; }
664
+ .cloud-dot--gray { background: var(--text-muted); }
665
+
666
+ .cloud-link {
667
+ color: var(--accent);
668
+ text-decoration: none;
669
+ font-size: 13px;
670
+ }
671
+
672
+ .cloud-link:hover {
673
+ text-decoration: underline;
674
+ }
675
+
676
+ .cloud-uptime {
677
+ color: var(--text-muted);
678
+ font-size: 12px;
679
+ }
680
+
681
+ .cloud-btn {
682
+ padding: 4px 10px;
683
+ border-radius: 4px;
684
+ border: 1px solid var(--border);
685
+ background: var(--bg-tertiary);
686
+ color: var(--text-secondary);
687
+ cursor: pointer;
688
+ font-size: 12px;
689
+ }
690
+
691
+ .cloud-btn:hover {
692
+ background: #333;
693
+ }
694
+
695
+ .cloud-btn:disabled {
696
+ opacity: 0.5;
697
+ cursor: default;
698
+ }
699
+
700
+ .connect-dialog-error {
701
+ color: #ef4444;
702
+ font-size: 13px;
703
+ margin-top: 8px;
704
+ display: none;
705
+ }
706
+
606
707
  /* Reduced motion */
607
708
  @media (prefers-reduced-motion: reduce) {
608
709
  .status-dot.running,
@@ -611,6 +712,183 @@
611
712
  }
612
713
  }
613
714
 
715
+ /* Mobile optimizations */
716
+ @media (max-width: 600px) {
717
+ body {
718
+ padding-bottom: env(safe-area-inset-bottom, 0);
719
+ }
720
+
721
+ .header {
722
+ padding: 16px;
723
+ padding-top: calc(16px + env(safe-area-inset-top, 0));
724
+ flex-wrap: wrap;
725
+ gap: 12px;
726
+ }
727
+
728
+ .header h1 {
729
+ font-size: 18px;
730
+ width: 100%;
731
+ }
732
+
733
+ .header h1 .emoji {
734
+ font-size: 22px;
735
+ }
736
+
737
+ .header-actions {
738
+ width: 100%;
739
+ justify-content: flex-end;
740
+ }
741
+
742
+ /* 1. Hide Share button on mobile (tunnel is for reaching phone, not FROM phone) */
743
+ #share-btn {
744
+ display: none !important;
745
+ }
746
+
747
+ /* 7. Reduce section spacing */
748
+ .main {
749
+ padding: 12px;
750
+ padding-left: calc(12px + env(safe-area-inset-left, 0));
751
+ padding-right: calc(12px + env(safe-area-inset-right, 0));
752
+ }
753
+
754
+ .section-header {
755
+ margin-bottom: 8px;
756
+ }
757
+
758
+ .instances {
759
+ gap: 10px;
760
+ }
761
+
762
+ .btn {
763
+ min-height: 44px;
764
+ min-width: 44px;
765
+ padding: 12px 16px;
766
+ }
767
+
768
+ .btn-small {
769
+ min-height: 44px;
770
+ padding: 10px 14px;
771
+ }
772
+
773
+ /* 2. Keep project name + status badge + Restart/Stop on one line */
774
+ .instance-header {
775
+ flex-wrap: wrap;
776
+ gap: 8px;
777
+ padding: 12px 16px;
778
+ }
779
+
780
+ .instance-actions {
781
+ margin-left: auto;
782
+ }
783
+
784
+ /* 3. Hide project path row on mobile */
785
+ .instance-path-row {
786
+ display: none;
787
+ }
788
+
789
+ /* 4. Compact terminal list (Overview, Architect, shells) */
790
+ .port-item {
791
+ flex-direction: row;
792
+ align-items: center;
793
+ padding: 8px 12px;
794
+ gap: 8px;
795
+ }
796
+
797
+ .port-actions {
798
+ width: auto;
799
+ }
800
+
801
+ .port-actions a {
802
+ display: flex;
803
+ align-items: center;
804
+ justify-content: center;
805
+ padding: 6px 12px;
806
+ flex: 0;
807
+ /* min-height handled by @media (pointer: coarse) at 44px */
808
+ }
809
+
810
+ .instance-body {
811
+ padding: 12px;
812
+ }
813
+
814
+ /* 5. Compact New Shell row */
815
+ .new-shell-row {
816
+ margin-top: 4px;
817
+ padding-top: 4px;
818
+ }
819
+
820
+ /* 6. Compact Recent Projects */
821
+ .recent-item {
822
+ flex-direction: row;
823
+ align-items: center;
824
+ padding: 12px 16px;
825
+ gap: 8px;
826
+ }
827
+
828
+ .recent-path {
829
+ display: none;
830
+ }
831
+
832
+ .recent-time {
833
+ font-size: 12px;
834
+ }
835
+
836
+ /* 7. Reduce section spacing (continued) */
837
+ .recents-section {
838
+ margin-top: 16px;
839
+ padding-top: 16px;
840
+ }
841
+
842
+ .instance-meta {
843
+ margin-top: 8px;
844
+ padding-top: 8px;
845
+ }
846
+
847
+ .launch-section {
848
+ padding: 16px;
849
+ }
850
+
851
+ .launch-form {
852
+ flex-direction: column;
853
+ }
854
+
855
+ .launch-form input[type="text"] {
856
+ min-height: 44px;
857
+ }
858
+
859
+ .btn-launch {
860
+ width: 100%;
861
+ min-height: 44px;
862
+ }
863
+
864
+ .dialog-box {
865
+ min-width: auto;
866
+ width: calc(100% - 32px);
867
+ margin: 16px;
868
+ }
869
+
870
+ .toast-container {
871
+ left: 16px;
872
+ right: 16px;
873
+ bottom: calc(16px + env(safe-area-inset-bottom, 0));
874
+ }
875
+ }
876
+
877
+ /* Touch-friendly tap targets */
878
+ @media (pointer: coarse) {
879
+ .btn, .port-actions a, .autocomplete-item, .recent-item {
880
+ min-height: 44px;
881
+ }
882
+
883
+ .copy-btn {
884
+ min-width: 44px;
885
+ min-height: 44px;
886
+ display: flex;
887
+ align-items: center;
888
+ justify-content: center;
889
+ }
890
+ }
891
+
614
892
  /* Dialog styles */
615
893
  .dialog-overlay {
616
894
  position: fixed;
@@ -680,7 +958,7 @@
680
958
  Agent Farm Control Tower
681
959
  </h1>
682
960
  <div class="header-actions">
683
- <button class="btn" onclick="refresh()">Refresh</button>
961
+ <span id="cloud-status"></span>
684
962
  </div>
685
963
  </header>
686
964
 
@@ -710,29 +988,28 @@
710
988
  Type a path to see suggestions. Directories with <code>codev/</code> are highlighted as valid projects.
711
989
  Non-codev directories will be initialized with <code>codev adopt</code> on first launch.
712
990
  </p>
713
- <div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--border);">
714
- <button class="btn" onclick="showCreateProjectDialog()">Create New Project...</button>
715
- </div>
716
991
  </div>
717
992
 
718
- <!-- Create Project Dialog -->
719
- <div class="dialog-overlay" id="create-dialog" style="display: none;">
993
+ <!-- Cloud Connect Dialog -->
994
+ <div class="dialog-overlay" id="connect-dialog" style="display: none;">
720
995
  <div class="dialog-box">
721
- <h3>Create New Project</h3>
996
+ <h3>Connect to Codev Cloud</h3>
722
997
  <div class="dialog-field">
723
- <label for="new-project-path">Parent Directory</label>
724
- <input type="text" id="new-project-parent" placeholder="~/Development" value="" />
998
+ <label for="connect-device-name">Device Name</label>
999
+ <input type="text" id="connect-device-name" placeholder="my-tower" />
725
1000
  </div>
726
1001
  <div class="dialog-field">
727
- <label for="new-project-name">Project Name</label>
728
- <input type="text" id="new-project-name" placeholder="my-project" />
1002
+ <label for="connect-server-url">Service URL</label>
1003
+ <input type="text" id="connect-server-url" value="https://cloud.codevos.ai" />
729
1004
  </div>
1005
+ <div class="connect-dialog-error" id="connect-error"></div>
730
1006
  <div class="dialog-actions">
731
- <button class="btn" onclick="hideCreateProjectDialog()">Cancel</button>
732
- <button class="btn btn-primary" onclick="createNewProject()">Create & Launch</button>
1007
+ <button class="btn" onclick="hideConnectDialog()">Cancel</button>
1008
+ <button class="btn btn-primary" id="connect-submit-btn" onclick="submitConnect()">Connect</button>
733
1009
  </div>
734
1010
  </div>
735
1011
  </div>
1012
+
736
1013
  </main>
737
1014
 
738
1015
  <div class="toast-container" id="toast-container"></div>
@@ -742,23 +1019,153 @@
742
1019
  let runningInstances = [];
743
1020
  let recentInstances = [];
744
1021
 
1022
+ // Auth helper: get headers with auth token if available
1023
+ function getAuthHeaders() {
1024
+ const key = localStorage.getItem('codev-web-key');
1025
+ return key ? { 'Authorization': `Bearer ${key}` } : {};
1026
+ }
1027
+
1028
+ // Auth helper: make authenticated fetch request
1029
+ async function authFetch(url, options = {}) {
1030
+ const headers = { ...getAuthHeaders(), ...(options.headers || {}) };
1031
+ const response = await fetch(url, { ...options, headers });
1032
+
1033
+ // If 401, clear key and redirect to login
1034
+ if (response.status === 401) {
1035
+ localStorage.removeItem('codev-web-key');
1036
+ location.reload();
1037
+ throw new Error('Unauthorized');
1038
+ }
1039
+ return response;
1040
+ }
1041
+
1042
+ // Logout function
1043
+ function logout() {
1044
+ localStorage.removeItem('codev-web-key');
1045
+ window.location.href = './';
1046
+ }
1047
+
745
1048
  // Initialize
746
1049
  async function init() {
1050
+ // Request notification permission
1051
+ if ('Notification' in window && Notification.permission === 'default') {
1052
+ try {
1053
+ await Notification.requestPermission();
1054
+ } catch (e) {
1055
+ // Ignore errors, notifications are optional
1056
+ }
1057
+ }
1058
+
1059
+ // Subscribe to SSE for push notifications
1060
+ subscribeToEvents();
1061
+
747
1062
  await refresh();
748
1063
  // Poll every 5 seconds
749
1064
  setInterval(refresh, 5000);
750
1065
  }
751
1066
 
1067
+ // SSE subscription for real-time notifications
1068
+ // Uses fetch + ReadableStream to support Authorization header
1069
+ let sseController = null;
1070
+
1071
+ async function subscribeToEvents() {
1072
+ // Abort any existing connection
1073
+ if (sseController) {
1074
+ sseController.abort();
1075
+ }
1076
+
1077
+ sseController = new AbortController();
1078
+
1079
+ try {
1080
+ const response = await fetch('./api/events', {
1081
+ headers: getAuthHeaders(),
1082
+ signal: sseController.signal,
1083
+ });
1084
+
1085
+ if (!response.ok) {
1086
+ console.log('SSE connection failed:', response.status);
1087
+ setTimeout(subscribeToEvents, 5000);
1088
+ return;
1089
+ }
1090
+
1091
+ const reader = response.body.getReader();
1092
+ const decoder = new TextDecoder();
1093
+ let buffer = '';
1094
+
1095
+ while (true) {
1096
+ const { done, value } = await reader.read();
1097
+
1098
+ if (done) {
1099
+ console.log('SSE stream ended, reconnecting...');
1100
+ setTimeout(subscribeToEvents, 5000);
1101
+ return;
1102
+ }
1103
+
1104
+ buffer += decoder.decode(value, { stream: true });
1105
+
1106
+ // Parse SSE format: "data: {...}\n\n"
1107
+ const lines = buffer.split('\n\n');
1108
+ buffer = lines.pop() || ''; // Keep incomplete message in buffer
1109
+
1110
+ for (const chunk of lines) {
1111
+ const dataMatch = chunk.match(/^data:\s*(.+)$/m);
1112
+ if (!dataMatch) continue;
1113
+
1114
+ try {
1115
+ const data = JSON.parse(dataMatch[1]);
1116
+
1117
+ if (data.type === 'connected') {
1118
+ console.log('SSE connected:', data.id);
1119
+ continue;
1120
+ }
1121
+
1122
+ // Show browser notification
1123
+ if ('Notification' in window && Notification.permission === 'granted') {
1124
+ const notification = new Notification(data.title, {
1125
+ body: data.body,
1126
+ icon: '/favicon.ico',
1127
+ tag: 'tower-notification-' + data.id,
1128
+ });
1129
+
1130
+ // Auto-close after 5 seconds
1131
+ setTimeout(() => notification.close(), 5000);
1132
+ }
1133
+
1134
+ // Also show in-app toast
1135
+ showToast(`${data.title}: ${data.body}`, data.type || 'info');
1136
+
1137
+ // Refresh to show updated status
1138
+ refresh();
1139
+ } catch (e) {
1140
+ console.error('SSE parse error:', e);
1141
+ }
1142
+ }
1143
+ }
1144
+ } catch (e) {
1145
+ if (e.name === 'AbortError') {
1146
+ // Intentional abort, don't reconnect
1147
+ return;
1148
+ }
1149
+ console.error('SSE error:', e);
1150
+ setTimeout(subscribeToEvents, 5000);
1151
+ }
1152
+ }
1153
+
752
1154
  // Refresh data from API
753
1155
  async function refresh() {
754
1156
  try {
755
- const response = await fetch('/api/status');
756
- if (!response.ok) throw new Error('Failed to fetch status');
1157
+ const [statusResponse, cloudStatus] = await Promise.all([
1158
+ authFetch('./api/status'),
1159
+ fetchCloudStatus(),
1160
+ ]);
757
1161
 
758
- const data = await response.json();
1162
+ if (!statusResponse.ok) throw new Error('Failed to fetch status');
1163
+
1164
+ const data = await statusResponse.json();
759
1165
  runningInstances = (data.instances || []).filter(i => i.running);
760
1166
  recentInstances = (data.instances || []).filter(i => !i.running);
761
1167
  render();
1168
+ renderCloudStatus(cloudStatus);
762
1169
  } catch (err) {
763
1170
  console.error('Refresh error:', err);
764
1171
  showToast('Failed to refresh: ' + err.message, 'error');
@@ -774,7 +1181,7 @@
774
1181
  <div class="empty-state">
775
1182
  <div class="icon">📭</div>
776
1183
  <h2>No running instances</h2>
777
- <p>Start a new instance below or run <code>af start</code> in a project directory.</p>
1184
+ <p>Start a new instance below or run <code>af dash start</code> in a project directory.</p>
778
1185
  </div>
779
1186
  `;
780
1187
  } else {
@@ -810,28 +1217,69 @@
810
1217
  function renderInstance(instance) {
811
1218
  const statusClass = instance.running ? 'running' : 'stopped';
812
1219
  const statusText = instance.running ? 'Running' : 'Stopped';
813
-
814
- const portsHtml = instance.ports.map(port => `
815
- <div class="port-item">
816
- <div class="port-info">
817
- <span class="port-status ${port.active ? 'active' : 'inactive'}"></span>
818
- <span class="port-type">${escapeHtml(port.type)}</span>
819
- <span class="port-url">Port ${port.port}</span>
1220
+ const hasGate = instance.gateStatus?.hasGate;
1221
+
1222
+ // Render terminals list - always show Overview first, then individual terminals
1223
+ const terminals = instance.terminals || [];
1224
+ let terminalsHtml = '';
1225
+
1226
+ if (instance.running) {
1227
+ // Always show Overview link first (opens split view with Architect + Status panel)
1228
+ terminalsHtml = `
1229
+ <div class="port-item">
1230
+ <div class="port-info">
1231
+ <span class="port-status active"></span>
1232
+ <span class="port-type">Overview</span>
1233
+ </div>
1234
+ <div class="port-actions">
1235
+ <a href="${escapeHtml(relUrl(instance.proxyUrl))}" target="_blank">Open</a>
1236
+ </div>
820
1237
  </div>
821
- <div class="port-actions">
822
- <a href="${escapeHtml(port.url)}" target="_blank" class="${port.active ? '' : 'disabled'}">
823
- Open
824
- </a>
1238
+ `;
1239
+
1240
+ // Then show individual terminals (open full-screen terminal view)
1241
+ if (terminals.length > 0) {
1242
+ terminalsHtml += terminals.map(terminal => `
1243
+ <div class="port-item">
1244
+ <div class="port-info">
1245
+ <span class="port-status ${terminal.active ? 'active' : 'inactive'}"></span>
1246
+ <span class="port-type">${escapeHtml(terminal.label)}</span>
1247
+ </div>
1248
+ <div class="port-actions">
1249
+ <a href="${escapeHtml(relUrl(terminal.url))}&fullscreen=1" target="_blank">Open</a>
1250
+ </div>
1251
+ </div>
1252
+ `).join('');
1253
+ }
1254
+
1255
+ // Add "New Shell" button for this instance
1256
+ terminalsHtml += `
1257
+ <div class="port-item new-shell-row" style="border-top: 1px dashed var(--border);">
1258
+ <div class="port-info">
1259
+ <span class="port-status" style="background: var(--accent);"></span>
1260
+ <span class="port-type">New Shell</span>
1261
+ </div>
1262
+ <div class="port-actions">
1263
+ <button class="btn btn-small" onclick="createShell('${escapeHtml(instance.projectPath)}')">+ Create</button>
1264
+ </div>
825
1265
  </div>
826
- </div>
827
- `).join('');
1266
+ `;
1267
+ }
828
1268
 
829
1269
  const lastUsed = instance.lastUsed
830
1270
  ? formatDate(instance.lastUsed)
831
1271
  : 'Never';
832
1272
 
1273
+ const gateIndicatorHtml = hasGate ? `
1274
+ <div class="gate-indicator">
1275
+ <span>⏳</span>
1276
+ <span class="gate-name">${escapeHtml(instance.gateStatus.gateName || 'approval')}</span>
1277
+ ${instance.gateStatus.builderId ? `<span class="gate-builder">(${escapeHtml(instance.gateStatus.builderId)})</span>` : ''}
1278
+ </div>
1279
+ ` : '';
1280
+
833
1281
  return `
834
- <div class="instance">
1282
+ <div class="instance ${hasGate ? 'gate-pending' : ''}" data-project="${escapeHtml(instance.projectPath)}">
835
1283
  <div class="instance-header">
836
1284
  <div class="instance-title">
837
1285
  <span class="instance-name">${escapeHtml(instance.projectName)}</span>
@@ -842,13 +1290,14 @@
842
1290
  </div>
843
1291
  <div class="instance-actions">
844
1292
  ${instance.running ? `
845
- <button class="btn btn-small" onclick="restartInstance(${instance.basePort}, '${escapeHtml(instance.projectPath)}')">Restart</button>
846
- <button class="btn btn-small btn-danger" onclick="stopInstance(${instance.basePort})">Stop</button>
1293
+ <button class="btn btn-small" onclick="restartInstance('${escapeHtml(instance.projectPath)}')">Restart</button>
1294
+ <button class="btn btn-small btn-danger" onclick="stopInstance('${escapeHtml(instance.projectPath)}')">Stop</button>
847
1295
  ` : `
848
1296
  <button class="btn btn-small btn-primary" onclick="launchPath('${escapeHtml(instance.projectPath)}')">Start</button>
849
1297
  `}
850
1298
  </div>
851
1299
  </div>
1300
+ ${gateIndicatorHtml}
852
1301
  <div class="instance-path-row">
853
1302
  <span class="instance-path" title="${escapeHtml(instance.projectPath)}">
854
1303
  ${escapeHtml(instance.projectPath)}
@@ -857,11 +1306,10 @@
857
1306
  </div>
858
1307
  <div class="instance-body">
859
1308
  <div class="ports-list">
860
- ${portsHtml}
1309
+ ${terminalsHtml}
861
1310
  </div>
862
1311
  <div class="instance-meta">
863
1312
  <span>Last active: ${lastUsed}</span>
864
- <span>Port block: ${instance.basePort}-${instance.basePort + 99}</span>
865
1313
  </div>
866
1314
  </div>
867
1315
  </div>
@@ -952,7 +1400,7 @@
952
1400
  }
953
1401
 
954
1402
  try {
955
- const response = await fetch('/api/browse?path=' + encodeURIComponent(inputPath));
1403
+ const response = await authFetch('./api/browse?path=' + encodeURIComponent(inputPath));
956
1404
  const data = await response.json();
957
1405
  suggestions = data.suggestions || [];
958
1406
  selectedIndex = -1;
@@ -1007,7 +1455,7 @@
1007
1455
  // Launch a specific path (from recents)
1008
1456
  async function launchPath(projectPath) {
1009
1457
  try {
1010
- const response = await fetch('/api/launch', {
1458
+ const response = await authFetch('./api/launch', {
1011
1459
  method: 'POST',
1012
1460
  headers: { 'Content-Type': 'application/json' },
1013
1461
  body: JSON.stringify({ projectPath })
@@ -1030,13 +1478,13 @@
1030
1478
  }
1031
1479
  }
1032
1480
 
1033
- // Stop an instance by port
1034
- async function stopInstance(basePort) {
1481
+ // Stop an instance by project path
1482
+ async function stopInstance(projectPath) {
1035
1483
  try {
1036
- const response = await fetch('/api/stop', {
1484
+ const response = await authFetch('./api/stop', {
1037
1485
  method: 'POST',
1038
1486
  headers: { 'Content-Type': 'application/json' },
1039
- body: JSON.stringify({ basePort })
1487
+ body: JSON.stringify({ projectPath })
1040
1488
  });
1041
1489
 
1042
1490
  const result = await response.json();
@@ -1053,20 +1501,20 @@
1053
1501
  }
1054
1502
 
1055
1503
  // Restart an instance
1056
- async function restartInstance(basePort, projectPath) {
1504
+ async function restartInstance(projectPath) {
1057
1505
  try {
1058
1506
  // First stop
1059
- await fetch('/api/stop', {
1507
+ await authFetch('./api/stop', {
1060
1508
  method: 'POST',
1061
1509
  headers: { 'Content-Type': 'application/json' },
1062
- body: JSON.stringify({ basePort })
1510
+ body: JSON.stringify({ projectPath })
1063
1511
  });
1064
1512
 
1065
1513
  // Wait a moment for processes to die
1066
1514
  await new Promise(r => setTimeout(r, 1000));
1067
1515
 
1068
1516
  // Then start
1069
- const response = await fetch('/api/launch', {
1517
+ const response = await authFetch('./api/launch', {
1070
1518
  method: 'POST',
1071
1519
  headers: { 'Content-Type': 'application/json' },
1072
1520
  body: JSON.stringify({ projectPath })
@@ -1096,7 +1544,7 @@
1096
1544
  }
1097
1545
 
1098
1546
  try {
1099
- const response = await fetch('/api/launch', {
1547
+ const response = await authFetch('./api/launch', {
1100
1548
  method: 'POST',
1101
1549
  headers: { 'Content-Type': 'application/json' },
1102
1550
  body: JSON.stringify({ projectPath })
@@ -1122,6 +1570,34 @@
1122
1570
  }
1123
1571
  }
1124
1572
 
1573
+ // Create a new shell for a running instance (via tower proxy)
1574
+ async function createShell(projectPath) {
1575
+ try {
1576
+ // Use tower proxy to route to the project's dashboard API
1577
+ const encodedPath = toBase64URL(projectPath);
1578
+ const response = await authFetch(`./project/${encodedPath}/api/tabs/shell`, {
1579
+ method: 'POST',
1580
+ headers: { 'Content-Type': 'application/json' },
1581
+ });
1582
+
1583
+ if (!response.ok) {
1584
+ const errorText = await response.text();
1585
+ throw new Error(errorText || 'Failed to create shell');
1586
+ }
1587
+
1588
+ const result = await response.json();
1589
+
1590
+ if (result.success === false) {
1591
+ throw new Error(result.error || 'Failed to create shell');
1592
+ }
1593
+
1594
+ showToast(`Shell created: ${result.name || 'shell'}`, 'success');
1595
+ setTimeout(refresh, 1000);
1596
+ } catch (err) {
1597
+ showToast('Failed to create shell: ' + err.message, 'error');
1598
+ }
1599
+ }
1600
+
1125
1601
  // Format date for display
1126
1602
  function formatDate(isoString) {
1127
1603
  const date = new Date(isoString);
@@ -1159,6 +1635,13 @@
1159
1635
  }
1160
1636
  }
1161
1637
 
1638
+ // Convert absolute paths to relative so links work behind reverse proxies.
1639
+ // E.g. "/project/abc/" → "./project/abc/"
1640
+ function relUrl(path) {
1641
+ if (path && path.startsWith('/')) return '.' + path;
1642
+ return path || '';
1643
+ }
1644
+
1162
1645
  // HTML escape
1163
1646
  function escapeHtml(str) {
1164
1647
  if (!str) return '';
@@ -1170,6 +1653,23 @@
1170
1653
  .replace(/'/g, '&#39;');
1171
1654
  }
1172
1655
 
1656
+ // Base64URL encoding (RFC 4648) for project paths
1657
+ // IMPORTANT: Use TextEncoder for Unicode support (btoa only handles Latin-1)
1658
+ function toBase64URL(str) {
1659
+ // Encode string to UTF-8 bytes, then to Base64, then to Base64URL
1660
+ const bytes = new TextEncoder().encode(str);
1661
+ const base64 = btoa(String.fromCharCode(...bytes));
1662
+ return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
1663
+ }
1664
+
1665
+ // Generate proxy URL for a project
1666
+ // All terminals are now multiplexed on a single port via WebSocket (Spec 0085)
1667
+ // The React dashboard handles tab selection internally
1668
+ function getProxyUrl(instance, portType) {
1669
+ const encodedPath = toBase64URL(instance.projectPath);
1670
+ return `./project/${encodedPath}/`;
1671
+ }
1672
+
1173
1673
  // Toast notifications
1174
1674
  function showToast(message, type = 'info') {
1175
1675
  const container = document.getElementById('toast-container');
@@ -1183,74 +1683,214 @@
1183
1683
  }, 4000);
1184
1684
  }
1185
1685
 
1186
- // Create project dialog functions
1187
- function showCreateProjectDialog() {
1188
- const dialog = document.getElementById('create-dialog');
1189
- dialog.style.display = 'flex';
1190
- // Default to ~/Development
1191
- document.getElementById('new-project-parent').value = '~/Development';
1192
- document.getElementById('new-project-name').value = '';
1193
- document.getElementById('new-project-name').focus();
1686
+ // Close dialogs on escape
1687
+ document.addEventListener('keydown', (e) => {
1688
+ if (e.key === 'Escape') {
1689
+ hideConnectDialog();
1690
+ }
1691
+ });
1692
+ document.getElementById('connect-dialog').addEventListener('click', (e) => {
1693
+ if (e.target.id === 'connect-dialog') {
1694
+ hideConnectDialog();
1695
+ }
1696
+ });
1697
+
1698
+ // Cloud status
1699
+ let cloudLoading = false;
1700
+
1701
+ async function fetchCloudStatus() {
1702
+ try {
1703
+ const res = await authFetch('./api/tunnel/status');
1704
+ if (res.status === 404) return null;
1705
+ if (!res.ok) return { state: 'error' };
1706
+ return await res.json();
1707
+ } catch {
1708
+ return null;
1709
+ }
1194
1710
  }
1195
1711
 
1196
- function hideCreateProjectDialog() {
1197
- document.getElementById('create-dialog').style.display = 'none';
1712
+ function formatUptime(ms) {
1713
+ const s = Math.floor(ms / 1000);
1714
+ if (s < 60) return s + 's';
1715
+ if (s < 3600) return Math.floor(s / 60) + 'm ' + (s % 60) + 's';
1716
+ const h = Math.floor(s / 3600);
1717
+ const m = Math.floor((s % 3600) / 60);
1718
+ return h + 'h ' + m + 'm';
1198
1719
  }
1199
1720
 
1200
- async function createNewProject() {
1201
- const parent = document.getElementById('new-project-parent').value.trim();
1202
- const name = document.getElementById('new-project-name').value.trim();
1721
+ // Store the latest cloud status for use by cloudConnect
1722
+ let lastCloudStatus = null;
1723
+
1724
+ function renderCloudStatus(status) {
1725
+ const el = document.getElementById('cloud-status');
1726
+ lastCloudStatus = status;
1203
1727
 
1204
- if (!parent) {
1205
- showToast('Please enter a parent directory', 'error');
1728
+ // When accessed through codevos.ai, cloud status is irrelevant
1729
+ if (window.location.hostname.endsWith('codevos.ai')) {
1730
+ el.innerHTML = '';
1206
1731
  return;
1207
1732
  }
1208
- if (!name) {
1209
- showToast('Please enter a project name', 'error');
1733
+
1734
+ if (!status || status.state === 'error') {
1735
+ el.innerHTML = '';
1210
1736
  return;
1211
1737
  }
1212
1738
 
1213
- // Validate name (no spaces, special chars)
1214
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
1215
- showToast('Project name can only contain letters, numbers, hyphens, and underscores', 'error');
1739
+ if (!status.registered) {
1740
+ el.innerHTML = `
1741
+ <span class="cloud-status">
1742
+ Codev Cloud
1743
+ <button class="cloud-btn" onclick="cloudConnect()" ${cloudLoading ? 'disabled' : ''}>Connect</button>
1744
+ </span>`;
1216
1745
  return;
1217
1746
  }
1218
1747
 
1219
- hideCreateProjectDialog();
1748
+ if (status.state === 'auth_failed') {
1749
+ el.innerHTML = `
1750
+ <span class="cloud-status">
1751
+ <span class="cloud-dot cloud-dot--red"></span>
1752
+ auth failed
1753
+ </span>`;
1754
+ return;
1755
+ }
1220
1756
 
1221
- try {
1222
- const response = await fetch('/api/create', {
1223
- method: 'POST',
1224
- headers: { 'Content-Type': 'application/json' },
1225
- body: JSON.stringify({ parent, name })
1226
- });
1757
+ if (status.state === 'connecting') {
1758
+ el.innerHTML = `
1759
+ <span class="cloud-status">
1760
+ <span class="cloud-dot cloud-dot--yellow"></span>
1761
+ connecting...
1762
+ </span>`;
1763
+ return;
1764
+ }
1227
1765
 
1228
- const result = await response.json();
1766
+ if (status.state === 'connected') {
1767
+ const name = escapeHtml(status.towerName || 'connected');
1768
+ const uptimeStr = status.uptime != null ? ' <span class="cloud-uptime">' + formatUptime(status.uptime) + '</span>' : '';
1769
+ el.innerHTML = `
1770
+ <span class="cloud-status">
1771
+ <span class="cloud-dot cloud-dot--green"></span>
1772
+ ${name}${uptimeStr}
1773
+ <button class="cloud-btn" onclick="cloudDisconnect()" ${cloudLoading ? 'disabled' : ''}>Disconnect</button>
1774
+ </span>`;
1775
+ return;
1776
+ }
1229
1777
 
1230
- if (!result.success) {
1231
- throw new Error(result.error || 'Create failed');
1778
+ // Disconnected
1779
+ el.innerHTML = `
1780
+ <span class="cloud-status">
1781
+ <span class="cloud-dot cloud-dot--gray"></span>
1782
+ disconnected
1783
+ <button class="cloud-btn" onclick="cloudConnect()" ${cloudLoading ? 'disabled' : ''}>Connect</button>
1784
+ </span>`;
1785
+ }
1786
+
1787
+ async function cloudConnect() {
1788
+ // Smart connect: if registered, reconnect without dialog
1789
+ if (lastCloudStatus && lastCloudStatus.registered) {
1790
+ cloudLoading = true;
1791
+ renderCloudStatus({ registered: true, state: 'connecting' });
1792
+ try {
1793
+ await authFetch('./api/tunnel/connect', { method: 'POST' });
1794
+ showToast('Connecting to cloud...', 'success');
1795
+ } catch (err) {
1796
+ showToast('Connect failed: ' + err.message, 'error');
1232
1797
  }
1233
-
1234
- showToast(`Project "${name}" created! Launching...`, 'success');
1798
+ cloudLoading = false;
1235
1799
  setTimeout(refresh, 2000);
1800
+ return;
1801
+ }
1802
+ // Not registered: open the connect dialog
1803
+ showConnectDialog();
1804
+ }
1805
+
1806
+ async function cloudDisconnect() {
1807
+ if (!confirm('This will disconnect from Codev Cloud. Continue?')) return;
1808
+ cloudLoading = true;
1809
+ try {
1810
+ const res = await authFetch('./api/tunnel/disconnect', { method: 'POST' });
1811
+ const data = await res.json();
1812
+ if (data.warning) {
1813
+ showToast(data.warning, 'error');
1814
+ } else {
1815
+ showToast('Disconnected from cloud', 'success');
1816
+ }
1236
1817
  } catch (err) {
1237
- showToast('Failed to create project: ' + err.message, 'error');
1818
+ showToast('Disconnect failed: ' + err.message, 'error');
1238
1819
  }
1820
+ cloudLoading = false;
1821
+ setTimeout(refresh, 1000);
1822
+ }
1823
+
1824
+ // Cloud connect dialog functions
1825
+ function showConnectDialog() {
1826
+ const dialog = document.getElementById('connect-dialog');
1827
+ const nameInput = document.getElementById('connect-device-name');
1828
+ const errorEl = document.getElementById('connect-error');
1829
+ errorEl.style.display = 'none';
1830
+ errorEl.textContent = '';
1831
+ // Default device name from hostname (fetched via /api/tunnel/status)
1832
+ nameInput.value = (lastCloudStatus && lastCloudStatus.hostname) || '';
1833
+ document.getElementById('connect-server-url').value = 'https://cloud.codevos.ai';
1834
+ dialog.style.display = 'flex';
1835
+ nameInput.focus();
1239
1836
  }
1240
1837
 
1241
- // Close dialog on escape
1242
- document.addEventListener('keydown', (e) => {
1243
- if (e.key === 'Escape') {
1244
- hideCreateProjectDialog();
1838
+ function hideConnectDialog() {
1839
+ document.getElementById('connect-dialog').style.display = 'none';
1840
+ }
1841
+
1842
+ function uiNormalizeDeviceName(raw) {
1843
+ return raw.trim().toLowerCase().replace(/[\s_]+/g, '-').replace(/[^a-z0-9-]/g, '');
1844
+ }
1845
+
1846
+ function uiValidateDeviceName(name) {
1847
+ if (!name) return { valid: false, error: 'Device name is required.' };
1848
+ if (name.length > 63) return { valid: false, error: 'Device name must be 63 characters or fewer.' };
1849
+ if (!/^[a-z0-9]/.test(name) || !/[a-z0-9]$/.test(name)) return { valid: false, error: 'Device name must start and end with a letter or number.' };
1850
+ if (/^-+$/.test(name)) return { valid: false, error: 'Device name cannot be all hyphens.' };
1851
+ return { valid: true };
1852
+ }
1853
+
1854
+ async function submitConnect() {
1855
+ const rawName = document.getElementById('connect-device-name').value;
1856
+ const serverUrl = document.getElementById('connect-server-url').value.trim();
1857
+ const errorEl = document.getElementById('connect-error');
1858
+ const submitBtn = document.getElementById('connect-submit-btn');
1859
+
1860
+ const name = uiNormalizeDeviceName(rawName);
1861
+ const result = uiValidateDeviceName(name);
1862
+ if (!result.valid) {
1863
+ errorEl.textContent = result.error;
1864
+ errorEl.style.display = 'block';
1865
+ return;
1245
1866
  }
1246
- });
1247
1867
 
1248
- // Close dialog on backdrop click
1249
- document.getElementById('create-dialog').addEventListener('click', (e) => {
1250
- if (e.target.id === 'create-dialog') {
1251
- hideCreateProjectDialog();
1868
+ errorEl.style.display = 'none';
1869
+ submitBtn.disabled = true;
1870
+ submitBtn.textContent = 'Connecting...';
1871
+
1872
+ try {
1873
+ const res = await authFetch('./api/tunnel/connect', {
1874
+ method: 'POST',
1875
+ headers: { 'Content-Type': 'application/json' },
1876
+ body: JSON.stringify({ name, serverUrl, origin: window.location.origin }),
1877
+ });
1878
+ const data = await res.json();
1879
+ if (data.authUrl) {
1880
+ window.location.href = data.authUrl;
1881
+ } else {
1882
+ errorEl.textContent = data.error || 'Unexpected response from server';
1883
+ errorEl.style.display = 'block';
1884
+ submitBtn.disabled = false;
1885
+ submitBtn.textContent = 'Connect';
1886
+ }
1887
+ } catch (err) {
1888
+ errorEl.textContent = 'Connection failed: ' + err.message;
1889
+ errorEl.style.display = 'block';
1890
+ submitBtn.disabled = false;
1891
+ submitBtn.textContent = 'Connect';
1252
1892
  }
1253
- });
1893
+ }
1254
1894
 
1255
1895
  // Initialize
1256
1896
  init();