@rubytech/create-maxy-code 0.1.557 → 0.1.559
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/package.json +1 -1
- package/payload/platform/plugins/admin/PLUGIN.md +2 -1
- package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.d.ts +1 -1
- package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.d.ts.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.js +7 -0
- package/payload/platform/plugins/admin/mcp/dist/content-producer-authoring-skills.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +20 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/lib/roster-census.d.ts +14 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/roster-census.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/roster-census.js +18 -0
- package/payload/platform/plugins/admin/mcp/dist/lib/roster-census.js.map +1 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +43 -1
- package/payload/platform/plugins/admin/skills/video-production/SKILL.md +106 -0
- package/payload/platform/plugins/admin/skills/video-production/__tests__/registration.sh +54 -0
- package/payload/platform/plugins/admin/skills/video-production/references/reel-authoring.md +83 -0
- package/payload/platform/plugins/admin/skills/video-production/render.mjs +429 -0
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +17 -0
- package/payload/platform/plugins/docs/PLUGIN.md +1 -0
- package/payload/platform/plugins/docs/references/video-production.md +37 -0
- package/payload/platform/plugins/filesystem/mcp/dist/lib/result.d.ts +5 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/result.d.ts.map +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/result.js +4 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/result.js.map +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-read.d.ts.map +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-read.js +2 -1
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-read.js.map +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.d.ts.map +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.js +2 -1
- package/payload/platform/plugins/filesystem/mcp/dist/tools/file-write.js.map +1 -1
- package/payload/platform/plugins/scheduling/PLUGIN.md +4 -2
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +15 -3
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/schedule-audit.test.js +72 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/schedule-audit.test.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/workflow-census.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/workflow-census.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/workflow-census.test.js +112 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/__tests__/workflow-census.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.d.ts +59 -6
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.js +154 -18
- package/payload/platform/plugins/scheduling/mcp/dist/lib/schedule-audit.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js +17 -2
- package/payload/platform/plugins/scheduling/mcp/dist/scripts/check-due-events.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-list-kind.test.d.ts +2 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-list-kind.test.d.ts.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-list-kind.test.js +25 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/__tests__/schedule-list-kind.test.js.map +1 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts +15 -0
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js +66 -46
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-list.js.map +1 -1
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js +5 -2
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/url-get/mcp/dist/index.js +13 -13
- package/payload/platform/plugins/url-get/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/workflows/PLUGIN.md +4 -1
- package/payload/platform/plugins/workflows/mcp/dist/__tests__/retry-delay.test.d.ts +2 -0
- package/payload/platform/plugins/workflows/mcp/dist/__tests__/retry-delay.test.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/__tests__/retry-delay.test.js +33 -0
- package/payload/platform/plugins/workflows/mcp/dist/__tests__/retry-delay.test.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/index.js +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/workflows/mcp/dist/lib/__tests__/step-resolver.test.d.ts +2 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/__tests__/step-resolver.test.d.ts.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/__tests__/step-resolver.test.js +42 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/__tests__/step-resolver.test.js.map +1 -0
- package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.d.ts +23 -5
- package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.d.ts.map +1 -1
- package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.js +20 -12
- package/payload/platform/plugins/workflows/mcp/dist/lib/step-resolver.js.map +1 -1
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.d.ts +2 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.d.ts.map +1 -1
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js +2 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-create.js.map +1 -1
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.d.ts +10 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.d.ts.map +1 -1
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js +53 -7
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-execute.js.map +1 -1
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.d.ts.map +1 -1
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.js +2 -0
- package/payload/platform/plugins/workflows/mcp/dist/tools/workflow-update.js.map +1 -1
- package/payload/platform/scripts/__tests__/provision-honours-disabled-agents.test.sh +4 -3
- package/payload/platform/scripts/__tests__/provision-specialist-retirement.test.sh +115 -0
- package/payload/platform/scripts/lib/provision-account-dir.sh +88 -13
- package/payload/platform/scripts/logs-rotate.sh +6 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +48 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-tee.d.ts +16 -0
- package/payload/platform/services/claude-session-manager/dist/pty-tee.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-tee.js +84 -0
- package/payload/platform/services/claude-session-manager/dist/pty-tee.js.map +1 -0
- package/payload/platform/templates/specialists/agents/content-producer.md +8 -0
- package/payload/premium-plugins/writer-craft/agents/writer-craft--manuscript-reviewer.md +2 -0
- package/payload/server/public/activity.html +5 -5
- package/payload/server/public/agents.html +4 -4
- package/payload/server/public/assets/{AdminLoginScreens-DrF7rAcb.js → AdminLoginScreens-DLogIsJT.js} +1 -1
- package/payload/server/public/assets/AdminLoginScreens-DLogIsJT.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-DLogIsJT.js.gz +0 -0
- package/payload/server/public/assets/{AdminShell-BVKfxSgh.js → AdminShell-B67tPTyE.js} +1 -1
- package/payload/server/public/assets/AdminShell-B67tPTyE.js.br +0 -0
- package/payload/server/public/assets/AdminShell-B67tPTyE.js.gz +0 -0
- package/payload/server/public/assets/{activity-CBcvL5MZ.js → activity-AVJSFESV.js} +1 -1
- package/payload/server/public/assets/activity-AVJSFESV.js.br +0 -0
- package/payload/server/public/assets/activity-AVJSFESV.js.gz +0 -0
- package/payload/server/public/assets/{admin-CCB9k3GB.js → admin-eFgbld42.js} +1 -1
- package/payload/server/public/assets/admin-eFgbld42.js.br +0 -0
- package/payload/server/public/assets/admin-eFgbld42.js.gz +0 -0
- package/payload/server/public/assets/{agents-Dzx8Uxgz.js → agents-CoWLZMf7.js} +1 -1
- package/payload/server/public/assets/agents-CoWLZMf7.js.br +0 -0
- package/payload/server/public/assets/agents-CoWLZMf7.js.gz +0 -0
- package/payload/server/public/assets/{browser-B7EsGyVd.js → browser-C6K66_fw.js} +1 -1
- package/payload/server/public/assets/browser-C6K66_fw.js.br +0 -0
- package/payload/server/public/assets/browser-C6K66_fw.js.gz +0 -0
- package/payload/server/public/assets/{calendar-D4qD0upn.js → calendar-Dnw_cP4b.js} +1 -1
- package/payload/server/public/assets/calendar-Dnw_cP4b.js.br +0 -0
- package/payload/server/public/assets/calendar-Dnw_cP4b.js.gz +0 -0
- package/payload/server/public/assets/chat-Cm0fkpg1.js +1 -0
- package/payload/server/public/assets/chat-Cm0fkpg1.js.br +0 -0
- package/payload/server/public/assets/chat-Cm0fkpg1.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-CjRH-BtF.js +1 -0
- package/payload/server/public/assets/chevron-left-CjRH-BtF.js.br +0 -0
- package/payload/server/public/assets/clock-wbYEt1kM.js +1 -0
- package/payload/server/public/assets/clock-wbYEt1kM.js.br +0 -0
- package/payload/server/public/assets/clock-wbYEt1kM.js.gz +0 -0
- package/payload/server/public/assets/{copy-CG2tAK16.js → copy-kuNM0Etw.js} +1 -1
- package/payload/server/public/assets/copy-kuNM0Etw.js.br +0 -0
- package/payload/server/public/assets/copy-kuNM0Etw.js.gz +0 -0
- package/payload/server/public/assets/data-c47T2NCd.js +1 -0
- package/payload/server/public/assets/data-c47T2NCd.js.br +1 -0
- package/payload/server/public/assets/data-c47T2NCd.js.gz +0 -0
- package/payload/server/public/assets/{file-text-47cIljhV.js → file-text-BWkobrcx.js} +1 -1
- package/payload/server/public/assets/file-text-BWkobrcx.js.br +0 -0
- package/payload/server/public/assets/file-text-BWkobrcx.js.gz +0 -0
- package/payload/server/public/assets/{graph-CDbi-eqF.js → graph-BGjTL5Vz.js} +1 -1
- package/payload/server/public/assets/graph-BGjTL5Vz.js.br +0 -0
- package/payload/server/public/assets/graph-BGjTL5Vz.js.gz +0 -0
- package/payload/server/public/assets/{graph-labels-Byp2RkVg.js → graph-labels-Dvc5Kw_r.js} +1 -1
- package/payload/server/public/assets/graph-labels-Dvc5Kw_r.js.br +0 -0
- package/payload/server/public/assets/graph-labels-Dvc5Kw_r.js.gz +0 -0
- package/payload/server/public/assets/{operator-B0oOVB8R.js → operator-CzBGv_he.js} +1 -1
- package/payload/server/public/assets/operator-CzBGv_he.js.br +0 -0
- package/payload/server/public/assets/operator-CzBGv_he.js.gz +0 -0
- package/payload/server/public/assets/{page-Dfce3YQd.js → page-BIY4Ku8Q.js} +1 -1
- package/payload/server/public/assets/page-BIY4Ku8Q.js.br +0 -0
- package/payload/server/public/assets/page-BIY4Ku8Q.js.gz +0 -0
- package/payload/server/public/assets/{page-DrbfHSQ9.js → page-DaySqamH.js} +1 -1
- package/payload/server/public/assets/page-DaySqamH.js.br +0 -0
- package/payload/server/public/assets/page-DaySqamH.js.gz +0 -0
- package/payload/server/public/assets/play-C8RA52_i.js +1 -0
- package/payload/server/public/assets/play-C8RA52_i.js.br +0 -0
- package/payload/server/public/assets/play-C8RA52_i.js.gz +0 -0
- package/payload/server/public/assets/{public-DJwMfl-4.js → public-pwDSvhBv.js} +1 -1
- package/payload/server/public/assets/public-pwDSvhBv.js.br +0 -0
- package/payload/server/public/assets/public-pwDSvhBv.js.gz +0 -0
- package/payload/server/public/assets/{rotate-ccw-Dbj7NrEB.js → rotate-ccw-B6s7tkEt.js} +1 -1
- package/payload/server/public/assets/rotate-ccw-B6s7tkEt.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-B6s7tkEt.js.gz +0 -0
- package/payload/server/public/assets/routines-CFlyRdYg.js +2 -0
- package/payload/server/public/assets/routines-CFlyRdYg.js.br +0 -0
- package/payload/server/public/assets/routines-CFlyRdYg.js.gz +0 -0
- package/payload/server/public/assets/{skills-EtKyNK_3.js → skills-C45kfL_Q.js} +1 -1
- package/payload/server/public/assets/skills-C45kfL_Q.js.br +0 -0
- package/payload/server/public/assets/skills-C45kfL_Q.js.gz +0 -0
- package/payload/server/public/assets/{tasks-U0poqfi9.js → tasks-BwoCTCu5.js} +1 -1
- package/payload/server/public/assets/tasks-BwoCTCu5.js.br +0 -0
- package/payload/server/public/assets/tasks-BwoCTCu5.js.gz +0 -0
- package/payload/server/public/assets/{triangle-alert-D7zwttJW.js → triangle-alert-B3bthSzx.js} +1 -1
- package/payload/server/public/assets/triangle-alert-B3bthSzx.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-B3bthSzx.js.gz +0 -0
- package/payload/server/public/assets/{useCopyFeedback-Ae_va0Ps.js → useCopyFeedback-BzJAkFEb.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-BzJAkFEb.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-BzJAkFEb.js.gz +0 -0
- package/payload/server/public/assets/{useMediaQuery-HRgPNA8l.css → useMediaQuery-DW_xRrMl.css} +1 -1
- package/payload/server/public/assets/useMediaQuery-DW_xRrMl.css.br +0 -0
- package/payload/server/public/assets/{useMediaQuery-HRgPNA8l.css.gz → useMediaQuery-DW_xRrMl.css.gz} +0 -0
- package/payload/server/public/assets/useVoiceRecorder-_qI_BAOo.js +2 -0
- package/payload/server/public/assets/useVoiceRecorder-_qI_BAOo.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-_qI_BAOo.js.gz +0 -0
- package/payload/server/public/assets/{wrench-BmXV4Tmh.js → wrench-Bqux9jaw.js} +1 -1
- package/payload/server/public/assets/wrench-Bqux9jaw.js.br +0 -0
- package/payload/server/public/assets/wrench-Bqux9jaw.js.gz +0 -0
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +7 -7
- package/payload/server/public/chat.html +13 -13
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +9 -9
- package/payload/server/public/index.html +13 -13
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +13 -13
- package/payload/server/public/routines.html +7 -7
- package/payload/server/public/skills.html +5 -5
- package/payload/server/public/tasks.html +6 -6
- package/payload/server/server.js +262 -85
- package/payload/server/public/assets/AdminLoginScreens-DrF7rAcb.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-DrF7rAcb.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-BVKfxSgh.js.br +0 -0
- package/payload/server/public/assets/AdminShell-BVKfxSgh.js.gz +0 -0
- package/payload/server/public/assets/activity-CBcvL5MZ.js.br +0 -0
- package/payload/server/public/assets/activity-CBcvL5MZ.js.gz +0 -0
- package/payload/server/public/assets/admin-CCB9k3GB.js.br +0 -0
- package/payload/server/public/assets/admin-CCB9k3GB.js.gz +0 -0
- package/payload/server/public/assets/agents-Dzx8Uxgz.js.br +0 -0
- package/payload/server/public/assets/agents-Dzx8Uxgz.js.gz +0 -0
- package/payload/server/public/assets/browser-B7EsGyVd.js.br +0 -0
- package/payload/server/public/assets/browser-B7EsGyVd.js.gz +0 -0
- package/payload/server/public/assets/calendar-D4qD0upn.js.br +0 -0
- package/payload/server/public/assets/calendar-D4qD0upn.js.gz +0 -0
- package/payload/server/public/assets/chat-BgOUm7r1.js +0 -1
- package/payload/server/public/assets/chat-BgOUm7r1.js.br +0 -0
- package/payload/server/public/assets/chat-BgOUm7r1.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-CGsW9Vsp.js +0 -1
- package/payload/server/public/assets/chevron-left-CGsW9Vsp.js.br +0 -0
- package/payload/server/public/assets/clock-B_xWWLQW.js +0 -1
- package/payload/server/public/assets/clock-B_xWWLQW.js.br +0 -0
- package/payload/server/public/assets/clock-B_xWWLQW.js.gz +0 -0
- package/payload/server/public/assets/copy-CG2tAK16.js.br +0 -0
- package/payload/server/public/assets/copy-CG2tAK16.js.gz +0 -0
- package/payload/server/public/assets/data-YEC6LkxI.js +0 -1
- package/payload/server/public/assets/data-YEC6LkxI.js.br +0 -0
- package/payload/server/public/assets/data-YEC6LkxI.js.gz +0 -0
- package/payload/server/public/assets/file-text-47cIljhV.js.br +0 -0
- package/payload/server/public/assets/file-text-47cIljhV.js.gz +0 -0
- package/payload/server/public/assets/graph-CDbi-eqF.js.br +0 -0
- package/payload/server/public/assets/graph-CDbi-eqF.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-Byp2RkVg.js.br +0 -0
- package/payload/server/public/assets/graph-labels-Byp2RkVg.js.gz +0 -0
- package/payload/server/public/assets/operator-B0oOVB8R.js.br +0 -0
- package/payload/server/public/assets/operator-B0oOVB8R.js.gz +0 -0
- package/payload/server/public/assets/page-Dfce3YQd.js.br +0 -0
- package/payload/server/public/assets/page-Dfce3YQd.js.gz +0 -0
- package/payload/server/public/assets/page-DrbfHSQ9.js.br +0 -0
- package/payload/server/public/assets/page-DrbfHSQ9.js.gz +0 -0
- package/payload/server/public/assets/play-Bankj0zU.js +0 -1
- package/payload/server/public/assets/play-Bankj0zU.js.br +0 -0
- package/payload/server/public/assets/play-Bankj0zU.js.gz +0 -0
- package/payload/server/public/assets/public-DJwMfl-4.js.br +0 -0
- package/payload/server/public/assets/public-DJwMfl-4.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-Dbj7NrEB.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-Dbj7NrEB.js.gz +0 -0
- package/payload/server/public/assets/routines-CdqGJwyD.js +0 -2
- package/payload/server/public/assets/routines-CdqGJwyD.js.br +0 -0
- package/payload/server/public/assets/routines-CdqGJwyD.js.gz +0 -0
- package/payload/server/public/assets/skills-EtKyNK_3.js.br +0 -0
- package/payload/server/public/assets/skills-EtKyNK_3.js.gz +0 -0
- package/payload/server/public/assets/tasks-U0poqfi9.js.br +0 -0
- package/payload/server/public/assets/tasks-U0poqfi9.js.gz +0 -0
- package/payload/server/public/assets/triangle-alert-D7zwttJW.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-D7zwttJW.js.gz +0 -0
- package/payload/server/public/assets/useCopyFeedback-Ae_va0Ps.js.br +0 -4
- package/payload/server/public/assets/useCopyFeedback-Ae_va0Ps.js.gz +0 -0
- package/payload/server/public/assets/useMediaQuery-HRgPNA8l.css.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-9qVOHhkb.js +0 -2
- package/payload/server/public/assets/useVoiceRecorder-9qVOHhkb.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-9qVOHhkb.js.gz +0 -0
- package/payload/server/public/assets/wrench-BmXV4Tmh.js.br +0 -0
- package/payload/server/public/assets/wrench-BmXV4Tmh.js.gz +0 -0
- /package/payload/server/public/assets/{useMediaQuery-DIm6yGOl.js → useMediaQuery-DJ6XeNN7.js} +0 -0
- /package/payload/server/public/assets/{useMediaQuery-DIm6yGOl.js.br → useMediaQuery-DJ6XeNN7.js.br} +0 -0
- /package/payload/server/public/assets/{useMediaQuery-DIm6yGOl.js.gz → useMediaQuery-DJ6XeNN7.js.gz} +0 -0
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Frame-accurate MP4 capture over the Chrome DevTools Protocol. No npm packages —
|
|
3
|
+
// Node 22's built-in fetch and WebSocket are the whole transport.
|
|
4
|
+
//
|
|
5
|
+
// node render.mjs --mode reel --target <path|file:|http:> --out <dir> --name <slug> \
|
|
6
|
+
// --width 1080 --height 1920 [--dsf 1] [--mobile] [--pre '<js>'] [--port 9333]
|
|
7
|
+
//
|
|
8
|
+
// node render.mjs --mode page --target <path|file:|http:> --out <dir> --name <slug> \
|
|
9
|
+
// --width 1920 --height 1080 --frames 900 [--dsf 1] [--mobile] \
|
|
10
|
+
// [--pre '<js>'] [--port 9333]
|
|
11
|
+
//
|
|
12
|
+
// The two modes differ in one thing: how the clock advances.
|
|
13
|
+
// reel — the page defines window.__frames and window.__seek(i); every frame is
|
|
14
|
+
// computed from the index alone, so the timeline is deterministic.
|
|
15
|
+
// page — Chrome's virtual time clock is advanced one frame at a time, so a page
|
|
16
|
+
// we do not control is captured with its own animation playing.
|
|
17
|
+
//
|
|
18
|
+
// This script owns the whole run: resolve the browser, launch it, capture, encode,
|
|
19
|
+
// poster, probe, stills, tear down, census. Splitting encode or cleanup out leaves
|
|
20
|
+
// the frame directory with no owner, which is how /tmp fills up with frames.
|
|
21
|
+
|
|
22
|
+
import {
|
|
23
|
+
writeFileSync, mkdirSync, rmSync, existsSync, readFileSync,
|
|
24
|
+
appendFileSync, statSync, readdirSync,
|
|
25
|
+
} from "node:fs";
|
|
26
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
27
|
+
import { join } from "node:path";
|
|
28
|
+
import { tmpdir } from "node:os";
|
|
29
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------- arguments
|
|
32
|
+
|
|
33
|
+
const argv = process.argv.slice(2);
|
|
34
|
+
const arg = (flag, fallback = null) => {
|
|
35
|
+
const i = argv.indexOf(flag);
|
|
36
|
+
return i >= 0 && i + 1 < argv.length ? argv[i + 1] : fallback;
|
|
37
|
+
};
|
|
38
|
+
const flag = f => argv.includes(f);
|
|
39
|
+
|
|
40
|
+
const mode = arg("--mode");
|
|
41
|
+
const target = arg("--target");
|
|
42
|
+
const outDir = arg("--out");
|
|
43
|
+
const name = arg("--name");
|
|
44
|
+
const width = Number(arg("--width", 1080));
|
|
45
|
+
const height = Number(arg("--height", 1920));
|
|
46
|
+
const dsf = Number(arg("--dsf", 1));
|
|
47
|
+
const mobile = flag("--mobile");
|
|
48
|
+
const pre = arg("--pre");
|
|
49
|
+
const port = arg("--port", "9333");
|
|
50
|
+
const framesArg = arg("--frames");
|
|
51
|
+
|
|
52
|
+
const FRAME_MS = 1000 / 30;
|
|
53
|
+
const FRAMES_ROOT = join(tmpdir(), "video-production");
|
|
54
|
+
const T0 = Date.now();
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------- logging
|
|
57
|
+
// Same fail-open shape as the platform hooks: always stdout, plus a durable
|
|
58
|
+
// breadcrumb in $LOG_DIR when that is a real directory. An unwritable log
|
|
59
|
+
// never fails a render.
|
|
60
|
+
|
|
61
|
+
const LOG_FILE =
|
|
62
|
+
process.env.LOG_DIR && existsSync(process.env.LOG_DIR)
|
|
63
|
+
? join(process.env.LOG_DIR, "video-render.log")
|
|
64
|
+
: null;
|
|
65
|
+
|
|
66
|
+
function log(line) {
|
|
67
|
+
const s = `${new Date().toISOString()} ${line}`;
|
|
68
|
+
console.log(s);
|
|
69
|
+
if (LOG_FILE) { try { appendFileSync(LOG_FILE, s + "\n"); } catch { /* fail open */ } }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function die(line) { log(line); process.exit(1); }
|
|
73
|
+
|
|
74
|
+
const run = name || "unnamed";
|
|
75
|
+
|
|
76
|
+
if (!mode || !["reel", "page"].includes(mode)) die(`[video-render] op=bad-args run=${run} reason=mode-must-be-reel-or-page`);
|
|
77
|
+
if (!target) die(`[video-render] op=bad-args run=${run} reason=target-required`);
|
|
78
|
+
if (!outDir) die(`[video-render] op=bad-args run=${run} reason=out-required`);
|
|
79
|
+
if (!name) die(`[video-render] op=bad-args run=${run} reason=name-required`);
|
|
80
|
+
// --name is joined into four filesystem paths and both output filenames, so it is
|
|
81
|
+
// a path component, not a label. A slash or a dot-dot would escape them.
|
|
82
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) {
|
|
83
|
+
die(`[video-render] op=bad-args run=${run} reason=name-must-be-a-slug value=${JSON.stringify(name)}`);
|
|
84
|
+
}
|
|
85
|
+
if (mode === "page" && !framesArg) die(`[video-render] op=bad-args run=${run} reason=frames-required-in-page-mode`);
|
|
86
|
+
|
|
87
|
+
const url =
|
|
88
|
+
/^(https?|file):/.test(target) ? target : pathToFileURL(target).href;
|
|
89
|
+
|
|
90
|
+
log(`[video-render] op=request run=${run} mode=${mode} target=${url} w=${width} h=${height} dsf=${dsf} fps=30`);
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------- census
|
|
93
|
+
|
|
94
|
+
// frameDirs/frameBytes are the strict leak signal: a capture directory outlives
|
|
95
|
+
// its run only when a render died without tearing down, so a healthy end reports
|
|
96
|
+
// zero for both. stillDirs/stillBytes are counted separately because verification
|
|
97
|
+
// stills are deliberately retained for the agent to read after the run — counting
|
|
98
|
+
// them as frames would make every successful render look like a leak.
|
|
99
|
+
function census(phase) {
|
|
100
|
+
let chromiumLive = 0, frameDirs = 0, frameBytes = 0, stillDirs = 0, stillBytes = 0;
|
|
101
|
+
try {
|
|
102
|
+
// A read, never a kill. This process's own argv carries no such string,
|
|
103
|
+
// so it cannot count itself.
|
|
104
|
+
chromiumLive = Number(execFileSync("bash", ["-c",
|
|
105
|
+
'pgrep -f "remote-debugging-port=" 2>/dev/null | wc -l']).toString().trim());
|
|
106
|
+
} catch { /* pgrep absent or no matches */ }
|
|
107
|
+
try {
|
|
108
|
+
for (const d of readdirSync(FRAMES_ROOT)) {
|
|
109
|
+
const p = join(FRAMES_ROOT, d);
|
|
110
|
+
if (!statSync(p).isDirectory()) continue;
|
|
111
|
+
const isStills = d.endsWith("-stills");
|
|
112
|
+
let bytes = 0;
|
|
113
|
+
for (const f of readdirSync(p)) {
|
|
114
|
+
try { bytes += statSync(join(p, f)).size; } catch { /* raced */ }
|
|
115
|
+
}
|
|
116
|
+
if (isStills) { stillDirs++; stillBytes += bytes; }
|
|
117
|
+
else { frameDirs++; frameBytes += bytes; }
|
|
118
|
+
}
|
|
119
|
+
} catch { /* root absent on a first run */ }
|
|
120
|
+
log(`[video-census] phase=${phase} run=${run} chromiumLive=${chromiumLive} frameDirs=${frameDirs} frameBytes=${frameBytes} stillDirs=${stillDirs} stillBytes=${stillBytes}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
census("start");
|
|
124
|
+
|
|
125
|
+
// ---------------------------------------------------------------- preflight
|
|
126
|
+
|
|
127
|
+
const platformRoot =
|
|
128
|
+
process.env.PLATFORM_ROOT || fileURLToPath(new URL("../../../../", import.meta.url));
|
|
129
|
+
const binFile = join(platformRoot, "config", "chromium-binary.path");
|
|
130
|
+
|
|
131
|
+
if (!existsSync(binFile)) {
|
|
132
|
+
die(`[video-render] op=chromium-missing run=${run} file=${binFile}`);
|
|
133
|
+
}
|
|
134
|
+
const bin = readFileSync(binFile, "utf8").trim();
|
|
135
|
+
if (!bin || !existsSync(bin)) {
|
|
136
|
+
die(`[video-render] op=chromium-missing run=${run} file=${binFile} bin=${bin || "(empty)"}`);
|
|
137
|
+
}
|
|
138
|
+
log(`[video-render] op=chromium-resolve run=${run} bin=${bin} source=config`);
|
|
139
|
+
|
|
140
|
+
for (const tool of ["ffmpeg", "ffprobe"]) {
|
|
141
|
+
try { execFileSync("which", [tool], { stdio: "ignore" }); }
|
|
142
|
+
catch { die(`[video-render] op=tool-missing run=${run} tool=${tool}`); }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const frameDir = join(FRAMES_ROOT, run);
|
|
146
|
+
const stillDir = join(FRAMES_ROOT, `${run}-stills`);
|
|
147
|
+
const profile = join(tmpdir(), `video-production-profile-${run}`);
|
|
148
|
+
rmSync(frameDir, { recursive: true, force: true });
|
|
149
|
+
rmSync(stillDir, { recursive: true, force: true });
|
|
150
|
+
rmSync(profile, { recursive: true, force: true });
|
|
151
|
+
mkdirSync(frameDir, { recursive: true });
|
|
152
|
+
mkdirSync(stillDir, { recursive: true });
|
|
153
|
+
mkdirSync(outDir, { recursive: true });
|
|
154
|
+
|
|
155
|
+
// ---------------------------------------------------------------- launch
|
|
156
|
+
// detached:true makes the child a process-group leader, which is what lets
|
|
157
|
+
// teardown kill the whole browser tree by group rather than leaving zygotes.
|
|
158
|
+
// Never pkill -f on the port: that pattern also matches the shell that ran it.
|
|
159
|
+
|
|
160
|
+
const child = spawn(bin, [
|
|
161
|
+
"--headless=new",
|
|
162
|
+
`--remote-debugging-port=${port}`,
|
|
163
|
+
"--remote-debugging-address=127.0.0.1",
|
|
164
|
+
`--user-data-dir=${profile}`,
|
|
165
|
+
"--no-sandbox",
|
|
166
|
+
"--disable-gpu",
|
|
167
|
+
"--disable-dev-shm-usage",
|
|
168
|
+
"--hide-scrollbars",
|
|
169
|
+
"--force-device-scale-factor=1",
|
|
170
|
+
"--no-first-run",
|
|
171
|
+
"--no-default-browser-check",
|
|
172
|
+
"--disable-background-networking",
|
|
173
|
+
"--disable-features=TranslateUI",
|
|
174
|
+
"--font-render-hinting=none",
|
|
175
|
+
`--window-size=${width},${height}`,
|
|
176
|
+
"about:blank",
|
|
177
|
+
], { detached: true, stdio: "ignore" });
|
|
178
|
+
child.unref();
|
|
179
|
+
const chromePid = child.pid;
|
|
180
|
+
log(`[video-render] op=chromium-start run=${run} pid=${chromePid} port=${port}`);
|
|
181
|
+
|
|
182
|
+
// ---------------------------------------------------------------- CDP
|
|
183
|
+
|
|
184
|
+
async function browserSocket(ms = 20000) {
|
|
185
|
+
const start = Date.now();
|
|
186
|
+
while (Date.now() - start < ms) {
|
|
187
|
+
try {
|
|
188
|
+
const r = await fetch(`http://127.0.0.1:${port}/json/version`);
|
|
189
|
+
if (r.ok) return (await r.json()).webSocketDebuggerUrl;
|
|
190
|
+
} catch { /* not up yet */ }
|
|
191
|
+
await new Promise(r => setTimeout(r, 300));
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const wsUrl = await browserSocket();
|
|
197
|
+
if (!wsUrl) {
|
|
198
|
+
killBrowser();
|
|
199
|
+
die(`[video-render] op=cdp-unreachable run=${run} port=${port}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const ws = new WebSocket(wsUrl);
|
|
203
|
+
await new Promise((res, rej) => {
|
|
204
|
+
ws.addEventListener("open", res, { once: true });
|
|
205
|
+
ws.addEventListener("error", rej, { once: true });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
let msgId = 0, sessionId = null;
|
|
209
|
+
const waiting = new Map();
|
|
210
|
+
const listeners = new Map();
|
|
211
|
+
|
|
212
|
+
ws.addEventListener("message", e => {
|
|
213
|
+
const m = JSON.parse(e.data);
|
|
214
|
+
if (m.id && waiting.has(m.id)) { waiting.get(m.id)(m); waiting.delete(m.id); return; }
|
|
215
|
+
if (m.method && listeners.has(m.method)) {
|
|
216
|
+
const arr = listeners.get(m.method);
|
|
217
|
+
listeners.delete(m.method);
|
|
218
|
+
for (const fn of arr) fn(m.params);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// Arm BEFORE sending the command that triggers the event, or the event can
|
|
223
|
+
// land between the send and the listen and the await never resolves.
|
|
224
|
+
function armed(method) {
|
|
225
|
+
let resolve;
|
|
226
|
+
const p = new Promise(r => { resolve = r; });
|
|
227
|
+
if (!listeners.has(method)) listeners.set(method, []);
|
|
228
|
+
listeners.get(method).push(resolve);
|
|
229
|
+
return p;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const send = (method, params = {}, timeoutMs = 90000) => new Promise((res, rej) => {
|
|
233
|
+
const n = ++msgId;
|
|
234
|
+
const t = setTimeout(() => { waiting.delete(n); rej(new Error(`cdp timeout: ${method}`)); }, timeoutMs);
|
|
235
|
+
waiting.set(n, m => {
|
|
236
|
+
clearTimeout(t);
|
|
237
|
+
m.error ? rej(new Error(`${method}: ${JSON.stringify(m.error)}`)) : res(m.result);
|
|
238
|
+
});
|
|
239
|
+
const msg = { id: n, method, params };
|
|
240
|
+
if (sessionId) msg.sessionId = sessionId;
|
|
241
|
+
ws.send(JSON.stringify(msg));
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
const ev = async expr =>
|
|
245
|
+
(await send("Runtime.evaluate", { expression: expr, returnByValue: true, awaitPromise: true }))
|
|
246
|
+
?.result?.value;
|
|
247
|
+
|
|
248
|
+
// Its own tab. Grabbing the first page target collides with any other CDP client
|
|
249
|
+
// already attached to this browser.
|
|
250
|
+
const { targetId } = await send("Target.createTarget", { url: "about:blank" });
|
|
251
|
+
sessionId = (await send("Target.attachToTarget", { targetId, flatten: true })).sessionId;
|
|
252
|
+
|
|
253
|
+
await send("Page.enable");
|
|
254
|
+
await send("Runtime.enable");
|
|
255
|
+
await send("Emulation.setDeviceMetricsOverride", {
|
|
256
|
+
width, height, deviceScaleFactor: dsf, mobile,
|
|
257
|
+
screenWidth: width, screenHeight: height,
|
|
258
|
+
});
|
|
259
|
+
// Load-bearing. Headless otherwise reports prefers-reduced-motion: reduce, and a
|
|
260
|
+
// page that honours it stops animating — a capture that is silently frozen.
|
|
261
|
+
await send("Emulation.setEmulatedMedia", {
|
|
262
|
+
features: [{ name: "prefers-reduced-motion", value: "no-preference" }],
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// ---------------------------------------------------------------- teardown
|
|
266
|
+
|
|
267
|
+
function killBrowser() {
|
|
268
|
+
try { process.kill(-chromePid, "SIGTERM"); }
|
|
269
|
+
catch { try { process.kill(chromePid, "SIGTERM"); } catch { /* already gone */ } }
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function teardown() {
|
|
273
|
+
try { await send("Target.closeTarget", { targetId }, 10000); } catch { /* browser may be gone */ }
|
|
274
|
+
try { ws.close(); } catch { /* already closed */ }
|
|
275
|
+
log(`[video-render] op=chromium-stop run=${run} pid=${chromePid}`);
|
|
276
|
+
killBrowser();
|
|
277
|
+
// The delay is required. A synchronous check reports a premature alive=false.
|
|
278
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
279
|
+
let alive = true;
|
|
280
|
+
try { process.kill(chromePid, 0); } catch { alive = false; }
|
|
281
|
+
if (alive) { try { process.kill(-chromePid, "SIGKILL"); } catch { /* raced */ } }
|
|
282
|
+
log(`[video-render] op=survives-stop run=${run} pid=${chromePid} alive=${alive}`);
|
|
283
|
+
rmSync(profile, { recursive: true, force: true });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// A failed render must leave no more behind than a successful one. The frames
|
|
287
|
+
// captured before the failure have no consumer, and a 4600-frame run abandons
|
|
288
|
+
// gigabytes if they are kept.
|
|
289
|
+
async function abort(line) {
|
|
290
|
+
await teardown();
|
|
291
|
+
rmSync(frameDir, { recursive: true, force: true });
|
|
292
|
+
rmSync(stillDir, { recursive: true, force: true }); // no stills exist on an abort
|
|
293
|
+
log(`${line} framesDirRemoved=${!existsSync(frameDir)}`);
|
|
294
|
+
census("abort");
|
|
295
|
+
process.exit(1);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ---------------------------------------------------------------- navigate
|
|
299
|
+
|
|
300
|
+
const loaded = armed("Page.loadEventFired");
|
|
301
|
+
try { await send("Page.navigate", { url }); }
|
|
302
|
+
catch (e) { await abort(`[video-render] op=navigate-failed run=${run} url=${url} error=${e.message}`); }
|
|
303
|
+
await loaded;
|
|
304
|
+
await new Promise(r => setTimeout(r, 1500)); // fonts
|
|
305
|
+
|
|
306
|
+
const stepTime = async ms => {
|
|
307
|
+
const expired = armed("Emulation.virtualTimeBudgetExpired");
|
|
308
|
+
await send("Emulation.setVirtualTimePolicy", { policy: "advance", budget: ms });
|
|
309
|
+
await expired;
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
if (mode === "page") await stepTime(1); // arm the virtual clock
|
|
313
|
+
if (pre) {
|
|
314
|
+
try { await ev(pre); }
|
|
315
|
+
catch (e) { await abort(`[video-render] op=pre-failed run=${run} error=${e.message}`); }
|
|
316
|
+
if (mode === "page") await stepTime(60); // let the pre-actions settle
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ---------------------------------------------------------------- the clock
|
|
320
|
+
|
|
321
|
+
let total, preShot, postShot;
|
|
322
|
+
if (mode === "reel") {
|
|
323
|
+
total = await ev("window.__frames");
|
|
324
|
+
if (!Number.isFinite(total) || total < 1) {
|
|
325
|
+
await abort(`[video-render] op=frames-missing run=${run} value=${JSON.stringify(total)}`);
|
|
326
|
+
}
|
|
327
|
+
preShot = async i => { await ev(`window.__seek(${i})`); };
|
|
328
|
+
postShot = async () => {};
|
|
329
|
+
} else {
|
|
330
|
+
total = Number(framesArg);
|
|
331
|
+
if (!Number.isFinite(total) || total < 1) {
|
|
332
|
+
await abort(`[video-render] op=bad-args run=${run} reason=frames-not-a-positive-number`);
|
|
333
|
+
}
|
|
334
|
+
preShot = async () => {};
|
|
335
|
+
postShot = async () => { await stepTime(FRAME_MS); };
|
|
336
|
+
}
|
|
337
|
+
log(`[video-render] op=frames-declared run=${run} frames=${total}`);
|
|
338
|
+
|
|
339
|
+
// ---------------------------------------------------------------- capture
|
|
340
|
+
|
|
341
|
+
async function shot(i) {
|
|
342
|
+
let last;
|
|
343
|
+
for (let attempt = 0; attempt < 4; attempt++) {
|
|
344
|
+
try {
|
|
345
|
+
const r = await send("Page.captureScreenshot",
|
|
346
|
+
{ format: "jpeg", quality: 92, captureBeyondViewport: false, fromSurface: true }, 25000);
|
|
347
|
+
if (r?.data) return Buffer.from(r.data, "base64");
|
|
348
|
+
last = new Error("no screenshot data");
|
|
349
|
+
} catch (e) { last = e; }
|
|
350
|
+
}
|
|
351
|
+
await abort(`[video-render] op=capture-failed run=${run} i=${i} error=${last?.message}`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
let bytesMin = Infinity, bytesMax = 0, bytesTotal = 0;
|
|
355
|
+
for (let i = 0; i < total; i++) {
|
|
356
|
+
await preShot(i);
|
|
357
|
+
const buf = await shot(i);
|
|
358
|
+
writeFileSync(join(frameDir, `f-${String(i).padStart(5, "0")}.jpg`), buf);
|
|
359
|
+
if (buf.length < bytesMin) bytesMin = buf.length;
|
|
360
|
+
if (buf.length > bytesMax) bytesMax = buf.length;
|
|
361
|
+
bytesTotal += buf.length;
|
|
362
|
+
if (i % 60 === 0) log(`[video-render] op=capture run=${run} i=${i} bytes=${buf.length}`);
|
|
363
|
+
await postShot(i);
|
|
364
|
+
}
|
|
365
|
+
// The blank-render discriminator. A frozen capture encodes to a perfectly valid,
|
|
366
|
+
// perfectly useless MP4, and nothing else separates it from a good one.
|
|
367
|
+
log(`[video-render] op=capture-done run=${run} frames=${total} bytesMin=${bytesMin} bytesMax=${bytesMax} bytesTotal=${bytesTotal}`);
|
|
368
|
+
|
|
369
|
+
await teardown();
|
|
370
|
+
|
|
371
|
+
// ---------------------------------------------------------------- encode
|
|
372
|
+
|
|
373
|
+
const mp4 = join(outDir, `${name}.mp4`);
|
|
374
|
+
const poster = join(outDir, `${name}-poster.jpg`);
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
execFileSync("ffmpeg", [
|
|
378
|
+
"-y", "-loglevel", "error", "-framerate", "30",
|
|
379
|
+
"-i", join(frameDir, "f-%05d.jpg"),
|
|
380
|
+
"-c:v", "libx264", "-crf", "21", "-preset", "slow",
|
|
381
|
+
"-pix_fmt", "yuv420p", "-movflags", "+faststart", "-an", mp4,
|
|
382
|
+
]);
|
|
383
|
+
} catch (e) {
|
|
384
|
+
die(`[video-render] op=encode-failed run=${run} error=${e.message}`);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Read back what was produced. Never assert the encode arguments as the result.
|
|
388
|
+
let probe;
|
|
389
|
+
try {
|
|
390
|
+
probe = JSON.parse(execFileSync("ffprobe", [
|
|
391
|
+
"-v", "error", "-select_streams", "v:0",
|
|
392
|
+
"-show_entries", "stream=width,height,codec_name",
|
|
393
|
+
"-show_entries", "format=duration", "-of", "json", mp4,
|
|
394
|
+
]).toString());
|
|
395
|
+
} catch (e) {
|
|
396
|
+
die(`[video-render] op=probe-failed run=${run} error=${e.message}`);
|
|
397
|
+
}
|
|
398
|
+
const stream = probe.streams?.[0] ?? {};
|
|
399
|
+
const durationS = Number(probe.format?.duration ?? 0);
|
|
400
|
+
const fileBytes = statSync(mp4).size;
|
|
401
|
+
log(`[video-render] op=encode run=${run} durationS=${durationS.toFixed(2)} wxh=${stream.width}x${stream.height} codec=${stream.codec_name} fileBytes=${fileBytes}`);
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
execFileSync("ffmpeg", ["-y", "-loglevel", "error", "-ss", String(durationS / 2),
|
|
405
|
+
"-i", mp4, "-frames:v", "1", "-q:v", "3", poster]);
|
|
406
|
+
} catch (e) {
|
|
407
|
+
die(`[video-render] op=poster-failed run=${run} error=${e.message}`);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// The agent must READ these. That is the step that catches a wrapped counter or a
|
|
411
|
+
// clipped headline, and no script can do it.
|
|
412
|
+
const stills = [];
|
|
413
|
+
for (const frac of [0.05, 0.35, 0.65, 0.95]) {
|
|
414
|
+
const at = join(stillDir, `s-${String(Math.round(frac * 100)).padStart(2, "0")}.jpg`);
|
|
415
|
+
try {
|
|
416
|
+
execFileSync("ffmpeg", ["-y", "-loglevel", "error", "-ss", String(durationS * frac),
|
|
417
|
+
"-i", mp4, "-frames:v", "1", "-q:v", "3", at]);
|
|
418
|
+
stills.push(at);
|
|
419
|
+
} catch { /* a still past the last keyframe is not fatal */ }
|
|
420
|
+
}
|
|
421
|
+
log(`[video-render] op=stills run=${run} count=${stills.length} paths=${stills.join(",")}`);
|
|
422
|
+
|
|
423
|
+
// ---------------------------------------------------------------- exit
|
|
424
|
+
|
|
425
|
+
rmSync(frameDir, { recursive: true, force: true });
|
|
426
|
+
const framesDirRemoved = !existsSync(frameDir);
|
|
427
|
+
log(`[video-render] op=exit run=${run} ranMs=${Date.now() - T0} framesDirRemoved=${framesDirRemoved} outputBytes=${fileBytes} mp4=${mp4} poster=${poster}`);
|
|
428
|
+
census("end");
|
|
429
|
+
process.exit(0);
|
|
@@ -9,6 +9,23 @@ Invoked by the admin agent directly.
|
|
|
9
9
|
|
|
10
10
|
This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
|
|
11
11
|
|
|
12
|
+
## 2026-08-04 (0.1.559)
|
|
13
|
+
|
|
14
|
+
- Upgrading no longer deletes specialists. Every upgrade was removing any specialist that did not ship with the install, including ones written for you, and never putting them back. An upgrade now only replaces what it actually ships, and reports at boot any specialist the roster cannot dispatch.
|
|
15
|
+
- A specialist can now tell an instruction from its coordinator apart from text that merely claims to be one. One was refusing genuine operator instructions as suspected injection; every specialist card now states where a coordinator message comes from and what authority it carries.
|
|
16
|
+
- Multi-step workflows can pass data between steps. The runner was discarding each step's structured result, so a later step could never read an earlier one's output.
|
|
17
|
+
- A Telegram bot that receives a message and never answers is now reported. That check existed on the WhatsApp path only.
|
|
18
|
+
- A conversation that starts on a channel and produces nothing is no longer invisible. The assistant's own output is now captured, so a session that boots and then says nothing leaves a trace to diagnose.
|
|
19
|
+
- On a quotation, a gap the assistant noticed while pricing now survives into the saved figures instead of being dropped when the job is handed to the pricing engine.
|
|
20
|
+
- The scheduling health count now covers routines that run without the model, not just those that use it, so the shift away from model-driven runs has something to measure against.
|
|
21
|
+
|
|
22
|
+
## 2026-08-04 (0.1.558)
|
|
23
|
+
|
|
24
|
+
- The assistant can now produce short videos and social reels. Ask it for a video and it renders an MP4 from your images, text and audio, rather than assembling a one-off pipeline each time.
|
|
25
|
+
- A public Telegram bot can now reply. Bots opened on the public route were being given a reply tool that was never connected, so messages arrived, the conversation stayed alive, and nothing ever came back.
|
|
26
|
+
- Scrolling back through a very long conversation no longer stops part-way. A single very large message could stall the scroll-back at a fixed point, putting everything above it out of reach no matter how far you scrolled; on one 15 MB conversation that was everything before the last 1.4 MB.
|
|
27
|
+
- The Telegram health check now separates how long ago a conversation was started from how long ago it last said anything, and can see a conversation that has not spoken yet.
|
|
28
|
+
|
|
12
29
|
## 2026-08-04 (0.1.557)
|
|
13
30
|
|
|
14
31
|
- The status dot on a one-time routine that has already run now shows it as finished rather than live, matching the card text added in the previous release.
|
|
@@ -27,6 +27,7 @@ Load these when users ask about Maxy features or need guidance:
|
|
|
27
27
|
- **Projects** → `references/projects-guide.md` — creating projects, tracking health, phase transitions, completing
|
|
28
28
|
- **Workflows** → `references/workflows.md` — saving and running multi-step processes, declared inputs, scheduling, run history
|
|
29
29
|
- **Slides** → `references/slides.md` — generating, theming, and reviewing HTML slide decks with `/slides`
|
|
30
|
+
- **Making videos** → `references/video-production.md` — social reels and animated explainers as real MP4 files, portrait or landscape, made on the device with no upload
|
|
30
31
|
- **Calendar & booking** → `references/calendar-booking.md` — the admin calendar at `/calendar`, deploying a public booking page, setting availability, and how bookings become meetings
|
|
31
32
|
- **Troubleshooting** → `references/troubleshooting.md` — common issues and how to resolve them
|
|
32
33
|
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Making videos
|
|
2
|
+
|
|
3
|
+
{{productName}} makes real video files on your own device. No upload, no external service, no subscription. Ask for one in plain words.
|
|
4
|
+
|
|
5
|
+
> "Make me three social reels about the free trial."
|
|
6
|
+
>
|
|
7
|
+
> "Can I have that in portrait and landscape?"
|
|
8
|
+
>
|
|
9
|
+
> "Record that page playing its animation."
|
|
10
|
+
|
|
11
|
+
## What you get
|
|
12
|
+
|
|
13
|
+
An MP4 file, silent, at whatever size you need. Portrait (1080x1920) for stories and reels, landscape (1920x1080) for a website or a presentation, or any other shape you ask for. Each video comes with a poster image, which is the still that shows before someone presses play.
|
|
14
|
+
|
|
15
|
+
Ask for another shape at any time and the same content is recut for it. You do not have to describe it again.
|
|
16
|
+
|
|
17
|
+
## Two kinds of video
|
|
18
|
+
|
|
19
|
+
**Made from scratch.** The agent designs the video itself, in your brand's colours and type, and animates it frame by frame. This is what you want for a social reel, an explainer, or anything with a message to land.
|
|
20
|
+
|
|
21
|
+
**A recording of a page.** If you already have a page that animates, the agent can record it playing. Useful when the page is the thing you want to show.
|
|
22
|
+
|
|
23
|
+
## How long it takes
|
|
24
|
+
|
|
25
|
+
A short reel takes a few minutes. A long recording of a full page can take considerably longer. The agent tells you when it starts and comes back when it is done.
|
|
26
|
+
|
|
27
|
+
## What to check
|
|
28
|
+
|
|
29
|
+
The agent looks at four frames from every video it makes before telling you it is finished, so a video that came out blank or with text running off the edge gets caught and re-rendered. You should still watch it. Text that is correct but reads badly is a judgement only you can make.
|
|
30
|
+
|
|
31
|
+
## Where the files go
|
|
32
|
+
|
|
33
|
+
Finished videos land in your account's output folder, and the agent sends them to you in the chat. Ask for them on a web page and they get published to your site with everything on one page you can open on a phone.
|
|
34
|
+
|
|
35
|
+
## Fonts
|
|
36
|
+
|
|
37
|
+
Videos use the fonts installed on your device. If you want a specific brand font and it has never been used on this device before, say so — it may need installing first.
|
|
@@ -6,7 +6,11 @@ export interface ToolResult {
|
|
|
6
6
|
/** Present and true only on error results, so `.isError` is well-typed on the
|
|
7
7
|
* ok|err union that handlers return. */
|
|
8
8
|
isError?: true;
|
|
9
|
+
/** Task 2406 — the tool's structured return, for a workflow step's
|
|
10
|
+
* `{{key.field}}`. Absent on errors: the workflow engine throws on isError
|
|
11
|
+
* before it reads the output. */
|
|
12
|
+
structuredContent?: Record<string, unknown>;
|
|
9
13
|
}
|
|
10
|
-
export declare const ok: (text: string) => ToolResult;
|
|
14
|
+
export declare const ok: (text: string, structuredContent?: Record<string, unknown>) => ToolResult;
|
|
11
15
|
export declare const err: (text: string) => ToolResult;
|
|
12
16
|
//# sourceMappingURL=result.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/lib/result.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C;6CACyC;IACzC,OAAO,CAAC,EAAE,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/lib/result.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC1C;6CACyC;IACzC,OAAO,CAAC,EAAE,IAAI,CAAC;IACf;;sCAEkC;IAClC,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC7C;AAED,eAAO,MAAM,EAAE,GAAI,MAAM,MAAM,EAAE,oBAAoB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,UAG7E,CAAC;AACH,eAAO,MAAM,GAAG,GAAI,MAAM,MAAM,KAAG,UAGjC,CAAC"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export const ok = (text) => ({
|
|
1
|
+
export const ok = (text, structuredContent) => ({
|
|
2
|
+
content: [{ type: "text", text }],
|
|
3
|
+
...(structuredContent ? { structuredContent } : {}),
|
|
4
|
+
});
|
|
2
5
|
export const err = (text) => ({
|
|
3
6
|
content: [{ type: "text", text }],
|
|
4
7
|
isError: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/lib/result.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"result.js","sourceRoot":"","sources":["../../src/lib/result.ts"],"names":[],"mappings":"AAYA,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,IAAY,EAAE,iBAA2C,EAAc,EAAE,CAAC,CAAC;IAC5F,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACjC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;CACpD,CAAC,CAAC;AACH,MAAM,CAAC,MAAM,GAAG,GAAG,CAAC,IAAY,EAAc,EAAE,CAAC,CAAC;IAChD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACjC,OAAO,EAAE,IAAI;CACd,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-read.d.ts","sourceRoot":"","sources":["../../src/tools/file-read.ts"],"names":[],"mappings":"AACA,OAAO,EAAiD,KAAK,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAK1F,wBAAgB,QAAQ,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,GAAG,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"file-read.d.ts","sourceRoot":"","sources":["../../src/tools/file-read.ts"],"names":[],"mappings":"AACA,OAAO,EAAiD,KAAK,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAK1F,wBAAgB,QAAQ,CAAC,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,GAAG,EAAE,GAAG,yCAoBpG"}
|
|
@@ -19,7 +19,8 @@ export function fileRead(args, ctx) {
|
|
|
19
19
|
return err(`file-read: not a file: ${args.path}`);
|
|
20
20
|
if (st.size > MAX_FILE)
|
|
21
21
|
return err(`file-read: file too large (${st.size} bytes, max ${MAX_FILE})`);
|
|
22
|
-
|
|
22
|
+
const content = readFileSync(abs, "utf-8");
|
|
23
|
+
return ok(content, { path: args.path, content, bytes: st.size });
|
|
23
24
|
}
|
|
24
25
|
catch (e) {
|
|
25
26
|
return err(`file-read: ${e.message}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-read.js","sourceRoot":"","sources":["../../src/tools/file-read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,iBAAiB,EAAY,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAE3C,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO;AAEzC,MAAM,UAAU,QAAQ,CAAC,IAAkE,EAAE,GAAQ;IACnG,MAAM,CAAC,GAAG,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3E,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,qBAAqB,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CACR,wCAAwC,CAAC,CAAC,QAAQ,KAAK,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,YAAY,CAC5G,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE;YAAE,OAAO,GAAG,CAAC,0BAA0B,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACpE,IAAI,EAAE,CAAC,IAAI,GAAG,QAAQ;YAAE,OAAO,GAAG,CAAC,8BAA8B,EAAE,CAAC,IAAI,eAAe,QAAQ,GAAG,CAAC,CAAC;QACpG,OAAO,EAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"file-read.js","sourceRoot":"","sources":["../../src/tools/file-read.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,iBAAiB,EAAY,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAE3C,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,OAAO;AAEzC,MAAM,UAAU,QAAQ,CAAC,IAAkE,EAAE,GAAQ;IACnG,MAAM,CAAC,GAAG,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC3E,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,qBAAqB,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAC/D,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CACR,wCAAwC,CAAC,CAAC,QAAQ,KAAK,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,YAAY,CAC5G,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE;YAAE,OAAO,GAAG,CAAC,0BAA0B,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QACpE,IAAI,EAAE,CAAC,IAAI,GAAG,QAAQ;YAAE,OAAO,GAAG,CAAC,8BAA8B,EAAE,CAAC,IAAI,eAAe,QAAQ,GAAG,CAAC,CAAC;QACpG,MAAM,OAAO,GAAG,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;QAC3C,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC;IACnE,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,GAAG,CAAC,cAAe,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-write.d.ts","sourceRoot":"","sources":["../../src/tools/file-write.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiD,KAAK,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAG1F,wBAAgB,SAAS,CACvB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EACnF,GAAG,EAAE,GAAG,
|
|
1
|
+
{"version":3,"file":"file-write.d.ts","sourceRoot":"","sources":["../../src/tools/file-write.ts"],"names":[],"mappings":"AAEA,OAAO,EAAiD,KAAK,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAG1F,wBAAgB,SAAS,CACvB,IAAI,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EACnF,GAAG,EAAE,GAAG,yCAoBT"}
|
|
@@ -16,7 +16,8 @@ export function fileWrite(args, ctx) {
|
|
|
16
16
|
try {
|
|
17
17
|
mkdirSync(dirname(abs), { recursive: true });
|
|
18
18
|
writeFileSync(abs, args.content, "utf-8");
|
|
19
|
-
|
|
19
|
+
const bytes = Buffer.byteLength(args.content);
|
|
20
|
+
return ok(`wrote ${bytes} bytes to ${args.path}`, { path: args.path, bytes });
|
|
20
21
|
}
|
|
21
22
|
catch (e) {
|
|
22
23
|
return err(`file-write: ${e.message}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-write.js","sourceRoot":"","sources":["../../src/tools/file-write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,iBAAiB,EAAY,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAE3C,MAAM,UAAU,SAAS,CACvB,IAAmF,EACnF,GAAQ;IAER,MAAM,CAAC,GAAG,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5E,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,sBAAsB,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAChE,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CACR,yCAAyC,CAAC,CAAC,QAAQ,KAAK,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,YAAY,CAC7G,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC1C,
|
|
1
|
+
{"version":3,"file":"file-write.js","sourceRoot":"","sources":["../../src/tools/file-write.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,iBAAiB,EAAY,MAAM,iBAAiB,CAAC;AAC1F,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AAE3C,MAAM,UAAU,SAAS,CACvB,IAAmF,EACnF,GAAQ;IAER,MAAM,CAAC,GAAG,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC,eAAe,EAAE,YAAY,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5E,IAAI,QAAQ,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,sBAAsB,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;IAChE,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CACR,yCAAyC,CAAC,CAAC,QAAQ,KAAK,iBAAiB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,YAAY,CAC7G,CAAC;IACJ,CAAC;IACD,IAAI,CAAC;QACH,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC1C,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC9C,OAAO,EAAE,CAAC,SAAS,KAAK,aAAa,IAAI,CAAC,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAChF,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,GAAG,CAAC,eAAgB,CAAW,CAAC,OAAO,EAAE,CAAC,CAAC;IACpD,CAAC;AACH,CAAC"}
|
|
@@ -138,11 +138,11 @@ Backfilling already-written `:Event` appointments into `:Meeting` is a separate
|
|
|
138
138
|
|
|
139
139
|
### Reading meetings back
|
|
140
140
|
|
|
141
|
-
Both labels are readable. `schedule-list` returns meetings and events together and tags each row `kind`; a meeting renders as `MEETING — <title> (<start>) → <end> (<uid>)`. `status` filters **events only**, because a `:Meeting` carries no `eventStatus`; `from`, `to` and `upcoming` apply to meetings on `startsAt`.
|
|
141
|
+
Both labels are readable. `schedule-list` returns meetings and events together and tags each row `kind`; a meeting renders as `MEETING — <title> (<start>) → <end> (<uid>)`. `status` filters **events only**, because a `:Meeting` carries no `eventStatus`; `from`, `to` and `upcoming` apply to meetings on `startsAt`. `kind` selects one source: `kind: "event"` returns scheduled events only, `kind: "meeting"` returns booked meetings only, and omitting it returns both. Because `status` is event-only, `kind: "meeting"` combined with a status returns nothing rather than silently dropping one of the two filters.
|
|
142
142
|
|
|
143
143
|
A meeting's handle is its `uid`. `schedule-event` returns it on creation, and `schedule-get`, `schedule-update` and `schedule-cancel` accept it wherever they accept an event id — they resolve an event first, then a meeting, so a caller never has to say which kind it holds. Updating a meeting sets `title`, `startsAt`, `endsAt` and `location` and never rewrites the `uid`; `eventStatus`, `recurrence`, `note`, `action` and `agentDispatch` have no meeting equivalent and do not cross over. A meeting DOES carry a `description`: `schedule-event` writes one, `schedule-get` returns it, and the admin Calendar edits it. `schedule-update` does not write it, so this tool's field set is narrower than the admin Calendar edit's. Cancelling a meeting **deletes** it and its attendee edges, the same as deleting it on the Calendar page — a `:Meeting` has no status to set to cancelled. A booking-sourced meeting that is deleted is rewritten by the next reconcile run.
|
|
144
144
|
|
|
145
|
-
Until this landed, `schedule-list` matched `:Event` alone while `schedule-event` wrote `:Meeting`, so an agent could not see its own writes: a calendar-sync turn read back an empty list, re-applied the same three meetings, and told the operator over WhatsApp that they had not saved. Observability is therefore two-part. Every list call logs `[schedule-list] op=result meetings=<n> events=<n>`. The standing check is `[schedule-audit] account=<id> meetings=<n> listable=<n> invisible=<n>`, emitted per account on every `reconcile-bookings` run (every 120 s) **before** that script's Cloudflare gates, so it also reports on installs with no booking site. `listable` is what the real `scheduleList` returns, not a re-implementation of its query — that is the whole point, since a copy would agree with a broken original. `invisible=0` is the success signal; `invisible>0` means a calendar entry exists that the agent cannot see.
|
|
145
|
+
Until this landed, `schedule-list` matched `:Event` alone while `schedule-event` wrote `:Meeting`, so an agent could not see its own writes: a calendar-sync turn read back an empty list, re-applied the same three meetings, and told the operator over WhatsApp that they had not saved. Observability is therefore two-part. Every list call logs `[schedule-list] op=result kind=<kind|any> meetings=<n> events=<n>`. The standing check is `[schedule-audit] account=<id> meetings=<n> listable=<n> invisible=<n>`, emitted per account on every `reconcile-bookings` run (every 120 s) **before** that script's Cloudflare gates, so it also reports on installs with no booking site. `listable` is what the real `scheduleList` returns, not a re-implementation of its query — that is the whole point, since a copy would agree with a broken original. `invisible=0` is the success signal; `invisible>0` means a calendar entry exists that the agent cannot see.
|
|
146
146
|
|
|
147
147
|
### Routine run records
|
|
148
148
|
|
|
@@ -389,6 +389,8 @@ The `sessionKey` parameter on `schedule-event` automatically creates the `PART_O
|
|
|
389
389
|
|
|
390
390
|
Export events as `.ics` files so users can add them to Apple Calendar, Google Calendar, or Outlook. Use `schedule-export-ics` with the event IDs; it writes the `.ics` under the account's `output/ics/` directory and returns the path. Deliver that file to the operator (see the `file-presentation` skill for how delivery works on this surface).
|
|
391
391
|
|
|
392
|
+
Both `schedule-list` and `schedule-export-ics` also return `structuredContent` — `{ events, count }` and the export result (`outputPath`, `eventCount`, `rruleMapped`, `rruleFallback`, `notFound`, `noStartDate`) — so a workflow step can address those fields directly instead of parsing the prose.
|
|
393
|
+
|
|
392
394
|
Import `.ics` files uploaded by the user. Use `schedule-import-ics` with the attachment path. Present the extracted events for confirmation before creating them via `schedule-event`.
|
|
393
395
|
|
|
394
396
|
Cron→RRULE mapping for recurring events: daily, weekly-with-days, and monthly-by-date patterns produce standard RRULE properties. Cron patterns that cannot map to RRULE (e.g. `0 8 1,15 * *`) produce individual VEVENTs per occurrence instead.
|