thursday-agent 0.12.0 → 0.13.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 (470) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/prerender-manifest.json +4 -4
  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 +1 -1
  11. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  12. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/.next/server/app/_not-found.html +1 -1
  14. package/.next/server/app/_not-found.rsc +18 -19
  15. package/.next/server/app/_not-found.segments/_full.segment.rsc +18 -19
  16. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -15
  17. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  18. package/.next/server/app/api/artifact/route.js +8 -8
  19. package/.next/server/app/api/artifact/route.js.nft.json +1 -1
  20. package/.next/server/app/api/bot/keep-working/route.js +7 -7
  21. package/.next/server/app/api/bot/keep-working/route.js.nft.json +1 -1
  22. package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
  23. package/.next/server/app/api/bot/memory/route.js +7 -7
  24. package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
  25. package/.next/server/app/api/bot/route.js +7 -7
  26. package/.next/server/app/api/bot/route.js.nft.json +1 -1
  27. package/.next/server/app/api/bot/thread/[id]/route.js +5 -5
  28. package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -1
  29. package/.next/server/app/api/bot/thread/route.js +3 -3
  30. package/.next/server/app/api/bot/thread/route.js.nft.json +1 -1
  31. package/.next/server/app/api/config/route.js +3 -3
  32. package/.next/server/app/api/config/route.js.nft.json +1 -1
  33. package/.next/server/app/api/events/route.js.nft.json +1 -1
  34. package/.next/server/app/api/favicon/[host]/route.js.nft.json +1 -1
  35. package/.next/server/app/api/file/[...path]/route.js +2 -2
  36. package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
  37. package/.next/server/app/api/llm-model/catalog/route.js +4 -4
  38. package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
  39. package/.next/server/app/api/llm-model/credits/route.js +4 -4
  40. package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
  41. package/.next/server/app/api/llm-model/route.js +6 -6
  42. package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
  43. package/.next/server/app/api/llm-model/subscription/route.js +6 -6
  44. package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
  45. package/.next/server/app/api/mcp/[name]/route.js +5 -5
  46. package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
  47. package/.next/server/app/api/mcp/oauth/callback/route.js +5 -5
  48. package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
  49. package/.next/server/app/api/mcp/route.js +5 -5
  50. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  51. package/.next/server/app/api/mcp/tools/route.js +5 -5
  52. package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
  53. package/.next/server/app/api/memory/edit/route.js +15 -14
  54. package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
  55. package/.next/server/app/api/memory/route.js +4 -4
  56. package/.next/server/app/api/memory/route.js.nft.json +1 -1
  57. package/.next/server/app/api/reach/route.js +13 -12
  58. package/.next/server/app/api/reach/route.js.nft.json +1 -1
  59. package/.next/server/app/api/routine/route.js +8 -9
  60. package/.next/server/app/api/routine/route.js.nft.json +1 -1
  61. package/.next/server/app/api/signins/route.js.nft.json +1 -1
  62. package/.next/server/app/api/skills/[source]/[name]/route.js +2 -2
  63. package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
  64. package/.next/server/app/api/skills/route.js +2 -2
  65. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  66. package/.next/server/app/api/thursday/call/route.js +6 -6
  67. package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
  68. package/.next/server/app/api/thursday/skills/route.js +6 -6
  69. package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
  70. package/.next/server/app/api/thursday/text/route.js +14 -13
  71. package/.next/server/app/api/thursday/text/route.js.nft.json +1 -1
  72. package/.next/server/app/api/thursday/tool-call/route.js +15 -14
  73. package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
  74. package/.next/server/app/api/workspace/files/route.js +10 -6
  75. package/.next/server/app/api/workspace/files/route.js.nft.json +1 -1
  76. package/.next/server/app/api/workspace/route.js +9 -4
  77. package/.next/server/app/api/workspace/route.js.nft.json +1 -1
  78. package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +2 -2
  79. package/.next/server/app/artifact/[...path]/page.js +5 -4
  80. package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
  81. package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
  82. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  83. package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -1
  84. package/.next/server/app/page/server-reference-manifest.json +67 -56
  85. package/.next/server/app/page.js +10 -11
  86. package/.next/server/app/page.js.nft.json +1 -1
  87. package/.next/server/app/page_client-reference-manifest.js +1 -1
  88. package/.next/server/chunks/{[root-of-the-server]__0o49ztf._.js → [root-of-the-server]__0-k4_ie._.js} +2 -2
  89. package/.next/server/chunks/[root-of-the-server]__0209ah4._.js +6 -6
  90. package/.next/server/chunks/[root-of-the-server]__04wldw9._.js +1 -1
  91. package/.next/server/chunks/[root-of-the-server]__0h_x6q6._.js +2 -2
  92. package/.next/server/chunks/{[root-of-the-server]__0cfszwn._.js → [root-of-the-server]__0i23tc1._.js} +2 -2
  93. package/.next/server/chunks/[root-of-the-server]__0mrk2it._.js +1 -1
  94. package/.next/server/chunks/[root-of-the-server]__0nr-v8s._.js +1 -1
  95. package/.next/server/chunks/{[root-of-the-server]__1tg5got._.js → [root-of-the-server]__0paqm5y._.js} +2 -2
  96. package/.next/server/chunks/[root-of-the-server]__0qog7sm._.js +8 -0
  97. package/.next/server/chunks/[root-of-the-server]__0sm5xvm._.js +1 -1
  98. package/.next/server/chunks/[root-of-the-server]__0yisc5v._.js +5 -0
  99. package/.next/server/chunks/[root-of-the-server]__1-swlkw._.js +3 -3
  100. package/.next/server/chunks/[root-of-the-server]__12iddfq._.js +2 -2
  101. package/.next/server/chunks/{[root-of-the-server]__0npn_pn._.js → [root-of-the-server]__15p29m3._.js} +2 -2
  102. package/.next/server/chunks/[root-of-the-server]__17ses32._.js +3 -0
  103. package/.next/server/chunks/[root-of-the-server]__18-2tsq._.js +2 -2
  104. package/.next/server/chunks/[root-of-the-server]__1cjxw0-._.js +1 -1
  105. package/.next/server/chunks/[root-of-the-server]__1gi2nns._.js +5 -0
  106. package/.next/server/chunks/{[root-of-the-server]__0rqgb17._.js → [root-of-the-server]__1iauz8a._.js} +2 -2
  107. package/.next/server/chunks/[root-of-the-server]__1k3j52n._.js +1 -1
  108. package/.next/server/chunks/{[root-of-the-server]__209k6pk._.js → [root-of-the-server]__1lb_pxg._.js} +2 -2
  109. package/.next/server/chunks/[root-of-the-server]__1ndi14y._.js +1 -1
  110. package/.next/server/chunks/[root-of-the-server]__1orn-5b._.js +1 -1
  111. package/.next/server/chunks/[root-of-the-server]__1xqsrqq._.js +5 -0
  112. package/.next/server/chunks/[root-of-the-server]__1ygplx1._.js +1 -1
  113. package/.next/server/chunks/[root-of-the-server]__2163i40._.js +5 -0
  114. package/.next/server/chunks/_0ci0abh._.js +3 -0
  115. package/.next/server/chunks/_0eauz4t._.js +1 -1
  116. package/.next/server/chunks/_0f6ts7z._.js +1 -1
  117. package/.next/server/chunks/_0fg-1oj._.js +5 -0
  118. package/.next/server/chunks/_0khihjk._.js +2 -2
  119. package/.next/server/chunks/_0n74zsf._.js +1 -1
  120. package/.next/server/chunks/_0na2fj1._.js +2 -2
  121. package/.next/server/chunks/_0oh_99-._.js +3 -0
  122. package/.next/server/chunks/_0x-ej3f._.js +24 -24
  123. package/.next/server/chunks/_0z3nz21._.js +3 -0
  124. package/.next/server/chunks/_10tkr26._.js +1 -1
  125. package/.next/server/chunks/_12-amdu._.js +2 -2
  126. package/.next/server/chunks/_136j09a._.js +1 -1
  127. package/.next/server/chunks/_15740dw._.js +1 -1
  128. package/.next/server/chunks/_18izeh1._.js +5 -0
  129. package/.next/server/chunks/_1_5mnq4._.js +1 -1
  130. package/.next/server/chunks/_1ajbhv6._.js +2 -2
  131. package/.next/server/chunks/_1dc_d1q._.js +2 -2
  132. package/.next/server/chunks/_1drgfx4._.js +1 -1
  133. package/.next/server/chunks/_1e48czp._.js +2 -2
  134. package/.next/server/chunks/_1f06zo4._.js +3 -0
  135. package/.next/server/chunks/_1ic3bew._.js +2 -2
  136. package/.next/server/chunks/_1jn-aep._.js +2 -2
  137. package/.next/server/chunks/_1jn5btd._.js +6 -0
  138. package/.next/server/chunks/_1kg70wc._.js +2 -2
  139. package/.next/server/chunks/_1m2i5pt._.js +5 -0
  140. package/.next/server/chunks/_1ogqovh._.js +4 -4
  141. package/.next/server/chunks/_1qxheoa._.js +1 -1
  142. package/.next/server/chunks/_1ry6afh._.js +18 -0
  143. package/.next/server/chunks/_1taa4bs._.js +1 -1
  144. package/.next/server/chunks/_1tpr507._.js +1 -1
  145. package/.next/server/chunks/_1y8w80a._.js +1 -1
  146. package/.next/server/chunks/_1zxp-p6._.js +1 -1
  147. package/.next/server/chunks/_204744f._.js +5 -0
  148. package/.next/server/chunks/_20fsr_z._.js +1 -1
  149. package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
  150. package/.next/server/chunks/features_0-cnt0l._.js +1 -1
  151. package/.next/server/chunks/features_0hdaci0._.js +1 -1
  152. package/.next/server/chunks/features_0p85gv6._.js +3 -0
  153. package/.next/server/chunks/{node_modules_zod_v4_0_v5xf5._.js → node_modules_zod_v4_01bha1c._.js} +2 -2
  154. package/.next/server/chunks/{node_modules_zod_v4_0q9hvql._.js → node_modules_zod_v4_0b99z6a._.js} +2 -2
  155. package/.next/server/chunks/{node_modules_zod_v4_1jg8iwg._.js → node_modules_zod_v4_1zz6lo1._.js} +2 -2
  156. package/.next/server/chunks/ssr/[root-of-the-server]__04upd5v._.js +3 -0
  157. package/.next/server/chunks/ssr/[root-of-the-server]__0dxszdt._.js +3 -0
  158. package/.next/server/chunks/ssr/[root-of-the-server]__0eezycw._.js +3 -0
  159. package/.next/server/chunks/ssr/[root-of-the-server]__0hpp3nw._.js +1 -1
  160. package/.next/server/chunks/ssr/[root-of-the-server]__0yngu44._.js +3 -0
  161. package/.next/server/chunks/ssr/[root-of-the-server]__16z72ny._.js +6 -0
  162. package/.next/server/chunks/ssr/[root-of-the-server]__1u_ry86._.js +3 -0
  163. package/.next/server/chunks/ssr/{[root-of-the-server]__1iwyp1e._.js → [root-of-the-server]__1unh_ew._.js} +1 -1
  164. package/.next/server/chunks/ssr/[root-of-the-server]__20re7ya._.js +3 -0
  165. package/.next/server/chunks/ssr/_00pgq9d._.js +5 -0
  166. package/.next/server/chunks/ssr/_07ke63g._.js +3 -0
  167. package/.next/server/chunks/ssr/_0agcbk7._.js +3 -0
  168. package/.next/server/chunks/ssr/_0e-gw2_._.js +3 -0
  169. package/.next/server/chunks/ssr/{_0fbi6om._.js → _0f_om6y._.js} +3 -3
  170. package/.next/server/chunks/ssr/_0fqhhwi._.js +1 -1
  171. package/.next/server/chunks/ssr/{_026qnc9._.js → _0gpn71k._.js} +3 -3
  172. package/.next/server/chunks/ssr/_0iizjr9._.js +3 -0
  173. package/.next/server/chunks/ssr/_0ixvzo6._.js +75 -0
  174. package/.next/server/chunks/ssr/_0kkdx7t._.js +3 -0
  175. package/.next/server/chunks/ssr/_0lufvw7._.js +118 -0
  176. package/.next/server/chunks/ssr/_0vi6ky6._.js +115 -0
  177. package/.next/server/chunks/ssr/{_10f-jib._.js → _0yhji3e._.js} +2 -2
  178. package/.next/server/chunks/ssr/_0z38nha._.js +3 -0
  179. package/.next/server/chunks/ssr/{_10mo72m._.js → _11nx2d4._.js} +2 -2
  180. package/.next/server/chunks/ssr/_12mijol._.js +2 -2
  181. package/.next/server/chunks/ssr/_150463c._.js +164 -0
  182. package/.next/server/chunks/ssr/{_0rmaf32._.js → _17d2ikl._.js} +2 -2
  183. package/.next/server/chunks/ssr/_19jez1a._.js +3 -0
  184. package/.next/server/chunks/ssr/_1blui1m._.js +3 -0
  185. package/.next/server/chunks/ssr/_1d423aj._.js +4 -4
  186. package/.next/server/chunks/ssr/_1kbk28w._.js +3 -0
  187. package/.next/server/chunks/ssr/_1kj_r9v._.js +3 -0
  188. package/.next/server/chunks/ssr/_1mqn9mc._.js +1 -1
  189. package/.next/server/chunks/ssr/{_0u5xv6g._.js → _1myh7ss._.js} +2 -2
  190. package/.next/server/chunks/ssr/_1neoj22._.js +1 -1
  191. package/.next/server/chunks/ssr/_1ou5sj0._.js +3 -0
  192. package/.next/server/chunks/ssr/_1p8xorg._.js +3 -0
  193. package/.next/server/chunks/ssr/_1tzacpl._.js +3 -0
  194. package/.next/server/chunks/ssr/_1xoch6f._.js +1 -1
  195. package/.next/server/chunks/ssr/_1yh5bj5._.js +1 -1
  196. package/.next/server/chunks/ssr/{_02c7o30._.js → _2021n9-._.js} +2 -2
  197. package/.next/server/chunks/ssr/_20argo3._.js +3 -0
  198. package/.next/server/chunks/ssr/features_bot_1ll3wp7._.js +3 -0
  199. package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +1 -1
  200. package/.next/server/chunks/ssr/features_bot_thread_query_ts_023eklt._.js +1 -1
  201. package/.next/server/chunks/ssr/features_config_1qxe5ur._.js +20 -0
  202. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +3 -3
  203. package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
  204. package/.next/server/chunks/ssr/features_routine_components_routine-setting_tsx_055fknz._.js +1 -1
  205. package/.next/server/chunks/ssr/features_workspace_00jz2hu._.js +1 -1
  206. package/.next/server/chunks/ssr/{node_modules_0ky76fz._.js → node_modules_06e0wlv._.js} +1 -1
  207. package/.next/server/chunks/ssr/{node_modules_0-x0zxm._.js → node_modules_0cj26-9._.js} +1 -1
  208. package/.next/server/chunks/ssr/{node_modules_0operbz._.js → node_modules_0d1rf5e._.js} +1 -1
  209. package/.next/server/chunks/ssr/{node_modules_0b0iy01._.js → node_modules_0e86703._.js} +1 -1
  210. package/.next/server/chunks/ssr/{node_modules_05b6e99._.js → node_modules_0es09am._.js} +1 -1
  211. package/.next/server/chunks/ssr/{node_modules_1iau_ea._.js → node_modules_0gr_dzf._.js} +1 -1
  212. package/.next/server/chunks/ssr/{node_modules_1hv-j1l._.js → node_modules_0gtcwz9._.js} +1 -1
  213. package/.next/server/chunks/ssr/{node_modules_1c2ikr9._.js → node_modules_0hi9h8f._.js} +1 -1
  214. package/.next/server/chunks/ssr/{node_modules_01u4ea3._.js → node_modules_0lkop0t._.js} +1 -1
  215. package/.next/server/chunks/ssr/{node_modules_1wvf3oo._.js → node_modules_0nghisk._.js} +1 -1
  216. package/.next/server/chunks/ssr/{node_modules_0yzmxfq._.js → node_modules_0stovz0._.js} +1 -1
  217. package/.next/server/chunks/ssr/{node_modules_00nrzz_._.js → node_modules_0we85kr._.js} +1 -1
  218. package/.next/server/chunks/ssr/{node_modules_01ed0yi._.js → node_modules_0ya7y1s._.js} +1 -1
  219. package/.next/server/chunks/ssr/{node_modules_0m3_5ge._.js → node_modules_0yrbwkj._.js} +1 -1
  220. package/.next/server/chunks/ssr/{node_modules_0v_-uht._.js → node_modules_1-x70ie._.js} +1 -1
  221. package/.next/server/chunks/ssr/{node_modules_07jp6tj._.js → node_modules_12fd9g0._.js} +1 -1
  222. package/.next/server/chunks/ssr/{node_modules_1wgnj8q._.js → node_modules_12wxrgr._.js} +1 -1
  223. package/.next/server/chunks/ssr/{node_modules_0azd56x._.js → node_modules_139uu4q._.js} +1 -1
  224. package/.next/server/chunks/ssr/{node_modules_1omg9xr._.js → node_modules_14gpoe1._.js} +1 -1
  225. package/.next/server/chunks/ssr/{node_modules_1ma_y68._.js → node_modules_18fmji6._.js} +1 -1
  226. package/.next/server/chunks/ssr/{node_modules_0ucridu._.js → node_modules_19kkel8._.js} +1 -1
  227. package/.next/server/chunks/ssr/{node_modules_0fpnetd._.js → node_modules_1gcine1._.js} +1 -1
  228. package/.next/server/chunks/ssr/{node_modules_1z3sh11._.js → node_modules_1h4trkm._.js} +1 -1
  229. package/.next/server/chunks/ssr/{node_modules_0lautv3._.js → node_modules_1mub_qu._.js} +1 -1
  230. package/.next/server/chunks/ssr/{node_modules_0ou0all._.js → node_modules_1p78svs._.js} +1 -1
  231. package/.next/server/chunks/ssr/{node_modules_19b0bir._.js → node_modules_1xp-7ep._.js} +1 -1
  232. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0tmjn9w._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0ay7_oi._.js} +1 -1
  233. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1_e8c3o._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_182ydq_._.js} +1 -1
  234. package/.next/server/middleware-build-manifest.js +3 -3
  235. package/.next/server/pages/404.html +1 -1
  236. package/.next/server/pages/500.html +1 -1
  237. package/.next/server/server-reference-manifest.js +1 -1
  238. package/.next/server/server-reference-manifest.json +69 -58
  239. package/.next/static/chunks/03df16hs01-xi.js +1 -0
  240. package/.next/static/chunks/0crocb7272z8k.js +1 -0
  241. package/.next/static/chunks/{0p9jcyqkpq71f.js → 0dqsenl3uk_8z.js} +1 -1
  242. package/.next/static/chunks/0hhn8mf953761.js +1 -0
  243. package/.next/static/chunks/0n1o3k0z4m7c_.js +1 -0
  244. package/.next/static/chunks/0oe2zavre2a7d.css +1 -0
  245. package/.next/static/chunks/0srg9tv4105-i.js +1 -0
  246. package/.next/static/chunks/0t599wrclly7i.js +18 -0
  247. package/.next/static/chunks/0x34bhth_sn35.js +1 -0
  248. package/.next/static/chunks/{0mscosx0eh3ee.js → 0x_6z8vt8hyf4.js} +3 -3
  249. package/.next/static/chunks/{0p50d-au6-3hx.js → 1_wstwzu1md-6.js} +2 -2
  250. package/.next/static/chunks/{40ij3wsn3vt7h.js → 1aughn81slf90.js} +1 -1
  251. package/.next/static/chunks/1g1j03h_l-e67.js +1 -0
  252. package/.next/static/chunks/1lp02y2nmddxg.js +1 -0
  253. package/.next/static/chunks/211s4xjtecj2l.js +1 -0
  254. package/.next/static/chunks/2244-cat2jlvd.js +1 -0
  255. package/.next/static/chunks/{2r6xnyg7ezyxr.js → 2eev_i93slion.js} +2 -2
  256. package/.next/static/chunks/2sjq4gsimlfqr.js +113 -0
  257. package/.next/static/chunks/{0qgn3u86jkbm7.js → 2uleilh8ph06-.js} +1 -1
  258. package/.next/static/chunks/{1czfknd4luo6t.js → 2zchsg03a1pdp.js} +1 -1
  259. package/.next/static/chunks/32tmfirfjpulv.js +1 -0
  260. package/.next/static/chunks/{2_c7mu6x1lu49.js → 376-s_i83irs-.js} +1 -1
  261. package/.next/static/chunks/3blk-kpn39xr-.js +1 -0
  262. package/.next/static/chunks/3eieehp7wquyu.js +1 -0
  263. package/.next/static/chunks/3kc-e7pcj50ae.js +1 -0
  264. package/.next/static/chunks/3rwpyuo9nrrop.js +17 -0
  265. package/.next/static/chunks/3sf4l25l353ja.js +1 -0
  266. package/.next/static/chunks/3vz7m1w688juj.js +3 -0
  267. package/.next/static/chunks/43skgt0h9woyu.js +1 -0
  268. package/app/api/events/app-event.client.ts +8 -2
  269. package/app/api/events/app-event.server.ts +7 -5
  270. package/app/api/events/app-event.ts +19 -8
  271. package/app/api/query-key.ts +13 -5
  272. package/bin/database.mjs +21 -11
  273. package/bin/thursday.mjs +5 -2
  274. package/components/ui/shiny-text.tsx +2 -2
  275. package/database/migrations/20260920024909_messy_baron_strucker/migration.sql +1 -0
  276. package/database/migrations/20260920024909_messy_baron_strucker/snapshot.json +1937 -0
  277. package/features/artifact/components/artifact-setting.tsx +18 -24
  278. package/features/bot/bot.action.ts +3 -0
  279. package/features/bot/bot.runner.ts +25 -3
  280. package/features/bot/bot.seed.ts +98 -22
  281. package/features/bot/components/bot-badge.tsx +2 -2
  282. package/features/bot/components/bot-mark.tsx +12 -3
  283. package/features/bot/components/bot-room.tsx +33 -10
  284. package/features/bot/components/bot-setting.tsx +88 -34
  285. package/features/bot/components/crew-motion.ts +161 -0
  286. package/features/bot/components/mark-palette.tsx +3 -3
  287. package/features/bot/components/room-conversation.tsx +13 -8
  288. package/features/bot/components/room-list.tsx +9 -5
  289. package/features/bot/components/room-pill.tsx +131 -41
  290. package/features/bot/components/thread-badge.tsx +4 -4
  291. package/features/bot/components/thread-setting.tsx +58 -19
  292. package/features/bot/mark.const.ts +24 -0
  293. package/features/bot/thread.query.ts +32 -14
  294. package/features/bot/thread.store.ts +19 -16
  295. package/features/config/components/config-badge.tsx +2 -2
  296. package/features/config/components/config-setting.tsx +63 -213
  297. package/features/config/config.const.ts +2 -1
  298. package/features/connectors/components/mcp-setting.tsx +60 -29
  299. package/features/intro/components/intro.tsx +2 -2
  300. package/features/memory/components/memory-setting.tsx +11 -7
  301. package/features/reach/channel.ts +9 -1
  302. package/features/reach/components/reach-guide.tsx +606 -105
  303. package/features/reach/discord.ts +18 -3
  304. package/features/reach/reach.schema.ts +6 -0
  305. package/features/reach/reach.ts +55 -2
  306. package/features/reach/slack.ts +2 -1
  307. package/features/reach/telegram.ts +5 -1
  308. package/features/routine/components/routine-setting.tsx +46 -18
  309. package/features/settings/components/setting-ui.tsx +17 -68
  310. package/features/settings/components/settings.tsx +4 -3
  311. package/features/settings/settings.alert.ts +8 -8
  312. package/features/skills/components/skills-setting.tsx +78 -12
  313. package/features/skills/skills.action.ts +12 -0
  314. package/features/skills/skills.query.ts +24 -0
  315. package/features/thursday/components/call-log.tsx +19 -14
  316. package/features/thursday/components/side-captions.tsx +3 -2
  317. package/features/thursday/components/thursday-setting.tsx +5 -6
  318. package/features/thursday/components/thursday.tsx +8 -9
  319. package/features/thursday/components/write-line.tsx +10 -5
  320. package/features/thursday/thursday.action.ts +4 -0
  321. package/features/thursday/thursday.query.ts +3 -2
  322. package/features/thursday/thursday.schema.ts +5 -0
  323. package/features/thursday/use-call-ring.ts +6 -1
  324. package/features/thursday/use-text-call.ts +25 -10
  325. package/features/thursday/use-thursday.ts +10 -1
  326. package/features/workspace/components/artifact-view.tsx +42 -33
  327. package/features/workspace/components/file-thumb.tsx +8 -4
  328. package/features/workspace/components/file-view.tsx +25 -2
  329. package/features/workspace/components/workspace-setting.tsx +27 -23
  330. package/features/workspace/workspace.query.ts +20 -1
  331. package/features/workspace/workspace.ts +14 -0
  332. package/guide/bots.md +27 -4
  333. package/guide/calls.md +5 -3
  334. package/guide/phone.md +23 -12
  335. package/guide/setup.md +20 -5
  336. package/guide/trouble.md +1 -1
  337. package/hooks/use-theme.ts +14 -0
  338. package/lib/protocol/events.client.ts +56 -14
  339. package/lib/protocol/events.server.ts +41 -12
  340. package/lib/utils.ts +1 -1
  341. package/package.json +1 -1
  342. package/scripts/reach-channels.test.mts +15 -2
  343. package/seed-skills/brief/daily-brief/SKILL.md +39 -0
  344. package/seed-skills/brief/daily-brief/page/brief.css +406 -0
  345. package/seed-skills/brief/daily-brief/references/brief.md +106 -0
  346. package/seed-skills/brief/daily-brief/references/preferences.md +67 -0
  347. package/seed-skills/brief/daily-brief/scripts/glance.mjs +128 -0
  348. package/seed-skills/brief/daily-brief/scripts/lib.mjs +182 -0
  349. package/seed-skills/brief/daily-brief/scripts/news.mjs +320 -0
  350. package/seed-skills/brief/daily-brief/scripts/page.mjs +322 -0
  351. package/seed-skills/brief/daily-brief/scripts/story.mjs +265 -0
  352. package/seed-skills/digest/README.md +20 -0
  353. package/seed-skills/digest/media-digest/SKILL.md +32 -0
  354. package/seed-skills/digest/media-digest/references/no-captions.md +20 -0
  355. package/seed-skills/digest/media-digest/references/page.md +56 -0
  356. package/seed-skills/digest/media-digest/references/reading.md +56 -0
  357. package/seed-skills/digest/media-digest/scripts/audio.mjs +232 -0
  358. package/seed-skills/digest/media-digest/scripts/digest.mjs +379 -0
  359. package/seed-skills/digest/media-digest/scripts/lib.mjs +284 -0
  360. package/seed-skills/digest/media-digest/scripts/part.mjs +16 -0
  361. package/seed-skills/digest/media-digest/scripts/text.mjs +287 -0
  362. package/seed-skills/digest/media-digest/scripts/yt.mjs +487 -0
  363. package/seed-skills/docs/README.md +32 -0
  364. package/seed-skills/docs/documents/SKILL.md +34 -0
  365. package/seed-skills/docs/documents/kit/package-lock.json +1596 -0
  366. package/seed-skills/docs/documents/kit/package.json +17 -0
  367. package/seed-skills/docs/documents/references/deck.md +63 -0
  368. package/seed-skills/docs/documents/references/invoice.md +64 -0
  369. package/seed-skills/docs/documents/references/pdf.md +59 -0
  370. package/seed-skills/docs/documents/references/print.md +43 -0
  371. package/seed-skills/docs/documents/references/sheets.md +49 -0
  372. package/seed-skills/docs/documents/references/translate.md +37 -0
  373. package/seed-skills/docs/documents/references/word.md +45 -0
  374. package/seed-skills/docs/documents/scripts/doc.mjs +123 -0
  375. package/seed-skills/docs/documents/scripts/lib/browser.mjs +259 -0
  376. package/seed-skills/docs/documents/scripts/lib/deck-chart.mjs +150 -0
  377. package/seed-skills/docs/documents/scripts/lib/deck.mjs +1132 -0
  378. package/seed-skills/docs/documents/scripts/lib/invoice.mjs +505 -0
  379. package/seed-skills/docs/documents/scripts/lib/kit.mjs +244 -0
  380. package/seed-skills/docs/documents/scripts/lib/pdf.mjs +645 -0
  381. package/seed-skills/docs/documents/scripts/lib/report.mjs +38 -0
  382. package/seed-skills/docs/documents/scripts/lib/sheet.mjs +523 -0
  383. package/seed-skills/docs/documents/scripts/lib/templates.mjs +34 -0
  384. package/seed-skills/docs/documents/scripts/lib/translate.mjs +691 -0
  385. package/seed-skills/docs/documents/scripts/lib/word.mjs +678 -0
  386. package/seed-skills/docs/documents/templates/letter.html +56 -0
  387. package/seed-skills/docs/documents/templates/print.css +219 -0
  388. package/seed-skills/docs/documents/templates/report.html +47 -0
  389. package/seed-skills/docs/documents/templates/resume.html +61 -0
  390. package/seed-skills/insta/instagram/references/post.md +31 -7
  391. package/seed-skills/trip/README.md +11 -0
  392. package/seed-skills/trip/travel/SKILL.md +40 -0
  393. package/seed-skills/trip/travel/page/itinerary.css +488 -0
  394. package/seed-skills/trip/travel/references/itinerary.md +70 -0
  395. package/seed-skills/trip/travel/references/plan.md +75 -0
  396. package/seed-skills/trip/travel/references/watch-and-book.md +39 -0
  397. package/seed-skills/trip/travel/scripts/flights.mjs +374 -0
  398. package/seed-skills/trip/travel/scripts/fx.mjs +57 -0
  399. package/seed-skills/trip/travel/scripts/hotels.mjs +356 -0
  400. package/seed-skills/trip/travel/scripts/itinerary.mjs +482 -0
  401. package/seed-skills/trip/travel/scripts/lib.mjs +120 -0
  402. package/seed-skills/trip/travel/scripts/selectors.mjs +78 -0
  403. package/seed-skills/trip/travel/scripts/watch.mjs +147 -0
  404. package/seed-skills/trip/travel/scripts/weather.mjs +150 -0
  405. package/seed-skills/tutor/picture-book/SKILL.md +6 -0
  406. package/seed-skills/tutor/picture-book/references/picture-book.md +22 -2
  407. package/.next/server/chunks/[root-of-the-server]__014ydvq._.js +0 -8
  408. package/.next/server/chunks/[root-of-the-server]__01d285y._.js +0 -5
  409. package/.next/server/chunks/[root-of-the-server]__04r4h-2._.js +0 -7
  410. package/.next/server/chunks/[root-of-the-server]__0ayldfn._.js +0 -5
  411. package/.next/server/chunks/[root-of-the-server]__17weeeg._.js +0 -5
  412. package/.next/server/chunks/[root-of-the-server]__1ai5lme._.js +0 -8
  413. package/.next/server/chunks/_05v8k21._.js +0 -7
  414. package/.next/server/chunks/_0k6qjcf._.js +0 -5
  415. package/.next/server/chunks/_1d_enhu._.js +0 -3
  416. package/.next/server/chunks/_1jc5g4p._.js +0 -6
  417. package/.next/server/chunks/_1nfr-75._.js +0 -3
  418. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0md6j0x._.js +0 -3
  419. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_14ih1vy._.js +0 -3
  420. package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1d6ez5-._.js +0 -3
  421. package/.next/server/chunks/ssr/[root-of-the-server]__0a967km._.js +0 -3
  422. package/.next/server/chunks/ssr/[root-of-the-server]__0tzekbl._.js +0 -68
  423. package/.next/server/chunks/ssr/[root-of-the-server]__1756892._.js +0 -3
  424. package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +0 -3
  425. package/.next/server/chunks/ssr/[root-of-the-server]__1nqw56a._.js +0 -3
  426. package/.next/server/chunks/ssr/[root-of-the-server]__1r77-88._.js +0 -3
  427. package/.next/server/chunks/ssr/[root-of-the-server]__1w9hqq8._.js +0 -8
  428. package/.next/server/chunks/ssr/_05pl_u8._.js +0 -3
  429. package/.next/server/chunks/ssr/_097-ljd._.js +0 -4
  430. package/.next/server/chunks/ssr/_0euzzf8._.js +0 -3
  431. package/.next/server/chunks/ssr/_0evkpto._.js +0 -3
  432. package/.next/server/chunks/ssr/_0igfcax._.js +0 -20
  433. package/.next/server/chunks/ssr/_0lu1n_x._.js +0 -3
  434. package/.next/server/chunks/ssr/_0nvh1h9._.js +0 -3
  435. package/.next/server/chunks/ssr/_0qmpw35._.js +0 -3
  436. package/.next/server/chunks/ssr/_0uv-qs4._.js +0 -3
  437. package/.next/server/chunks/ssr/_132qkfz._.js +0 -5
  438. package/.next/server/chunks/ssr/_15zqt-t._.js +0 -3
  439. package/.next/server/chunks/ssr/_16gfxpn._.js +0 -3
  440. package/.next/server/chunks/ssr/_184327j._.js +0 -3
  441. package/.next/server/chunks/ssr/_18ujhhq._.js +0 -3
  442. package/.next/server/chunks/ssr/_1lx6w_o._.js +0 -67
  443. package/.next/server/chunks/ssr/_1ntpny5._.js +0 -3
  444. package/.next/server/chunks/ssr/_1o1g4uk._.js +0 -5
  445. package/.next/server/chunks/ssr/_1xx4zlb._.js +0 -3
  446. package/.next/server/chunks/ssr/_2008vmi._.js +0 -164
  447. package/.next/server/chunks/ssr/features_bot_1n1s7bh._.js +0 -3
  448. package/.next/static/chunks/09pkygbht7ki8.js +0 -18
  449. package/.next/static/chunks/0bm-nhblcmmj4.js +0 -17
  450. package/.next/static/chunks/0c_g4-11sha-1.js +0 -1
  451. package/.next/static/chunks/0hhza87hkbqaz.js +0 -1
  452. package/.next/static/chunks/0samf80yu2b17.js +0 -1
  453. package/.next/static/chunks/0vlgzqo9ka90v.js +0 -1
  454. package/.next/static/chunks/1g9yyvculuxj2.js +0 -1
  455. package/.next/static/chunks/1jz8n0mhtdzr8.js +0 -1
  456. package/.next/static/chunks/206laxm6maiz2.js +0 -1
  457. package/.next/static/chunks/23nfu5g4vl_m7.js +0 -1
  458. package/.next/static/chunks/2h-tlhb2ffhkk.js +0 -1
  459. package/.next/static/chunks/2i5_w8olnrvo0.js +0 -3
  460. package/.next/static/chunks/2m6jd3586ijb-.js +0 -1
  461. package/.next/static/chunks/2m8iqwc6sykln.js +0 -1
  462. package/.next/static/chunks/33fyp-byw48ju.js +0 -65
  463. package/.next/static/chunks/35yd_27d9ylra.js +0 -1
  464. package/.next/static/chunks/3ag1i3__yl94f.js +0 -1
  465. package/.next/static/chunks/3m_ejzg-xn21r.css +0 -1
  466. package/.next/static/chunks/3s-jpd3ky49-n.js +0 -1
  467. package/features/reach/components/reach-state.tsx +0 -71
  468. /package/.next/static/{QSe0rTdXU5kh5BBzr2XBB → fiNGsQfna8qldl8wU8wqr}/_buildManifest.js +0 -0
  469. /package/.next/static/{QSe0rTdXU5kh5BBzr2XBB → fiNGsQfna8qldl8wU8wqr}/_clientMiddlewareManifest.js +0 -0
  470. /package/.next/static/{QSe0rTdXU5kh5BBzr2XBB → fiNGsQfna8qldl8wU8wqr}/_ssgManifest.js +0 -0
@@ -0,0 +1,374 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ currency,
4
+ day,
5
+ fail,
6
+ money,
7
+ oneLine,
8
+ parseArgs,
9
+ shipped,
10
+ shortDay,
11
+ } from "./lib.mjs";
12
+ /**
13
+ * Flights from Google Flights as a few lines: airline, times, stops, duration and price
14
+ * per option, read from the page's own labels in this shell's browser session. Picking
15
+ * an outbound lists the returns; picking every leg reaches the booking page, whose url
16
+ * is the itinerary's deep link and what `watch.mjs` checks.
17
+ *
18
+ * node flights.mjs <from> <to> <depart YYYY-MM-DD> [<return YYYY-MM-DD>]
19
+ * [--adults 1] [--children 0] [--cabin economy|premium|business|first]
20
+ * [--nonstop] [--currency USD] [--max 8] [--sort best|price|duration] [--pick N[,M]]
21
+ *
22
+ * <from> and <to> are airport or city codes (ICN, SEL, FUK) or city names.
23
+ */
24
+ import { FLIGHTS } from "./selectors.mjs";
25
+
26
+ const USAGE =
27
+ "usage: node flights.mjs <from> <to> <depart YYYY-MM-DD> [<return YYYY-MM-DD>] [--adults 1] [--children 0] [--cabin economy|premium|business|first] [--nonstop] [--currency USD] [--max 8] [--sort best|price|duration] [--pick N[,M]]";
28
+ const CABINS = {
29
+ economy: "",
30
+ premium: " premium economy class",
31
+ business: " business class",
32
+ first: " first class",
33
+ };
34
+
35
+ const opts = parseArgs();
36
+ const [from, to, departArg, returnArg] = opts._;
37
+ if (!from || !to || !departArg) fail(USAGE);
38
+ const depart = day(departArg, "The departure");
39
+ const back = returnArg ? day(returnArg, "The return") : null;
40
+ if (back && back < depart) fail("The return is before the departure.");
41
+ if (depart < new Date().toLocaleDateString("en-CA"))
42
+ fail(`${depart} has passed: flights are searched from today on.`);
43
+ const cur = currency(opts.currency);
44
+ const adults = Number(opts.adults ?? 1);
45
+ const children = Number(opts.children ?? 0);
46
+ if (!(adults >= 1 && adults <= 9) || !(children >= 0 && children <= 8))
47
+ fail("--adults is 1-9 and --children 0-8.");
48
+ const cabin = String(opts.cabin ?? "economy");
49
+ if (!(cabin in CABINS)) fail("--cabin is economy, premium, business or first.");
50
+ const max = Number(opts.max ?? 8);
51
+ const sort = String(opts.sort ?? "best");
52
+ if (!["best", "price", "duration"].includes(sort))
53
+ fail("--sort is best, price or duration.");
54
+ const pick = opts.pick
55
+ ? String(opts.pick)
56
+ .split(",")
57
+ .map((n) => Number(n))
58
+ : [];
59
+ if (pick.some((n) => !Number.isInteger(n) || n < 1))
60
+ fail(
61
+ "--pick takes the # of a listed flight, and for a round trip the # of a return after it: --pick 3 or --pick 3,1.",
62
+ );
63
+ if (pick.length > (back ? 2 : 1))
64
+ fail(
65
+ `--pick takes ${back ? "two numbers" : "one number"} on a ${back ? "round trip" : "one-way trip"}.`,
66
+ );
67
+
68
+ // Google reads a sentence in `q`; hl=en keeps the labels below in English on any machine
69
+ const who = `for ${adults} adult${adults > 1 ? "s" : ""}${children ? ` and ${children} child${children > 1 ? "ren" : ""}` : ""}`;
70
+ const sentence = `Flights to ${to} from ${from} on ${depart}${back ? ` through ${back}` : " one way"} ${who}${CABINS[cabin]}${opts.nonstop ? " nonstop" : ""}`;
71
+ const searchUrl = `https://www.google.com/travel/flights?q=${encodeURIComponent(sentence)}&curr=${cur}&hl=en&gl=us`;
72
+
73
+ const { inPage, orFail } = await shipped("browser/scripts/session.mjs");
74
+
75
+ const started = Date.now();
76
+ const got = orFail(
77
+ await inPage(
78
+ async (page, { url, css, noRows, returning, booking, consent, pick }) => {
79
+ const text = () =>
80
+ page.evaluate(
81
+ () =>
82
+ (document.querySelector("[role=main]") ?? document.body).innerText,
83
+ );
84
+ const visible = (sel) =>
85
+ page.evaluate(
86
+ (sel) =>
87
+ [...document.querySelectorAll(sel)]
88
+ .filter((e) => e.offsetParent !== null)
89
+ .map((e) => ({
90
+ label: e.getAttribute("aria-label") ?? "",
91
+ text: (e.closest("li") ?? e).innerText ?? "",
92
+ })),
93
+ sel,
94
+ );
95
+ // Results stream in: wait for the list to stop growing, the page's own "nothing",
96
+ // or a form left blank (Google did not read the sentence and showed its front page)
97
+ const settle = async (none) => {
98
+ const start = Date.now();
99
+ let last = -1;
100
+ let still = 0;
101
+ while (Date.now() - start < 30000) {
102
+ await page.waitForTimeout(400);
103
+ const now = await page.evaluate(
104
+ ({ row, none, from }) => ({
105
+ n: [...document.querySelectorAll(row)].filter(
106
+ (e) => e.offsetParent !== null,
107
+ ).length,
108
+ none: new RegExp(none).test(document.body.innerText),
109
+ blank:
110
+ document.readyState === "complete" &&
111
+ document.querySelector(from)?.value === "",
112
+ }),
113
+ { row: css.row, none, from: css.from },
114
+ );
115
+ if (!now.n && (now.none || (now.blank && Date.now() - start > 5000)))
116
+ return;
117
+ still = now.n && now.n === last ? still + 1 : 0;
118
+ last = now.n;
119
+ if (still >= 3) return;
120
+ }
121
+ };
122
+ const read = async () => ({
123
+ url: page.url(),
124
+ rows: await visible(css.row),
125
+ text: (await text()).slice(0, 6000),
126
+ });
127
+
128
+ await page.goto(url, { waitUntil: "domcontentloaded", timeout: 45000 });
129
+ if (page.url().includes(consent.host)) {
130
+ await page
131
+ .getByRole("button", { name: new RegExp(consent.button) })
132
+ .first()
133
+ .click({ timeout: 10000 })
134
+ .catch(() => {});
135
+ await page
136
+ .waitForURL(/travel\/flights/, { timeout: 20000 })
137
+ .catch(() => {});
138
+ if (page.url().includes(consent.host))
139
+ return {
140
+ error: `Google's consent page did not let go: ${page.url()}`,
141
+ };
142
+ }
143
+ await settle(noRows);
144
+ const form = await page.evaluate(
145
+ (css) => ({
146
+ from: document.querySelector(css.from)?.value ?? null,
147
+ to: document.querySelector(css.to)?.value ?? null,
148
+ depart: document.querySelector(css.depart)?.value ?? null,
149
+ passengers:
150
+ document
151
+ .querySelector(css.passengers)
152
+ ?.getAttribute("aria-label") ?? null,
153
+ }),
154
+ css,
155
+ );
156
+ const steps = [{ ...(await read()), form }];
157
+ for (const [leg, n] of pick.entries()) {
158
+ const rows = steps.at(-1).rows;
159
+ if (n > rows.length)
160
+ return { error: `There is no #${n}: the list has ${rows.length}.` };
161
+ await page.evaluate(
162
+ ({ sel, i }) =>
163
+ [...document.querySelectorAll(sel)]
164
+ .filter((e) => e.offsetParent !== null)
165
+ [i].click(),
166
+ { sel: css.row, i: n - 1 },
167
+ );
168
+ // After an outbound comes the return list, or the booking page when nothing is left to pick
169
+ const next = await page
170
+ .waitForFunction(
171
+ ({ returning, booking, leg }) => {
172
+ const t = document.body.innerText;
173
+ if (new RegExp(booking, "i").test(t)) return "booking";
174
+ if (leg === 0 && new RegExp(returning, "i").test(t))
175
+ return "returning";
176
+ return null;
177
+ },
178
+ { returning, booking, leg },
179
+ { timeout: 25000 },
180
+ )
181
+ .then((h) => h.jsonValue())
182
+ .catch(() => null);
183
+ if (!next) return { stuck: page.url() };
184
+ if (next === "returning") await settle(noRows);
185
+ else {
186
+ await page
187
+ .waitForSelector(css.seller, { timeout: 15000 })
188
+ .catch(() => {});
189
+ await page.waitForTimeout(1500);
190
+ }
191
+ steps.push({
192
+ ...(await read()),
193
+ kind: next,
194
+ sellers: await page.$$eval(css.seller, (els) =>
195
+ els.map((e) => e.getAttribute("aria-label")),
196
+ ),
197
+ });
198
+ }
199
+ return { steps };
200
+ },
201
+ {
202
+ url: searchUrl,
203
+ css: FLIGHTS.css,
204
+ noRows: FLIGHTS.noRows.source,
205
+ returning: FLIGHTS.returning.source,
206
+ booking: FLIGHTS.booking.source,
207
+ consent: { host: FLIGHTS.consentHost, button: FLIGHTS.consentButton },
208
+ pick,
209
+ },
210
+ ),
211
+ );
212
+
213
+ if (got.stuck)
214
+ fail(
215
+ `Google Flights did not go on to the return list or the booking page after the click: layout changed? Snapshot ${got.stuck} once and fix scripts/selectors.mjs.`,
216
+ );
217
+
218
+ // "5:00 PM" → "17:00"
219
+ const clock = (t) => {
220
+ const [, h, m, ap] = t.match(/(\d{1,2}):(\d{2})\s?([AP]M)/);
221
+ return `${String((Number(h) % 12) + (ap === "PM" ? 12 : 0)).padStart(2, "0")}:${m}`;
222
+ };
223
+ // "25 hr 5 min" → "25h05", "33 hr" → "33h", "50 min" → "0h50"; words around it stay
224
+ const span = (s) =>
225
+ s
226
+ .replace(/(\d+) hr(?: (\d+) min)?/, (_, h, m) =>
227
+ m ? `${h}h${m.padStart(2, "0")}` : `${h}h`,
228
+ )
229
+ .replace(/^(\d+) min/, (_, m) => `0h${m.padStart(2, "0")}`)
230
+ .trim();
231
+ const dayOf = (md) => Date.parse(`${md} 2000`);
232
+
233
+ function parseRow({ label, text }, n) {
234
+ const price = label.match(FLIGHTS.price)?.[1];
235
+ const carrier = label.match(FLIGHTS.carrier);
236
+ const legs = label.match(FLIGHTS.legs);
237
+ if (!carrier || !legs) return { n, raw: oneLine(label, 220) };
238
+ const codes = text.match(FLIGHTS.route) ?? [
239
+ null,
240
+ ...(text.match(FLIGHTS.codes) ?? []),
241
+ ];
242
+ // Days later it lands, across a new year too (2000 was a leap year)
243
+ const plus =
244
+ (Math.round((dayOf(legs[6]) - dayOf(legs[3])) / 864e5) + 366) % 366;
245
+ const layovers = [...label.matchAll(FLIGHTS.layover)].map(
246
+ (m) => `${span(m[1])} ${m[3] ?? m[2]}`,
247
+ );
248
+ const stops = carrier[1] === "Nonstop" ? 0 : Number.parseInt(carrier[1], 10);
249
+ const duration = span(label.match(FLIGHTS.duration)?.[1] ?? "?");
250
+ return {
251
+ n,
252
+ price: price ? Number(price.replace(/,/g, "")) : null,
253
+ // "Etihad. Operated by Hifly for Etihad Airways": the seller's name is what books it
254
+ airline: carrier[2].split(". ")[0].replace(/ and /g, ", "),
255
+ from: codes[1] ?? legs[1],
256
+ to: codes[2] ?? legs[4],
257
+ dep: clock(legs[2]),
258
+ arr: `${clock(legs[5])}${plus > 0 ? `+${plus}` : ""}`,
259
+ date: legs[3],
260
+ stops,
261
+ layovers,
262
+ duration,
263
+ minutes:
264
+ Number(duration.match(/(\d+)h/)?.[1] ?? 0) * 60 +
265
+ Number(duration.match(/h(\d+)/)?.[1] ?? 0),
266
+ };
267
+ }
268
+
269
+ // `tfu` only remembers how the page was reached; the itinerary is all in `tfs`
270
+ const shorter = (url) => url.replace(/&tfu=[^&]*/, "");
271
+
272
+ const line = (f) =>
273
+ f.raw
274
+ ? `#${f.n} (unparsed) ${f.raw}`
275
+ : `#${f.n} ${f.airline} ${f.from} ${f.dep} → ${f.to} ${f.arr} ${f.stops ? `${f.stops} stop${f.stops > 1 ? "s" : ""} (${f.layovers.join(", ")})` : "nonstop"} ${f.duration} ${money(f.price, cur)}`;
276
+
277
+ function listRows(step, what) {
278
+ const rows = step.rows.map((r, i) => parseRow(r, i + 1));
279
+ if (!rows.length) {
280
+ const said = [...new Set(step.text.match(FLIGHTS.noRows) ?? [])];
281
+ if (said.length) fail(`Google Flights: ${said.join(". ")}.`);
282
+ fail(
283
+ `No flight rows on ${step.url}: layout changed? Snapshot it once and fix scripts/selectors.mjs.`,
284
+ );
285
+ }
286
+ if (rows.every((r) => r.raw))
287
+ fail(
288
+ `Flight rows came back but none parsed: the labels changed. First one: ${rows[0].raw}. Fix scripts/selectors.mjs.`,
289
+ );
290
+ const kept = rows
291
+ .filter((r) => !(opts.nonstop && r.stops))
292
+ .sort((a, b) =>
293
+ sort === "price"
294
+ ? (a.price ?? 1e12) - (b.price ?? 1e12)
295
+ : sort === "duration"
296
+ ? (a.minutes ?? 1e9) - (b.minutes ?? 1e9)
297
+ : a.n - b.n,
298
+ );
299
+ const cheapest = Math.min(...rows.map((r) => r.price ?? 1e12));
300
+ console.log(
301
+ `${what} (${rows.length} found, ${kept.length > max ? `first ${max} shown` : "all shown"}, cheapest ${money(cheapest, cur)}):`,
302
+ );
303
+ for (const r of kept.slice(0, max)) console.log(line(r));
304
+ return rows;
305
+ }
306
+
307
+ const [search, ...picked] = got.steps;
308
+ const form = search.form;
309
+ if (!form.from && !search.rows.length)
310
+ fail(
311
+ `Google did not read the search "${sentence}" (the form came back empty): use airport codes (ICN, FUK) or a plainer city name.`,
312
+ );
313
+ if (form.depart && !form.depart.startsWith(shortDay(depart)))
314
+ fail(
315
+ `Google read the departure as "${form.depart}", not ${shortDay(depart)}: say the date as YYYY-MM-DD.`,
316
+ );
317
+ console.log(
318
+ `${form.from ?? from} → ${form.to ?? to}, ${shortDay(depart)}${back ? ` – ${shortDay(back)}` : " one way"}, ${form.passengers?.replace(/, change.*/, "") ?? who}, ${cabin}, ${cur}`,
319
+ );
320
+ const insight = search.text.match(FLIGHTS.insight)?.[0];
321
+ if (insight) console.log(`${insight}.`);
322
+ console.log(`Search: ${searchUrl}`);
323
+ const outbound = listRows(
324
+ search,
325
+ back ? "Outbound (price is the round-trip total)" : "Flights",
326
+ );
327
+
328
+ if (!picked.length) {
329
+ console.log(
330
+ back
331
+ ? "Next: --pick <#> lists the returns that go with that outbound."
332
+ : "Next: --pick <#> opens its booking page: sellers, bags and the deep link.",
333
+ );
334
+ } else {
335
+ const chosen = outbound[pick[0] - 1];
336
+ console.log(`\nPicked outbound: ${line(chosen)}`);
337
+ const last = picked.at(-1);
338
+ if (last.kind === "returning") {
339
+ listRows(last, "Returns with it (price is the round-trip total)");
340
+ console.log(`Outbound fixed: ${shorter(last.url)}`);
341
+ console.log(
342
+ `Next: --pick ${pick[0]},<#> opens the booking page for that pair.`,
343
+ );
344
+ } else {
345
+ if (picked.length === 2) {
346
+ const ret = parseRow(picked[0].rows[pick[1] - 1], pick[1]);
347
+ console.log(`Picked return: ${line(ret)}`);
348
+ }
349
+ const sellers = (last.sellers ?? [])
350
+ .map((l) => l.match(FLIGHTS.sellerLabel))
351
+ .filter(Boolean)
352
+ .map(
353
+ (m) =>
354
+ `${m[1].replace(/ airline$/, " (airline)")} ${money(Number(m[2].replace(/,/g, "")), cur)}`,
355
+ );
356
+ if (!sellers.length)
357
+ fail(
358
+ `The booking page came up with no sellers: layout changed? Snapshot ${last.url} once and fix scripts/selectors.mjs.`,
359
+ );
360
+ console.log(`Book with: ${sellers.join(" | ")}`);
361
+ const bags = [...new Set(last.text.match(FLIGHTS.bags) ?? [])];
362
+ if (bags.length) console.log(`Bags: ${bags.join(", ")}`);
363
+ const verdict = last.text.match(FLIGHTS.insight)?.[0];
364
+ const usual = last.text.match(FLIGHTS.usualRange)?.[1];
365
+ if (verdict || usual)
366
+ console.log(
367
+ `Price: ${verdict ?? ""}${usual ? `${verdict ? "; " : ""}similar trips usually ${usual}` : ""}.`,
368
+ );
369
+ console.log(
370
+ `Booking page (deep link, and what watch.mjs checks): ${shorter(last.url)}`,
371
+ );
372
+ }
373
+ }
374
+ console.log(`(${((Date.now() - started) / 1000).toFixed(1)}s)`);
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * An amount in other currencies at today's rate, with the rate's date and source, so a
4
+ * price found in yen can sit beside a budget in dollars. ECB reference rates through
5
+ * Frankfurter; a currency the ECB does not quote (TWD, VND, AED …) comes from
6
+ * ExchangeRate-API's open feed. No key.
7
+ *
8
+ * node fx.mjs <amount> <FROM> <TO[,TO…]> node fx.mjs 150 USD JPY,KRW
9
+ */
10
+ import { fail, getJson, parseArgs } from "./lib.mjs";
11
+
12
+ const opts = parseArgs();
13
+ const [amountArg, fromArg, toArg] = opts._;
14
+ const amount = Number(String(amountArg ?? "").replace(/,/g, ""));
15
+ if (!Number.isFinite(amount) || !fromArg || !toArg)
16
+ fail(
17
+ "usage: node fx.mjs <amount> <FROM> <TO[,TO…]> e.g. node fx.mjs 150 USD JPY,KRW",
18
+ );
19
+ const from = fromArg.toUpperCase();
20
+ const targets = toArg
21
+ .toUpperCase()
22
+ .split(",")
23
+ .map((c) => c.trim())
24
+ .filter((c) => c && c !== from);
25
+ for (const c of [from, ...targets])
26
+ if (!/^[A-Z]{3}$/.test(c))
27
+ fail(`"${c}" is not a three-letter currency code.`);
28
+
29
+ const show = (value, code) =>
30
+ new Intl.NumberFormat("en-US", {
31
+ style: "currency",
32
+ currency: code,
33
+ maximumFractionDigits: value >= 100 ? 0 : 2,
34
+ }).format(value);
35
+
36
+ const ecb = await getJson("https://api.frankfurter.dev/v1/currencies");
37
+ const rates = {};
38
+ let dated = "";
39
+ if (from in ecb && targets.every((c) => c in ecb)) {
40
+ const r = await getJson(
41
+ `https://api.frankfurter.dev/v1/latest?base=${from}&symbols=${targets.join(",")}`,
42
+ );
43
+ Object.assign(rates, r.rates);
44
+ dated = `ECB reference rate of ${r.date}, via Frankfurter`;
45
+ } else {
46
+ const r = await getJson(`https://open.er-api.com/v6/latest/${from}`);
47
+ if (r.result !== "success")
48
+ fail(`No rates for ${from}: ${r["error-type"] ?? "unknown currency"}.`);
49
+ for (const c of targets) {
50
+ if (!(c in r.rates)) fail(`No rate for ${c}: is it a real currency code?`);
51
+ rates[c] = r.rates[c];
52
+ }
53
+ dated = `Rates By Exchange Rate API, updated ${r.time_last_update_utc.slice(5, 16)}`;
54
+ }
55
+ console.log(
56
+ `${show(amount, from)} = ${targets.map((c) => show(amount * rates[c], c)).join(" = ")} (1 ${from} = ${targets.map((c) => `${rates[c]} ${c}`).join(", ")}; ${dated})`,
57
+ );