gitspace 0.2.0-rc.20 → 0.2.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +11 -6
- package/.claude/settings.local.json +0 -10
- package/.gitspace/bundle.json +0 -50
- package/.gitspace/events.json +0 -11
- package/.gitspace/processes.json +0 -23
- package/.gitspace/scripts/select/01-status.sh +0 -39
- package/.gitspace/scripts/setup/01-install-deps.sh +0 -12
- package/.gitspace/scripts/setup/02-typecheck.sh +0 -16
- package/AGENTS.md +0 -469
- package/CLAUDE.md +0 -1
- package/bun.lock +0 -794
- package/docs/CONNECTION.md +0 -623
- package/docs/GATEWAY-WORKER.md +0 -319
- package/docs/GETTING-STARTED.md +0 -448
- package/docs/GITSPACE-PLATFORM.md +0 -1819
- package/docs/INFRASTRUCTURE.md +0 -1347
- package/docs/PROTOCOL.md +0 -619
- package/docs/QUICKSTART.md +0 -183
- package/docs/RELAY.md +0 -327
- package/docs/REMOTE-DESIGN.md +0 -554
- package/docs/ROADMAP.md +0 -564
- package/docs/SITE_DOCS_FIGMA_MAKE.md +0 -1176
- package/docs/STACK-DESIGN.md +0 -588
- package/docs/UNIFIED_ARCHITECTURE.md +0 -138
- package/experiments/pty-benchmark.ts +0 -148
- package/experiments/pty-latency.ts +0 -100
- package/experiments/router/client.ts +0 -199
- package/experiments/router/protocol.ts +0 -74
- package/experiments/router/router.ts +0 -217
- package/experiments/router/session.ts +0 -180
- package/experiments/router/test.ts +0 -133
- package/experiments/socket-bandwidth.ts +0 -77
- package/homebrew/gitspace.rb +0 -45
- package/landing-page/ATTRIBUTIONS.md +0 -3
- package/landing-page/README.md +0 -11
- package/landing-page/bun.lock +0 -801
- package/landing-page/guidelines/Guidelines.md +0 -61
- package/landing-page/index.html +0 -37
- package/landing-page/package.json +0 -90
- package/landing-page/postcss.config.mjs +0 -15
- package/landing-page/public/_redirects +0 -1
- package/landing-page/public/favicon.png +0 -0
- package/landing-page/src/app/App.tsx +0 -53
- package/landing-page/src/app/components/figma/ImageWithFallback.tsx +0 -27
- package/landing-page/src/app/components/ui/accordion.tsx +0 -66
- package/landing-page/src/app/components/ui/alert-dialog.tsx +0 -157
- package/landing-page/src/app/components/ui/alert.tsx +0 -66
- package/landing-page/src/app/components/ui/aspect-ratio.tsx +0 -11
- package/landing-page/src/app/components/ui/avatar.tsx +0 -53
- package/landing-page/src/app/components/ui/badge.tsx +0 -46
- package/landing-page/src/app/components/ui/breadcrumb.tsx +0 -109
- package/landing-page/src/app/components/ui/button.tsx +0 -57
- package/landing-page/src/app/components/ui/calendar.tsx +0 -75
- package/landing-page/src/app/components/ui/card.tsx +0 -92
- package/landing-page/src/app/components/ui/carousel.tsx +0 -241
- package/landing-page/src/app/components/ui/chart.tsx +0 -353
- package/landing-page/src/app/components/ui/checkbox.tsx +0 -32
- package/landing-page/src/app/components/ui/collapsible.tsx +0 -33
- package/landing-page/src/app/components/ui/command.tsx +0 -177
- package/landing-page/src/app/components/ui/context-menu.tsx +0 -252
- package/landing-page/src/app/components/ui/dialog.tsx +0 -135
- package/landing-page/src/app/components/ui/drawer.tsx +0 -132
- package/landing-page/src/app/components/ui/dropdown-menu.tsx +0 -257
- package/landing-page/src/app/components/ui/form.tsx +0 -168
- package/landing-page/src/app/components/ui/hover-card.tsx +0 -44
- package/landing-page/src/app/components/ui/input-otp.tsx +0 -77
- package/landing-page/src/app/components/ui/input.tsx +0 -21
- package/landing-page/src/app/components/ui/label.tsx +0 -24
- package/landing-page/src/app/components/ui/menubar.tsx +0 -276
- package/landing-page/src/app/components/ui/navigation-menu.tsx +0 -168
- package/landing-page/src/app/components/ui/pagination.tsx +0 -127
- package/landing-page/src/app/components/ui/popover.tsx +0 -48
- package/landing-page/src/app/components/ui/progress.tsx +0 -31
- package/landing-page/src/app/components/ui/radio-group.tsx +0 -45
- package/landing-page/src/app/components/ui/resizable.tsx +0 -56
- package/landing-page/src/app/components/ui/scroll-area.tsx +0 -58
- package/landing-page/src/app/components/ui/select.tsx +0 -189
- package/landing-page/src/app/components/ui/separator.tsx +0 -28
- package/landing-page/src/app/components/ui/sheet.tsx +0 -139
- package/landing-page/src/app/components/ui/sidebar.tsx +0 -726
- package/landing-page/src/app/components/ui/skeleton.tsx +0 -13
- package/landing-page/src/app/components/ui/slider.tsx +0 -63
- package/landing-page/src/app/components/ui/sonner.tsx +0 -25
- package/landing-page/src/app/components/ui/switch.tsx +0 -31
- package/landing-page/src/app/components/ui/table.tsx +0 -116
- package/landing-page/src/app/components/ui/tabs.tsx +0 -66
- package/landing-page/src/app/components/ui/textarea.tsx +0 -18
- package/landing-page/src/app/components/ui/toggle-group.tsx +0 -73
- package/landing-page/src/app/components/ui/toggle.tsx +0 -47
- package/landing-page/src/app/components/ui/tooltip.tsx +0 -61
- package/landing-page/src/app/components/ui/use-mobile.ts +0 -21
- package/landing-page/src/app/components/ui/utils.ts +0 -6
- package/landing-page/src/components/docs/DocsContent.tsx +0 -801
- package/landing-page/src/components/docs/DocsSidebar.tsx +0 -90
- package/landing-page/src/components/landing/CTA.tsx +0 -59
- package/landing-page/src/components/landing/Comparison.tsx +0 -84
- package/landing-page/src/components/landing/FaultyTerminal.tsx +0 -424
- package/landing-page/src/components/landing/Features.tsx +0 -201
- package/landing-page/src/components/landing/Hero.tsx +0 -142
- package/landing-page/src/components/landing/Pricing.tsx +0 -140
- package/landing-page/src/components/landing/Roadmap.tsx +0 -86
- package/landing-page/src/components/landing/Security.tsx +0 -81
- package/landing-page/src/components/landing/TerminalWindow.tsx +0 -27
- package/landing-page/src/components/landing/UseCases.tsx +0 -55
- package/landing-page/src/components/landing/Workflow.tsx +0 -101
- package/landing-page/src/components/layout/DashboardNavbar.tsx +0 -37
- package/landing-page/src/components/layout/Footer.tsx +0 -55
- package/landing-page/src/components/layout/LandingNavbar.tsx +0 -82
- package/landing-page/src/components/ui/badge.tsx +0 -39
- package/landing-page/src/components/ui/breadcrumb.tsx +0 -115
- package/landing-page/src/components/ui/button.tsx +0 -57
- package/landing-page/src/components/ui/card.tsx +0 -79
- package/landing-page/src/components/ui/mock-terminal.tsx +0 -68
- package/landing-page/src/components/ui/separator.tsx +0 -28
- package/landing-page/src/lib/utils.ts +0 -6
- package/landing-page/src/main.tsx +0 -10
- package/landing-page/src/pages/Dashboard.tsx +0 -133
- package/landing-page/src/pages/DocsPage.tsx +0 -79
- package/landing-page/src/pages/LandingPage.tsx +0 -31
- package/landing-page/src/pages/TerminalView.tsx +0 -106
- package/landing-page/src/styles/fonts.css +0 -0
- package/landing-page/src/styles/index.css +0 -3
- package/landing-page/src/styles/tailwind.css +0 -4
- package/landing-page/src/styles/theme.css +0 -181
- package/landing-page/vite.config.ts +0 -19
- package/scripts/GHOSTTY_TAB_BUG.md +0 -106
- package/scripts/build.ts +0 -298
- package/scripts/migrate-secrets.ts +0 -77
- package/scripts/release.ts +0 -140
- package/scripts/sample-events.ts +0 -263
- package/scripts/test-tabs-minimal.ts +0 -68
- package/scripts/test-tabs-workaround.ts +0 -95
- package/scripts/test-tabs.ts +0 -171
- package/src/__tests__/test-utils.ts +0 -298
- package/src/app/input/__tests__/sessionCommands.test.ts +0 -40
- package/src/app/input/sessionCommands.ts +0 -94
- package/src/app/session/__tests__/useAttachController.test.ts +0 -229
- package/src/app/session/createSessionBackend.bun.ts +0 -76
- package/src/app/session/createSessionBackend.web.ts +0 -104
- package/src/app/session/types.ts +0 -16
- package/src/app/session/useAttachController.ts +0 -220
- package/src/app/session/useProcessActions.ts +0 -201
- package/src/app/session/useSessionClient.ts +0 -35
- package/src/app/session/useWorkspaceDeleteFlow.ts +0 -170
- package/src/app.tui.tsx +0 -2929
- package/src/app.web.tsx +0 -1454
- package/src/commands/__tests__/connect-key.test.ts +0 -10
- package/src/commands/__tests__/events.test.ts +0 -201
- package/src/commands/__tests__/notifications.test.ts +0 -349
- package/src/commands/__tests__/process.test.ts +0 -251
- package/src/commands/__tests__/serve-messages.test.ts +0 -190
- package/src/commands/__tests__/serve-process-hosting.test.ts +0 -63
- package/src/commands/access.ts +0 -298
- package/src/commands/add.ts +0 -455
- package/src/commands/auth.ts +0 -369
- package/src/commands/bundle.ts +0 -232
- package/src/commands/config.ts +0 -242
- package/src/commands/connect-key.ts +0 -1
- package/src/commands/connect.ts +0 -576
- package/src/commands/directory.ts +0 -16
- package/src/commands/events.ts +0 -157
- package/src/commands/host.ts +0 -566
- package/src/commands/identity.ts +0 -184
- package/src/commands/linear.ts +0 -717
- package/src/commands/list.ts +0 -181
- package/src/commands/migrate.ts +0 -52
- package/src/commands/notifications.ts +0 -351
- package/src/commands/process.ts +0 -104
- package/src/commands/relay.ts +0 -315
- package/src/commands/remove.ts +0 -279
- package/src/commands/review.ts +0 -787
- package/src/commands/serve.ts +0 -1946
- package/src/commands/share.ts +0 -451
- package/src/commands/status.ts +0 -125
- package/src/commands/switch.ts +0 -361
- package/src/commands/tmux.ts +0 -317
- package/src/components/DPad.web.tsx +0 -343
- package/src/components/DiffViewer.web.tsx +0 -1192
- package/src/components/Events.tsx +0 -137
- package/src/components/Events.tui.tsx +0 -129
- package/src/components/Events.web.tsx +0 -386
- package/src/components/FloatingControls.web.tsx +0 -112
- package/src/components/FloatingJogWheel.web.tsx +0 -240
- package/src/components/Flow.tsx +0 -458
- package/src/components/Flow.tui.tsx +0 -343
- package/src/components/Flow.web.tsx +0 -442
- package/src/components/Inbox.tsx +0 -448
- package/src/components/Inbox.tui.tsx +0 -262
- package/src/components/Inbox.web.tsx +0 -329
- package/src/components/MachineList.tsx +0 -187
- package/src/components/MachineList.tui.tsx +0 -161
- package/src/components/MachineList.web.tsx +0 -210
- package/src/components/NumPad.web.tsx +0 -270
- package/src/components/ProjectList.tsx +0 -175
- package/src/components/ProjectList.tui.tsx +0 -109
- package/src/components/ProjectList.web.tsx +0 -143
- package/src/components/ProjectOnboardingStep.ts +0 -23
- package/src/components/ProjectOnboardingStep.tui.tsx +0 -88
- package/src/components/ProjectOnboardingStep.web.tsx +0 -59
- package/src/components/RemoteMachineScreen.tui.tsx +0 -690
- package/src/components/ScriptTerminal.tui.tsx +0 -160
- package/src/components/ScriptTerminal.web.tsx +0 -89
- package/src/components/SessionTerminal.tui.tsx +0 -406
- package/src/components/SessionTerminal.web.tsx +0 -467
- package/src/components/SpacesBrowser.tsx +0 -540
- package/src/components/SpacesBrowser.tui.tsx +0 -258
- package/src/components/SpacesBrowser.web.tsx +0 -332
- package/src/components/TerminalControls.web.tsx +0 -464
- package/src/components/ThreadPanel.web.tsx +0 -798
- package/src/components/__tests__/SpacesBrowser.test.ts +0 -541
- package/src/components/__tests__/SpacesBrowser.tui.test.tsx +0 -249
- package/src/components/__tests__/script-terminal-buffer.tui.test.ts +0 -72
- package/src/components/index.ts +0 -105
- package/src/components/review-decision-colors.ts +0 -11
- package/src/components/script-terminal-buffer.tui.ts +0 -37
- package/src/components/session-terminal-page-navigation.ts +0 -48
- package/src/components/terminal-bracketed-paste.tui.test.ts +0 -43
- package/src/components/terminal-bracketed-paste.tui.ts +0 -46
- package/src/core/__tests__/access.test.ts +0 -240
- package/src/core/__tests__/bundle-refresh.test.ts +0 -567
- package/src/core/__tests__/bundle.test.ts +0 -209
- package/src/core/__tests__/github-review.test.ts +0 -781
- package/src/core/__tests__/project-lifecycle.test.ts +0 -137
- package/src/core/__tests__/workspace-lifecycle.test.ts +0 -159
- package/src/core/__tests__/workspace.test.ts +0 -149
- package/src/core/access.ts +0 -277
- package/src/core/bundle-refresh.ts +0 -1064
- package/src/core/bundle.ts +0 -326
- package/src/core/config.ts +0 -405
- package/src/core/git.ts +0 -768
- package/src/core/github-review.ts +0 -761
- package/src/core/github.ts +0 -151
- package/src/core/identity.ts +0 -631
- package/src/core/linear.ts +0 -403
- package/src/core/preferences-service.ts +0 -17
- package/src/core/project-catalog.ts +0 -52
- package/src/core/project-lifecycle.ts +0 -163
- package/src/core/review-executor.ts +0 -316
- package/src/core/review.ts +0 -407
- package/src/core/secret-runtime.ts +0 -167
- package/src/core/shell.ts +0 -117
- package/src/core/trusted-relays.ts +0 -315
- package/src/core/workspace-lifecycle.ts +0 -216
- package/src/core/workspace.ts +0 -363
- package/src/hooks/__tests__/useLocalSession.tui.test.ts +0 -557
- package/src/hooks/index.ts +0 -8
- package/src/hooks/index.tui.ts +0 -32
- package/src/hooks/useDaemonStatus.tui.ts +0 -174
- package/src/hooks/useLocalSession.tui.ts +0 -395
- package/src/hooks/useRelayConnection.web.ts +0 -54
- package/src/hooks/useRemoteMachines.tui.ts +0 -166
- package/src/hooks/useRemoteTerminal.tui.ts +0 -22
- package/src/hooks/useReview.web.ts +0 -248
- package/src/hooks/useTerminal.web.ts +0 -36
- package/src/hooks/useUserActivity.ts +0 -61
- package/src/hooks/useVisualViewport.web.ts +0 -104
- package/src/index.ts +0 -1376
- package/src/lib/events/__tests__/collector-filter.test.ts +0 -105
- package/src/lib/events/__tests__/store-query.test.ts +0 -103
- package/src/lib/events/collector.ts +0 -494
- package/src/lib/events/filters.ts +0 -26
- package/src/lib/events/index.ts +0 -11
- package/src/lib/events/indexer.ts +0 -14
- package/src/lib/events/paths.ts +0 -69
- package/src/lib/events/reader.ts +0 -212
- package/src/lib/events/store.ts +0 -141
- package/src/lib/invite.web.ts +0 -58
- package/src/lib/preferences-service.web.ts +0 -41
- package/src/lib/processes/__tests__/config.test.ts +0 -83
- package/src/lib/processes/__tests__/names.test.ts +0 -125
- package/src/lib/processes/__tests__/schema.test.ts +0 -208
- package/src/lib/processes/__tests__/watchdog.test.ts +0 -210
- package/src/lib/processes/autostart.ts +0 -16
- package/src/lib/processes/config.ts +0 -187
- package/src/lib/processes/control.ts +0 -53
- package/src/lib/processes/editor.ts +0 -32
- package/src/lib/processes/events-config.ts +0 -37
- package/src/lib/processes/index.ts +0 -14
- package/src/lib/processes/instances.ts +0 -20
- package/src/lib/processes/manager.ts +0 -131
- package/src/lib/processes/names.ts +0 -71
- package/src/lib/processes/registry.ts +0 -26
- package/src/lib/processes/runner.ts +0 -211
- package/src/lib/processes/scheduler.ts +0 -17
- package/src/lib/processes/schema.ts +0 -74
- package/src/lib/processes/session-list.ts +0 -15
- package/src/lib/processes/state.ts +0 -82
- package/src/lib/processes/watchdog.test.ts +0 -79
- package/src/lib/processes/watchdog.ts +0 -106
- package/src/lib/remote-session/__tests__/protocol.test.ts +0 -291
- package/src/lib/remote-session/index.ts +0 -7
- package/src/lib/remote-session/protocol.ts +0 -443
- package/src/lib/remote-session/session-handler.ts +0 -1298
- package/src/lib/remote-session/workspace-scanner.ts +0 -161
- package/src/lib/sonner.web.ts +0 -1
- package/src/lib/storage/identity-store.web.ts +0 -94
- package/src/lib/tmux-lite/README.md +0 -81
- package/src/lib/tmux-lite/cli.ts +0 -855
- package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +0 -349
- package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +0 -291
- package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +0 -142
- package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +0 -339
- package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +0 -477
- package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +0 -499
- package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +0 -371
- package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +0 -573
- package/src/lib/tmux-lite/crypto/access-control.test.ts +0 -512
- package/src/lib/tmux-lite/crypto/access-control.ts +0 -320
- package/src/lib/tmux-lite/crypto/frames.test.ts +0 -262
- package/src/lib/tmux-lite/crypto/frames.ts +0 -141
- package/src/lib/tmux-lite/crypto/handshake.ts +0 -894
- package/src/lib/tmux-lite/crypto/identity.test.ts +0 -220
- package/src/lib/tmux-lite/crypto/identity.ts +0 -286
- package/src/lib/tmux-lite/crypto/index.ts +0 -51
- package/src/lib/tmux-lite/crypto/invites.test.ts +0 -381
- package/src/lib/tmux-lite/crypto/invites.ts +0 -215
- package/src/lib/tmux-lite/crypto/keyexchange.ts +0 -435
- package/src/lib/tmux-lite/crypto/keys.test.ts +0 -58
- package/src/lib/tmux-lite/crypto/keys.ts +0 -47
- package/src/lib/tmux-lite/crypto/secretbox.test.ts +0 -169
- package/src/lib/tmux-lite/crypto/secretbox.ts +0 -124
- package/src/lib/tmux-lite/handshake-handler.ts +0 -451
- package/src/lib/tmux-lite/process-run.integration.test.ts +0 -266
- package/src/lib/tmux-lite/protocol.test.ts +0 -307
- package/src/lib/tmux-lite/protocol.ts +0 -291
- package/src/lib/tmux-lite/relay-client.ts +0 -506
- package/src/lib/tmux-lite/server-lifecycle.test.ts +0 -212
- package/src/lib/tmux-lite/server.ts +0 -1412
- package/src/lib/tmux-lite/shell-integration.sh +0 -37
- package/src/lib/tmux-lite/terminal-queries.test.ts +0 -54
- package/src/lib/tmux-lite/terminal-queries.ts +0 -49
- package/src/notifications/__tests__/useNotifications.test.ts +0 -739
- package/src/notifications/index.ts +0 -32
- package/src/notifications/policy.test.ts +0 -424
- package/src/notifications/policy.ts +0 -139
- package/src/notifications/types.ts +0 -82
- package/src/notifications/useNotifications.ts +0 -350
- package/src/pages/ReviewPage.web.tsx +0 -511
- package/src/preferences/index.ts +0 -1
- package/src/preferences/types.ts +0 -9
- package/src/relay/__tests__/e2e-flow.test.ts +0 -1284
- package/src/relay/__tests__/helpers/auth.ts +0 -354
- package/src/relay/__tests__/helpers/ports.ts +0 -51
- package/src/relay/__tests__/protocol-validation.test.ts +0 -265
- package/src/relay/authorization.ts +0 -303
- package/src/relay/embedded-assets.generated.d.ts +0 -15
- package/src/relay/identity.ts +0 -352
- package/src/relay/index.ts +0 -57
- package/src/relay/pipes.test.ts +0 -427
- package/src/relay/pipes.ts +0 -195
- package/src/relay/protocol.ts +0 -804
- package/src/relay/registries.test.ts +0 -437
- package/src/relay/registries.ts +0 -593
- package/src/relay/server.test.ts +0 -1323
- package/src/relay/server.ts +0 -1128
- package/src/relay/signing.ts +0 -238
- package/src/relay/types.ts +0 -69
- package/src/relay-client/__tests__/machine-directory-client.test.ts +0 -152
- package/src/relay-client/__tests__/useMachineDirectory.test.ts +0 -172
- package/src/relay-client/adapters/browser.ts +0 -27
- package/src/relay-client/adapters/node.ts +0 -29
- package/src/relay-client/index.ts +0 -33
- package/src/relay-client/machine-directory-client.ts +0 -244
- package/src/relay-client/useMachineDirectory.ts +0 -175
- package/src/serve/client-session-manager.ts +0 -635
- package/src/serve/daemon.ts +0 -497
- package/src/serve/pty-session.ts +0 -236
- package/src/serve/types.ts +0 -174
- package/src/session/__tests__/backend-manager.test.ts +0 -101
- package/src/session/__tests__/local-session-backend.test.ts +0 -1129
- package/src/session/__tests__/reducer.test.ts +0 -80
- package/src/session/__tests__/remote-session-backend.test.ts +0 -995
- package/src/session/__tests__/session-name.test.ts +0 -35
- package/src/session/__tests__/useBundleRefreshAttachFlow.test.ts +0 -431
- package/src/session/__tests__/useRemoteSessionClient.test.ts +0 -424
- package/src/session/__tests__/workspace-shell-hooks.integration.test.ts +0 -268
- package/src/session/__tests__/workspace-shell-hooks.test.ts +0 -24
- package/src/session/adapters/browser-remote.ts +0 -101
- package/src/session/adapters/node-remote.ts +0 -135
- package/src/session/backend-key.ts +0 -5
- package/src/session/backend-manager.ts +0 -80
- package/src/session/backend.ts +0 -93
- package/src/session/backends/local-session-backend.ts +0 -1119
- package/src/session/backends/remote-session-backend.ts +0 -1378
- package/src/session/crypto/__tests__/web-terminal.test.ts +0 -1158
- package/src/session/crypto/frames.web.ts +0 -205
- package/src/session/crypto/handshake.web.ts +0 -396
- package/src/session/crypto/identity.web.ts +0 -133
- package/src/session/crypto/keyexchange.web.ts +0 -246
- package/src/session/crypto/relay-signing.web.ts +0 -53
- package/src/session/events.ts +0 -38
- package/src/session/index.ts +0 -116
- package/src/session/reducer.ts +0 -274
- package/src/session/selectors.ts +0 -28
- package/src/session/session-name.ts +0 -50
- package/src/session/types.ts +0 -101
- package/src/session/useBundleRefreshAttachFlow.ts +0 -608
- package/src/session/useRemoteSessionClient.ts +0 -424
- package/src/session/useSessionEngine.ts +0 -432
- package/src/session/workspace-shell-hooks.ts +0 -35
- package/src/tui/__tests__/input-text.test.ts +0 -24
- package/src/tui/__tests__/local-terminal-sync.test.ts +0 -82
- package/src/tui/__tests__/session-terminal-page-navigation.test.ts +0 -94
- package/src/tui/app.tsx +0 -2
- package/src/tui/index.ts +0 -18
- package/src/tui/input-text.ts +0 -38
- package/src/tui/local-terminal-sync.ts +0 -41
- package/src/types/bundle-refresh.ts +0 -42
- package/src/types/bundle.ts +0 -130
- package/src/types/config.ts +0 -287
- package/src/types/errors.ts +0 -292
- package/src/types/events.ts +0 -91
- package/src/types/identity.ts +0 -284
- package/src/types/processes.ts +0 -45
- package/src/types/review.ts +0 -349
- package/src/types/script-phase.ts +0 -3
- package/src/types/workspace-fuzzy.ts +0 -49
- package/src/types/workspace.ts +0 -151
- package/src/utils/__tests__/onboarding.test.ts +0 -358
- package/src/utils/__tests__/run-scripts.test.ts +0 -535
- package/src/utils/__tests__/run-workspace-scripts.test.ts +0 -406
- package/src/utils/__tests__/workspace-setup.integration.test.ts +0 -633
- package/src/utils/__tests__/workspace-state.test.ts +0 -78
- package/src/utils/bun-socket-writer.ts +0 -80
- package/src/utils/clipboard.ts +0 -53
- package/src/utils/deps.test.ts +0 -31
- package/src/utils/deps.ts +0 -145
- package/src/utils/device.web.ts +0 -163
- package/src/utils/fuzzy-match.ts +0 -125
- package/src/utils/hostnames.ts +0 -43
- package/src/utils/hunk-header.ts +0 -17
- package/src/utils/id.ts +0 -9
- package/src/utils/logger.ts +0 -127
- package/src/utils/markdown.ts +0 -254
- package/src/utils/normalize-env-key.ts +0 -13
- package/src/utils/onboarding.ts +0 -279
- package/src/utils/prompts.ts +0 -176
- package/src/utils/run-commands.ts +0 -112
- package/src/utils/run-scripts.ts +0 -337
- package/src/utils/run-workspace-scripts.ts +0 -355
- package/src/utils/sanitize.test.ts +0 -149
- package/src/utils/sanitize.ts +0 -162
- package/src/utils/secrets.ts +0 -836
- package/src/utils/shell-escape.ts +0 -40
- package/src/utils/utf8.ts +0 -79
- package/src/utils/workspace-id.ts +0 -55
- package/src/utils/workspace-state.ts +0 -427
- package/src/version.generated.d.ts +0 -2
- package/todo-security.md +0 -92
- package/tsconfig.json +0 -29
- package/web/README.md +0 -73
- package/web/bun.lock +0 -675
- package/web/eslint.config.js +0 -23
- package/web/index.css +0 -249
- package/web/index.html +0 -16
- package/web/main.tsx +0 -10
- package/web/package.json +0 -39
- package/web/public/vite.svg +0 -1
- package/web/tsconfig.app.json +0 -35
- package/web/tsconfig.json +0 -7
- package/web/tsconfig.node.json +0 -26
- package/web/vite.config.ts +0 -39
- package/worker/bun.lock +0 -237
- package/worker/package.json +0 -22
- package/worker/schema.sql +0 -96
- package/worker/src/handlers/auth.ts +0 -451
- package/worker/src/handlers/subdomains.ts +0 -376
- package/worker/src/handlers/user.ts +0 -98
- package/worker/src/index.ts +0 -70
- package/worker/src/middleware/auth.ts +0 -152
- package/worker/src/services/cloudflare.ts +0 -609
- package/worker/src/types.ts +0 -96
- package/worker/tsconfig.json +0 -15
- package/worker/wrangler.toml +0 -26
package/src/utils/secrets.ts
DELETED
|
@@ -1,836 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Secure secrets management using Bun.secrets API.
|
|
3
|
-
*
|
|
4
|
-
* Current format: a SINGLE unified keychain entry containing both global
|
|
5
|
-
* and project-scoped secret maps.
|
|
6
|
-
*
|
|
7
|
-
* Legacy formats still supported for reads/migration:
|
|
8
|
-
* - project blobs: project:<name>
|
|
9
|
-
* - global blob: global
|
|
10
|
-
* - very old per-secret keys: <project>:<key> and <key>
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const SERVICE_NAME = 'com.gitspace';
|
|
14
|
-
|
|
15
|
-
// Keychain entry names
|
|
16
|
-
const UNIFIED_SECRETS_KEY = 'secrets';
|
|
17
|
-
const PROJECT_SECRETS_PREFIX = 'project:';
|
|
18
|
-
const GLOBAL_SECRETS_KEY = 'global';
|
|
19
|
-
|
|
20
|
-
interface UnifiedSecretsBlob {
|
|
21
|
-
global: Record<string, string>;
|
|
22
|
-
projects: Record<string, Record<string, string>>;
|
|
23
|
-
metadata: {
|
|
24
|
-
schemaVersion: number;
|
|
25
|
-
legacyMigrationComplete: boolean;
|
|
26
|
-
legacyEntriesRetained: boolean;
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const UNIFIED_SECRETS_SCHEMA_VERSION = 2;
|
|
31
|
-
|
|
32
|
-
// In-memory cache for unified secrets blob
|
|
33
|
-
let unifiedSecretsCache: UnifiedSecretsBlob | null = null;
|
|
34
|
-
|
|
35
|
-
// Process-level marker used for reminders about legacy cleanup.
|
|
36
|
-
let legacyEntriesDetected = false;
|
|
37
|
-
const legacyProjectBlobChecked = new Set<string>();
|
|
38
|
-
let legacyGlobalBlobChecked = false;
|
|
39
|
-
|
|
40
|
-
function createEmptyBlob(): UnifiedSecretsBlob {
|
|
41
|
-
return {
|
|
42
|
-
global: {},
|
|
43
|
-
projects: {},
|
|
44
|
-
metadata: {
|
|
45
|
-
schemaVersion: UNIFIED_SECRETS_SCHEMA_VERSION,
|
|
46
|
-
legacyMigrationComplete: false,
|
|
47
|
-
legacyEntriesRetained: false,
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function normalizeRecord(value: unknown): Record<string, string> {
|
|
53
|
-
if (!value || typeof value !== 'object') {
|
|
54
|
-
return {};
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const output: Record<string, string> = {};
|
|
58
|
-
for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {
|
|
59
|
-
if (typeof raw === 'string') {
|
|
60
|
-
output[key] = raw;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return output;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function parseUnifiedSecretsBlob(raw: string | null): UnifiedSecretsBlob {
|
|
67
|
-
if (!raw) {
|
|
68
|
-
return createEmptyBlob();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
const parsed = JSON.parse(raw) as {
|
|
73
|
-
global?: unknown;
|
|
74
|
-
projects?: unknown;
|
|
75
|
-
metadata?: unknown;
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
const projects: Record<string, Record<string, string>> = {};
|
|
79
|
-
if (parsed.projects && typeof parsed.projects === 'object') {
|
|
80
|
-
for (const [projectName, projectSecrets] of Object.entries(parsed.projects as Record<string, unknown>)) {
|
|
81
|
-
projects[projectName] = normalizeRecord(projectSecrets);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const metadata =
|
|
86
|
-
parsed.metadata && typeof parsed.metadata === 'object'
|
|
87
|
-
? (parsed.metadata as {
|
|
88
|
-
schemaVersion?: unknown;
|
|
89
|
-
legacyMigrationComplete?: unknown;
|
|
90
|
-
legacyEntriesRetained?: unknown;
|
|
91
|
-
})
|
|
92
|
-
: {};
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
global: normalizeRecord(parsed.global),
|
|
96
|
-
projects,
|
|
97
|
-
metadata: {
|
|
98
|
-
schemaVersion:
|
|
99
|
-
typeof metadata.schemaVersion === 'number' && Number.isFinite(metadata.schemaVersion)
|
|
100
|
-
? metadata.schemaVersion
|
|
101
|
-
: UNIFIED_SECRETS_SCHEMA_VERSION,
|
|
102
|
-
legacyMigrationComplete: metadata.legacyMigrationComplete === true,
|
|
103
|
-
legacyEntriesRetained: metadata.legacyEntriesRetained === true,
|
|
104
|
-
},
|
|
105
|
-
};
|
|
106
|
-
} catch {
|
|
107
|
-
return createEmptyBlob();
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function isLegacyMigrationComplete(blob: UnifiedSecretsBlob): boolean {
|
|
112
|
-
return blob.metadata.legacyMigrationComplete === true;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async function loadUnifiedSecretsBlob(): Promise<UnifiedSecretsBlob> {
|
|
116
|
-
if (unifiedSecretsCache) {
|
|
117
|
-
return unifiedSecretsCache;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const raw = await Bun.secrets.get({
|
|
121
|
-
service: SERVICE_NAME,
|
|
122
|
-
name: UNIFIED_SECRETS_KEY,
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
unifiedSecretsCache = parseUnifiedSecretsBlob(raw);
|
|
126
|
-
if (unifiedSecretsCache.metadata.legacyMigrationComplete) {
|
|
127
|
-
legacyEntriesDetected = unifiedSecretsCache.metadata.legacyEntriesRetained;
|
|
128
|
-
}
|
|
129
|
-
return unifiedSecretsCache;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async function saveUnifiedSecretsBlob(blob: UnifiedSecretsBlob): Promise<void> {
|
|
133
|
-
const normalized: UnifiedSecretsBlob = {
|
|
134
|
-
global: { ...blob.global },
|
|
135
|
-
projects: { ...blob.projects },
|
|
136
|
-
metadata: {
|
|
137
|
-
schemaVersion: UNIFIED_SECRETS_SCHEMA_VERSION,
|
|
138
|
-
legacyMigrationComplete: blob.metadata.legacyMigrationComplete === true,
|
|
139
|
-
legacyEntriesRetained: blob.metadata.legacyEntriesRetained === true,
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
unifiedSecretsCache = normalized;
|
|
143
|
-
|
|
144
|
-
await Bun.secrets.set({
|
|
145
|
-
service: SERVICE_NAME,
|
|
146
|
-
name: UNIFIED_SECRETS_KEY,
|
|
147
|
-
value: JSON.stringify(normalized),
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Clear the in-memory secrets cache
|
|
153
|
-
* Useful for long-running processes that need fresh values
|
|
154
|
-
*/
|
|
155
|
-
export function clearSecretsCache(): void {
|
|
156
|
-
unifiedSecretsCache = null;
|
|
157
|
-
legacyProjectBlobChecked.clear();
|
|
158
|
-
legacyGlobalBlobChecked = false;
|
|
159
|
-
legacyEntriesDetected = false;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ============================================================================
|
|
163
|
-
// Project Secrets (stored as single JSON blob per project)
|
|
164
|
-
// ============================================================================
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Get the keychain entry name for a project's secrets blob
|
|
168
|
-
*/
|
|
169
|
-
function getProjectSecretsKey(projectName: string): string {
|
|
170
|
-
return `${PROJECT_SECRETS_PREFIX}${projectName}`;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
async function loadLegacyProjectBlob(projectName: string): Promise<{
|
|
174
|
-
hasLegacyEntry: boolean;
|
|
175
|
-
secrets: Record<string, string>;
|
|
176
|
-
}> {
|
|
177
|
-
legacyProjectBlobChecked.add(projectName);
|
|
178
|
-
const raw = await Bun.secrets.get({
|
|
179
|
-
service: SERVICE_NAME,
|
|
180
|
-
name: getProjectSecretsKey(projectName),
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
if (!raw) {
|
|
184
|
-
return { hasLegacyEntry: false, secrets: {} };
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
legacyEntriesDetected = true;
|
|
188
|
-
try {
|
|
189
|
-
return { hasLegacyEntry: true, secrets: normalizeRecord(JSON.parse(raw)) };
|
|
190
|
-
} catch {
|
|
191
|
-
return { hasLegacyEntry: true, secrets: {} };
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
async function loadLegacyGlobalBlob(): Promise<{
|
|
196
|
-
hasLegacyEntry: boolean;
|
|
197
|
-
secrets: Record<string, string>;
|
|
198
|
-
}> {
|
|
199
|
-
legacyGlobalBlobChecked = true;
|
|
200
|
-
const raw = await Bun.secrets.get({
|
|
201
|
-
service: SERVICE_NAME,
|
|
202
|
-
name: GLOBAL_SECRETS_KEY,
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
if (!raw) {
|
|
206
|
-
return { hasLegacyEntry: false, secrets: {} };
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
legacyEntriesDetected = true;
|
|
210
|
-
try {
|
|
211
|
-
return { hasLegacyEntry: true, secrets: normalizeRecord(JSON.parse(raw)) };
|
|
212
|
-
} catch {
|
|
213
|
-
return { hasLegacyEntry: true, secrets: {} };
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
function mergeSecrets(
|
|
218
|
-
existing: Record<string, string>,
|
|
219
|
-
legacy: Record<string, string>
|
|
220
|
-
): Record<string, string> {
|
|
221
|
-
// New-format values win if both are present.
|
|
222
|
-
return { ...legacy, ...existing };
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Load project secrets from the unified blob.
|
|
227
|
-
*/
|
|
228
|
-
async function loadProjectSecretsBlob(projectName: string): Promise<Record<string, string>> {
|
|
229
|
-
const blob = await loadUnifiedSecretsBlob();
|
|
230
|
-
return { ...(blob.projects[projectName] || {}) };
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Save the secrets blob for a project to keychain
|
|
235
|
-
*/
|
|
236
|
-
async function saveProjectSecretsBlob(
|
|
237
|
-
projectName: string,
|
|
238
|
-
secrets: Record<string, string>
|
|
239
|
-
): Promise<void> {
|
|
240
|
-
const blob = await loadUnifiedSecretsBlob();
|
|
241
|
-
if (Object.keys(secrets).length === 0) {
|
|
242
|
-
delete blob.projects[projectName];
|
|
243
|
-
} else {
|
|
244
|
-
blob.projects[projectName] = { ...secrets };
|
|
245
|
-
}
|
|
246
|
-
await saveUnifiedSecretsBlob(blob);
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Store a secret for a project
|
|
251
|
-
*/
|
|
252
|
-
export async function setProjectSecret(
|
|
253
|
-
projectName: string,
|
|
254
|
-
key: string,
|
|
255
|
-
value: string
|
|
256
|
-
): Promise<void> {
|
|
257
|
-
const secrets = await loadProjectSecretsBlob(projectName);
|
|
258
|
-
secrets[key] = value;
|
|
259
|
-
await saveProjectSecretsBlob(projectName, secrets);
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Retrieve a secret for a project
|
|
264
|
-
*
|
|
265
|
-
* Checks the consolidated blob first, then falls back to old per-secret
|
|
266
|
-
* format for seamless migration from older versions.
|
|
267
|
-
*/
|
|
268
|
-
export async function getProjectSecret(
|
|
269
|
-
projectName: string,
|
|
270
|
-
key: string
|
|
271
|
-
): Promise<string | null> {
|
|
272
|
-
const blob = await loadUnifiedSecretsBlob();
|
|
273
|
-
const current = blob.projects[projectName] || {};
|
|
274
|
-
|
|
275
|
-
// Found in new format
|
|
276
|
-
if (current[key] !== undefined) {
|
|
277
|
-
return current[key];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (isLegacyMigrationComplete(blob)) {
|
|
281
|
-
return null;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Try legacy project blob format: project:<name>
|
|
285
|
-
if (!legacyProjectBlobChecked.has(projectName)) {
|
|
286
|
-
const legacyBlob = await loadLegacyProjectBlob(projectName);
|
|
287
|
-
if (legacyBlob.hasLegacyEntry) {
|
|
288
|
-
blob.projects[projectName] = mergeSecrets(current, legacyBlob.secrets);
|
|
289
|
-
await saveUnifiedSecretsBlob(blob);
|
|
290
|
-
if (blob.projects[projectName][key] !== undefined) {
|
|
291
|
-
return blob.projects[projectName][key];
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
// Try old format: ${projectName}:${key}
|
|
297
|
-
const oldKeychainName = `${projectName}:${key}`;
|
|
298
|
-
const oldValue = await Bun.secrets.get({
|
|
299
|
-
service: SERVICE_NAME,
|
|
300
|
-
name: oldKeychainName,
|
|
301
|
-
});
|
|
302
|
-
|
|
303
|
-
if (oldValue) {
|
|
304
|
-
legacyEntriesDetected = true;
|
|
305
|
-
blob.projects[projectName] = {
|
|
306
|
-
...(blob.projects[projectName] || {}),
|
|
307
|
-
[key]: oldValue,
|
|
308
|
-
};
|
|
309
|
-
await saveUnifiedSecretsBlob(blob);
|
|
310
|
-
|
|
311
|
-
return oldValue;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Delete a secret for a project
|
|
319
|
-
*/
|
|
320
|
-
export async function deleteProjectSecret(
|
|
321
|
-
projectName: string,
|
|
322
|
-
key: string
|
|
323
|
-
): Promise<boolean> {
|
|
324
|
-
const secrets = await loadProjectSecretsBlob(projectName);
|
|
325
|
-
if (!(key in secrets)) {
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
328
|
-
delete secrets[key];
|
|
329
|
-
await saveProjectSecretsBlob(projectName, secrets);
|
|
330
|
-
return true;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Get all secrets for a project given a list of secret keys
|
|
335
|
-
* Returns a record of key -> value for all found secrets
|
|
336
|
-
*/
|
|
337
|
-
export async function getProjectSecrets(
|
|
338
|
-
projectName: string,
|
|
339
|
-
keys: string[]
|
|
340
|
-
): Promise<Record<string, string>> {
|
|
341
|
-
const blob = await loadUnifiedSecretsBlob();
|
|
342
|
-
let secrets = blob.projects[projectName] || {};
|
|
343
|
-
let changed = false;
|
|
344
|
-
|
|
345
|
-
if (!isLegacyMigrationComplete(blob) && !legacyProjectBlobChecked.has(projectName)) {
|
|
346
|
-
const legacyBlob = await loadLegacyProjectBlob(projectName);
|
|
347
|
-
if (legacyBlob.hasLegacyEntry) {
|
|
348
|
-
secrets = mergeSecrets(secrets, legacyBlob.secrets);
|
|
349
|
-
blob.projects[projectName] = secrets;
|
|
350
|
-
changed = true;
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
const result: Record<string, string> = {};
|
|
355
|
-
|
|
356
|
-
for (const key of keys) {
|
|
357
|
-
if (key in secrets) {
|
|
358
|
-
result[key] = secrets[key];
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
if (isLegacyMigrationComplete(blob)) {
|
|
363
|
-
continue;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Legacy per-secret fallback (very old format)
|
|
367
|
-
const oldKeychainName = `${projectName}:${key}`;
|
|
368
|
-
const oldValue = await Bun.secrets.get({
|
|
369
|
-
service: SERVICE_NAME,
|
|
370
|
-
name: oldKeychainName,
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
if (oldValue) {
|
|
374
|
-
legacyEntriesDetected = true;
|
|
375
|
-
result[key] = oldValue;
|
|
376
|
-
blob.projects[projectName] = {
|
|
377
|
-
...(blob.projects[projectName] || {}),
|
|
378
|
-
[key]: oldValue,
|
|
379
|
-
};
|
|
380
|
-
changed = true;
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
if (changed) {
|
|
385
|
-
await saveUnifiedSecretsBlob(blob);
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
return result;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
/**
|
|
392
|
-
* Preload project secrets into the cache
|
|
393
|
-
* Call this early in an operation to trigger a single keychain prompt,
|
|
394
|
-
* then subsequent getProjectSecret calls will use cached values.
|
|
395
|
-
*/
|
|
396
|
-
export async function preloadProjectSecrets(
|
|
397
|
-
projectName: string,
|
|
398
|
-
keys: string[]
|
|
399
|
-
): Promise<Record<string, string>> {
|
|
400
|
-
return getProjectSecrets(projectName, keys);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
/**
|
|
404
|
-
* Delete all secrets for a project given a list of secret keys
|
|
405
|
-
*/
|
|
406
|
-
export async function deleteProjectSecrets(
|
|
407
|
-
projectName: string,
|
|
408
|
-
keys: string[]
|
|
409
|
-
): Promise<void> {
|
|
410
|
-
const secrets = await loadProjectSecretsBlob(projectName);
|
|
411
|
-
for (const key of keys) {
|
|
412
|
-
delete secrets[key];
|
|
413
|
-
}
|
|
414
|
-
await saveProjectSecretsBlob(projectName, secrets);
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Delete the entire secrets blob for a project
|
|
419
|
-
* Used when removing a project entirely
|
|
420
|
-
*/
|
|
421
|
-
export async function deleteAllProjectSecrets(projectName: string): Promise<void> {
|
|
422
|
-
const blob = await loadUnifiedSecretsBlob();
|
|
423
|
-
delete blob.projects[projectName];
|
|
424
|
-
await saveUnifiedSecretsBlob(blob);
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// ============================================================================
|
|
428
|
-
// Global Secrets (stored as single JSON blob)
|
|
429
|
-
// ============================================================================
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Load global secrets from the unified blob.
|
|
433
|
-
*/
|
|
434
|
-
async function loadGlobalSecretsBlob(): Promise<Record<string, string>> {
|
|
435
|
-
const blob = await loadUnifiedSecretsBlob();
|
|
436
|
-
return { ...blob.global };
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Save the global secrets blob to keychain
|
|
441
|
-
*/
|
|
442
|
-
async function saveGlobalSecretsBlob(secrets: Record<string, string>): Promise<void> {
|
|
443
|
-
const blob = await loadUnifiedSecretsBlob();
|
|
444
|
-
blob.global = { ...secrets };
|
|
445
|
-
await saveUnifiedSecretsBlob(blob);
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
/**
|
|
449
|
-
* Store a global secret (not project-scoped)
|
|
450
|
-
* Used for: GITSPACE_TOKEN, TUNNEL_TOKEN_{subdomain}, etc.
|
|
451
|
-
*/
|
|
452
|
-
export async function setSecret(key: string, value: string): Promise<void> {
|
|
453
|
-
const secrets = await loadGlobalSecretsBlob();
|
|
454
|
-
secrets[key] = value;
|
|
455
|
-
await saveGlobalSecretsBlob(secrets);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
/**
|
|
459
|
-
* Retrieve a global secret
|
|
460
|
-
*
|
|
461
|
-
* Checks the consolidated blob first, then falls back to old per-secret
|
|
462
|
-
* format for seamless migration from older versions.
|
|
463
|
-
*/
|
|
464
|
-
export async function getSecret(key: string): Promise<string | null> {
|
|
465
|
-
const blob = await loadUnifiedSecretsBlob();
|
|
466
|
-
const secrets = blob.global;
|
|
467
|
-
|
|
468
|
-
// Found in new format
|
|
469
|
-
if (secrets[key] !== undefined) {
|
|
470
|
-
return secrets[key];
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
if (isLegacyMigrationComplete(blob)) {
|
|
474
|
-
return null;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// Try legacy global blob format: global
|
|
478
|
-
if (!legacyGlobalBlobChecked) {
|
|
479
|
-
const legacyBlob = await loadLegacyGlobalBlob();
|
|
480
|
-
if (legacyBlob.hasLegacyEntry) {
|
|
481
|
-
blob.global = mergeSecrets(blob.global, legacyBlob.secrets);
|
|
482
|
-
await saveUnifiedSecretsBlob(blob);
|
|
483
|
-
if (blob.global[key] !== undefined) {
|
|
484
|
-
return blob.global[key];
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// Try old format: direct key name
|
|
490
|
-
const oldValue = await Bun.secrets.get({
|
|
491
|
-
service: SERVICE_NAME,
|
|
492
|
-
name: key,
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
if (oldValue) {
|
|
496
|
-
legacyEntriesDetected = true;
|
|
497
|
-
blob.global[key] = oldValue;
|
|
498
|
-
await saveUnifiedSecretsBlob(blob);
|
|
499
|
-
|
|
500
|
-
return oldValue;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
return null;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* Delete a global secret
|
|
508
|
-
*/
|
|
509
|
-
export async function deleteSecret(key: string): Promise<boolean> {
|
|
510
|
-
const secrets = await loadGlobalSecretsBlob();
|
|
511
|
-
if (!(key in secrets)) {
|
|
512
|
-
return false;
|
|
513
|
-
}
|
|
514
|
-
delete secrets[key];
|
|
515
|
-
await saveGlobalSecretsBlob(secrets);
|
|
516
|
-
return true;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
export interface PreloadAllSecretsResult {
|
|
520
|
-
legacyEntriesDetected: boolean;
|
|
521
|
-
importedLegacyProjectEntries: number;
|
|
522
|
-
importedLegacyGlobalEntry: boolean;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
export interface PreloadAllSecretsOptions {
|
|
526
|
-
globalLegacyKeys?: string[];
|
|
527
|
-
projectLegacyKeys?: Record<string, string[]>;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Preload all secrets for the process into memory.
|
|
532
|
-
* This is intended to be called once at startup (serve + TUI).
|
|
533
|
-
*/
|
|
534
|
-
export async function preloadAllSecrets(
|
|
535
|
-
projectNames: string[],
|
|
536
|
-
options: PreloadAllSecretsOptions = {}
|
|
537
|
-
): Promise<PreloadAllSecretsResult> {
|
|
538
|
-
const blob = await loadUnifiedSecretsBlob();
|
|
539
|
-
|
|
540
|
-
if (isLegacyMigrationComplete(blob)) {
|
|
541
|
-
legacyEntriesDetected = blob.metadata.legacyEntriesRetained;
|
|
542
|
-
return {
|
|
543
|
-
legacyEntriesDetected,
|
|
544
|
-
importedLegacyProjectEntries: 0,
|
|
545
|
-
importedLegacyGlobalEntry: false,
|
|
546
|
-
};
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
let importedLegacyProjectEntries = 0;
|
|
550
|
-
let importedLegacyGlobalEntry = false;
|
|
551
|
-
let changed = false;
|
|
552
|
-
let detectedLegacyEntries = false;
|
|
553
|
-
|
|
554
|
-
const projectLegacyKeys = options.projectLegacyKeys ?? {};
|
|
555
|
-
const globalLegacyKeys = [...new Set(options.globalLegacyKeys ?? [])];
|
|
556
|
-
|
|
557
|
-
const projectNamesToCheck = [...new Set([...projectNames, ...Object.keys(projectLegacyKeys)])];
|
|
558
|
-
|
|
559
|
-
for (const projectName of projectNamesToCheck) {
|
|
560
|
-
const legacyProject = await loadLegacyProjectBlob(projectName);
|
|
561
|
-
if (!legacyProject.hasLegacyEntry) {
|
|
562
|
-
// continue with per-key migration below
|
|
563
|
-
} else {
|
|
564
|
-
detectedLegacyEntries = true;
|
|
565
|
-
importedLegacyProjectEntries += 1;
|
|
566
|
-
const current = blob.projects[projectName] || {};
|
|
567
|
-
const merged = mergeSecrets(current, legacyProject.secrets);
|
|
568
|
-
if (JSON.stringify(current) !== JSON.stringify(merged)) {
|
|
569
|
-
blob.projects[projectName] = merged;
|
|
570
|
-
changed = true;
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
const oldKeys = [...new Set(projectLegacyKeys[projectName] ?? [])];
|
|
575
|
-
for (const key of oldKeys) {
|
|
576
|
-
const oldValue = await Bun.secrets.get({
|
|
577
|
-
service: SERVICE_NAME,
|
|
578
|
-
name: `${projectName}:${key}`,
|
|
579
|
-
});
|
|
580
|
-
|
|
581
|
-
if (!oldValue) {
|
|
582
|
-
continue;
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
detectedLegacyEntries = true;
|
|
586
|
-
const currentProjectSecrets = blob.projects[projectName] || {};
|
|
587
|
-
if (currentProjectSecrets[key] === undefined) {
|
|
588
|
-
blob.projects[projectName] = {
|
|
589
|
-
...currentProjectSecrets,
|
|
590
|
-
[key]: oldValue,
|
|
591
|
-
};
|
|
592
|
-
changed = true;
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
const legacyGlobal = await loadLegacyGlobalBlob();
|
|
598
|
-
if (legacyGlobal.hasLegacyEntry) {
|
|
599
|
-
detectedLegacyEntries = true;
|
|
600
|
-
importedLegacyGlobalEntry = true;
|
|
601
|
-
const merged = mergeSecrets(blob.global, legacyGlobal.secrets);
|
|
602
|
-
if (JSON.stringify(blob.global) !== JSON.stringify(merged)) {
|
|
603
|
-
blob.global = merged;
|
|
604
|
-
changed = true;
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
for (const key of globalLegacyKeys) {
|
|
609
|
-
const oldValue = await Bun.secrets.get({
|
|
610
|
-
service: SERVICE_NAME,
|
|
611
|
-
name: key,
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
if (!oldValue) {
|
|
615
|
-
continue;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
detectedLegacyEntries = true;
|
|
619
|
-
if (blob.global[key] === undefined) {
|
|
620
|
-
blob.global[key] = oldValue;
|
|
621
|
-
changed = true;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
blob.metadata.legacyMigrationComplete = true;
|
|
626
|
-
blob.metadata.legacyEntriesRetained = detectedLegacyEntries;
|
|
627
|
-
changed = true;
|
|
628
|
-
|
|
629
|
-
legacyEntriesDetected = detectedLegacyEntries;
|
|
630
|
-
|
|
631
|
-
if (changed) {
|
|
632
|
-
await saveUnifiedSecretsBlob(blob);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
return {
|
|
636
|
-
legacyEntriesDetected,
|
|
637
|
-
importedLegacyProjectEntries,
|
|
638
|
-
importedLegacyGlobalEntry,
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
export function hasLegacyEntriesInProcess(): boolean {
|
|
643
|
-
return legacyEntriesDetected;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
export interface CleanupLegacySecretsResult {
|
|
647
|
-
deleted: number;
|
|
648
|
-
missing: number;
|
|
649
|
-
errors: string[];
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
export interface CleanupLegacySecretsOptions {
|
|
653
|
-
globalLegacyKeys?: string[];
|
|
654
|
-
projectLegacyKeys?: Record<string, string[]>;
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
/**
|
|
658
|
-
* Remove legacy keychain entries now that unified secrets are in use.
|
|
659
|
-
* This only removes legacy blob entries (`project:*`, `global`).
|
|
660
|
-
*
|
|
661
|
-
* TODO(v0.3+): remove legacy blob reads and this cleanup path once
|
|
662
|
-
* unified secrets storage has been stable for several releases.
|
|
663
|
-
*/
|
|
664
|
-
export async function cleanupLegacySecretEntries(
|
|
665
|
-
projectNames: string[],
|
|
666
|
-
options: CleanupLegacySecretsOptions = {}
|
|
667
|
-
): Promise<CleanupLegacySecretsResult> {
|
|
668
|
-
const result: CleanupLegacySecretsResult = {
|
|
669
|
-
deleted: 0,
|
|
670
|
-
missing: 0,
|
|
671
|
-
errors: [],
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
const projectLegacyKeys = options.projectLegacyKeys ?? {};
|
|
675
|
-
const globalLegacyKeys = options.globalLegacyKeys ?? [];
|
|
676
|
-
|
|
677
|
-
const entries = new Set<string>([
|
|
678
|
-
GLOBAL_SECRETS_KEY,
|
|
679
|
-
...[...new Set(projectNames)].map((name) => getProjectSecretsKey(name)),
|
|
680
|
-
...globalLegacyKeys,
|
|
681
|
-
]);
|
|
682
|
-
|
|
683
|
-
for (const [projectName, keys] of Object.entries(projectLegacyKeys)) {
|
|
684
|
-
for (const key of keys) {
|
|
685
|
-
entries.add(`${projectName}:${key}`);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
for (const name of entries) {
|
|
690
|
-
try {
|
|
691
|
-
const deleted = await Bun.secrets.delete({
|
|
692
|
-
service: SERVICE_NAME,
|
|
693
|
-
name,
|
|
694
|
-
});
|
|
695
|
-
if (deleted) {
|
|
696
|
-
result.deleted += 1;
|
|
697
|
-
} else {
|
|
698
|
-
result.missing += 1;
|
|
699
|
-
}
|
|
700
|
-
} catch (error) {
|
|
701
|
-
result.errors.push(`${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
if (result.errors.length === 0) {
|
|
706
|
-
const blob = await loadUnifiedSecretsBlob();
|
|
707
|
-
if (blob.metadata.legacyEntriesRetained) {
|
|
708
|
-
blob.metadata.legacyEntriesRetained = false;
|
|
709
|
-
await saveUnifiedSecretsBlob(blob);
|
|
710
|
-
}
|
|
711
|
-
legacyEntriesDetected = false;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
return result;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
// ============================================================================
|
|
718
|
-
// Migration from old format (one keychain entry per secret)
|
|
719
|
-
// ============================================================================
|
|
720
|
-
|
|
721
|
-
/**
|
|
722
|
-
* Read a secret in the OLD format directly from keychain
|
|
723
|
-
* Old format: project secrets were stored as `${projectName}:${key}`
|
|
724
|
-
* Old format: global secrets were stored directly as `${key}`
|
|
725
|
-
*/
|
|
726
|
-
async function getOldFormatSecret(keychainName: string): Promise<string | null> {
|
|
727
|
-
return Bun.secrets.get({
|
|
728
|
-
service: SERVICE_NAME,
|
|
729
|
-
name: keychainName,
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
/**
|
|
734
|
-
* Delete a secret in the OLD format from keychain
|
|
735
|
-
*/
|
|
736
|
-
async function deleteOldFormatSecret(keychainName: string): Promise<boolean> {
|
|
737
|
-
return Bun.secrets.delete({
|
|
738
|
-
service: SERVICE_NAME,
|
|
739
|
-
name: keychainName,
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
/**
|
|
744
|
-
* Migrate secrets from old format to new consolidated format
|
|
745
|
-
*
|
|
746
|
-
* Old format:
|
|
747
|
-
* - Project secrets: `${projectName}:${key}` per secret
|
|
748
|
-
* - Global secrets: `${key}` per secret
|
|
749
|
-
*
|
|
750
|
-
* New format:
|
|
751
|
-
* - Project secrets: `project:${projectName}` containing JSON blob
|
|
752
|
-
* - Global secrets: `global` containing JSON blob
|
|
753
|
-
*
|
|
754
|
-
* @param projects - Map of project name -> array of secret keys
|
|
755
|
-
* @param globalKeys - Array of global secret keys to migrate
|
|
756
|
-
* @param deleteOld - Whether to delete old entries after migration
|
|
757
|
-
* @returns Migration report
|
|
758
|
-
*/
|
|
759
|
-
export async function migrateSecrets(
|
|
760
|
-
projects: Record<string, string[]>,
|
|
761
|
-
globalKeys: string[],
|
|
762
|
-
deleteOld: boolean = false
|
|
763
|
-
): Promise<{
|
|
764
|
-
projectSecretsMigrated: number;
|
|
765
|
-
globalSecretsMigrated: number;
|
|
766
|
-
oldEntriesDeleted: number;
|
|
767
|
-
errors: string[];
|
|
768
|
-
}> {
|
|
769
|
-
const result = {
|
|
770
|
-
projectSecretsMigrated: 0,
|
|
771
|
-
globalSecretsMigrated: 0,
|
|
772
|
-
oldEntriesDeleted: 0,
|
|
773
|
-
errors: [] as string[],
|
|
774
|
-
};
|
|
775
|
-
|
|
776
|
-
// Migrate project secrets
|
|
777
|
-
for (const [projectName, keys] of Object.entries(projects)) {
|
|
778
|
-
const newSecrets: Record<string, string> = {};
|
|
779
|
-
|
|
780
|
-
for (const key of keys) {
|
|
781
|
-
const oldKeychainName = `${projectName}:${key}`;
|
|
782
|
-
try {
|
|
783
|
-
const value = await getOldFormatSecret(oldKeychainName);
|
|
784
|
-
if (value) {
|
|
785
|
-
newSecrets[key] = value;
|
|
786
|
-
result.projectSecretsMigrated++;
|
|
787
|
-
|
|
788
|
-
if (deleteOld) {
|
|
789
|
-
await deleteOldFormatSecret(oldKeychainName);
|
|
790
|
-
result.oldEntriesDeleted++;
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
} catch (err) {
|
|
794
|
-
result.errors.push(`Failed to migrate ${oldKeychainName}: ${err}`);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// Save consolidated blob if we found any secrets
|
|
799
|
-
if (Object.keys(newSecrets).length > 0) {
|
|
800
|
-
// Merge with any existing secrets in new format
|
|
801
|
-
const existing = await loadProjectSecretsBlob(projectName);
|
|
802
|
-
const merged = { ...existing, ...newSecrets };
|
|
803
|
-
await saveProjectSecretsBlob(projectName, merged);
|
|
804
|
-
}
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
// Migrate global secrets
|
|
808
|
-
const newGlobalSecrets: Record<string, string> = {};
|
|
809
|
-
|
|
810
|
-
for (const key of globalKeys) {
|
|
811
|
-
try {
|
|
812
|
-
const value = await getOldFormatSecret(key);
|
|
813
|
-
if (value) {
|
|
814
|
-
newGlobalSecrets[key] = value;
|
|
815
|
-
result.globalSecretsMigrated++;
|
|
816
|
-
|
|
817
|
-
if (deleteOld) {
|
|
818
|
-
await deleteOldFormatSecret(key);
|
|
819
|
-
result.oldEntriesDeleted++;
|
|
820
|
-
}
|
|
821
|
-
}
|
|
822
|
-
} catch (err) {
|
|
823
|
-
result.errors.push(`Failed to migrate global secret ${key}: ${err}`);
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// Save consolidated global blob if we found any secrets
|
|
828
|
-
if (Object.keys(newGlobalSecrets).length > 0) {
|
|
829
|
-
// Merge with any existing secrets in new format
|
|
830
|
-
const existing = await loadGlobalSecretsBlob();
|
|
831
|
-
const merged = { ...existing, ...newGlobalSecrets };
|
|
832
|
-
await saveGlobalSecretsBlob(merged);
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
return result;
|
|
836
|
-
}
|