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.
- package/.next/BUILD_ID +1 -1
- package/.next/app-path-routes-manifest.json +0 -1
- package/.next/build-manifest.json +3 -3
- package/.next/prerender-manifest.json +3 -3
- package/.next/routes-manifest.json +0 -6
- package/.next/server/app/_global-error/page.js.nft.json +1 -1
- package/.next/server/app/_global-error.html +1 -1
- package/.next/server/app/_global-error.rsc +1 -1
- package/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/_not-found.html +1 -1
- package/.next/server/app/_not-found.rsc +2 -2
- package/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
- package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/server/app/api/artifact/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/keep-working/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/files/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/bot/thread/route.js.nft.json +1 -1
- package/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/server/app/api/favicon/[host]/route.js.nft.json +1 -1
- package/.next/server/app/api/file/[...path]/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/catalog/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/credits/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/route.js.nft.json +1 -1
- package/.next/server/app/api/llm-model/subscription/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/oauth/callback/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/route.js.nft.json +1 -1
- package/.next/server/app/api/mcp/tools/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/edit/route.js.nft.json +1 -1
- package/.next/server/app/api/memory/route.js.nft.json +1 -1
- package/.next/server/app/api/reach/route.js +13 -13
- package/.next/server/app/api/reach/route.js.nft.json +1 -1
- package/.next/server/app/api/routine/route.js.nft.json +1 -1
- package/.next/server/app/api/signins/route.js +1 -1
- package/.next/server/app/api/signins/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/[source]/[name]/route.js.nft.json +1 -1
- package/.next/server/app/api/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/call/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/skills/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/text/route.js.nft.json +1 -1
- package/.next/server/app/api/thursday/tool-call/route.js +14 -15
- package/.next/server/app/api/thursday/tool-call/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/files/route.js.nft.json +1 -1
- package/.next/server/app/api/workspace/route.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page/server-reference-manifest.json +1 -1
- package/.next/server/app/artifact/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/artifact/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/favicon.ico/route.js.nft.json +1 -1
- package/.next/server/app/manifest.webmanifest/route.js.nft.json +1 -1
- package/.next/server/app/page/server-reference-manifest.json +56 -56
- package/.next/server/app/page.js +4 -4
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +0 -1
- package/.next/server/chunks/[root-of-the-server]__014ydvq._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__0209ah4._.js +7 -5
- package/.next/server/chunks/[root-of-the-server]__04r4h-2._.js +4 -2
- package/.next/server/chunks/[root-of-the-server]__0542v_d._.js +3 -0
- package/.next/server/chunks/[root-of-the-server]__0gewdg9._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__0mrk2it._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1-swlkw._.js +2 -2
- package/.next/server/chunks/[root-of-the-server]__12iddfq._.js +1 -1
- package/.next/server/chunks/[root-of-the-server]__1ai5lme._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__1cjxw0-._.js +2 -2
- package/.next/server/chunks/{[root-of-the-server]__1fy2g82._.js → [root-of-the-server]__1k3j52n._.js} +1 -1
- package/.next/server/chunks/[root-of-the-server]__1kyjt5m._.js +3 -3
- package/.next/server/chunks/[root-of-the-server]__1ygplx1._.js +1 -1
- package/.next/server/chunks/_05v8k21._.js +3 -1
- package/.next/server/chunks/_0awtznl._.js +1 -1
- package/.next/server/chunks/_0na2fj1._.js +1 -1
- package/.next/server/chunks/_0x-ej3f._.js +17 -17
- package/.next/server/chunks/_12-amdu._.js +2 -2
- package/.next/server/chunks/_15740dw._.js +60 -0
- package/.next/server/chunks/_1dc_d1q._.js +3 -3
- package/.next/server/chunks/_1i350l5._.js +3 -1
- package/.next/server/chunks/_1jc5g4p._.js +2 -2
- package/.next/server/chunks/_1jn-aep._.js +4 -4
- package/.next/server/chunks/_1m88lkh._.js +11 -11
- package/.next/server/chunks/_1ogqovh._.js +4 -4
- package/.next/server/chunks/_1uuucy-._.js +1 -1
- package/.next/server/chunks/_1zxp-p6._.js +1 -1
- package/.next/server/chunks/features_0rfvely._.js +5 -3
- package/.next/server/chunks/ssr/{[root-of-the-server]__1se25x0._.js → [root-of-the-server]__0hpp3nw._.js} +29 -13
- package/.next/server/chunks/ssr/[root-of-the-server]__0m7u9j-._.js +3 -0
- package/.next/server/chunks/ssr/[root-of-the-server]__0tzekbl._.js +24 -60
- package/.next/server/chunks/ssr/[root-of-the-server]__1cz2rz5._.js +3 -0
- package/.next/server/chunks/ssr/{[root-of-the-server]__0tvavy9._.js → [root-of-the-server]__1iwyp1e._.js} +1 -1
- package/.next/server/chunks/ssr/[root-of-the-server]__1w9hqq8._.js +1 -1
- package/.next/server/chunks/ssr/_097-ljd._.js +1 -1
- package/.next/server/chunks/ssr/{_173siic._.js → _0btzccu._.js} +2 -2
- package/.next/server/chunks/ssr/_0fqhhwi._.js +1 -1
- package/.next/server/chunks/ssr/{_1ng831c._.js → _0hjzb5o._.js} +2 -2
- package/.next/server/chunks/ssr/_0ld9_qq._.js +1 -1
- package/.next/server/chunks/ssr/{_1doidrw._.js → _0ouzu9f._.js} +2 -2
- package/.next/server/chunks/ssr/{_1flsjeo._.js → _0u7ko71._.js} +2 -2
- package/.next/server/chunks/ssr/{_0hlxij-._.js → _0wd_o8-._.js} +2 -2
- package/.next/server/chunks/ssr/_0ya0cb0._.js +3 -0
- package/.next/server/chunks/ssr/_10yyqtk._.js +67 -0
- package/.next/server/chunks/ssr/{_0bp-_fi._.js → _12eyown._.js} +3 -3
- package/.next/server/chunks/ssr/_12mijol._.js +11 -11
- package/.next/server/chunks/ssr/_132qkfz._.js +1 -1
- package/.next/server/chunks/ssr/_184327j._.js +1 -1
- package/.next/server/chunks/ssr/{_0tg0vf8._.js → _19ps4p3._.js} +7 -7
- package/.next/server/chunks/ssr/{_1yk6yak._.js → _1bhy4yg._.js} +2 -2
- package/.next/server/chunks/ssr/_1d423aj._.js +7 -5
- package/.next/server/chunks/ssr/{_0s41irp._.js → _1kd843u._.js} +2 -2
- package/.next/server/chunks/ssr/{_0p9pmxe._.js → _1kq2ye_._.js} +2 -2
- package/.next/server/chunks/ssr/_1mqn9mc._.js +1 -1
- package/.next/server/chunks/ssr/_1neoj22._.js +1 -1
- package/.next/server/chunks/ssr/_1p8xorg._.js +3 -0
- package/.next/server/chunks/ssr/{_17v-33v._.js → _1q683w8._.js} +3 -3
- package/.next/server/chunks/ssr/_1tawxu2._.js +3 -0
- package/.next/server/chunks/ssr/{_05rxyf9._.js → _1w2oba6._.js} +2 -2
- package/.next/server/chunks/ssr/_1xoch6f._.js +1 -1
- package/.next/server/chunks/ssr/{_1u2jw0i._.js → _1ybo1hd._.js} +3 -3
- package/.next/server/chunks/ssr/{features_bot_1yr-_pm._.js → features_bot_0s11m4r._.js} +2 -2
- package/.next/server/chunks/ssr/features_connectors_components_mcp-setting_tsx_0jmbgvp._.js +1 -1
- package/.next/server/chunks/ssr/features_memory_components_memory-setting_tsx_0oa43sw._.js +1 -1
- package/.next/server/chunks/ssr/features_routine_components_routine-setting_tsx_055fknz._.js +1 -1
- package/.next/server/chunks/ssr/features_workspace_00jz2hu._.js +1 -1
- package/.next/server/chunks/ssr/{node_modules_04t200t._.js → node_modules_00pocul._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1dcbvfw._.js → node_modules_02zt4az._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0eb4rx7._.js → node_modules_05ojmcl._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_08g12f-._.js → node_modules_06i6bli._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1expacw._.js → node_modules_09dj429._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0wq4-ts._.js → node_modules_0auzgvx._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_048wmmj._.js → node_modules_0evjwh1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1qehmwm._.js → node_modules_0gmdur8._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1ksh9gy._.js → node_modules_0j3te-1._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1n0h9sf._.js → node_modules_0rbaymq._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0511ha-._.js → node_modules_0tf8_1e._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_18woe20._.js → node_modules_0yqy-0b._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0zjqwz7._.js → node_modules_1-53ebx._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1zf46lp._.js → node_modules_1-y9aho._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1xzo2wn._.js → node_modules_11bltzp._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_11hbkuj._.js → node_modules_18jn2qo._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1zkpek5._.js → node_modules_1_te3bu._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0cdu6jq._.js → node_modules_1c0pg3k._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0piws2a._.js → node_modules_1en2jx5._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1m2ho33._.js → node_modules_1p-c8cr._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0pk6i-e._.js → node_modules_1qe-oxg._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0s1lwc9._.js → node_modules_1rjt833._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1_afpz7._.js → node_modules_1tzw5sn._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1cxc8kn._.js → node_modules_1x6uwvn._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_0f2k5l0._.js → node_modules_1zqddke._.js} +1 -1
- package/.next/server/chunks/ssr/{node_modules_1_ban4u._.js → node_modules_211zgim._.js} +1 -1
- 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
- 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
- package/.next/server/middleware-build-manifest.js +3 -3
- package/.next/server/pages/404.html +1 -1
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.js +1 -1
- package/.next/server/server-reference-manifest.json +57 -57
- package/.next/static/chunks/{0oz3rs47yy3p8.js → 03-724sj0nk_z.js} +2 -2
- package/.next/static/chunks/{42890jqk049q1.js → 03bhu4mk8en2q.js} +1 -1
- package/.next/static/chunks/{3vynnr71ghvom.js → 04imzjhvzw65d.js} +2 -2
- package/.next/static/chunks/{1pjygp7hqib6j.js → 0gljzv9fz1g7d.js} +1 -1
- package/.next/static/chunks/{2d4z3er53ng8r.js → 0h0lythv1ql93.js} +1 -1
- package/.next/static/chunks/{2o4dndk0d68do.js → 0hzbj7f48cmx0.js} +1 -1
- package/.next/static/chunks/{2ky81z-jgvasc.js → 0lwunwbl7u7pt.js} +1 -1
- package/.next/static/chunks/{0u9ae9k_2g7k3.js → 0r6nwxg4dv83t.js} +1 -1
- package/.next/static/chunks/0rn9nbg6aif9f.js +17 -0
- package/.next/static/chunks/{19ojn4lomsk0r.js → 0x_w4shtvrrfg.js} +1 -1
- package/.next/static/chunks/{2sdwbfs1x5skp.js → 0ympuji_ufypd.js} +1 -1
- package/.next/static/chunks/{3t9huugh-5_ps.js → 0zmrz4zfzm4dt.js} +1 -1
- package/.next/static/chunks/{3exjsi8pfdvzj.js → 11tlxkr7a_rpc.js} +1 -1
- package/.next/static/chunks/{0o980477kfqez.js → 12scesro428m7.js} +2 -2
- package/.next/static/chunks/16s2fx3890_jd.js +65 -0
- package/.next/static/chunks/{0oyz95sujd2c7.js → 1qzfgplyo18tc.js} +1 -1
- package/.next/static/chunks/{04e-e--5lk26x.js → 1r4zajz14jp9-.js} +1 -1
- package/.next/static/chunks/1s6m4cmbn7htf.js +1 -0
- package/.next/static/chunks/1wl9t1-3i-q8p.css +1 -0
- package/.next/static/chunks/2-asot3p1j3yg.js +1 -0
- package/.next/static/chunks/{0u3ktztrzu1fa.js → 2-jn4xscbm-i2.js} +1 -1
- package/.next/static/chunks/{0w20wnnfrscq7.js → 22h9gvclqtx_2.js} +1 -1
- package/.next/static/chunks/{337g9g_ux2gj-.js → 26-eblxwnykck.js} +1 -1
- package/.next/static/chunks/{43q9cgbuw8krx.js → 2kzwhbuobpkw3.js} +2 -2
- package/.next/static/chunks/{22shnca_6zfzs.js → 304q9p54tcgm1.js} +1 -1
- package/.next/static/chunks/{1u5i-jti87jbo.js → 35wdx3wt7cg4a.js} +1 -1
- package/.next/static/chunks/{1p764d75_0lhv.js → 37unh9460im7v.js} +2 -2
- package/.next/static/chunks/{01xjer0i_e5z5.js → 39gj5vxz1zpm_.js} +1 -1
- package/.next/static/chunks/{35kwn237o4wdn.js → 39vrqy1r_00jc.js} +1 -1
- package/.next/static/chunks/{09120tms63y21.js → 3dipmu0mptqum.js} +1 -1
- package/.next/static/chunks/3gki-squw28yu.js +1 -0
- package/.next/static/chunks/{3obf9n3cxj99k.js → 3jvldk2wvwbge.js} +1 -1
- package/.next/static/chunks/{1mwee7xvitlp9.js → 3nnzdl_te20hv.js} +1 -1
- package/.next/static/chunks/{2m267diam_vib.js → 3tz4dr0_8pq1-.js} +1 -1
- package/.next/static/chunks/{2gocl3dq0_h1s.js → 3x6pggb-2kyrk.js} +1 -1
- package/.next/static/chunks/{025bmrgeukcb2.js → 4440mws9jz3mt.js} +1 -1
- package/README.md +1 -1
- package/app/api/thursday/tool-call/route.ts +1 -15
- package/docs/images/demo-still.jpg +0 -0
- package/docs/live-calls.md +19 -11
- package/features/ai/prompts/live.prompt.ts +51 -26
- package/features/ai/prompts/prompt-helper.ts +1 -9
- package/features/ai/prompts/thursday.prompt.ts +8 -2
- package/features/bot/bot.run.ts +9 -3
- package/features/bot/bot.seed.ts +35 -91
- package/features/bot/components/bot-room.tsx +0 -6
- package/features/bot/components/room-pill.tsx +4 -2
- package/features/bot/seed-bots.ts +2 -2
- package/features/intro/components/intro.tsx +2 -3
- package/features/reach/reach.ts +2 -2
- package/features/signins/signins.query.ts +66 -1
- package/features/thursday/components/side-captions.tsx +31 -8
- package/features/thursday/components/thursday.tsx +97 -39
- package/features/thursday/components/write-line.tsx +28 -17
- package/features/thursday/thursday.action.ts +4 -20
- package/features/thursday/thursday.schema.ts +0 -9
- package/features/thursday/thursday.store.ts +11 -15
- package/features/thursday/thursday.text.ts +10 -8
- package/features/thursday/tool-line.ts +17 -6
- package/features/thursday/use-call-ring.ts +1 -5
- package/features/thursday/use-text-call.ts +37 -21
- package/features/thursday/use-thursday.ts +14 -44
- package/features/workspace/workspace.ts +4 -2
- package/guide/bots.md +12 -2
- package/guide/calls.md +7 -5
- package/lib/live/live.session.ts +0 -39
- package/package.json +1 -1
- package/scripts/live-session.test.mts +19 -26
- package/seed-skills/analyst/README.md +3 -4
- package/seed-skills/analyst/data-report/SKILL.md +5 -4
- package/seed-skills/insta/instagram/SKILL.md +5 -2
- package/seed-skills/insta/instagram/references/post.md +2 -2
- package/seed-skills/insta/instagram/scripts/compose.mjs +5 -1
- package/seed-skills/insta/instagram/scripts/inbox.mjs +5 -1
- package/seed-skills/insta/instagram/scripts/lib.mjs +12 -70
- package/seed-skills/insta/instagram/scripts/media.mjs +1 -1
- package/seed-skills/insta/instagram/scripts/profile.mjs +6 -2
- package/seed-skills/insta/instagram/scripts/read.mjs +5 -1
- package/seed-skills/insta/instagram/scripts/search.mjs +6 -2
- package/seed-skills/insta/instagram/scripts/sheet.mjs +31 -88
- package/seed-skills/insta/instagram/templates/carousel.html +1 -1
- package/seed-skills/tutor/picture-book/SKILL.md +16 -0
- package/seed-skills/tutor/picture-book/book/book.css +181 -0
- package/seed-skills/tutor/picture-book/book/book.html +33 -0
- package/seed-skills/tutor/picture-book/book/book.js +60 -0
- package/seed-skills/tutor/picture-book/references/picture-book.md +104 -0
- package/seed-skills/tutor/picture-book/scripts/book-video.mjs +177 -0
- package/seed-skills/tutor/picture-book/scripts/book.mjs +98 -0
- package/skills/browser/SKILL.md +13 -4
- package/skills/browser/references/scripts.md +55 -0
- package/{seed-skills/insta/instagram → skills/browser}/scripts/render.mjs +1 -1
- package/skills/browser/scripts/session.mjs +105 -0
- package/skills/browser/scripts/sheet.mjs +97 -0
- package/{seed-skills/insta/instagram → skills/browser}/scripts/webimage.mjs +1 -1
- package/skills/interactive-page/SKILL.md +4 -3
- package/{seed-skills/analyst/data-report → skills/interactive-page}/scripts/chart.mjs +3 -3
- package/skills/interactive-page/scripts/page.mjs +1 -0
- package/.next/server/app/api/probe/route/app-paths-manifest.json +0 -3
- package/.next/server/app/api/probe/route/build-manifest.json +0 -12
- package/.next/server/app/api/probe/route/server-reference-manifest.json +0 -4
- package/.next/server/app/api/probe/route.js +0 -10
- package/.next/server/app/api/probe/route.js.map +0 -5
- package/.next/server/app/api/probe/route.js.nft.json +0 -1
- package/.next/server/app/api/probe/route_client-reference-manifest.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__012s2o8._.js +0 -3
- package/.next/server/chunks/[root-of-the-server]__0z0hc1f._.js +0 -4
- package/.next/server/chunks/[root-of-the-server]__1v7-zke._.js +0 -5
- package/.next/server/chunks/_0whlpyi._.js +0 -4
- package/.next/server/chunks/_next-internal_server_app_api_probe_route_actions_0cqd30j.js +0 -3
- package/.next/server/chunks/ssr/[root-of-the-server]__0cxqzfc._.js +0 -103
- package/.next/server/chunks/ssr/[root-of-the-server]__1-d2tgh._.js +0 -3
- package/.next/server/chunks/ssr/_070nv1_._.js +0 -3
- package/.next/server/chunks/ssr/_0u5xv6g._.js +0 -3
- package/.next/server/chunks/ssr/_157yfrk._.js +0 -3
- package/.next/server/chunks/ssr/_19n08g5._.js +0 -3
- package/.next/static/chunks/03kgfyrli_0gz.js +0 -1
- package/.next/static/chunks/24dst4mgurbsh.js +0 -1
- package/.next/static/chunks/25zq8gzhbdsr7.js +0 -1
- package/.next/static/chunks/2qod5tqlpz8b7.js +0 -17
- package/.next/static/chunks/30skcjexa57zn.js +0 -101
- package/.next/static/chunks/3qq4o8w2rv_e9.css +0 -1
- package/lib/probe.server.ts +0 -17
- package/lib/probe.ts +0 -55
- package/seed-skills/mail/mail/SKILL.md +0 -37
- package/seed-skills/mail/mail/recipes/gmail.mjs +0 -448
- package/seed-skills/mail/mail/references/recipes.md +0 -55
- package/seed-skills/mail/mail/scripts/mail.mjs +0 -202
- /package/.next/static/{Ar5V4kMdL4CE1trx8anrH → En1vUPazxcYjPjnnahcN5}/_buildManifest.js +0 -0
- /package/.next/static/{Ar5V4kMdL4CE1trx8anrH → En1vUPazxcYjPjnnahcN5}/_clientMiddlewareManifest.js +0 -0
- /package/.next/static/{Ar5V4kMdL4CE1trx8anrH → En1vUPazxcYjPjnnahcN5}/_ssgManifest.js +0 -0
- /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
|
|
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\
|
|
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,
|
|
967
|
-
assert.equal(
|
|
968
|
-
|
|
969
|
-
|
|
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
|
-
/
|
|
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
|
|
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\
|
|
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({
|
|
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({
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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 $
|
|
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 $
|
|
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
|
|
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 {
|
|
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 {
|
|
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
|
-
*
|
|
8
|
-
* (
|
|
9
|
-
*
|
|
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
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 (
|
|
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 {
|
|
10
|
+
import { shipped } from "./lib.mjs";
|
|
11
11
|
import { media, patterns, postLine, short } from "./media.mjs";
|
|
12
|
-
|
|
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 {
|
|
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 {
|
|
10
|
+
import { shipped } from "./lib.mjs";
|
|
11
11
|
import { media, postLine, short } from "./media.mjs";
|
|
12
|
-
|
|
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
|
-
*
|
|
4
|
-
*
|
|
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 {
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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.
|