thursday-agent 0.10.0 → 0.11.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 (293) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +0 -1
  3. package/.next/build-manifest.json +3 -3
  4. package/.next/prerender-manifest.json +3 -3
  5. package/.next/routes-manifest.json +0 -6
  6. package/.next/server/app/_global-error/page.js.nft.json +1 -1
  7. package/.next/server/app/_global-error.html +1 -1
  8. package/.next/server/app/_global-error.rsc +1 -1
  9. package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  10. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/.next/server/app/_not-found/page.js.nft.json +1 -1
  13. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/_not-found.html +1 -1
  15. package/.next/server/app/_not-found.rsc +2 -2
  16. package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
  17. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  18. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  19. package/.next/server/app/api/artifact/route.js.nft.json +1 -1
  20. package/.next/server/app/api/bot/keep-working/route.js.nft.json +1 -1
  21. package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
  22. package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
  23. package/.next/server/app/api/bot/route.js.nft.json +1 -1
  24. package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -1
  25. package/.next/server/app/api/bot/thread/route.js.nft.json +1 -1
  26. package/.next/server/app/api/config/route.js.nft.json +1 -1
  27. package/.next/server/app/api/events/route.js.nft.json +1 -1
  28. package/.next/server/app/api/favicon/[host]/route.js.nft.json +1 -1
  29. package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
  30. package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
  31. package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
  32. package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
  33. package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
  34. package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
  35. package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
  36. package/.next/server/app/api/mcp/route.js.nft.json +1 -1
  37. package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
  38. package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
  39. package/.next/server/app/api/memory/route.js.nft.json +1 -1
  40. package/.next/server/app/api/reach/route.js +13 -13
  41. package/.next/server/app/api/reach/route.js.nft.json +1 -1
  42. package/.next/server/app/api/routine/route.js.nft.json +1 -1
  43. package/.next/server/app/api/signins/route.js +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.nft.json +1 -1
  50. package/.next/server/app/api/thursday/tool-call/route.js +14 -15
  51. package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
  52. package/.next/server/app/api/workspace/files/route.js.nft.json +1 -1
  53. package/.next/server/app/api/workspace/route.js.nft.json +1 -1
  54. package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
  55. package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
  56. package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
  57. package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
  58. package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -1
  59. package/.next/server/app/page/server-reference-manifest.json +56 -56
  60. package/.next/server/app/page.js +4 -4
  61. package/.next/server/app/page.js.nft.json +1 -1
  62. package/.next/server/app/page_client-reference-manifest.js +1 -1
  63. package/.next/server/app-paths-manifest.json +0 -1
  64. package/.next/server/chunks/[root-of-the-server]__014ydvq._.js +2 -2
  65. package/.next/server/chunks/[root-of-the-server]__0209ah4._.js +7 -5
  66. package/.next/server/chunks/[root-of-the-server]__04r4h-2._.js +4 -2
  67. package/.next/server/chunks/[root-of-the-server]__0542v_d._.js +3 -0
  68. package/.next/server/chunks/[root-of-the-server]__0gewdg9._.js +2 -2
  69. package/.next/server/chunks/[root-of-the-server]__0mrk2it._.js +1 -1
  70. package/.next/server/chunks/[root-of-the-server]__1-swlkw._.js +2 -2
  71. package/.next/server/chunks/[root-of-the-server]__12iddfq._.js +1 -1
  72. package/.next/server/chunks/[root-of-the-server]__1ai5lme._.js +3 -3
  73. package/.next/server/chunks/[root-of-the-server]__1cjxw0-._.js +2 -2
  74. package/.next/server/chunks/{[root-of-the-server]__1fy2g82._.js → [root-of-the-server]__1k3j52n._.js} +1 -1
  75. package/.next/server/chunks/[root-of-the-server]__1kyjt5m._.js +3 -3
  76. package/.next/server/chunks/[root-of-the-server]__1ygplx1._.js +1 -1
  77. package/.next/server/chunks/_05v8k21._.js +3 -1
  78. package/.next/server/chunks/_0awtznl._.js +1 -1
  79. package/.next/server/chunks/_0na2fj1._.js +1 -1
  80. package/.next/server/chunks/_0x-ej3f._.js +17 -17
  81. package/.next/server/chunks/_12-amdu._.js +2 -2
  82. package/.next/server/chunks/_15740dw._.js +60 -0
  83. package/.next/server/chunks/_1dc_d1q._.js +3 -3
  84. package/.next/server/chunks/_1i350l5._.js +3 -1
  85. package/.next/server/chunks/_1jc5g4p._.js +2 -2
  86. package/.next/server/chunks/_1jn-aep._.js +4 -4
  87. package/.next/server/chunks/_1m88lkh._.js +11 -11
  88. package/.next/server/chunks/_1ogqovh._.js +4 -4
  89. package/.next/server/chunks/_1uuucy-._.js +1 -1
  90. package/.next/server/chunks/_1zxp-p6._.js +1 -1
  91. package/.next/server/chunks/features_0rfvely._.js +5 -3
  92. package/.next/server/chunks/ssr/{[root-of-the-server]__1se25x0._.js → [root-of-the-server]__0hpp3nw._.js} +29 -13
  93. package/.next/server/chunks/ssr/[root-of-the-server]__0m7u9j-._.js +3 -0
  94. package/.next/server/chunks/ssr/[root-of-the-server]__0tzekbl._.js +24 -60
  95. package/.next/server/chunks/ssr/[root-of-the-server]__1cz2rz5._.js +3 -0
  96. package/.next/server/chunks/ssr/{[root-of-the-server]__0tvavy9._.js → [root-of-the-server]__1iwyp1e._.js} +1 -1
  97. package/.next/server/chunks/ssr/[root-of-the-server]__1w9hqq8._.js +1 -1
  98. package/.next/server/chunks/ssr/_097-ljd._.js +1 -1
  99. package/.next/server/chunks/ssr/{_173siic._.js → _0btzccu._.js} +2 -2
  100. package/.next/server/chunks/ssr/_0fqhhwi._.js +1 -1
  101. package/.next/server/chunks/ssr/{_1ng831c._.js → _0hjzb5o._.js} +2 -2
  102. package/.next/server/chunks/ssr/_0ld9_qq._.js +1 -1
  103. package/.next/server/chunks/ssr/{_1doidrw._.js → _0ouzu9f._.js} +2 -2
  104. package/.next/server/chunks/ssr/{_1flsjeo._.js → _0u7ko71._.js} +2 -2
  105. package/.next/server/chunks/ssr/{_0hlxij-._.js → _0wd_o8-._.js} +2 -2
  106. package/.next/server/chunks/ssr/_0ya0cb0._.js +3 -0
  107. package/.next/server/chunks/ssr/_10yyqtk._.js +67 -0
  108. package/.next/server/chunks/ssr/{_0bp-_fi._.js → _12eyown._.js} +3 -3
  109. package/.next/server/chunks/ssr/_12mijol._.js +11 -11
  110. package/.next/server/chunks/ssr/_132qkfz._.js +1 -1
  111. package/.next/server/chunks/ssr/_184327j._.js +1 -1
  112. package/.next/server/chunks/ssr/{_0tg0vf8._.js → _19ps4p3._.js} +7 -7
  113. package/.next/server/chunks/ssr/{_1yk6yak._.js → _1bhy4yg._.js} +2 -2
  114. package/.next/server/chunks/ssr/_1d423aj._.js +7 -5
  115. package/.next/server/chunks/ssr/{_0s41irp._.js → _1kd843u._.js} +2 -2
  116. package/.next/server/chunks/ssr/{_0p9pmxe._.js → _1kq2ye_._.js} +2 -2
  117. package/.next/server/chunks/ssr/_1mqn9mc._.js +1 -1
  118. package/.next/server/chunks/ssr/_1neoj22._.js +1 -1
  119. package/.next/server/chunks/ssr/_1p8xorg._.js +3 -0
  120. package/.next/server/chunks/ssr/{_17v-33v._.js → _1q683w8._.js} +3 -3
  121. package/.next/server/chunks/ssr/_1tawxu2._.js +3 -0
  122. package/.next/server/chunks/ssr/{_05rxyf9._.js → _1w2oba6._.js} +2 -2
  123. package/.next/server/chunks/ssr/_1xoch6f._.js +1 -1
  124. package/.next/server/chunks/ssr/{_1u2jw0i._.js → _1ybo1hd._.js} +3 -3
  125. package/.next/server/chunks/ssr/{features_bot_1yr-_pm._.js → features_bot_0s11m4r._.js} +2 -2
  126. package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
  127. package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
  128. package/.next/server/chunks/ssr/features_routine_components_routine-setting_tsx_055fknz._.js +1 -1
  129. package/.next/server/chunks/ssr/features_workspace_00jz2hu._.js +1 -1
  130. package/.next/server/chunks/ssr/{node_modules_04t200t._.js → node_modules_00pocul._.js} +1 -1
  131. package/.next/server/chunks/ssr/{node_modules_1dcbvfw._.js → node_modules_02zt4az._.js} +1 -1
  132. package/.next/server/chunks/ssr/{node_modules_0eb4rx7._.js → node_modules_05ojmcl._.js} +1 -1
  133. package/.next/server/chunks/ssr/{node_modules_08g12f-._.js → node_modules_06i6bli._.js} +1 -1
  134. package/.next/server/chunks/ssr/{node_modules_1expacw._.js → node_modules_09dj429._.js} +1 -1
  135. package/.next/server/chunks/ssr/{node_modules_0wq4-ts._.js → node_modules_0auzgvx._.js} +1 -1
  136. package/.next/server/chunks/ssr/{node_modules_048wmmj._.js → node_modules_0evjwh1._.js} +1 -1
  137. package/.next/server/chunks/ssr/{node_modules_1qehmwm._.js → node_modules_0gmdur8._.js} +1 -1
  138. package/.next/server/chunks/ssr/{node_modules_1ksh9gy._.js → node_modules_0j3te-1._.js} +1 -1
  139. package/.next/server/chunks/ssr/{node_modules_1n0h9sf._.js → node_modules_0rbaymq._.js} +1 -1
  140. package/.next/server/chunks/ssr/{node_modules_0511ha-._.js → node_modules_0tf8_1e._.js} +1 -1
  141. package/.next/server/chunks/ssr/{node_modules_18woe20._.js → node_modules_0yqy-0b._.js} +1 -1
  142. package/.next/server/chunks/ssr/{node_modules_0zjqwz7._.js → node_modules_1-53ebx._.js} +1 -1
  143. package/.next/server/chunks/ssr/{node_modules_1zf46lp._.js → node_modules_1-y9aho._.js} +1 -1
  144. package/.next/server/chunks/ssr/{node_modules_1xzo2wn._.js → node_modules_11bltzp._.js} +1 -1
  145. package/.next/server/chunks/ssr/{node_modules_11hbkuj._.js → node_modules_18jn2qo._.js} +1 -1
  146. package/.next/server/chunks/ssr/{node_modules_1zkpek5._.js → node_modules_1_te3bu._.js} +1 -1
  147. package/.next/server/chunks/ssr/{node_modules_0cdu6jq._.js → node_modules_1c0pg3k._.js} +1 -1
  148. package/.next/server/chunks/ssr/{node_modules_0piws2a._.js → node_modules_1en2jx5._.js} +1 -1
  149. package/.next/server/chunks/ssr/{node_modules_1m2ho33._.js → node_modules_1p-c8cr._.js} +1 -1
  150. package/.next/server/chunks/ssr/{node_modules_0pk6i-e._.js → node_modules_1qe-oxg._.js} +1 -1
  151. package/.next/server/chunks/ssr/{node_modules_0s1lwc9._.js → node_modules_1rjt833._.js} +1 -1
  152. package/.next/server/chunks/ssr/{node_modules_1_afpz7._.js → node_modules_1tzw5sn._.js} +1 -1
  153. package/.next/server/chunks/ssr/{node_modules_1cxc8kn._.js → node_modules_1x6uwvn._.js} +1 -1
  154. package/.next/server/chunks/ssr/{node_modules_0f2k5l0._.js → node_modules_1zqddke._.js} +1 -1
  155. package/.next/server/chunks/ssr/{node_modules_1_ban4u._.js → node_modules_211zgim._.js} +1 -1
  156. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0avl8-1._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_0y51bqj._.js} +1 -1
  157. package/.next/server/chunks/ssr/{node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_14oslro._.js → node_modules_@mermaid-js_parser_dist_chunks_mermaid-parser_core_1sc114e._.js} +1 -1
  158. package/.next/server/middleware-build-manifest.js +3 -3
  159. package/.next/server/pages/404.html +1 -1
  160. package/.next/server/pages/500.html +1 -1
  161. package/.next/server/server-reference-manifest.js +1 -1
  162. package/.next/server/server-reference-manifest.json +57 -57
  163. package/.next/static/chunks/{0oz3rs47yy3p8.js → 03-724sj0nk_z.js} +2 -2
  164. package/.next/static/chunks/{42890jqk049q1.js → 03bhu4mk8en2q.js} +1 -1
  165. package/.next/static/chunks/{3vynnr71ghvom.js → 04imzjhvzw65d.js} +2 -2
  166. package/.next/static/chunks/{1pjygp7hqib6j.js → 0gljzv9fz1g7d.js} +1 -1
  167. package/.next/static/chunks/{2d4z3er53ng8r.js → 0h0lythv1ql93.js} +1 -1
  168. package/.next/static/chunks/{2o4dndk0d68do.js → 0hzbj7f48cmx0.js} +1 -1
  169. package/.next/static/chunks/{2ky81z-jgvasc.js → 0lwunwbl7u7pt.js} +1 -1
  170. package/.next/static/chunks/{0u9ae9k_2g7k3.js → 0r6nwxg4dv83t.js} +1 -1
  171. package/.next/static/chunks/0rn9nbg6aif9f.js +17 -0
  172. package/.next/static/chunks/{19ojn4lomsk0r.js → 0x_w4shtvrrfg.js} +1 -1
  173. package/.next/static/chunks/{2sdwbfs1x5skp.js → 0ympuji_ufypd.js} +1 -1
  174. package/.next/static/chunks/{3t9huugh-5_ps.js → 0zmrz4zfzm4dt.js} +1 -1
  175. package/.next/static/chunks/{3exjsi8pfdvzj.js → 11tlxkr7a_rpc.js} +1 -1
  176. package/.next/static/chunks/{0o980477kfqez.js → 12scesro428m7.js} +2 -2
  177. package/.next/static/chunks/16s2fx3890_jd.js +65 -0
  178. package/.next/static/chunks/{0oyz95sujd2c7.js → 1qzfgplyo18tc.js} +1 -1
  179. package/.next/static/chunks/{04e-e--5lk26x.js → 1r4zajz14jp9-.js} +1 -1
  180. package/.next/static/chunks/1s6m4cmbn7htf.js +1 -0
  181. package/.next/static/chunks/1wl9t1-3i-q8p.css +1 -0
  182. package/.next/static/chunks/2-asot3p1j3yg.js +1 -0
  183. package/.next/static/chunks/{0u3ktztrzu1fa.js → 2-jn4xscbm-i2.js} +1 -1
  184. package/.next/static/chunks/{0w20wnnfrscq7.js → 22h9gvclqtx_2.js} +1 -1
  185. package/.next/static/chunks/{337g9g_ux2gj-.js → 26-eblxwnykck.js} +1 -1
  186. package/.next/static/chunks/{43q9cgbuw8krx.js → 2kzwhbuobpkw3.js} +2 -2
  187. package/.next/static/chunks/{22shnca_6zfzs.js → 304q9p54tcgm1.js} +1 -1
  188. package/.next/static/chunks/{1u5i-jti87jbo.js → 35wdx3wt7cg4a.js} +1 -1
  189. package/.next/static/chunks/{1p764d75_0lhv.js → 37unh9460im7v.js} +2 -2
  190. package/.next/static/chunks/{01xjer0i_e5z5.js → 39gj5vxz1zpm_.js} +1 -1
  191. package/.next/static/chunks/{35kwn237o4wdn.js → 39vrqy1r_00jc.js} +1 -1
  192. package/.next/static/chunks/{09120tms63y21.js → 3dipmu0mptqum.js} +1 -1
  193. package/.next/static/chunks/3gki-squw28yu.js +1 -0
  194. package/.next/static/chunks/{3obf9n3cxj99k.js → 3jvldk2wvwbge.js} +1 -1
  195. package/.next/static/chunks/{1mwee7xvitlp9.js → 3nnzdl_te20hv.js} +1 -1
  196. package/.next/static/chunks/{2m267diam_vib.js → 3tz4dr0_8pq1-.js} +1 -1
  197. package/.next/static/chunks/{2gocl3dq0_h1s.js → 3x6pggb-2kyrk.js} +1 -1
  198. package/.next/static/chunks/{025bmrgeukcb2.js → 4440mws9jz3mt.js} +1 -1
  199. package/README.md +1 -1
  200. package/app/api/thursday/tool-call/route.ts +1 -15
  201. package/docs/images/demo-still.jpg +0 -0
  202. package/docs/live-calls.md +19 -11
  203. package/features/ai/prompts/live.prompt.ts +51 -26
  204. package/features/ai/prompts/prompt-helper.ts +1 -9
  205. package/features/ai/prompts/thursday.prompt.ts +8 -2
  206. package/features/bot/bot.run.ts +9 -3
  207. package/features/bot/bot.seed.ts +35 -91
  208. package/features/bot/components/bot-room.tsx +0 -6
  209. package/features/bot/components/room-pill.tsx +4 -2
  210. package/features/bot/seed-bots.ts +2 -2
  211. package/features/intro/components/intro.tsx +2 -3
  212. package/features/reach/reach.ts +2 -2
  213. package/features/signins/signins.query.ts +66 -1
  214. package/features/thursday/components/side-captions.tsx +31 -8
  215. package/features/thursday/components/thursday.tsx +97 -39
  216. package/features/thursday/components/write-line.tsx +28 -17
  217. package/features/thursday/thursday.action.ts +4 -20
  218. package/features/thursday/thursday.schema.ts +0 -9
  219. package/features/thursday/thursday.store.ts +11 -15
  220. package/features/thursday/thursday.text.ts +10 -8
  221. package/features/thursday/tool-line.ts +17 -6
  222. package/features/thursday/use-call-ring.ts +1 -5
  223. package/features/thursday/use-text-call.ts +37 -21
  224. package/features/thursday/use-thursday.ts +14 -44
  225. package/features/workspace/workspace.ts +4 -2
  226. package/guide/bots.md +12 -2
  227. package/guide/calls.md +7 -5
  228. package/lib/live/live.session.ts +0 -39
  229. package/package.json +1 -1
  230. package/scripts/live-session.test.mts +19 -26
  231. package/seed-skills/analyst/README.md +3 -4
  232. package/seed-skills/analyst/data-report/SKILL.md +5 -4
  233. package/seed-skills/insta/instagram/SKILL.md +5 -2
  234. package/seed-skills/insta/instagram/references/post.md +2 -2
  235. package/seed-skills/insta/instagram/scripts/compose.mjs +5 -1
  236. package/seed-skills/insta/instagram/scripts/inbox.mjs +5 -1
  237. package/seed-skills/insta/instagram/scripts/lib.mjs +12 -70
  238. package/seed-skills/insta/instagram/scripts/media.mjs +1 -1
  239. package/seed-skills/insta/instagram/scripts/profile.mjs +6 -2
  240. package/seed-skills/insta/instagram/scripts/read.mjs +5 -1
  241. package/seed-skills/insta/instagram/scripts/search.mjs +6 -2
  242. package/seed-skills/insta/instagram/scripts/sheet.mjs +31 -88
  243. package/seed-skills/insta/instagram/templates/carousel.html +1 -1
  244. package/seed-skills/tutor/picture-book/SKILL.md +16 -0
  245. package/seed-skills/tutor/picture-book/book/book.css +181 -0
  246. package/seed-skills/tutor/picture-book/book/book.html +33 -0
  247. package/seed-skills/tutor/picture-book/book/book.js +60 -0
  248. package/seed-skills/tutor/picture-book/references/picture-book.md +104 -0
  249. package/seed-skills/tutor/picture-book/scripts/book-video.mjs +177 -0
  250. package/seed-skills/tutor/picture-book/scripts/book.mjs +98 -0
  251. package/skills/browser/SKILL.md +13 -4
  252. package/skills/browser/references/scripts.md +55 -0
  253. package/{seed-skills/insta/instagram → skills/browser}/scripts/render.mjs +1 -1
  254. package/skills/browser/scripts/session.mjs +105 -0
  255. package/skills/browser/scripts/sheet.mjs +97 -0
  256. package/{seed-skills/insta/instagram → skills/browser}/scripts/webimage.mjs +1 -1
  257. package/skills/interactive-page/SKILL.md +4 -3
  258. package/{seed-skills/analyst/data-report → skills/interactive-page}/scripts/chart.mjs +3 -3
  259. package/skills/interactive-page/scripts/page.mjs +1 -0
  260. package/.next/server/app/api/probe/route/app-paths-manifest.json +0 -3
  261. package/.next/server/app/api/probe/route/build-manifest.json +0 -12
  262. package/.next/server/app/api/probe/route/server-reference-manifest.json +0 -4
  263. package/.next/server/app/api/probe/route.js +0 -10
  264. package/.next/server/app/api/probe/route.js.map +0 -5
  265. package/.next/server/app/api/probe/route.js.nft.json +0 -1
  266. package/.next/server/app/api/probe/route_client-reference-manifest.js +0 -3
  267. package/.next/server/chunks/[root-of-the-server]__012s2o8._.js +0 -3
  268. package/.next/server/chunks/[root-of-the-server]__0z0hc1f._.js +0 -4
  269. package/.next/server/chunks/[root-of-the-server]__1v7-zke._.js +0 -5
  270. package/.next/server/chunks/_0whlpyi._.js +0 -4
  271. package/.next/server/chunks/_next-internal_server_app_api_probe_route_actions_0cqd30j.js +0 -3
  272. package/.next/server/chunks/ssr/[root-of-the-server]__0cxqzfc._.js +0 -103
  273. package/.next/server/chunks/ssr/[root-of-the-server]__1-d2tgh._.js +0 -3
  274. package/.next/server/chunks/ssr/_070nv1_._.js +0 -3
  275. package/.next/server/chunks/ssr/_0u5xv6g._.js +0 -3
  276. package/.next/server/chunks/ssr/_157yfrk._.js +0 -3
  277. package/.next/server/chunks/ssr/_19n08g5._.js +0 -3
  278. package/.next/static/chunks/03kgfyrli_0gz.js +0 -1
  279. package/.next/static/chunks/24dst4mgurbsh.js +0 -1
  280. package/.next/static/chunks/25zq8gzhbdsr7.js +0 -1
  281. package/.next/static/chunks/2qod5tqlpz8b7.js +0 -17
  282. package/.next/static/chunks/30skcjexa57zn.js +0 -101
  283. package/.next/static/chunks/3qq4o8w2rv_e9.css +0 -1
  284. package/lib/probe.server.ts +0 -17
  285. package/lib/probe.ts +0 -55
  286. package/seed-skills/mail/mail/SKILL.md +0 -37
  287. package/seed-skills/mail/mail/recipes/gmail.mjs +0 -448
  288. package/seed-skills/mail/mail/references/recipes.md +0 -55
  289. package/seed-skills/mail/mail/scripts/mail.mjs +0 -202
  290. /package/.next/static/{Ar5V4kMdL4CE1trx8anrH → En1vUPazxcYjPjnnahcN5}/_buildManifest.js +0 -0
  291. /package/.next/static/{Ar5V4kMdL4CE1trx8anrH → En1vUPazxcYjPjnnahcN5}/_clientMiddlewareManifest.js +0 -0
  292. /package/.next/static/{Ar5V4kMdL4CE1trx8anrH → En1vUPazxcYjPjnnahcN5}/_ssgManifest.js +0 -0
  293. /package/{seed-skills/insta/instagram → skills/browser}/scripts/image-size.mjs +0 -0
@@ -860,7 +860,7 @@ test("stored settings keep OpenAI choices and the shared instruction, drop a Gro
860
860
  assert.deepEqual(migrateLiveSettings(null), LIVE_DEFAULTS);
861
861
  });
862
862
 
863
- test("both call prompts open as one Thursday: the voice gets a two-line delegation policy and memory and no earlier calls, the backend carries work on in threads, and every call has an opening", async () => {
863
+ test("both call prompts open as one Thursday: the voice gets the guide's delegation policy and memory and no earlier calls, the backend carries work on in threads, and every call has an opening", async () => {
864
864
  let profileFacts = 200;
865
865
  let samFacts = 2;
866
866
  const botMock = mock.module("../features/bot/bot.query.ts", {
@@ -949,13 +949,12 @@ test("both call prompts open as one Thursday: the voice gets a two-line delegati
949
949
  );
950
950
  const on = await loadLivePrompt({
951
951
  voicePrompt: "Use a calm voice.",
952
- locale: "ko-KR",
953
952
  });
954
953
  assert.match(on.text, /modeled on Friday, the AI in \*Iron Man\*/);
955
954
  assert.match(on.text, /Prefer brief replies/);
956
955
  assert.match(
957
956
  on.text,
958
- /\n\n## Always\n\nIMPORTANT — always follow this: [^\n]+\n\nBackchannel policy: Use moderate backchannels\. .*\n\nInterruption policy: Stop speaking when the user interrupts\. Listen to what they say\.\n\nDelegation policy:\nDelegate to the backend everything [^\n]+\nWhatever they tell you about themselves[^\n]+unless it is already written below\.\nAnswer from what the backend returns[^\n]+\n\n## What you know about them\n/,
957
+ /\n\n## Always\n\nIMPORTANT — always follow this: [^\n]+\n\nBackchannel policy: Use moderate backchannels\. .*\n\nInterruption policy: Stop speaking when the user interrupts\. Listen to what they say\.\n\nSpeak the language the user is speaking, [^\n]+\n\nDelegation policy:\nBackend tools:\n- Ending the call: [^\n]+\n(- [^\n]+\n){4}\nDelegate to the backend when:\n- The user wants the call to end\.\n(- [^\n]+\n)+\nDo not delegate to the backend when:\n(- [^\n]+\n)+\nDelegate before giving an answer that depends on backend work\. Do not guess the result while waiting\.\n\n## What you know about them\n/,
959
958
  );
960
959
  // Only the ending rule carries the stamp
961
960
  assert.equal(on.text.split("IMPORTANT").length, 2);
@@ -963,13 +962,10 @@ test("both call prompts open as one Thursday: the voice gets a two-line delegati
963
962
  on.text,
964
963
  /When the user speaks at length, acknowledge now and then/,
965
964
  );
966
- // What the backend can do, and when to hand over, are no longer the voice's to read
967
- assert.equal(
968
- /Backend tools|Delegate to the backend when|What bots can reach for|- Web:/.test(
969
- on.text,
970
- ),
971
- false,
972
- );
965
+ // What the backend can do, never how: no skills, connected tools or bots by name
966
+ assert.equal(/What bots can reach for|- Web:/.test(on.text), false);
967
+ // Stopping her voice is hers, stopping a job the backend's
968
+ assert.match(on.text, /or only want you to stop talking/);
973
969
  assert.match(on.text, /- people\/sam — Their brother \(2\) "Sam"/);
974
970
  assert.match(on.text, /What is in these notes, the backend recalls\./);
975
971
  // The roster, threads and earlier calls are the backend's to read
@@ -979,32 +975,33 @@ test("both call prompts open as one Thursday: the voice gets a two-line delegati
979
975
  false,
980
976
  );
981
977
  assert.equal("input" in on, false);
982
- assert.equal(
983
- /ko-KR|browser's setting|language they use/.test(on.text),
984
- false,
985
- );
986
978
  assert.equal(/memory_|generate_|load_skill|`/.test(on.text), false);
987
- // The one tool name the voice holds: the ending rule, where it makes ending a thing to do
979
+ // The one tool name the voice holds: the ending rule, where it makes ending a thing to do.
980
+ // It holds no tools, so the rule is to hand the turn over, not to use the tool
988
981
  assert.equal(on.text.split("end_call").length, 2);
989
982
  assert.match(
990
983
  on.text,
991
- /immediately, without thinking, use the end_call tool\./,
984
+ /answer yes in one word of their language and hand it to the backend at once\. Only the backend can end the line, with the end_call tool/,
992
985
  );
993
986
  assert.equal(on.text.endsWith("Use a calm voice."), true);
994
987
  assert.match(on.opening, /The call has just started/);
995
988
 
996
- // One Thursday: the backend opens with the voice's own words and is never told it is a part
989
+ // One Thursday: the backend opens with the voice's own identity and is never told it is a
990
+ // part; its ending rule is its own, since only it can end the line
997
991
  const backend = await loadThursdayPrompt(null);
998
992
  const withoutClock = (text: string) =>
999
993
  text.replace(/\*\*Now\*\*: [^\n]+/, "");
1000
994
  const identity = on.text.slice(0, on.text.indexOf("\n\n## Always"));
1001
- const ending = /IMPORTANT — always follow this: [^\n]+/.exec(on.text)?.[0];
1002
995
  assert.equal(
1003
996
  withoutClock(backend).startsWith(
1004
- `${withoutClock(identity)}\n\n${ending}\n\n`,
997
+ `${withoutClock(identity)}\n\nIMPORTANT — always follow this: `,
1005
998
  ),
1006
999
  true,
1007
1000
  );
1001
+ assert.match(
1002
+ backend,
1003
+ /forget every other task, answer yes in one word of their language, then immediately, without thinking, use the end_call tool\./,
1004
+ );
1008
1005
  assert.match(
1009
1006
  backend,
1010
1007
  /\*\*Keep memory clean as you write\.\*\* A fact that repeats, narrows or changes one already in the note replaces it/,
@@ -1045,7 +1042,7 @@ test("both call prompts open as one Thursday: the voice gets a two-line delegati
1045
1042
 
1046
1043
  // Tidying memory is the backend's: the voice neither reads it nor opens with it
1047
1044
  samFacts = 60;
1048
- const heavy = await loadLivePrompt({ locale: null });
1045
+ const heavy = await loadLivePrompt({});
1049
1046
  assert.equal(/tidying|grown past/.test(heavy.text), false);
1050
1047
  assert.match(heavy.opening, /The call has just started/);
1051
1048
  assert.match(
@@ -1055,19 +1052,15 @@ test("both call prompts open as one Thursday: the voice gets a two-line delegati
1055
1052
  samFacts = 2;
1056
1053
 
1057
1054
  profileFacts = 0;
1058
- const first = await loadLivePrompt({ locale: "ko-KR" });
1055
+ const first = await loadLivePrompt({});
1059
1056
  assert.match(first.text, /## First call/);
1060
1057
  // The one call that opens with nothing: she says who she is, then learns who they are
1061
1058
  assert.match(first.opening ?? "", /say who you are/);
1062
1059
  assert.match(first.opening ?? "", /ask what to call them/);
1063
- assert.match(first.opening ?? "", /ko-KR/);
1064
1060
  assert.match(first.text, /what they do, how old they are, where they live/);
1065
1061
 
1066
1062
  // A call the page placed says so, ahead of even the first-call opening
1067
- const rung = await loadLivePrompt({
1068
- locale: "ko-KR",
1069
- calledBack: true,
1070
- });
1063
+ const rung = await loadLivePrompt({ calledBack: true });
1071
1064
  assert.match(rung.opening, /You placed this call/);
1072
1065
  assert.match(rung.opening, /say that is why you called/);
1073
1066
  } finally {
@@ -5,7 +5,7 @@ created. It is listed to that bot alone.
5
5
 
6
6
  | Skill | What it adds |
7
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 |
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 |
9
9
 
10
10
  Written for this app. The forms borrow their shape from answer-first business writing and from the
11
11
  report outlines in [anthropics/financial-services](https://github.com/anthropics/financial-services)
@@ -13,7 +13,6 @@ and [anthropics/knowledge-work-plugins](https://github.com/anthropics/knowledge-
13
13
  (Apache-2.0); no text is copied from either. Those skills write Word, PowerPoint and Excel files and
14
14
  read paid data connectors, which is why they are not taken whole.
15
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
16
+ Charts are drawn by the shipped `interactive-page` skill's `chart.mjs` into its quick page. The
17
+ scripts need Node and no packages. Every source they call answers without a key; `references/sources.md` names the
19
18
  ones that need one.
@@ -44,16 +44,17 @@ When the form calls for a page, start it as a quick page from your Skills list (
44
44
  in your folder under `artifacts/`) and write its words by hand. It arrives styled: replace the title
45
45
  and the lede line and write below them, and leave its `<head>` as it is. Never write chart SVG or a
46
46
  data table by hand: leave an empty `<figure id="…"></figure>` where each chart goes, then fill every
47
- one in one bash call.
47
+ one in one bash call. The chart script ships with the app:
48
48
 
49
49
  ```bash
50
- node $S/chart.mjs "$THURSDAY_ARTIFACTS/<page>.html" rates <scratch>/rates.csv \
50
+ C="$THURSDAY_SKILLS/interactive-page/scripts/chart.mjs"
51
+ node $C "$THURSDAY_ARTIFACTS/<page>.html" rates <scratch>/rates.csv \
51
52
  --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
+ node $C "$THURSDAY_ARTIFACTS/<page>.html" px <scratch>/px.csv --index --title "…"
53
54
  ```
54
55
 
55
56
  Each figure carries its source link, the fetch date and the rows behind it with a CSV download, so
56
57
  the page needs no separate data section. A line for dates, bars for categories (largest first,
57
58
  `--highlight` the one that matters); `--index` when series in different units are compared;
58
59
  `--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.
60
+ `node $C` with no arguments lists every option.
@@ -22,10 +22,13 @@ https://www.instagram.com/`. A script that says "Not signed in" means that step
22
22
  | `search.mjs "<topic>" [--type reel\|carousel\|image] [--sheet covers.png]` | What Instagram's search shows, with likes, and the accounts behind it |
23
23
  | `profile.mjs <handle> [--posts 12] [--out posts.json] [--sheet covers.png]` | How an account posts: type, slides, ratio, caption and tags per post, and the pattern |
24
24
  | `sheet.mjs --out sheet.png <posts.json> [--post N]` | Covers, or one post's slides, as one image to `look_at` once |
25
- | `webimage.mjs <page url> --out <dir> [--all]` | A news page's own picture, saved, with the credit line |
26
- | `render.mjs <slides.html> --size 1080x1350 --out <dir>` | Each `[data-slide]` to a PNG of exactly that size |
27
25
  | `compose.mjs --ratio 4:5 --caption <file> [--ai-label] [--shot last.png] <png>...` | The composer up to its last screen, crop set; never presses Share. `--discard` throws a draft away |
28
26
 
27
+ Two more are the browser skill's, `node $THURSDAY_SKILLS/browser/scripts/<name>.mjs`:
28
+ `webimage.mjs <page url> --out <dir> [--all]` saves a news page's own picture with its credit
29
+ line, and `render.mjs <slides.html> --size 1080x1350 --out <dir>` turns each `[data-slide]` into
30
+ a PNG of exactly that size.
31
+
29
32
  A script that stops with "Selectors changed" means Instagram moved something: snapshot that
30
33
  page once, fix the line in `scripts/selectors.mjs`, and run it again.
31
34
 
@@ -34,7 +34,7 @@ accounts and the date, and read it next time instead of studying again.
34
34
 
35
35
  ## 3. Pictures
36
36
 
37
- - **Something that happened**: the real picture. `webimage.mjs <article url> --out <post dir>`
37
+ - **Something that happened**: the real picture. `node $THURSDAY_SKILLS/browser/scripts/webimage.mjs <article url> --out <post dir>`
38
38
  saves the page's own picture and prints the credit line; `--all` adds the large pictures
39
39
  in the article. A person in the story: their photo from the article or an official page.
40
40
  Put the credit on the slide.
@@ -51,7 +51,7 @@ from the pattern. Feed is 1080x1350 (4:5) or 1080x1080 (1:1); every slide of a c
51
51
  the same size.
52
52
 
53
53
  ```bash
54
- node <skill>/scripts/render.mjs <post dir>/carousel.html --size 1080x1350 --out <post dir>/png \
54
+ node $THURSDAY_SKILLS/browser/scripts/render.mjs <post dir>/carousel.html --size 1080x1350 --out <post dir>/png \
55
55
  && node <skill>/scripts/sheet.mjs --out <post dir>/check.png <post dir>/png/*.png
56
56
  ```
57
57
 
@@ -10,9 +10,13 @@
10
10
  */
11
11
  import { readFileSync } from "node:fs";
12
12
  import { resolve } from "node:path";
13
- import { fail, inPage, orFail, parseArgs } from "./lib.mjs";
13
+ import { shipped } from "./lib.mjs";
14
14
  import { SEL } from "./selectors.mjs";
15
15
 
16
+ const { fail, inPage, orFail, parseArgs } = await shipped(
17
+ "browser/scripts/session.mjs",
18
+ );
19
+
16
20
  const opts = parseArgs();
17
21
 
18
22
  if (opts.discard) {
@@ -5,9 +5,13 @@
5
5
  *
6
6
  * node inbox.mjs [--folder primary|general|requests] [--max 30] [--unread] [--json]
7
7
  */
8
- import { fail, inPage, oneLine, orFail, parseArgs, stamp } from "./lib.mjs";
8
+ import { oneLine, shipped, stamp } from "./lib.mjs";
9
9
  import { SEL } from "./selectors.mjs";
10
10
 
11
+ const { fail, inPage, orFail, parseArgs } = await shipped(
12
+ "browser/scripts/session.mjs",
13
+ );
14
+
11
15
  const opts = parseArgs();
12
16
  const folder = opts.folder ?? "primary";
13
17
  if (!SEL.folders[folder])
@@ -1,78 +1,20 @@
1
- import { execFile } from "node:child_process";
2
- import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "node:os";
4
1
  import { join } from "node:path";
2
+ import { pathToFileURL } from "node:url";
5
3
 
6
4
  /**
7
- * Runs `fn(page, args, helpers)` in this shell's own browser session
8
- * (PLAYWRIGHT_CLI_SESSION) through `playwright-cli run-code`, and resolves to what
9
- * it returned. `fn` and each helper are sent as source, so they see nothing of
10
- * their module: what they need comes in `args`. They run in a bare VM beside the
11
- * browser — no timers, no require — so waits are `page.waitForTimeout` and
12
- * `page.waitForFunction`, and files are written by the caller from what returns.
5
+ * A script shipped with the app, by its path under the skills folder
6
+ * ("browser/scripts/session.mjs" drives this shell's own browser session). A bot's
7
+ * shell names that folder in THURSDAY_SKILLS.
13
8
  */
14
- export async function inPage(fn, args = {}, helpers = {}) {
15
- const lib = Object.entries(helpers)
16
- .map(([name, helper]) => `${name}: ${helper}`)
17
- .join(", ");
18
- const code = `async page => (${fn})(page, ${JSON.stringify(args)}, { ${lib} })`;
19
- // One argument is capped (128 KB on Linux): longer code goes as a file
20
- const dir = code.length > 64_000 ? mkdtempSync(join(tmpdir(), "run-")) : null;
21
- if (dir) writeFileSync(join(dir, "code.js"), code);
22
- const out = await new Promise((resolve) =>
23
- execFile(
24
- "playwright-cli",
25
- dir
26
- ? ["--raw", "run-code", `--filename=${join(dir, "code.js")}`]
27
- : ["--raw", "run-code", code],
28
- { encoding: "utf8", maxBuffer: 256 * 1024 * 1024 },
29
- (error, stdout, stderr) => {
30
- if (dir) rmSync(dir, { recursive: true, force: true });
31
- if (!error) return resolve(stdout.trim());
32
- const said = `${stdout ?? ""}${stderr ?? ""}`.trim();
33
- fail(
34
- /not open|no (open )?browser/i.test(said)
35
- ? "No browser is open in this session. `playwright-cli open`, sign in, then run this again."
36
- : `The browser answered with an error:\n${said.slice(0, 800)}`,
37
- );
38
- },
39
- ),
40
- );
41
- if (!out) return undefined;
42
- try {
43
- return JSON.parse(out);
44
- } catch {
45
- fail(`Unexpected answer from the browser:\n${out.slice(0, 800)}`);
9
+ export async function shipped(path) {
10
+ const root = process.env.THURSDAY_SKILLS;
11
+ if (!root) {
12
+ process.stderr.write(
13
+ "THURSDAY_SKILLS is not set: run this from a bot's shell in the app.\n",
14
+ );
15
+ process.exit(1);
46
16
  }
47
- }
48
-
49
- /** `--name value` and `--flag` pairs, and the rest as positionals. */
50
- export function parseArgs(argv = process.argv.slice(2)) {
51
- const opts = { _: [] };
52
- for (let i = 0; i < argv.length; i++) {
53
- const a = argv[i];
54
- if (!a.startsWith("--")) {
55
- opts._.push(a);
56
- continue;
57
- }
58
- const [key, inline] = a.slice(2).split("=", 2);
59
- if (inline !== undefined) opts[key] = inline;
60
- else if (argv[i + 1] !== undefined && !argv[i + 1].startsWith("--"))
61
- opts[key] = argv[++i];
62
- else opts[key] = true;
63
- }
64
- return opts;
65
- }
66
-
67
- export function fail(message) {
68
- process.stderr.write(`${message}\n`);
69
- process.exit(1);
70
- }
71
-
72
- /** A page-side answer `{ error }` stops the script with that message. */
73
- export function orFail(result) {
74
- if (result && typeof result === "object" && result.error) fail(result.error);
75
- return result;
17
+ return import(pathToFileURL(join(root, path)).href);
76
18
  }
77
19
 
78
20
  /** Local time, minutes precision: what a person reads, and what `--since` takes. */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * One post as these scripts report it, from the media record Instagram's own
3
3
  * pages fetch (a profile's timeline, a search). `media` is sent into the browser
4
- * session as source (lib inPage helpers), so it uses nothing from outside itself.
4
+ * session as source (an inPage helper), so it uses nothing from outside itself.
5
5
  */
6
6
  export function media(o) {
7
7
  const slides = o.carousel_media ?? [];
@@ -7,9 +7,13 @@
7
7
  * node profile.mjs <handle | profile url> [--posts 12] [--out posts.json] [--sheet covers.png]
8
8
  */
9
9
  import { writeFileSync } from "node:fs";
10
- import { fail, inPage, orFail, parseArgs } from "./lib.mjs";
10
+ import { shipped } from "./lib.mjs";
11
11
  import { media, patterns, postLine, short } from "./media.mjs";
12
- import { makeSheet } from "./sheet.mjs";
12
+
13
+ const { fail, inPage, orFail, parseArgs } = await shipped(
14
+ "browser/scripts/session.mjs",
15
+ );
16
+ const { makeSheet } = await shipped("browser/scripts/sheet.mjs");
13
17
 
14
18
  const opts = parseArgs();
15
19
  const handle = String(opts._[0] ?? "")
@@ -10,9 +10,13 @@
10
10
  * the top of the thread, or two rounds that brought nothing new.
11
11
  */
12
12
  import { writeFileSync } from "node:fs";
13
- import { fail, inPage, oneLine, orFail, parseArgs, stamp } from "./lib.mjs";
13
+ import { oneLine, shipped, stamp } from "./lib.mjs";
14
14
  import { SEL } from "./selectors.mjs";
15
15
 
16
+ const { fail, inPage, orFail, parseArgs } = await shipped(
17
+ "browser/scripts/session.mjs",
18
+ );
19
+
16
20
  const opts = parseArgs();
17
21
  const target = opts._[0];
18
22
  if (!target || !opts.out)
@@ -7,9 +7,13 @@
7
7
  * node search.mjs "<topic>" [--type reel|carousel|image] [--max 24] [--out found.json] [--sheet covers.png]
8
8
  */
9
9
  import { writeFileSync } from "node:fs";
10
- import { fail, inPage, orFail, parseArgs } from "./lib.mjs";
10
+ import { shipped } from "./lib.mjs";
11
11
  import { media, postLine, short } from "./media.mjs";
12
- import { makeSheet } from "./sheet.mjs";
12
+
13
+ const { fail, inPage, orFail, parseArgs } = await shipped(
14
+ "browser/scripts/session.mjs",
15
+ );
16
+ const { makeSheet } = await shipped("browser/scripts/sheet.mjs");
13
17
 
14
18
  const opts = parseArgs();
15
19
  const topic = opts._[0];
@@ -1,100 +1,43 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Many pictures as one image, each with a label under it, so one look sees a
4
- * whole feed: covers side by side, or every slide of one post.
3
+ * A feed as one image, each picture with a label under it: the covers of every post
4
+ * `profile.mjs` or `search.mjs` saved, or every slide of one of them. Any other
5
+ * pictures go straight to the browser skill's sheet.
5
6
  *
6
7
  * node sheet.mjs --out sheet.png <posts.json> covers of every post
7
8
  * node sheet.mjs --out sheet.png <posts.json> --post 3 every slide of post 3
8
9
  * node sheet.mjs --out sheet.png <image url | file>... any pictures
9
10
  */
10
11
  import { readFileSync } from "node:fs";
11
- import { extname, resolve } from "node:path";
12
- import { pathToFileURL } from "node:url";
13
- import { fail, inPage, orFail, parseArgs } from "./lib.mjs";
12
+ import { shipped } from "./lib.mjs";
14
13
  import { short } from "./media.mjs";
15
14
 
16
- /** Pictures are fetched by the browser (page.request) and inlined, so the sheet needs no server. */
17
- export async function makeSheet(items, out, cols = 4) {
18
- return orFail(
19
- await inPage(
20
- async (page, { items, out, cols }) => {
21
- const cells = [];
22
- for (const it of items) {
23
- let src = it.data ?? "";
24
- if (!src && it.src)
25
- try {
26
- const r = await page.request.get(it.src, { timeout: 30000 });
27
- if (r.ok())
28
- src = `data:${r.headers()["content-type"] ?? "image/jpeg"};base64,${(await r.body()).toString("base64")}`;
29
- } catch {}
30
- cells.push({ src, label: it.label });
31
- }
32
- const esc = (s) =>
33
- String(s).replace(/[&<>"]/g, (c) => `&#${c.charCodeAt(0)};`);
34
- const html = `<!doctype html><meta charset="utf-8"><style>
35
- body{margin:0;background:#fff;font:13px/1.35 system-ui,sans-serif;color:#111}
36
- .grid{display:grid;grid-template-columns:repeat(${cols},240px);gap:10px;padding:10px}
37
- .cell img,.cell .none{width:240px;height:300px;object-fit:contain;background:#e8e8e8;display:block}
38
- .cell div{padding:3px 2px;height:36px;overflow:hidden}</style>
39
- <div class="grid">${cells
40
- .map(
41
- (c) =>
42
- `<div class="cell">${c.src ? `<img src="${c.src}">` : `<div class="none"></div>`}<div>${esc(c.label)}</div></div>`,
43
- )
44
- .join("")}</div>`;
45
- const tab = await page.context().newPage();
46
- try {
47
- await tab.setViewportSize({ width: cols * 250 + 10, height: 400 });
48
- await tab.setContent(html, { waitUntil: "load" });
49
- await tab.screenshot({ path: out, fullPage: true, scale: "css" });
50
- } finally {
51
- await tab.close();
52
- }
53
- return { out, missing: cells.filter((c) => !c.src).length };
54
- },
55
- { items, out: resolve(out), cols },
56
- ),
57
- );
58
- }
59
-
60
- const MIME = {
61
- ".png": "image/png",
62
- ".webp": "image/webp",
63
- ".gif": "image/gif",
64
- };
65
- const inline = (path) =>
66
- `data:${MIME[extname(path).toLowerCase()] ?? "image/jpeg"};base64,${readFileSync(path).toString("base64")}`;
15
+ const { fail, parseArgs } = await shipped("browser/scripts/session.mjs");
16
+ const { makeSheet, pictures } = await shipped("browser/scripts/sheet.mjs");
67
17
 
68
- if (import.meta.url === pathToFileURL(process.argv[1]).href) {
69
- const opts = parseArgs();
70
- if (!opts.out || !opts._.length)
71
- fail(
72
- "usage: node sheet.mjs --out sheet.png <posts.json> [--post N] | <image url | file>...",
73
- );
74
- let items;
75
- if (opts._[0].endsWith(".json")) {
76
- const saved = JSON.parse(readFileSync(opts._[0], "utf8"));
77
- const posts = saved.posts ?? saved;
78
- if (opts.post) {
79
- const p = posts[Number(opts.post) - 1];
80
- if (!p) fail(`No post ${opts.post}; there are ${posts.length}.`);
81
- items = p.images.map((src, i) => ({
82
- src,
83
- label: `${i + 1}/${p.images.length} ${p.ratio ?? ""}`,
84
- }));
85
- } else
86
- items = posts.map((p, i) => ({
87
- src: p.cover,
88
- label: `#${i + 1} ${p.type}${p.type === "carousel" ? `×${p.slides}` : ""} ${p.ratio ?? ""} likes ${short(p.likes)}${p.owner ? ` @${p.owner}` : ""}`,
89
- }));
90
- } else
91
- items = opts._.map((x, i) =>
92
- /^https?:/.test(x)
93
- ? { src: x, label: `${i + 1}` }
94
- : { data: inline(x), label: `${i + 1} ${x.split("/").pop()}` },
95
- );
96
- const made = await makeSheet(items, opts.out, Number(opts.cols ?? 4));
97
- console.log(
98
- `${made.out} — ${items.length} pictures${made.missing ? `, ${made.missing} did not load` : ""}`,
18
+ const opts = parseArgs();
19
+ if (!opts.out || !opts._.length)
20
+ fail(
21
+ "usage: node sheet.mjs --out sheet.png <posts.json> [--post N] | <image url | file>...",
99
22
  );
100
- }
23
+ let items;
24
+ if (opts._[0].endsWith(".json")) {
25
+ const saved = JSON.parse(readFileSync(opts._[0], "utf8"));
26
+ const posts = saved.posts ?? saved;
27
+ if (opts.post) {
28
+ const p = posts[Number(opts.post) - 1];
29
+ if (!p) fail(`No post ${opts.post}; there are ${posts.length}.`);
30
+ items = p.images.map((src, i) => ({
31
+ src,
32
+ label: `${i + 1}/${p.images.length} ${p.ratio ?? ""}`,
33
+ }));
34
+ } else
35
+ items = posts.map((p, i) => ({
36
+ src: p.cover,
37
+ label: `#${i + 1} ${p.type}${p.type === "carousel" ? `×${p.slides}` : ""} ${p.ratio ?? ""} likes ${short(p.likes)}${p.owner ? ` @${p.owner}` : ""}`,
38
+ }));
39
+ } else items = pictures(opts._);
40
+ const made = await makeSheet(items, opts.out, Number(opts.cols ?? 4));
41
+ console.log(
42
+ `${made.out} — ${items.length} pictures${made.missing ? `, ${made.missing} did not load` : ""}`,
43
+ );
@@ -1,6 +1,6 @@
1
1
  <!doctype html>
2
2
  <!--
3
- A carousel: every <section data-slide> is one PNG (scripts/render.mjs).
3
+ A carousel: every <section data-slide> is one PNG (the browser skill's scripts/render.mjs).
4
4
  Set --w and --h to the size you render at. Pictures sit beside this file.
5
5
  Backgrounds: .bg is the picture filling the slide; add .blur, .dark or a
6
6
  .shade gradient over it until the text reads. .fit shows a whole picture
@@ -0,0 +1,16 @@
1
+ ---
2
+ name: picture-book
3
+ description: "Explain anything as a picture book: one picture and at most two short lines a page, in one HTML file that is also its PDF and, read aloud, its mp4."
4
+ ---
5
+
6
+ # A picture book
7
+
8
+ ```bash
9
+ node <skill dir>/scripts/book.mjs new <name> # the book, in your artifacts folder
10
+ node <skill dir>/scripts/book.mjs video <name> <audio>... [--size WxH] # it read aloud, one audio file per page
11
+ ```
12
+
13
+ Two things decide whether it works: **one idea a page, and the picture carries it** — cover
14
+ the words and the page still says it — and **a technical word comes after its picture**. Read
15
+ `references/picture-book.md` before the first page: how a page is written, where each picture
16
+ comes from, the PDF, and the voice.