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
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — Codex accounts (per-account CODEX_HOME)
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// The Codex twin of lib/claude-accounts.js (docs/codex-sidepanel.md → Codex
|
|
6
|
+
// accounts). Default is the ambient CODEX_HOME (or ~/.codex); every added
|
|
7
|
+
// account owns ~/.codex-accounts/<id>, whose config.toml, prompts/ and
|
|
8
|
+
// AGENTS.md link back to Default's so model, MCP and tool settings stay shared,
|
|
9
|
+
// while auth.json, sessions/ and the state database stay its own.
|
|
10
|
+
//
|
|
11
|
+
// One account, two credentials, one mode. Each account can hold a ChatGPT
|
|
12
|
+
// login (Codex's own, in its CODEX_HOME: auth.json or the keyring Codex is set
|
|
13
|
+
// to use) and an OpenAI API key (SynaBun's `.synabun-api-key`, 0600, in the
|
|
14
|
+
// same folder). Codex keeps one credential per CODEX_HOME — `codex login
|
|
15
|
+
// --with-api-key` replaces a ChatGPT login and the reverse — so SynaBun never
|
|
16
|
+
// writes the key into Codex's store: it hands it to the process that runs
|
|
17
|
+
// under API-key mode, and the ChatGPT login next to it stays untouched.
|
|
18
|
+
// - `codex exec` (the SDK: workers, loops) reads CODEX_API_KEY, which wins
|
|
19
|
+
// over auth.json.
|
|
20
|
+
// - `codex app-server` (panel tabs, the Assistant's brain) ignores that
|
|
21
|
+
// variable: it starts with an ephemeral credential store and is signed in
|
|
22
|
+
// with `account/login/start {type:'apiKey'}` over its stdin.
|
|
23
|
+
// describe() is the one reading of an account's state; launchFor() is the one
|
|
24
|
+
// place a Codex process gets its credential. A key Codex itself holds (an older
|
|
25
|
+
// `codex login --with-api-key`) is shown as the account's API key and used by
|
|
26
|
+
// Codex as it is.
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
existsSync, lstatSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, symlinkSync, writeFileSync,
|
|
30
|
+
} from 'node:fs';
|
|
31
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
32
|
+
import { homedir } from 'node:os';
|
|
33
|
+
import { readProfileApiKey, saveProfileApiKey, clearProfileApiKey } from './claude-profile-api-key.js';
|
|
34
|
+
import { apiKeyHint, resolveClaudeAuth } from './claude-accounts.js';
|
|
35
|
+
import { normalizeAdminSandbox, sandboxElevationFor } from './agent-sandbox-elevation.js';
|
|
36
|
+
|
|
37
|
+
export const CODEX_DEFAULT_ACCOUNT_ID = 'default';
|
|
38
|
+
export const CODEX_AUTH_MODES = Object.freeze(['subscription', 'apiKey']);
|
|
39
|
+
// Everything inherited from SynaBun's own environment that would decide which
|
|
40
|
+
// OpenAI identity a Codex process uses: keys and tokens, and the organization /
|
|
41
|
+
// project headers that belong to an inherited key. Removed from every account
|
|
42
|
+
// launch: the account and its mode decide.
|
|
43
|
+
export const CODEX_CREDENTIAL_ENV = Object.freeze([
|
|
44
|
+
'OPENAI_API_KEY', 'CODEX_API_KEY', 'CODEX_ACCESS_TOKEN',
|
|
45
|
+
'OPENAI_ORGANIZATION', 'OPENAI_PROJECT',
|
|
46
|
+
'OPENAI_IDENTITY_TOKEN_FILE', 'OPENAI_WORKLOAD_IDENTITY_CONTEXT', 'OPENAI_FEDERATION_RULE_ID',
|
|
47
|
+
]);
|
|
48
|
+
// An app-server that runs on a key SynaBun holds: nothing it is told is kept,
|
|
49
|
+
// and the account's own auth.json (its ChatGPT login) is not read or replaced.
|
|
50
|
+
export const CODEX_EPHEMERAL_STORE_ARGS = Object.freeze(['-c', 'cli_auth_credentials_store="ephemeral"']);
|
|
51
|
+
const SHARED_FILES = ['config.toml', 'AGENTS.md'];
|
|
52
|
+
const STATUS_MAX_AGE_MS = 5 * 60_000;
|
|
53
|
+
|
|
54
|
+
export class CodexAccountError extends Error {
|
|
55
|
+
constructor(code, message, status = 400) {
|
|
56
|
+
super(message);
|
|
57
|
+
this.name = 'CodexAccountError';
|
|
58
|
+
this.code = code;
|
|
59
|
+
this.status = status;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** 'chatgpt' is accepted for the subscription mode (what the UI calls it). */
|
|
64
|
+
export function normalizeCodexMode(mode) {
|
|
65
|
+
if (mode === 'chatgpt') return 'subscription';
|
|
66
|
+
return CODEX_AUTH_MODES.includes(mode) ? mode : null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function readJson(path, fallback = null) { try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return fallback; } }
|
|
70
|
+
function writeJsonAtomic(path, value) {
|
|
71
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
72
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
73
|
+
writeFileSync(tmp, JSON.stringify(value, null, 2) + '\n', 'utf8');
|
|
74
|
+
renameSync(tmp, path);
|
|
75
|
+
}
|
|
76
|
+
function sanitizeId(value) {
|
|
77
|
+
return String(value || '').trim().toLowerCase().replace(/[^a-z0-9-]/g, '').slice(0, 64);
|
|
78
|
+
}
|
|
79
|
+
function sanitizeLabel(value, fallback) {
|
|
80
|
+
const label = String(value || '').replace(/[\r\n\t]/g, ' ').trim().slice(0, 80);
|
|
81
|
+
return label || fallback;
|
|
82
|
+
}
|
|
83
|
+
const text = (value, max = 200) => (typeof value === 'string' && value.trim() ? value.trim().slice(0, max) : null);
|
|
84
|
+
|
|
85
|
+
function jwtClaims(token) {
|
|
86
|
+
try {
|
|
87
|
+
const part = String(token || '').split('.')[1];
|
|
88
|
+
if (!part) return null;
|
|
89
|
+
return JSON.parse(Buffer.from(part.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8'));
|
|
90
|
+
} catch { return null; }
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* What Codex's own auth.json in a CODEX_HOME holds, without its secrets:
|
|
95
|
+
* { kind: 'chatgpt', email, plan } | { kind: 'apiKey', hint } | null (no file,
|
|
96
|
+
* or nothing usable in it). With a keyring store there is no file: null says
|
|
97
|
+
* nothing about a login then.
|
|
98
|
+
*/
|
|
99
|
+
export function readCodexAuthRecord(home) {
|
|
100
|
+
if (!home) return null;
|
|
101
|
+
let data;
|
|
102
|
+
try { data = JSON.parse(readFileSync(join(home, 'auth.json'), 'utf8')); } catch { return null; }
|
|
103
|
+
if (!data || typeof data !== 'object') return null;
|
|
104
|
+
const mode = String(data.auth_mode || '').toLowerCase();
|
|
105
|
+
const tokens = data.tokens && typeof data.tokens === 'object' ? data.tokens : null;
|
|
106
|
+
if (tokens && (tokens.id_token || tokens.access_token) && mode !== 'apikey') {
|
|
107
|
+
const claims = jwtClaims(tokens.id_token) || {};
|
|
108
|
+
const auth = claims['https://api.openai.com/auth'] || {};
|
|
109
|
+
return { kind: 'chatgpt', email: text(claims.email), plan: text(auth.chatgpt_plan_type, 40) };
|
|
110
|
+
}
|
|
111
|
+
if (typeof data.OPENAI_API_KEY === 'string' && data.OPENAI_API_KEY.trim()) {
|
|
112
|
+
return { kind: 'apiKey', hint: apiKeyHint(data.OPENAI_API_KEY.trim()) };
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** A launch's environment applied to a base: inherited credentials out, the account's in (no key). */
|
|
118
|
+
export function applyCodexLaunchEnv(base, launch) {
|
|
119
|
+
const env = { ...(base || {}) };
|
|
120
|
+
for (const key of launch?.unset || CODEX_CREDENTIAL_ENV) delete env[key];
|
|
121
|
+
return Object.assign(env, launch?.env || {});
|
|
122
|
+
}
|
|
123
|
+
/** What `codex exec` (the SDK) gets on top: in API-key mode, the key SynaBun holds. Spawn-only. */
|
|
124
|
+
export function codexExecEnv(launch) {
|
|
125
|
+
return { ...(launch?.env || {}), ...(launch?.apiKey ? { CODEX_API_KEY: launch.apiKey } : {}) };
|
|
126
|
+
}
|
|
127
|
+
/** The `-c` overrides every Codex process of the launch starts with: the Windows sandbox the Admin sandbox setting allows. */
|
|
128
|
+
export function codexLaunchArgs(launch) {
|
|
129
|
+
return [...(launch?.configArgs || [])];
|
|
130
|
+
}
|
|
131
|
+
/** The `-c` overrides an app-server of the account starts with (before the subcommand). */
|
|
132
|
+
export function codexAppServerArgs(launch) {
|
|
133
|
+
return [...codexLaunchArgs(launch), ...(launch?.apiKey ? CODEX_EPHEMERAL_STORE_ARGS : [])];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function createCodexAccounts({
|
|
137
|
+
dataHome,
|
|
138
|
+
homeDir = homedir(),
|
|
139
|
+
env = process.env,
|
|
140
|
+
accountsRoot = null,
|
|
141
|
+
log = () => {},
|
|
142
|
+
now = Date.now,
|
|
143
|
+
// async (id) => the `account/read` answer of an app-server run under that
|
|
144
|
+
// account's own store ({ account: { type, email, planType } | null }), or null.
|
|
145
|
+
readNativeStatus = null,
|
|
146
|
+
statusTimeoutMs = 20_000,
|
|
147
|
+
// Called before a new account links Default's config.toml (server.js repairs it there).
|
|
148
|
+
prepareDefaultConfig = () => {},
|
|
149
|
+
// () => the Admin sandbox setting (lib/agent-sandbox-setting.js): which Windows sandbox every launch may use.
|
|
150
|
+
adminSandbox = () => false,
|
|
151
|
+
} = {}) {
|
|
152
|
+
if (!dataHome) throw new Error('createCodexAccounts requires dataHome');
|
|
153
|
+
const registryPath = resolve(dataHome, 'data', 'codex-accounts.json');
|
|
154
|
+
const defaultHome = resolve(env.CODEX_HOME || resolve(homeDir, '.codex'));
|
|
155
|
+
const root = resolve(accountsRoot || resolve(homeDir, '.codex-accounts'));
|
|
156
|
+
const nativeStatus = new Map();
|
|
157
|
+
const refreshing = new Map();
|
|
158
|
+
const listeners = new Set();
|
|
159
|
+
let nativeReader = typeof readNativeStatus === 'function' ? readNativeStatus : null;
|
|
160
|
+
|
|
161
|
+
// The registry keeps Default's row in the list (older readers expect it), always at Default's home.
|
|
162
|
+
function load() {
|
|
163
|
+
const data = readJson(registryPath, null);
|
|
164
|
+
const rows = Array.isArray(data?.accounts) ? data.accounts.filter((row) => row && typeof row.id === 'string' && row.id) : [];
|
|
165
|
+
let defaults = rows.find((row) => row.id === CODEX_DEFAULT_ACCOUNT_ID);
|
|
166
|
+
const others = rows.filter((row) => row.id !== CODEX_DEFAULT_ACCOUNT_ID);
|
|
167
|
+
defaults = { email: '', planType: '', type: 'chatgpt', createdAt: null, ...(defaults || {}), id: CODEX_DEFAULT_ACCOUNT_ID, label: 'Default', home: defaultHome };
|
|
168
|
+
return { accounts: [defaults, ...others], defaultId: CODEX_DEFAULT_ACCOUNT_ID };
|
|
169
|
+
}
|
|
170
|
+
function save(data) {
|
|
171
|
+
writeJsonAtomic(registryPath, { accounts: data.accounts, defaultId: CODEX_DEFAULT_ACCOUNT_ID, updatedAt: new Date(now()).toISOString() });
|
|
172
|
+
}
|
|
173
|
+
function rowOf(data, id) { return data.accounts.find((row) => row.id === id) || null; }
|
|
174
|
+
function storedMode(data, id) { return normalizeCodexMode(rowOf(data, id)?.authMode); }
|
|
175
|
+
|
|
176
|
+
/** The registry row of an account (with its home), or null. Never a fallback to another account. */
|
|
177
|
+
function profileFor(id) {
|
|
178
|
+
const wanted = typeof id === 'string' && id ? id : CODEX_DEFAULT_ACCOUNT_ID;
|
|
179
|
+
return rowOf(load(), wanted);
|
|
180
|
+
}
|
|
181
|
+
function keyHome(id) {
|
|
182
|
+
const row = profileFor(id);
|
|
183
|
+
if (!row || row.id !== id) throw new CodexAccountError('ACCOUNT_NOT_FOUND', 'Unknown Codex account', 404);
|
|
184
|
+
return row.home;
|
|
185
|
+
}
|
|
186
|
+
function readApiKey(id) { return readProfileApiKey(keyHome(id), 'codex'); }
|
|
187
|
+
function keyState(id) {
|
|
188
|
+
try {
|
|
189
|
+
const key = readApiKey(id);
|
|
190
|
+
return key ? { present: true, hint: apiKeyHint(key), error: null, key } : { present: false, hint: null, error: null, key: null };
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (error?.code === 'ACCOUNT_NOT_FOUND') throw error;
|
|
193
|
+
return { present: false, hint: null, error: 'unreadable', key: null };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
function authFile(home) { return join(home, 'auth.json'); }
|
|
197
|
+
function fileState(file) {
|
|
198
|
+
try { return { exists: true, mtimeMs: statSync(file).mtimeMs }; } catch { return { exists: false, mtimeMs: 0 }; }
|
|
199
|
+
}
|
|
200
|
+
// Codex's own credential in the account's home: its ChatGPT login (subscription) or a key it holds.
|
|
201
|
+
function nativeOf(id, home) {
|
|
202
|
+
const file = fileState(authFile(home));
|
|
203
|
+
const status = nativeStatus.get(id);
|
|
204
|
+
// Codex rewrites auth.json when it signs in or out: a file newer than the last check, or one that
|
|
205
|
+
// appeared or went away since, outranks that check until Codex is asked again.
|
|
206
|
+
const fresh = status && file.exists === status.fileExisted && !(file.exists && file.mtimeMs > status.wall);
|
|
207
|
+
if (fresh) {
|
|
208
|
+
return {
|
|
209
|
+
subscription: { signedIn: status.kind === 'chatgpt', email: status.email, plan: status.plan, checked: true, checkedAt: status.checkedAt },
|
|
210
|
+
nativeKey: { present: status.kind === 'apiKey', hint: status.kind === 'apiKey' ? (readCodexAuthRecord(home)?.hint || null) : null },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const record = readCodexAuthRecord(home);
|
|
214
|
+
const signedIn = record?.kind === 'chatgpt' ? true : record?.kind === 'apiKey' ? false : status?.kind === 'none' ? false : null;
|
|
215
|
+
return {
|
|
216
|
+
subscription: { signedIn, email: record?.kind === 'chatgpt' ? record.email : null, plan: record?.kind === 'chatgpt' ? record.plan : null, checked: false, checkedAt: status?.checkedAt || null },
|
|
217
|
+
nativeKey: { present: record?.kind === 'apiKey', hint: record?.kind === 'apiKey' ? record.hint : null },
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function describeProfile(profile, data = load()) {
|
|
221
|
+
const id = profile.id;
|
|
222
|
+
const isDefault = id === CODEX_DEFAULT_ACCOUNT_ID;
|
|
223
|
+
const home = isDefault ? defaultHome : profile.home;
|
|
224
|
+
const key = keyState(id);
|
|
225
|
+
const native = nativeOf(id, home);
|
|
226
|
+
const preferredMode = storedMode(data, id);
|
|
227
|
+
const hasKey = key.present || native.nativeKey.present;
|
|
228
|
+
const auth = resolveClaudeAuth({ preferredMode, hasKey, subscription: native.subscription.signedIn });
|
|
229
|
+
const keySource = key.present ? 'synabun' : native.nativeKey.present ? 'codex' : null;
|
|
230
|
+
const keyHint = key.present ? key.hint : native.nativeKey.hint;
|
|
231
|
+
const sub = native.subscription;
|
|
232
|
+
const identity = auth.mode === 'apiKey' ? keyHint : auth.mode === 'subscription' ? (sub.email || null) : null;
|
|
233
|
+
const row = {
|
|
234
|
+
id, provider: 'codex',
|
|
235
|
+
label: isDefault ? 'Default' : sanitizeLabel(profile.label || profile.email, 'Codex account'), home,
|
|
236
|
+
isDefault, builtin: isDefault, createdAt: profile.createdAt || null,
|
|
237
|
+
mode: auth.mode, preferredMode, signedIn: auth.signedIn, switchable: auth.switchable, identity: identity || null,
|
|
238
|
+
subscription: { signedIn: sub.signedIn, email: sub.email, plan: sub.plan, checked: sub.checked, checkedAt: sub.checkedAt },
|
|
239
|
+
apiKey: { present: hasKey, hint: keyHint || null, error: key.error, source: keySource },
|
|
240
|
+
// The fields older readers use (Automation Studio, the launch selectors), from the same state.
|
|
241
|
+
loggedIn: auth.signedIn,
|
|
242
|
+
email: auth.mode === 'subscription' ? (sub.email || '') : '',
|
|
243
|
+
planType: auth.mode === 'subscription' ? (sub.plan || '') : '',
|
|
244
|
+
type: auth.mode === 'apiKey' ? 'apiKey' : 'chatgpt',
|
|
245
|
+
authMethod: auth.mode === 'apiKey' ? 'apiKey' : auth.mode === 'subscription' && sub.signedIn ? 'chatgpt' : null,
|
|
246
|
+
};
|
|
247
|
+
return { row, key: key.key };
|
|
248
|
+
}
|
|
249
|
+
function describe(id) {
|
|
250
|
+
const wanted = sanitizeId(id) || CODEX_DEFAULT_ACCOUNT_ID;
|
|
251
|
+
const data = load();
|
|
252
|
+
const profile = rowOf(data, wanted);
|
|
253
|
+
return profile ? describeProfile(profile, data).row : null;
|
|
254
|
+
}
|
|
255
|
+
function list() {
|
|
256
|
+
const data = load();
|
|
257
|
+
return data.accounts.map((profile) => describeProfile(profile, data).row);
|
|
258
|
+
}
|
|
259
|
+
function clientRow(row) {
|
|
260
|
+
const { home, ...rest } = row;
|
|
261
|
+
return rest;
|
|
262
|
+
}
|
|
263
|
+
function listForClient() { return list().map(clientRow); }
|
|
264
|
+
function describeForClient(id) { const row = describe(id); return row ? clientRow(row) : null; }
|
|
265
|
+
function homeFor(id) { return profileFor(sanitizeId(id) || CODEX_DEFAULT_ACCOUNT_ID)?.home || null; }
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* How a Codex process runs under an account: `unset` the inherited
|
|
269
|
+
* credential variables, set `env` (its CODEX_HOME), and in API-key mode with
|
|
270
|
+
* a key SynaBun holds, `apiKey` — for codexExecEnv() / codexAppServerArgs()
|
|
271
|
+
* and the app-server's stdin login. `apiKey` is a secret: spawn and stdin
|
|
272
|
+
* only, never a log, a response, an event or a file.
|
|
273
|
+
*/
|
|
274
|
+
function launchFor(id) {
|
|
275
|
+
const wanted = typeof id === 'string' && id ? id : CODEX_DEFAULT_ACCOUNT_ID;
|
|
276
|
+
const data = load();
|
|
277
|
+
const profile = rowOf(data, wanted);
|
|
278
|
+
if (!profile || !profile.home) {
|
|
279
|
+
return { ok: false, accountId: wanted, code: 'account_unavailable', message: 'That Codex account is no longer set up in SynaBun.' };
|
|
280
|
+
}
|
|
281
|
+
const { row, key } = describeProfile(profile, data);
|
|
282
|
+
const apiKey = row.mode === 'apiKey' && row.apiKey.source === 'synabun' && key ? key : null;
|
|
283
|
+
// A launch override, never written into config.toml: Default's home is the user's own ~/.codex.
|
|
284
|
+
let on = false;
|
|
285
|
+
try { on = normalizeAdminSandbox(adminSandbox()); } catch { on = false; }
|
|
286
|
+
const sandbox = sandboxElevationFor('codex', on);
|
|
287
|
+
return {
|
|
288
|
+
ok: true, accountId: wanted, label: row.label, mode: row.mode, signedIn: row.signedIn, identity: row.identity, home: row.home,
|
|
289
|
+
env: { CODEX_HOME: row.home }, unset: [...CODEX_CREDENTIAL_ENV], apiKey,
|
|
290
|
+
// Which credential a process started with: a change means the account's idle processes restart.
|
|
291
|
+
credential: [row.mode || 'none', row.apiKey.source || '', row.apiKey.hint || '', row.subscription.email || ''].join(':'),
|
|
292
|
+
// The Windows sandbox it may use ('elevated' | 'unelevated'): a change restarts idle app-servers too.
|
|
293
|
+
windowsSandbox: sandbox.value, configArgs: sandbox.args,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Why an account cannot run now ('' when it can, or when that is not known yet). */
|
|
298
|
+
function unusableReason(id) {
|
|
299
|
+
const row = describe(id);
|
|
300
|
+
if (!row) return 'That Codex account is no longer set up in SynaBun.';
|
|
301
|
+
if (row.apiKey.error && row.mode !== 'subscription') return `The API key saved for ${row.label} cannot be read. Save it again.`;
|
|
302
|
+
if (row.signedIn === false) return `${row.label} is signed out. Sign in with ChatGPT or add an API key to it first.`;
|
|
303
|
+
return '';
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
function writeMode(id, mode, data = load()) {
|
|
307
|
+
const account = rowOf(data, id);
|
|
308
|
+
if (!account) throw new CodexAccountError('ACCOUNT_NOT_FOUND', `Codex account not found: ${id}`, 404);
|
|
309
|
+
if (mode) account.authMode = mode; else delete account.authMode;
|
|
310
|
+
save(data);
|
|
311
|
+
}
|
|
312
|
+
/** Choose what an account's sessions use. The chosen credential must be there. */
|
|
313
|
+
function setMode(id, value) {
|
|
314
|
+
const wanted = sanitizeId(id) || CODEX_DEFAULT_ACCOUNT_ID;
|
|
315
|
+
const mode = normalizeCodexMode(value);
|
|
316
|
+
if (!mode) throw new CodexAccountError('MODE_INVALID', 'Choose "subscription" (ChatGPT) or "apiKey".', 400);
|
|
317
|
+
const row = describe(wanted);
|
|
318
|
+
if (!row) throw new CodexAccountError('ACCOUNT_NOT_FOUND', `Codex account not found: ${id}`, 404);
|
|
319
|
+
if (mode === 'apiKey' && !row.apiKey.present) throw new CodexAccountError('MODE_NEEDS_CREDENTIAL', `Add an API key to ${row.label} first.`, 409);
|
|
320
|
+
if (mode === 'subscription' && row.subscription.signedIn === false) throw new CodexAccountError('MODE_NEEDS_CREDENTIAL', `Sign in to ${row.label} with ChatGPT first.`, 409);
|
|
321
|
+
writeMode(wanted, mode);
|
|
322
|
+
emit(wanted);
|
|
323
|
+
return describe(wanted);
|
|
324
|
+
}
|
|
325
|
+
function saveApiKey(id, key) {
|
|
326
|
+
saveProfileApiKey(keyHome(id), key, 'codex');
|
|
327
|
+
// A key just pasted is what the user wants this account to use.
|
|
328
|
+
writeMode(id, 'apiKey');
|
|
329
|
+
emit(id);
|
|
330
|
+
}
|
|
331
|
+
function clearApiKey(id) {
|
|
332
|
+
clearProfileApiKey(keyHome(id));
|
|
333
|
+
const data = load();
|
|
334
|
+
if (storedMode(data, id) === 'apiKey') writeMode(id, null, data);
|
|
335
|
+
emit(id);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Remember what Codex said about the account's own store (`account/read`). */
|
|
339
|
+
function noteNativeStatus(id, status) {
|
|
340
|
+
if (!status || typeof status !== 'object' || !Object.hasOwn(status, 'account')) return;
|
|
341
|
+
const account = status.account;
|
|
342
|
+
const kind = !account ? 'none' : account.type === 'chatgpt' ? 'chatgpt' : account.type === 'apiKey' ? 'apiKey' : null;
|
|
343
|
+
if (!kind) return; // A store Codex filled another way (Bedrock): the file reading stands.
|
|
344
|
+
const profile = profileFor(id);
|
|
345
|
+
if (!profile) return;
|
|
346
|
+
const file = fileState(authFile(profile.home));
|
|
347
|
+
nativeStatus.set(id, {
|
|
348
|
+
kind,
|
|
349
|
+
email: kind === 'chatgpt' ? text(account.email) : null,
|
|
350
|
+
plan: kind === 'chatgpt' ? text(account.planType, 40) : null,
|
|
351
|
+
checkedAt: now(),
|
|
352
|
+
wall: Date.now(),
|
|
353
|
+
fileExisted: file.exists,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
function forgetNativeStatus(id) { nativeStatus.delete(id); }
|
|
357
|
+
function setNativeStatusReader(reader) { nativeReader = typeof reader === 'function' ? reader : null; }
|
|
358
|
+
function signature(row) { return row ? JSON.stringify([row.label, row.mode, row.signedIn, row.identity, row.subscription.signedIn, row.subscription.email, row.subscription.plan, row.apiKey.present, row.apiKey.hint, row.apiKey.source, row.preferredMode]) : ''; }
|
|
359
|
+
function checkOne(id) {
|
|
360
|
+
if (refreshing.has(id)) return refreshing.get(id);
|
|
361
|
+
const job = (async () => {
|
|
362
|
+
let timer;
|
|
363
|
+
try {
|
|
364
|
+
const status = await Promise.race([
|
|
365
|
+
Promise.resolve().then(() => nativeReader(id)),
|
|
366
|
+
new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('timeout')), statusTimeoutMs); timer.unref?.(); }),
|
|
367
|
+
]);
|
|
368
|
+
if (status && profileFor(id)) noteNativeStatus(id, status);
|
|
369
|
+
} catch (error) {
|
|
370
|
+
log('codex-accounts', `status check failed for ${id}: ${String(error?.message || error).slice(0, 120)}`);
|
|
371
|
+
} finally { clearTimeout(timer); refreshing.delete(id); }
|
|
372
|
+
})();
|
|
373
|
+
refreshing.set(id, job);
|
|
374
|
+
return job;
|
|
375
|
+
}
|
|
376
|
+
function stale(id, maxAgeMs = STATUS_MAX_AGE_MS) {
|
|
377
|
+
const known = nativeStatus.get(id);
|
|
378
|
+
if (!known || now() - known.checkedAt >= maxAgeMs) return true;
|
|
379
|
+
const profile = profileFor(id);
|
|
380
|
+
if (!profile) return false;
|
|
381
|
+
const file = fileState(authFile(profile.home));
|
|
382
|
+
return file.exists !== known.fileExisted || (file.exists && file.mtimeMs > known.wall);
|
|
383
|
+
}
|
|
384
|
+
/** Ask Codex about these accounts' own stores (all by default); listeners hear about every change. */
|
|
385
|
+
async function refreshStatus(ids = null, { force = false, maxAgeMs = STATUS_MAX_AGE_MS } = {}) {
|
|
386
|
+
if (!nativeReader) return list();
|
|
387
|
+
const wanted = (Array.isArray(ids) ? ids : load().accounts.map((row) => row.id)).filter((id) => profileFor(id));
|
|
388
|
+
const before = new Map(wanted.map((id) => [id, signature(describe(id))]));
|
|
389
|
+
await Promise.all(wanted.map((id) => (!force && !stale(id, maxAgeMs) ? null : checkOne(id))));
|
|
390
|
+
for (const id of wanted) if (before.get(id) !== signature(describe(id))) emit(id);
|
|
391
|
+
return list();
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); }
|
|
395
|
+
function emit(accountId, extra = {}) {
|
|
396
|
+
for (const listener of [...listeners]) { try { listener({ accountId, ...extra }); } catch {} }
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function linkShared(home) {
|
|
400
|
+
try { prepareDefaultConfig(); } catch (error) { log('codex-accounts', `config repair failed: ${error?.message || error}`); }
|
|
401
|
+
const linked = [];
|
|
402
|
+
const link = (name, kind) => {
|
|
403
|
+
const source = resolve(defaultHome, name);
|
|
404
|
+
const target = resolve(home, name);
|
|
405
|
+
try { lstatSync(target); return; } catch {}
|
|
406
|
+
try {
|
|
407
|
+
if (!existsSync(source)) {
|
|
408
|
+
// config.toml and prompts/ are made when missing (shared from the start); AGENTS.md is not ours to create.
|
|
409
|
+
if (name === 'AGENTS.md') return;
|
|
410
|
+
if (kind === 'dir') mkdirSync(source, { recursive: true }); else { mkdirSync(defaultHome, { recursive: true }); writeFileSync(source, '', 'utf8'); }
|
|
411
|
+
}
|
|
412
|
+
symlinkSync(source, target, kind === 'dir' ? (process.platform === 'win32' ? 'junction' : 'dir') : 'file');
|
|
413
|
+
linked.push(name);
|
|
414
|
+
} catch (error) { log('codex-accounts', `link ${name} failed: ${error?.message || error}`); }
|
|
415
|
+
};
|
|
416
|
+
for (const name of SHARED_FILES) link(name, 'file');
|
|
417
|
+
link('prompts', 'dir');
|
|
418
|
+
return linked;
|
|
419
|
+
}
|
|
420
|
+
function seedHome(id) {
|
|
421
|
+
const account = id && id !== CODEX_DEFAULT_ACCOUNT_ID ? profileFor(id) : null;
|
|
422
|
+
if (!account) throw new CodexAccountError('ACCOUNT_NOT_FOUND', `Codex account not found: ${id}`, 404);
|
|
423
|
+
mkdirSync(resolve(account.home, 'sessions'), { recursive: true });
|
|
424
|
+
return { home: account.home, linked: linkShared(account.home) };
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function create({ label } = {}) {
|
|
428
|
+
const data = load();
|
|
429
|
+
const id = `acct-${now().toString(36)}${Math.floor(Math.random() * 1296).toString(36).padStart(2, '0')}`;
|
|
430
|
+
const account = {
|
|
431
|
+
id, label: sanitizeLabel(label, `Codex ${data.accounts.length + 1}`), email: '', planType: '', type: 'chatgpt',
|
|
432
|
+
home: resolve(root, id), createdAt: new Date(now()).toISOString(),
|
|
433
|
+
};
|
|
434
|
+
if (label && String(label).trim()) account.labelCustom = true;
|
|
435
|
+
data.accounts.push(account);
|
|
436
|
+
save(data);
|
|
437
|
+
const seeded = seedHome(id);
|
|
438
|
+
// A folder made a moment ago holds no login: nothing to ask Codex.
|
|
439
|
+
noteNativeStatus(id, { account: null });
|
|
440
|
+
emit(id, { created: true });
|
|
441
|
+
return { ...describe(id), seeded };
|
|
442
|
+
}
|
|
443
|
+
function rename(id, label) {
|
|
444
|
+
const wanted = sanitizeId(id);
|
|
445
|
+
if (wanted === CODEX_DEFAULT_ACCOUNT_ID) throw new CodexAccountError('DEFAULT_ACCOUNT', 'The default account cannot be renamed here', 409);
|
|
446
|
+
const data = load();
|
|
447
|
+
const account = rowOf(data, wanted);
|
|
448
|
+
if (!account) throw new CodexAccountError('ACCOUNT_NOT_FOUND', `Codex account not found: ${id}`, 404);
|
|
449
|
+
account.label = sanitizeLabel(label, account.label);
|
|
450
|
+
account.labelCustom = true;
|
|
451
|
+
save(data);
|
|
452
|
+
emit(wanted);
|
|
453
|
+
return describe(wanted);
|
|
454
|
+
}
|
|
455
|
+
function remove(id, { inUse = () => null } = {}) {
|
|
456
|
+
const wanted = sanitizeId(id);
|
|
457
|
+
if (wanted === CODEX_DEFAULT_ACCOUNT_ID) throw new CodexAccountError('DEFAULT_ACCOUNT', 'The default account cannot be removed', 409);
|
|
458
|
+
const data = load();
|
|
459
|
+
const index = data.accounts.findIndex((row) => row.id === wanted);
|
|
460
|
+
if (index === -1) throw new CodexAccountError('ACCOUNT_NOT_FOUND', `Codex account not found: ${id}`, 404);
|
|
461
|
+
const reason = inUse(wanted);
|
|
462
|
+
if (reason) throw new CodexAccountError('ACCOUNT_IN_USE', typeof reason === 'string' ? reason : `Codex account ${wanted} is in use`, 409);
|
|
463
|
+
const [account] = data.accounts.splice(index, 1);
|
|
464
|
+
save(data);
|
|
465
|
+
nativeStatus.delete(wanted);
|
|
466
|
+
const home = account.home ? resolve(account.home) : '';
|
|
467
|
+
// Only a folder SynaBun made for the account, under ~/.codex-accounts: never Default's home.
|
|
468
|
+
if (home && home !== defaultHome && home.startsWith(`${root}${sep}`)) {
|
|
469
|
+
try { rmSync(home, { recursive: true, force: true }); } catch (error) { log('codex-accounts', `remove home failed: ${error?.message || error}`); }
|
|
470
|
+
}
|
|
471
|
+
emit(wanted, { removed: true });
|
|
472
|
+
return { ok: true, removed: account.id };
|
|
473
|
+
}
|
|
474
|
+
/** A ChatGPT sign-in just finished: it is what the account uses from now on, and names a still unnamed account. */
|
|
475
|
+
function noteSubscriptionSignIn(id, status = null) {
|
|
476
|
+
const wanted = sanitizeId(id) || CODEX_DEFAULT_ACCOUNT_ID;
|
|
477
|
+
if (!profileFor(wanted)) return null;
|
|
478
|
+
if (status) noteNativeStatus(wanted, status);
|
|
479
|
+
const data = load();
|
|
480
|
+
const account = rowOf(data, wanted);
|
|
481
|
+
const row = describe(wanted);
|
|
482
|
+
if (account && wanted !== CODEX_DEFAULT_ACCOUNT_ID && row?.subscription.email) {
|
|
483
|
+
account.email = row.subscription.email;
|
|
484
|
+
account.planType = row.subscription.plan || '';
|
|
485
|
+
if (!account.labelCustom && /^(Codex \d+|New account)$/.test(account.label || '')) account.label = row.subscription.email;
|
|
486
|
+
}
|
|
487
|
+
if (account) account.authMode = 'subscription';
|
|
488
|
+
save(data);
|
|
489
|
+
emit(wanted);
|
|
490
|
+
return describe(wanted);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
registryPath, defaultHome, root,
|
|
495
|
+
list, listForClient, describe, describeForClient, profileFor, homeFor, readApiKey, saveApiKey, clearApiKey,
|
|
496
|
+
// The registry rows (id, label, home, …) for readers that only need where an account lives.
|
|
497
|
+
registryRows: () => load().accounts,
|
|
498
|
+
get: describe, find: describe,
|
|
499
|
+
launchFor, unusableReason, setMode, noteNativeStatus, forgetNativeStatus, noteSubscriptionSignIn, setNativeStatusReader,
|
|
500
|
+
refreshStatus, stale, subscribe, create, rename, remove, seedHome,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
@@ -138,7 +138,8 @@ export function validateCodexConfigRequirements(config, result) {
|
|
|
138
138
|
throw error;
|
|
139
139
|
}
|
|
140
140
|
for (const [name, required] of Object.entries(requirements.featureRequirements || {})) {
|
|
141
|
-
|
|
141
|
+
const value = config.features?.[name] ?? config[`features.${name}`];
|
|
142
|
+
if (value !== undefined && value !== required) {
|
|
142
143
|
const error = new Error(`Feature ${name} is restricted by managed requirements.`);
|
|
143
144
|
error.code = 'MANAGED_REQUIREMENT';
|
|
144
145
|
throw error;
|
|
@@ -108,7 +108,7 @@ export async function listCodexPages(request, method, params = {}, key = 'data')
|
|
|
108
108
|
throw new Error(`${method} exceeded the catalog pagination limit.`);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
export function buildCodexCompatParams(message, { activeThreadId, cwd } = {}) {
|
|
111
|
+
export function buildCodexCompatParams(message, { activeThreadId, cwd, sandboxPolicyForMode } = {}) {
|
|
112
112
|
const threadId = owns(message, 'threadId') ? message.threadId : (activeThreadId || null);
|
|
113
113
|
const params = threadActions.has(message.type) ? { threadId: required(threadId, 'Thread') } : {};
|
|
114
114
|
switch (message.type) {
|
|
@@ -183,6 +183,10 @@ export function buildCodexCompatParams(message, { activeThreadId, cwd } = {}) {
|
|
|
183
183
|
&& Object.values(message.tags).every(value => typeof value === 'string') ? { tags: message.tags } : {}),
|
|
184
184
|
};
|
|
185
185
|
case 'thread_settings_update':
|
|
186
|
+
if (owns(message, 'sandboxMode')) {
|
|
187
|
+
if (!sandboxPolicyForMode) invalid('Sandbox settings are unavailable.');
|
|
188
|
+
params.sandboxPolicy = message.sandboxMode === null ? null : sandboxPolicyForMode(message.sandboxMode, message.cwd || cwd);
|
|
189
|
+
}
|
|
186
190
|
if (owns(message, 'disabledPluginIds')) {
|
|
187
191
|
if (!Array.isArray(message.disabledPluginIds) || message.disabledPluginIds.some(id => typeof id !== 'string' || !id.trim())) invalid('Disabled plugins must be a list of plugin ids.');
|
|
188
192
|
params.disabledPluginIds = [...new Set(message.disabledPluginIds)];
|
|
@@ -223,7 +227,9 @@ export async function handleCodexCompatMessage(message, context) {
|
|
|
223
227
|
}
|
|
224
228
|
if (message.type === 'thread_settings_update' && context.activeTurnId) invalid('Wait for the current turn before changing session settings.');
|
|
225
229
|
await context.beforeRequest?.(message.type, params);
|
|
226
|
-
const run = () =>
|
|
230
|
+
const run = () => message.type === 'experimental_features_set' && context.setFeatureEnablement
|
|
231
|
+
? context.setFeatureEnablement(params.enablement)
|
|
232
|
+
: context.request(route[0], params, /^verification_(enroll|delete|verify)$/.test(message.type) ? 120000 : message.type === 'feedback_upload' ? 30000 : 10000);
|
|
227
233
|
const result = mutations.has(message.type) ? await context.withWriterOperation(message.type, run) : await run();
|
|
228
234
|
if (message.type === 'mcp_app_resource_read') {
|
|
229
235
|
validateAppResources(result);
|
|
@@ -18,7 +18,11 @@ export function buildCodexConfigEdits(config, prefix = []) {
|
|
|
18
18
|
const edits = [];
|
|
19
19
|
for (const [key, value] of Object.entries(config)) {
|
|
20
20
|
if (value === undefined) continue;
|
|
21
|
-
|
|
21
|
+
// Settings uses dotted feature paths. Quoting the entire path creates an
|
|
22
|
+
// ignored top-level literal key ("features.apps"), rather than [features].
|
|
23
|
+
// Keep quoting nested MCP server identities such as "team.docs".
|
|
24
|
+
const path = !prefix.length && /^features\.[A-Za-z0-9_-]+$/.test(key)
|
|
25
|
+
? key.split('.') : [...prefix, key];
|
|
22
26
|
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
23
27
|
edits.push(...buildCodexConfigEdits(value, path));
|
|
24
28
|
continue;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Managed ChatGPT auth stays inside the existing account-scoped app-server. An API key never goes
|
|
2
|
+
// through here: Codex keeps one credential per CODEX_HOME, so a native API-key login would replace the
|
|
3
|
+
// account's ChatGPT sign-in. SynaBun keeps the key per account instead (lib/codex-accounts.js).
|
|
4
|
+
export function createCodexPanelAuth({ request, accountId, onChange = () => {}, onSuccess = async () => {}, timeoutMs = 10 * 60_000 }) {
|
|
5
|
+
let state = { status: 'idle' }, generation = 0, timer, early = [];
|
|
6
|
+
const snapshot = () => state.accountId && state.accountId !== accountId() ? { status: 'idle', accountId: accountId() } : ({ ...state, accountId: state.accountId || accountId() });
|
|
7
|
+
const update = patch => { state = { ...state, ...patch }; onChange(snapshot()); return snapshot(); };
|
|
8
|
+
const active = () => ['starting', 'pending'].includes(state.status);
|
|
9
|
+
async function complete(params) {
|
|
10
|
+
if (state.status === 'starting') { early.push(params); return; }
|
|
11
|
+
if (state.status !== 'pending' || !params.loginId || params.loginId !== state.loginId || state.accountId !== accountId()) return;
|
|
12
|
+
const epoch = generation;
|
|
13
|
+
clearTimeout(timer);
|
|
14
|
+
update({ status: params.success ? 'success' : 'error', authUrl: '', verificationUrl: '', userCode: '', error: params.success ? '' : (params.error || 'Codex sign-in failed. Try again.') });
|
|
15
|
+
if (params.success) {
|
|
16
|
+
try { await onSuccess(state.accountId); }
|
|
17
|
+
catch { if (epoch === generation) update({ error: 'Signed in, but account details could not refresh. Use Refresh.' }); }
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function start(loginType = 'chatgpt') {
|
|
21
|
+
if (!['chatgpt', 'chatgptDeviceCode'].includes(loginType)) throw new Error('Unsupported managed login type');
|
|
22
|
+
if (active()) return snapshot();
|
|
23
|
+
const epoch = ++generation;
|
|
24
|
+
early = [];
|
|
25
|
+
update({ status: 'starting', accountId: accountId(), loginId: null, authUrl: '', verificationUrl: '', userCode: '', error: '' });
|
|
26
|
+
try {
|
|
27
|
+
const login = await request('account/login/start', { type: loginType }, 15000);
|
|
28
|
+
if (epoch !== generation) {
|
|
29
|
+
if (login?.loginId) await request('account/login/cancel', { loginId: login.loginId }, 10000);
|
|
30
|
+
return snapshot();
|
|
31
|
+
}
|
|
32
|
+
if (!login?.loginId) throw new Error('Codex did not return a managed sign-in attempt. Update Codex and retry.');
|
|
33
|
+
update({ status: 'pending', loginId: login.loginId, authUrl: login.authUrl || '', verificationUrl: login.verificationUrl || '', userCode: login.userCode || '' });
|
|
34
|
+
timer = setTimeout(() => {
|
|
35
|
+
const timedGeneration = generation;
|
|
36
|
+
void cancel().then(() => {
|
|
37
|
+
if (generation === timedGeneration + 1 && state.status === 'cancelled') update({ status: 'error', error: 'Codex sign-in timed out. Try again.' });
|
|
38
|
+
}).catch(() => {});
|
|
39
|
+
}, timeoutMs); timer.unref?.();
|
|
40
|
+
for (const params of early) await complete(params);
|
|
41
|
+
early = [];
|
|
42
|
+
} catch (error) { if (epoch === generation) update({ status: 'error', error: error.message }); }
|
|
43
|
+
return snapshot();
|
|
44
|
+
}
|
|
45
|
+
async function cancel() {
|
|
46
|
+
if (!active()) return snapshot();
|
|
47
|
+
const loginId = state.loginId;
|
|
48
|
+
const epoch = ++generation; clearTimeout(timer);
|
|
49
|
+
update({ status: 'cancelled', authUrl: '', verificationUrl: '', userCode: '', error: '' });
|
|
50
|
+
if (loginId) {
|
|
51
|
+
try { await request('account/login/cancel', { loginId }, 10000); }
|
|
52
|
+
catch (error) { if (epoch === generation) update({ status: 'error', error: `Could not cancel Codex sign-in: ${error.message}` }); throw error; }
|
|
53
|
+
}
|
|
54
|
+
return snapshot();
|
|
55
|
+
}
|
|
56
|
+
function interrupted() {
|
|
57
|
+
++generation; clearTimeout(timer);
|
|
58
|
+
if (active()) update({ status: 'error', authUrl: '', verificationUrl: '', userCode: '', error: 'Codex disconnected during sign-in. Reconnect and retry.' });
|
|
59
|
+
}
|
|
60
|
+
function reset() { ++generation; clearTimeout(timer); state = { status: 'idle', accountId: accountId() }; return update({}); }
|
|
61
|
+
return { start, cancel, complete, snapshot, active, interrupted, reset };
|
|
62
|
+
}
|
|
@@ -2,7 +2,7 @@ import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileS
|
|
|
2
2
|
import { dirname } from 'node:path';
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
|
|
5
|
-
export const CODEX_SESSION_SETTING_KEYS = Object.freeze(['model', 'effort', 'summary', 'personality', 'serviceTier', 'approvalPolicy', 'approvalsReviewer', 'disabledPluginIds']);
|
|
5
|
+
export const CODEX_SESSION_SETTING_KEYS = Object.freeze(['model', 'effort', 'summary', 'personality', 'serviceTier', 'approvalPolicy', 'sandboxPolicy', 'approvalsReviewer', 'disabledPluginIds']);
|
|
6
6
|
const own = (value, key) => Object.prototype.hasOwnProperty.call(value, key);
|
|
7
7
|
|
|
8
8
|
/** Session choices are scoped to account/thread and never written to config.toml. */
|