thursday-agent 0.8.0 → 0.10.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 +3 -3
- 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.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 +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/artifact/route.js.nft.json +1 -1
- 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.nft.json +1 -1
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/route.js.nft.json +1 -1
- 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.nft.json +1 -1
- package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/edit/route.js +1 -1
- package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/probe/route.js.nft.json +1 -1
- package/.next/server/app/api/reach/route.js +11 -11
- package/.next/server/app/api/reach/route.js.nft.json +1 -1
- package/.next/server/app/api/routine/route.js +6 -6
- 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.nft.json +1 -1
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/text/route.js +11 -11
- package/.next/server/app/api/thursday/text/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js +6 -6
- package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/files/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/route.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
- 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 +56 -56
- package/.next/server/app/page.js +3 -3
- 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]__012s2o8._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__04r4h-2._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0mrk2it._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__0nmhgeo._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1fy2g82._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1ndi14y._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1nmflqk._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1v7-zke._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1wnzmx9._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__1xv-5cq._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1ygplx1._.js +1 -1
- package/.next/server/chunks/_05v8k21._.js +1 -1
- package/.next/server/chunks/_0awtznl._.js +4 -4
- package/.next/server/chunks/_0k6qjcf._.js +1 -1
- package/.next/server/chunks/_0na2fj1._.js +5 -5
- package/.next/server/chunks/_0x-ej3f._.js +21 -21
- package/.next/server/chunks/_1dc_d1q._.js +22 -20
- package/.next/server/chunks/_1jc5g4p._.js +2 -2
- package/.next/server/chunks/_1jn-aep._.js +50 -48
- package/.next/server/chunks/{_1u8ygbz._.js → _1m88lkh._.js} +7 -7
- package/.next/server/chunks/_1uuucy-._.js +4 -4
- package/.next/server/chunks/_1v9r2ke._.js +1 -1
- package/.next/server/chunks/ssr/{[root-of-the-server]__1h4bnrf._.js → [root-of-the-server]__0cxqzfc._.js} +2 -2
- package/.next/server/chunks/ssr/{[root-of-the-server]__1kyi568._.js → [root-of-the-server]__0tvavy9._.js} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__0tzekbl._.js +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1-d2tgh._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__1se25x0._.js +1 -1
- package/.next/server/chunks/ssr/{_1i3ddn7._.js → _05pl_u8._.js} +1 -1
- package/.next/server/chunks/ssr/_05rxyf9._.js +3 -0
- package/.next/server/chunks/ssr/_070nv1_._.js +3 -0
- package/.next/server/chunks/ssr/_097-ljd._.js +1 -1
- package/.next/server/chunks/ssr/{_10y6r1n._.js → _0bp-_fi._.js} +3 -3
- package/.next/server/chunks/ssr/_0fqhhwi._.js +1 -1
- package/.next/server/chunks/ssr/_0hlxij-._.js +3 -0
- package/.next/server/chunks/ssr/_0ld9_qq._.js +1 -1
- package/.next/server/chunks/ssr/{_0z8jpch._.js → _0p9pmxe._.js} +2 -2
- package/.next/server/chunks/ssr/{_1y-ea28._.js → _0s41irp._.js} +2 -2
- package/.next/server/chunks/ssr/_0tg0vf8._.js +164 -0
- package/.next/server/chunks/ssr/_0u5xv6g._.js +3 -0
- package/.next/server/chunks/ssr/{_1_rqu8x._.js → _12mijol._.js} +7 -7
- package/.next/server/chunks/ssr/_15zqt-t._.js +1 -1
- package/.next/server/chunks/ssr/{_1oijqjw._.js → _173siic._.js} +2 -2
- package/.next/server/chunks/ssr/_17v-33v._.js +20 -0
- package/.next/server/chunks/ssr/_184327j._.js +1 -1
- package/.next/server/chunks/ssr/{_1nod5hh._.js → _19n08g5._.js} +2 -2
- package/.next/server/chunks/ssr/_1alxym6._.js +3 -0
- package/.next/server/chunks/ssr/{_0hzz82u._.js → _1d423aj._.js} +25 -23
- package/.next/server/chunks/ssr/{_0ewory0._.js → _1doidrw._.js} +2 -2
- package/.next/server/chunks/ssr/{_0pm-2p-._.js → _1flsjeo._.js} +2 -2
- package/.next/server/chunks/ssr/_1mqn9mc._.js +1 -1
- package/.next/server/chunks/ssr/_1neoj22._.js +1 -1
- package/.next/server/chunks/ssr/_1ng831c._.js +3 -0
- package/.next/server/chunks/ssr/{_19g570d._.js → _1u2jw0i._.js} +3 -3
- package/.next/server/chunks/ssr/_1xoch6f._.js +1 -1
- package/.next/server/chunks/ssr/{_1u1mxu9._.js → _1yk6yak._.js} +2 -2
- package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +1 -1
- package/.next/server/chunks/ssr/features_bot_1yr-_pm._.js +3 -0
- package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
- 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/{node_modules_1_8mv4v._.js → node_modules_048wmmj._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1p9rdsy._.js → node_modules_04t200t._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_03ptrl9._.js → node_modules_0511ha-._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_07ih6wb._.js → node_modules_08g12f-._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_01f8_se._.js → node_modules_0cdu6jq._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_111sh9o._.js → node_modules_0eb4rx7._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_10gbm2y._.js → node_modules_0f2k5l0._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ix-g49._.js → node_modules_0piws2a._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0t-44lj._.js → node_modules_0pk6i-e._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0lg75qx._.js → node_modules_0s1lwc9._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1v9tc1c._.js → node_modules_0wq4-ts._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1m3gb16._.js → node_modules_0zjqwz7._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0b8df3s._.js → node_modules_11hbkuj._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_00jv9_3._.js → node_modules_18woe20._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1x825hz._.js → node_modules_1_afpz7._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0woscox._.js → node_modules_1_ban4u._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0gt-uw9._.js → node_modules_1cxc8kn._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_059vtf2._.js → node_modules_1dcbvfw._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1lsyy7k._.js → node_modules_1expacw._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1-7qawg._.js → node_modules_1ksh9gy._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_06w645b._.js → node_modules_1m2ho33._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0sz5yg1._.js → node_modules_1n0h9sf._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1teyzfa._.js → node_modules_1qehmwm._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0yv-re6._.js → node_modules_1xzo2wn._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_20xmwtu._.js → node_modules_1zf46lp._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0ii1l89._.js → node_modules_1zkpek5._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@base-ui_react_0jne69c._.js → node_modules_@base-ui_react_0buz1-j._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_00q1oa3._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0avl8-1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1-2_5z7._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_14oslro._.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 +57 -57
- package/.next/static/chunks/03kgfyrli_0gz.js +1 -0
- package/.next/static/chunks/{1e4yrzs5evq_r.js → 09120tms63y21.js} +1 -1
- package/.next/static/chunks/{2zhfdbq50ee5i.js → 0o980477kfqez.js} +1 -1
- package/.next/static/chunks/{1ocy4w0v20a3v.js → 0oz3rs47yy3p8.js} +2 -2
- package/.next/static/chunks/{2be08eqxsfc1y.js → 0w20wnnfrscq7.js} +1 -1
- package/.next/static/chunks/{09c13vnqy7al3.js → 19ojn4lomsk0r.js} +2 -2
- package/.next/static/chunks/{2om4lkg86feb4.js → 1mwee7xvitlp9.js} +1 -1
- package/.next/static/chunks/1p764d75_0lhv.js +18 -0
- package/.next/static/chunks/{0oz7a68qanm99.js → 1pjygp7hqib6j.js} +1 -1
- package/.next/static/chunks/22shnca_6zfzs.js +1 -0
- package/.next/static/chunks/{34s52v8f841zo.js → 24dst4mgurbsh.js} +1 -1
- package/.next/static/chunks/25zq8gzhbdsr7.js +1 -0
- package/.next/static/chunks/2d4z3er53ng8r.js +1 -0
- package/.next/static/chunks/{2d3yhjtafgmtw.js → 2ky81z-jgvasc.js} +1 -1
- package/.next/static/chunks/2m6jd3586ijb-.js +1 -0
- package/.next/static/chunks/{0k5tkt_qi52kb.js → 2ndw4w4u2x51r.js} +1 -1
- package/.next/static/chunks/2qod5tqlpz8b7.js +17 -0
- package/.next/static/chunks/{21gf2t_whhurc.js → 30skcjexa57zn.js} +2 -2
- package/.next/static/chunks/{2upc9mkcc3nlq.js → 35kwn237o4wdn.js} +1 -1
- package/.next/static/chunks/{2u9yjkpkoij75.js → 3exjsi8pfdvzj.js} +1 -1
- package/.next/static/chunks/3obf9n3cxj99k.js +1 -0
- package/.next/static/chunks/3qq4o8w2rv_e9.css +1 -0
- package/.next/static/chunks/{0g1p98s2pfx2w.js → 3vynnr71ghvom.js} +2 -2
- package/.next/static/chunks/{44kjmi5h_p8px.js → 42890jqk049q1.js} +1 -1
- package/README.md +35 -14
- package/bin/port.mjs +11 -7
- package/bin/thursday.mjs +1 -1
- package/components/ui/radio-group.tsx +1 -1
- package/components/ui/shiny-text.tsx +3 -22
- package/docs/how-it-works.md +11 -3
- package/docs/images/first-run.png +0 -0
- package/docs/images/keeps-going.png +0 -0
- package/docs/images/two-minds.png +0 -0
- package/features/ai/components/model-browser.tsx +1 -1
- package/features/ai/components/model-picker.tsx +1 -1
- package/features/ai/guide.ts +9 -4
- package/features/ai/prompts/bot.prompt.ts +4 -1
- package/features/ai/tools/routine.tool.ts +28 -16
- package/features/bot/bot.runner.ts +3 -1
- package/features/bot/components/bot-mark.tsx +6 -1
- package/features/bot/components/bot-setting.tsx +4 -6
- package/features/bot/components/bot-tool.tsx +2 -0
- package/features/bot/components/room-list.tsx +12 -3
- package/features/bot/components/room-pill.tsx +26 -19
- package/features/config/components/config-setting.tsx +17 -5
- package/features/config/components/voice-key.tsx +38 -18
- package/features/connectors/mcp.const.ts +90 -59
- package/features/intro/components/intro.tsx +50 -27
- package/features/routine/components/routine-mark.tsx +4 -6
- package/features/routine/components/routine-setting.tsx +332 -143
- package/features/routine/routine.query.ts +24 -3
- package/features/routine/routine.schema.ts +55 -4
- package/features/settings/components/install-app.tsx +1 -4
- package/features/settings/components/setting-ui.tsx +1 -1
- package/features/skills/skills.discover.ts +2 -1
- package/features/skills/skills.query.ts +5 -1
- package/features/skills/skills.schema.ts +2 -0
- package/features/thursday/components/ascii-orb.tsx +39 -10
- package/features/thursday/components/connect-wave.tsx +284 -0
- package/features/thursday/components/side-captions.tsx +29 -17
- package/features/thursday/components/thursday-setting.tsx +3 -3
- package/features/thursday/components/thursday.tsx +130 -58
- package/features/thursday/components/voice-picker.tsx +1 -1
- package/features/thursday/components/write-line.tsx +2 -2
- package/features/thursday/open-work.ts +11 -3
- package/features/thursday/tool-line.ts +94 -12
- package/features/thursday/use-text-call.ts +3 -2
- package/features/thursday/use-thursday.ts +5 -2
- package/features/workspace/components/artifact-view.tsx +129 -7
- package/guide/bots.md +17 -5
- package/guide/calls.md +15 -7
- package/guide/setup.md +10 -2
- package/knip.json +1 -0
- package/lib/desktop-notify.ts +3 -2
- package/package.json +1 -1
- package/scripts/bot-context.test.mts +1 -1
- package/seed-skills/analyst/README.md +19 -0
- package/seed-skills/analyst/data-report/SKILL.md +59 -0
- package/seed-skills/analyst/data-report/references/brief.md +31 -0
- package/seed-skills/analyst/data-report/references/company.md +41 -0
- package/seed-skills/analyst/data-report/references/comparison.md +42 -0
- package/seed-skills/analyst/data-report/references/decision.md +28 -0
- package/seed-skills/analyst/data-report/references/market-size.md +49 -0
- package/seed-skills/analyst/data-report/references/price-check.md +33 -0
- package/seed-skills/analyst/data-report/references/sources.md +51 -0
- package/seed-skills/analyst/data-report/references/trend.md +39 -0
- package/seed-skills/analyst/data-report/scripts/chart.mjs +679 -0
- package/seed-skills/analyst/data-report/scripts/fetch.mjs +521 -0
- package/seed-skills/insta/instagram/SKILL.md +33 -0
- package/seed-skills/insta/instagram/references/dm.md +49 -0
- package/seed-skills/insta/instagram/references/post.md +74 -0
- package/seed-skills/insta/instagram/scripts/compose.mjs +329 -0
- package/seed-skills/insta/instagram/scripts/image-size.mjs +30 -0
- package/seed-skills/insta/instagram/scripts/inbox.mjs +159 -0
- package/seed-skills/insta/instagram/scripts/lib.mjs +91 -0
- package/seed-skills/insta/instagram/scripts/media.mjs +104 -0
- package/seed-skills/insta/instagram/scripts/profile.mjs +139 -0
- package/seed-skills/insta/instagram/scripts/read.mjs +240 -0
- package/seed-skills/insta/instagram/scripts/render.mjs +126 -0
- package/seed-skills/insta/instagram/scripts/search.mjs +133 -0
- package/seed-skills/insta/instagram/scripts/selectors.mjs +56 -0
- package/seed-skills/insta/instagram/scripts/sheet.mjs +100 -0
- package/seed-skills/insta/instagram/scripts/webimage.mjs +105 -0
- package/seed-skills/insta/instagram/templates/carousel.html +62 -0
- package/seed-skills/mail/mail/SKILL.md +37 -0
- package/seed-skills/mail/mail/recipes/gmail.mjs +448 -0
- package/seed-skills/mail/mail/references/recipes.md +55 -0
- package/seed-skills/mail/mail/scripts/mail.mjs +202 -0
- package/seed-skills/marketer/README.md +6 -4
- package/seed-skills/marketer/copywriting/SKILL.md +1 -1
- package/seed-skills/marketer/emails/SKILL.md +1 -1
- package/seed-skills/marketer/launch/SKILL.md +1 -1
- package/seed-skills/marketer/product-marketing/SKILL.md +5 -5
- package/seed-skills/marketer/seo-audit/SKILL.md +1 -1
- package/seed-skills/marketer/social/SKILL.md +1 -1
- package/skills/computer/SKILL.md +1 -0
- package/skills/interactive-page/SKILL.md +4 -0
- package/.next/server/chunks/[root-of-the-server]__0n-l_n_._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0wvo2of._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1e6aut3._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__1ziojdq._.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__1srp8l5._.js +0 -3
- package/.next/server/chunks/ssr/_0mes4s0._.js +0 -3
- package/.next/server/chunks/ssr/_0ya0cb0._.js +0 -3
- package/.next/server/chunks/ssr/_0yto5s7._.js +0 -20
- package/.next/server/chunks/ssr/_10dw2nv._.js +0 -3
- package/.next/server/chunks/ssr/_1p0gxeo._.js +0 -164
- package/.next/server/chunks/ssr/_1r41yvh._.js +0 -3
- package/.next/server/chunks/ssr/_1r79ksv._.js +0 -3
- package/.next/server/chunks/ssr/features_bot_1ogjt52._.js +0 -3
- package/.next/server/chunks/ssr/node_modules_@base-ui_react_0b3d4m9._.js +0 -3
- package/.next/static/chunks/03omwz_vi2ndg.js +0 -1
- package/.next/static/chunks/0_1bqgd6wt_og.js +0 -1
- package/.next/static/chunks/0abgkzikaznxc.js +0 -1
- package/.next/static/chunks/0bddkpash59-o.js +0 -17
- package/.next/static/chunks/0c2-bzhy3a0ib.js +0 -1
- package/.next/static/chunks/0iilfb1ocevnx.js +0 -1
- package/.next/static/chunks/0imbhx-j830a_.css +0 -1
- package/.next/static/chunks/24h1y9dhy3m_2.js +0 -1
- package/.next/static/chunks/2f_5saocsko_b.js +0 -1
- package/.next/static/chunks/2lwbr5vx0ixaj.js +0 -18
- /package/.next/static/{UiOMPYpQ4fqGyu5PzS-7j → Ar5V4kMdL4CE1trx8anrH}/_buildManifest.js +0 -0
- /package/.next/static/{UiOMPYpQ4fqGyu5PzS-7j → Ar5V4kMdL4CE1trx8anrH}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{UiOMPYpQ4fqGyu5PzS-7j → Ar5V4kMdL4CE1trx8anrH}/_ssgManifest.js +0 -0
|
@@ -11,12 +11,21 @@ import { markSeenAction } from "@/features/bot/bot.action";
|
|
|
11
11
|
import type { Bot } from "@/features/bot/bot.schema";
|
|
12
12
|
import { shortenPaths } from "@/features/bot/components/attachments";
|
|
13
13
|
import { BotMark } from "@/features/bot/components/bot-mark";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
roomOpens,
|
|
16
|
+
type ThreadView,
|
|
17
|
+
useBotThreads,
|
|
18
|
+
} from "@/features/bot/thread.store";
|
|
15
19
|
import { FileThumb } from "@/features/workspace/components/file-thumb";
|
|
16
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
opensOnFinish,
|
|
22
|
+
pathsIn,
|
|
23
|
+
viewKindOf,
|
|
24
|
+
} from "@/features/workspace/file-kind";
|
|
25
|
+
import { toDate } from "@/lib/date-like";
|
|
17
26
|
import { unwrapResult } from "@/lib/protocol/result";
|
|
18
27
|
import { revalidate, useServerRoute } from "@/lib/protocol/use-server-route";
|
|
19
|
-
import { errorToString } from "@/lib/utils";
|
|
28
|
+
import { errorToString, plainText } from "@/lib/utils";
|
|
20
29
|
import { FileViewer, useOpenFile } from "./file-view";
|
|
21
30
|
|
|
22
31
|
/**
|
|
@@ -52,6 +61,86 @@ export type Finished = {
|
|
|
52
61
|
/** File faces a card draws before the rest fold into a count. */
|
|
53
62
|
const FACES_SHOWN = 4;
|
|
54
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Cards waved off in this browser, newest first, so a reload does not bring them back. Short:
|
|
66
|
+
* a card also goes once its thread is read, which the server keeps.
|
|
67
|
+
*/
|
|
68
|
+
const DISMISSED_KEY = "thursday.corner-dismissed";
|
|
69
|
+
const DISMISSED_KEEP = 50;
|
|
70
|
+
|
|
71
|
+
function readDismissed(): string[] {
|
|
72
|
+
try {
|
|
73
|
+
const stored = JSON.parse(
|
|
74
|
+
window.localStorage.getItem(DISMISSED_KEY) ?? "[]",
|
|
75
|
+
);
|
|
76
|
+
return Array.isArray(stored) ? stored : [];
|
|
77
|
+
} catch {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function rememberDismissed(ids: string[]) {
|
|
83
|
+
try {
|
|
84
|
+
const kept = [
|
|
85
|
+
...ids,
|
|
86
|
+
...readDismissed().filter((id) => !ids.includes(id)),
|
|
87
|
+
].slice(0, DISMISSED_KEEP);
|
|
88
|
+
window.localStorage.setItem(DISMISSED_KEY, JSON.stringify(kept));
|
|
89
|
+
} catch {
|
|
90
|
+
// blocked storage: the card comes back after a reload, which is only a card
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* A job finished while this page is not the one in front: the browser's own notification,
|
|
96
|
+
* which brings Thursday forward when pressed — the card is waiting there. Nothing when the
|
|
97
|
+
* page is in front (the card says it) or notifications were never allowed (see
|
|
98
|
+
* `askToNotify`).
|
|
99
|
+
*/
|
|
100
|
+
function tellFinished(threadId: string, label: string, words: string) {
|
|
101
|
+
if (typeof Notification === "undefined") return;
|
|
102
|
+
if (Notification.permission !== "granted") return;
|
|
103
|
+
if (document.visibilityState === "visible" && document.hasFocus()) return;
|
|
104
|
+
try {
|
|
105
|
+
const shown = new Notification(label, {
|
|
106
|
+
body: words,
|
|
107
|
+
tag: threadId,
|
|
108
|
+
icon: "/icon.svg",
|
|
109
|
+
});
|
|
110
|
+
shown.onclick = () => {
|
|
111
|
+
window.focus();
|
|
112
|
+
shown.close();
|
|
113
|
+
};
|
|
114
|
+
} catch {
|
|
115
|
+
// a browser that shows none from a page; the card and the room still have it
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Asks once, in the browser's quiet way, whether this page may show a notification when a
|
|
121
|
+
* job finishes out of sight. Called as a call is placed: the moment someone starts handing
|
|
122
|
+
* out work, and never on load.
|
|
123
|
+
*/
|
|
124
|
+
export function askToNotify() {
|
|
125
|
+
if (typeof Notification === "undefined") return;
|
|
126
|
+
if (Notification.permission !== "default") return;
|
|
127
|
+
void Notification.requestPermission().catch(() => undefined);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** A finished job as a card, read off its row when no event brought it: after a reload. */
|
|
131
|
+
function cardOf(thread: ThreadView): Finished {
|
|
132
|
+
const files = pathsIn(thread.outcome ?? "");
|
|
133
|
+
const lead = files.findIndex(opensOnFinish);
|
|
134
|
+
return {
|
|
135
|
+
threadId: thread.id,
|
|
136
|
+
label: thread.label,
|
|
137
|
+
bot: thread.bot.name,
|
|
138
|
+
words: plainText(thread.outcome ?? "").slice(0, FINISHED_NOTICE.words),
|
|
139
|
+
paths:
|
|
140
|
+
lead > 0 ? [files[lead], ...files.filter((_, at) => at !== lead)] : files,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
55
144
|
function Notice() {
|
|
56
145
|
const [rows, setRows] = useState<Finished[]>([]);
|
|
57
146
|
const { data: bots } = useServerRoute<Bot[]>(queryKey.bot);
|
|
@@ -66,7 +155,8 @@ function Notice() {
|
|
|
66
155
|
event.paths[0],
|
|
67
156
|
event.paths.filter((path) => viewKindOf(path) === "image"),
|
|
68
157
|
),
|
|
69
|
-
finished: (event) =>
|
|
158
|
+
finished: (event) => {
|
|
159
|
+
tellFinished(event.threadId, event.label, event.words);
|
|
70
160
|
setRows((was) =>
|
|
71
161
|
[
|
|
72
162
|
{
|
|
@@ -79,11 +169,40 @@ function Notice() {
|
|
|
79
169
|
// A job that finishes twice (picked back up, ended again) keeps one card
|
|
80
170
|
...was.filter((row) => row.threadId !== event.threadId),
|
|
81
171
|
].slice(0, FINISHED_NOTICE.rows),
|
|
82
|
-
)
|
|
172
|
+
);
|
|
173
|
+
},
|
|
83
174
|
});
|
|
84
175
|
|
|
85
|
-
|
|
176
|
+
// What finished while the page was away, or before a reload, and is still unread: the
|
|
177
|
+
// corner holds it until it is opened or waved off, as an event would have put it there
|
|
178
|
+
const seeded = useRef(false);
|
|
179
|
+
useEffect(() => {
|
|
180
|
+
if (seeded.current || !threads.length) return;
|
|
181
|
+
seeded.current = true;
|
|
182
|
+
const dismissed = new Set(readDismissed());
|
|
183
|
+
const waiting = threads
|
|
184
|
+
.filter(
|
|
185
|
+
(thread) =>
|
|
186
|
+
thread.status === "done" && !thread.seen && !dismissed.has(thread.id),
|
|
187
|
+
)
|
|
188
|
+
.sort(
|
|
189
|
+
(a, b) => toDate(b.updatedAt).getTime() - toDate(a.updatedAt).getTime(),
|
|
190
|
+
);
|
|
191
|
+
if (!waiting.length) return;
|
|
192
|
+
setRows((was) =>
|
|
193
|
+
[
|
|
194
|
+
...was,
|
|
195
|
+
...waiting
|
|
196
|
+
.filter((thread) => !was.some((row) => row.threadId === thread.id))
|
|
197
|
+
.map(cardOf),
|
|
198
|
+
].slice(0, FINISHED_NOTICE.rows),
|
|
199
|
+
);
|
|
200
|
+
}, [threads]);
|
|
201
|
+
|
|
202
|
+
const drop = (threadId: string) => {
|
|
203
|
+
rememberDismissed([threadId]);
|
|
86
204
|
setRows((was) => was.filter((row) => row.threadId !== threadId));
|
|
205
|
+
};
|
|
87
206
|
|
|
88
207
|
// Read somewhere else (the room, `thread_show`): the card goes. A card is armed
|
|
89
208
|
// only once its thread was seen unread, since the thread list may still hold
|
|
@@ -147,7 +266,10 @@ function Notice() {
|
|
|
147
266
|
<span className="flex-1">{rows.length} new</span>
|
|
148
267
|
<button
|
|
149
268
|
type="button"
|
|
150
|
-
onClick={() =>
|
|
269
|
+
onClick={() => {
|
|
270
|
+
rememberDismissed(rows.map((row) => row.threadId));
|
|
271
|
+
setRows([]);
|
|
272
|
+
}}
|
|
151
273
|
className="rounded-md px-1 font-sans text-[11px] outline-none hover:text-foreground focus-visible:ring-3 focus-visible:ring-ring/50"
|
|
152
274
|
>
|
|
153
275
|
Clear all
|
package/guide/bots.md
CHANGED
|
@@ -67,7 +67,7 @@ clear later why the bot changed course. Saying the same to her on a call does th
|
|
|
67
67
|
A job ends in the thing that was asked for and a short report. Anything longer than a few
|
|
68
68
|
lines is a file. Nothing opens by itself: every finished job waits as a card in the corner at
|
|
69
69
|
the bottom left of the call screen — the bot, the job's name, how its answer begins, and its
|
|
70
|
-
files under that — until it is opened or dismissed. Opening a card, or a file on it, counts as
|
|
70
|
+
files under that — until it is opened or dismissed, a reload of the page included. Opening a card, or a file on it, counts as
|
|
71
71
|
reading the result: the thread stops showing as new, and she will not bring it up on a call.
|
|
72
72
|
Dismissing a card does not; the result still waits in its thread. The files also show under
|
|
73
73
|
the bot's words in the thread: pictures as thumbnails, a page or a written report as a small preview
|
|
@@ -80,10 +80,22 @@ folder, listed on its page in Settings › Bots.
|
|
|
80
80
|
|
|
81
81
|
## Routines: jobs that start by themselves
|
|
82
82
|
|
|
83
|
-
A routine is a bot, a job and a time: every weekday at nine, every six
|
|
84
|
-
telling Thursday — "every morning at nine, go through my mail"
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
A routine is a bot, a job and a time: tomorrow at eight, every weekday at nine, every six
|
|
84
|
+
hours. It is made by telling Thursday — "every morning at nine, go through my mail", "in an
|
|
85
|
+
hour, check the fares again" — or in **Settings › Routines**, which lists them all, each with
|
|
86
|
+
one switch. **When** has three kinds of time, and at most twelve routines exist:
|
|
87
|
+
|
|
88
|
+
- **Once**: a day (today, tomorrow, the two days after, or any day from the calendar) and a
|
|
89
|
+
time, or **In an hour**. It starts that once and then switches itself off; it stays in the
|
|
90
|
+
list with its run, and picking a new day and time sets it again. A time already gone is
|
|
91
|
+
refused.
|
|
92
|
+
- **On set days**: a time of day on the days picked. Every day, Weekdays and Weekends fill the
|
|
93
|
+
days in one press.
|
|
94
|
+
- **Every few hours**: 1, 2, 3, 6, 12 or 24 hours after each start; any other number can be
|
|
95
|
+
asked of Thursday.
|
|
96
|
+
|
|
97
|
+
What is picked is filled blue, and the line under the field says it back in words, with when
|
|
98
|
+
it first starts.
|
|
87
99
|
|
|
88
100
|
Each time it is due, a routine opens an ordinary thread, marked with a repeat sign, and that
|
|
89
101
|
thread behaves like any other: it shows in the corner of the call screen, a question pauses
|
package/guide/calls.md
CHANGED
|
@@ -74,10 +74,13 @@ keeps them under the search.
|
|
|
74
74
|
## Hearing the words
|
|
75
75
|
|
|
76
76
|
**Settings › Thursday › Captions** picks where the words show: under her face, or down the
|
|
77
|
-
two sides of the screen.
|
|
78
|
-
|
|
79
|
-
her side instead, under what she
|
|
80
|
-
ones fainter
|
|
77
|
+
two sides of the screen. A narrow window always shows them under her face. What she is
|
|
78
|
+
doing — a search, work handed to a bot — shows as one line under her face; with the words down
|
|
79
|
+
the sides it stands on her side instead, under what she said, the last three steps at a time
|
|
80
|
+
with the older ones fainter, and the steps stay with that answer for the rest of the call:
|
|
81
|
+
clicking an earlier answer of hers brings it back with the steps behind it. What she is
|
|
82
|
+
thinking about stays under her face either way. Every call is kept; **Settings › Thursday ›
|
|
83
|
+
History** opens them,
|
|
81
84
|
one call at a time, and deletes a single call or all of them.
|
|
82
85
|
|
|
83
86
|
## Her voice, her face, her models
|
|
@@ -98,13 +101,18 @@ one call at a time, and deletes a single call or all of them.
|
|
|
98
101
|
While a call is open, a question or a result from a bot comes into the conversation by
|
|
99
102
|
itself, in a quiet moment, and she says it.
|
|
100
103
|
|
|
101
|
-
With no call open,
|
|
104
|
+
With no call open, a finished job lands as a card in the bottom left corner, and if Thursday
|
|
105
|
+
is not the window in front, as a notification that brings her forward when pressed (the
|
|
106
|
+
browser asks once, the first time a call is placed). With no Thursday window open at all,
|
|
107
|
+
the computer's own notification says it instead; on a Mac pressing that one opens Script
|
|
108
|
+
Editor rather than the app. **Settings › Thursday › Starting a
|
|
102
109
|
call › She calls you** makes the screen ring instead: "When a job needs me" rings for a
|
|
103
110
|
question or a stop — where it starts — "Whenever a job ends" rings for results too, and
|
|
104
111
|
"Never" leaves it to the notification. It is one call however much is waiting: while it rings, the screen says under her
|
|
105
112
|
face whose work it is about and what it asks — with the answers the bot offered, and the
|
|
106
|
-
other work waiting by name — and one round button, Answer. Tapping her face answers too,
|
|
107
|
-
|
|
113
|
+
other work waiting by name — and one round button, Answer. Tapping her face answers too, and
|
|
114
|
+
so does the wake phrase when it is on (the screen says so beside "not now"); Esc is "not now".
|
|
115
|
+
She opens by saying why she called, then tells the rest one by one. A ring
|
|
108
116
|
nobody takes becomes a short missed list in the same place, with Call back, until it is
|
|
109
117
|
called back or cleared with Esc; the work itself stays in the room either way. She cannot
|
|
110
118
|
switch this on herself: when they ask to be
|
package/guide/setup.md
CHANGED
|
@@ -37,7 +37,8 @@ Settings › Thursday instead.
|
|
|
37
37
|
|
|
38
38
|
A skill is a written-down way of doing something that a bot reads before it starts. Some ship
|
|
39
39
|
with the app — the browser, this Mac, finding more skills, building a page or a chart, and
|
|
40
|
-
writing a new skill.
|
|
40
|
+
writing a new skill. The one for this Mac is not listed on other computers.
|
|
41
|
+
**Settings › Skills** lists them, switches one off, uploads one, or
|
|
41
42
|
writes one. A bot can also install one from the open registry while it works: for itself
|
|
42
43
|
alone unless they asked for every bot to have it. A bot's own skills sit in its own folder
|
|
43
44
|
and are not on that screen, which lists the shared ones.
|
|
@@ -49,6 +50,12 @@ details. Once connected, its tools are there for bots to search and call, and up
|
|
|
49
50
|
them can be pinned to one bot so they are always in front of it. Signing in happens in a
|
|
50
51
|
popup window; a server that needs it again turns red on that screen.
|
|
51
52
|
|
|
53
|
+
The list starts with everyday services — to-dos, the house, flights, and Zapier for Gmail,
|
|
54
|
+
Google Calendar and the other apps connected there — and the tools for developers come after.
|
|
55
|
+
Home Assistant is the one that asks for two things first: the address of their own Home
|
|
56
|
+
Assistant, and a long-lived access token made on their profile page there. Its Model Context
|
|
57
|
+
Protocol Server integration has to be added in Home Assistant too.
|
|
58
|
+
|
|
52
59
|
## Sites they signed in to
|
|
53
60
|
|
|
54
61
|
When a bot needs them signed in to a website — to post, to read mail, to order — it opens a
|
|
@@ -77,7 +84,8 @@ of the Settings list for as long as it is not installed. Safari on a Mac does it
|
|
|
77
84
|
its File menu, **Add to Dock**; Firefox cannot. It is the same local app either way; nothing
|
|
78
85
|
moves anywhere.
|
|
79
86
|
|
|
80
|
-
The app comes back on the same address every time it starts
|
|
87
|
+
The app comes back on the same address every time it starts: the first start takes port 4747,
|
|
88
|
+
which other apps rarely use, and keeps it. If that port is taken by
|
|
81
89
|
something else one day, it says so where it was started and serves on the next one for that
|
|
82
90
|
day; the browser then shows other settings, because a browser keeps settings per address.
|
|
83
91
|
Starting it once with `--port` and a number moves it to that address for good.
|
package/knip.json
CHANGED
package/lib/desktop-notify.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { execFile } from "node:child_process";
|
|
|
2
2
|
import { APP_NAME } from "@/config";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Native desktop notification for work that finished while
|
|
6
|
-
*
|
|
5
|
+
* Native desktop notification for work that finished while no page is open (with one open,
|
|
6
|
+
* the page shows its own). Best effort: the inbox is the durable signal, so failures are
|
|
7
|
+
* swallowed. On macOS it is AppleScript's, so pressing it opens Script Editor, not the app.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
/** The OS truncates longer text anyway. */
|
package/package.json
CHANGED
|
@@ -1326,7 +1326,7 @@ test("the call makes, reads and removes a routine, and is told when its time can
|
|
|
1326
1326
|
String(
|
|
1327
1327
|
await routine({ action: "create", ...job, time: "09:00", everyHours: 6 }),
|
|
1328
1328
|
),
|
|
1329
|
-
/
|
|
1329
|
+
/Give one of `at`, `time` or `everyHours`/,
|
|
1330
1330
|
);
|
|
1331
1331
|
assert.match(
|
|
1332
1332
|
String(
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Analyst's kit
|
|
2
|
+
|
|
3
|
+
One skill the Analyst seed is born with, copied into `.agents/skills/` in its own folder when it is
|
|
4
|
+
created. It is listed to that bot alone.
|
|
5
|
+
|
|
6
|
+
| Skill | What it adds |
|
|
7
|
+
|---|---|
|
|
8
|
+
| `data-report` | Seven report forms, each with its structure and a filled example; `fetch.mjs`, which pulls a published series (FRED, World Bank, Yahoo Finance, ECB rates, SEC filings, Wikipedia pageviews) into a CSV that names its source; `chart.mjs`, which draws a CSV into a quick page as a cited figure |
|
|
9
|
+
|
|
10
|
+
Written for this app. The forms borrow their shape from answer-first business writing and from the
|
|
11
|
+
report outlines in [anthropics/financial-services](https://github.com/anthropics/financial-services)
|
|
12
|
+
and [anthropics/knowledge-work-plugins](https://github.com/anthropics/knowledge-work-plugins)
|
|
13
|
+
(Apache-2.0); no text is copied from either. Those skills write Word, PowerPoint and Excel files and
|
|
14
|
+
read paid data connectors, which is why they are not taken whole.
|
|
15
|
+
|
|
16
|
+
`chart.mjs` fills a page made by the shipped `interactive-page` skill's quick path and uses its
|
|
17
|
+
colour tokens; it draws inline SVG, so the page still opens with no network. The scripts need Node
|
|
18
|
+
and no packages. Every source they call answers without a key; `references/sources.md` names the
|
|
19
|
+
ones that need one.
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: data-report
|
|
3
|
+
description: "Answers and reports built on real numbers: the form each kind of question wants (a brief, a comparison with a pick, a market size, a trend, a company snapshot, a price check, a buy-or-wait decision), scripts that fetch a published series into CSV with its source, and one that draws a CSV into a page as a cited chart."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Data report
|
|
7
|
+
|
|
8
|
+
## The form comes from the question
|
|
9
|
+
|
|
10
|
+
Pick one form and read only its file in `references/` before collecting anything: it says what to
|
|
11
|
+
collect, what the final text holds, and when a page earns its place.
|
|
12
|
+
|
|
13
|
+
| The question | Form |
|
|
14
|
+
|---|---|
|
|
15
|
+
| Anything else with numbers in it | `brief.md` |
|
|
16
|
+
| Which of these (products, plans, places, companies)? | `comparison.md` |
|
|
17
|
+
| How big is this market, how much could we sell? | `market-size.md` |
|
|
18
|
+
| How has X moved, and why? | `trend.md` |
|
|
19
|
+
| How is this company doing, how were its results? | `company.md` |
|
|
20
|
+
| Where is it cheapest to buy X right now? | `price-check.md` |
|
|
21
|
+
| Should I buy / sign / switch — or wait? | `decision.md` |
|
|
22
|
+
|
|
23
|
+
Every form opens with the answer and its two or three numbers; a reader who stops after the first
|
|
24
|
+
lines has what they asked for. A title says the finding ("Rent rose faster than pay"), not the topic.
|
|
25
|
+
|
|
26
|
+
## Numbers by script, not by hand
|
|
27
|
+
|
|
28
|
+
The skill's scripts are in `scripts/` of this skill's folder. `node` runs them; nothing to install.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
S=<this skill's folder>/scripts
|
|
32
|
+
# A series into CSV, with its source written into the file. It prints the rows' shape and both ends.
|
|
33
|
+
node $S/fetch.mjs fred DGS10,MORTGAGE30US --from 2020 --out <scratch>/rates.csv
|
|
34
|
+
node $S/fetch.mjs yahoo 005930.KS,^KS11 --from 2025-01 --interval 1wk --label Samsung,KOSPI --out <scratch>/px.csv
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`references/sources.md` says which source holds what, the series ids worth knowing, and what the
|
|
38
|
+
scripts cannot reach. A CSV you build from a page or a file you were given takes the same shape: a
|
|
39
|
+
`# source: <url>` line (and `# fetched: YYYY-MM-DD`) above a header row.
|
|
40
|
+
|
|
41
|
+
## A page: the quick page, with charts drawn into it
|
|
42
|
+
|
|
43
|
+
When the form calls for a page, start it as a quick page from your Skills list (one styled HTML file
|
|
44
|
+
in your folder under `artifacts/`) and write its words by hand. It arrives styled: replace the title
|
|
45
|
+
and the lede line and write below them, and leave its `<head>` as it is. Never write chart SVG or a
|
|
46
|
+
data table by hand: leave an empty `<figure id="…"></figure>` where each chart goes, then fill every
|
|
47
|
+
one in one bash call.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
node $S/chart.mjs "$THURSDAY_ARTIFACTS/<page>.html" rates <scratch>/rates.csv \
|
|
51
|
+
--title "Mortgage rates followed the 10-year down" --unit "%" --mark "2024-09=First Fed cut" --locale ko
|
|
52
|
+
node $S/chart.mjs "$THURSDAY_ARTIFACTS/<page>.html" px <scratch>/px.csv --index --title "…"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Each figure carries its source link, the fetch date and the rows behind it with a CSV download, so
|
|
56
|
+
the page needs no separate data section. A line for dates, bars for categories (largest first,
|
|
57
|
+
`--highlight` the one that matters); `--index` when series in different units are compared;
|
|
58
|
+
`--mark` for each dated event the text explains. Run it again to replace a figure.
|
|
59
|
+
`node $S/chart.mjs` with no arguments lists every option.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Brief
|
|
2
|
+
|
|
3
|
+
The default form: a question with numbers in it that no other form fits. Most answers end here,
|
|
4
|
+
as final text, with no page.
|
|
5
|
+
|
|
6
|
+
## Collect
|
|
7
|
+
|
|
8
|
+
The one to three figures that answer the question, each from the page or file that publishes it,
|
|
9
|
+
with that page's date. A figure you work out (a share, a change, a per-person cost) shows its
|
|
10
|
+
arithmetic in one line.
|
|
11
|
+
|
|
12
|
+
## Final text
|
|
13
|
+
|
|
14
|
+
1. The answer in one sentence, carrying its number.
|
|
15
|
+
2. Up to three lines, one reason each, each with its figure and source.
|
|
16
|
+
3. What is not solid: an estimate, a source older than the question, two sources that disagree
|
|
17
|
+
(give both).
|
|
18
|
+
|
|
19
|
+
A page is worth making only when the reasons need a chart or a table longer than five rows; then
|
|
20
|
+
the page is these same lines on top, the chart or table under them.
|
|
21
|
+
|
|
22
|
+
## Example (illustrative numbers)
|
|
23
|
+
|
|
24
|
+
> Q: How much has the price of a subway ride gone up in this city?
|
|
25
|
+
>
|
|
26
|
+
> Up 39% in ten years: ₩1,050 in 2015 to ₩1,450 now (city transit site, fares page, 2026).
|
|
27
|
+
> - The last rise was ₩1,250 → ₩1,400 in 2023, then ₩1,550 planned for next year was cut to
|
|
28
|
+
> ₩1,450 (city press release, 2025-06).
|
|
29
|
+
> - Consumer prices rose 24% over the same years (national statistics office CPI, 2015 = 100 → 124),
|
|
30
|
+
> so the fare grew faster than prices.
|
|
31
|
+
> - Not solid: the planned rise for next year is a proposal, not a decision.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Company snapshot
|
|
2
|
+
|
|
3
|
+
How a company is doing, or how its latest results went, in the terms investors and employees use.
|
|
4
|
+
|
|
5
|
+
## Collect
|
|
6
|
+
|
|
7
|
+
- The latest quarter and the same quarter a year earlier: revenue, operating income, net income,
|
|
8
|
+
margin; the full-year figures for context. From the company's own filing or results release —
|
|
9
|
+
a US filer through `fetch.mjs sec`, a Korean one from its DART filing or IR page.
|
|
10
|
+
- What management said it expects next (guidance), in their words, with the date.
|
|
11
|
+
- Consensus estimates, only from a page that shows them, with that page's name and date; the
|
|
12
|
+
beat or miss is actual against that.
|
|
13
|
+
- The share price reaction: `fetch.mjs yahoo`, the close before the release against the close
|
|
14
|
+
after, and against the market index over the same days.
|
|
15
|
+
- Valuation when asked: price ÷ earnings per share over the last four quarters, with both inputs.
|
|
16
|
+
|
|
17
|
+
## Final text
|
|
18
|
+
|
|
19
|
+
The headline in one sentence (grew or shrank, by how much, beat or missed), the one number
|
|
20
|
+
behind the reaction, and what to watch next quarter.
|
|
21
|
+
|
|
22
|
+
## Page
|
|
23
|
+
|
|
24
|
+
1. Title: the result as a finding. Lede: the headline and its cause.
|
|
25
|
+
2. A results table: this quarter, a year ago, change; estimates beside it when you have them.
|
|
26
|
+
3. Revenue and operating income by quarter, two years (`--quarterly`), as a line or bars.
|
|
27
|
+
4. The price around the release against the index (`--index`), marked at the release.
|
|
28
|
+
5. Three things to watch, each with the level that would matter.
|
|
29
|
+
|
|
30
|
+
## Example (illustrative numbers)
|
|
31
|
+
|
|
32
|
+
> **Northwind grew 18% but guided below what the market expected**
|
|
33
|
+
>
|
|
34
|
+
> | (₩ billion) | Q2 2026 | Q2 2025 | Change |
|
|
35
|
+
> |---|---|---|---|
|
|
36
|
+
> | Revenue | 1,240 | 1,051 | +18% |
|
|
37
|
+
> | Operating income | 161 | 147 | +10% |
|
|
38
|
+
> | Operating margin | 13.0% | 14.0% | −1.0 pt |
|
|
39
|
+
>
|
|
40
|
+
> Shares fell 6.2% the day after (index −0.4%): Q3 revenue guidance of ₩1,260–1,290 billion sat
|
|
41
|
+
> under the ₩1,330 billion consensus (brokerage summary on a finance portal, 2026-07-28).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Comparison with a pick
|
|
2
|
+
|
|
3
|
+
Options side by side — products, plans, cities, schools, companies — ending in one pick for what
|
|
4
|
+
the user said matters.
|
|
5
|
+
|
|
6
|
+
## Collect
|
|
7
|
+
|
|
8
|
+
- The options: the ones the user named, exactly those. Asked to choose them, three to five that a
|
|
9
|
+
person shopping today would really consider, and say why those.
|
|
10
|
+
- The criteria: what the user said matters first, then what decides this kind of choice (price
|
|
11
|
+
over the time it is owned, not only the sticker; the spec people regret skipping).
|
|
12
|
+
- Every cell from a page you opened, all on the same basis: the same period, the same definition,
|
|
13
|
+
one currency (say the exchange rate and its date). A cell you could not fill is "—" with the
|
|
14
|
+
reason in a note, never blank and never a guess.
|
|
15
|
+
|
|
16
|
+
## Final text
|
|
17
|
+
|
|
18
|
+
The pick and the one number that decides it, then the runner-up and when it wins instead
|
|
19
|
+
("Take B; if you fly more than twice a year, A pays for itself"). The page's path.
|
|
20
|
+
|
|
21
|
+
## Page
|
|
22
|
+
|
|
23
|
+
1. Title: the pick as a finding. Lede: the pick, its deciding number, and who should choose
|
|
24
|
+
otherwise.
|
|
25
|
+
2. The matrix: options as columns (or rows, when there are more than four), criteria as rows,
|
|
26
|
+
units in the row label, the best cell in each row marked, the pick's column marked. A photo
|
|
27
|
+
of each option when people choose it partly by how it looks.
|
|
28
|
+
3. One bar chart for the criterion that decides it (`--highlight` the pick).
|
|
29
|
+
4. Per option, two lines: what it is best at, what it gives up.
|
|
30
|
+
5. Sources and the date checked.
|
|
31
|
+
|
|
32
|
+
## Example (illustrative numbers)
|
|
33
|
+
|
|
34
|
+
> **Plan B costs the least for a family of four** — ₩118,000 a month against ₩131,000 and ₩146,000.
|
|
35
|
+
>
|
|
36
|
+
> | | Plan A | **Plan B** | Plan C |
|
|
37
|
+
> |---|---|---|---|
|
|
38
|
+
> | Monthly, 4 lines (₩) | 131,000 | **118,000** | 146,000 |
|
|
39
|
+
> | Data per line (GB) | 30 | 25 | **unlimited** |
|
|
40
|
+
> | Contract (months) | 24 | **none** | 24 |
|
|
41
|
+
>
|
|
42
|
+
> Plan C wins only if two or more of you use more than 25 GB most months.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Buy, sign, switch — or wait
|
|
2
|
+
|
|
3
|
+
A yes or no the user has to make, answered with the numbers it turns on.
|
|
4
|
+
|
|
5
|
+
## Collect
|
|
6
|
+
|
|
7
|
+
- The full cost of each path over the time it runs: price, running costs, what is lost by
|
|
8
|
+
leaving (a penalty, a resale value), what the money would earn otherwise.
|
|
9
|
+
- The break-even: the point (months, uses, a price, a rate) where the answer flips.
|
|
10
|
+
- What is known about the future that bears on it, from a source: a model launch date, a
|
|
11
|
+
scheduled rate decision, a seasonal sale, a published price increase.
|
|
12
|
+
|
|
13
|
+
## Final text
|
|
14
|
+
|
|
15
|
+
The verdict first — **Yes**, **No**, or **Wait until <when or what>** — with the number that
|
|
16
|
+
decides it, then the condition that would flip it. No verdict without a number under it.
|
|
17
|
+
|
|
18
|
+
## Page
|
|
19
|
+
|
|
20
|
+
Only when the costs need a table: each path's costs over time, the break-even as a line chart
|
|
21
|
+
(both paths' cumulative cost, crossing at the break-even), then what would change the verdict.
|
|
22
|
+
|
|
23
|
+
## Example (illustrative numbers)
|
|
24
|
+
|
|
25
|
+
> **Wait until November.** The current model costs ₩1,390,000; its successor is announced for
|
|
26
|
+
> 10-28 (maker's event page), and the current model dropped 18% within six weeks of the last
|
|
27
|
+
> launch (comparison site's price history, 2025). Buy now only if you need it before November:
|
|
28
|
+
> waiting is worth about ₩250,000.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Market size
|
|
2
|
+
|
|
3
|
+
How big a market is and how much of it is reachable: TAM, SAM, SOM, each with its arithmetic in
|
|
4
|
+
the open, so a reader can change one input and see the rest move.
|
|
5
|
+
|
|
6
|
+
## Collect
|
|
7
|
+
|
|
8
|
+
- A published total, when one exists: who measured it, for which year, what it counts (sales or
|
|
9
|
+
spending, which countries, which products). Two publishers usually disagree; take both.
|
|
10
|
+
- Bottom-up inputs, each with its source: how many buyers (a census, a registry, a company's
|
|
11
|
+
own count), what share of them buy, how often, at what price.
|
|
12
|
+
- Growth: the series itself (`fetch.mjs`), or the publisher's rate with its years.
|
|
13
|
+
|
|
14
|
+
## The arithmetic
|
|
15
|
+
|
|
16
|
+
Build it both ways and show both:
|
|
17
|
+
|
|
18
|
+
- **Top-down**: published total × the share that fits the question (region, segment) = SAM.
|
|
19
|
+
- **Bottom-up**: buyers × share who buy × purchases a year × price = SAM.
|
|
20
|
+
|
|
21
|
+
Each is a table of drivers, one row each: the value, its unit, its source. The result is a range
|
|
22
|
+
(low and high inputs), never one point. When the two ways differ by more than about 2×, say which
|
|
23
|
+
input is most likely wrong. SOM is SAM × a share you state and justify (what the leader holds,
|
|
24
|
+
what a new entrant took in its first years), never a share picked to look good.
|
|
25
|
+
|
|
26
|
+
## Final text
|
|
27
|
+
|
|
28
|
+
SAM as a range in one currency and year, the SOM that follows from the share you assumed, and the
|
|
29
|
+
input the answer depends on most.
|
|
30
|
+
|
|
31
|
+
## Page
|
|
32
|
+
|
|
33
|
+
Title: the size as a finding. Then the two driver tables, a bar chart of TAM / SAM / SOM, the
|
|
34
|
+
growth series as a line when there is one, and the assumptions the user may want to change.
|
|
35
|
+
|
|
36
|
+
## Example (illustrative numbers)
|
|
37
|
+
|
|
38
|
+
> **Home coffee subscriptions here: ₩180–260 billion a year (2025)**
|
|
39
|
+
>
|
|
40
|
+
> | Bottom-up driver | Value | Source |
|
|
41
|
+
> |---|---|---|
|
|
42
|
+
> | Households | 22.3 M | Statistics office, 2025 |
|
|
43
|
+
> | Buy beans online at least monthly | 4–6% | Industry survey, 2024 (n = 3,000) |
|
|
44
|
+
> | Orders a year | 10 | same survey |
|
|
45
|
+
> | Average order | ₩19,000 | median of 12 shops' listings, checked 2026-09 |
|
|
46
|
+
> | **SAM** | **₩170–255 billion** | 22.3 M × 4–6% × 10 × ₩19,000 |
|
|
47
|
+
>
|
|
48
|
+
> Top-down: ₩1.9 trillion coffee retail (market report, 2025) × 12% online bean share = ₩228 billion.
|
|
49
|
+
> SOM at 2% of SAM (what the third-largest shop holds): ₩3.4–5.1 billion.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Price check
|
|
2
|
+
|
|
3
|
+
Where one exact thing is cheapest to buy right now, and whether now is a good time.
|
|
4
|
+
|
|
5
|
+
## Collect
|
|
6
|
+
|
|
7
|
+
- The exact item: model number, size, color, condition (new, refurbished, used). Two listings of
|
|
8
|
+
"the same" thing often differ here; a cheaper one that is not the same item is left out or
|
|
9
|
+
said to differ.
|
|
10
|
+
- From each seller: the price the user pays at the door — item, shipping, tax or duty, any fee —
|
|
11
|
+
delivery date, return terms, and the listing's link. Open each listing; a search result's price
|
|
12
|
+
is often stale.
|
|
13
|
+
- Price history when a price tracker or comparison site shows it: the lowest and usual price over
|
|
14
|
+
months, so "cheapest now" can be told from "cheap".
|
|
15
|
+
- Imports: duty and tax at the user's border, and the exchange rate with its date.
|
|
16
|
+
|
|
17
|
+
## Final text
|
|
18
|
+
|
|
19
|
+
Where to buy and the total paid, how much it saves against the next best, and whether the price
|
|
20
|
+
is low, usual or high against its recent history. The time you checked.
|
|
21
|
+
|
|
22
|
+
## Page
|
|
23
|
+
|
|
24
|
+
Only when there are more than about five sellers or the history matters: a table sorted by total
|
|
25
|
+
paid (the pick marked), a bar chart of totals (`--highlight` the pick), the history as a line when
|
|
26
|
+
there is one.
|
|
27
|
+
|
|
28
|
+
## Example (illustrative numbers)
|
|
29
|
+
|
|
30
|
+
> Cheapest now: Shop B, ₩389,000 delivered by Friday — ₩24,000 under the next best (Shop A,
|
|
31
|
+
> ₩413,000 with ₩3,000 shipping). The lowest in the last six months was ₩372,000 (a comparison
|
|
32
|
+
> site's history, 2026-05); ₩389,000 is near the low end of its usual ₩385,000–420,000.
|
|
33
|
+
> Checked 2026-09-19 14:10.
|