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
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { getAllProjectNames, getSpacesDir, readProjectConfig } from './config.js';
|
|
4
|
-
import { logger } from '../utils/logger.js';
|
|
5
|
-
import { preloadAllSecrets } from '../utils/secrets.js';
|
|
6
|
-
import { SpacesError } from '../types/errors.js';
|
|
7
|
-
|
|
8
|
-
interface SecretRuntimeState {
|
|
9
|
-
ignoreKeychainAndSkipSecrets: boolean;
|
|
10
|
-
projectsWithSecrets: Set<string>;
|
|
11
|
-
legacyEntriesDetected: boolean;
|
|
12
|
-
legacyReminderConsumed: boolean;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface SecretMigrationInputs {
|
|
16
|
-
projectNames: string[];
|
|
17
|
-
projectSecretKeys: Record<string, string[]>;
|
|
18
|
-
globalSecretKeys: string[];
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function readHostSubdomains(): string[] {
|
|
22
|
-
const hostPath = join(getSpacesDir(), 'host.json');
|
|
23
|
-
if (!existsSync(hostPath)) {
|
|
24
|
-
return [];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
const parsed = JSON.parse(readFileSync(hostPath, 'utf-8')) as {
|
|
29
|
-
subdomain?: unknown;
|
|
30
|
-
subdomains?: unknown;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const found = new Set<string>();
|
|
34
|
-
if (typeof parsed.subdomain === 'string' && parsed.subdomain.length > 0) {
|
|
35
|
-
found.add(parsed.subdomain);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (Array.isArray(parsed.subdomains)) {
|
|
39
|
-
for (const candidate of parsed.subdomains) {
|
|
40
|
-
if (typeof candidate === 'string' && candidate.length > 0) {
|
|
41
|
-
found.add(candidate);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return [...found];
|
|
47
|
-
} catch {
|
|
48
|
-
return [];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function getSecretMigrationInputs(): SecretMigrationInputs {
|
|
53
|
-
const projectNames = getAllProjectNames();
|
|
54
|
-
const projectSecretKeys: Record<string, string[]> = {};
|
|
55
|
-
const globalSecretKeys = new Set<string>([
|
|
56
|
-
'GITSPACE_TOKEN',
|
|
57
|
-
'linear-api-key',
|
|
58
|
-
'relay:signingPrivateKey',
|
|
59
|
-
]);
|
|
60
|
-
|
|
61
|
-
for (const projectName of projectNames) {
|
|
62
|
-
globalSecretKeys.add(`linear-api-key-${projectName}`);
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const config = readProjectConfig(projectName);
|
|
66
|
-
const keys = [...new Set(config.bundleSecretKeys ?? [])];
|
|
67
|
-
if (keys.length > 0) {
|
|
68
|
-
projectSecretKeys[projectName] = keys;
|
|
69
|
-
}
|
|
70
|
-
} catch (error) {
|
|
71
|
-
logger.debug(
|
|
72
|
-
`[secrets] Failed reading project config for ${projectName}: ${error instanceof Error ? error.message : String(error)}`
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
for (const subdomain of readHostSubdomains()) {
|
|
78
|
-
globalSecretKeys.add(`TUNNEL_TOKEN_${subdomain}`);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
projectNames,
|
|
83
|
-
projectSecretKeys,
|
|
84
|
-
globalSecretKeys: [...globalSecretKeys],
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const state: SecretRuntimeState = {
|
|
89
|
-
ignoreKeychainAndSkipSecrets: false,
|
|
90
|
-
projectsWithSecrets: new Set<string>(),
|
|
91
|
-
legacyEntriesDetected: false,
|
|
92
|
-
legacyReminderConsumed: false,
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
export interface InitializeSecretRuntimeOptions {
|
|
96
|
-
ignoreKeychainAndSkipSecrets?: boolean;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export async function initializeSecretRuntime(
|
|
100
|
-
options: InitializeSecretRuntimeOptions = {}
|
|
101
|
-
): Promise<void> {
|
|
102
|
-
const ignore = options.ignoreKeychainAndSkipSecrets ?? false;
|
|
103
|
-
state.ignoreKeychainAndSkipSecrets = ignore;
|
|
104
|
-
state.legacyEntriesDetected = false;
|
|
105
|
-
state.legacyReminderConsumed = false;
|
|
106
|
-
|
|
107
|
-
const migrationInputs = getSecretMigrationInputs();
|
|
108
|
-
state.projectsWithSecrets = new Set(Object.keys(migrationInputs.projectSecretKeys));
|
|
109
|
-
|
|
110
|
-
if (ignore) {
|
|
111
|
-
if (state.projectsWithSecrets.size > 0) {
|
|
112
|
-
logger.warning(
|
|
113
|
-
'Ignoring keychain and skipping secret-dependent scripts (use with caution).'
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
const preloadResult = await preloadAllSecrets(migrationInputs.projectNames, {
|
|
121
|
-
projectLegacyKeys: migrationInputs.projectSecretKeys,
|
|
122
|
-
globalLegacyKeys: migrationInputs.globalSecretKeys,
|
|
123
|
-
});
|
|
124
|
-
state.legacyEntriesDetected = preloadResult.legacyEntriesDetected;
|
|
125
|
-
} catch (error) {
|
|
126
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
127
|
-
throw new SpacesError(
|
|
128
|
-
`Failed to preload keychain secrets at startup: ${message}\n\n` +
|
|
129
|
-
'Unlock keychain access and retry, or run with --ignore-keychain-and-skip-secrets to continue without secret-dependent scripts.',
|
|
130
|
-
'USER_ERROR',
|
|
131
|
-
1
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function consumeLegacyCleanupReminderForTui(): string | null {
|
|
137
|
-
if (!state.legacyEntriesDetected || state.legacyReminderConsumed) {
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
state.legacyReminderConsumed = true;
|
|
142
|
-
return 'Legacy keychain entries are still present. Run `gssh migrate cleanup-legacy` once you are confident the unified keychain storage is stable.';
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export function shouldSkipSecretDependentScripts(
|
|
146
|
-
projectName: string,
|
|
147
|
-
configuredSecretKeys?: string[]
|
|
148
|
-
): boolean {
|
|
149
|
-
if (!state.ignoreKeychainAndSkipSecrets) {
|
|
150
|
-
return false;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (configuredSecretKeys && configuredSecretKeys.length > 0) {
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (state.projectsWithSecrets.has(projectName)) {
|
|
158
|
-
return true;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
try {
|
|
162
|
-
const config = readProjectConfig(projectName);
|
|
163
|
-
return (config.bundleSecretKeys ?? []).length > 0;
|
|
164
|
-
} catch {
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
}
|
package/src/core/shell.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shell session management - spawns subshells for workspaces
|
|
3
|
-
* Uses tmux-lite for session persistence and management
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { spawn, spawnSync } from 'child_process'
|
|
7
|
-
import { logger } from '../utils/logger.js'
|
|
8
|
-
import { prepareWorkspaceForSession } from './workspace-lifecycle.js'
|
|
9
|
-
import {
|
|
10
|
-
createSession,
|
|
11
|
-
isNested,
|
|
12
|
-
} from '../lib/tmux-lite/cli.js'
|
|
13
|
-
import { buildWorkspaceSessionHooks } from '../session/workspace-shell-hooks.js'
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Print a message to terminal using echo (same mechanism as scripts)
|
|
17
|
-
*/
|
|
18
|
-
function printToTerminal(message: string): void {
|
|
19
|
-
spawnSync('echo', [message], { stdio: 'inherit' })
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Open a workspace in an interactive subshell
|
|
24
|
-
*
|
|
25
|
-
* Flow:
|
|
26
|
-
* 1. Determine if setup or select scripts should run
|
|
27
|
-
* 2. Run the appropriate scripts in the terminal
|
|
28
|
-
* 3. Spawn an interactive subshell in the workspace directory
|
|
29
|
-
* 4. User gets control of the shell with their environment ready
|
|
30
|
-
*
|
|
31
|
-
* @param sessionName - Custom name for the tmux-lite session (required for new sessions)
|
|
32
|
-
*/
|
|
33
|
-
export async function openWorkspaceShell(
|
|
34
|
-
workspacePath: string,
|
|
35
|
-
projectName: string,
|
|
36
|
-
repository: string,
|
|
37
|
-
noSetup: boolean = false,
|
|
38
|
-
sessionName?: string
|
|
39
|
-
): Promise<void> {
|
|
40
|
-
const workspaceId = workspacePath.split('/').pop() || 'workspace'
|
|
41
|
-
|
|
42
|
-
const prepareResult = await prepareWorkspaceForSession({
|
|
43
|
-
projectName,
|
|
44
|
-
workspacePath,
|
|
45
|
-
workspaceName: workspaceId,
|
|
46
|
-
repository,
|
|
47
|
-
noSetup,
|
|
48
|
-
interactiveScripts: true,
|
|
49
|
-
bundleMode: 'prompt-refresh',
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
if (!prepareResult.success) {
|
|
53
|
-
throw new Error(`Workspace scripts failed during ${prepareResult.phase} phase: ${prepareResult.error}`)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
printToTerminal('')
|
|
57
|
-
printToTerminal('💡 Press Ctrl+Esc to detach and return to GitSpace TUI')
|
|
58
|
-
printToTerminal('')
|
|
59
|
-
|
|
60
|
-
// Create or attach to tmux-lite session
|
|
61
|
-
await openTmuxLiteSession(workspacePath, projectName, workspaceId, sessionName)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Build a full session name from components
|
|
66
|
-
*/
|
|
67
|
-
function buildSessionName(projectName: string, workspaceId: string, sessionName: string): string {
|
|
68
|
-
return `${projectName}:${workspaceId}:${sessionName}`
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Open a tmux-lite session for the workspace
|
|
73
|
-
* Creates a new session or attaches to an existing one
|
|
74
|
-
* @param sessionName - Custom suffix for the session name
|
|
75
|
-
*/
|
|
76
|
-
async function openTmuxLiteSession(
|
|
77
|
-
workspacePath: string,
|
|
78
|
-
projectName: string,
|
|
79
|
-
workspaceId: string,
|
|
80
|
-
sessionName?: string
|
|
81
|
-
): Promise<void> {
|
|
82
|
-
// Check if we're already in a tmux-lite session
|
|
83
|
-
if (isNested()) {
|
|
84
|
-
logger.error('Already inside a tmux-lite session. Detach first with Ctrl+Esc.')
|
|
85
|
-
return
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const suffix = sessionName && sessionName.trim().length > 0
|
|
90
|
-
? sessionName
|
|
91
|
-
: `${Date.now()}`
|
|
92
|
-
const fullSessionName = buildSessionName(projectName, workspaceId, suffix)
|
|
93
|
-
|
|
94
|
-
logger.debug(`Creating tmux-lite session: ${fullSessionName}`)
|
|
95
|
-
|
|
96
|
-
// Create new session
|
|
97
|
-
const session = await createSession(fullSessionName, workspacePath, {
|
|
98
|
-
hooks: buildWorkspaceSessionHooks(projectName, workspaceId),
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
// Spawn the CLI attach command as a subprocess with inherited stdio
|
|
102
|
-
// This works better with TUI suspension than direct attach() call
|
|
103
|
-
const cliPath = new URL('../lib/tmux-lite/cli.ts', import.meta.url).pathname
|
|
104
|
-
const proc = spawn('bun', ['run', cliPath, 'attach', session.id, '-f'], {
|
|
105
|
-
stdio: 'inherit',
|
|
106
|
-
cwd: workspacePath,
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
await new Promise<void>((resolve, reject) => {
|
|
110
|
-
proc.on('exit', () => resolve())
|
|
111
|
-
proc.on('error', (err) => reject(err))
|
|
112
|
-
})
|
|
113
|
-
} catch (error) {
|
|
114
|
-
logger.error(`Failed to open tmux-lite session: ${(error as Error).message}`)
|
|
115
|
-
throw error
|
|
116
|
-
}
|
|
117
|
-
}
|
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Trusted relay management
|
|
3
|
-
*
|
|
4
|
-
* Manages the list of relays this machine trusts for remote connections.
|
|
5
|
-
* Relays are identified by their URL and Ed25519 public key.
|
|
6
|
-
*
|
|
7
|
-
* Storage: ~/.gitspace/.identity/trusted-relays.json
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
11
|
-
import { join } from "node:path";
|
|
12
|
-
import { sha256 } from "@noble/hashes/sha2.js";
|
|
13
|
-
import { getIdentityDir } from "./identity.js";
|
|
14
|
-
|
|
15
|
-
// ============================================================================
|
|
16
|
-
// Types
|
|
17
|
-
// ============================================================================
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* A trusted relay entry
|
|
21
|
-
*/
|
|
22
|
-
export interface TrustedRelay {
|
|
23
|
-
/** Relay WebSocket URL (e.g., wss://relay.example.com) */
|
|
24
|
-
url: string;
|
|
25
|
-
/** Ed25519 signing public key (base64) */
|
|
26
|
-
publicKey: string;
|
|
27
|
-
/** Human-readable fingerprint */
|
|
28
|
-
fingerprint: string;
|
|
29
|
-
/** Optional label from relay */
|
|
30
|
-
label?: string;
|
|
31
|
-
/** When trust was established (Unix ms) */
|
|
32
|
-
trustedAt: number;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Result of checking relay trust status
|
|
37
|
-
*/
|
|
38
|
-
export type RelayTrustStatus = "trusted" | "mismatch" | "unknown";
|
|
39
|
-
|
|
40
|
-
// ============================================================================
|
|
41
|
-
// Constants
|
|
42
|
-
// ============================================================================
|
|
43
|
-
|
|
44
|
-
const TRUSTED_RELAYS_FILE = "trusted-relays.json";
|
|
45
|
-
|
|
46
|
-
// ============================================================================
|
|
47
|
-
// Paths
|
|
48
|
-
// ============================================================================
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Get path to trusted relays file
|
|
52
|
-
*/
|
|
53
|
-
function getTrustedRelaysPath(): string {
|
|
54
|
-
return join(getIdentityDir(), TRUSTED_RELAYS_FILE);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// ============================================================================
|
|
58
|
-
// Fingerprint Computation
|
|
59
|
-
// ============================================================================
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Compute fingerprint from relay public key
|
|
63
|
-
*
|
|
64
|
-
* Format: "Kx4f:2nB9:mP3q:vR8s" (16 chars with colons)
|
|
65
|
-
*/
|
|
66
|
-
export function computeRelayFingerprint(publicKey: string): string {
|
|
67
|
-
const keyBytes = Buffer.from(publicKey, "base64");
|
|
68
|
-
const hash = sha256(keyBytes);
|
|
69
|
-
const b64url = Buffer.from(hash).toString("base64url").substring(0, 16);
|
|
70
|
-
return b64url.match(/.{1,4}/g)?.join(":") || b64url;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// ============================================================================
|
|
74
|
-
// URL Normalization
|
|
75
|
-
// ============================================================================
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Normalize a relay URL for consistent comparison
|
|
79
|
-
*
|
|
80
|
-
* Removes trailing slashes and normalizes protocol
|
|
81
|
-
*/
|
|
82
|
-
function normalizeUrl(url: string): string {
|
|
83
|
-
// Normalize the URL
|
|
84
|
-
let normalized = url.toLowerCase().trim();
|
|
85
|
-
|
|
86
|
-
// Remove trailing slashes
|
|
87
|
-
while (normalized.endsWith("/")) {
|
|
88
|
-
normalized = normalized.slice(0, -1);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Normalize ws:// to wss:// for non-localhost
|
|
92
|
-
if (normalized.startsWith("ws://") && !isLocalhostUrl(normalized)) {
|
|
93
|
-
normalized = "wss://" + normalized.slice(5);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return normalized;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/**
|
|
100
|
-
* Check if URL is localhost
|
|
101
|
-
*/
|
|
102
|
-
function isLocalhostUrl(url: string): boolean {
|
|
103
|
-
try {
|
|
104
|
-
const parsed = new URL(url);
|
|
105
|
-
const host = parsed.hostname.toLowerCase();
|
|
106
|
-
return host === "localhost" || host === "127.0.0.1" || host === "::1";
|
|
107
|
-
} catch {
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// ============================================================================
|
|
113
|
-
// Storage Operations
|
|
114
|
-
// ============================================================================
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Load trusted relays from disk
|
|
118
|
-
*/
|
|
119
|
-
export function getTrustedRelays(): TrustedRelay[] {
|
|
120
|
-
const path = getTrustedRelaysPath();
|
|
121
|
-
|
|
122
|
-
if (!existsSync(path)) {
|
|
123
|
-
return [];
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
const content = readFileSync(path, "utf-8");
|
|
128
|
-
return JSON.parse(content) as TrustedRelay[];
|
|
129
|
-
} catch {
|
|
130
|
-
console.warn("[trusted-relays] Failed to parse trusted relays file");
|
|
131
|
-
return [];
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Save trusted relays to disk
|
|
137
|
-
*/
|
|
138
|
-
function saveTrustedRelays(relays: TrustedRelay[]): void {
|
|
139
|
-
const identityDir = getIdentityDir();
|
|
140
|
-
|
|
141
|
-
// Create directory if needed (should already exist from identity setup)
|
|
142
|
-
if (!existsSync(identityDir)) {
|
|
143
|
-
const { mkdirSync } = require("node:fs");
|
|
144
|
-
mkdirSync(identityDir, { recursive: true, mode: 0o700 });
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
writeFileSync(
|
|
148
|
-
getTrustedRelaysPath(),
|
|
149
|
-
JSON.stringify(relays, null, 2),
|
|
150
|
-
{ encoding: "utf-8", mode: 0o600 }
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// ============================================================================
|
|
155
|
-
// Trust Management
|
|
156
|
-
// ============================================================================
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Add a relay to the trusted list
|
|
160
|
-
*
|
|
161
|
-
* If the URL already exists, updates the public key and label.
|
|
162
|
-
*
|
|
163
|
-
* @param url - Relay WebSocket URL
|
|
164
|
-
* @param publicKey - Ed25519 public key (base64)
|
|
165
|
-
* @param label - Optional relay label
|
|
166
|
-
* @returns The created/updated entry
|
|
167
|
-
*/
|
|
168
|
-
export function addTrustedRelay(
|
|
169
|
-
url: string,
|
|
170
|
-
publicKey: string,
|
|
171
|
-
label?: string
|
|
172
|
-
): TrustedRelay {
|
|
173
|
-
const relays = getTrustedRelays();
|
|
174
|
-
const normalizedUrl = normalizeUrl(url);
|
|
175
|
-
|
|
176
|
-
// Check if already exists by URL
|
|
177
|
-
const existing = relays.find((r) => normalizeUrl(r.url) === normalizedUrl);
|
|
178
|
-
|
|
179
|
-
if (existing) {
|
|
180
|
-
// Update existing entry
|
|
181
|
-
existing.publicKey = publicKey;
|
|
182
|
-
existing.fingerprint = computeRelayFingerprint(publicKey);
|
|
183
|
-
existing.label = label ?? existing.label;
|
|
184
|
-
existing.trustedAt = Date.now();
|
|
185
|
-
saveTrustedRelays(relays);
|
|
186
|
-
return existing;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Create new entry
|
|
190
|
-
const entry: TrustedRelay = {
|
|
191
|
-
url: normalizedUrl,
|
|
192
|
-
publicKey,
|
|
193
|
-
fingerprint: computeRelayFingerprint(publicKey),
|
|
194
|
-
label,
|
|
195
|
-
trustedAt: Date.now(),
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
relays.push(entry);
|
|
199
|
-
saveTrustedRelays(relays);
|
|
200
|
-
|
|
201
|
-
return entry;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
/**
|
|
205
|
-
* Remove a relay from the trusted list
|
|
206
|
-
*
|
|
207
|
-
* @param urlOrFingerprint - URL or fingerprint (or prefix) to match
|
|
208
|
-
* @returns The removed entry, or null if not found
|
|
209
|
-
*/
|
|
210
|
-
export function removeTrustedRelay(
|
|
211
|
-
urlOrFingerprint: string
|
|
212
|
-
): TrustedRelay | null {
|
|
213
|
-
const relays = getTrustedRelays();
|
|
214
|
-
const searchLower = urlOrFingerprint.toLowerCase();
|
|
215
|
-
|
|
216
|
-
const index = relays.findIndex((r) => {
|
|
217
|
-
const urlLower = normalizeUrl(r.url);
|
|
218
|
-
const fingerprintLower = r.fingerprint.toLowerCase();
|
|
219
|
-
const labelLower = r.label?.toLowerCase();
|
|
220
|
-
|
|
221
|
-
return (
|
|
222
|
-
urlLower === searchLower ||
|
|
223
|
-
urlLower.includes(searchLower) ||
|
|
224
|
-
fingerprintLower === searchLower ||
|
|
225
|
-
fingerprintLower.startsWith(searchLower) ||
|
|
226
|
-
labelLower === searchLower
|
|
227
|
-
);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
if (index === -1) {
|
|
231
|
-
return null;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const [removed] = relays.splice(index, 1);
|
|
235
|
-
saveTrustedRelays(relays);
|
|
236
|
-
|
|
237
|
-
return removed;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* Get a trusted relay by URL
|
|
242
|
-
*
|
|
243
|
-
* @param url - Relay WebSocket URL
|
|
244
|
-
* @returns The relay entry if found, null otherwise
|
|
245
|
-
*/
|
|
246
|
-
export function getTrustedRelay(url: string): TrustedRelay | null {
|
|
247
|
-
const relays = getTrustedRelays();
|
|
248
|
-
const normalizedUrl = normalizeUrl(url);
|
|
249
|
-
|
|
250
|
-
return relays.find((r) => normalizeUrl(r.url) === normalizedUrl) ?? null;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Find a trusted relay by fingerprint or label
|
|
255
|
-
*
|
|
256
|
-
* @param fingerprintOrLabel - Fingerprint (or prefix) or label to match
|
|
257
|
-
* @returns The relay entry if found, null otherwise
|
|
258
|
-
*/
|
|
259
|
-
export function findTrustedRelay(
|
|
260
|
-
fingerprintOrLabel: string
|
|
261
|
-
): TrustedRelay | null {
|
|
262
|
-
const relays = getTrustedRelays();
|
|
263
|
-
const searchLower = fingerprintOrLabel.toLowerCase();
|
|
264
|
-
|
|
265
|
-
return (
|
|
266
|
-
relays.find((r) => {
|
|
267
|
-
const fingerprintLower = r.fingerprint.toLowerCase();
|
|
268
|
-
const labelLower = r.label?.toLowerCase();
|
|
269
|
-
|
|
270
|
-
return (
|
|
271
|
-
fingerprintLower === searchLower ||
|
|
272
|
-
fingerprintLower.startsWith(searchLower) ||
|
|
273
|
-
labelLower === searchLower
|
|
274
|
-
);
|
|
275
|
-
}) ?? null
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Check if a relay is trusted
|
|
281
|
-
*
|
|
282
|
-
* @param url - Relay WebSocket URL
|
|
283
|
-
* @param publicKey - Ed25519 public key (base64) from relay
|
|
284
|
-
* @returns Trust status: 'trusted', 'mismatch', or 'unknown'
|
|
285
|
-
*/
|
|
286
|
-
export function isRelayTrusted(
|
|
287
|
-
url: string,
|
|
288
|
-
publicKey: string
|
|
289
|
-
): RelayTrustStatus {
|
|
290
|
-
// Localhost is always auto-trusted
|
|
291
|
-
if (isLocalhostUrl(url)) {
|
|
292
|
-
return "trusted";
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const trustedRelay = getTrustedRelay(url);
|
|
296
|
-
|
|
297
|
-
if (!trustedRelay) {
|
|
298
|
-
return "unknown";
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// Check if public key matches
|
|
302
|
-
if (trustedRelay.publicKey === publicKey) {
|
|
303
|
-
return "trusted";
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// URL known but key doesn't match - SECURITY WARNING
|
|
307
|
-
return "mismatch";
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Check if a URL is localhost (for auto-trust)
|
|
312
|
-
*/
|
|
313
|
-
export function isLocalhost(url: string): boolean {
|
|
314
|
-
return isLocalhostUrl(url);
|
|
315
|
-
}
|