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.
Files changed (301) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/build-manifest.json +3 -3
  3. package/.next/prerender-manifest.json +3 -3
  4. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  5. package/.next/server/app/_global-error.html +1 -1
  6. package/.next/server/app/_global-error.rsc +1 -1
  7. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  10. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  11. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  12. package/.next/server/app/_not-found.html +1 -1
  13. package/.next/server/app/_not-found.rsc +2 -2
  14. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  15. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  17. package/.next/server/app/api/artifact/route.js.nft.json +1 -1
  18. package/.next/server/app/api/bot/keep-working/route.js.nft.json +1 -1
  19. package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
  20. package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
  21. package/.next/server/app/api/bot/route.js.nft.json +1 -1
  22. package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -1
  23. package/.next/server/app/api/bot/thread/route.js.nft.json +1 -1
  24. package/.next/server/app/api/config/route.js.nft.json +1 -1
  25. package/.next/server/app/api/events/route.js.nft.json +1 -1
  26. package/.next/server/app/api/favicon/[host]/route.js.nft.json +1 -1
  27. package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
  28. package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
  29. package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
  30. package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
  31. package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
  32. package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
  33. package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
  34. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  35. package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
  36. package/.next/server/app/api/memory/edit/route.js +1 -1
  37. package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
  38. package/.next/server/app/api/memory/route.js.nft.json +1 -1
  39. package/.next/server/app/api/probe/route.js.nft.json +1 -1
  40. package/.next/server/app/api/reach/route.js +11 -11
  41. package/.next/server/app/api/reach/route.js.nft.json +1 -1
  42. package/.next/server/app/api/routine/route.js +6 -6
  43. package/.next/server/app/api/routine/route.js.nft.json +1 -1
  44. package/.next/server/app/api/signins/route.js.nft.json +1 -1
  45. package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
  46. package/.next/server/app/api/skills/route.js.nft.json +1 -1
  47. package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
  48. package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
  49. package/.next/server/app/api/thursday/text/route.js +11 -11
  50. package/.next/server/app/api/thursday/text/route.js.nft.json +1 -1
  51. package/.next/server/app/api/thursday/tool-call/route.js +6 -6
  52. package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
  53. package/.next/server/app/api/workspace/files/route.js.nft.json +1 -1
  54. package/.next/server/app/api/workspace/route.js.nft.json +1 -1
  55. package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
  56. package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
  57. package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
  58. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  59. package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -1
  60. package/.next/server/app/page/server-reference-manifest.json +56 -56
  61. package/.next/server/app/page.js +3 -3
  62. package/.next/server/app/page.js.nft.json +1 -1
  63. package/.next/server/app/page_client-reference-manifest.js +1 -1
  64. package/.next/server/chunks/[root-of-the-server]__012s2o8._.js +1 -1
  65. package/.next/server/chunks/[root-of-the-server]__04r4h-2._.js +1 -1
  66. package/.next/server/chunks/[root-of-the-server]__0mrk2it._.js +1 -1
  67. package/.next/server/chunks/[root-of-the-server]__0nmhgeo._.js +3 -0
  68. package/.next/server/chunks/[root-of-the-server]__1fy2g82._.js +3 -0
  69. package/.next/server/chunks/[root-of-the-server]__1ndi14y._.js +1 -1
  70. package/.next/server/chunks/[root-of-the-server]__1nmflqk._.js +3 -0
  71. package/.next/server/chunks/[root-of-the-server]__1v7-zke._.js +1 -1
  72. package/.next/server/chunks/[root-of-the-server]__1wnzmx9._.js +3 -0
  73. package/.next/server/chunks/[root-of-the-server]__1xv-5cq._.js +1 -1
  74. package/.next/server/chunks/[root-of-the-server]__1ygplx1._.js +1 -1
  75. package/.next/server/chunks/_05v8k21._.js +1 -1
  76. package/.next/server/chunks/_0awtznl._.js +4 -4
  77. package/.next/server/chunks/_0k6qjcf._.js +1 -1
  78. package/.next/server/chunks/_0na2fj1._.js +5 -5
  79. package/.next/server/chunks/_0x-ej3f._.js +21 -21
  80. package/.next/server/chunks/_1dc_d1q._.js +22 -20
  81. package/.next/server/chunks/_1jc5g4p._.js +2 -2
  82. package/.next/server/chunks/_1jn-aep._.js +50 -48
  83. package/.next/server/chunks/{_1u8ygbz._.js → _1m88lkh._.js} +7 -7
  84. package/.next/server/chunks/_1uuucy-._.js +4 -4
  85. package/.next/server/chunks/_1v9r2ke._.js +1 -1
  86. package/.next/server/chunks/ssr/{[root-of-the-server]__1h4bnrf._.js → [root-of-the-server]__0cxqzfc._.js} +2 -2
  87. package/.next/server/chunks/ssr/{[root-of-the-server]__1kyi568._.js → [root-of-the-server]__0tvavy9._.js} +1 -1
  88. package/.next/server/chunks/ssr/[root-of-the-server]__0tzekbl._.js +1 -1
  89. package/.next/server/chunks/ssr/[root-of-the-server]__1-d2tgh._.js +3 -0
  90. package/.next/server/chunks/ssr/[root-of-the-server]__1se25x0._.js +1 -1
  91. package/.next/server/chunks/ssr/{_1i3ddn7._.js → _05pl_u8._.js} +1 -1
  92. package/.next/server/chunks/ssr/_05rxyf9._.js +3 -0
  93. package/.next/server/chunks/ssr/_070nv1_._.js +3 -0
  94. package/.next/server/chunks/ssr/_097-ljd._.js +1 -1
  95. package/.next/server/chunks/ssr/{_10y6r1n._.js → _0bp-_fi._.js} +3 -3
  96. package/.next/server/chunks/ssr/_0fqhhwi._.js +1 -1
  97. package/.next/server/chunks/ssr/_0hlxij-._.js +3 -0
  98. package/.next/server/chunks/ssr/_0ld9_qq._.js +1 -1
  99. package/.next/server/chunks/ssr/{_0z8jpch._.js → _0p9pmxe._.js} +2 -2
  100. package/.next/server/chunks/ssr/{_1y-ea28._.js → _0s41irp._.js} +2 -2
  101. package/.next/server/chunks/ssr/_0tg0vf8._.js +164 -0
  102. package/.next/server/chunks/ssr/_0u5xv6g._.js +3 -0
  103. package/.next/server/chunks/ssr/{_1_rqu8x._.js → _12mijol._.js} +7 -7
  104. package/.next/server/chunks/ssr/_15zqt-t._.js +1 -1
  105. package/.next/server/chunks/ssr/{_1oijqjw._.js → _173siic._.js} +2 -2
  106. package/.next/server/chunks/ssr/_17v-33v._.js +20 -0
  107. package/.next/server/chunks/ssr/_184327j._.js +1 -1
  108. package/.next/server/chunks/ssr/{_1nod5hh._.js → _19n08g5._.js} +2 -2
  109. package/.next/server/chunks/ssr/_1alxym6._.js +3 -0
  110. package/.next/server/chunks/ssr/{_0hzz82u._.js → _1d423aj._.js} +25 -23
  111. package/.next/server/chunks/ssr/{_0ewory0._.js → _1doidrw._.js} +2 -2
  112. package/.next/server/chunks/ssr/{_0pm-2p-._.js → _1flsjeo._.js} +2 -2
  113. package/.next/server/chunks/ssr/_1mqn9mc._.js +1 -1
  114. package/.next/server/chunks/ssr/_1neoj22._.js +1 -1
  115. package/.next/server/chunks/ssr/_1ng831c._.js +3 -0
  116. package/.next/server/chunks/ssr/{_19g570d._.js → _1u2jw0i._.js} +3 -3
  117. package/.next/server/chunks/ssr/_1xoch6f._.js +1 -1
  118. package/.next/server/chunks/ssr/{_1u1mxu9._.js → _1yk6yak._.js} +2 -2
  119. package/.next/server/chunks/ssr/features_bot_1s_-2vj._.js +1 -1
  120. package/.next/server/chunks/ssr/features_bot_1yr-_pm._.js +3 -0
  121. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  122. package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
  123. package/.next/server/chunks/ssr/features_routine_components_routine-setting_tsx_055fknz._.js +1 -1
  124. package/.next/server/chunks/ssr/{node_modules_1_8mv4v._.js → node_modules_048wmmj._.js} +1 -1
  125. package/.next/server/chunks/ssr/{node_modules_1p9rdsy._.js → node_modules_04t200t._.js} +1 -1
  126. package/.next/server/chunks/ssr/{node_modules_03ptrl9._.js → node_modules_0511ha-._.js} +1 -1
  127. package/.next/server/chunks/ssr/{node_modules_07ih6wb._.js → node_modules_08g12f-._.js} +1 -1
  128. package/.next/server/chunks/ssr/{node_modules_01f8_se._.js → node_modules_0cdu6jq._.js} +1 -1
  129. package/.next/server/chunks/ssr/{node_modules_111sh9o._.js → node_modules_0eb4rx7._.js} +1 -1
  130. package/.next/server/chunks/ssr/{node_modules_10gbm2y._.js → node_modules_0f2k5l0._.js} +1 -1
  131. package/.next/server/chunks/ssr/{node_modules_1ix-g49._.js → node_modules_0piws2a._.js} +1 -1
  132. package/.next/server/chunks/ssr/{node_modules_0t-44lj._.js → node_modules_0pk6i-e._.js} +1 -1
  133. package/.next/server/chunks/ssr/{node_modules_0lg75qx._.js → node_modules_0s1lwc9._.js} +1 -1
  134. package/.next/server/chunks/ssr/{node_modules_1v9tc1c._.js → node_modules_0wq4-ts._.js} +1 -1
  135. package/.next/server/chunks/ssr/{node_modules_1m3gb16._.js → node_modules_0zjqwz7._.js} +1 -1
  136. package/.next/server/chunks/ssr/{node_modules_0b8df3s._.js → node_modules_11hbkuj._.js} +1 -1
  137. package/.next/server/chunks/ssr/{node_modules_00jv9_3._.js → node_modules_18woe20._.js} +1 -1
  138. package/.next/server/chunks/ssr/{node_modules_1x825hz._.js → node_modules_1_afpz7._.js} +1 -1
  139. package/.next/server/chunks/ssr/{node_modules_0woscox._.js → node_modules_1_ban4u._.js} +1 -1
  140. package/.next/server/chunks/ssr/{node_modules_0gt-uw9._.js → node_modules_1cxc8kn._.js} +1 -1
  141. package/.next/server/chunks/ssr/{node_modules_059vtf2._.js → node_modules_1dcbvfw._.js} +1 -1
  142. package/.next/server/chunks/ssr/{node_modules_1lsyy7k._.js → node_modules_1expacw._.js} +1 -1
  143. package/.next/server/chunks/ssr/{node_modules_1-7qawg._.js → node_modules_1ksh9gy._.js} +1 -1
  144. package/.next/server/chunks/ssr/{node_modules_06w645b._.js → node_modules_1m2ho33._.js} +1 -1
  145. package/.next/server/chunks/ssr/{node_modules_0sz5yg1._.js → node_modules_1n0h9sf._.js} +1 -1
  146. package/.next/server/chunks/ssr/{node_modules_1teyzfa._.js → node_modules_1qehmwm._.js} +1 -1
  147. package/.next/server/chunks/ssr/{node_modules_0yv-re6._.js → node_modules_1xzo2wn._.js} +1 -1
  148. package/.next/server/chunks/ssr/{node_modules_20xmwtu._.js → node_modules_1zf46lp._.js} +1 -1
  149. package/.next/server/chunks/ssr/{node_modules_0ii1l89._.js → node_modules_1zkpek5._.js} +1 -1
  150. package/.next/server/chunks/ssr/{node_modules_@base-ui_react_0jne69c._.js → node_modules_@base-ui_react_0buz1-j._.js} +1 -1
  151. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_00q1oa3._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0avl8-1._.js} +1 -1
  152. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1-2_5z7._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_14oslro._.js} +1 -1
  153. package/.next/server/middleware-build-manifest.js +3 -3
  154. package/.next/server/pages/404.html +1 -1
  155. package/.next/server/pages/500.html +1 -1
  156. package/.next/server/server-reference-manifest.js +1 -1
  157. package/.next/server/server-reference-manifest.json +57 -57
  158. package/.next/static/chunks/03kgfyrli_0gz.js +1 -0
  159. package/.next/static/chunks/{1e4yrzs5evq_r.js → 09120tms63y21.js} +1 -1
  160. package/.next/static/chunks/{2zhfdbq50ee5i.js → 0o980477kfqez.js} +1 -1
  161. package/.next/static/chunks/{1ocy4w0v20a3v.js → 0oz3rs47yy3p8.js} +2 -2
  162. package/.next/static/chunks/{2be08eqxsfc1y.js → 0w20wnnfrscq7.js} +1 -1
  163. package/.next/static/chunks/{09c13vnqy7al3.js → 19ojn4lomsk0r.js} +2 -2
  164. package/.next/static/chunks/{2om4lkg86feb4.js → 1mwee7xvitlp9.js} +1 -1
  165. package/.next/static/chunks/1p764d75_0lhv.js +18 -0
  166. package/.next/static/chunks/{0oz7a68qanm99.js → 1pjygp7hqib6j.js} +1 -1
  167. package/.next/static/chunks/22shnca_6zfzs.js +1 -0
  168. package/.next/static/chunks/{34s52v8f841zo.js → 24dst4mgurbsh.js} +1 -1
  169. package/.next/static/chunks/25zq8gzhbdsr7.js +1 -0
  170. package/.next/static/chunks/2d4z3er53ng8r.js +1 -0
  171. package/.next/static/chunks/{2d3yhjtafgmtw.js → 2ky81z-jgvasc.js} +1 -1
  172. package/.next/static/chunks/2m6jd3586ijb-.js +1 -0
  173. package/.next/static/chunks/{0k5tkt_qi52kb.js → 2ndw4w4u2x51r.js} +1 -1
  174. package/.next/static/chunks/2qod5tqlpz8b7.js +17 -0
  175. package/.next/static/chunks/{21gf2t_whhurc.js → 30skcjexa57zn.js} +2 -2
  176. package/.next/static/chunks/{2upc9mkcc3nlq.js → 35kwn237o4wdn.js} +1 -1
  177. package/.next/static/chunks/{2u9yjkpkoij75.js → 3exjsi8pfdvzj.js} +1 -1
  178. package/.next/static/chunks/3obf9n3cxj99k.js +1 -0
  179. package/.next/static/chunks/3qq4o8w2rv_e9.css +1 -0
  180. package/.next/static/chunks/{0g1p98s2pfx2w.js → 3vynnr71ghvom.js} +2 -2
  181. package/.next/static/chunks/{44kjmi5h_p8px.js → 42890jqk049q1.js} +1 -1
  182. package/README.md +35 -14
  183. package/bin/port.mjs +11 -7
  184. package/bin/thursday.mjs +1 -1
  185. package/components/ui/radio-group.tsx +1 -1
  186. package/components/ui/shiny-text.tsx +3 -22
  187. package/docs/how-it-works.md +11 -3
  188. package/docs/images/first-run.png +0 -0
  189. package/docs/images/keeps-going.png +0 -0
  190. package/docs/images/two-minds.png +0 -0
  191. package/features/ai/components/model-browser.tsx +1 -1
  192. package/features/ai/components/model-picker.tsx +1 -1
  193. package/features/ai/guide.ts +9 -4
  194. package/features/ai/prompts/bot.prompt.ts +4 -1
  195. package/features/ai/tools/routine.tool.ts +28 -16
  196. package/features/bot/bot.runner.ts +3 -1
  197. package/features/bot/components/bot-mark.tsx +6 -1
  198. package/features/bot/components/bot-setting.tsx +4 -6
  199. package/features/bot/components/bot-tool.tsx +2 -0
  200. package/features/bot/components/room-list.tsx +12 -3
  201. package/features/bot/components/room-pill.tsx +26 -19
  202. package/features/config/components/config-setting.tsx +17 -5
  203. package/features/config/components/voice-key.tsx +38 -18
  204. package/features/connectors/mcp.const.ts +90 -59
  205. package/features/intro/components/intro.tsx +50 -27
  206. package/features/routine/components/routine-mark.tsx +4 -6
  207. package/features/routine/components/routine-setting.tsx +332 -143
  208. package/features/routine/routine.query.ts +24 -3
  209. package/features/routine/routine.schema.ts +55 -4
  210. package/features/settings/components/install-app.tsx +1 -4
  211. package/features/settings/components/setting-ui.tsx +1 -1
  212. package/features/skills/skills.discover.ts +2 -1
  213. package/features/skills/skills.query.ts +5 -1
  214. package/features/skills/skills.schema.ts +2 -0
  215. package/features/thursday/components/ascii-orb.tsx +39 -10
  216. package/features/thursday/components/connect-wave.tsx +284 -0
  217. package/features/thursday/components/side-captions.tsx +29 -17
  218. package/features/thursday/components/thursday-setting.tsx +3 -3
  219. package/features/thursday/components/thursday.tsx +130 -58
  220. package/features/thursday/components/voice-picker.tsx +1 -1
  221. package/features/thursday/components/write-line.tsx +2 -2
  222. package/features/thursday/open-work.ts +11 -3
  223. package/features/thursday/tool-line.ts +94 -12
  224. package/features/thursday/use-text-call.ts +3 -2
  225. package/features/thursday/use-thursday.ts +5 -2
  226. package/features/workspace/components/artifact-view.tsx +129 -7
  227. package/guide/bots.md +17 -5
  228. package/guide/calls.md +15 -7
  229. package/guide/setup.md +10 -2
  230. package/knip.json +1 -0
  231. package/lib/desktop-notify.ts +3 -2
  232. package/package.json +1 -1
  233. package/scripts/bot-context.test.mts +1 -1
  234. package/seed-skills/analyst/README.md +19 -0
  235. package/seed-skills/analyst/data-report/SKILL.md +59 -0
  236. package/seed-skills/analyst/data-report/references/brief.md +31 -0
  237. package/seed-skills/analyst/data-report/references/company.md +41 -0
  238. package/seed-skills/analyst/data-report/references/comparison.md +42 -0
  239. package/seed-skills/analyst/data-report/references/decision.md +28 -0
  240. package/seed-skills/analyst/data-report/references/market-size.md +49 -0
  241. package/seed-skills/analyst/data-report/references/price-check.md +33 -0
  242. package/seed-skills/analyst/data-report/references/sources.md +51 -0
  243. package/seed-skills/analyst/data-report/references/trend.md +39 -0
  244. package/seed-skills/analyst/data-report/scripts/chart.mjs +679 -0
  245. package/seed-skills/analyst/data-report/scripts/fetch.mjs +521 -0
  246. package/seed-skills/insta/instagram/SKILL.md +33 -0
  247. package/seed-skills/insta/instagram/references/dm.md +49 -0
  248. package/seed-skills/insta/instagram/references/post.md +74 -0
  249. package/seed-skills/insta/instagram/scripts/compose.mjs +329 -0
  250. package/seed-skills/insta/instagram/scripts/image-size.mjs +30 -0
  251. package/seed-skills/insta/instagram/scripts/inbox.mjs +159 -0
  252. package/seed-skills/insta/instagram/scripts/lib.mjs +91 -0
  253. package/seed-skills/insta/instagram/scripts/media.mjs +104 -0
  254. package/seed-skills/insta/instagram/scripts/profile.mjs +139 -0
  255. package/seed-skills/insta/instagram/scripts/read.mjs +240 -0
  256. package/seed-skills/insta/instagram/scripts/render.mjs +126 -0
  257. package/seed-skills/insta/instagram/scripts/search.mjs +133 -0
  258. package/seed-skills/insta/instagram/scripts/selectors.mjs +56 -0
  259. package/seed-skills/insta/instagram/scripts/sheet.mjs +100 -0
  260. package/seed-skills/insta/instagram/scripts/webimage.mjs +105 -0
  261. package/seed-skills/insta/instagram/templates/carousel.html +62 -0
  262. package/seed-skills/mail/mail/SKILL.md +37 -0
  263. package/seed-skills/mail/mail/recipes/gmail.mjs +448 -0
  264. package/seed-skills/mail/mail/references/recipes.md +55 -0
  265. package/seed-skills/mail/mail/scripts/mail.mjs +202 -0
  266. package/seed-skills/marketer/README.md +6 -4
  267. package/seed-skills/marketer/copywriting/SKILL.md +1 -1
  268. package/seed-skills/marketer/emails/SKILL.md +1 -1
  269. package/seed-skills/marketer/launch/SKILL.md +1 -1
  270. package/seed-skills/marketer/product-marketing/SKILL.md +5 -5
  271. package/seed-skills/marketer/seo-audit/SKILL.md +1 -1
  272. package/seed-skills/marketer/social/SKILL.md +1 -1
  273. package/skills/computer/SKILL.md +1 -0
  274. package/skills/interactive-page/SKILL.md +4 -0
  275. package/.next/server/chunks/[root-of-the-server]__0n-l_n_._.js +0 -3
  276. package/.next/server/chunks/[root-of-the-server]__0wvo2of._.js +0 -3
  277. package/.next/server/chunks/[root-of-the-server]__1e6aut3._.js +0 -3
  278. package/.next/server/chunks/[root-of-the-server]__1ziojdq._.js +0 -3
  279. package/.next/server/chunks/ssr/[root-of-the-server]__1srp8l5._.js +0 -3
  280. package/.next/server/chunks/ssr/_0mes4s0._.js +0 -3
  281. package/.next/server/chunks/ssr/_0ya0cb0._.js +0 -3
  282. package/.next/server/chunks/ssr/_0yto5s7._.js +0 -20
  283. package/.next/server/chunks/ssr/_10dw2nv._.js +0 -3
  284. package/.next/server/chunks/ssr/_1p0gxeo._.js +0 -164
  285. package/.next/server/chunks/ssr/_1r41yvh._.js +0 -3
  286. package/.next/server/chunks/ssr/_1r79ksv._.js +0 -3
  287. package/.next/server/chunks/ssr/features_bot_1ogjt52._.js +0 -3
  288. package/.next/server/chunks/ssr/node_modules_@base-ui_react_0b3d4m9._.js +0 -3
  289. package/.next/static/chunks/03omwz_vi2ndg.js +0 -1
  290. package/.next/static/chunks/0_1bqgd6wt_og.js +0 -1
  291. package/.next/static/chunks/0abgkzikaznxc.js +0 -1
  292. package/.next/static/chunks/0bddkpash59-o.js +0 -17
  293. package/.next/static/chunks/0c2-bzhy3a0ib.js +0 -1
  294. package/.next/static/chunks/0iilfb1ocevnx.js +0 -1
  295. package/.next/static/chunks/0imbhx-j830a_.css +0 -1
  296. package/.next/static/chunks/24h1y9dhy3m_2.js +0 -1
  297. package/.next/static/chunks/2f_5saocsko_b.js +0 -1
  298. package/.next/static/chunks/2lwbr5vx0ixaj.js +0 -18
  299. /package/.next/static/{UiOMPYpQ4fqGyu5PzS-7j → Ar5V4kMdL4CE1trx8anrH}/_buildManifest.js +0 -0
  300. /package/.next/static/{UiOMPYpQ4fqGyu5PzS-7j → Ar5V4kMdL4CE1trx8anrH}/_clientMiddlewareManifest.js +0 -0
  301. /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 { roomOpens, useBotThreads } from "@/features/bot/thread.store";
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 { viewKindOf } from "@/features/workspace/file-kind";
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
- const drop = (threadId: string) =>
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={() => setRows([])}
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 hours. It is made by
84
- telling Thursday — "every morning at nine, go through my mail" — or in **Settings ›
85
- Routines**, which lists them all, each with one switch. There are two kinds of time, a time of
86
- day on chosen days, or every so many hours, and at most twelve routines.
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. What she is doing — a search, work handed to a bot, what she is
78
- thinking about — shows as one line under her face; with the words down the sides it stands on
79
- her side instead, under what she last said, the last three steps at a time with the older
80
- ones fainter. Every call is kept; **Settings › Thursday › History** opens them,
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, the computer shows a notification. **Settings › Thursday › Starting a
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, Esc
107
- is "not now", and she opens by saying why she called, then tells the rest one by one. A ring
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. **Settings › Skills** lists them, switches one off, uploads one, or
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. If that port is taken by
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
@@ -3,6 +3,7 @@
3
3
  "entry": ["scripts/intro-voice.mts", ".agents/skills/*/*.cjs"],
4
4
  "ignore": [
5
5
  "skills/**",
6
+ "seed-skills/**",
6
7
  "components/ui/**",
7
8
  ".agents/skills/!(verify|cleanup|checkup)/**"
8
9
  ],
@@ -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 nobody is on the
6
- * line. Best effort: the inbox is the durable signal, so failures are swallowed.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thursday-agent",
3
- "version": "0.8.0",
3
+ "version": "0.10.0",
4
4
  "description": "A voice agent that runs on your own machine — talk to her, she hands the slow work to bots with a shell, a browser and skills.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -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
- /not both/,
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.