thursday-agent 0.2.0 → 0.3.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 (535) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +5 -2
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +5 -5
  5. package/.next/routes-manifest.json +24 -6
  6. package/.next/server/app/_global-error/page.js +2 -2
  7. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  8. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  9. package/.next/server/app/_global-error.html +1 -1
  10. package/.next/server/app/_global-error.rsc +2 -2
  11. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  12. package/.next/server/app/_global-error.segments/_full.segment.rsc +2 -2
  13. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  14. package/.next/server/app/_not-found/page.js +2 -2
  15. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  16. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next/server/app/_not-found.html +1 -1
  18. package/.next/server/app/_not-found.rsc +14 -14
  19. package/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  20. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +12 -12
  21. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  22. package/.next/server/app/api/artifact/route.js +3 -3
  23. package/.next/server/app/api/artifact/route.js.nft.json +1 -1
  24. package/.next/server/app/api/bot/memory/files/route/app-paths-manifest.json +3 -0
  25. package/.next/server/app/api/bot/memory/files/route.js +10 -0
  26. package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -0
  27. package/.next/server/app/api/bot/memory/files/route_client-reference-manifest.js +3 -0
  28. package/.next/server/app/api/bot/memory/route/app-paths-manifest.json +3 -0
  29. package/.next/server/app/api/bot/memory/route.js +14 -0
  30. package/.next/server/app/api/bot/{notes → memory}/route.js.nft.json +1 -1
  31. package/.next/server/app/api/bot/memory/route_client-reference-manifest.js +3 -0
  32. package/.next/server/app/api/bot/route.js +6 -6
  33. package/.next/server/app/api/bot/route.js.nft.json +1 -1
  34. package/.next/server/app/api/bot/task/[id]/route.js +8 -8
  35. package/.next/server/app/api/bot/task/[id]/route.js.nft.json +1 -1
  36. package/.next/server/app/api/bot/task/route.js +8 -8
  37. package/.next/server/app/api/bot/task/route.js.nft.json +1 -1
  38. package/.next/server/app/api/config/route.js +5 -5
  39. package/.next/server/app/api/config/route.js.nft.json +1 -1
  40. package/.next/server/app/api/events/route.js +4 -5
  41. package/.next/server/app/api/events/route.js.nft.json +1 -1
  42. package/.next/server/app/api/file/[...path]/route.js +3 -3
  43. package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
  44. package/.next/server/app/api/llm-model/catalog/route.js +9 -8
  45. package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
  46. package/.next/server/app/api/llm-model/credits/route/app-paths-manifest.json +3 -0
  47. package/.next/server/app/api/llm-model/credits/route/build-manifest.json +12 -0
  48. package/.next/server/app/api/llm-model/credits/route/server-reference-manifest.json +4 -0
  49. package/.next/server/app/api/llm-model/credits/route.js +16 -0
  50. package/.next/server/app/api/llm-model/credits/route.js.map +5 -0
  51. package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -0
  52. package/.next/server/app/api/llm-model/credits/route_client-reference-manifest.js +3 -0
  53. package/.next/server/app/api/llm-model/route.js +10 -8
  54. package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
  55. package/.next/server/app/api/llm-model/subscription/route/app-paths-manifest.json +3 -0
  56. package/.next/server/app/api/llm-model/subscription/route/build-manifest.json +12 -0
  57. package/.next/server/app/api/llm-model/subscription/route/server-reference-manifest.json +4 -0
  58. package/.next/server/app/api/llm-model/subscription/route.js +15 -0
  59. package/.next/server/app/api/llm-model/subscription/route.js.map +5 -0
  60. package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -0
  61. package/.next/server/app/api/llm-model/subscription/route_client-reference-manifest.js +3 -0
  62. package/.next/server/app/api/mcp/[name]/route.js +4 -4
  63. package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
  64. package/.next/server/app/api/mcp/oauth/callback/route.js +5 -5
  65. package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
  66. package/.next/server/app/api/mcp/route.js +4 -4
  67. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  68. package/.next/server/app/api/mcp/tools/route.js +4 -4
  69. package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
  70. package/.next/server/app/api/memory/edit/route/app-paths-manifest.json +3 -0
  71. package/.next/server/app/api/memory/edit/route/build-manifest.json +12 -0
  72. package/.next/server/app/api/memory/edit/route/server-reference-manifest.json +4 -0
  73. package/.next/server/app/api/memory/edit/route.js +24 -0
  74. package/.next/server/app/api/memory/edit/route.js.map +5 -0
  75. package/.next/server/app/api/memory/edit/route.js.nft.json +1 -0
  76. package/.next/server/app/api/memory/{tidy → edit}/route_client-reference-manifest.js +1 -1
  77. package/.next/server/app/api/memory/route.js +6 -7
  78. package/.next/server/app/api/memory/route.js.nft.json +1 -1
  79. package/.next/server/app/api/skills/[source]/[name]/route.js +3 -3
  80. package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
  81. package/.next/server/app/api/skills/route.js +3 -3
  82. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  83. package/.next/server/app/api/thursday/call/route.js +9 -7
  84. package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
  85. package/.next/server/app/api/thursday/skills/route.js +9 -7
  86. package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
  87. package/.next/server/app/api/thursday/tool-call/route.js +17 -15
  88. package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
  89. package/.next/server/app/api/workspace/route.js +4 -5
  90. package/.next/server/app/api/workspace/route.js.nft.json +1 -1
  91. package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
  92. package/.next/server/app/artifact/[...path]/page.js +5 -6
  93. package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
  94. package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
  95. package/.next/server/app/favicon.ico/route.js +3 -4
  96. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  97. package/.next/server/app/favicon.ico.body +0 -0
  98. package/.next/server/app/page/server-reference-manifest.json +57 -90
  99. package/.next/server/app/page.js +10 -10
  100. package/.next/server/app/page.js.nft.json +1 -1
  101. package/.next/server/app/page_client-reference-manifest.js +1 -1
  102. package/.next/server/app-paths-manifest.json +5 -2
  103. package/.next/server/chunks/[externals]__0dzuk6_._.js +3 -0
  104. package/.next/server/chunks/[root-of-the-server]__01dkp4a._.js +3 -0
  105. package/.next/server/chunks/[root-of-the-server]__02va5wz._.js +1 -1
  106. package/.next/server/chunks/[root-of-the-server]__02xjoto._.js +3 -0
  107. package/.next/server/chunks/[root-of-the-server]__04q--0f._.js +8 -0
  108. package/.next/server/chunks/[root-of-the-server]__0bl-e63._.js +3 -0
  109. package/.next/server/chunks/[root-of-the-server]__0ce8nf8._.js +5 -0
  110. package/.next/server/chunks/[root-of-the-server]__0ffbg85._.js +3 -0
  111. package/.next/server/chunks/[root-of-the-server]__0j7z0r3._.js +12 -0
  112. package/.next/server/chunks/[root-of-the-server]__0jq4ohy._.js +8 -0
  113. package/.next/server/chunks/[root-of-the-server]__0ls6_8y._.js +8 -0
  114. package/.next/server/chunks/[root-of-the-server]__0ncs9uu._.js +3 -0
  115. package/.next/server/chunks/[root-of-the-server]__0pa2n68._.js +3 -0
  116. package/.next/server/chunks/[root-of-the-server]__0q0vvt2._.js +5 -0
  117. package/.next/server/chunks/[root-of-the-server]__1-ga0kh._.js +5 -0
  118. package/.next/server/chunks/[root-of-the-server]__11n256_._.js +5 -0
  119. package/.next/server/chunks/[root-of-the-server]__12cabg9._.js +3 -0
  120. package/.next/server/chunks/[root-of-the-server]__1339jnv._.js +8 -0
  121. package/.next/server/chunks/[root-of-the-server]__15bjnu5._.js +5 -0
  122. package/.next/server/chunks/[root-of-the-server]__18_teo8._.js +8 -0
  123. package/.next/server/chunks/[root-of-the-server]__1_04mcf._.js +5 -0
  124. package/.next/server/chunks/[root-of-the-server]__1anc1p9._.js +3 -0
  125. package/.next/server/chunks/[root-of-the-server]__1ccon1w._.js +5 -5
  126. package/.next/server/chunks/[root-of-the-server]__1fizn31._.js +3 -0
  127. package/.next/server/chunks/[root-of-the-server]__1llj3-b._.js +3 -0
  128. package/.next/server/chunks/[root-of-the-server]__1orn-5b._.js +11 -0
  129. package/.next/server/chunks/[root-of-the-server]__1p8m3f4._.js +3 -0
  130. package/.next/server/chunks/[root-of-the-server]__1wa1k7r._.js +8 -0
  131. package/.next/server/chunks/_0-73of8._.js +5 -5
  132. package/.next/server/chunks/_0-fjql4._.js +26 -0
  133. package/.next/server/chunks/_00xh_q0._.js +27 -0
  134. package/.next/server/chunks/{_0pppbb5._.js → _03kp0jp._.js} +163 -36
  135. package/.next/server/chunks/_051qh8u._.js +3 -0
  136. package/.next/server/chunks/{node_modules_next_04q-o58._.js → _05dla15._.js} +2 -2
  137. package/.next/server/chunks/_05h2cii._.js +1 -1
  138. package/.next/server/chunks/_0c9j6rb._.js +49 -0
  139. package/.next/server/chunks/_0fzqt2y._.js +130 -0
  140. package/.next/server/chunks/{_0vnutv9._.js → _0ioz3nd._.js} +1 -1
  141. package/.next/server/chunks/_0iwfby5._.js +3 -0
  142. package/.next/server/chunks/_0mecv0f._.js +26 -0
  143. package/.next/server/chunks/_0mh8j4m._.js +3 -0
  144. package/.next/server/chunks/_0n74zsf._.js +4 -4
  145. package/.next/server/chunks/_0obojvv._.js +3 -0
  146. package/.next/server/chunks/_0recufk._.js +49 -0
  147. package/.next/server/chunks/_0rnf-6n._.js +4 -4
  148. package/.next/server/chunks/_0sb7tlz._.js +3 -0
  149. package/.next/server/chunks/_0u10wmw._.js +5 -5
  150. package/.next/server/chunks/_0u4aiy2._.js +3 -0
  151. package/.next/server/chunks/_0wp4le3._.js +1 -1
  152. package/.next/server/chunks/_0y5fg-x._.js +1 -1
  153. package/.next/server/chunks/_1230wbx._.js +10 -0
  154. package/.next/server/chunks/_12nheja._.js +3 -0
  155. package/.next/server/chunks/_13510xh._.js +3 -0
  156. package/.next/server/chunks/_15hq89o._.js +1 -1
  157. package/.next/server/chunks/_17l5ygt._.js +49 -0
  158. package/.next/server/chunks/_18-1wcc._.js +5 -5
  159. package/.next/server/chunks/_1_5mnq4._.js +4 -4
  160. package/.next/server/chunks/_1_9mjx-._.js +3 -0
  161. package/.next/server/chunks/_1d0j961._.js +6 -0
  162. package/.next/server/chunks/_1d5zmy8._.js +1 -1
  163. package/.next/server/chunks/_1drgfx4._.js +4 -4
  164. package/.next/server/chunks/_1f46eie._.js +3 -0
  165. package/.next/server/chunks/_1h5_8sk._.js +3 -0
  166. package/.next/server/chunks/_1hdnxb7._.js +3 -0
  167. package/.next/server/chunks/_1j8k_j5._.js +3 -0
  168. package/.next/server/chunks/_1krd9rf._.js +3 -0
  169. package/.next/server/chunks/_1nc0eui._.js +11 -0
  170. package/.next/server/chunks/_1nula3v._.js +3 -0
  171. package/.next/server/chunks/_1pf4qe-._.js +3 -0
  172. package/.next/server/chunks/_1php4eq._.js +1 -1
  173. package/.next/server/chunks/_1qxheoa._.js +3 -0
  174. package/.next/server/chunks/_1x6kd1r._.js +3 -0
  175. package/.next/server/chunks/_1yuv4vp._.js +1 -1
  176. package/.next/server/chunks/_1zjrdag._.js +3 -0
  177. package/.next/server/chunks/_next-internal_server_app_api_bot_memory_files_route_actions_1_xd_8f.js +3 -0
  178. package/.next/server/chunks/_next-internal_server_app_api_bot_memory_route_actions_1a8xfr7.js +3 -0
  179. package/.next/server/chunks/_next-internal_server_app_api_llm-model_credits_route_actions_0v424pb.js +3 -0
  180. package/.next/server/chunks/_next-internal_server_app_api_llm-model_subscription_route_actions_0-udcm5.js +3 -0
  181. package/.next/server/chunks/_next-internal_server_app_api_memory_edit_route_actions_02sfnmr.js +3 -0
  182. package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
  183. package/.next/server/chunks/features_0rpfxbk._.js +3 -0
  184. package/.next/server/chunks/features_1qf3cei._.js +3 -0
  185. package/.next/server/chunks/features_bot_0zt92oh._.js +3 -0
  186. package/.next/server/chunks/features_bot_bot_query_ts_12mgkl2._.js +3 -0
  187. package/.next/server/chunks/features_memory_1uqvksn._.js +6 -0
  188. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0a1rd09._.js → node_modules_consola_dist_chunks_prompt_mjs_0i7cvml._.js} +1 -1
  189. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_06067w6._.js → node_modules_consola_dist_chunks_prompt_mjs_0icqaqw._.js} +1 -1
  190. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_096ais_._.js → node_modules_consola_dist_chunks_prompt_mjs_0r28-sm._.js} +1 -1
  191. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_02s3hjt._.js → node_modules_consola_dist_chunks_prompt_mjs_0v_7drq._.js} +1 -1
  192. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1xoq2rl._.js +3 -0
  193. package/.next/server/chunks/{node_modules_consola_dist_chunks_prompt_mjs_0tvkfgh._.js → node_modules_consola_dist_chunks_prompt_mjs_1y49572._.js} +1 -1
  194. package/.next/server/chunks/ssr/[root-of-the-server]__09q9med._.js +1 -1
  195. package/.next/server/chunks/ssr/{[root-of-the-server]__1qfqvmg._.js → [root-of-the-server]__0_69wqr._.js} +2 -2
  196. package/.next/server/chunks/ssr/[root-of-the-server]__0hg5gib._.js +1 -1
  197. package/.next/server/chunks/ssr/[root-of-the-server]__0i8ie7f._.js +3 -0
  198. package/.next/server/chunks/ssr/{[root-of-the-server]__0jsmmb-._.js → [root-of-the-server]__0mz33a7._.js} +1 -1
  199. package/.next/server/chunks/ssr/[root-of-the-server]__0tbkdlx._.js +3 -0
  200. package/.next/server/chunks/ssr/[root-of-the-server]__0vnmv-s._.js +3 -0
  201. package/.next/server/chunks/ssr/{[root-of-the-server]__0f6s48x._.js → [root-of-the-server]__0xpcv9w._.js} +3 -3
  202. package/.next/server/chunks/ssr/[root-of-the-server]__0xtssj3._.js +1 -1
  203. package/.next/server/chunks/ssr/[root-of-the-server]__0zkv7z_._.js +3 -0
  204. package/.next/server/chunks/ssr/[root-of-the-server]__10-dz5o._.js +57 -0
  205. package/.next/server/chunks/ssr/[root-of-the-server]__15_uhip._.js +3 -0
  206. package/.next/server/chunks/ssr/[root-of-the-server]__16jc196._.js +1 -1
  207. package/.next/server/chunks/ssr/{[root-of-the-server]__0m1b5ky._.js → [root-of-the-server]__1cyjywu._.js} +1 -1
  208. package/.next/server/chunks/ssr/[root-of-the-server]__1eh7hid._.js +3 -0
  209. package/.next/server/chunks/ssr/[root-of-the-server]__1gtlw6v._.js +43 -0
  210. package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +1 -1
  211. package/.next/server/chunks/ssr/[root-of-the-server]__1ox7hbk._.js +4 -0
  212. package/.next/server/chunks/ssr/[root-of-the-server]__1pduf5n._.js +3 -0
  213. package/.next/server/chunks/ssr/{_1mznrxk._.js → _001_ms1._.js} +3 -3
  214. package/.next/server/chunks/ssr/_01_6m32._.js +3 -0
  215. package/.next/server/chunks/ssr/_01n3o0m._.js +3 -0
  216. package/.next/server/chunks/ssr/_07uchnc._.js +3 -0
  217. package/.next/server/chunks/ssr/_0ap2fzn._.js +3 -0
  218. package/.next/server/chunks/ssr/_0c-ql2m._.js +3 -0
  219. package/.next/server/chunks/ssr/_0h42fze._.js +3 -0
  220. package/.next/server/chunks/ssr/_0h7zdni._.js +1 -1
  221. package/.next/server/chunks/ssr/_0i_6q1w._.js +41 -0
  222. package/.next/server/chunks/ssr/_0kbkmar._.js +3 -0
  223. package/.next/server/chunks/ssr/{_077-g6s._.js → _0mb8zc8._.js} +3 -3
  224. package/.next/server/chunks/ssr/_0mo2w06._.js +3 -0
  225. package/.next/server/chunks/ssr/{_13punpm._.js → _0q0zan6._.js} +2 -2
  226. package/.next/server/chunks/ssr/{_040sk32._.js → _0u2jv_x._.js} +76 -82
  227. package/.next/server/chunks/ssr/_0y0tiht._.js +4 -0
  228. package/.next/server/chunks/ssr/_0zhoc3r._.js +3 -0
  229. package/.next/server/chunks/ssr/_1-teee5._.js +3 -3
  230. package/.next/server/chunks/ssr/_13fke0x._.js +3 -0
  231. package/.next/server/chunks/ssr/{_1lip4th._.js → _13z3f85._.js} +2 -2
  232. package/.next/server/chunks/ssr/_14_lw10._.js +3 -0
  233. package/.next/server/chunks/ssr/_15j2_8f._.js +3 -0
  234. package/.next/server/chunks/ssr/{_09ja_w1._.js → _1eoqg_g._.js} +2 -2
  235. package/.next/server/chunks/ssr/_1sji22n._.js +3 -0
  236. package/.next/server/chunks/ssr/_1spzm8z._.js +3 -0
  237. package/.next/server/chunks/ssr/{_0x_nu5e._.js → _1vi_uo7._.js} +2 -2
  238. package/.next/server/chunks/ssr/_1vyd1ji._.js +1 -1
  239. package/.next/server/chunks/ssr/{_1m3bsfr._.js → _1xvgn77._.js} +2 -2
  240. package/.next/server/chunks/ssr/_20kd_n0._.js +42 -0
  241. package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +3 -0
  242. package/.next/server/chunks/ssr/features_config_components_config-setting_tsx_0xfwqdg._.js +1 -1
  243. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  244. package/.next/server/chunks/ssr/lib_date-like_ts_02is0pw._.js +3 -0
  245. package/.next/server/chunks/ssr/{node_modules_18_dk7_._.js → node_modules_063xwpp._.js} +1 -1
  246. package/.next/server/chunks/ssr/{node_modules_01qnv-a._.js → node_modules_0bvraul._.js} +1 -1
  247. package/.next/server/chunks/ssr/{node_modules_0n1kj4p._.js → node_modules_0eu1va6._.js} +1 -1
  248. package/.next/server/chunks/ssr/{node_modules_0kl7-tn._.js → node_modules_0h50b8n._.js} +1 -1
  249. package/.next/server/chunks/ssr/{node_modules_0_n241r._.js → node_modules_0h5hnr1._.js} +1 -1
  250. package/.next/server/chunks/ssr/{node_modules_0lrjk5t._.js → node_modules_0h7u60n._.js} +1 -1
  251. package/.next/server/chunks/ssr/{node_modules_0ndvxub._.js → node_modules_0lm7_vm._.js} +1 -1
  252. package/.next/server/chunks/ssr/{node_modules_13r44jq._.js → node_modules_0mwym0g._.js} +1 -1
  253. package/.next/server/chunks/ssr/{node_modules_17lrt3y._.js → node_modules_0rjg6t1._.js} +1 -1
  254. package/.next/server/chunks/ssr/node_modules_0x3-ply._.js +3 -0
  255. package/.next/server/chunks/ssr/{node_modules_004qlr4._.js → node_modules_0y_cded._.js} +1 -1
  256. package/.next/server/chunks/ssr/{node_modules_0kxz4gt._.js → node_modules_0z5tsu8._.js} +1 -1
  257. package/.next/server/chunks/ssr/{node_modules_0iz1rzd._.js → node_modules_0zp9r92._.js} +1 -1
  258. package/.next/server/chunks/ssr/{node_modules_19dp7ko._.js → node_modules_1-hc6uv._.js} +1 -1
  259. package/.next/server/chunks/ssr/{node_modules_1q0p85z._.js → node_modules_113tz0l._.js} +1 -1
  260. package/.next/server/chunks/ssr/{node_modules_1toafc-._.js → node_modules_124bue5._.js} +1 -1
  261. package/.next/server/chunks/ssr/node_modules_17bwo43._.js +55 -0
  262. package/.next/server/chunks/ssr/{node_modules_08d8bke._.js → node_modules_1_n3ztw._.js} +1 -1
  263. package/.next/server/chunks/ssr/{node_modules_1dwguw_._.js → node_modules_1bc7gu8._.js} +1 -1
  264. package/.next/server/chunks/ssr/{node_modules_20yebtj._.js → node_modules_1bgb6-f._.js} +1 -1
  265. package/.next/server/chunks/ssr/{node_modules_1n5ygqe._.js → node_modules_1h4a4pp._.js} +1 -1
  266. package/.next/server/chunks/ssr/{node_modules_04n7mjs._.js → node_modules_1i7u1ia._.js} +1 -1
  267. package/.next/server/chunks/ssr/{node_modules_1hn8z1f._.js → node_modules_1juq3hs._.js} +1 -1
  268. package/.next/server/chunks/ssr/{node_modules_1xyd7ov._.js → node_modules_1l0qtdu._.js} +1 -1
  269. package/.next/server/chunks/ssr/{node_modules_0m93zeg._.js → node_modules_1t9cyee._.js} +1 -1
  270. package/.next/server/chunks/ssr/{node_modules_09xhur7._.js → node_modules_1uuj9x1._.js} +1 -1
  271. package/.next/server/chunks/ssr/{node_modules_1hd-5tv._.js → node_modules_1vqb6po._.js} +1 -1
  272. package/.next/server/chunks/ssr/{node_modules_1zu988w._.js → node_modules_1xx28h1._.js} +1 -1
  273. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0m9a4y0._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0d3skpl._.js} +1 -1
  274. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_13w_wy9._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0i44kk5._.js} +1 -1
  275. package/.next/server/chunks/ssr/node_modules_@vercel_oidc_dist_0qe30a_._.js +3 -0
  276. package/.next/server/chunks/ssr/{node_modules_next_dist_0w6mzq5._.js → node_modules_next_dist_0drixxt._.js} +4 -4
  277. package/.next/server/chunks/ssr/node_modules_next_dist_1n3w9lb._.js +2 -2
  278. package/.next/server/instrumentation.js +1 -1
  279. package/.next/server/middleware-build-manifest.js +3 -3
  280. package/.next/server/pages/404.html +1 -1
  281. package/.next/server/pages/500.html +1 -1
  282. package/.next/server/server-reference-manifest.js +1 -1
  283. package/.next/server/server-reference-manifest.json +58 -91
  284. package/.next/static/chunks/{31ttnjt6g35a0.js → 0ev4hyghq0mbb.js} +1 -1
  285. package/.next/static/chunks/0r5qcctujhxli.js +1 -0
  286. package/.next/static/chunks/0tmgrnlje14-s.js +3 -0
  287. package/.next/static/chunks/0x79tx04dxbpp.js +1 -0
  288. package/.next/static/chunks/0xzgvc0fkdb6t.js +1 -0
  289. package/.next/static/chunks/10teww7wt-f6h.js +1 -0
  290. package/.next/static/chunks/14hgtsjg-kael.css +1 -0
  291. package/.next/static/chunks/17donp69y4do7.js +40 -0
  292. package/.next/static/chunks/{3_h9txt3tq8t8.js → 1dkjg87pxk0n9.js} +1 -1
  293. package/.next/static/chunks/1ekc7ul556kbl.js +1 -0
  294. package/.next/static/chunks/{3ard_noqapnw1.js → 1grhjo7a5a0_-.js} +1 -1
  295. package/.next/static/chunks/1kq8h8wi_e0bc.js +53 -0
  296. package/.next/static/chunks/1okqiww3pnwka.js +1 -0
  297. package/.next/static/chunks/26-pr2c9ro222.js +1 -0
  298. package/.next/static/chunks/{18vhba44l1fc9.js → 280t0_e99i276.js} +1 -1
  299. package/.next/static/chunks/28cizcdaabd4h.js +1 -0
  300. package/.next/static/chunks/2a0k6jlq0_v1u.js +2 -0
  301. package/.next/static/chunks/2fzs9fm8yarvq.js +1 -0
  302. package/.next/static/chunks/2h0sd2vumhb15.js +1 -0
  303. package/.next/static/chunks/2hw2f6zmt7fmt.js +1 -0
  304. package/.next/static/chunks/2t4vhi3m8ahya.js +1 -0
  305. package/.next/static/chunks/2tigdtz3dpcy8.js +1 -0
  306. package/.next/static/chunks/3111pj5wnjgos.js +1 -0
  307. package/.next/static/chunks/{07kl352c-lvcz.js → 3agpgkwaa321p.js} +1 -1
  308. package/.next/static/chunks/3cfi_q6d_wtci.js +1 -0
  309. package/.next/static/chunks/3ok9_x74rip3v.js +1 -0
  310. package/.next/static/chunks/3xk7yqgomx7yq.js +1 -0
  311. package/.next/static/chunks/3xpbkl66ssib5.js +38 -0
  312. package/.next/static/media/favicon.37o1pjldwi2uc.ico +0 -0
  313. package/README.md +6 -4
  314. package/app/api/bot/memory/files/route.ts +14 -0
  315. package/app/api/bot/memory/route.ts +5 -0
  316. package/app/api/events/app-event.ts +3 -5
  317. package/app/api/llm-model/credits/route.ts +11 -0
  318. package/app/api/llm-model/route.ts +6 -1
  319. package/app/api/llm-model/subscription/route.ts +11 -0
  320. package/app/api/memory/edit/route.ts +7 -0
  321. package/app/api/memory/route.ts +21 -0
  322. package/app/api/query-key.ts +19 -4
  323. package/bin/thursday.mjs +3 -0
  324. package/components/ui/shiny-text.tsx +48 -134
  325. package/database/migrations/{20260909060813_optimal_sabretooth → 20260910173049_numerous_spirit}/migration.sql +24 -34
  326. package/database/migrations/{20260909060813_optimal_sabretooth → 20260910173049_numerous_spirit}/snapshot.json +71 -145
  327. package/database/migrations/20260911030320_opposite_jack_power/migration.sql +1 -0
  328. package/database/migrations/{20260909063821_bitter_tenebrous → 20260911030320_opposite_jack_power}/snapshot.json +57 -184
  329. package/features/ai/chatgpt.ts +617 -0
  330. package/features/ai/components/chatgpt-sign-in.tsx +66 -0
  331. package/features/ai/components/model-picker.tsx +17 -1
  332. package/features/ai/components/provider-icon.tsx +1 -0
  333. package/features/ai/load-tools.ts +58 -42
  334. package/features/ai/model.schema.ts +232 -34
  335. package/features/ai/model.ts +169 -24
  336. package/features/ai/prompts/bot.prompt.ts +156 -65
  337. package/features/ai/prompts/memory-edit.prompt.ts +33 -0
  338. package/features/ai/prompts/prompt-helper.ts +45 -26
  339. package/features/ai/prompts/thursday.prompt.ts +40 -17
  340. package/features/ai/tools/bot.tool.ts +35 -52
  341. package/features/ai/tools/connected.ts +17 -4
  342. package/features/ai/tools/memory.tool.ts +133 -72
  343. package/features/ai/tools/search.tool.ts +136 -47
  344. package/features/ai/tools/tool-name.ts +2 -5
  345. package/features/ai/tools/workspace.tool.ts +2 -1
  346. package/features/bot/bot.action.ts +8 -18
  347. package/features/bot/bot.memory.ts +89 -0
  348. package/features/bot/bot.query.ts +37 -57
  349. package/features/bot/bot.run.ts +507 -241
  350. package/features/bot/bot.runner.ts +410 -114
  351. package/features/bot/bot.schema.ts +79 -19
  352. package/features/bot/bot.seed.ts +89 -16
  353. package/features/bot/components/bot-mark.tsx +122 -20
  354. package/features/bot/components/bot-room.tsx +159 -130
  355. package/features/bot/components/bot-setting.tsx +790 -402
  356. package/features/bot/components/bot-tool.tsx +2 -6
  357. package/features/bot/components/mark-palette.tsx +54 -0
  358. package/features/bot/components/path-chips.tsx +1 -1
  359. package/features/bot/components/task-badge.tsx +26 -6
  360. package/features/bot/components/task-reply.tsx +22 -33
  361. package/features/bot/components/task-setting.tsx +20 -17
  362. package/features/bot/task.query.ts +215 -28
  363. package/features/bot/task.store.ts +53 -9
  364. package/features/config/components/config-setting.tsx +265 -34
  365. package/features/config/components/models-badge.tsx +42 -0
  366. package/features/config/config.action.ts +12 -0
  367. package/features/config/config.const.ts +38 -2
  368. package/features/connectors/mcp.manager.ts +10 -2
  369. package/features/intro/components/intro.tsx +47 -46
  370. package/features/memory/components/memory-edit.tsx +338 -0
  371. package/features/memory/components/memory-setting.tsx +75 -77
  372. package/features/memory/memory.action.ts +3 -43
  373. package/features/memory/memory.edit.ts +87 -0
  374. package/features/memory/memory.query.ts +24 -42
  375. package/features/memory/memory.schema.ts +29 -106
  376. package/features/settings/components/settings.tsx +2 -0
  377. package/features/settings/settings.alert.ts +3 -1
  378. package/features/thursday/components/ascii-orb.tsx +106 -90
  379. package/features/thursday/components/boot.tsx +7 -0
  380. package/features/thursday/components/call-log.tsx +219 -92
  381. package/features/thursday/components/face.tsx +6 -4
  382. package/features/thursday/components/tab-state.tsx +97 -0
  383. package/features/thursday/components/thursday-mark.tsx +4 -1
  384. package/features/thursday/components/thursday-setting.tsx +17 -47
  385. package/features/thursday/components/thursday.tsx +208 -95
  386. package/features/thursday/thursday.action.ts +12 -17
  387. package/features/thursday/thursday.query.ts +61 -70
  388. package/features/thursday/thursday.schema.ts +20 -7
  389. package/features/thursday/tool-line.ts +85 -11
  390. package/features/thursday/use-thursday.ts +86 -59
  391. package/features/workspace/components/file-view.tsx +4 -2
  392. package/features/workspace/workspace.ts +110 -26
  393. package/lib/limits.ts +1 -1
  394. package/lib/oauth-page.ts +27 -0
  395. package/lib/realtime/openai.ts +2 -2
  396. package/lib/realtime/realtime.protocol.ts +1 -1
  397. package/lib/realtime/realtime.tap.ts +122 -0
  398. package/lib/sandbox.ts +59 -36
  399. package/package.json +1 -1
  400. package/skills/browser/SKILL.md +55 -34
  401. package/.next/server/app/api/bot/notes/route/app-paths-manifest.json +0 -3
  402. package/.next/server/app/api/bot/notes/route.js +0 -14
  403. package/.next/server/app/api/bot/notes/route_client-reference-manifest.js +0 -3
  404. package/.next/server/app/api/memory/tidy/route/app-paths-manifest.json +0 -3
  405. package/.next/server/app/api/memory/tidy/route.js +0 -22
  406. package/.next/server/app/api/memory/tidy/route.js.nft.json +0 -1
  407. package/.next/server/chunks/0d-b_next_dist_server_app-render_after-task-async-storage_external_0-exn7p.js +0 -3
  408. package/.next/server/chunks/[root-of-the-server]__0-npkh_._.js +0 -3
  409. package/.next/server/chunks/[root-of-the-server]__005var5._.js +0 -3
  410. package/.next/server/chunks/[root-of-the-server]__019dx9f._.js +0 -5
  411. package/.next/server/chunks/[root-of-the-server]__02_2sm-._.js +0 -5
  412. package/.next/server/chunks/[root-of-the-server]__04_8spk._.js +0 -3
  413. package/.next/server/chunks/[root-of-the-server]__05luceg._.js +0 -3
  414. package/.next/server/chunks/[root-of-the-server]__06t77t4._.js +0 -11
  415. package/.next/server/chunks/[root-of-the-server]__0bed6hu._.js +0 -8
  416. package/.next/server/chunks/[root-of-the-server]__0cro2dz._.js +0 -5
  417. package/.next/server/chunks/[root-of-the-server]__0dupqt7._.js +0 -5
  418. package/.next/server/chunks/[root-of-the-server]__0emr6go._.js +0 -3
  419. package/.next/server/chunks/[root-of-the-server]__0le4d-p._.js +0 -3
  420. package/.next/server/chunks/[root-of-the-server]__0m8ywyo._.js +0 -10
  421. package/.next/server/chunks/[root-of-the-server]__0nfuugi._.js +0 -5
  422. package/.next/server/chunks/[root-of-the-server]__0p32jfz._.js +0 -12
  423. package/.next/server/chunks/[root-of-the-server]__0phdgco._.js +0 -8
  424. package/.next/server/chunks/[root-of-the-server]__0wvgrsl._.js +0 -3
  425. package/.next/server/chunks/[root-of-the-server]__10qshki._.js +0 -3
  426. package/.next/server/chunks/[root-of-the-server]__11j52t0._.js +0 -5
  427. package/.next/server/chunks/[root-of-the-server]__1692zrv._.js +0 -3
  428. package/.next/server/chunks/[root-of-the-server]__16thabu._.js +0 -3
  429. package/.next/server/chunks/[root-of-the-server]__173nhwf._.js +0 -8
  430. package/.next/server/chunks/[root-of-the-server]__1_aoyr9._.js +0 -3
  431. package/.next/server/chunks/[root-of-the-server]__1bn73ih._.js +0 -10
  432. package/.next/server/chunks/[root-of-the-server]__1bqhuh9._.js +0 -8
  433. package/.next/server/chunks/[root-of-the-server]__1cqizfq._.js +0 -5
  434. package/.next/server/chunks/[root-of-the-server]__1e-ci13._.js +0 -3
  435. package/.next/server/chunks/[root-of-the-server]__1fboj2j._.js +0 -3
  436. package/.next/server/chunks/[root-of-the-server]__1lr7j9t._.js +0 -5
  437. package/.next/server/chunks/[root-of-the-server]__1oli73c._.js +0 -3
  438. package/.next/server/chunks/[root-of-the-server]__1txedx6._.js +0 -8
  439. package/.next/server/chunks/[root-of-the-server]__1untqv8._.js +0 -5
  440. package/.next/server/chunks/[root-of-the-server]__1zgpz1w._.js +0 -3
  441. package/.next/server/chunks/_00_584r._.js +0 -293
  442. package/.next/server/chunks/_012yhfm._.js +0 -39
  443. package/.next/server/chunks/_06mxlgn._.js +0 -3
  444. package/.next/server/chunks/_07rtlxv._.js +0 -3
  445. package/.next/server/chunks/_0a351g7._.js +0 -6
  446. package/.next/server/chunks/_0kjjmag._.js +0 -3
  447. package/.next/server/chunks/_0nn99o2._.js +0 -3
  448. package/.next/server/chunks/_0zuzmt_._.js +0 -39
  449. package/.next/server/chunks/_106co2o._.js +0 -3
  450. package/.next/server/chunks/_12bbv5i._.js +0 -100
  451. package/.next/server/chunks/_15q1pig._.js +0 -3
  452. package/.next/server/chunks/_19wg5yn._.js +0 -138
  453. package/.next/server/chunks/_1bt95p9._.js +0 -55
  454. package/.next/server/chunks/_1cbk7ms._.js +0 -26
  455. package/.next/server/chunks/_1drv2et._.js +0 -40
  456. package/.next/server/chunks/_1ic3bew._.js +0 -5
  457. package/.next/server/chunks/_1mxn_lb._.js +0 -3
  458. package/.next/server/chunks/_1rysr-r._.js +0 -10
  459. package/.next/server/chunks/_1u3rwbr._.js +0 -3
  460. package/.next/server/chunks/_1x7p319._.js +0 -6
  461. package/.next/server/chunks/_next-internal_server_app_api_bot_notes_route_actions_0qcsyu7.js +0 -3
  462. package/.next/server/chunks/_next-internal_server_app_api_memory_tidy_route_actions_0_cfuom.js +0 -3
  463. package/.next/server/chunks/features_bot_task_query_ts_11xuzl3._.js +0 -3
  464. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0gzvgfv._.js +0 -3
  465. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0vorg6w._.js +0 -3
  466. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1tpvamf._.js +0 -3
  467. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1vmaq9f._.js +0 -3
  468. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1y9-wcr._.js +0 -3
  469. package/.next/server/chunks/ssr/[externals]_node_crypto_1kn79-7._.js +0 -3
  470. package/.next/server/chunks/ssr/[root-of-the-server]__0iuoa-r._.js +0 -3
  471. package/.next/server/chunks/ssr/[root-of-the-server]__0un-erv._.js +0 -4
  472. package/.next/server/chunks/ssr/[root-of-the-server]__1-mnhyd._.js +0 -92
  473. package/.next/server/chunks/ssr/[root-of-the-server]__1bww500._.js +0 -3
  474. package/.next/server/chunks/ssr/[root-of-the-server]__1vcdast._.js +0 -3
  475. package/.next/server/chunks/ssr/[root-of-the-server]__1xwnu-h._.js +0 -3
  476. package/.next/server/chunks/ssr/_0-mc7ro._.js +0 -3
  477. package/.next/server/chunks/ssr/_01p5r6-._.js +0 -3
  478. package/.next/server/chunks/ssr/_03webdr._.js +0 -3
  479. package/.next/server/chunks/ssr/_09-0k3z._.js +0 -3
  480. package/.next/server/chunks/ssr/_0f_b_9s._.js +0 -3
  481. package/.next/server/chunks/ssr/_0fafgvl._.js +0 -3
  482. package/.next/server/chunks/ssr/_0sf1isi._.js +0 -3
  483. package/.next/server/chunks/ssr/_0su6_08._.js +0 -3
  484. package/.next/server/chunks/ssr/_0ti51xc._.js +0 -3
  485. package/.next/server/chunks/ssr/_0w5bgak._.js +0 -3
  486. package/.next/server/chunks/ssr/_0y84qo6._.js +0 -3
  487. package/.next/server/chunks/ssr/_1ciyoo3._.js +0 -3
  488. package/.next/server/chunks/ssr/_1e-dgz0._.js +0 -4
  489. package/.next/server/chunks/ssr/_1fq7zgb._.js +0 -3
  490. package/.next/server/chunks/ssr/_1g6l4uj._.js +0 -3
  491. package/.next/server/chunks/ssr/_1ruzehw._.js +0 -3
  492. package/.next/server/chunks/ssr/features_bot_components_bot-setting_tsx_01ajft6._.js +0 -4
  493. package/.next/server/chunks/ssr/features_config_components_14jqgy4._.js +0 -3
  494. package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +0 -4
  495. package/.next/server/chunks/ssr/node_modules_zod_v4_classic_external_15fmmz3.js +0 -55
  496. package/.next/static/chunks/05b6nx_o3_f_n.js +0 -1
  497. package/.next/static/chunks/0dmns-gi0raa4.js +0 -1
  498. package/.next/static/chunks/0f9sq-7q_yxyl.js +0 -1
  499. package/.next/static/chunks/0oc4_xoia77dg.css +0 -1
  500. package/.next/static/chunks/0pheo8m3a-rzi.js +0 -1
  501. package/.next/static/chunks/19i8h2t-5evjx.js +0 -1
  502. package/.next/static/chunks/1c5fcj473025q.js +0 -1
  503. package/.next/static/chunks/1pi_hbmjw-f-z.js +0 -1
  504. package/.next/static/chunks/1twt6k09vg4np.js +0 -1
  505. package/.next/static/chunks/1ytnip8wdng93.js +0 -1
  506. package/.next/static/chunks/28gotuisldmom.js +0 -1
  507. package/.next/static/chunks/2998p0y7k1c8z.js +0 -3
  508. package/.next/static/chunks/29gyb3nubj51j.js +0 -53
  509. package/.next/static/chunks/2l233xfycjpd8.js +0 -1
  510. package/.next/static/chunks/2p5n7pa3c40t-.js +0 -2
  511. package/.next/static/chunks/2ru1q132af7h7.js +0 -1
  512. package/.next/static/chunks/2t8wqycwg3y04.js +0 -2
  513. package/.next/static/chunks/2uxum7qbyeyxd.js +0 -1
  514. package/.next/static/chunks/32z_ypgtchw48.js +0 -1
  515. package/.next/static/chunks/33pyjb3xd1iez.js +0 -1
  516. package/.next/static/chunks/3omud5xunrk1f.js +0 -1
  517. package/.next/static/chunks/42dnrkbph9e-1.js +0 -1
  518. package/.next/static/media/favicon.2vob68tjqpejf.ico +0 -0
  519. package/database/migrations/20260909063821_bitter_tenebrous/migration.sql +0 -1
  520. package/features/ai/prompts/memory-tidy.prompt.ts +0 -129
  521. package/features/ai/tools/tidy.tool.ts +0 -22
  522. package/features/bot/bot.notes.ts +0 -120
  523. package/features/memory/components/memory-tidy.tsx +0 -395
  524. package/features/memory/memory.tidy.ts +0 -332
  525. package/features/memory/tidy.query.ts +0 -135
  526. package/features/thursday/use-call-title.ts +0 -31
  527. /package/.next/server/app/api/bot/{notes → memory/files}/route/build-manifest.json +0 -0
  528. /package/.next/server/app/api/bot/{notes → memory/files}/route/server-reference-manifest.json +0 -0
  529. /package/.next/server/app/api/bot/{notes → memory/files}/route.js.map +0 -0
  530. /package/.next/server/app/api/{memory/tidy → bot/memory}/route/build-manifest.json +0 -0
  531. /package/.next/server/app/api/{memory/tidy → bot/memory}/route/server-reference-manifest.json +0 -0
  532. /package/.next/server/app/api/{memory/tidy → bot/memory}/route.js.map +0 -0
  533. /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → x1cCqY0kWpR_VcYx_lWO5}/_buildManifest.js +0 -0
  534. /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → x1cCqY0kWpR_VcYx_lWO5}/_clientMiddlewareManifest.js +0 -0
  535. /package/.next/static/{YR4NWcDDuzwLbTTU7U7Vr → x1cCqY0kWpR_VcYx_lWO5}/_ssgManifest.js +0 -0
@@ -0,0 +1,617 @@
1
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
2
+ import { createServer, type Server } from "node:http";
3
+ import { createOpenAI } from "@ai-sdk/openai";
4
+ import { type LanguageModel, wrapLanguageModel } from "ai";
5
+ import { formatDistanceToNowStrict } from "date-fns";
6
+ import { z } from "zod";
7
+ import { appEvents } from "@/app/api/events/app-event.server";
8
+ import { CHATGPT_SIGN_IN, CHATGPT_USAGE_HIGH } from "@/config";
9
+ import { readConfig, writeConfig } from "@/features/config/config.query";
10
+ import { logger } from "@/lib/logger";
11
+ import { oauthPage } from "@/lib/oauth-page";
12
+ import { publicError } from "@/lib/public-error";
13
+ import { createKeyedLock } from "@/lib/queue";
14
+ import { errorToString } from "@/lib/utils";
15
+ import { type SubscriptionUsage, TEXT_MODEL_PROVIDERS } from "./model.schema";
16
+
17
+ /**
18
+ * ChatGPT as a text provider: the Codex usage a ChatGPT plan carries, in place of an API key.
19
+ * Signing in is OpenAI's OAuth for the Codex CLI, the way other agent apps reach a plan. The
20
+ * model is the Responses API behind chatgpt.com, which takes less than api.openai.com does;
21
+ * `codexFetch` is the difference.
22
+ */
23
+
24
+ /** The Codex CLI's public OAuth client. */
25
+ const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
26
+ const AUTHORIZE_URL = "https://auth.openai.com/oauth/authorize";
27
+ const TOKEN_URL = "https://auth.openai.com/oauth/token";
28
+ /** The only redirect registered for that client, so the answer comes back to this port and no other. */
29
+ const CALLBACK_PORT = 1455;
30
+ const REDIRECT_URI = `http://localhost:${CALLBACK_PORT}/auth/callback`;
31
+ const SCOPE = "openid profile email offline_access";
32
+ const CODEX_URL = "https://chatgpt.com/backend-api/codex";
33
+ /** The access token's claim naming the account and its plan. */
34
+ const AUTH_CLAIM = "https://api.openai.com/auth";
35
+ /** How this app names itself to OpenAI. */
36
+ const ORIGINATOR = "thursday";
37
+
38
+ const { apiKeyName: SIGN_IN_KEY, label: LABEL } = TEXT_MODEL_PROVIDERS.chatgpt;
39
+ const NOT_SIGNED_IN = `${LABEL} is not signed in — sign in from Config.`;
40
+
41
+ /** What a sign-in leaves in config. Tokens: it never reaches the browser. */
42
+ const SignInSchema = z.object({
43
+ access: z.string().min(1),
44
+ refresh: z.string().min(1),
45
+ /** When the access token runs out, epoch ms. */
46
+ expires: z.number(),
47
+ accountId: z.string().min(1),
48
+ /** The plan as the token names it ("free", "plus", "pro"); null when it does not say. */
49
+ plan: z.string().nullable(),
50
+ });
51
+ type SignIn = z.infer<typeof SignInSchema>;
52
+
53
+ type Pinned = {
54
+ __chatgptListener?: Server | null;
55
+ __chatgptRenewal?: ReturnType<typeof createKeyedLock>;
56
+ };
57
+
58
+ /** Pinned to globalThis: next dev reloads this module, and the listener holding the port and the renewal lane must be the originals. */
59
+ const pinned = globalThis as Pinned;
60
+
61
+ /**
62
+ * One renewal at a time. A refresh token is spent by using it, so two requests renewing at
63
+ * once leave one holding a token OpenAI has already retired — which signs the app out.
64
+ */
65
+ const renewal = (pinned.__chatgptRenewal ??= createKeyedLock());
66
+
67
+ /**
68
+ * Starts a sign-in and returns the page to send the browser to. The answer comes back to the
69
+ * loopback port, so the app listens there until it arrives or `CHATGPT_SIGN_IN.waitMs` passes;
70
+ * starting again replaces a sign-in still waiting.
71
+ */
72
+ export async function startChatGptSignIn(): Promise<string> {
73
+ stopListening();
74
+ const verifier = randomBytes(32).toString("base64url");
75
+ const state = randomBytes(16).toString("hex");
76
+
77
+ const server = createServer((request, response) => {
78
+ void answerCallback(request.url ?? "/", { state, verifier }).then(
79
+ (page) => {
80
+ response.writeHead(page.status, {
81
+ "content-type": "text/html; charset=utf-8",
82
+ });
83
+ response.end(
84
+ oauthPage(page.title, page.detail, {
85
+ autoClose: page.status === 200,
86
+ }),
87
+ () => {
88
+ if (page.last && pinned.__chatgptListener === server) {
89
+ stopListening();
90
+ }
91
+ },
92
+ );
93
+ },
94
+ );
95
+ });
96
+
97
+ await new Promise<void>((resolve, reject) => {
98
+ server.once("error", reject);
99
+ server.listen(CALLBACK_PORT, "127.0.0.1", () => resolve());
100
+ }).catch((cause: NodeJS.ErrnoException) => {
101
+ publicError(
102
+ cause.code === "EADDRINUSE"
103
+ ? `Port ${CALLBACK_PORT} is in use — another ChatGPT or Codex sign-in is waiting. Finish or close it, then try again.`
104
+ : `Could not wait for the sign-in: ${errorToString(cause)}`,
105
+ );
106
+ });
107
+ pinned.__chatgptListener = server;
108
+ // A page nobody finishes must not hold the port for the life of the process
109
+ setTimeout(() => {
110
+ if (pinned.__chatgptListener === server) stopListening();
111
+ }, CHATGPT_SIGN_IN.waitMs).unref();
112
+
113
+ const url = new URL(AUTHORIZE_URL);
114
+ url.search = new URLSearchParams({
115
+ response_type: "code",
116
+ client_id: CLIENT_ID,
117
+ redirect_uri: REDIRECT_URI,
118
+ scope: SCOPE,
119
+ code_challenge: createHash("sha256").update(verifier).digest("base64url"),
120
+ code_challenge_method: "S256",
121
+ state,
122
+ // What the Codex CLI asks for: the account's organizations in the token, and the short consent page
123
+ id_token_add_organizations: "true",
124
+ codex_cli_simplified_flow: "true",
125
+ originator: ORIGINATOR,
126
+ }).toString();
127
+ return url.toString();
128
+ }
129
+
130
+ function stopListening() {
131
+ const server = pinned.__chatgptListener;
132
+ pinned.__chatgptListener = null;
133
+ server?.close();
134
+ server?.closeIdleConnections();
135
+ }
136
+
137
+ type CallbackPage = {
138
+ status: number;
139
+ title: string;
140
+ detail: string;
141
+ /** The sign-in is over, however it went, so the port is let go. */
142
+ last: boolean;
143
+ };
144
+
145
+ async function answerCallback(
146
+ path: string,
147
+ flow: { state: string; verifier: string },
148
+ ): Promise<CallbackPage> {
149
+ const url = new URL(path, REDIRECT_URI);
150
+ // A browser asks the same port for a favicon; that is not the answer
151
+ if (url.pathname !== "/auth/callback") {
152
+ return { status: 404, title: "Not found", detail: "", last: false };
153
+ }
154
+
155
+ const error = url.searchParams.get("error");
156
+ if (error) {
157
+ const description = url.searchParams.get("error_description");
158
+ return {
159
+ status: 400,
160
+ title: "Sign-in failed",
161
+ detail: description ? `${error}: ${description}` : error,
162
+ last: true,
163
+ };
164
+ }
165
+ const code = url.searchParams.get("code");
166
+ if (!code || url.searchParams.get("state") !== flow.state) {
167
+ return {
168
+ status: 400,
169
+ title: "Sign-in failed",
170
+ detail:
171
+ "This is not the sign-in that was started. Start again from Config.",
172
+ last: false,
173
+ };
174
+ }
175
+
176
+ try {
177
+ const signIn = await requestToken({
178
+ grant_type: "authorization_code",
179
+ code,
180
+ code_verifier: flow.verifier,
181
+ redirect_uri: REDIRECT_URI,
182
+ });
183
+ await writeConfig(SIGN_IN_KEY, JSON.stringify(signIn));
184
+ // The screen that opened this page is waiting on it
185
+ appEvents.emit({ type: "config" });
186
+ return {
187
+ status: 200,
188
+ title: "Signed in to ChatGPT",
189
+ detail: "Bots can run on your plan now. This window closes itself.",
190
+ last: true,
191
+ };
192
+ } catch (cause) {
193
+ logger.warn({ cause }, "chatgpt sign-in failed");
194
+ return {
195
+ status: 502,
196
+ title: "Sign-in failed",
197
+ detail: errorToString(cause),
198
+ last: true,
199
+ };
200
+ }
201
+ }
202
+
203
+ /** The account, plan and expiry an access token carries. Read, never verified: it came from OpenAI over TLS. */
204
+ function claimsOf(access: string) {
205
+ try {
206
+ const payload = JSON.parse(
207
+ Buffer.from(access.split(".")[1] ?? "", "base64url").toString("utf8"),
208
+ ) as Record<string, unknown>;
209
+ const auth = (payload[AUTH_CLAIM] ?? {}) as Record<string, unknown>;
210
+ return {
211
+ accountId:
212
+ typeof auth.chatgpt_account_id === "string"
213
+ ? auth.chatgpt_account_id
214
+ : null,
215
+ plan:
216
+ typeof auth.chatgpt_plan_type === "string"
217
+ ? auth.chatgpt_plan_type
218
+ : null,
219
+ expires: typeof payload.exp === "number" ? payload.exp * 1000 : null,
220
+ };
221
+ } catch {
222
+ return { accountId: null, plan: null, expires: null };
223
+ }
224
+ }
225
+
226
+ /** One call to OpenAI's token endpoint — the first exchange, or a renewal. A refusal comes back in its own words. */
227
+ async function requestToken(fields: Record<string, string>): Promise<SignIn> {
228
+ const response = await fetch(TOKEN_URL, {
229
+ method: "POST",
230
+ headers: {
231
+ accept: "application/json",
232
+ "content-type": "application/x-www-form-urlencoded",
233
+ },
234
+ body: new URLSearchParams({ client_id: CLIENT_ID, ...fields }),
235
+ }).catch((cause: unknown) => {
236
+ logger.warn({ cause }, "chatgpt token endpoint unreachable");
237
+ publicError("Could not reach ChatGPT's sign-in");
238
+ });
239
+ const body = (await response.json().catch(() => null)) as {
240
+ access_token?: string;
241
+ refresh_token?: string;
242
+ expires_in?: number;
243
+ error?: string | { message?: string; code?: string };
244
+ error_description?: string;
245
+ } | null;
246
+
247
+ const access = body?.access_token;
248
+ if (!response.ok || !access) {
249
+ const error = body?.error;
250
+ const said =
251
+ typeof error === "string"
252
+ ? [error, body?.error_description].filter(Boolean).join(": ")
253
+ : (error?.message ?? error?.code ?? "");
254
+ publicError(
255
+ `ChatGPT's sign-in answered ${response.status}${said ? `: ${said}` : ""}`,
256
+ );
257
+ }
258
+
259
+ const claims = claimsOf(access);
260
+ const refresh = body?.refresh_token ?? fields.refresh_token;
261
+ if (!claims.accountId || !refresh) {
262
+ publicError("ChatGPT's sign-in came back without an account");
263
+ }
264
+ const expiresIn = body?.expires_in;
265
+ return {
266
+ access,
267
+ refresh,
268
+ expires:
269
+ typeof expiresIn === "number"
270
+ ? Date.now() + expiresIn * 1000
271
+ : (claims.expires ?? Date.now()),
272
+ accountId: claims.accountId,
273
+ plan: claims.plan,
274
+ };
275
+ }
276
+
277
+ async function readSignIn(): Promise<SignIn | null> {
278
+ const stored = await readConfig(SIGN_IN_KEY);
279
+ if (!stored) return null;
280
+ try {
281
+ return SignInSchema.parse(JSON.parse(stored));
282
+ } catch {
283
+ return null;
284
+ }
285
+ }
286
+
287
+ const expiring = (signIn: SignIn) =>
288
+ signIn.expires - CHATGPT_SIGN_IN.renewBeforeMs <= Date.now();
289
+
290
+ /** The sign-in to send a request with, renewed first when its access token is about to run out. */
291
+ async function currentSignIn(): Promise<SignIn> {
292
+ const stored = await readSignIn();
293
+ if (!stored) publicError(NOT_SIGNED_IN);
294
+ if (!expiring(stored)) return stored;
295
+
296
+ return renewal(SIGN_IN_KEY, async () => {
297
+ // Another request may have renewed it while this one waited its turn
298
+ const latest = await readSignIn();
299
+ if (!latest) publicError(NOT_SIGNED_IN);
300
+ if (!expiring(latest)) return latest;
301
+ try {
302
+ const renewed = await requestToken({
303
+ grant_type: "refresh_token",
304
+ refresh_token: latest.refresh,
305
+ });
306
+ await writeConfig(SIGN_IN_KEY, JSON.stringify(renewed));
307
+ return renewed;
308
+ } catch (cause) {
309
+ publicError(
310
+ `Could not renew the ${LABEL} sign-in (${errorToString(cause)}). If it keeps failing, sign in again from Config.`,
311
+ );
312
+ }
313
+ });
314
+ }
315
+
316
+ /** The plan the signed-in account is on, for the settings screen; null when nobody is signed in. */
317
+ export async function readChatGptPlan(): Promise<string | null> {
318
+ return (await readSignIn())?.plan ?? null;
319
+ }
320
+
321
+ /** Answers the sign-in's own token, so no second key is needed to say what is left. */
322
+ const USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
323
+
324
+ type UsageWindow = {
325
+ used_percent?: number;
326
+ /** Epoch seconds. */
327
+ reset_at?: number;
328
+ reset_after_seconds?: number;
329
+ };
330
+
331
+ /**
332
+ * How much of the plan the signed-in account has used, by its tightest window — Free has one of
333
+ * 30 days, a paid plan a short one and a weekly one — and when that window frees up; null when
334
+ * nobody is signed in. A sign-in the plan turns away is a state of the sign-in, not a failed
335
+ * read, so it comes back as `refused` in OpenAI's words.
336
+ */
337
+ export async function readChatGptUsage(): Promise<SubscriptionUsage | null> {
338
+ if (!(await readSignIn())) return null;
339
+ let signIn: SignIn;
340
+ try {
341
+ signIn = await currentSignIn();
342
+ } catch (cause) {
343
+ return { refused: errorToString(cause) };
344
+ }
345
+
346
+ const response = await fetch(USAGE_URL, {
347
+ headers: {
348
+ accept: "application/json",
349
+ authorization: `Bearer ${signIn.access}`,
350
+ "chatgpt-account-id": signIn.accountId,
351
+ originator: ORIGINATOR,
352
+ },
353
+ }).catch((cause: unknown) => {
354
+ logger.warn({ cause }, "chatgpt usage unreachable");
355
+ publicError("Could not reach ChatGPT");
356
+ });
357
+ const body = (await response.json().catch(() => null)) as {
358
+ plan_type?: string;
359
+ rate_limit?: {
360
+ limit_reached?: boolean;
361
+ primary_window?: UsageWindow | null;
362
+ secondary_window?: UsageWindow | null;
363
+ };
364
+ detail?: string;
365
+ error?: { message?: string };
366
+ } | null;
367
+
368
+ if (response.status === 401 || response.status === 403) {
369
+ return {
370
+ refused:
371
+ body?.error?.message ??
372
+ body?.detail ??
373
+ `ChatGPT answered ${response.status}`,
374
+ };
375
+ }
376
+ if (!response.ok) publicError(`ChatGPT answered ${response.status}`);
377
+
378
+ const tightest = [
379
+ body?.rate_limit?.primary_window,
380
+ body?.rate_limit?.secondary_window,
381
+ ]
382
+ .filter(
383
+ (window): window is UsageWindow =>
384
+ typeof window?.used_percent === "number",
385
+ )
386
+ .sort((a, b) => (b.used_percent ?? 0) - (a.used_percent ?? 0))[0];
387
+ if (!tightest)
388
+ publicError("ChatGPT did not say how much of the plan is used");
389
+
390
+ const usedPercent = Math.round(tightest.used_percent ?? 0);
391
+ const spent = Boolean(body?.rate_limit?.limit_reached);
392
+ const resetsAt = tightest.reset_at
393
+ ? tightest.reset_at * 1000
394
+ : tightest.reset_after_seconds
395
+ ? Date.now() + tightest.reset_after_seconds * 1000
396
+ : null;
397
+ return {
398
+ plan: body?.plan_type ?? signIn.plan,
399
+ usedPercent,
400
+ resetsAt: resetsAt ? new Date(resetsAt).toISOString() : null,
401
+ spent,
402
+ high: spent || usedPercent >= CHATGPT_USAGE_HIGH,
403
+ };
404
+ }
405
+
406
+ /** A Codex model on the signed-in plan. */
407
+ export function chatGptModel(modelId: string): LanguageModel {
408
+ const chatgpt = createOpenAI({
409
+ baseURL: CODEX_URL,
410
+ // The sdk builds no request without a key; codexFetch signs every one with the sign-in instead
411
+ apiKey: "signed-in",
412
+ headers: { "OpenAI-Beta": "responses=experimental" },
413
+ fetch: codexFetch,
414
+ });
415
+ // One key for the model's life — a run, its compactions, its answers — so its calls share a prompt cache
416
+ const promptCacheKey = randomUUID();
417
+ return wrapLanguageModel({
418
+ model: chatgpt.responses(modelId),
419
+ middleware: {
420
+ // Unstored: the sdk then sends whole items rather than ids the backend never kept, and asks
421
+ // for the encrypted reasoning a stored thread needs to be replayed
422
+ transformParams: async ({ params }) => ({
423
+ ...params,
424
+ providerOptions: {
425
+ ...params.providerOptions,
426
+ openai: {
427
+ promptCacheKey,
428
+ ...params.providerOptions?.openai,
429
+ store: false,
430
+ },
431
+ },
432
+ }),
433
+ },
434
+ });
435
+ }
436
+
437
+ type CodexBody = {
438
+ input?: { role?: string; content?: string | { text?: string }[] }[];
439
+ instructions?: string;
440
+ stream?: boolean;
441
+ store?: boolean;
442
+ max_output_tokens?: number;
443
+ };
444
+
445
+ /**
446
+ * What the ChatGPT backend asks of a Responses call that api.openai.com does not, applied to
447
+ * every request the sdk makes: signed with the current sign-in, so a job that outlives a token
448
+ * renews it; always streamed and never stored; the system prompt as `instructions`, which it
449
+ * refuses a call without; no output cap.
450
+ */
451
+ async function codexFetch(
452
+ input: RequestInfo | URL,
453
+ init?: RequestInit,
454
+ ): Promise<Response> {
455
+ const signIn = await currentSignIn();
456
+ const headers = new Headers(init?.headers);
457
+ headers.set("authorization", `Bearer ${signIn.access}`);
458
+ headers.set("chatgpt-account-id", signIn.accountId);
459
+ headers.set("originator", ORIGINATOR);
460
+
461
+ if (
462
+ init?.method !== "POST" ||
463
+ typeof init.body !== "string" ||
464
+ !String(input).endsWith("/responses")
465
+ ) {
466
+ return fetch(input, { ...init, headers });
467
+ }
468
+
469
+ const body = JSON.parse(init.body) as CodexBody;
470
+ const streamed = body.stream === true;
471
+ const system: string[] = [];
472
+ body.input = (body.input ?? []).filter((item) => {
473
+ if (item.role !== "system" && item.role !== "developer") return true;
474
+ system.push(
475
+ typeof item.content === "string"
476
+ ? item.content
477
+ : (item.content ?? []).map((part) => part.text ?? "").join("\n"),
478
+ );
479
+ return false;
480
+ });
481
+ body.instructions =
482
+ [body.instructions, ...system].filter(Boolean).join("\n\n") ||
483
+ "You are a helpful assistant.";
484
+ body.store = false;
485
+ body.stream = true;
486
+ body.max_output_tokens = undefined;
487
+
488
+ headers.set("accept", "text/event-stream");
489
+ const response = await fetch(input, {
490
+ ...init,
491
+ headers,
492
+ body: JSON.stringify(body),
493
+ });
494
+ if (response.status === 429) return usageLimitOf(response);
495
+ if (streamed || !response.ok) return response;
496
+ return finishedOf(response);
497
+ }
498
+
499
+ /**
500
+ * A 429 is either a rate limit a moment fixes or the plan's usage spent until its window resets
501
+ * — hours, or on Free a month. The second becomes a 402 in the backend's words: the sdk does not
502
+ * retry it, and a job fails with it (model.ts modelFailureOf) instead of waking every few
503
+ * minutes to be refused again.
504
+ */
505
+ async function usageLimitOf(response: Response): Promise<Response> {
506
+ const text = await response.text();
507
+ let said: {
508
+ code?: string;
509
+ type?: string;
510
+ plan_type?: string;
511
+ resets_at?: number;
512
+ resets_in_seconds?: number;
513
+ } = {};
514
+ try {
515
+ said = (JSON.parse(text) as { error?: typeof said }).error ?? {};
516
+ } catch {}
517
+
518
+ if (
519
+ !/usage_limit_reached|usage_not_included/.test(`${said.code} ${said.type}`)
520
+ ) {
521
+ const retryAfter = response.headers.get("retry-after");
522
+ return new Response(text, {
523
+ status: 429,
524
+ headers: {
525
+ "content-type": "application/json",
526
+ ...(retryAfter ? { "retry-after": retryAfter } : {}),
527
+ },
528
+ });
529
+ }
530
+
531
+ const resets = said.resets_at
532
+ ? said.resets_at * 1000
533
+ : said.resets_in_seconds
534
+ ? Date.now() + said.resets_in_seconds * 1000
535
+ : null;
536
+ const plan = said.plan_type ? ` on the ${said.plan_type} plan` : "";
537
+ const when = resets
538
+ ? ` It resets in ${formatDistanceToNowStrict(resets)}.`
539
+ : "";
540
+ return Response.json(
541
+ {
542
+ error: {
543
+ message: `${LABEL} usage limit reached${plan}.${when}`,
544
+ code: said.code ?? said.type,
545
+ },
546
+ },
547
+ { status: 402 },
548
+ );
549
+ }
550
+
551
+ type StreamEvent = {
552
+ type?: string;
553
+ item?: unknown;
554
+ response?: {
555
+ output?: unknown[];
556
+ error?: { code?: string; message?: string } | null;
557
+ };
558
+ code?: string;
559
+ message?: string;
560
+ };
561
+
562
+ /**
563
+ * The one JSON body a caller that did not stream expects, read off the stream the backend
564
+ * insists on. The finished event's `output` arrives empty, so items are gathered as each
565
+ * completes. A failed response becomes an error in its own words: a 400 when it was the
566
+ * context, which a compaction fixes, a 500 otherwise.
567
+ */
568
+ async function finishedOf(stream: Response): Promise<Response> {
569
+ const items: unknown[] = [];
570
+ let finished: { output?: unknown[] } | null = null;
571
+ let failure: { code?: string; message?: string } | null = null;
572
+
573
+ const blocks = (await stream.text()).replaceAll("\r\n", "\n").split("\n\n");
574
+ for (const block of blocks) {
575
+ const data = block
576
+ .split("\n")
577
+ .filter((line) => line.startsWith("data:"))
578
+ .map((line) => line.slice(5).trimStart())
579
+ .join("\n");
580
+ if (!data || data === "[DONE]") continue;
581
+ let event: StreamEvent;
582
+ try {
583
+ event = JSON.parse(data) as StreamEvent;
584
+ } catch {
585
+ continue;
586
+ }
587
+ if (event.type === "response.output_item.done") {
588
+ items.push(event.item);
589
+ } else if (
590
+ event.type === "response.completed" ||
591
+ event.type === "response.incomplete"
592
+ ) {
593
+ finished = event.response ?? {};
594
+ } else if (event.type === "response.failed") {
595
+ failure = event.response?.error ?? { message: "The response failed" };
596
+ } else if (event.type === "error") {
597
+ failure = { code: event.code, message: event.message };
598
+ }
599
+ }
600
+
601
+ if (failure || !finished) {
602
+ return Response.json(
603
+ {
604
+ error: {
605
+ message:
606
+ failure?.message ?? "The stream ended before the response finished",
607
+ code: failure?.code,
608
+ },
609
+ },
610
+ { status: failure?.code === "context_length_exceeded" ? 400 : 500 },
611
+ );
612
+ }
613
+ return Response.json({
614
+ ...finished,
615
+ output: finished.output?.length ? finished.output : items,
616
+ });
617
+ }
@@ -0,0 +1,66 @@
1
+ "use client";
2
+
3
+ import { type ComponentProps, useEffect, useRef, useState } from "react";
4
+ import { Button } from "@/components/ui/button";
5
+ import { toast } from "@/components/ui/toast";
6
+ import { startChatGptSignInAction } from "@/features/config/config.action";
7
+ import { useServerAction } from "@/lib/protocol/use-server-action";
8
+
9
+ /** How often to look whether the sign-in window was closed, and how long to keep looking: as long as the server listens for its answer. */
10
+ const WINDOW_POLL_MS = 700;
11
+ const WINDOW_WATCH_MS = 10 * 60_000;
12
+
13
+ /**
14
+ * Opens ChatGPT's sign-in in a window of its own. The answer lands on the server, which keeps it
15
+ * and signals the screen (`config`); whoever draws this takes it away once signed in. Until then
16
+ * the button waits, and stops waiting when the window is closed without finishing.
17
+ */
18
+ export function ChatGptSignIn({
19
+ variant,
20
+ }: {
21
+ variant?: ComponentProps<typeof Button>["variant"];
22
+ }) {
23
+ const [waiting, setWaiting] = useState(false);
24
+ const watch = useRef<ReturnType<typeof setInterval> | null>(null);
25
+ const stopWatching = () => {
26
+ if (watch.current) clearInterval(watch.current);
27
+ watch.current = null;
28
+ };
29
+ useEffect(() => stopWatching, []);
30
+
31
+ const [start, starting] = useServerAction(startChatGptSignInAction, {
32
+ onOk: (url) => {
33
+ const popup = window.open(
34
+ url,
35
+ "thursday-chatgpt",
36
+ "popup,width=520,height=720",
37
+ );
38
+ if (!popup) {
39
+ toast.add({
40
+ type: "error",
41
+ title: "The sign-in window was blocked",
42
+ description: "Allow pop-ups for this page, then try again",
43
+ });
44
+ return;
45
+ }
46
+ setWaiting(true);
47
+ stopWatching();
48
+ const deadline = Date.now() + WINDOW_WATCH_MS;
49
+ watch.current = setInterval(() => {
50
+ if (!popup.closed && Date.now() < deadline) return;
51
+ stopWatching();
52
+ setWaiting(false);
53
+ }, WINDOW_POLL_MS);
54
+ },
55
+ });
56
+
57
+ return (
58
+ <Button
59
+ variant={variant}
60
+ loading={starting || waiting}
61
+ onClick={() => start()}
62
+ >
63
+ Sign in with ChatGPT
64
+ </Button>
65
+ );
66
+ }