gitspace 0.2.0-rc.1
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/.claude/settings.local.json +21 -0
- package/.gitspace/bundle.json +50 -0
- package/.gitspace/select/01-status.sh +40 -0
- package/.gitspace/setup/01-install-deps.sh +12 -0
- package/.gitspace/setup/02-typecheck.sh +16 -0
- package/AGENTS.md +439 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +25 -0
- package/README.md +607 -0
- package/bin/gssh +62 -0
- package/bun.lock +647 -0
- package/docs/CONNECTION.md +623 -0
- package/docs/GATEWAY-WORKER.md +319 -0
- package/docs/GETTING-STARTED.md +448 -0
- package/docs/GITSPACE-PLATFORM.md +1819 -0
- package/docs/INFRASTRUCTURE.md +1347 -0
- package/docs/PROTOCOL.md +619 -0
- package/docs/QUICKSTART.md +174 -0
- package/docs/RELAY.md +327 -0
- package/docs/REMOTE-DESIGN.md +549 -0
- package/docs/ROADMAP.md +564 -0
- package/docs/SITE_DOCS_FIGMA_MAKE.md +1167 -0
- package/docs/STACK-DESIGN.md +588 -0
- package/docs/UNIFIED_ARCHITECTURE.md +292 -0
- package/experiments/pty-benchmark.ts +148 -0
- package/experiments/pty-latency.ts +100 -0
- package/experiments/router/client.ts +199 -0
- package/experiments/router/protocol.ts +74 -0
- package/experiments/router/router.ts +217 -0
- package/experiments/router/session.ts +180 -0
- package/experiments/router/test.ts +133 -0
- package/experiments/socket-bandwidth.ts +77 -0
- package/homebrew/gitspace.rb +45 -0
- package/landing-page/ATTRIBUTIONS.md +3 -0
- package/landing-page/README.md +11 -0
- package/landing-page/bun.lock +801 -0
- package/landing-page/guidelines/Guidelines.md +61 -0
- package/landing-page/index.html +37 -0
- package/landing-page/package.json +90 -0
- package/landing-page/postcss.config.mjs +15 -0
- package/landing-page/public/_redirects +1 -0
- package/landing-page/public/favicon.png +0 -0
- package/landing-page/src/app/App.tsx +53 -0
- package/landing-page/src/app/components/figma/ImageWithFallback.tsx +27 -0
- package/landing-page/src/app/components/ui/accordion.tsx +66 -0
- package/landing-page/src/app/components/ui/alert-dialog.tsx +157 -0
- package/landing-page/src/app/components/ui/alert.tsx +66 -0
- package/landing-page/src/app/components/ui/aspect-ratio.tsx +11 -0
- package/landing-page/src/app/components/ui/avatar.tsx +53 -0
- package/landing-page/src/app/components/ui/badge.tsx +46 -0
- package/landing-page/src/app/components/ui/breadcrumb.tsx +109 -0
- package/landing-page/src/app/components/ui/button.tsx +57 -0
- package/landing-page/src/app/components/ui/calendar.tsx +75 -0
- package/landing-page/src/app/components/ui/card.tsx +92 -0
- package/landing-page/src/app/components/ui/carousel.tsx +241 -0
- package/landing-page/src/app/components/ui/chart.tsx +353 -0
- package/landing-page/src/app/components/ui/checkbox.tsx +32 -0
- package/landing-page/src/app/components/ui/collapsible.tsx +33 -0
- package/landing-page/src/app/components/ui/command.tsx +177 -0
- package/landing-page/src/app/components/ui/context-menu.tsx +252 -0
- package/landing-page/src/app/components/ui/dialog.tsx +135 -0
- package/landing-page/src/app/components/ui/drawer.tsx +132 -0
- package/landing-page/src/app/components/ui/dropdown-menu.tsx +257 -0
- package/landing-page/src/app/components/ui/form.tsx +168 -0
- package/landing-page/src/app/components/ui/hover-card.tsx +44 -0
- package/landing-page/src/app/components/ui/input-otp.tsx +77 -0
- package/landing-page/src/app/components/ui/input.tsx +21 -0
- package/landing-page/src/app/components/ui/label.tsx +24 -0
- package/landing-page/src/app/components/ui/menubar.tsx +276 -0
- package/landing-page/src/app/components/ui/navigation-menu.tsx +168 -0
- package/landing-page/src/app/components/ui/pagination.tsx +127 -0
- package/landing-page/src/app/components/ui/popover.tsx +48 -0
- package/landing-page/src/app/components/ui/progress.tsx +31 -0
- package/landing-page/src/app/components/ui/radio-group.tsx +45 -0
- package/landing-page/src/app/components/ui/resizable.tsx +56 -0
- package/landing-page/src/app/components/ui/scroll-area.tsx +58 -0
- package/landing-page/src/app/components/ui/select.tsx +189 -0
- package/landing-page/src/app/components/ui/separator.tsx +28 -0
- package/landing-page/src/app/components/ui/sheet.tsx +139 -0
- package/landing-page/src/app/components/ui/sidebar.tsx +726 -0
- package/landing-page/src/app/components/ui/skeleton.tsx +13 -0
- package/landing-page/src/app/components/ui/slider.tsx +63 -0
- package/landing-page/src/app/components/ui/sonner.tsx +25 -0
- package/landing-page/src/app/components/ui/switch.tsx +31 -0
- package/landing-page/src/app/components/ui/table.tsx +116 -0
- package/landing-page/src/app/components/ui/tabs.tsx +66 -0
- package/landing-page/src/app/components/ui/textarea.tsx +18 -0
- package/landing-page/src/app/components/ui/toggle-group.tsx +73 -0
- package/landing-page/src/app/components/ui/toggle.tsx +47 -0
- package/landing-page/src/app/components/ui/tooltip.tsx +61 -0
- package/landing-page/src/app/components/ui/use-mobile.ts +21 -0
- package/landing-page/src/app/components/ui/utils.ts +6 -0
- package/landing-page/src/components/docs/DocsContent.tsx +718 -0
- package/landing-page/src/components/docs/DocsSidebar.tsx +84 -0
- package/landing-page/src/components/landing/CTA.tsx +59 -0
- package/landing-page/src/components/landing/Comparison.tsx +84 -0
- package/landing-page/src/components/landing/FaultyTerminal.tsx +424 -0
- package/landing-page/src/components/landing/Features.tsx +201 -0
- package/landing-page/src/components/landing/Hero.tsx +142 -0
- package/landing-page/src/components/landing/Pricing.tsx +140 -0
- package/landing-page/src/components/landing/Roadmap.tsx +86 -0
- package/landing-page/src/components/landing/Security.tsx +81 -0
- package/landing-page/src/components/landing/TerminalWindow.tsx +27 -0
- package/landing-page/src/components/landing/UseCases.tsx +55 -0
- package/landing-page/src/components/landing/Workflow.tsx +101 -0
- package/landing-page/src/components/layout/DashboardNavbar.tsx +37 -0
- package/landing-page/src/components/layout/Footer.tsx +55 -0
- package/landing-page/src/components/layout/LandingNavbar.tsx +82 -0
- package/landing-page/src/components/ui/badge.tsx +39 -0
- package/landing-page/src/components/ui/breadcrumb.tsx +115 -0
- package/landing-page/src/components/ui/button.tsx +57 -0
- package/landing-page/src/components/ui/card.tsx +79 -0
- package/landing-page/src/components/ui/mock-terminal.tsx +68 -0
- package/landing-page/src/components/ui/separator.tsx +28 -0
- package/landing-page/src/lib/utils.ts +6 -0
- package/landing-page/src/main.tsx +10 -0
- package/landing-page/src/pages/Dashboard.tsx +133 -0
- package/landing-page/src/pages/DocsPage.tsx +79 -0
- package/landing-page/src/pages/LandingPage.tsx +31 -0
- package/landing-page/src/pages/TerminalView.tsx +106 -0
- package/landing-page/src/styles/fonts.css +0 -0
- package/landing-page/src/styles/index.css +3 -0
- package/landing-page/src/styles/tailwind.css +4 -0
- package/landing-page/src/styles/theme.css +181 -0
- package/landing-page/vite.config.ts +19 -0
- package/npm/darwin-arm64/bin/gssh +0 -0
- package/npm/darwin-arm64/package.json +20 -0
- package/package.json +74 -0
- package/scripts/build.ts +284 -0
- package/scripts/release.ts +140 -0
- package/src/__tests__/test-utils.ts +298 -0
- package/src/commands/__tests__/serve-messages.test.ts +190 -0
- package/src/commands/access.ts +298 -0
- package/src/commands/add.ts +452 -0
- package/src/commands/auth.ts +364 -0
- package/src/commands/connect.ts +287 -0
- package/src/commands/directory.ts +16 -0
- package/src/commands/host.ts +396 -0
- package/src/commands/identity.ts +184 -0
- package/src/commands/list.ts +200 -0
- package/src/commands/relay.ts +315 -0
- package/src/commands/remove.ts +241 -0
- package/src/commands/serve.ts +1493 -0
- package/src/commands/share.ts +456 -0
- package/src/commands/status.ts +125 -0
- package/src/commands/switch.ts +353 -0
- package/src/commands/tmux.ts +317 -0
- package/src/core/__tests__/access.test.ts +240 -0
- package/src/core/access.ts +277 -0
- package/src/core/bundle.ts +342 -0
- package/src/core/config.ts +510 -0
- package/src/core/git.ts +317 -0
- package/src/core/github.ts +151 -0
- package/src/core/identity.ts +631 -0
- package/src/core/linear.ts +225 -0
- package/src/core/shell.ts +161 -0
- package/src/core/trusted-relays.ts +315 -0
- package/src/index.ts +821 -0
- package/src/lib/remote-session/index.ts +7 -0
- package/src/lib/remote-session/protocol.ts +267 -0
- package/src/lib/remote-session/session-handler.ts +581 -0
- package/src/lib/remote-session/workspace-scanner.ts +167 -0
- package/src/lib/tmux-lite/README.md +81 -0
- package/src/lib/tmux-lite/cli.ts +796 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +349 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +291 -0
- package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +142 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +339 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +477 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +499 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +371 -0
- package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +573 -0
- package/src/lib/tmux-lite/crypto/access-control.test.ts +512 -0
- package/src/lib/tmux-lite/crypto/access-control.ts +320 -0
- package/src/lib/tmux-lite/crypto/frames.test.ts +262 -0
- package/src/lib/tmux-lite/crypto/frames.ts +141 -0
- package/src/lib/tmux-lite/crypto/handshake.ts +894 -0
- package/src/lib/tmux-lite/crypto/identity.test.ts +220 -0
- package/src/lib/tmux-lite/crypto/identity.ts +286 -0
- package/src/lib/tmux-lite/crypto/index.ts +51 -0
- package/src/lib/tmux-lite/crypto/invites.test.ts +381 -0
- package/src/lib/tmux-lite/crypto/invites.ts +215 -0
- package/src/lib/tmux-lite/crypto/keyexchange.ts +435 -0
- package/src/lib/tmux-lite/crypto/keys.test.ts +58 -0
- package/src/lib/tmux-lite/crypto/keys.ts +47 -0
- package/src/lib/tmux-lite/crypto/secretbox.test.ts +169 -0
- package/src/lib/tmux-lite/crypto/secretbox.ts +124 -0
- package/src/lib/tmux-lite/handshake-handler.ts +451 -0
- package/src/lib/tmux-lite/protocol.test.ts +307 -0
- package/src/lib/tmux-lite/protocol.ts +266 -0
- package/src/lib/tmux-lite/relay-client.ts +506 -0
- package/src/lib/tmux-lite/server.ts +1250 -0
- package/src/lib/tmux-lite/shell-integration.sh +37 -0
- package/src/lib/tmux-lite/terminal-queries.test.ts +54 -0
- package/src/lib/tmux-lite/terminal-queries.ts +49 -0
- package/src/relay/__tests__/e2e-flow.test.ts +1284 -0
- package/src/relay/__tests__/helpers/auth.ts +354 -0
- package/src/relay/__tests__/helpers/ports.ts +51 -0
- package/src/relay/__tests__/protocol-validation.test.ts +265 -0
- package/src/relay/authorization.ts +303 -0
- package/src/relay/embedded-assets.generated.d.ts +15 -0
- package/src/relay/identity.ts +352 -0
- package/src/relay/index.ts +57 -0
- package/src/relay/pipes.test.ts +427 -0
- package/src/relay/pipes.ts +195 -0
- package/src/relay/protocol.ts +804 -0
- package/src/relay/registries.test.ts +437 -0
- package/src/relay/registries.ts +593 -0
- package/src/relay/server.test.ts +1323 -0
- package/src/relay/server.ts +1092 -0
- package/src/relay/signing.ts +238 -0
- package/src/relay/types.ts +69 -0
- package/src/serve/client-session-manager.ts +622 -0
- package/src/serve/daemon.ts +497 -0
- package/src/serve/pty-session.ts +236 -0
- package/src/serve/types.ts +169 -0
- package/src/shared/components/Flow.tsx +453 -0
- package/src/shared/components/Flow.tui.tsx +343 -0
- package/src/shared/components/Flow.web.tsx +442 -0
- package/src/shared/components/Inbox.tsx +446 -0
- package/src/shared/components/Inbox.tui.tsx +262 -0
- package/src/shared/components/Inbox.web.tsx +329 -0
- package/src/shared/components/MachineList.tsx +187 -0
- package/src/shared/components/MachineList.tui.tsx +161 -0
- package/src/shared/components/MachineList.web.tsx +210 -0
- package/src/shared/components/ProjectList.tsx +176 -0
- package/src/shared/components/ProjectList.tui.tsx +109 -0
- package/src/shared/components/ProjectList.web.tsx +143 -0
- package/src/shared/components/SpacesBrowser.tsx +332 -0
- package/src/shared/components/SpacesBrowser.tui.tsx +163 -0
- package/src/shared/components/SpacesBrowser.web.tsx +221 -0
- package/src/shared/components/index.ts +103 -0
- package/src/shared/hooks/index.ts +16 -0
- package/src/shared/hooks/useNavigation.ts +226 -0
- package/src/shared/index.ts +122 -0
- package/src/shared/providers/LocalMachineProvider.ts +425 -0
- package/src/shared/providers/MachineProvider.ts +165 -0
- package/src/shared/providers/RemoteMachineProvider.ts +444 -0
- package/src/shared/providers/index.ts +26 -0
- package/src/shared/types.ts +145 -0
- package/src/tui/adapters.ts +120 -0
- package/src/tui/app.tsx +1816 -0
- package/src/tui/components/Terminal.tsx +580 -0
- package/src/tui/hooks/index.ts +35 -0
- package/src/tui/hooks/useAppState.ts +314 -0
- package/src/tui/hooks/useDaemonStatus.ts +174 -0
- package/src/tui/hooks/useInboxTUI.ts +113 -0
- package/src/tui/hooks/useRemoteMachines.ts +209 -0
- package/src/tui/index.ts +24 -0
- package/src/tui/state.ts +299 -0
- package/src/tui/terminal-bracketed-paste.test.ts +45 -0
- package/src/tui/terminal-bracketed-paste.ts +47 -0
- package/src/types/bundle.ts +112 -0
- package/src/types/config.ts +89 -0
- package/src/types/errors.ts +206 -0
- package/src/types/identity.ts +284 -0
- package/src/types/workspace-fuzzy.ts +49 -0
- package/src/types/workspace.ts +151 -0
- package/src/utils/bun-socket-writer.ts +80 -0
- package/src/utils/deps.ts +127 -0
- package/src/utils/fuzzy-match.ts +125 -0
- package/src/utils/logger.ts +127 -0
- package/src/utils/markdown.ts +254 -0
- package/src/utils/onboarding.ts +229 -0
- package/src/utils/prompts.ts +114 -0
- package/src/utils/run-commands.ts +112 -0
- package/src/utils/run-scripts.ts +142 -0
- package/src/utils/sanitize.ts +98 -0
- package/src/utils/secrets.ts +122 -0
- package/src/utils/shell-escape.ts +40 -0
- package/src/utils/utf8.ts +79 -0
- package/src/utils/workspace-state.ts +47 -0
- package/src/web/README.md +73 -0
- package/src/web/bun.lock +575 -0
- package/src/web/eslint.config.js +23 -0
- package/src/web/index.html +16 -0
- package/src/web/package.json +37 -0
- package/src/web/public/vite.svg +1 -0
- package/src/web/src/App.tsx +604 -0
- package/src/web/src/assets/react.svg +1 -0
- package/src/web/src/components/Terminal.tsx +207 -0
- package/src/web/src/hooks/useRelayConnection.ts +224 -0
- package/src/web/src/hooks/useTerminal.ts +699 -0
- package/src/web/src/index.css +55 -0
- package/src/web/src/lib/crypto/__tests__/web-terminal.test.ts +1158 -0
- package/src/web/src/lib/crypto/frames.ts +205 -0
- package/src/web/src/lib/crypto/handshake.ts +396 -0
- package/src/web/src/lib/crypto/identity.ts +128 -0
- package/src/web/src/lib/crypto/keyexchange.ts +246 -0
- package/src/web/src/lib/crypto/relay-signing.ts +53 -0
- package/src/web/src/lib/invite.ts +58 -0
- package/src/web/src/lib/storage/identity-store.ts +94 -0
- package/src/web/src/main.tsx +10 -0
- package/src/web/src/types/identity.ts +45 -0
- package/src/web/tsconfig.app.json +28 -0
- package/src/web/tsconfig.json +7 -0
- package/src/web/tsconfig.node.json +26 -0
- package/src/web/vite.config.ts +31 -0
- package/todo-security.md +92 -0
- package/tsconfig.json +23 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-shm +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-wal +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-shm +0 -0
- package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-wal +0 -0
- package/worker/bun.lock +237 -0
- package/worker/package.json +22 -0
- package/worker/schema.sql +96 -0
- package/worker/src/handlers/auth.ts +451 -0
- package/worker/src/handlers/subdomains.ts +376 -0
- package/worker/src/handlers/user.ts +98 -0
- package/worker/src/index.ts +70 -0
- package/worker/src/middleware/auth.ts +152 -0
- package/worker/src/services/cloudflare.ts +609 -0
- package/worker/src/types.ts +96 -0
- package/worker/tsconfig.json +15 -0
- package/worker/wrangler.toml +26 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// @ts-nocheck - Uses Bun-specific APIs (Bun.Terminal)
|
|
2
|
+
/**
|
|
3
|
+
* PTY session wrapper for remote terminal access
|
|
4
|
+
*
|
|
5
|
+
* Wraps Bun.Terminal to provide:
|
|
6
|
+
* - Encrypted frame I/O using session keys
|
|
7
|
+
* - Resize handling with SIGWINCH
|
|
8
|
+
* - Clean lifecycle management
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createFrame, openFrame } from "../lib/tmux-lite/crypto/frames.js";
|
|
12
|
+
import { decodeControl, type SessionCtrl } from "../lib/tmux-lite/protocol.js";
|
|
13
|
+
import type { SessionKeys } from "../types/identity.js";
|
|
14
|
+
import { STREAM_ID } from "./types.js";
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Types
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
/** PTY session options */
|
|
21
|
+
export interface PTYSessionOptions {
|
|
22
|
+
/** Shell to spawn (default: $SHELL or /bin/bash) */
|
|
23
|
+
shell?: string;
|
|
24
|
+
/** Working directory */
|
|
25
|
+
cwd?: string;
|
|
26
|
+
/** Environment variables */
|
|
27
|
+
env?: Record<string, string>;
|
|
28
|
+
/** Initial terminal columns */
|
|
29
|
+
cols?: number;
|
|
30
|
+
/** Initial terminal rows */
|
|
31
|
+
rows?: number;
|
|
32
|
+
/** Session encryption keys */
|
|
33
|
+
sessionKeys: SessionKeys;
|
|
34
|
+
/** Callback for encrypted output data */
|
|
35
|
+
onData: (encrypted: Buffer) => void;
|
|
36
|
+
/** Callback when PTY exits */
|
|
37
|
+
onClose: (exitCode: number) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// PTYSession Class
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Manages a PTY session with encrypted I/O
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* const pty = new PTYSession({
|
|
50
|
+
* sessionKeys,
|
|
51
|
+
* onData: (encrypted) => relay.send(connectionId, encrypted),
|
|
52
|
+
* onClose: (code) => console.log(`Exit: ${code}`),
|
|
53
|
+
* });
|
|
54
|
+
*
|
|
55
|
+
* // Write encrypted input from client
|
|
56
|
+
* pty.write(encryptedFrame);
|
|
57
|
+
*
|
|
58
|
+
* // Handle resize
|
|
59
|
+
* pty.resize(120, 40);
|
|
60
|
+
*
|
|
61
|
+
* // Clean up
|
|
62
|
+
* pty.close();
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export class PTYSession {
|
|
66
|
+
private terminal: ReturnType<typeof Bun.Terminal> | null = null;
|
|
67
|
+
private proc: ReturnType<typeof Bun.spawn> | null = null;
|
|
68
|
+
private sendKey: Uint8Array;
|
|
69
|
+
private receiveKey: Uint8Array;
|
|
70
|
+
private onData: (encrypted: Buffer) => void;
|
|
71
|
+
private onClose: (exitCode: number) => void;
|
|
72
|
+
private closed = false;
|
|
73
|
+
|
|
74
|
+
constructor(options: PTYSessionOptions) {
|
|
75
|
+
this.sendKey = options.sessionKeys.sendKey;
|
|
76
|
+
this.receiveKey = options.sessionKeys.receiveKey;
|
|
77
|
+
this.onData = options.onData;
|
|
78
|
+
this.onClose = options.onClose;
|
|
79
|
+
|
|
80
|
+
const shell = options.shell ?? process.env.SHELL ?? "/bin/bash";
|
|
81
|
+
const cwd = options.cwd ?? process.env.HOME ?? "/";
|
|
82
|
+
const cols = options.cols ?? 80;
|
|
83
|
+
const rows = options.rows ?? 24;
|
|
84
|
+
|
|
85
|
+
// Build environment
|
|
86
|
+
const env: Record<string, string> = {
|
|
87
|
+
...process.env as Record<string, string>,
|
|
88
|
+
...options.env,
|
|
89
|
+
TERM: "xterm-256color",
|
|
90
|
+
SPACES_REMOTE: "true",
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Create PTY terminal
|
|
94
|
+
this.terminal = new Bun.Terminal({
|
|
95
|
+
cols,
|
|
96
|
+
rows,
|
|
97
|
+
data: (_term: unknown, data: Uint8Array) => {
|
|
98
|
+
if (this.closed) return;
|
|
99
|
+
|
|
100
|
+
// Encrypt and send output
|
|
101
|
+
const frame = createFrame(STREAM_ID.DATA, data, this.sendKey);
|
|
102
|
+
this.onData(frame);
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Spawn shell process
|
|
107
|
+
this.proc = Bun.spawn([shell], {
|
|
108
|
+
terminal: this.terminal,
|
|
109
|
+
cwd,
|
|
110
|
+
env,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Handle process exit
|
|
114
|
+
this.proc.exited.then((code) => {
|
|
115
|
+
if (!this.closed) {
|
|
116
|
+
this.closed = true;
|
|
117
|
+
this.onClose(code);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get the process ID
|
|
124
|
+
*/
|
|
125
|
+
get pid(): number | undefined {
|
|
126
|
+
return this.proc?.pid;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if session is closed
|
|
131
|
+
*/
|
|
132
|
+
get isClosed(): boolean {
|
|
133
|
+
return this.closed;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Write encrypted input data to PTY
|
|
138
|
+
*
|
|
139
|
+
* Decrypts the frame and writes plaintext to stdin.
|
|
140
|
+
*
|
|
141
|
+
* @param encryptedFrame - Encrypted frame from client
|
|
142
|
+
* @returns True if write succeeded, false on decryption failure
|
|
143
|
+
*/
|
|
144
|
+
write(encryptedFrame: Buffer | Uint8Array): boolean {
|
|
145
|
+
if (this.closed || !this.terminal) return false;
|
|
146
|
+
|
|
147
|
+
const result = openFrame(Buffer.from(encryptedFrame), this.receiveKey);
|
|
148
|
+
if (!result) {
|
|
149
|
+
console.warn("[pty-session] Failed to decrypt input frame");
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check stream ID
|
|
154
|
+
if (result.streamId === STREAM_ID.CONTROL) {
|
|
155
|
+
// Handle control message
|
|
156
|
+
this.handleControlMessage(result.data);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Write data to PTY stdin
|
|
161
|
+
this.terminal.write(result.data);
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Handle control messages (resize, etc.)
|
|
167
|
+
* Uses tmux-lite SessionCtrl format for consistency
|
|
168
|
+
*/
|
|
169
|
+
private handleControlMessage(data: Buffer): void {
|
|
170
|
+
try {
|
|
171
|
+
const msg = decodeControl(data) as SessionCtrl;
|
|
172
|
+
|
|
173
|
+
switch (msg.type) {
|
|
174
|
+
case "resize":
|
|
175
|
+
this.resize(msg.cols, msg.rows);
|
|
176
|
+
break;
|
|
177
|
+
case "detach":
|
|
178
|
+
// Detach is handled at a higher level, ignore here
|
|
179
|
+
break;
|
|
180
|
+
case "attach-init":
|
|
181
|
+
// attach-init doesn't apply to PTYSession (already started)
|
|
182
|
+
break;
|
|
183
|
+
default:
|
|
184
|
+
console.warn("[pty-session] Unknown control message:", msg);
|
|
185
|
+
}
|
|
186
|
+
} catch (e) {
|
|
187
|
+
console.warn("[pty-session] Invalid control message:", e);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Resize the PTY
|
|
193
|
+
*
|
|
194
|
+
* @param cols - Number of columns
|
|
195
|
+
* @param rows - Number of rows
|
|
196
|
+
*/
|
|
197
|
+
resize(cols: number, rows: number): void {
|
|
198
|
+
if (this.closed || !this.terminal || !this.proc) return;
|
|
199
|
+
|
|
200
|
+
try {
|
|
201
|
+
this.terminal.resize(cols, rows);
|
|
202
|
+
// Send SIGWINCH to process group so child processes get it
|
|
203
|
+
try {
|
|
204
|
+
process.kill(-this.proc.pid, "SIGWINCH");
|
|
205
|
+
} catch {
|
|
206
|
+
// Fallback to direct signal if process group fails
|
|
207
|
+
try {
|
|
208
|
+
process.kill(this.proc.pid, "SIGWINCH");
|
|
209
|
+
} catch {
|
|
210
|
+
// Process may have exited
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch (e) {
|
|
214
|
+
console.warn("[pty-session] Resize failed:", e);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Close the PTY session
|
|
220
|
+
*
|
|
221
|
+
* Kills the process and cleans up resources.
|
|
222
|
+
*/
|
|
223
|
+
close(): void {
|
|
224
|
+
if (this.closed) return;
|
|
225
|
+
this.closed = true;
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
this.proc?.kill();
|
|
229
|
+
} catch {
|
|
230
|
+
// Already exited
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
this.terminal = null;
|
|
234
|
+
this.proc = null;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the gssh serve command
|
|
3
|
+
*
|
|
4
|
+
* Defines configuration, session state, and events for the machine-side daemon.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PTYSession } from "./pty-session.js";
|
|
8
|
+
import type { Identity, SessionKeys, AccessType } from "../types/identity.js";
|
|
9
|
+
import type { AccessControlList } from "../lib/tmux-lite/crypto/access-control.js";
|
|
10
|
+
import { FrameType } from "../lib/tmux-lite/protocol.js";
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// Permission Helpers
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check if an access type grants write permission (terminal input)
|
|
18
|
+
*
|
|
19
|
+
* Only 'full' access allows writing to the terminal.
|
|
20
|
+
* 'session-invite' is read-only.
|
|
21
|
+
*/
|
|
22
|
+
export function canWrite(accessType: AccessType | undefined): boolean {
|
|
23
|
+
return accessType === 'full';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Check if an access type grants management permission
|
|
28
|
+
*
|
|
29
|
+
* Management includes: create/kill sessions, delete workspaces, etc.
|
|
30
|
+
* Only 'full' access allows management operations.
|
|
31
|
+
*/
|
|
32
|
+
export function canManage(accessType: AccessType | undefined): boolean {
|
|
33
|
+
return accessType === 'full';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Check if a client can attach to a specific session
|
|
38
|
+
*
|
|
39
|
+
* - 'full' access can attach to any session
|
|
40
|
+
* - 'session-invite' can only attach to the specific session they were invited to
|
|
41
|
+
*/
|
|
42
|
+
export function canAttachSession(
|
|
43
|
+
accessType: AccessType | undefined,
|
|
44
|
+
grantedSessionId: string | undefined,
|
|
45
|
+
targetSessionId: string
|
|
46
|
+
): boolean {
|
|
47
|
+
if (accessType === 'full') return true;
|
|
48
|
+
if (accessType === 'session-invite') {
|
|
49
|
+
return grantedSessionId === targetSessionId;
|
|
50
|
+
}
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// ============================================================================
|
|
55
|
+
// Configuration Types
|
|
56
|
+
// ============================================================================
|
|
57
|
+
|
|
58
|
+
/** Configuration for the serve command */
|
|
59
|
+
export interface ServeOptions {
|
|
60
|
+
/** Relay WebSocket URL */
|
|
61
|
+
relay: string;
|
|
62
|
+
/** Machine identity for authentication */
|
|
63
|
+
identity: Identity;
|
|
64
|
+
/** Access control list for authorized clients */
|
|
65
|
+
accessList: AccessControlList;
|
|
66
|
+
/** Shell to spawn (default: $SHELL or /bin/bash) */
|
|
67
|
+
shell?: string;
|
|
68
|
+
/** Extra environment variables for PTY sessions */
|
|
69
|
+
env?: Record<string, string>;
|
|
70
|
+
/** Handshake timeout in milliseconds (default: 30000) */
|
|
71
|
+
handshakeTimeoutMs?: number;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ============================================================================
|
|
75
|
+
// Session Types
|
|
76
|
+
// ============================================================================
|
|
77
|
+
|
|
78
|
+
/** State of a client session */
|
|
79
|
+
export type ClientSessionState = "handshaking" | "browsing" | "attached" | "closed";
|
|
80
|
+
|
|
81
|
+
/** Client session data */
|
|
82
|
+
export interface ClientSession {
|
|
83
|
+
/** Unique connection ID from relay */
|
|
84
|
+
connectionId: string;
|
|
85
|
+
/** Current session state */
|
|
86
|
+
state: ClientSessionState;
|
|
87
|
+
/** When handshake started (Unix ms) */
|
|
88
|
+
handshakeStartedAt: number;
|
|
89
|
+
/** PTY session (created after attach_session) - legacy, use tmuxSocket instead */
|
|
90
|
+
ptySession?: PTYSession;
|
|
91
|
+
/** tmux-lite session socket connection */
|
|
92
|
+
tmuxSocket?: Awaited<ReturnType<typeof Bun.connect>>;
|
|
93
|
+
/** Buffered writer for tmux-lite socket (Bun sockets can partially write under backpressure) */
|
|
94
|
+
tmuxSocketWriter?: {
|
|
95
|
+
write(data: Buffer | Uint8Array | ArrayBuffer): void;
|
|
96
|
+
flush(): void;
|
|
97
|
+
clear(): void;
|
|
98
|
+
};
|
|
99
|
+
/** Path to tmux-lite session socket */
|
|
100
|
+
sessionSocketPath?: string;
|
|
101
|
+
/** Session encryption keys */
|
|
102
|
+
sessionKeys?: SessionKeys;
|
|
103
|
+
/** Granted access type */
|
|
104
|
+
accessType?: AccessType;
|
|
105
|
+
/** Session ID for session-invite access */
|
|
106
|
+
sessionId?: string;
|
|
107
|
+
/** Peer's identity ID */
|
|
108
|
+
peerIdentityId?: string;
|
|
109
|
+
/** Attached tmux-lite session ID (when state === "attached") */
|
|
110
|
+
attachedSessionId?: string;
|
|
111
|
+
/** True if waiting for initial resize before sending attach-init */
|
|
112
|
+
waitingForResize?: boolean;
|
|
113
|
+
/** Buffer for incomplete frames from tmux-lite socket */
|
|
114
|
+
frameBuffer?: Buffer;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Stream IDs (for encrypted relay framing)
|
|
119
|
+
// ============================================================================
|
|
120
|
+
//
|
|
121
|
+
// Stream IDs align with FrameType from tmux-lite protocol:
|
|
122
|
+
// - DATA (0) = FrameType.PTY: raw terminal bytes
|
|
123
|
+
// - CONTROL (1) = FrameType.CONTROL: JSON control messages (resize, detach, etc.)
|
|
124
|
+
//
|
|
125
|
+
// See src/lib/tmux-lite/protocol.ts for SessionCtrl/SessionEvent types.
|
|
126
|
+
|
|
127
|
+
/** Stream IDs for frame routing - aligned with FrameType */
|
|
128
|
+
export const STREAM_ID = {
|
|
129
|
+
/** Terminal data stream (same as FrameType.PTY) */
|
|
130
|
+
DATA: FrameType.PTY,
|
|
131
|
+
/** Control messages (same as FrameType.CONTROL) */
|
|
132
|
+
CONTROL: FrameType.CONTROL,
|
|
133
|
+
} as const;
|
|
134
|
+
|
|
135
|
+
// ============================================================================
|
|
136
|
+
// Event Types
|
|
137
|
+
// ============================================================================
|
|
138
|
+
|
|
139
|
+
/** Events emitted by the serve daemon */
|
|
140
|
+
export type ServeEvent =
|
|
141
|
+
| { type: "client_connected"; connectionId: string }
|
|
142
|
+
| { type: "client_authenticated"; connectionId: string; identityId: string; accessType: AccessType; sessionId?: string }
|
|
143
|
+
| { type: "client_disconnected"; connectionId: string; reason: string }
|
|
144
|
+
| { type: "relay_connected" }
|
|
145
|
+
| { type: "relay_disconnected"; code: number; reason: string }
|
|
146
|
+
| { type: "relay_reconnecting"; attempt: number }
|
|
147
|
+
| { type: "error"; connectionId?: string; error: Error };
|
|
148
|
+
|
|
149
|
+
/** Event handler for serve events */
|
|
150
|
+
export type ServeEventHandler = (event: ServeEvent) => void;
|
|
151
|
+
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// Relay Protocol Types
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
/** Envelope for messages from relay (includes routing info) */
|
|
157
|
+
export interface RelayEnvelope {
|
|
158
|
+
/** Connection ID for routing */
|
|
159
|
+
connectionId: string;
|
|
160
|
+
/** Raw message data */
|
|
161
|
+
data: Uint8Array;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Handshake message envelope */
|
|
165
|
+
export interface HandshakeMessageEnvelope {
|
|
166
|
+
type: "handshake";
|
|
167
|
+
phase: "client_hello" | "server_hello" | "client_auth" | "server_auth";
|
|
168
|
+
data: unknown;
|
|
169
|
+
}
|