thursday-agent 0.8.0 → 0.9.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 (264) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/prerender-manifest.json +3 -3
  4. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  5. package/.next/server/app/_global-error.html +1 -1
  6. package/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  11. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/server/app/_not-found.html +1 -1
  13. package/.next/server/app/_not-found.rsc +2 -2
  14. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  17. package/.next/server/app/api/artifact/route.js.nft.json +1 -1
  18. package/.next/server/app/api/bot/keep-working/route.js.nft.json +1 -1
  19. package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
  20. package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
  21. package/.next/server/app/api/bot/route.js.nft.json +1 -1
  22. package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -1
  23. package/.next/server/app/api/bot/thread/route.js.nft.json +1 -1
  24. package/.next/server/app/api/config/route.js.nft.json +1 -1
  25. package/.next/server/app/api/events/route.js.nft.json +1 -1
  26. package/.next/server/app/api/favicon/[host]/route.js.nft.json +1 -1
  27. package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
  28. package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
  29. package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
  30. package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
  31. package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
  32. package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
  33. package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
  34. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  35. package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
  36. package/.next/server/app/api/memory/edit/route.js +1 -1
  37. package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
  38. package/.next/server/app/api/memory/route.js.nft.json +1 -1
  39. package/.next/server/app/api/probe/route.js.nft.json +1 -1
  40. package/.next/server/app/api/reach/route.js +11 -11
  41. package/.next/server/app/api/reach/route.js.nft.json +1 -1
  42. package/.next/server/app/api/routine/route.js +6 -6
  43. package/.next/server/app/api/routine/route.js.nft.json +1 -1
  44. package/.next/server/app/api/signins/route.js.nft.json +1 -1
  45. package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
  46. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  47. package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
  48. package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
  49. package/.next/server/app/api/thursday/text/route.js +11 -11
  50. package/.next/server/app/api/thursday/text/route.js.nft.json +1 -1
  51. package/.next/server/app/api/thursday/tool-call/route.js +6 -6
  52. package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
  53. package/.next/server/app/api/workspace/files/route.js.nft.json +1 -1
  54. package/.next/server/app/api/workspace/route.js.nft.json +1 -1
  55. package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
  56. package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
  57. package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
  58. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  59. package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -1
  60. package/.next/server/app/page/server-reference-manifest.json +56 -56
  61. package/.next/server/app/page.js +3 -3
  62. package/.next/server/app/page.js.nft.json +1 -1
  63. package/.next/server/app/page_client-reference-manifest.js +1 -1
  64. package/.next/server/chunks/[root-of-the-server]__012s2o8._.js +1 -1
  65. package/.next/server/chunks/[root-of-the-server]__04r4h-2._.js +1 -1
  66. package/.next/server/chunks/[root-of-the-server]__0mrk2it._.js +1 -1
  67. package/.next/server/chunks/[root-of-the-server]__0nmhgeo._.js +3 -0
  68. package/.next/server/chunks/[root-of-the-server]__1fy2g82._.js +3 -0
  69. package/.next/server/chunks/[root-of-the-server]__1ndi14y._.js +1 -1
  70. package/.next/server/chunks/[root-of-the-server]__1nmflqk._.js +3 -0
  71. package/.next/server/chunks/[root-of-the-server]__1v7-zke._.js +1 -1
  72. package/.next/server/chunks/[root-of-the-server]__1wnzmx9._.js +3 -0
  73. package/.next/server/chunks/[root-of-the-server]__1xv-5cq._.js +1 -1
  74. package/.next/server/chunks/[root-of-the-server]__1ygplx1._.js +1 -1
  75. package/.next/server/chunks/_05v8k21._.js +1 -1
  76. package/.next/server/chunks/_0awtznl._.js +4 -4
  77. package/.next/server/chunks/_0k6qjcf._.js +1 -1
  78. package/.next/server/chunks/_0na2fj1._.js +5 -5
  79. package/.next/server/chunks/_0x-ej3f._.js +21 -21
  80. package/.next/server/chunks/_1dc_d1q._.js +22 -20
  81. package/.next/server/chunks/_1jc5g4p._.js +2 -2
  82. package/.next/server/chunks/_1jn-aep._.js +50 -48
  83. package/.next/server/chunks/{_1u8ygbz._.js → _1m88lkh._.js} +7 -7
  84. package/.next/server/chunks/_1uuucy-._.js +4 -4
  85. package/.next/server/chunks/_1v9r2ke._.js +1 -1
  86. package/.next/server/chunks/ssr/{[root-of-the-server]__1h4bnrf._.js → [root-of-the-server]__0oo-wlv._.js} +2 -2
  87. package/.next/server/chunks/ssr/{[root-of-the-server]__1kyi568._.js → [root-of-the-server]__0tvavy9._.js} +1 -1
  88. package/.next/server/chunks/ssr/[root-of-the-server]__0tzekbl._.js +1 -1
  89. package/.next/server/chunks/ssr/[root-of-the-server]__0zuns7e._.js +3 -0
  90. package/.next/server/chunks/ssr/[root-of-the-server]__1se25x0._.js +1 -1
  91. package/.next/server/chunks/ssr/{_1i3ddn7._.js → _05pl_u8._.js} +1 -1
  92. package/.next/server/chunks/ssr/_070nv1_._.js +3 -0
  93. package/.next/server/chunks/ssr/_097-ljd._.js +1 -1
  94. package/.next/server/chunks/ssr/_0dshsh4._.js +3 -0
  95. package/.next/server/chunks/ssr/_0fqhhwi._.js +1 -1
  96. package/.next/server/chunks/ssr/{_0z8jpch._.js → _0ital-2._.js} +2 -2
  97. package/.next/server/chunks/ssr/{_19g570d._.js → _0kjj5ts._.js} +3 -3
  98. package/.next/server/chunks/ssr/{_1nod5hh._.js → _0l_mcfz._.js} +2 -2
  99. package/.next/server/chunks/ssr/_0ld9_qq._.js +1 -1
  100. package/.next/server/chunks/ssr/_0ltz2q5._.js +3 -0
  101. package/.next/server/chunks/ssr/_0scf7xy._.js +3 -0
  102. package/.next/server/chunks/ssr/_0u5xv6g._.js +3 -0
  103. package/.next/server/chunks/ssr/{_0ewory0._.js → _0urj3mc._.js} +2 -2
  104. package/.next/server/chunks/ssr/{_0pm-2p-._.js → _1-zyvdb._.js} +2 -2
  105. package/.next/server/chunks/ssr/{_1_rqu8x._.js → _12mijol._.js} +7 -7
  106. package/.next/server/chunks/ssr/_15zqt-t._.js +1 -1
  107. package/.next/server/chunks/ssr/_184327j._.js +1 -1
  108. package/.next/server/chunks/ssr/{_1y-ea28._.js → _1a-nuxx._.js} +2 -2
  109. package/.next/server/chunks/ssr/_1alxym6._.js +3 -0
  110. package/.next/server/chunks/ssr/{_0hzz82u._.js → _1d423aj._.js} +25 -23
  111. package/.next/server/chunks/ssr/{_1u1mxu9._.js → _1flbs77._.js} +2 -2
  112. package/.next/server/chunks/ssr/_1mqn9mc._.js +1 -1
  113. package/.next/server/chunks/ssr/_1neoj22._.js +1 -1
  114. package/.next/server/chunks/ssr/{_1oijqjw._.js → _1tmu8z-._.js} +2 -2
  115. package/.next/server/chunks/ssr/{_10y6r1n._.js → _1uy0xom._.js} +3 -3
  116. package/.next/server/chunks/ssr/_1x-kojb._.js +20 -0
  117. package/.next/server/chunks/ssr/_1xoch6f._.js +1 -1
  118. package/.next/server/chunks/ssr/_1z74dmq._.js +164 -0
  119. package/.next/server/chunks/ssr/features_bot_1nr94ks._.js +3 -0
  120. package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +1 -1
  121. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  122. package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
  123. package/.next/server/chunks/ssr/features_routine_components_routine-setting_tsx_055fknz._.js +1 -1
  124. package/.next/server/chunks/ssr/{node_modules_111sh9o._.js → node_modules_03d0rfz._.js} +1 -1
  125. package/.next/server/chunks/ssr/{node_modules_0lg75qx._.js → node_modules_04_sbbc._.js} +1 -1
  126. package/.next/server/chunks/ssr/{node_modules_10gbm2y._.js → node_modules_05ybmta._.js} +1 -1
  127. package/.next/server/chunks/ssr/{node_modules_1v9tc1c._.js → node_modules_068ws5v._.js} +1 -1
  128. package/.next/server/chunks/ssr/{node_modules_1_8mv4v._.js → node_modules_06bb4q8._.js} +1 -1
  129. package/.next/server/chunks/ssr/{node_modules_01f8_se._.js → node_modules_07rs_p2._.js} +1 -1
  130. package/.next/server/chunks/ssr/{node_modules_0ii1l89._.js → node_modules_0dzm4um._.js} +1 -1
  131. package/.next/server/chunks/ssr/{node_modules_20xmwtu._.js → node_modules_0jljel9._.js} +1 -1
  132. package/.next/server/chunks/ssr/{node_modules_1ix-g49._.js → node_modules_0sgy-dc._.js} +1 -1
  133. package/.next/server/chunks/ssr/{node_modules_0woscox._.js → node_modules_0t_ez6-._.js} +1 -1
  134. package/.next/server/chunks/ssr/{node_modules_059vtf2._.js → node_modules_0th357h._.js} +1 -1
  135. package/.next/server/chunks/ssr/{node_modules_06w645b._.js → node_modules_0ubdtk-._.js} +1 -1
  136. package/.next/server/chunks/ssr/{node_modules_0b8df3s._.js → node_modules_0uyh_vb._.js} +1 -1
  137. package/.next/server/chunks/ssr/{node_modules_07ih6wb._.js → node_modules_0wnlddw._.js} +1 -1
  138. package/.next/server/chunks/ssr/{node_modules_0gt-uw9._.js → node_modules_0x630an._.js} +1 -1
  139. package/.next/server/chunks/ssr/{node_modules_1x825hz._.js → node_modules_0yvz9e0._.js} +1 -1
  140. package/.next/server/chunks/ssr/{node_modules_0t-44lj._.js → node_modules_12et8d5._.js} +1 -1
  141. package/.next/server/chunks/ssr/{node_modules_1m3gb16._.js → node_modules_13kwejm._.js} +1 -1
  142. package/.next/server/chunks/ssr/{node_modules_1p9rdsy._.js → node_modules_1af3w91._.js} +1 -1
  143. package/.next/server/chunks/ssr/{node_modules_0sz5yg1._.js → node_modules_1bucjlk._.js} +1 -1
  144. package/.next/server/chunks/ssr/{node_modules_1teyzfa._.js → node_modules_1lkix61._.js} +1 -1
  145. package/.next/server/chunks/ssr/{node_modules_1lsyy7k._.js → node_modules_1ogczzz._.js} +1 -1
  146. package/.next/server/chunks/ssr/{node_modules_1-7qawg._.js → node_modules_1s-gb-1._.js} +1 -1
  147. package/.next/server/chunks/ssr/{node_modules_0yv-re6._.js → node_modules_1uwg-gz._.js} +1 -1
  148. package/.next/server/chunks/ssr/{node_modules_03ptrl9._.js → node_modules_1wbkzl9._.js} +1 -1
  149. package/.next/server/chunks/ssr/{node_modules_00jv9_3._.js → node_modules_1z1lrbk._.js} +1 -1
  150. package/.next/server/chunks/ssr/{node_modules_@base-ui_react_0jne69c._.js → node_modules_@base-ui_react_0buz1-j._.js} +1 -1
  151. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_00q1oa3._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0r443fh._.js} +1 -1
  152. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1-2_5z7._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_10wnlbk._.js} +1 -1
  153. package/.next/server/middleware-build-manifest.js +3 -3
  154. package/.next/server/pages/404.html +1 -1
  155. package/.next/server/pages/500.html +1 -1
  156. package/.next/server/server-reference-manifest.js +1 -1
  157. package/.next/server/server-reference-manifest.json +57 -57
  158. package/.next/static/chunks/06nt9tatr-dug.js +1 -0
  159. package/.next/static/chunks/0_awn8tucznxc.js +1 -0
  160. package/.next/static/chunks/{34s52v8f841zo.js → 0ge-hx0ed83a1.js} +1 -1
  161. package/.next/static/chunks/0pda_vf-abwvx.js +1 -0
  162. package/.next/static/chunks/{2u9yjkpkoij75.js → 0qmd8n52hp-6v.js} +1 -1
  163. package/.next/static/chunks/0rowp9rx3qbxu.js +18 -0
  164. package/.next/static/chunks/{1ocy4w0v20a3v.js → 13ixzheqw7o-w.js} +2 -2
  165. package/.next/static/chunks/1grkt5gr8pbg1.js +17 -0
  166. package/.next/static/chunks/{21gf2t_whhurc.js → 1yugpvvi8qrbu.js} +2 -2
  167. package/.next/static/chunks/{1e4yrzs5evq_r.js → 245hb85nsy6j4.js} +1 -1
  168. package/.next/static/chunks/27lkanxvtdn09.js +1 -0
  169. package/.next/static/chunks/{0oz7a68qanm99.js → 27tvv1i-smrck.js} +1 -1
  170. package/.next/static/chunks/{2zhfdbq50ee5i.js → 2c64drdevdla3.js} +1 -1
  171. package/.next/static/chunks/{0k5tkt_qi52kb.js → 2ndw4w4u2x51r.js} +1 -1
  172. package/.next/static/chunks/37j27b3e1gt7r.js +1 -0
  173. package/.next/static/chunks/{2om4lkg86feb4.js → 38301ji-85i30.js} +1 -1
  174. package/.next/static/chunks/{09c13vnqy7al3.js → 3bbzsitgiltnt.js} +2 -2
  175. package/.next/static/chunks/3fec-aeteddr-.js +1 -0
  176. package/.next/static/chunks/3go3hwp1z2_h2.css +1 -0
  177. package/.next/static/chunks/3h2bun3hcggg8.js +1 -0
  178. package/.next/static/chunks/{0g1p98s2pfx2w.js → 3h2e-xv1zxh_f.js} +2 -2
  179. package/.next/static/chunks/3j1y335o95mj7.js +1 -0
  180. package/.next/static/chunks/3obf9n3cxj99k.js +1 -0
  181. package/.next/static/chunks/{2upc9mkcc3nlq.js → 427qsc6kahxyo.js} +1 -1
  182. package/README.md +31 -14
  183. package/bin/port.mjs +11 -7
  184. package/bin/thursday.mjs +1 -1
  185. package/components/ui/radio-group.tsx +2 -2
  186. package/components/ui/shiny-text.tsx +3 -22
  187. package/docs/how-it-works.md +11 -3
  188. package/docs/images/first-run.png +0 -0
  189. package/docs/images/keeps-going.png +0 -0
  190. package/docs/images/two-minds.png +0 -0
  191. package/features/ai/components/model-browser.tsx +1 -1
  192. package/features/ai/components/model-picker.tsx +1 -1
  193. package/features/ai/guide.ts +9 -4
  194. package/features/ai/prompts/bot.prompt.ts +4 -1
  195. package/features/ai/tools/routine.tool.ts +28 -16
  196. package/features/bot/bot.runner.ts +3 -1
  197. package/features/bot/components/bot-mark.tsx +6 -1
  198. package/features/bot/components/bot-setting.tsx +2 -4
  199. package/features/bot/components/bot-tool.tsx +2 -0
  200. package/features/bot/components/room-list.tsx +12 -3
  201. package/features/bot/components/room-pill.tsx +26 -19
  202. package/features/config/components/config-setting.tsx +17 -5
  203. package/features/config/components/voice-key.tsx +38 -18
  204. package/features/connectors/mcp.const.ts +90 -59
  205. package/features/intro/components/intro.tsx +50 -27
  206. package/features/routine/components/routine-mark.tsx +4 -6
  207. package/features/routine/components/routine-setting.tsx +332 -143
  208. package/features/routine/routine.query.ts +24 -3
  209. package/features/routine/routine.schema.ts +55 -4
  210. package/features/settings/components/install-app.tsx +1 -4
  211. package/features/settings/components/setting-ui.tsx +1 -1
  212. package/features/skills/skills.discover.ts +2 -1
  213. package/features/skills/skills.query.ts +5 -1
  214. package/features/skills/skills.schema.ts +2 -0
  215. package/features/thursday/components/ascii-orb.tsx +39 -10
  216. package/features/thursday/components/connect-wave.tsx +284 -0
  217. package/features/thursday/components/side-captions.tsx +29 -17
  218. package/features/thursday/components/thursday-setting.tsx +2 -2
  219. package/features/thursday/components/thursday.tsx +130 -58
  220. package/features/thursday/components/voice-picker.tsx +1 -1
  221. package/features/thursday/components/write-line.tsx +2 -2
  222. package/features/thursday/open-work.ts +11 -3
  223. package/features/thursday/tool-line.ts +94 -12
  224. package/features/thursday/use-text-call.ts +3 -2
  225. package/features/thursday/use-thursday.ts +5 -2
  226. package/features/workspace/components/artifact-view.tsx +129 -7
  227. package/guide/bots.md +17 -5
  228. package/guide/calls.md +15 -7
  229. package/guide/setup.md +10 -2
  230. package/lib/desktop-notify.ts +3 -2
  231. package/package.json +1 -1
  232. package/scripts/bot-context.test.mts +1 -1
  233. package/skills/computer/SKILL.md +1 -0
  234. package/skills/interactive-page/SKILL.md +4 -0
  235. package/.next/server/chunks/[root-of-the-server]__0n-l_n_._.js +0 -3
  236. package/.next/server/chunks/[root-of-the-server]__0wvo2of._.js +0 -3
  237. package/.next/server/chunks/[root-of-the-server]__1e6aut3._.js +0 -3
  238. package/.next/server/chunks/[root-of-the-server]__1ziojdq._.js +0 -3
  239. package/.next/server/chunks/ssr/[root-of-the-server]__1srp8l5._.js +0 -3
  240. package/.next/server/chunks/ssr/_0mes4s0._.js +0 -3
  241. package/.next/server/chunks/ssr/_0ya0cb0._.js +0 -3
  242. package/.next/server/chunks/ssr/_0yto5s7._.js +0 -20
  243. package/.next/server/chunks/ssr/_10dw2nv._.js +0 -3
  244. package/.next/server/chunks/ssr/_1p0gxeo._.js +0 -164
  245. package/.next/server/chunks/ssr/_1r41yvh._.js +0 -3
  246. package/.next/server/chunks/ssr/_1r79ksv._.js +0 -3
  247. package/.next/server/chunks/ssr/features_bot_1ogjt52._.js +0 -3
  248. package/.next/server/chunks/ssr/node_modules_@base-ui_react_0b3d4m9._.js +0 -3
  249. package/.next/static/chunks/03omwz_vi2ndg.js +0 -1
  250. package/.next/static/chunks/0_1bqgd6wt_og.js +0 -1
  251. package/.next/static/chunks/0abgkzikaznxc.js +0 -1
  252. package/.next/static/chunks/0bddkpash59-o.js +0 -17
  253. package/.next/static/chunks/0c2-bzhy3a0ib.js +0 -1
  254. package/.next/static/chunks/0iilfb1ocevnx.js +0 -1
  255. package/.next/static/chunks/0imbhx-j830a_.css +0 -1
  256. package/.next/static/chunks/24h1y9dhy3m_2.js +0 -1
  257. package/.next/static/chunks/2be08eqxsfc1y.js +0 -1
  258. package/.next/static/chunks/2d3yhjtafgmtw.js +0 -1
  259. package/.next/static/chunks/2f_5saocsko_b.js +0 -1
  260. package/.next/static/chunks/2lwbr5vx0ixaj.js +0 -18
  261. package/.next/static/chunks/44kjmi5h_p8px.js +0 -1
  262. /package/.next/static/{UiOMPYpQ4fqGyu5PzS-7j → u8kJqShUAa49vY7BxAWsw}/_buildManifest.js +0 -0
  263. /package/.next/static/{UiOMPYpQ4fqGyu5PzS-7j → u8kJqShUAa49vY7BxAWsw}/_clientMiddlewareManifest.js +0 -0
  264. /package/.next/static/{UiOMPYpQ4fqGyu5PzS-7j → u8kJqShUAa49vY7BxAWsw}/_ssgManifest.js +0 -0
@@ -300,14 +300,8 @@ export function happenedIn(
300
300
  text: `stopped · ${label}`,
301
301
  });
302
302
  } else if (thread.status === "done") {
303
- out.push({
304
- rank: 3,
305
- at: own.name,
306
- from: own,
307
- to: [],
308
- sign: "done",
309
- text: `done · ${label}`,
310
- });
303
+ // A job's own ending is the card in the screen's left corner (artifact-view); the pill
304
+ // keeps its bubbles for what is still going on
311
305
  } else if (thread.status === "working") {
312
306
  // Words from the user already have their bubble.
313
307
  if (!fresh.some((line) => line.kind === "user")) {
@@ -525,24 +519,33 @@ export function Chip({
525
519
  pending > 0
526
520
  ? `${pending} ${pending === 1 ? "needs" : "need"} a reply`
527
521
  : "",
528
- unread > 0
529
- ? `${unread} new ${unread === 1 ? "result" : "results"}`
530
- : "",
531
522
  ]
532
523
  .filter(Boolean)
533
524
  .join(" · ")}
525
+ {unread > 0 && (
526
+ // What just arrived is what the card most needs you to see
527
+ <span className="text-brand">
528
+ {pending > 0 && " · "}
529
+ {unread} new {unread === 1 ? "result" : "results"}
530
+ </span>
531
+ )}
534
532
  </span>
535
533
  </p>
536
534
  {/* px-1: a row keeps its own 8px, so its mark lands on the rail while
537
535
  the shape it lights up on hover stays inside the card's corners */}
538
536
  <div className="max-h-[45vh] overflow-y-auto px-1.5 pb-2">
539
- {rows.map((thread) => (
540
- <ThreadRow
541
- key={thread.id}
542
- thread={thread}
543
- onPick={() => onPick(thread.id)}
544
- />
545
- ))}
537
+ {/* Keyed on growing, so the rows rise in after the card each time it opens */}
538
+ <div key={String(grown)}>
539
+ {rows.map((thread, index) => (
540
+ <div
541
+ key={thread.id}
542
+ style={{ animationDelay: `${120 + index * 60}ms` }}
543
+ className="animate-in duration-300 fill-mode-backwards fade-in slide-in-from-bottom-1"
544
+ >
545
+ <ThreadRow thread={thread} onPick={() => onPick(thread.id)} />
546
+ </div>
547
+ ))}
548
+ </div>
546
549
  </div>
547
550
  <span className="block h-1.5" />
548
551
  </div>
@@ -697,6 +700,9 @@ function Crew({
697
700
  /** The hand-off up, drawn over the face it points at. */
698
701
  bubble: Handoff | null;
699
702
  }) {
703
+ // While anyone is moving or waiting, the rest step back so it reads at a glance who is
704
+ const lit = (face: CrewFace) => face.awake || face.waiting || face.word;
705
+ const astir = crew.some(lit);
700
706
  return (
701
707
  <span className="flex min-w-0 shrink items-center">
702
708
  {crew.map((face, index) => {
@@ -707,7 +713,8 @@ function Crew({
707
713
  // these are not circles. Earlier faces sit on top, so the dot on a
708
714
  // waiting face is never covered by its neighbour.
709
715
  className={cn(
710
- "relative shrink-0 transition-[margin] duration-500 ease-out",
716
+ "relative shrink-0 transition-[margin,opacity] duration-500 ease-out",
717
+ astir && !lit(face) && "opacity-40",
711
718
  // A word to the left has already broken the shingle.
712
719
  index > 0 && !crew[index - 1].word && "-ml-2",
713
720
  face.standIn && "opacity-35",
@@ -208,7 +208,12 @@ function ConfigScreen({ screen }: { screen: keyof typeof SCREENS }) {
208
208
  * bots think with. The same cards, marks and dialogs as the screen above, so a key set
209
209
  * on the way in is the key Settings shows.
210
210
  */
211
- export function AccountsSetup() {
211
+ export function AccountsSetup({
212
+ /** Leave the voice key out: the screen asked for it already. */
213
+ withoutVoice = false,
214
+ }: {
215
+ withoutVoice?: boolean;
216
+ }) {
212
217
  const { data } = useServerRoute<ConfigStatus[]>(queryKey.config);
213
218
  const isSet = (key: string) => isConfigSet(data, key);
214
219
  const entries = (id: ConfigGroup["id"]) =>
@@ -217,11 +222,18 @@ export function AccountsSetup() {
217
222
  // already set. The rest are one press away, over the row rather than below it, so the
218
223
  // screen around it does not move
219
224
  const [more, setMore] = useState(false);
220
- const marks = [...entries("voice"), ...entries("text")];
225
+ const voice = entries("voice");
226
+ const marks = [...voice, ...entries("text")];
227
+ // Without the voice key the row is one shorter, not refilled from the rest
221
228
  const first = marks.filter(
222
- (entry, at) => at < ACCOUNTS_FIRST || isSet(entry.key),
229
+ (entry, at) =>
230
+ (at < ACCOUNTS_FIRST || isSet(entry.key)) &&
231
+ !(withoutVoice && voice.includes(entry)),
232
+ );
233
+ const rest = marks.filter(
234
+ (entry) =>
235
+ !first.includes(entry) && !(withoutVoice && voice.includes(entry)),
223
236
  );
224
- const rest = marks.filter((entry) => !first.includes(entry));
225
237
 
226
238
  return (
227
239
  <div className="flex flex-col gap-4">
@@ -395,7 +407,7 @@ function KeyRow({
395
407
  <span className="flex items-center gap-2 truncate text-sm font-medium">
396
408
  {entry.label}
397
409
  {entry.recommended && (
398
- <span className="rounded-full px-1.5 font-mono text-[9.5px] leading-4 font-normal text-brand ring-1 ring-brand">
410
+ <span className="rounded-full px-1.5 font-mono text-[9.5px] leading-4 font-normal text-muted-foreground ring-1 ring-border ring-inset">
399
411
  recommended
400
412
  </span>
401
413
  )}
@@ -33,6 +33,8 @@ export function VoiceKeys({
33
33
  autoFocus,
34
34
  /** Compact layout for the call screen. */
35
35
  dense = false,
36
+ /** The field alone, the provider's mark inside it: for a spot whose heading already names the key. */
37
+ plain = false,
36
38
  onCancel,
37
39
  onSaved,
38
40
  className,
@@ -40,6 +42,7 @@ export function VoiceKeys({
40
42
  ref?: Ref<VoiceKeysHandle>;
41
43
  autoFocus?: boolean;
42
44
  dense?: boolean;
45
+ plain?: boolean;
43
46
  onCancel?: () => void;
44
47
  onSaved?: (provider: "openai") => void;
45
48
  className?: string;
@@ -90,7 +93,7 @@ export function VoiceKeys({
90
93
 
91
94
  return (
92
95
  <div className={cn("w-full", dense ? "space-y-2" : "space-y-4", className)}>
93
- {dense && (
96
+ {dense && !plain && (
94
97
  <div className="flex h-6 items-center justify-between gap-2">
95
98
  <span className="px-0.5 font-mono text-[11px] text-muted-foreground">
96
99
  Voice key
@@ -115,6 +118,7 @@ export function VoiceKeys({
115
118
  saved={isConfigSet(config, LIVE_PROVIDER.apiKeyName)}
116
119
  autoFocus={autoFocus}
117
120
  dense={dense}
121
+ plain={plain}
118
122
  value={drafts[LIVE_PROVIDER.apiKeyName] ?? ""}
119
123
  ready={ready(LIVE_PROVIDER.apiKeyName)}
120
124
  saving={saving === LIVE_PROVIDER.apiKeyName}
@@ -137,16 +141,18 @@ export function VoiceKeys({
137
141
  </p>
138
142
  )}
139
143
 
140
- <p
141
- className={cn(
142
- "px-0.5 font-mono text-muted-foreground",
143
- dense ? "text-[11px]" : "pt-1 text-center text-[12.5px]",
144
- )}
145
- >
146
- {dense
147
- ? "Live voice and reasoning share this OpenAI API key."
148
- : "Live uses an OpenAI API key, billed separately from ChatGPT. Stored on this machine, in this app's database."}
149
- </p>
144
+ {!plain && (
145
+ <p
146
+ className={cn(
147
+ "px-0.5 font-mono text-muted-foreground",
148
+ dense ? "text-[11px]" : "pt-1 text-center text-[12.5px]",
149
+ )}
150
+ >
151
+ {dense
152
+ ? "Live voice and reasoning share this OpenAI API key."
153
+ : "Live uses an OpenAI API key, billed separately from ChatGPT. Stored on this machine, in this app's database."}
154
+ </p>
155
+ )}
150
156
  </div>
151
157
  );
152
158
  }
@@ -156,6 +162,7 @@ function KeyField({
156
162
  saved,
157
163
  autoFocus,
158
164
  dense,
165
+ plain,
159
166
  value,
160
167
  ready,
161
168
  saving,
@@ -166,6 +173,7 @@ function KeyField({
166
173
  saved: boolean;
167
174
  autoFocus?: boolean;
168
175
  dense?: boolean;
176
+ plain?: boolean;
169
177
  value: string;
170
178
  ready: boolean;
171
179
  saving: boolean;
@@ -174,20 +182,23 @@ function KeyField({
174
182
  }) {
175
183
  return (
176
184
  <div className="space-y-2">
177
- <span className="flex items-center gap-2 px-0.5 text-muted-foreground">
178
- <ProviderIcon provider={provider.id} className="size-4 shrink-0" />
179
- <span
180
- className={cn("font-mono", dense ? "text-[11px]" : "text-[12.5px]")}
181
- >
182
- {provider.label}
185
+ {!plain && (
186
+ <span className="flex items-center gap-2 px-0.5 text-muted-foreground">
187
+ <ProviderIcon provider={provider.id} className="size-4 shrink-0" />
188
+ <span
189
+ className={cn("font-mono", dense ? "text-[11px]" : "text-[12.5px]")}
190
+ >
191
+ {provider.label}
192
+ </span>
183
193
  </span>
184
- </span>
194
+ )}
185
195
 
186
196
  <KeyInput
187
197
  provider={provider}
188
198
  saved={saved}
189
199
  autoFocus={autoFocus}
190
200
  dense={dense}
201
+ mark={plain}
191
202
  value={value}
192
203
  ready={ready}
193
204
  saving={saving}
@@ -204,6 +215,7 @@ export function KeyInput({
204
215
  saved,
205
216
  autoFocus,
206
217
  dense,
218
+ mark,
207
219
  value,
208
220
  ready,
209
221
  saving,
@@ -214,6 +226,8 @@ export function KeyInput({
214
226
  saved: boolean;
215
227
  autoFocus?: boolean;
216
228
  dense?: boolean;
229
+ /** The provider's mark at the start of the field. */
230
+ mark?: boolean;
217
231
  value: string;
218
232
  ready: boolean;
219
233
  saving: boolean;
@@ -236,6 +250,12 @@ export function KeyInput({
236
250
  saved || ready ? "border-foreground bg-transparent" : "border-border",
237
251
  )}
238
252
  >
253
+ {mark && (
254
+ <ProviderIcon
255
+ provider={provider.id}
256
+ className="size-4 shrink-0 text-muted-foreground"
257
+ />
258
+ )}
239
259
  <Input
240
260
  autoFocus={autoFocus}
241
261
  type="password"
@@ -32,60 +32,64 @@ export type MCPPreset = MCPServerForm & {
32
32
  /**
33
33
  * First-party servers only, each one reachable at the URL below. Most sign in
34
34
  * with OAuth and ask nothing up front; a few want a token pasted into the
35
- * header (GitHub, whose server skips OAuth discovery), and the docs servers
36
- * need no account at all. Two stdio entries at the end drive a browser.
35
+ * header (GitHub, whose server skips OAuth discovery; Home Assistant, whose
36
+ * URL is also the user's own), and flight search and the docs servers need
37
+ * no account at all. Everyday services come first, developer tools after;
38
+ * two stdio entries at the end drive a browser.
37
39
  */
38
40
  export const MCP_PRESETS: MCPPreset[] = [
39
41
  {
40
- name: "notion",
41
- icon: "notion.so",
42
- description: "Pages, databases, and search",
43
- config: { url: "https://mcp.notion.com/mcp" },
42
+ name: "todoist",
43
+ icon: "todoist.com",
44
+ description: "Tasks, projects, and due dates",
45
+ config: { url: "https://ai.todoist.net/mcp" },
44
46
  },
45
47
  {
46
- name: "github",
47
- icon: "github.com",
48
- description: "Repos, issues, PRs — paste a personal access token",
48
+ name: "home-assistant",
49
+ icon: "home-assistant.io",
50
+ description:
51
+ "Lights, switches, and the rest of the house — your address and a long-lived token",
49
52
  config: {
50
- url: "https://api.githubcopilot.com/mcp/",
51
- headers: { Authorization: "Bearer <your-github-pat>" },
53
+ url: "http://homeassistant.local:8123/api/mcp",
54
+ headers: { Authorization: "Bearer <your-home-assistant-token>" },
52
55
  },
53
56
  },
54
57
  {
55
- name: "linear",
56
- icon: "linear.app",
57
- description: "Issues, projects, and cycles",
58
- config: { url: "https://mcp.linear.app/mcp" },
58
+ name: "kiwi",
59
+ icon: "kiwi.com",
60
+ description: "Flight search — no account",
61
+ config: { url: "https://mcp.kiwi.com" },
59
62
  },
60
63
  {
61
- name: "slack",
62
- icon: "slack.com",
63
- description: "Channels, messages, and search",
64
- config: { url: "https://mcp.slack.com/mcp" },
64
+ name: "zapier",
65
+ icon: "zapier.com",
66
+ description:
67
+ "Gmail, Google Calendar, and thousands of apps you connect there",
68
+ config: { url: "https://mcp.zapier.com/api/v1/connect" },
65
69
  },
66
70
  {
67
- name: "atlassian",
68
- icon: "atlassian.com",
69
- description: "Jira and Confluence",
70
- config: { url: "https://mcp.atlassian.com/v1/mcp" },
71
+ name: "notion",
72
+ icon: "notion.so",
73
+ description: "Pages, databases, and search",
74
+ config: { url: "https://mcp.notion.com/mcp" },
71
75
  },
72
76
  {
73
- name: "asana",
74
- icon: "asana.com",
75
- description: "Tasks, projects, and portfolios",
76
- config: { url: "https://mcp.asana.com/mcp" },
77
+ name: "canva",
78
+ icon: "canva.com",
79
+ description: "Create and manage designs",
80
+ config: { url: "https://mcp.canva.com/mcp" },
77
81
  },
78
82
  {
79
- name: "monday",
80
- icon: "monday.com",
81
- description: "Boards, items, and updates",
82
- config: { url: "https://mcp.monday.com/mcp" },
83
+ name: "slack",
84
+ icon: "slack.com",
85
+ description: "Channels, messages, and search",
86
+ config: { url: "https://mcp.slack.com/mcp" },
83
87
  },
84
88
  {
85
- name: "clickup",
86
- icon: "clickup.com",
87
- description: "Tasks, docs, and spaces",
88
- config: { url: "https://mcp.clickup.com/mcp" },
89
+ name: "box",
90
+ icon: "box.com",
91
+ description: "Files, folders, and content search",
92
+ config: { url: "https://mcp.box.com/mcp" },
89
93
  },
90
94
  {
91
95
  name: "airtable",
@@ -94,10 +98,10 @@ export const MCP_PRESETS: MCPPreset[] = [
94
98
  config: { url: "https://mcp.airtable.com/mcp" },
95
99
  },
96
100
  {
97
- name: "box",
98
- icon: "box.com",
99
- description: "Files, folders, and content search",
100
- config: { url: "https://mcp.box.com/mcp" },
101
+ name: "fireflies",
102
+ icon: "fireflies.ai",
103
+ description: "Meeting transcripts and summaries",
104
+ config: { url: "https://mcp.fireflies.ai/mcp" },
101
105
  },
102
106
  {
103
107
  name: "figma",
@@ -106,28 +110,34 @@ export const MCP_PRESETS: MCPPreset[] = [
106
110
  config: { url: "https://mcp.figma.com/mcp" },
107
111
  },
108
112
  {
109
- name: "canva",
110
- icon: "canva.com",
111
- description: "Create and manage designs",
112
- config: { url: "https://mcp.canva.com/mcp" },
113
+ name: "asana",
114
+ icon: "asana.com",
115
+ description: "Tasks, projects, and portfolios",
116
+ config: { url: "https://mcp.asana.com/mcp" },
113
117
  },
114
118
  {
115
- name: "sanity",
116
- icon: "sanity.io",
117
- description: "Structured content and datasets",
118
- config: { url: "https://mcp.sanity.io/mcp" },
119
+ name: "monday",
120
+ icon: "monday.com",
121
+ description: "Boards, items, and updates",
122
+ config: { url: "https://mcp.monday.com/mcp" },
119
123
  },
120
124
  {
121
- name: "webflow",
122
- icon: "webflow.com",
123
- description: "Sites, pages, and CMS collections",
124
- config: { url: "https://mcp.webflow.com/mcp" },
125
+ name: "clickup",
126
+ icon: "clickup.com",
127
+ description: "Tasks, docs, and spaces",
128
+ config: { url: "https://mcp.clickup.com/mcp" },
125
129
  },
126
130
  {
127
- name: "wix",
128
- icon: "wix.com",
129
- description: "Sites, stores, and bookings",
130
- config: { url: "https://mcp.wix.com/mcp" },
131
+ name: "linear",
132
+ icon: "linear.app",
133
+ description: "Issues, projects, and cycles",
134
+ config: { url: "https://mcp.linear.app/mcp" },
135
+ },
136
+ {
137
+ name: "atlassian",
138
+ icon: "atlassian.com",
139
+ description: "Jira and Confluence",
140
+ config: { url: "https://mcp.atlassian.com/v1/mcp" },
131
141
  },
132
142
  {
133
143
  name: "hubspot",
@@ -148,10 +158,22 @@ export const MCP_PRESETS: MCPPreset[] = [
148
158
  config: { url: "https://mcp.intercom.com/mcp" },
149
159
  },
150
160
  {
151
- name: "fireflies",
152
- icon: "fireflies.ai",
153
- description: "Meeting transcripts and summaries",
154
- config: { url: "https://mcp.fireflies.ai/mcp" },
161
+ name: "wix",
162
+ icon: "wix.com",
163
+ description: "Sites, stores, and bookings",
164
+ config: { url: "https://mcp.wix.com/mcp" },
165
+ },
166
+ {
167
+ name: "webflow",
168
+ icon: "webflow.com",
169
+ description: "Sites, pages, and CMS collections",
170
+ config: { url: "https://mcp.webflow.com/mcp" },
171
+ },
172
+ {
173
+ name: "sanity",
174
+ icon: "sanity.io",
175
+ description: "Structured content and datasets",
176
+ config: { url: "https://mcp.sanity.io/mcp" },
155
177
  },
156
178
  {
157
179
  name: "stripe",
@@ -177,6 +199,15 @@ export const MCP_PRESETS: MCPPreset[] = [
177
199
  description: "Spend, cards, and transactions",
178
200
  config: { url: "https://mcp.ramp.com/mcp" },
179
201
  },
202
+ {
203
+ name: "github",
204
+ icon: "github.com",
205
+ description: "Repos, issues, PRs — paste a personal access token",
206
+ config: {
207
+ url: "https://api.githubcopilot.com/mcp/",
208
+ headers: { Authorization: "Bearer <your-github-pat>" },
209
+ },
210
+ },
180
211
  {
181
212
  name: "vercel",
182
213
  icon: "vercel.com",
@@ -15,6 +15,12 @@ import { queryKey } from "@/app/api/query-key";
15
15
  import { Button } from "@/components/ui/button";
16
16
  import { ShinyText } from "@/components/ui/shiny-text";
17
17
  import { Switch } from "@/components/ui/switch";
18
+ import {
19
+ Tooltip,
20
+ TooltipContent,
21
+ TooltipProvider,
22
+ TooltipTrigger,
23
+ } from "@/components/ui/tooltip";
18
24
  import { ModelPicker } from "@/features/ai/components/model-picker";
19
25
  import {
20
26
  parseTextModel,
@@ -188,23 +194,33 @@ export function Intro({
188
194
  )}
189
195
  >
190
196
  {/* Her recorded voice, and the way to switch it off */}
191
- <button
192
- type="button"
193
- onClick={voice.toggle}
194
- aria-label={voice.muted ? "Let her speak" : "Mute her"}
195
- aria-pressed={voice.muted}
196
- className="absolute top-5 right-5 z-10 grid h-7.5 w-8 place-items-center rounded-[10px] text-muted-foreground ring-1 ring-border outline-none transition-colors hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50"
197
- >
198
- {voice.muted ? (
199
- <VolumeX className="size-[15px]" />
200
- ) : (
201
- <Volume2 className="size-[15px]" />
202
- )}
203
- </button>
197
+ <TooltipProvider>
198
+ <Tooltip>
199
+ <TooltipTrigger
200
+ render={
201
+ <Button
202
+ size="icon"
203
+ variant="ghost"
204
+ onClick={voice.toggle}
205
+ aria-label={voice.muted ? "Let her speak" : "Mute her"}
206
+ aria-pressed={voice.muted}
207
+ className="absolute top-5 right-5 z-10 text-muted-foreground hover:text-foreground"
208
+ />
209
+ }
210
+ >
211
+ {voice.muted ? <VolumeX /> : <Volume2 />}
212
+ </TooltipTrigger>
213
+ <TooltipContent side="bottom">
214
+ {voice.muted ? "Let her speak" : "Mute her voice"}
215
+ </TooltipContent>
216
+ </Tooltip>
217
+ </TooltipProvider>
204
218
 
205
219
  {/* The call screen's own column, so nothing moves when the intro lifts */}
206
220
  <div className="flex h-full flex-col items-center justify-center gap-5 pt-[7vh]">
207
- <div className="relative w-[min(28rem,72vw,52vh)]">
221
+ {/* The call screen's face box and `--face-bleed`: the canvas draws past the box, and
222
+ what stands beside her stands past the canvas */}
223
+ <div className="relative w-[min(20rem,52vw,37.5vh)] [--face-bleed:19.5%]">
208
224
  <button
209
225
  type="button"
210
226
  disabled={!last || !keyed}
@@ -222,7 +238,7 @@ export function Intro({
222
238
  failed={false}
223
239
  word={word}
224
240
  getSpectrum={step === "hello" ? demo.voice : voice.spectrum}
225
- className="w-full"
241
+ className="-m-(--face-bleed) w-[calc(100%+2*var(--face-bleed))] max-w-none"
226
242
  />
227
243
  </button>
228
244
 
@@ -237,7 +253,7 @@ export function Intro({
237
253
  // Where the caller's words go on a call: the caller's turn
238
254
  <div
239
255
  key={step}
240
- className="absolute top-1/2 left-full ml-1.5 flex w-[min(22rem,26vw)] -translate-y-1/2 animate-in flex-col gap-4 text-left fade-in slide-in-from-bottom-1 duration-300"
256
+ className="absolute top-1/2 left-full ml-[calc(var(--face-bleed)+0.375rem)] flex w-[min(22rem,26vw)] -translate-y-1/2 animate-in flex-col gap-4 text-left fade-in slide-in-from-bottom-1 duration-300"
241
257
  >
242
258
  {step === "key" && (
243
259
  <KeyTurn
@@ -268,7 +284,7 @@ export function Intro({
268
284
  under the button keep theirs: her face and the button stand still from step to step */}
269
285
  <div className="flex h-14 items-center gap-2 text-[13px] text-muted-foreground">
270
286
  {step === "hello" ? (
271
- <p className="max-w-130 text-[20px] leading-[1.5] text-balance text-foreground">
287
+ <p className="max-w-130 animate-in text-[20px] leading-[1.5] text-balance text-foreground duration-700 fill-mode-backwards fade-in slide-in-from-bottom-2">
272
288
  Just talk to her. She gets it done on this computer, and tells
273
289
  you when it is ready.
274
290
  </p>
@@ -294,7 +310,12 @@ export function Intro({
294
310
  } else if (last) leave(keyed);
295
311
  else setStep(STEPS[at + 1]);
296
312
  }}
297
- className="h-12 px-7 pl-8 text-[15px]"
313
+ // on the first screen it follows her line up, once
314
+ className={cn(
315
+ "h-12 px-7 pl-8 text-[15px]",
316
+ step === "hello" &&
317
+ "animate-in delay-300 duration-700 fill-mode-backwards fade-in slide-in-from-bottom-2",
318
+ )}
298
319
  >
299
320
  {step === "hello"
300
321
  ? "Start"
@@ -341,7 +362,7 @@ export function Intro({
341
362
  key={name}
342
363
  className={cn(
343
364
  "h-1.75 rounded-full transition-all duration-300",
344
- index === at ? "w-5.5 bg-brand" : "w-1.75",
365
+ index === at ? "w-5.5 bg-foreground" : "w-1.75",
345
366
  index < at && "bg-foreground/40",
346
367
  index > at && "bg-border",
347
368
  )}
@@ -416,10 +437,12 @@ const Fine = ({ children }: { children: React.ReactNode }) => (
416
437
  function Done({ children, tail }: { children: string; tail?: string }) {
417
438
  return (
418
439
  <p className="flex items-center gap-2.5 text-sm">
419
- <span className="grid size-5 shrink-0 place-items-center rounded-full bg-primary text-primary-foreground">
440
+ <span className="grid size-5 shrink-0 animate-in place-items-center rounded-full bg-primary text-primary-foreground duration-300 zoom-in-50">
420
441
  <Check className="size-3" />
421
442
  </span>
422
- {children}
443
+ <span className="animate-in delay-150 duration-300 fill-mode-backwards fade-in slide-in-from-left-1">
444
+ {children}
445
+ </span>
423
446
  {tail && (
424
447
  <span className="truncate font-mono text-[11px] text-muted-foreground/70">
425
448
  {tail}
@@ -440,14 +463,14 @@ function KeyTurn({ keyed, onSaved }: { keyed: boolean; onSaved: () => void }) {
440
463
  return (
441
464
  <>
442
465
  <Mine>Paste your OpenAI API key</Mine>
443
- <VoiceKeys dense autoFocus onSaved={onSaved} />
466
+ <VoiceKeys dense plain autoFocus onSaved={onSaved} />
444
467
  <a
445
468
  href="https://platform.openai.com/api-keys"
446
469
  target="_blank"
447
470
  rel="noreferrer"
448
471
  className="flex items-center gap-2.5 text-[13px] font-medium text-foreground no-underline"
449
472
  >
450
- <span className="flex h-7.5 items-center gap-1.5 rounded-full bg-muted px-3.5 transition-colors hover:bg-accent">
473
+ <span className="flex h-7.5 shrink-0 items-center gap-1.5 rounded-full bg-muted px-3.5 whitespace-nowrap transition-colors hover:bg-accent">
451
474
  Get a key
452
475
  <ArrowUpRight className="size-3.5" />
453
476
  </span>
@@ -591,7 +614,7 @@ function MicTurn({ mic }: { mic: MicState }) {
591
614
  </span>
592
615
  ) : (
593
616
  <span className="mt-1.5 flex items-center gap-2 text-[13px]">
594
- <Mic className="size-4 shrink-0 animate-pulse text-brand" />
617
+ <Mic className="size-4 shrink-0" />
595
618
  <ShinyText
596
619
  text={`Try it now: say "${wake.phrase}"`}
597
620
  speed={2.2}
@@ -659,8 +682,8 @@ function BotsTurn({
659
682
  })}
660
683
  </div>
661
684
  <Fine>
662
- Three to start is plenty. The rest wait in Settings › Bots, and you can
663
- make your own.
685
+ Every one comes along. Switch off any you will not use: Settings › Bots
686
+ has them all, and you can make your own.
664
687
  </Fine>
665
688
  </>
666
689
  );
@@ -686,7 +709,7 @@ function ModelsTurn() {
686
709
  return (
687
710
  <>
688
711
  <Mine>Pick what they think with</Mine>
689
- <AccountsSetup />
712
+ <AccountsSetup withoutVoice />
690
713
  <div className="flex items-center gap-2.5">
691
714
  <span className="shrink-0 text-[13px] text-muted-foreground">
692
715
  Bots think with
@@ -1,9 +1,7 @@
1
- import { Repeat } from "lucide-react";
1
+ import { type LucideIcon, Repeat } from "lucide-react";
2
2
 
3
3
  /**
4
- * The glyph of something that starts by itself: the settings nav, a routine's row, and
5
- * every job a routine opened, in Threads and in the room.
4
+ * The glyph of something that starts by itself: the settings nav, a routine's row, every
5
+ * job a routine opened, in Threads and in the room, and the call's line that sets one.
6
6
  */
7
- export function RoutineMark({ className }: { className?: string }) {
8
- return <Repeat className={className} aria-hidden="true" />;
9
- }
7
+ export const RoutineMark: LucideIcon = Repeat;