synabun 2.0.5 → 2.0.9
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/CHANGELOG.md +78 -0
- package/README.md +2 -1
- package/THIRD-PARTY-LICENSES.md +13 -0
- package/docs/whatsapp.md +151 -16
- package/hooks/claude-code/post-plan.mjs +6 -30
- package/hooks/claude-code/session-start.mjs +1 -4
- package/hooks/claude-code/shared.mjs +112 -75
- package/lib/child-path.js +303 -0
- package/lib/claude-hooks.js +2 -1
- package/lib/data-home-migration.js +2 -0
- package/lib/desktop-app.js +265 -0
- package/lib/external-tools.js +7 -3
- package/lib/log-redact.js +102 -0
- package/lib/package-processes.js +670 -0
- package/lib/packaged-runtime.js +138 -11
- package/lib/runtime-dependencies.js +225 -0
- package/mcp-server/dist/http.d.ts +3 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +15 -4
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +9 -6
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/services/browser-bench.d.ts +1 -1
- package/mcp-server/dist/services/browser-bench.d.ts.map +1 -1
- package/mcp-server/dist/services/browser-bench.js +1 -1
- package/mcp-server/dist/services/browser-bench.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +108 -0
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -0
- package/mcp-server/dist/services/budget-judgments.js +153 -0
- package/mcp-server/dist/services/budget-judgments.js.map +1 -0
- package/mcp-server/dist/services/categories.d.ts +35 -2
- package/mcp-server/dist/services/categories.d.ts.map +1 -1
- package/mcp-server/dist/services/categories.js +77 -33
- package/mcp-server/dist/services/categories.js.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts +5 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +41 -4
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/desktop-bench.d.ts +7 -7
- package/mcp-server/dist/services/desktop-bench.d.ts.map +1 -1
- package/mcp-server/dist/services/desktop-bench.js +1 -1
- package/mcp-server/dist/services/desktop-bench.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +11 -0
- package/mcp-server/dist/services/identity.d.ts.map +1 -1
- package/mcp-server/dist/services/identity.js +24 -0
- package/mcp-server/dist/services/identity.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +13 -0
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +34 -2
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts +1 -1
- package/mcp-server/dist/services/typesafe-config.d.ts.map +1 -1
- package/mcp-server/dist/services/typesafe-config.js +4 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +11 -8
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +35 -14
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/bluesky.js +1 -1
- package/mcp-server/dist/tools/bluesky.js.map +1 -1
- package/mcp-server/dist/tools/browser-advanced.d.ts +1 -1
- package/mcp-server/dist/tools/browser-advanced.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-advanced.js +1 -1
- package/mcp-server/dist/tools/browser-advanced.js.map +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-batch.js +1 -1
- package/mcp-server/dist/tools/browser-batch.js.map +1 -1
- package/mcp-server/dist/tools/browser-cheatsheet.d.ts +1 -1
- package/mcp-server/dist/tools/browser-cheatsheet.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-cheatsheet.js +1 -1
- package/mcp-server/dist/tools/browser-cheatsheet.js.map +1 -1
- package/mcp-server/dist/tools/browser-interact.d.ts +1 -1
- package/mcp-server/dist/tools/browser-interact.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-interact.js +1 -1
- package/mcp-server/dist/tools/browser-interact.js.map +1 -1
- package/mcp-server/dist/tools/browser-navigate.d.ts +1 -1
- package/mcp-server/dist/tools/browser-navigate.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-navigate.js +1 -1
- package/mcp-server/dist/tools/browser-navigate.js.map +1 -1
- package/mcp-server/dist/tools/browser-observe.d.ts +1 -1
- package/mcp-server/dist/tools/browser-observe.d.ts.map +1 -1
- package/mcp-server/dist/tools/browser-observe.js +1 -1
- package/mcp-server/dist/tools/browser-observe.js.map +1 -1
- package/mcp-server/dist/tools/card-tools.d.ts +1 -1
- package/mcp-server/dist/tools/card-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/card-tools.js +1 -1
- package/mcp-server/dist/tools/card-tools.js.map +1 -1
- package/mcp-server/dist/tools/category.d.ts +1 -1
- package/mcp-server/dist/tools/category.d.ts.map +1 -1
- package/mcp-server/dist/tools/category.js +5 -1
- package/mcp-server/dist/tools/category.js.map +1 -1
- package/mcp-server/dist/tools/choice.d.ts +1 -1
- package/mcp-server/dist/tools/choice.d.ts.map +1 -1
- package/mcp-server/dist/tools/choice.js +1 -1
- package/mcp-server/dist/tools/choice.js.map +1 -1
- package/mcp-server/dist/tools/computer.d.ts +1 -1
- package/mcp-server/dist/tools/computer.d.ts.map +1 -1
- package/mcp-server/dist/tools/computer.js +1 -1
- package/mcp-server/dist/tools/computer.js.map +1 -1
- package/mcp-server/dist/tools/discord-channel.d.ts +1 -1
- package/mcp-server/dist/tools/discord-channel.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-channel.js +1 -1
- package/mcp-server/dist/tools/discord-channel.js.map +1 -1
- package/mcp-server/dist/tools/discord-guild.d.ts +1 -1
- package/mcp-server/dist/tools/discord-guild.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-guild.js +1 -1
- package/mcp-server/dist/tools/discord-guild.js.map +1 -1
- package/mcp-server/dist/tools/discord-member.d.ts +1 -1
- package/mcp-server/dist/tools/discord-member.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-member.js +1 -1
- package/mcp-server/dist/tools/discord-member.js.map +1 -1
- package/mcp-server/dist/tools/discord-message.d.ts +1 -1
- package/mcp-server/dist/tools/discord-message.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-message.js +1 -1
- package/mcp-server/dist/tools/discord-message.js.map +1 -1
- package/mcp-server/dist/tools/discord-onboarding.d.ts +1 -1
- package/mcp-server/dist/tools/discord-onboarding.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-onboarding.js +1 -1
- package/mcp-server/dist/tools/discord-onboarding.js.map +1 -1
- package/mcp-server/dist/tools/discord-role.d.ts +1 -1
- package/mcp-server/dist/tools/discord-role.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-role.js +1 -1
- package/mcp-server/dist/tools/discord-role.js.map +1 -1
- package/mcp-server/dist/tools/discord-thread.d.ts +1 -1
- package/mcp-server/dist/tools/discord-thread.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-thread.js +1 -1
- package/mcp-server/dist/tools/discord-thread.js.map +1 -1
- package/mcp-server/dist/tools/discord-webhook.d.ts +1 -1
- package/mcp-server/dist/tools/discord-webhook.d.ts.map +1 -1
- package/mcp-server/dist/tools/discord-webhook.js +1 -1
- package/mcp-server/dist/tools/discord-webhook.js.map +1 -1
- package/mcp-server/dist/tools/fb-groups.d.ts +1 -1
- package/mcp-server/dist/tools/fb-groups.d.ts.map +1 -1
- package/mcp-server/dist/tools/fb-groups.js +1 -1
- package/mcp-server/dist/tools/fb-groups.js.map +1 -1
- package/mcp-server/dist/tools/forget.d.ts +1 -1
- package/mcp-server/dist/tools/forget.d.ts.map +1 -1
- package/mcp-server/dist/tools/forget.js +1 -1
- package/mcp-server/dist/tools/forget.js.map +1 -1
- package/mcp-server/dist/tools/git-tools.d.ts +1 -1
- package/mcp-server/dist/tools/git-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/git-tools.js +1 -1
- package/mcp-server/dist/tools/git-tools.js.map +1 -1
- package/mcp-server/dist/tools/gsc/experience.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/experience.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/experience.js +1 -1
- package/mcp-server/dist/tools/gsc/experience.js.map +1 -1
- package/mcp-server/dist/tools/gsc/indexing.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/indexing.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/indexing.js +1 -1
- package/mcp-server/dist/tools/gsc/indexing.js.map +1 -1
- package/mcp-server/dist/tools/gsc/inspect.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/inspect.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/inspect.js +1 -1
- package/mcp-server/dist/tools/gsc/inspect.js.map +1 -1
- package/mcp-server/dist/tools/gsc/navigate.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/navigate.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/navigate.js +1 -1
- package/mcp-server/dist/tools/gsc/navigate.js.map +1 -1
- package/mcp-server/dist/tools/gsc/performance.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/performance.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/performance.js +1 -1
- package/mcp-server/dist/tools/gsc/performance.js.map +1 -1
- package/mcp-server/dist/tools/gsc/settings.d.ts +1 -1
- package/mcp-server/dist/tools/gsc/settings.d.ts.map +1 -1
- package/mcp-server/dist/tools/gsc/settings.js +1 -1
- package/mcp-server/dist/tools/gsc/settings.js.map +1 -1
- package/mcp-server/dist/tools/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/image-tools.js +1 -1
- package/mcp-server/dist/tools/image-tools.js.map +1 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +1 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js +1 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +1 -1
- package/mcp-server/dist/tools/loop.d.ts +1 -1
- package/mcp-server/dist/tools/loop.d.ts.map +1 -1
- package/mcp-server/dist/tools/loop.js +1 -1
- package/mcp-server/dist/tools/loop.js.map +1 -1
- package/mcp-server/dist/tools/memories.d.ts +1 -1
- package/mcp-server/dist/tools/memories.d.ts.map +1 -1
- package/mcp-server/dist/tools/memories.js +1 -1
- package/mcp-server/dist/tools/memories.js.map +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
- package/mcp-server/dist/tools/morelogin.js +1 -1
- package/mcp-server/dist/tools/morelogin.js.map +1 -1
- package/mcp-server/dist/tools/profile.d.ts +1 -1
- package/mcp-server/dist/tools/profile.d.ts.map +1 -1
- package/mcp-server/dist/tools/profile.js +1 -1
- package/mcp-server/dist/tools/profile.js.map +1 -1
- package/mcp-server/dist/tools/recall.d.ts +1 -1
- package/mcp-server/dist/tools/recall.d.ts.map +1 -1
- package/mcp-server/dist/tools/recall.js +1 -1
- package/mcp-server/dist/tools/recall.js.map +1 -1
- package/mcp-server/dist/tools/reflect.d.ts +1 -1
- package/mcp-server/dist/tools/reflect.d.ts.map +1 -1
- package/mcp-server/dist/tools/reflect.js +1 -1
- package/mcp-server/dist/tools/reflect.js.map +1 -1
- package/mcp-server/dist/tools/remember.d.ts +1 -1
- package/mcp-server/dist/tools/remember.d.ts.map +1 -1
- package/mcp-server/dist/tools/remember.js +13 -11
- package/mcp-server/dist/tools/remember.js.map +1 -1
- package/mcp-server/dist/tools/restore.d.ts +1 -1
- package/mcp-server/dist/tools/restore.d.ts.map +1 -1
- package/mcp-server/dist/tools/restore.js +1 -1
- package/mcp-server/dist/tools/restore.js.map +1 -1
- package/mcp-server/dist/tools/style-guide-tools.d.ts +1 -1
- package/mcp-server/dist/tools/style-guide-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/style-guide-tools.js +1 -1
- package/mcp-server/dist/tools/style-guide-tools.js.map +1 -1
- package/mcp-server/dist/tools/sync.d.ts +1 -1
- package/mcp-server/dist/tools/sync.d.ts.map +1 -1
- package/mcp-server/dist/tools/sync.js +1 -1
- package/mcp-server/dist/tools/sync.js.map +1 -1
- package/mcp-server/dist/tools/tictactoe-tools.d.ts +1 -1
- package/mcp-server/dist/tools/tictactoe-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/tictactoe-tools.js +1 -1
- package/mcp-server/dist/tools/tictactoe-tools.js.map +1 -1
- package/mcp-server/dist/tools/utils.d.ts +1 -1
- package/mcp-server/dist/tools/utils.d.ts.map +1 -1
- package/mcp-server/dist/tools/utils.js +1 -1
- package/mcp-server/dist/tools/utils.js.map +1 -1
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts +17 -0
- package/mcp-server/dist/tools/whatsapp-send-file.d.ts.map +1 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js +44 -0
- package/mcp-server/dist/tools/whatsapp-send-file.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +3 -3
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
- package/mcp-server/dist/tools/youtube/analytics.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/analytics.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/analytics.js +1 -1
- package/mcp-server/dist/tools/youtube/analytics.js.map +1 -1
- package/mcp-server/dist/tools/youtube/download.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/download.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/download.js +1 -1
- package/mcp-server/dist/tools/youtube/download.js.map +1 -1
- package/mcp-server/dist/tools/youtube/media.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/media.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/media.js +1 -1
- package/mcp-server/dist/tools/youtube/media.js.map +1 -1
- package/mcp-server/dist/tools/youtube/metadata.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/metadata.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/metadata.js +1 -1
- package/mcp-server/dist/tools/youtube/metadata.js.map +1 -1
- package/mcp-server/dist/tools/youtube/navigate.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/navigate.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/navigate.js +1 -1
- package/mcp-server/dist/tools/youtube/navigate.js.map +1 -1
- package/mcp-server/dist/tools/youtube/playlist.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/playlist.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/playlist.js +1 -1
- package/mcp-server/dist/tools/youtube/playlist.js.map +1 -1
- package/mcp-server/dist/tools/youtube/schedule.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/schedule.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/schedule.js +1 -1
- package/mcp-server/dist/tools/youtube/schedule.js.map +1 -1
- package/mcp-server/dist/tools/youtube/source.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/source.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/source.js +1 -1
- package/mcp-server/dist/tools/youtube/source.js.map +1 -1
- package/mcp-server/dist/tools/youtube/upload.d.ts +1 -1
- package/mcp-server/dist/tools/youtube/upload.d.ts.map +1 -1
- package/mcp-server/dist/tools/youtube/upload.js +4 -9
- package/mcp-server/dist/tools/youtube/upload.js.map +1 -1
- package/mcp-server/dist/tui.js +18 -5
- package/mcp-server/dist/tui.js.map +1 -1
- package/mcp-server/dist/version.d.ts +5 -0
- package/mcp-server/dist/version.d.ts.map +1 -0
- package/mcp-server/dist/version.js +21 -0
- package/mcp-server/dist/version.js.map +1 -0
- package/mcp-server/package-lock.json +3 -3
- package/mcp-server/package.json +3 -3
- package/mcp-server/run.mjs +15 -9
- package/mcp-server/src/http.ts +18 -3
- package/mcp-server/src/index.ts +9 -6
- package/mcp-server/src/services/browser-bench.ts +1 -1
- package/mcp-server/src/services/budget-judgments.ts +183 -0
- package/mcp-server/src/services/categories.ts +77 -32
- package/mcp-server/src/services/codex-config-heal.ts +35 -4
- package/mcp-server/src/services/desktop-bench.ts +1 -1
- package/mcp-server/src/services/identity.ts +27 -0
- package/mcp-server/src/services/neural-interface.ts +34 -2
- package/mcp-server/src/services/typesafe-config.ts +4 -1
- package/mcp-server/src/tools/agents.ts +34 -15
- package/mcp-server/src/tools/bluesky.ts +1 -1
- package/mcp-server/src/tools/browser-advanced.ts +1 -1
- package/mcp-server/src/tools/browser-batch.ts +1 -1
- package/mcp-server/src/tools/browser-cheatsheet.ts +1 -1
- package/mcp-server/src/tools/browser-interact.ts +1 -1
- package/mcp-server/src/tools/browser-navigate.ts +1 -1
- package/mcp-server/src/tools/browser-observe.ts +1 -1
- package/mcp-server/src/tools/card-tools.ts +1 -1
- package/mcp-server/src/tools/category.ts +6 -1
- package/mcp-server/src/tools/choice.ts +1 -1
- package/mcp-server/src/tools/computer.ts +1 -1
- package/mcp-server/src/tools/discord-channel.ts +1 -1
- package/mcp-server/src/tools/discord-guild.ts +1 -1
- package/mcp-server/src/tools/discord-member.ts +1 -1
- package/mcp-server/src/tools/discord-message.ts +1 -1
- package/mcp-server/src/tools/discord-onboarding.ts +1 -1
- package/mcp-server/src/tools/discord-role.ts +1 -1
- package/mcp-server/src/tools/discord-thread.ts +1 -1
- package/mcp-server/src/tools/discord-webhook.ts +1 -1
- package/mcp-server/src/tools/fb-groups.ts +1 -1
- package/mcp-server/src/tools/forget.ts +1 -1
- package/mcp-server/src/tools/git-tools.ts +1 -1
- package/mcp-server/src/tools/gsc/experience.ts +1 -1
- package/mcp-server/src/tools/gsc/indexing.ts +1 -1
- package/mcp-server/src/tools/gsc/inspect.ts +1 -1
- package/mcp-server/src/tools/gsc/navigate.ts +1 -1
- package/mcp-server/src/tools/gsc/performance.ts +1 -1
- package/mcp-server/src/tools/gsc/settings.ts +1 -1
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/leonardo-browser-tools.ts +1 -1
- package/mcp-server/src/tools/loop.ts +1 -1
- package/mcp-server/src/tools/memories.ts +1 -1
- package/mcp-server/src/tools/morelogin.ts +1 -1
- package/mcp-server/src/tools/profile.ts +1 -1
- package/mcp-server/src/tools/recall.ts +1 -1
- package/mcp-server/src/tools/reflect.ts +1 -1
- package/mcp-server/src/tools/remember.ts +13 -12
- package/mcp-server/src/tools/restore.ts +1 -1
- package/mcp-server/src/tools/style-guide-tools.ts +1 -1
- package/mcp-server/src/tools/sync.ts +1 -1
- package/mcp-server/src/tools/tictactoe-tools.ts +1 -1
- package/mcp-server/src/tools/utils.ts +1 -1
- package/mcp-server/src/tools/whatsapp-send-file.ts +51 -0
- package/mcp-server/src/tools/whiteboard-tools.ts +1 -1
- package/mcp-server/src/tools/youtube/analytics.ts +1 -1
- package/mcp-server/src/tools/youtube/download.ts +1 -1
- package/mcp-server/src/tools/youtube/media.ts +1 -1
- package/mcp-server/src/tools/youtube/metadata.ts +1 -1
- package/mcp-server/src/tools/youtube/navigate.ts +1 -1
- package/mcp-server/src/tools/youtube/playlist.ts +1 -1
- package/mcp-server/src/tools/youtube/schedule.ts +1 -1
- package/mcp-server/src/tools/youtube/source.ts +1 -1
- package/mcp-server/src/tools/youtube/upload.ts +4 -12
- package/mcp-server/src/tui.ts +24 -10
- package/mcp-server/src/version.ts +22 -0
- package/neural-interface/i18n/en.json +645 -57
- package/neural-interface/i18n/pt-BR.json +644 -56
- package/neural-interface/lib/account-usage.js +163 -0
- package/neural-interface/lib/agent-sandbox-elevation.js +102 -0
- package/neural-interface/lib/agent-sandbox-setting.js +57 -0
- package/neural-interface/lib/assistant-api.js +274 -52
- package/neural-interface/lib/assistant-brains/claude.js +15 -2
- package/neural-interface/lib/assistant-brains/codex.js +3 -0
- package/neural-interface/lib/assistant-brains/opencode.js +17 -4
- package/neural-interface/lib/assistant-budget-planner.js +390 -0
- package/neural-interface/lib/assistant-budget-split.js +408 -0
- package/neural-interface/lib/assistant-budget.js +94 -95
- package/neural-interface/lib/assistant-config.js +62 -14
- package/neural-interface/lib/assistant-dispatch.js +432 -125
- package/neural-interface/lib/assistant-jev.js +26 -3
- package/neural-interface/lib/assistant-persona.js +68 -11
- package/neural-interface/lib/assistant-plan-permissions.js +8 -1
- package/neural-interface/lib/assistant-route-accounts.js +14 -0
- package/neural-interface/lib/assistant-route-gate-codex.js +3 -1
- package/neural-interface/lib/assistant-route-gate.js +19 -3
- package/neural-interface/lib/assistant-router.js +373 -42
- package/neural-interface/lib/assistant-runtime.js +312 -48
- package/neural-interface/lib/assistant-user-writer.js +4 -2
- package/neural-interface/lib/augmented-path.js +317 -53
- package/neural-interface/lib/claude-accounts.js +342 -31
- package/neural-interface/lib/claude-agent-bridge.js +90 -3
- package/neural-interface/lib/claude-executable.js +6 -1
- package/neural-interface/lib/claude-model-catalog.js +3 -3
- package/neural-interface/lib/claude-panel-auth.js +221 -0
- package/neural-interface/lib/claude-profile-api-key.js +62 -0
- package/neural-interface/lib/cli-install-api.js +137 -0
- package/neural-interface/lib/cli-install.js +797 -0
- package/neural-interface/lib/cli-path.js +130 -0
- package/neural-interface/lib/cli-resolver.js +744 -0
- package/neural-interface/lib/cli-update-planner.js +143 -7
- package/neural-interface/lib/codex-account-auth.js +180 -0
- package/neural-interface/lib/codex-accounts.js +502 -0
- package/neural-interface/lib/codex-capabilities.js +2 -1
- package/neural-interface/lib/codex-compat-rpc.js +8 -2
- package/neural-interface/lib/codex-mcp-approvals.js +5 -1
- package/neural-interface/lib/codex-panel-auth.js +62 -0
- package/neural-interface/lib/codex-session-settings.js +1 -1
- package/neural-interface/lib/codex-settings-schema.js +122 -0
- package/neural-interface/lib/desktop/config.js +3 -0
- package/neural-interface/lib/exec-loop-command.js +3 -1
- package/neural-interface/lib/git-bash/bzip2.js +339 -0
- package/neural-interface/lib/git-bash/claude-settings.js +73 -0
- package/neural-interface/lib/git-bash/extract-worker.js +34 -0
- package/neural-interface/lib/git-bash/install.js +529 -0
- package/neural-interface/lib/git-bash/licenses/COPYING +360 -0
- package/neural-interface/lib/git-bash/licenses/LGPL-2.1 +511 -0
- package/neural-interface/lib/git-bash/manager.js +268 -0
- package/neural-interface/lib/git-bash/pins.js +206 -0
- package/neural-interface/lib/git-bash/pins.json +34 -0
- package/neural-interface/lib/git-bash/resolver.js +194 -0
- package/neural-interface/lib/git-read.js +34 -2
- package/neural-interface/lib/install-details.js +163 -0
- package/neural-interface/lib/mcp-installer.js +14 -2
- package/neural-interface/lib/native-loop-providers.js +22 -4
- package/neural-interface/lib/native-loop-runtime.js +9 -4
- package/neural-interface/lib/os-path.js +319 -0
- package/neural-interface/lib/provider-panel-api-key-auth.js +92 -0
- package/neural-interface/lib/provider-panel-terminal-auth.js +96 -0
- package/neural-interface/lib/pty-host/core.js +2 -1
- package/neural-interface/lib/pty-host/manager.js +27 -2
- package/neural-interface/lib/remote-policy.js +20 -11
- package/neural-interface/lib/session-indexer.js +3 -1
- package/neural-interface/lib/session-title-generator.js +6 -3
- package/neural-interface/lib/system-about.js +340 -0
- package/neural-interface/lib/system-activity.js +66 -0
- package/neural-interface/lib/terminal-env.js +11 -3
- package/neural-interface/lib/terminal-path.js +55 -0
- package/neural-interface/lib/virtual-socket.js +12 -1
- package/neural-interface/lib/whatsapp/baileys-adapter.js +165 -45
- package/neural-interface/lib/whatsapp/bridge.js +78 -34
- package/neural-interface/lib/whatsapp/commands.js +13 -14
- package/neural-interface/lib/whatsapp/config.js +134 -20
- package/neural-interface/lib/whatsapp/fake-baileys.js +99 -14
- package/neural-interface/lib/whatsapp/host-core.js +168 -35
- package/neural-interface/lib/whatsapp/inbound.js +5 -3
- package/neural-interface/lib/whatsapp/local-file.js +524 -0
- package/neural-interface/lib/whatsapp/manager.js +92 -32
- package/neural-interface/lib/whatsapp/paths.js +11 -1
- package/neural-interface/lib/whatsapp/protocol.js +65 -5
- package/neural-interface/lib/whatsapp/redact.js +63 -0
- package/neural-interface/lib/whatsapp/service.js +343 -91
- package/neural-interface/lib/whatsapp/transcription.js +463 -0
- package/neural-interface/package-lock.json +2 -2
- package/neural-interface/package.json +1 -1
- package/neural-interface/public/index.html +2 -0
- package/neural-interface/public/index2d.html +2 -0
- package/neural-interface/public/offline.html +16 -1
- package/neural-interface/public/onboarding.html +96 -2
- package/neural-interface/public/shared/account-i18n.js +7 -0
- package/neural-interface/public/shared/account-menu.js +542 -0
- package/neural-interface/public/shared/account-usage.js +79 -0
- package/neural-interface/public/shared/account-view.js +336 -0
- package/neural-interface/public/shared/agent-runtime-options.js +28 -8
- package/neural-interface/public/shared/assistant/asst-account-menu.js +298 -0
- package/neural-interface/public/shared/assistant/asst-accounts.js +182 -184
- package/neural-interface/public/shared/assistant/asst-api.js +39 -2
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +288 -32
- package/neural-interface/public/shared/assistant/asst-budget.js +259 -303
- package/neural-interface/public/shared/assistant/asst-mascot.js +126 -7
- package/neural-interface/public/shared/assistant/asst-menu.js +219 -80
- package/neural-interface/public/shared/assistant/asst-model-chip.js +135 -0
- package/neural-interface/public/shared/assistant/asst-panel.js +87 -16
- package/neural-interface/public/shared/assistant/asst-render.js +38 -7
- package/neural-interface/public/shared/assistant/asst-route.js +72 -14
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +45 -6
- package/neural-interface/public/shared/assistant/asst-slash.js +1 -1
- package/neural-interface/public/shared/assistant/asst-state.js +1 -1
- package/neural-interface/public/shared/assistant/asst-styles.js +160 -29
- package/neural-interface/public/shared/cdx/cdx-commands.js +4 -1
- package/neural-interface/public/shared/cdx/cdx-features.js +22 -14
- package/neural-interface/public/shared/cdx/cdx-icons.js +2 -0
- package/neural-interface/public/shared/cdx/cdx-panel.js +49 -108
- package/neural-interface/public/shared/cdx/cdx-settings.js +231 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +172 -111
- package/neural-interface/public/shared/cdx/cdx-tabs.js +264 -664
- package/neural-interface/public/shared/cdx/cdx-ws.js +2 -0
- package/neural-interface/public/shared/claude-account-view.js +3 -0
- package/neural-interface/public/shared/cli-install-view.js +392 -0
- package/neural-interface/public/shared/cli-status.js +282 -22
- package/neural-interface/public/shared/cp/cp-events.js +2 -1
- package/neural-interface/public/shared/cp/cp-styles.js +46 -0
- package/neural-interface/public/shared/desktop-bridge.js +39 -0
- package/neural-interface/public/shared/desktop-titlebar-page.js +59 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +122 -0
- package/neural-interface/public/shared/desktop-titlebar.js +153 -0
- package/neural-interface/public/shared/desktop-update-view.js +462 -0
- package/neural-interface/public/shared/editor/editor-panel.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +1 -1
- package/neural-interface/public/shared/git-bash-view.js +145 -0
- package/neural-interface/public/shared/html-shell.js +1 -0
- package/neural-interface/public/shared/i18n.js +19 -1
- package/neural-interface/public/shared/install-details-view.js +86 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +4 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +36 -1
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +17 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +169 -2
- package/neural-interface/public/shared/pwa-install-page.js +3 -1
- package/neural-interface/public/shared/settings/settings-about-pane.js +271 -0
- package/neural-interface/public/shared/settings/settings-about.js +254 -0
- package/neural-interface/public/shared/settings/settings-ia.js +15 -0
- package/neural-interface/public/shared/showcase-privacy.js +490 -0
- package/neural-interface/public/shared/storage.js +30 -3
- package/neural-interface/public/shared/styles.css +281 -32
- package/neural-interface/public/shared/synabun-mascot.js +48 -2
- package/neural-interface/public/shared/ui-claude-panel.js +262 -217
- package/neural-interface/public/shared/ui-cli-banner.js +355 -0
- package/neural-interface/public/shared/ui-explorer.js +2 -0
- package/neural-interface/public/shared/ui-git-bash.js +124 -0
- package/neural-interface/public/shared/ui-judgments.js +10 -1
- package/neural-interface/public/shared/ui-loading.js +30 -6
- package/neural-interface/public/shared/ui-menubar.js +13 -3
- package/neural-interface/public/shared/ui-navbar.js +3 -1
- package/neural-interface/public/shared/ui-provider-auth.js +104 -0
- package/neural-interface/public/shared/ui-provider-terminal-auth.js +223 -0
- package/neural-interface/public/shared/ui-sessions.js +9 -3
- package/neural-interface/public/shared/ui-settings.js +55 -3
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -1
- package/neural-interface/public/shared/ui-sync.js +8 -0
- package/neural-interface/public/shared/ui-terminal.js +17 -0
- package/neural-interface/public/shared/ui-tool-updates.js +5 -0
- package/neural-interface/public/shared/ui-update.js +598 -7
- package/neural-interface/public/shared/ui-whatsapp.js +21 -6
- package/neural-interface/public/shared/whatsapp/wa-view.js +51 -8
- package/neural-interface/public/shared/worker-end.js +2 -0
- package/neural-interface/public/sw.js +1 -1
- package/neural-interface/public/variant/2d/main.js +10 -1
- package/neural-interface/public/variant/3d/main.js +10 -1
- package/neural-interface/scripts/rebuild-pty.js +14 -2
- package/neural-interface/server.js +1384 -679
- package/npm-shrinkwrap.json +4424 -0
- package/package.json +57 -2
- package/postinstall.js +61 -47
- package/preuninstall.js +8 -35
- package/setup.js +80 -47
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/schedule.md +1 -1
- package/updater.cmd +2 -2
- package/updater.mjs +97 -3
|
@@ -2,9 +2,22 @@
|
|
|
2
2
|
// SynaBun Neural Interface — Update Alert
|
|
3
3
|
// Checks for npm updates on page load, shows toolbar badge,
|
|
4
4
|
// and provides a 3-step modal wizard with blocking backup gate.
|
|
5
|
+
//
|
|
6
|
+
// Inside the SynaBun desktop app the updater is the app's own: it downloads and
|
|
7
|
+
// installs by itself, and the same badge, drawer item and dialog, plus a notice
|
|
8
|
+
// (the countdown before the restart, "updated", a failed install), show what it
|
|
9
|
+
// reports through window.synabunDesktop (desktop-bridge.js,
|
|
10
|
+
// desktop-update-view.js). The npm/GitHub check above is never asked.
|
|
5
11
|
// ═══════════════════════════════════════════
|
|
6
12
|
|
|
7
13
|
import { showUpdateToast } from './ui-tool-updates.js';
|
|
14
|
+
import { emit } from './state.js';
|
|
15
|
+
import { getLocale, t } from './i18n.js';
|
|
16
|
+
import { desktopBridge } from './desktop-bridge.js';
|
|
17
|
+
import {
|
|
18
|
+
desktopCheckOutcome, desktopUpdateMeta, desktopUpdateNotice, desktopUpdateTooltip, desktopUpdateView, fillDesktopCopy, readDesktopUpdateState,
|
|
19
|
+
versionDataFromDesktop,
|
|
20
|
+
} from './desktop-update-view.js';
|
|
8
21
|
|
|
9
22
|
const $ = (id) => document.getElementById(id);
|
|
10
23
|
const SYNABUN_GITHUB_URL = 'https://github.com/danilokhury/Synabun';
|
|
@@ -12,9 +25,15 @@ const SYNABUN_GITHUB_URL = 'https://github.com/danilokhury/Synabun';
|
|
|
12
25
|
let _versionData = null;
|
|
13
26
|
let _btnWired = false;
|
|
14
27
|
let _pollWired = false;
|
|
28
|
+
// Inside the desktop app: the updater's last report as it came, and the update
|
|
29
|
+
// dialog while it is open ({ overlay, update, close }).
|
|
30
|
+
let _desktopState = null;
|
|
31
|
+
let _desktopDialog = null;
|
|
15
32
|
|
|
16
|
-
|
|
33
|
+
/** Where an update comes from, as the badge, the toast and the menu name it. */
|
|
34
|
+
export function synabunUpdateSourceLabel(data) {
|
|
17
35
|
const src = data?.source;
|
|
36
|
+
if (data?.desktop || src === 'app') return desktopT('update.source');
|
|
18
37
|
if (src === 'both') return 'npm + GitHub';
|
|
19
38
|
if (src === 'github') return 'GitHub';
|
|
20
39
|
if (src === 'npm') return 'npm';
|
|
@@ -34,8 +53,10 @@ function applyTopRightButton(data) {
|
|
|
34
53
|
else badge.removeAttribute('aria-label');
|
|
35
54
|
}
|
|
36
55
|
if (has) {
|
|
37
|
-
const srcLabel =
|
|
38
|
-
btn.dataset.tooltip =
|
|
56
|
+
const srcLabel = synabunUpdateSourceLabel(data);
|
|
57
|
+
btn.dataset.tooltip = data.desktop
|
|
58
|
+
? desktopUpdateTooltip(_desktopState, { t: desktopT })
|
|
59
|
+
: `SynaBun update available: v${data.current} → v${data.latest} (${srcLabel})`;
|
|
39
60
|
}
|
|
40
61
|
if (!_btnWired) {
|
|
41
62
|
_btnWired = true;
|
|
@@ -53,7 +74,7 @@ function applyTopRightButton(data) {
|
|
|
53
74
|
|
|
54
75
|
function maybeShowUpdateAlert(data, { once = true } = {}) {
|
|
55
76
|
if (!data?.updateAvailable) return;
|
|
56
|
-
const key = `synabun-update-alert:${data.current}->${data.latest}:${
|
|
77
|
+
const key = `synabun-update-alert:${data.current}->${data.latest}:${synabunUpdateSourceLabel(data)}`;
|
|
57
78
|
try {
|
|
58
79
|
if (once && sessionStorage.getItem(key) === '1') return;
|
|
59
80
|
sessionStorage.setItem(key, '1');
|
|
@@ -61,7 +82,7 @@ function maybeShowUpdateAlert(data, { once = true } = {}) {
|
|
|
61
82
|
|
|
62
83
|
setTimeout(() => {
|
|
63
84
|
showUpdateToast({
|
|
64
|
-
updates: [{ label: `SynaBun v${data.current} → v${data.latest}`, source:
|
|
85
|
+
updates: [{ label: `SynaBun v${data.current} → v${data.latest}`, source: synabunUpdateSourceLabel(data) }],
|
|
65
86
|
errors: [],
|
|
66
87
|
onClick: () => openSynabunUpdateModal(),
|
|
67
88
|
});
|
|
@@ -69,6 +90,8 @@ function maybeShowUpdateAlert(data, { once = true } = {}) {
|
|
|
69
90
|
}
|
|
70
91
|
|
|
71
92
|
export async function initUpdate() {
|
|
93
|
+
const desktop = desktopBridge();
|
|
94
|
+
if (desktop) return initDesktopUpdate(desktop);
|
|
72
95
|
try {
|
|
73
96
|
const res = await fetch('/api/system/version');
|
|
74
97
|
if (!res.ok) return;
|
|
@@ -89,6 +112,17 @@ export async function initUpdate() {
|
|
|
89
112
|
// re-applies the top-right button + badge state. Returns the latest
|
|
90
113
|
// _synabunUpdateCache shape so callers can compose toasts.
|
|
91
114
|
export async function forceCheckSynabunUpdate({ alert = false } = {}) {
|
|
115
|
+
// Inside the desktop app the app checks its own release feed; an error is
|
|
116
|
+
// null here, as a failed request is, so callers list it under the errors.
|
|
117
|
+
const desktop = desktopBridge();
|
|
118
|
+
if (desktop) {
|
|
119
|
+
try {
|
|
120
|
+
const data = applyDesktopState(await desktop.updates.check(), { alert });
|
|
121
|
+
return data.status === 'error' ? null : data;
|
|
122
|
+
} catch {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
92
126
|
try {
|
|
93
127
|
const res = await fetch('/api/system/version?force=1');
|
|
94
128
|
if (!res.ok) return null;
|
|
@@ -101,6 +135,21 @@ export async function forceCheckSynabunUpdate({ alert = false } = {}) {
|
|
|
101
135
|
}
|
|
102
136
|
}
|
|
103
137
|
|
|
138
|
+
/**
|
|
139
|
+
* The update state for a surface that refreshes often (Notifications): the
|
|
140
|
+
* desktop app's last report, which costs no check, or a fresh check of the
|
|
141
|
+
* server for an npm or Git install, as before.
|
|
142
|
+
*/
|
|
143
|
+
export async function syncSynabunUpdateData() {
|
|
144
|
+
const desktop = desktopBridge();
|
|
145
|
+
if (!desktop) return forceCheckSynabunUpdate();
|
|
146
|
+
try {
|
|
147
|
+
return applyDesktopState(await desktop.updates.state(), { alert: false });
|
|
148
|
+
} catch {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
104
153
|
export function openSynabunUpdateModal() {
|
|
105
154
|
if (!_versionData?.updateAvailable) return;
|
|
106
155
|
openUpdateModal();
|
|
@@ -112,10 +161,550 @@ export function getSynabunUpdateData() {
|
|
|
112
161
|
return _versionData;
|
|
113
162
|
}
|
|
114
163
|
|
|
164
|
+
/** The line under the SynaBun item of Notifications > Updates. */
|
|
165
|
+
export function synabunUpdateMeta(data) {
|
|
166
|
+
if (data?.desktop) return desktopUpdateMeta(_desktopState, { t: desktopT });
|
|
167
|
+
return data?.source ? `Source: ${data.source}` : '';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── Desktop app ──
|
|
171
|
+
//
|
|
172
|
+
// window.synabunDesktop.updates is the app's updater (packaging/desktop). It
|
|
173
|
+
// checks about 30 s after the app opens and every 6 h, downloads what it finds,
|
|
174
|
+
// and installs it by itself once SynaBun is idle, after a one-minute countdown
|
|
175
|
+
// (the notice below, with Later); it reports each change here, and the menu bar
|
|
176
|
+
// and the tray of the app say so through onOpen. The page shows what it reports
|
|
177
|
+
// and passes on Install now and Later, never more.
|
|
178
|
+
|
|
179
|
+
/** A `desktop.*` string in the page's language; DESKTOP_COPY when the dictionary lacks it. */
|
|
180
|
+
function desktopT(key, params) {
|
|
181
|
+
const full = `desktop.${key}`;
|
|
182
|
+
const value = t(full, params);
|
|
183
|
+
return value === full ? fillDesktopCopy(key, params) : value;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** A time of day (an ISO time), written the way the page's language writes it. */
|
|
187
|
+
function desktopTime(iso) {
|
|
188
|
+
try {
|
|
189
|
+
return new Date(iso).toLocaleTimeString(getLocale(), { hour: '2-digit', minute: '2-digit' });
|
|
190
|
+
} catch {
|
|
191
|
+
return new Date(iso).toISOString().slice(11, 16);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** A number with `digits` decimals, written the way the page's language writes it. */
|
|
196
|
+
function desktopNumber(value, digits) {
|
|
197
|
+
try {
|
|
198
|
+
return value.toLocaleString(getLocale(), { minimumFractionDigits: digits, maximumFractionDigits: digits });
|
|
199
|
+
} catch {
|
|
200
|
+
return value.toFixed(digits);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Take one report of the app's updater: the badge, the drawer, an open dialog and the notice follow it. */
|
|
205
|
+
function applyDesktopState(state, { alert = true } = {}) {
|
|
206
|
+
_desktopState = state && typeof state === 'object' ? state : null;
|
|
207
|
+
_versionData = versionDataFromDesktop(_desktopState);
|
|
208
|
+
applyTopRightButton(_versionData);
|
|
209
|
+
// An offer that waits for the user (macOS: a copy to move to Applications, or one in a folder SynaBun
|
|
210
|
+
// cannot write): one toast per version and session. Every other update downloads and installs by
|
|
211
|
+
// itself, and the notice speaks when there is something to say.
|
|
212
|
+
if (alert && _versionData.updateAvailable && _versionData.status === 'available' && _versionData.install !== 'restart') maybeShowUpdateAlert(_versionData);
|
|
213
|
+
emit('synabun-update:changed', _versionData);
|
|
214
|
+
_desktopDialog?.update(_desktopState);
|
|
215
|
+
renderDesktopNotice();
|
|
216
|
+
return _versionData;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// ── The notice (bottom right): the countdown before the restart, "updated", a failed install ──
|
|
220
|
+
|
|
221
|
+
let _notice = null; // { el, key, kind, timer, hold }
|
|
222
|
+
|
|
223
|
+
const NOTICE_ICONS = { up: '↑', ok: '✓', err: '!' };
|
|
224
|
+
|
|
225
|
+
function hideDesktopNotice() {
|
|
226
|
+
const current = _notice;
|
|
227
|
+
if (!current) return;
|
|
228
|
+
_notice = null;
|
|
229
|
+
clearInterval(current.timer);
|
|
230
|
+
clearTimeout(current.timer);
|
|
231
|
+
current.el.classList.remove('show');
|
|
232
|
+
setTimeout(() => current.el.remove(), 250);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** What the notice says now: drawn from the last report, again every second while it counts down. */
|
|
236
|
+
function renderDesktopNotice() {
|
|
237
|
+
const notice = desktopBridge() ? desktopUpdateNotice(_desktopState, { t: desktopT }) : null;
|
|
238
|
+
if (!notice) {
|
|
239
|
+
// A notice that says something of its own for a while (Later's answer, "updated") stays until it is done.
|
|
240
|
+
if (_notice && !_notice.hold) hideDesktopNotice();
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (_notice && _notice.key === notice.key) {
|
|
244
|
+
const line = _notice.el.querySelector('#desktop-update-notice-line');
|
|
245
|
+
if (line && line.textContent !== notice.line) line.textContent = notice.line;
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (notice.kind === 'failed') {
|
|
249
|
+
// A failed update is said once per session; the badge and the dialog keep it.
|
|
250
|
+
const mark = `synabun-update-notice:${notice.key}`;
|
|
251
|
+
try {
|
|
252
|
+
if (sessionStorage.getItem(mark) === '1') { if (_notice && !_notice.hold) hideDesktopNotice(); return; }
|
|
253
|
+
sessionStorage.setItem(mark, '1');
|
|
254
|
+
} catch {}
|
|
255
|
+
}
|
|
256
|
+
showDesktopNotice(notice);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function showDesktopNotice(notice) {
|
|
260
|
+
hideDesktopNotice();
|
|
261
|
+
document.querySelectorAll('.update-notice').forEach(el => el.remove());
|
|
262
|
+
const el = document.createElement('div');
|
|
263
|
+
el.className = 'update-check-toast update-notice';
|
|
264
|
+
el.id = 'desktop-update-notice';
|
|
265
|
+
el.dataset.kind = notice.kind;
|
|
266
|
+
el.setAttribute('role', 'status');
|
|
267
|
+
const link = notice.link ? `<a class="update-notice-link" id="desktop-update-notice-link" href="${esc(notice.link.href)}" target="_blank" rel="noopener noreferrer">${esc(notice.link.label)}</a>` : '';
|
|
268
|
+
const buttons = notice.actions.map(item => `<button class="action-btn action-btn--ghost update-notice-btn" type="button" data-notice-action="${esc(item.id)}" id="desktop-update-notice-${esc(item.id)}">${esc(item.label)}</button>`).join('');
|
|
269
|
+
el.innerHTML = `
|
|
270
|
+
<div class="utc-icon utc-icon--${notice.tone}" aria-hidden="true">${NOTICE_ICONS[notice.tone] || '↑'}</div>
|
|
271
|
+
<div class="utc-body">
|
|
272
|
+
<div class="utc-title" id="desktop-update-notice-title">${esc(notice.title)}</div>
|
|
273
|
+
<div class="utc-sub" id="desktop-update-notice-line"${notice.ticking ? ' aria-live="off"' : ''}>${esc(notice.line)}</div>
|
|
274
|
+
<div class="update-notice-actions">${link}${buttons}</div>
|
|
275
|
+
</div>`;
|
|
276
|
+
el.querySelectorAll('[data-notice-action]').forEach((btn) => {
|
|
277
|
+
btn.addEventListener('click', (event) => { event.stopPropagation(); noticeAction(btn.dataset.noticeAction); });
|
|
278
|
+
});
|
|
279
|
+
document.body.appendChild(el);
|
|
280
|
+
requestAnimationFrame(() => el.classList.add('show'));
|
|
281
|
+
const current = { el, key: notice.key, kind: notice.kind, timer: null, hold: false };
|
|
282
|
+
_notice = current;
|
|
283
|
+
if (notice.ticking) {
|
|
284
|
+
current.timer = setInterval(() => { if (_notice === current) renderDesktopNotice(); }, 1000);
|
|
285
|
+
} else if (notice.kind === 'updated') {
|
|
286
|
+
// Said once: the app forgets it now, and the notice stays a little while on its own.
|
|
287
|
+
current.hold = true;
|
|
288
|
+
current.timer = setTimeout(() => { if (_notice === current) hideDesktopNotice(); }, 12000);
|
|
289
|
+
try { desktopBridge()?.updates.acknowledge?.()?.catch?.(() => {}); } catch {}
|
|
290
|
+
} else {
|
|
291
|
+
current.timer = setTimeout(() => { if (_notice === current) hideDesktopNotice(); }, 15000);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function noticeAction(id) {
|
|
296
|
+
const desktop = desktopBridge();
|
|
297
|
+
const current = _notice;
|
|
298
|
+
if (!desktop || !current) return;
|
|
299
|
+
if (id === 'dismiss') { hideDesktopNotice(); return; }
|
|
300
|
+
if (id === 'details') { hideDesktopNotice(); openDesktopUpdateModal(); return; }
|
|
301
|
+
if (id !== 'later') return;
|
|
302
|
+
// Later: Later's answer takes the notice's place for a moment.
|
|
303
|
+
clearInterval(current.timer);
|
|
304
|
+
current.hold = true;
|
|
305
|
+
current.el.querySelectorAll('[data-notice-action]').forEach(btn => { btn.disabled = true; });
|
|
306
|
+
let answer = null;
|
|
307
|
+
try { answer = await desktop.updates.later(); } catch {}
|
|
308
|
+
if (_notice !== current) return;
|
|
309
|
+
const line = current.el.querySelector('#desktop-update-notice-line');
|
|
310
|
+
if (line) line.textContent = desktopT('notice.postponed');
|
|
311
|
+
current.el.querySelector('.update-notice-actions')?.remove();
|
|
312
|
+
current.timer = setTimeout(() => { if (_notice === current) hideDesktopNotice(); }, 5000);
|
|
313
|
+
if (answer) applyDesktopState(answer, { alert: false });
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function initDesktopUpdate(desktop) {
|
|
317
|
+
if (!_pollWired) {
|
|
318
|
+
_pollWired = true;
|
|
319
|
+
// No polling of our own: the app checks on its schedule and says what changed.
|
|
320
|
+
try { desktop.updates.onChange((state) => applyDesktopState(state)); } catch {}
|
|
321
|
+
try { desktop.updates.onOpen?.((state) => handleDesktopMenuCheck(state)); } catch {}
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
applyDesktopState(await desktop.updates.state());
|
|
325
|
+
} catch { /* silent — no update UI if the app does not answer */ }
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Check for Updates… in the app's own menu or tray finished: the dialog for an offer, a short toast otherwise. */
|
|
329
|
+
function handleDesktopMenuCheck(state) {
|
|
330
|
+
applyDesktopState(state, { alert: false });
|
|
331
|
+
const outcome = desktopCheckOutcome(state);
|
|
332
|
+
if (outcome === 'offer') openUpdateModal();
|
|
333
|
+
else if (outcome === 'error') showUpdateToast({ updates: [], errors: [readDesktopUpdateState(state).error || desktopT('update.errorFallback')] });
|
|
334
|
+
else if (outcome === 'up-to-date') showUpdateToast({ updates: [], errors: [] });
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function prefersLessMotion() {
|
|
338
|
+
try { return window.matchMedia?.('(prefers-reduced-motion: reduce)').matches === true; }
|
|
339
|
+
catch { return false; }
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const DESKTOP_ARROW = '<svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>';
|
|
343
|
+
const DESKTOP_BACKUP_ICON = '<svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="vertical-align:-2px;margin-right:6px"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>';
|
|
344
|
+
|
|
345
|
+
/** One page of the desktop dialog, from desktopUpdateView(). Everything the app or the feed said goes in as text. */
|
|
346
|
+
function renderDesktopStep(view) {
|
|
347
|
+
const pill = view.testFeed ? ` <span class="update-channel-pill">${esc(desktopT('update.testFeed'))}</span>` : '';
|
|
348
|
+
const working = view.step === 'installing' ? '<span class="update-spinner"></span>' : '';
|
|
349
|
+
const diff = view.latest ? `
|
|
350
|
+
<div class="update-version-diff">
|
|
351
|
+
<span class="update-ver update-ver--old">v${esc(view.current)}</span>
|
|
352
|
+
${DESKTOP_ARROW}
|
|
353
|
+
<span class="update-ver update-ver--new">v${esc(view.latest)}</span>
|
|
354
|
+
</div>` : '';
|
|
355
|
+
const body = view.body.map(line => `<p class="update-desktop-line" style="font-size:12px;color:var(--t-muted);margin:12px 0 6px">${esc(line)}</p>`).join('');
|
|
356
|
+
const notes = view.notes !== null ? `
|
|
357
|
+
<div class="update-notes-label">${esc(view.notesLabel)}</div>
|
|
358
|
+
<div class="update-notes" id="desktop-update-notes" tabindex="0">${esc(view.notes)}</div>` : '';
|
|
359
|
+
const unknown = view.progress?.percent === null;
|
|
360
|
+
const progress = view.progress ? `
|
|
361
|
+
<div class="update-progress" id="desktop-update-progress" role="progressbar" aria-label="${esc(view.title)}" aria-valuemin="0" aria-valuemax="100"${unknown ? '' : ` aria-valuenow="${view.progress.percent}"`}>
|
|
362
|
+
<div class="update-progress-bar${unknown ? ' update-progress-bar--unknown' : ''}" id="desktop-update-progress-bar" style="width:${unknown ? 100 : view.progress.percent}%"></div>
|
|
363
|
+
</div>
|
|
364
|
+
<div class="update-progress-text" id="desktop-update-progress-text">${esc(view.progress.text)}</div>` : '';
|
|
365
|
+
const file = view.file ? `<p class="update-desktop-file" id="desktop-update-file" style="font-size:11px;color:var(--t-muted);opacity:.75;margin:0 0 6px;overflow-wrap:anywhere">${esc(view.file)}</p>` : '';
|
|
366
|
+
const link = view.link ? `<p class="update-desktop-link" style="margin:2px 0 6px"><a class="update-notice-link" id="desktop-update-link" href="${esc(view.link.href)}" target="_blank" rel="noopener noreferrer">${esc(view.link.label)}</a></p>` : '';
|
|
367
|
+
const backup = view.backup ? `
|
|
368
|
+
<button class="action-btn action-btn--ghost" type="button" data-desktop-action="${view.backup.id}" id="desktop-update-backup" style="width:100%;margin:10px 0 0">${DESKTOP_BACKUP_ICON}${esc(view.backup.label)}</button>
|
|
369
|
+
<div class="update-backup-status" id="desktop-update-backup-status" style="display:none">
|
|
370
|
+
<span class="update-backup-icon" id="desktop-update-backup-icon"></span>
|
|
371
|
+
<span id="desktop-update-backup-text"></span>
|
|
372
|
+
</div>` : '';
|
|
373
|
+
const actions = view.actions.length ? `
|
|
374
|
+
<div class="tag-delete-modal-actions" style="margin-top:14px">
|
|
375
|
+
${view.actions.map(item => `<button class="action-btn action-btn--${item.kind}" type="button" data-desktop-action="${item.id}" id="desktop-update-${item.id}">${esc(item.label)}</button>`).join('')}
|
|
376
|
+
</div>` : '';
|
|
377
|
+
return `
|
|
378
|
+
<h3 class="update-modal-title" id="desktop-update-title">${working}${esc(view.title)}${pill}</h3>
|
|
379
|
+
${diff}
|
|
380
|
+
${body}
|
|
381
|
+
${notes}
|
|
382
|
+
${progress}
|
|
383
|
+
${file}
|
|
384
|
+
${link}
|
|
385
|
+
${backup}
|
|
386
|
+
${actions}`;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* The update dialog inside the desktop app: the same shell and liquid step
|
|
391
|
+
* transition as the npm wizard below, one page per desktopUpdateView() step,
|
|
392
|
+
* kept current by every report while it is open. A progress report moves the
|
|
393
|
+
* bar in place; a new step slides in.
|
|
394
|
+
*/
|
|
395
|
+
function openDesktopUpdateModal() {
|
|
396
|
+
const desktop = desktopBridge();
|
|
397
|
+
if (!desktop) return;
|
|
398
|
+
// One dialog: a second request (the badge, the menu, the tray) leaves the open one as it is.
|
|
399
|
+
if (_desktopDialog) return;
|
|
400
|
+
const viewOf = (state) => desktopUpdateView(state, { t: desktopT, number: desktopNumber, time: desktopTime });
|
|
401
|
+
const first = viewOf(_desktopState);
|
|
402
|
+
if (first.step === 'none') return;
|
|
403
|
+
|
|
404
|
+
let view = null;
|
|
405
|
+
let isAnimating = false;
|
|
406
|
+
let pending = null;
|
|
407
|
+
let working = false;
|
|
408
|
+
// A page whose sentence counts down (the restart's seconds) is drawn again every second.
|
|
409
|
+
let tick = null;
|
|
410
|
+
|
|
411
|
+
const overlay = document.createElement('div');
|
|
412
|
+
overlay.className = 'tag-delete-overlay';
|
|
413
|
+
overlay.id = 'desktop-update-overlay';
|
|
414
|
+
overlay.style.zIndex = '300100';
|
|
415
|
+
|
|
416
|
+
const modal = document.createElement('div');
|
|
417
|
+
modal.className = 'tag-delete-modal update-modal update-modal--desktop';
|
|
418
|
+
modal.id = 'desktop-update-modal';
|
|
419
|
+
modal.style.maxWidth = '460px';
|
|
420
|
+
modal.style.textAlign = 'left';
|
|
421
|
+
modal.setAttribute('role', 'dialog');
|
|
422
|
+
modal.setAttribute('aria-modal', 'true');
|
|
423
|
+
modal.setAttribute('aria-labelledby', 'desktop-update-title');
|
|
424
|
+
|
|
425
|
+
const dotsRow = document.createElement('div');
|
|
426
|
+
dotsRow.className = 'update-steps';
|
|
427
|
+
dotsRow.innerHTML = [1, 2, 3].map(() => `<span class="update-step-dot"></span>`).join('');
|
|
428
|
+
|
|
429
|
+
const stepWrap = document.createElement('div');
|
|
430
|
+
stepWrap.className = 'update-step-content';
|
|
431
|
+
|
|
432
|
+
modal.appendChild(dotsRow);
|
|
433
|
+
modal.appendChild(stepWrap);
|
|
434
|
+
overlay.appendChild(modal);
|
|
435
|
+
|
|
436
|
+
// The page that stays: during a transition the one leaving is still there for a moment.
|
|
437
|
+
const liveStep = () => [...stepWrap.children].find(el => el.classList.contains('update-step') && !el.classList.contains('is-leaving')) || stepWrap;
|
|
438
|
+
|
|
439
|
+
function syncDots(dot) {
|
|
440
|
+
dotsRow.style.visibility = dot ? '' : 'hidden';
|
|
441
|
+
dotsRow.querySelectorAll('.update-step-dot').forEach((el, i) => {
|
|
442
|
+
el.classList.toggle('active', i + 1 === dot);
|
|
443
|
+
el.classList.toggle('done', i + 1 < dot);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function buildStepNode(next) {
|
|
448
|
+
const node = document.createElement('div');
|
|
449
|
+
node.className = 'update-step';
|
|
450
|
+
node.dataset.step = next.step;
|
|
451
|
+
node.innerHTML = renderDesktopStep(next);
|
|
452
|
+
return node;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function bindStepEvents(scope) {
|
|
456
|
+
scope.querySelectorAll('[data-desktop-action]').forEach((btn) => {
|
|
457
|
+
btn.addEventListener('click', () => act(btn.dataset.desktopAction));
|
|
458
|
+
});
|
|
459
|
+
// The step's own answer takes the focus, so Enter does what the dialog offers.
|
|
460
|
+
const primary = scope.querySelector('.action-btn--primary');
|
|
461
|
+
if (primary) { try { primary.focus({ preventScroll: true }); } catch {} }
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// What changes inside one step: the bar and its words, a sentence, the file line.
|
|
465
|
+
function patchStep(next) {
|
|
466
|
+
const scope = liveStep();
|
|
467
|
+
if (next.progress) {
|
|
468
|
+
const meter = scope.querySelector('#desktop-update-progress');
|
|
469
|
+
const bar = scope.querySelector('#desktop-update-progress-bar');
|
|
470
|
+
const words = scope.querySelector('#desktop-update-progress-text');
|
|
471
|
+
const unknown = next.progress.percent === null;
|
|
472
|
+
if (bar) {
|
|
473
|
+
bar.style.width = `${unknown ? 100 : next.progress.percent}%`;
|
|
474
|
+
bar.classList.toggle('update-progress-bar--unknown', unknown);
|
|
475
|
+
}
|
|
476
|
+
if (meter) {
|
|
477
|
+
if (unknown) meter.removeAttribute('aria-valuenow');
|
|
478
|
+
else meter.setAttribute('aria-valuenow', String(next.progress.percent));
|
|
479
|
+
}
|
|
480
|
+
if (words && words.textContent !== next.progress.text) words.textContent = next.progress.text;
|
|
481
|
+
}
|
|
482
|
+
const lines = scope.querySelectorAll('.update-desktop-line');
|
|
483
|
+
next.body.forEach((line, i) => { if (lines[i] && lines[i].textContent !== line) lines[i].textContent = line; });
|
|
484
|
+
const file = scope.querySelector('#desktop-update-file');
|
|
485
|
+
if (file && next.file && file.textContent !== next.file) file.textContent = next.file;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// The same liquid transition as setStep() in the npm wizard below.
|
|
489
|
+
function setStep(next) {
|
|
490
|
+
if (isAnimating) { pending = next; return; }
|
|
491
|
+
view = next;
|
|
492
|
+
syncDots(next.dot);
|
|
493
|
+
|
|
494
|
+
const outgoing = stepWrap.firstElementChild;
|
|
495
|
+
const incoming = buildStepNode(next);
|
|
496
|
+
|
|
497
|
+
if (!outgoing) {
|
|
498
|
+
stepWrap.appendChild(incoming);
|
|
499
|
+
bindStepEvents(incoming);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
if (prefersLessMotion()) {
|
|
504
|
+
stepWrap.replaceChild(incoming, outgoing);
|
|
505
|
+
bindStepEvents(incoming);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
isAnimating = true;
|
|
510
|
+
const oldH = outgoing.offsetHeight;
|
|
511
|
+
const pinW = modal.offsetWidth;
|
|
512
|
+
stepWrap.style.height = oldH + 'px';
|
|
513
|
+
modal.style.width = pinW + 'px';
|
|
514
|
+
|
|
515
|
+
incoming.style.position = 'absolute';
|
|
516
|
+
incoming.style.left = '0';
|
|
517
|
+
incoming.style.right = '0';
|
|
518
|
+
incoming.style.top = '0';
|
|
519
|
+
incoming.style.visibility = 'hidden';
|
|
520
|
+
stepWrap.appendChild(incoming);
|
|
521
|
+
bindStepEvents(incoming);
|
|
522
|
+
const newH = incoming.offsetHeight;
|
|
523
|
+
|
|
524
|
+
outgoing.style.position = 'absolute';
|
|
525
|
+
outgoing.style.left = '0';
|
|
526
|
+
outgoing.style.right = '0';
|
|
527
|
+
outgoing.style.top = '0';
|
|
528
|
+
outgoing.classList.add('is-leaving');
|
|
529
|
+
|
|
530
|
+
incoming.style.visibility = '';
|
|
531
|
+
incoming.classList.add('is-entering');
|
|
532
|
+
|
|
533
|
+
requestAnimationFrame(() => {
|
|
534
|
+
stepWrap.style.height = newH + 'px';
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
let settled = false;
|
|
538
|
+
const finalize = () => {
|
|
539
|
+
if (settled) return;
|
|
540
|
+
settled = true;
|
|
541
|
+
incoming.removeEventListener('animationend', onIncomingEnd);
|
|
542
|
+
if (outgoing.parentNode === stepWrap) stepWrap.removeChild(outgoing);
|
|
543
|
+
incoming.classList.remove('is-entering');
|
|
544
|
+
incoming.style.position = '';
|
|
545
|
+
incoming.style.left = '';
|
|
546
|
+
incoming.style.right = '';
|
|
547
|
+
incoming.style.top = '';
|
|
548
|
+
incoming.style.visibility = '';
|
|
549
|
+
stepWrap.style.height = '';
|
|
550
|
+
modal.style.width = '';
|
|
551
|
+
isAnimating = false;
|
|
552
|
+
// A report that came while the step slid in: a new step slides in next, the same one is patched.
|
|
553
|
+
const later = pending;
|
|
554
|
+
pending = null;
|
|
555
|
+
if (later && later.step !== view.step) setStep(later);
|
|
556
|
+
else if (later) { view = later; patchStep(later); }
|
|
557
|
+
};
|
|
558
|
+
const onIncomingEnd = (ev) => {
|
|
559
|
+
if (ev.target === incoming && ev.animationName === 'synabunStepIn') finalize();
|
|
560
|
+
};
|
|
561
|
+
incoming.addEventListener('animationend', onIncomingEnd);
|
|
562
|
+
setTimeout(finalize, 480);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const shapeOf = (page) => `${page.step}|${page.dot}|${page.body.length}|${page.actions.map(item => item.id).join(',')}|${page.link ? 1 : 0}`;
|
|
566
|
+
|
|
567
|
+
function update(state) {
|
|
568
|
+
// A check this dialog asked for (Try again): its page stays until the answer.
|
|
569
|
+
if (readDesktopUpdateState(state).status === 'checking') return;
|
|
570
|
+
const next = viewOf(state);
|
|
571
|
+
if (next.step === 'none') { close(); return; }
|
|
572
|
+
keepTicking(next.ticking);
|
|
573
|
+
if (isAnimating) { pending = next; return; }
|
|
574
|
+
// The two pages of the manual install (before and after the download) are one step with two faces;
|
|
575
|
+
// a ready update draws again when what it waits for, or its buttons, change.
|
|
576
|
+
if (!view || shapeOf(next) !== shapeOf(view)) { setStep(next); return; }
|
|
577
|
+
view = next;
|
|
578
|
+
patchStep(next);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function keepTicking(on) {
|
|
582
|
+
if (on && !tick) tick = setInterval(() => update(_desktopState), 1000);
|
|
583
|
+
else if (!on && tick) { clearInterval(tick); tick = null; }
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function setBusy(busy) {
|
|
587
|
+
working = busy;
|
|
588
|
+
liveStep().querySelectorAll('[data-desktop-action]').forEach((btn) => { btn.disabled = busy; });
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
async function act(id) {
|
|
592
|
+
if (working) return;
|
|
593
|
+
// Later on a ready update postpones its restart; everywhere else it only closes the dialog.
|
|
594
|
+
if (id === 'later' && view?.step === 'downloaded') {
|
|
595
|
+
close();
|
|
596
|
+
try { const answer = await desktop.updates.later?.(); if (answer) applyDesktopState(answer, { alert: false }); } catch {}
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (id === 'later' || id === 'hide' || id === 'close') { close(); return; }
|
|
600
|
+
if (id === 'backup') { downloadBackup(); return; }
|
|
601
|
+
// Install now: at once when SynaBun is idle, otherwise as soon as it is (the app decides).
|
|
602
|
+
const call = id === 'download' || id === 'manual-download' ? 'download'
|
|
603
|
+
: id === 'install-now' || id === 'move' || id === 'show-file' ? 'install'
|
|
604
|
+
: id === 'retry' ? (view?.retry === 'download' ? 'download' : 'check')
|
|
605
|
+
: null;
|
|
606
|
+
if (!call) return;
|
|
607
|
+
setBusy(true);
|
|
608
|
+
let answer = null;
|
|
609
|
+
try {
|
|
610
|
+
answer = await desktop.updates[call]();
|
|
611
|
+
} catch (err) {
|
|
612
|
+
// The app did not take the request: this dialog says so, the badge keeps the app's last report.
|
|
613
|
+
setBusy(false);
|
|
614
|
+
update({ ...(_desktopState || {}), status: 'error', error: String(err?.message || '') || null });
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
// The answer is a report like any other.
|
|
618
|
+
if (answer) applyDesktopState(answer, { alert: false });
|
|
619
|
+
if (call === 'check' && desktopCheckOutcome(answer) === 'up-to-date') {
|
|
620
|
+
close();
|
|
621
|
+
showUpdateToast({ updates: [], errors: [] });
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (_desktopDialog && _desktopDialog.overlay === overlay) setBusy(false);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
async function downloadBackup() {
|
|
628
|
+
const scope = liveStep();
|
|
629
|
+
const btn = scope.querySelector('#desktop-update-backup');
|
|
630
|
+
const status = scope.querySelector('#desktop-update-backup-status');
|
|
631
|
+
const icon = scope.querySelector('#desktop-update-backup-icon');
|
|
632
|
+
const text = scope.querySelector('#desktop-update-backup-text');
|
|
633
|
+
if (!btn || !status || !icon || !text) return;
|
|
634
|
+
|
|
635
|
+
btn.disabled = true;
|
|
636
|
+
status.style.display = 'flex';
|
|
637
|
+
icon.className = 'update-backup-icon spin';
|
|
638
|
+
icon.textContent = '⟳';
|
|
639
|
+
icon.style.color = '';
|
|
640
|
+
text.textContent = desktopT('update.backupWorking');
|
|
641
|
+
|
|
642
|
+
try {
|
|
643
|
+
const res = await fetch('/api/system/backup');
|
|
644
|
+
if (!res.ok) {
|
|
645
|
+
let errMsg = `HTTP ${res.status}`;
|
|
646
|
+
try { const body = await res.json(); errMsg = body.error || errMsg; } catch {}
|
|
647
|
+
throw new Error(errMsg);
|
|
648
|
+
}
|
|
649
|
+
const blob = await res.blob();
|
|
650
|
+
const disposition = res.headers.get('content-disposition') || '';
|
|
651
|
+
const match = disposition.match(/filename="?([^"]+)"?/);
|
|
652
|
+
const filename = match ? match[1] : 'synabun-backup.zip';
|
|
653
|
+
|
|
654
|
+
const url = URL.createObjectURL(blob);
|
|
655
|
+
const a = document.createElement('a');
|
|
656
|
+
a.href = url; a.download = filename;
|
|
657
|
+
document.body.appendChild(a); a.click(); a.remove();
|
|
658
|
+
URL.revokeObjectURL(url);
|
|
659
|
+
|
|
660
|
+
icon.className = 'update-backup-icon';
|
|
661
|
+
icon.textContent = '✓';
|
|
662
|
+
icon.style.color = '#34c759';
|
|
663
|
+
text.textContent = desktopT('update.backupDone', { file: filename, size: desktopNumber(blob.size / 1024 / 1024, 1) });
|
|
664
|
+
} catch (err) {
|
|
665
|
+
icon.className = 'update-backup-icon';
|
|
666
|
+
icon.textContent = '✗';
|
|
667
|
+
icon.style.color = 'var(--accent-red)';
|
|
668
|
+
text.textContent = desktopT('update.backupFailed', { reason: err.message });
|
|
669
|
+
} finally {
|
|
670
|
+
btn.disabled = false;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// While it installs there is nothing to dismiss: SynaBun is closing.
|
|
675
|
+
const dismissable = () => view?.step !== 'installing';
|
|
676
|
+
|
|
677
|
+
function onKey(event) {
|
|
678
|
+
if (event.key !== 'Escape' || !dismissable()) return;
|
|
679
|
+
event.stopPropagation();
|
|
680
|
+
event.preventDefault();
|
|
681
|
+
close();
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function close() {
|
|
685
|
+
if (!_desktopDialog || _desktopDialog.overlay !== overlay) return;
|
|
686
|
+
_desktopDialog = null;
|
|
687
|
+
keepTicking(false);
|
|
688
|
+
window.removeEventListener('keydown', onKey, true);
|
|
689
|
+
overlay.classList.add('closing');
|
|
690
|
+
setTimeout(() => overlay.remove(), 150);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
overlay.addEventListener('click', (e) => {
|
|
694
|
+
if (e.target === overlay && dismissable()) close();
|
|
695
|
+
});
|
|
696
|
+
window.addEventListener('keydown', onKey, true);
|
|
697
|
+
_desktopDialog = { overlay, update, close };
|
|
698
|
+
document.body.appendChild(overlay);
|
|
699
|
+
setStep(first);
|
|
700
|
+
keepTicking(first.ticking);
|
|
701
|
+
}
|
|
702
|
+
|
|
115
703
|
// ── Modal Wizard ──
|
|
116
704
|
|
|
117
705
|
function openUpdateModal() {
|
|
118
706
|
if (!_versionData) return;
|
|
707
|
+
if (_versionData.desktop) { openDesktopUpdateModal(); return; }
|
|
119
708
|
|
|
120
709
|
let step = 1;
|
|
121
710
|
let backupDone = false;
|
|
@@ -487,12 +1076,14 @@ function openUpdateModal() {
|
|
|
487
1076
|
failure.code = code;
|
|
488
1077
|
throw failure;
|
|
489
1078
|
}
|
|
490
|
-
await res.json();
|
|
1079
|
+
const launched = await res.json();
|
|
1080
|
+
// On Windows the updater also stops the SynaBun MCP servers AI clients started (updater.mjs).
|
|
1081
|
+
const onWindows = launched?.platform === 'win32';
|
|
491
1082
|
|
|
492
1083
|
icon.className = 'update-backup-icon';
|
|
493
1084
|
icon.textContent = '✓';
|
|
494
1085
|
icon.style.color = '#34c759';
|
|
495
|
-
text.innerHTML = `<strong>Updater terminal launched</strong><span class="update-run-detail">SynaBun is shutting down so npm can replace the package. Watch the terminal for install progress; SynaBun will relaunch automatically when it finishes.</span
|
|
1086
|
+
text.innerHTML = `<strong>Updater terminal launched</strong><span class="update-run-detail">SynaBun is shutting down so npm can replace the package. Watch the terminal for install progress; SynaBun will relaunch automatically when it finishes.</span>${onWindows ? '<span class="update-run-detail">It also stops the SynaBun MCP servers your AI clients started. Reconnect them afterwards (in Claude Code: /mcp).</span>' : ''}<span class="update-run-hint">If no terminal window appears, ${onWindows ? 'close your AI clients, then ' : ''}run <code>${esc(installPlan.displayCommand || 'npm i -g synabun@latest')}</code> manually.</span>`;
|
|
496
1087
|
runBtn.innerHTML = 'Updater running…';
|
|
497
1088
|
|
|
498
1089
|
// Server is shutting down. Optionally redirect to a friendly offline
|