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.
- package/.next/BUILD_ID +1 -1
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +4 -4
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +18 -19
- package/.next/server/app/_not-found.segments/_full.segment.rsc +18 -19
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +14 -15
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/artifact/route.js +8 -8
- package/.next/server/app/api/artifact/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/keep-working/route.js +7 -7
- package/.next/server/app/api/bot/keep-working/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/route.js +7 -7
- package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/route.js +7 -7
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/[id]/route.js +5 -5
- package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/route.js +3 -3
- package/.next/server/app/api/bot/thread/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js +3 -3
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/server/app/api/favicon/[host]/route.js.nft.json +1 -1
- package/.next/server/app/api/file/[...path]/route.js +2 -2
- package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/catalog/route.js +4 -4
- package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/credits/route.js +4 -4
- package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/route.js +6 -6
- package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/subscription/route.js +6 -6
- package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/[name]/route.js +5 -5
- package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/oauth/callback/route.js +5 -5
- package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js +5 -5
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js +5 -5
- package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/edit/route.js +15 -14
- package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/route.js +4 -4
- package/.next/server/app/api/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/reach/route.js +13 -12
- package/.next/server/app/api/reach/route.js.nft.json +1 -1
- package/.next/server/app/api/routine/route.js +8 -9
- package/.next/server/app/api/routine/route.js.nft.json +1 -1
- package/.next/server/app/api/signins/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js +2 -2
- package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js +2 -2
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/call/route.js +6 -6
- package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/skills/route.js +6 -6
- package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/text/route.js +14 -13
- package/.next/server/app/api/thursday/text/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js +15 -14
- package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/files/route.js +10 -6
- package/.next/server/app/api/workspace/files/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/route.js +9 -4
- package/.next/server/app/api/workspace/route.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +2 -2
- package/.next/server/app/artifact/[...path]/page.js +5 -4
- package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -1
- package/.next/server/app/page/server-reference-manifest.json +67 -56
- package/.next/server/app/page.js +10 -11
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/chunks/{[root-of-the-server]__0o49ztf._.js → [root-of-the-server]__0-k4_ie._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__0209ah4._.js +6 -6
- package/.next/server/chunks/[root-of-the-server]__04wldw9._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0h_x6q6._.js +2 -2
- package/.next/server/chunks/{[root-of-the-server]__0cfszwn._.js → [root-of-the-server]__0i23tc1._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__0mrk2it._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0nr-v8s._.js +1 -1
- package/.next/server/chunks/{[root-of-the-server]__1tg5got._.js → [root-of-the-server]__0paqm5y._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__0qog7sm._.js +8 -0
- package/.next/server/chunks/[root-of-the-server]__0sm5xvm._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0yisc5v._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__1-swlkw._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__12iddfq._.js +2 -2
- package/.next/server/chunks/{[root-of-the-server]__0npn_pn._.js → [root-of-the-server]__15p29m3._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__17ses32._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__18-2tsq._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__1cjxw0-._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1gi2nns._.js +5 -0
- package/.next/server/chunks/{[root-of-the-server]__0rqgb17._.js → [root-of-the-server]__1iauz8a._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__1k3j52n._.js +1 -1
- package/.next/server/chunks/{[root-of-the-server]__209k6pk._.js → [root-of-the-server]__1lb_pxg._.js} +2 -2
- package/.next/server/chunks/[root-of-the-server]__1ndi14y._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1orn-5b._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1xqsrqq._.js +5 -0
- package/.next/server/chunks/[root-of-the-server]__1ygplx1._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__2163i40._.js +5 -0
- package/.next/server/chunks/_0ci0abh._.js +3 -0
- package/.next/server/chunks/_0eauz4t._.js +1 -1
- package/.next/server/chunks/_0f6ts7z._.js +1 -1
- package/.next/server/chunks/_0fg-1oj._.js +5 -0
- package/.next/server/chunks/_0khihjk._.js +2 -2
- package/.next/server/chunks/_0n74zsf._.js +1 -1
- package/.next/server/chunks/_0na2fj1._.js +2 -2
- package/.next/server/chunks/_0oh_99-._.js +3 -0
- package/.next/server/chunks/_0x-ej3f._.js +24 -24
- package/.next/server/chunks/_0z3nz21._.js +3 -0
- package/.next/server/chunks/_10tkr26._.js +1 -1
- package/.next/server/chunks/_12-amdu._.js +2 -2
- package/.next/server/chunks/_136j09a._.js +1 -1
- package/.next/server/chunks/_15740dw._.js +1 -1
- package/.next/server/chunks/_18izeh1._.js +5 -0
- package/.next/server/chunks/_1_5mnq4._.js +1 -1
- package/.next/server/chunks/_1ajbhv6._.js +2 -2
- package/.next/server/chunks/_1dc_d1q._.js +2 -2
- package/.next/server/chunks/_1drgfx4._.js +1 -1
- package/.next/server/chunks/_1e48czp._.js +2 -2
- package/.next/server/chunks/_1f06zo4._.js +3 -0
- package/.next/server/chunks/_1ic3bew._.js +2 -2
- package/.next/server/chunks/_1jn-aep._.js +2 -2
- package/.next/server/chunks/_1jn5btd._.js +6 -0
- package/.next/server/chunks/_1kg70wc._.js +2 -2
- package/.next/server/chunks/_1m2i5pt._.js +5 -0
- package/.next/server/chunks/_1ogqovh._.js +4 -4
- package/.next/server/chunks/_1qxheoa._.js +1 -1
- package/.next/server/chunks/_1ry6afh._.js +18 -0
- package/.next/server/chunks/_1taa4bs._.js +1 -1
- package/.next/server/chunks/_1tpr507._.js +1 -1
- package/.next/server/chunks/_1y8w80a._.js +1 -1
- package/.next/server/chunks/_1zxp-p6._.js +1 -1
- package/.next/server/chunks/_204744f._.js +5 -0
- package/.next/server/chunks/_20fsr_z._.js +1 -1
- package/.next/server/chunks/config_ts_1g4sz5o._.js +1 -1
- package/.next/server/chunks/features_0-cnt0l._.js +1 -1
- package/.next/server/chunks/features_0hdaci0._.js +1 -1
- package/.next/server/chunks/features_0p85gv6._.js +3 -0
- package/.next/server/chunks/{node_modules_zod_v4_0_v5xf5._.js → node_modules_zod_v4_01bha1c._.js} +2 -2
- package/.next/server/chunks/{node_modules_zod_v4_0q9hvql._.js → node_modules_zod_v4_0b99z6a._.js} +2 -2
- package/.next/server/chunks/{node_modules_zod_v4_1jg8iwg._.js → node_modules_zod_v4_1zz6lo1._.js} +2 -2
- package/.next/server/chunks/ssr/[root-of-the-server]__04upd5v._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0dxszdt._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0eezycw._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0hpp3nw._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0yngu44._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__16z72ny._.js +6 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1u_ry86._.js +3 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__1iwyp1e._.js → [root-of-the-server]__1unh_ew._.js} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__20re7ya._.js +3 -0
- package/.next/server/chunks/ssr/_00pgq9d._.js +5 -0
- package/.next/server/chunks/ssr/_07ke63g._.js +3 -0
- package/.next/server/chunks/ssr/_0agcbk7._.js +3 -0
- package/.next/server/chunks/ssr/_0e-gw2_._.js +3 -0
- package/.next/server/chunks/ssr/{_0fbi6om._.js → _0f_om6y._.js} +3 -3
- package/.next/server/chunks/ssr/_0fqhhwi._.js +1 -1
- package/.next/server/chunks/ssr/{_026qnc9._.js → _0gpn71k._.js} +3 -3
- package/.next/server/chunks/ssr/_0iizjr9._.js +3 -0
- package/.next/server/chunks/ssr/_0ixvzo6._.js +75 -0
- package/.next/server/chunks/ssr/_0kkdx7t._.js +3 -0
- package/.next/server/chunks/ssr/_0lufvw7._.js +118 -0
- package/.next/server/chunks/ssr/_0vi6ky6._.js +115 -0
- package/.next/server/chunks/ssr/{_10f-jib._.js → _0yhji3e._.js} +2 -2
- package/.next/server/chunks/ssr/_0z38nha._.js +3 -0
- package/.next/server/chunks/ssr/{_10mo72m._.js → _11nx2d4._.js} +2 -2
- package/.next/server/chunks/ssr/_12mijol._.js +2 -2
- package/.next/server/chunks/ssr/_150463c._.js +164 -0
- package/.next/server/chunks/ssr/{_0rmaf32._.js → _17d2ikl._.js} +2 -2
- package/.next/server/chunks/ssr/_19jez1a._.js +3 -0
- package/.next/server/chunks/ssr/_1blui1m._.js +3 -0
- package/.next/server/chunks/ssr/_1d423aj._.js +4 -4
- package/.next/server/chunks/ssr/_1kbk28w._.js +3 -0
- package/.next/server/chunks/ssr/_1kj_r9v._.js +3 -0
- package/.next/server/chunks/ssr/_1mqn9mc._.js +1 -1
- package/.next/server/chunks/ssr/{_0u5xv6g._.js → _1myh7ss._.js} +2 -2
- package/.next/server/chunks/ssr/_1neoj22._.js +1 -1
- package/.next/server/chunks/ssr/_1ou5sj0._.js +3 -0
- package/.next/server/chunks/ssr/_1p8xorg._.js +3 -0
- package/.next/server/chunks/ssr/_1tzacpl._.js +3 -0
- package/.next/server/chunks/ssr/_1xoch6f._.js +1 -1
- package/.next/server/chunks/ssr/_1yh5bj5._.js +1 -1
- package/.next/server/chunks/ssr/{_02c7o30._.js → _2021n9-._.js} +2 -2
- package/.next/server/chunks/ssr/_20argo3._.js +3 -0
- package/.next/server/chunks/ssr/features_bot_1ll3wp7._.js +3 -0
- package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +1 -1
- package/.next/server/chunks/ssr/features_bot_thread_query_ts_023eklt._.js +1 -1
- package/.next/server/chunks/ssr/features_config_1qxe5ur._.js +20 -0
- package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +3 -3
- package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
- package/.next/server/chunks/ssr/features_routine_components_routine-setting_tsx_055fknz._.js +1 -1
- package/.next/server/chunks/ssr/features_workspace_00jz2hu._.js +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ky76fz._.js → node_modules_06e0wlv._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0-x0zxm._.js → node_modules_0cj26-9._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0operbz._.js → node_modules_0d1rf5e._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0b0iy01._.js → node_modules_0e86703._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_05b6e99._.js → node_modules_0es09am._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1iau_ea._.js → node_modules_0gr_dzf._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1hv-j1l._.js → node_modules_0gtcwz9._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1c2ikr9._.js → node_modules_0hi9h8f._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_01u4ea3._.js → node_modules_0lkop0t._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1wvf3oo._.js → node_modules_0nghisk._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0yzmxfq._.js → node_modules_0stovz0._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_00nrzz_._.js → node_modules_0we85kr._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_01ed0yi._.js → node_modules_0ya7y1s._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0m3_5ge._.js → node_modules_0yrbwkj._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0v_-uht._.js → node_modules_1-x70ie._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_07jp6tj._.js → node_modules_12fd9g0._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1wgnj8q._.js → node_modules_12wxrgr._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0azd56x._.js → node_modules_139uu4q._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1omg9xr._.js → node_modules_14gpoe1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ma_y68._.js → node_modules_18fmji6._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ucridu._.js → node_modules_19kkel8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0fpnetd._.js → node_modules_1gcine1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1z3sh11._.js → node_modules_1h4trkm._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0lautv3._.js → node_modules_1mub_qu._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ou0all._.js → node_modules_1p78svs._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_19b0bir._.js → node_modules_1xp-7ep._.js} +1 -1
- 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
- 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
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +69 -58
- package/.next/static/chunks/03df16hs01-xi.js +1 -0
- package/.next/static/chunks/0crocb7272z8k.js +1 -0
- package/.next/static/chunks/{0p9jcyqkpq71f.js → 0dqsenl3uk_8z.js} +1 -1
- package/.next/static/chunks/0hhn8mf953761.js +1 -0
- package/.next/static/chunks/0n1o3k0z4m7c_.js +1 -0
- package/.next/static/chunks/0oe2zavre2a7d.css +1 -0
- package/.next/static/chunks/0srg9tv4105-i.js +1 -0
- package/.next/static/chunks/0t599wrclly7i.js +18 -0
- package/.next/static/chunks/0x34bhth_sn35.js +1 -0
- package/.next/static/chunks/{0mscosx0eh3ee.js → 0x_6z8vt8hyf4.js} +3 -3
- package/.next/static/chunks/{0p50d-au6-3hx.js → 1_wstwzu1md-6.js} +2 -2
- package/.next/static/chunks/{40ij3wsn3vt7h.js → 1aughn81slf90.js} +1 -1
- package/.next/static/chunks/1g1j03h_l-e67.js +1 -0
- package/.next/static/chunks/1lp02y2nmddxg.js +1 -0
- package/.next/static/chunks/211s4xjtecj2l.js +1 -0
- package/.next/static/chunks/2244-cat2jlvd.js +1 -0
- package/.next/static/chunks/{2r6xnyg7ezyxr.js → 2eev_i93slion.js} +2 -2
- package/.next/static/chunks/2sjq4gsimlfqr.js +113 -0
- package/.next/static/chunks/{0qgn3u86jkbm7.js → 2uleilh8ph06-.js} +1 -1
- package/.next/static/chunks/{1czfknd4luo6t.js → 2zchsg03a1pdp.js} +1 -1
- package/.next/static/chunks/32tmfirfjpulv.js +1 -0
- package/.next/static/chunks/{2_c7mu6x1lu49.js → 376-s_i83irs-.js} +1 -1
- package/.next/static/chunks/3blk-kpn39xr-.js +1 -0
- package/.next/static/chunks/3eieehp7wquyu.js +1 -0
- package/.next/static/chunks/3kc-e7pcj50ae.js +1 -0
- package/.next/static/chunks/3rwpyuo9nrrop.js +17 -0
- package/.next/static/chunks/3sf4l25l353ja.js +1 -0
- package/.next/static/chunks/3vz7m1w688juj.js +3 -0
- package/.next/static/chunks/43skgt0h9woyu.js +1 -0
- package/app/api/events/app-event.client.ts +8 -2
- package/app/api/events/app-event.server.ts +7 -5
- package/app/api/events/app-event.ts +19 -8
- package/app/api/query-key.ts +13 -5
- package/bin/database.mjs +21 -11
- package/bin/thursday.mjs +5 -2
- package/components/ui/shiny-text.tsx +2 -2
- package/database/migrations/20260920024909_messy_baron_strucker/migration.sql +1 -0
- package/database/migrations/20260920024909_messy_baron_strucker/snapshot.json +1937 -0
- package/features/artifact/components/artifact-setting.tsx +18 -24
- package/features/bot/bot.action.ts +3 -0
- package/features/bot/bot.runner.ts +25 -3
- package/features/bot/bot.seed.ts +98 -22
- package/features/bot/components/bot-badge.tsx +2 -2
- package/features/bot/components/bot-mark.tsx +12 -3
- package/features/bot/components/bot-room.tsx +33 -10
- package/features/bot/components/bot-setting.tsx +88 -34
- package/features/bot/components/crew-motion.ts +161 -0
- package/features/bot/components/mark-palette.tsx +3 -3
- package/features/bot/components/room-conversation.tsx +13 -8
- package/features/bot/components/room-list.tsx +9 -5
- package/features/bot/components/room-pill.tsx +131 -41
- package/features/bot/components/thread-badge.tsx +4 -4
- package/features/bot/components/thread-setting.tsx +58 -19
- package/features/bot/mark.const.ts +24 -0
- package/features/bot/thread.query.ts +32 -14
- package/features/bot/thread.store.ts +19 -16
- package/features/config/components/config-badge.tsx +2 -2
- package/features/config/components/config-setting.tsx +63 -213
- package/features/config/config.const.ts +2 -1
- package/features/connectors/components/mcp-setting.tsx +60 -29
- package/features/intro/components/intro.tsx +2 -2
- package/features/memory/components/memory-setting.tsx +11 -7
- package/features/reach/channel.ts +9 -1
- package/features/reach/components/reach-guide.tsx +606 -105
- package/features/reach/discord.ts +18 -3
- package/features/reach/reach.schema.ts +6 -0
- package/features/reach/reach.ts +55 -2
- package/features/reach/slack.ts +2 -1
- package/features/reach/telegram.ts +5 -1
- package/features/routine/components/routine-setting.tsx +46 -18
- package/features/settings/components/setting-ui.tsx +17 -68
- package/features/settings/components/settings.tsx +4 -3
- package/features/settings/settings.alert.ts +8 -8
- package/features/skills/components/skills-setting.tsx +78 -12
- package/features/skills/skills.action.ts +12 -0
- package/features/skills/skills.query.ts +24 -0
- package/features/thursday/components/call-log.tsx +19 -14
- package/features/thursday/components/side-captions.tsx +3 -2
- package/features/thursday/components/thursday-setting.tsx +5 -6
- package/features/thursday/components/thursday.tsx +8 -9
- package/features/thursday/components/write-line.tsx +10 -5
- package/features/thursday/thursday.action.ts +4 -0
- package/features/thursday/thursday.query.ts +3 -2
- package/features/thursday/thursday.schema.ts +5 -0
- package/features/thursday/use-call-ring.ts +6 -1
- package/features/thursday/use-text-call.ts +25 -10
- package/features/thursday/use-thursday.ts +10 -1
- package/features/workspace/components/artifact-view.tsx +42 -33
- package/features/workspace/components/file-thumb.tsx +8 -4
- package/features/workspace/components/file-view.tsx +25 -2
- package/features/workspace/components/workspace-setting.tsx +27 -23
- package/features/workspace/workspace.query.ts +20 -1
- package/features/workspace/workspace.ts +14 -0
- package/guide/bots.md +27 -4
- package/guide/calls.md +5 -3
- package/guide/phone.md +23 -12
- package/guide/setup.md +20 -5
- package/guide/trouble.md +1 -1
- package/hooks/use-theme.ts +14 -0
- package/lib/protocol/events.client.ts +56 -14
- package/lib/protocol/events.server.ts +41 -12
- package/lib/utils.ts +1 -1
- package/package.json +1 -1
- package/scripts/reach-channels.test.mts +15 -2
- package/seed-skills/brief/daily-brief/SKILL.md +39 -0
- package/seed-skills/brief/daily-brief/page/brief.css +406 -0
- package/seed-skills/brief/daily-brief/references/brief.md +106 -0
- package/seed-skills/brief/daily-brief/references/preferences.md +67 -0
- package/seed-skills/brief/daily-brief/scripts/glance.mjs +128 -0
- package/seed-skills/brief/daily-brief/scripts/lib.mjs +182 -0
- package/seed-skills/brief/daily-brief/scripts/news.mjs +320 -0
- package/seed-skills/brief/daily-brief/scripts/page.mjs +322 -0
- package/seed-skills/brief/daily-brief/scripts/story.mjs +265 -0
- package/seed-skills/digest/README.md +20 -0
- package/seed-skills/digest/media-digest/SKILL.md +32 -0
- package/seed-skills/digest/media-digest/references/no-captions.md +20 -0
- package/seed-skills/digest/media-digest/references/page.md +56 -0
- package/seed-skills/digest/media-digest/references/reading.md +56 -0
- package/seed-skills/digest/media-digest/scripts/audio.mjs +232 -0
- package/seed-skills/digest/media-digest/scripts/digest.mjs +379 -0
- package/seed-skills/digest/media-digest/scripts/lib.mjs +284 -0
- package/seed-skills/digest/media-digest/scripts/part.mjs +16 -0
- package/seed-skills/digest/media-digest/scripts/text.mjs +287 -0
- package/seed-skills/digest/media-digest/scripts/yt.mjs +487 -0
- package/seed-skills/docs/README.md +32 -0
- package/seed-skills/docs/documents/SKILL.md +34 -0
- package/seed-skills/docs/documents/kit/package-lock.json +1596 -0
- package/seed-skills/docs/documents/kit/package.json +17 -0
- package/seed-skills/docs/documents/references/deck.md +63 -0
- package/seed-skills/docs/documents/references/invoice.md +64 -0
- package/seed-skills/docs/documents/references/pdf.md +59 -0
- package/seed-skills/docs/documents/references/print.md +43 -0
- package/seed-skills/docs/documents/references/sheets.md +49 -0
- package/seed-skills/docs/documents/references/translate.md +37 -0
- package/seed-skills/docs/documents/references/word.md +45 -0
- package/seed-skills/docs/documents/scripts/doc.mjs +123 -0
- package/seed-skills/docs/documents/scripts/lib/browser.mjs +259 -0
- package/seed-skills/docs/documents/scripts/lib/deck-chart.mjs +150 -0
- package/seed-skills/docs/documents/scripts/lib/deck.mjs +1132 -0
- package/seed-skills/docs/documents/scripts/lib/invoice.mjs +505 -0
- package/seed-skills/docs/documents/scripts/lib/kit.mjs +244 -0
- package/seed-skills/docs/documents/scripts/lib/pdf.mjs +645 -0
- package/seed-skills/docs/documents/scripts/lib/report.mjs +38 -0
- package/seed-skills/docs/documents/scripts/lib/sheet.mjs +523 -0
- package/seed-skills/docs/documents/scripts/lib/templates.mjs +34 -0
- package/seed-skills/docs/documents/scripts/lib/translate.mjs +691 -0
- package/seed-skills/docs/documents/scripts/lib/word.mjs +678 -0
- package/seed-skills/docs/documents/templates/letter.html +56 -0
- package/seed-skills/docs/documents/templates/print.css +219 -0
- package/seed-skills/docs/documents/templates/report.html +47 -0
- package/seed-skills/docs/documents/templates/resume.html +61 -0
- package/seed-skills/insta/instagram/references/post.md +31 -7
- package/seed-skills/trip/README.md +11 -0
- package/seed-skills/trip/travel/SKILL.md +40 -0
- package/seed-skills/trip/travel/page/itinerary.css +488 -0
- package/seed-skills/trip/travel/references/itinerary.md +70 -0
- package/seed-skills/trip/travel/references/plan.md +75 -0
- package/seed-skills/trip/travel/references/watch-and-book.md +39 -0
- package/seed-skills/trip/travel/scripts/flights.mjs +374 -0
- package/seed-skills/trip/travel/scripts/fx.mjs +57 -0
- package/seed-skills/trip/travel/scripts/hotels.mjs +356 -0
- package/seed-skills/trip/travel/scripts/itinerary.mjs +482 -0
- package/seed-skills/trip/travel/scripts/lib.mjs +120 -0
- package/seed-skills/trip/travel/scripts/selectors.mjs +78 -0
- package/seed-skills/trip/travel/scripts/watch.mjs +147 -0
- package/seed-skills/trip/travel/scripts/weather.mjs +150 -0
- package/seed-skills/tutor/picture-book/SKILL.md +6 -0
- package/seed-skills/tutor/picture-book/references/picture-book.md +22 -2
- package/.next/server/chunks/[root-of-the-server]__014ydvq._.js +0 -8
- package/.next/server/chunks/[root-of-the-server]__01d285y._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__04r4h-2._.js +0 -7
- package/.next/server/chunks/[root-of-the-server]__0ayldfn._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__17weeeg._.js +0 -5
- package/.next/server/chunks/[root-of-the-server]__1ai5lme._.js +0 -8
- package/.next/server/chunks/_05v8k21._.js +0 -7
- package/.next/server/chunks/_0k6qjcf._.js +0 -5
- package/.next/server/chunks/_1d_enhu._.js +0 -3
- package/.next/server/chunks/_1jc5g4p._.js +0 -6
- package/.next/server/chunks/_1nfr-75._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_0md6j0x._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_14ih1vy._.js +0 -3
- package/.next/server/chunks/node_modules_consola_dist_chunks_prompt_mjs_1d6ez5-._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0a967km._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0tzekbl._.js +0 -68
- package/.next/server/chunks/ssr/[root-of-the-server]__1756892._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1mzi-ju._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1nqw56a._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1r77-88._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1w9hqq8._.js +0 -8
- package/.next/server/chunks/ssr/_05pl_u8._.js +0 -3
- package/.next/server/chunks/ssr/_097-ljd._.js +0 -4
- package/.next/server/chunks/ssr/_0euzzf8._.js +0 -3
- package/.next/server/chunks/ssr/_0evkpto._.js +0 -3
- package/.next/server/chunks/ssr/_0igfcax._.js +0 -20
- package/.next/server/chunks/ssr/_0lu1n_x._.js +0 -3
- package/.next/server/chunks/ssr/_0nvh1h9._.js +0 -3
- package/.next/server/chunks/ssr/_0qmpw35._.js +0 -3
- package/.next/server/chunks/ssr/_0uv-qs4._.js +0 -3
- package/.next/server/chunks/ssr/_132qkfz._.js +0 -5
- package/.next/server/chunks/ssr/_15zqt-t._.js +0 -3
- package/.next/server/chunks/ssr/_16gfxpn._.js +0 -3
- package/.next/server/chunks/ssr/_184327j._.js +0 -3
- package/.next/server/chunks/ssr/_18ujhhq._.js +0 -3
- package/.next/server/chunks/ssr/_1lx6w_o._.js +0 -67
- package/.next/server/chunks/ssr/_1ntpny5._.js +0 -3
- package/.next/server/chunks/ssr/_1o1g4uk._.js +0 -5
- package/.next/server/chunks/ssr/_1xx4zlb._.js +0 -3
- package/.next/server/chunks/ssr/_2008vmi._.js +0 -164
- package/.next/server/chunks/ssr/features_bot_1n1s7bh._.js +0 -3
- package/.next/static/chunks/09pkygbht7ki8.js +0 -18
- package/.next/static/chunks/0bm-nhblcmmj4.js +0 -17
- package/.next/static/chunks/0c_g4-11sha-1.js +0 -1
- package/.next/static/chunks/0hhza87hkbqaz.js +0 -1
- package/.next/static/chunks/0samf80yu2b17.js +0 -1
- package/.next/static/chunks/0vlgzqo9ka90v.js +0 -1
- package/.next/static/chunks/1g9yyvculuxj2.js +0 -1
- package/.next/static/chunks/1jz8n0mhtdzr8.js +0 -1
- package/.next/static/chunks/206laxm6maiz2.js +0 -1
- package/.next/static/chunks/23nfu5g4vl_m7.js +0 -1
- package/.next/static/chunks/2h-tlhb2ffhkk.js +0 -1
- package/.next/static/chunks/2i5_w8olnrvo0.js +0 -3
- package/.next/static/chunks/2m6jd3586ijb-.js +0 -1
- package/.next/static/chunks/2m8iqwc6sykln.js +0 -1
- package/.next/static/chunks/33fyp-byw48ju.js +0 -65
- package/.next/static/chunks/35yd_27d9ylra.js +0 -1
- package/.next/static/chunks/3ag1i3__yl94f.js +0 -1
- package/.next/static/chunks/3m_ejzg-xn21r.css +0 -1
- package/.next/static/chunks/3s-jpd3ky49-n.js +0 -1
- package/features/reach/components/reach-state.tsx +0 -71
- /package/.next/static/{QSe0rTdXU5kh5BBzr2XBB → fiNGsQfna8qldl8wU8wqr}/_buildManifest.js +0 -0
- /package/.next/static/{QSe0rTdXU5kh5BBzr2XBB → fiNGsQfna8qldl8wU8wqr}/_clientMiddlewareManifest.js +0 -0
- /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
|
+
);
|