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.
Files changed (318) hide show
  1. package/.claude/settings.local.json +21 -0
  2. package/.gitspace/bundle.json +50 -0
  3. package/.gitspace/select/01-status.sh +40 -0
  4. package/.gitspace/setup/01-install-deps.sh +12 -0
  5. package/.gitspace/setup/02-typecheck.sh +16 -0
  6. package/AGENTS.md +439 -0
  7. package/CLAUDE.md +1 -0
  8. package/LICENSE +25 -0
  9. package/README.md +607 -0
  10. package/bin/gssh +62 -0
  11. package/bun.lock +647 -0
  12. package/docs/CONNECTION.md +623 -0
  13. package/docs/GATEWAY-WORKER.md +319 -0
  14. package/docs/GETTING-STARTED.md +448 -0
  15. package/docs/GITSPACE-PLATFORM.md +1819 -0
  16. package/docs/INFRASTRUCTURE.md +1347 -0
  17. package/docs/PROTOCOL.md +619 -0
  18. package/docs/QUICKSTART.md +174 -0
  19. package/docs/RELAY.md +327 -0
  20. package/docs/REMOTE-DESIGN.md +549 -0
  21. package/docs/ROADMAP.md +564 -0
  22. package/docs/SITE_DOCS_FIGMA_MAKE.md +1167 -0
  23. package/docs/STACK-DESIGN.md +588 -0
  24. package/docs/UNIFIED_ARCHITECTURE.md +292 -0
  25. package/experiments/pty-benchmark.ts +148 -0
  26. package/experiments/pty-latency.ts +100 -0
  27. package/experiments/router/client.ts +199 -0
  28. package/experiments/router/protocol.ts +74 -0
  29. package/experiments/router/router.ts +217 -0
  30. package/experiments/router/session.ts +180 -0
  31. package/experiments/router/test.ts +133 -0
  32. package/experiments/socket-bandwidth.ts +77 -0
  33. package/homebrew/gitspace.rb +45 -0
  34. package/landing-page/ATTRIBUTIONS.md +3 -0
  35. package/landing-page/README.md +11 -0
  36. package/landing-page/bun.lock +801 -0
  37. package/landing-page/guidelines/Guidelines.md +61 -0
  38. package/landing-page/index.html +37 -0
  39. package/landing-page/package.json +90 -0
  40. package/landing-page/postcss.config.mjs +15 -0
  41. package/landing-page/public/_redirects +1 -0
  42. package/landing-page/public/favicon.png +0 -0
  43. package/landing-page/src/app/App.tsx +53 -0
  44. package/landing-page/src/app/components/figma/ImageWithFallback.tsx +27 -0
  45. package/landing-page/src/app/components/ui/accordion.tsx +66 -0
  46. package/landing-page/src/app/components/ui/alert-dialog.tsx +157 -0
  47. package/landing-page/src/app/components/ui/alert.tsx +66 -0
  48. package/landing-page/src/app/components/ui/aspect-ratio.tsx +11 -0
  49. package/landing-page/src/app/components/ui/avatar.tsx +53 -0
  50. package/landing-page/src/app/components/ui/badge.tsx +46 -0
  51. package/landing-page/src/app/components/ui/breadcrumb.tsx +109 -0
  52. package/landing-page/src/app/components/ui/button.tsx +57 -0
  53. package/landing-page/src/app/components/ui/calendar.tsx +75 -0
  54. package/landing-page/src/app/components/ui/card.tsx +92 -0
  55. package/landing-page/src/app/components/ui/carousel.tsx +241 -0
  56. package/landing-page/src/app/components/ui/chart.tsx +353 -0
  57. package/landing-page/src/app/components/ui/checkbox.tsx +32 -0
  58. package/landing-page/src/app/components/ui/collapsible.tsx +33 -0
  59. package/landing-page/src/app/components/ui/command.tsx +177 -0
  60. package/landing-page/src/app/components/ui/context-menu.tsx +252 -0
  61. package/landing-page/src/app/components/ui/dialog.tsx +135 -0
  62. package/landing-page/src/app/components/ui/drawer.tsx +132 -0
  63. package/landing-page/src/app/components/ui/dropdown-menu.tsx +257 -0
  64. package/landing-page/src/app/components/ui/form.tsx +168 -0
  65. package/landing-page/src/app/components/ui/hover-card.tsx +44 -0
  66. package/landing-page/src/app/components/ui/input-otp.tsx +77 -0
  67. package/landing-page/src/app/components/ui/input.tsx +21 -0
  68. package/landing-page/src/app/components/ui/label.tsx +24 -0
  69. package/landing-page/src/app/components/ui/menubar.tsx +276 -0
  70. package/landing-page/src/app/components/ui/navigation-menu.tsx +168 -0
  71. package/landing-page/src/app/components/ui/pagination.tsx +127 -0
  72. package/landing-page/src/app/components/ui/popover.tsx +48 -0
  73. package/landing-page/src/app/components/ui/progress.tsx +31 -0
  74. package/landing-page/src/app/components/ui/radio-group.tsx +45 -0
  75. package/landing-page/src/app/components/ui/resizable.tsx +56 -0
  76. package/landing-page/src/app/components/ui/scroll-area.tsx +58 -0
  77. package/landing-page/src/app/components/ui/select.tsx +189 -0
  78. package/landing-page/src/app/components/ui/separator.tsx +28 -0
  79. package/landing-page/src/app/components/ui/sheet.tsx +139 -0
  80. package/landing-page/src/app/components/ui/sidebar.tsx +726 -0
  81. package/landing-page/src/app/components/ui/skeleton.tsx +13 -0
  82. package/landing-page/src/app/components/ui/slider.tsx +63 -0
  83. package/landing-page/src/app/components/ui/sonner.tsx +25 -0
  84. package/landing-page/src/app/components/ui/switch.tsx +31 -0
  85. package/landing-page/src/app/components/ui/table.tsx +116 -0
  86. package/landing-page/src/app/components/ui/tabs.tsx +66 -0
  87. package/landing-page/src/app/components/ui/textarea.tsx +18 -0
  88. package/landing-page/src/app/components/ui/toggle-group.tsx +73 -0
  89. package/landing-page/src/app/components/ui/toggle.tsx +47 -0
  90. package/landing-page/src/app/components/ui/tooltip.tsx +61 -0
  91. package/landing-page/src/app/components/ui/use-mobile.ts +21 -0
  92. package/landing-page/src/app/components/ui/utils.ts +6 -0
  93. package/landing-page/src/components/docs/DocsContent.tsx +718 -0
  94. package/landing-page/src/components/docs/DocsSidebar.tsx +84 -0
  95. package/landing-page/src/components/landing/CTA.tsx +59 -0
  96. package/landing-page/src/components/landing/Comparison.tsx +84 -0
  97. package/landing-page/src/components/landing/FaultyTerminal.tsx +424 -0
  98. package/landing-page/src/components/landing/Features.tsx +201 -0
  99. package/landing-page/src/components/landing/Hero.tsx +142 -0
  100. package/landing-page/src/components/landing/Pricing.tsx +140 -0
  101. package/landing-page/src/components/landing/Roadmap.tsx +86 -0
  102. package/landing-page/src/components/landing/Security.tsx +81 -0
  103. package/landing-page/src/components/landing/TerminalWindow.tsx +27 -0
  104. package/landing-page/src/components/landing/UseCases.tsx +55 -0
  105. package/landing-page/src/components/landing/Workflow.tsx +101 -0
  106. package/landing-page/src/components/layout/DashboardNavbar.tsx +37 -0
  107. package/landing-page/src/components/layout/Footer.tsx +55 -0
  108. package/landing-page/src/components/layout/LandingNavbar.tsx +82 -0
  109. package/landing-page/src/components/ui/badge.tsx +39 -0
  110. package/landing-page/src/components/ui/breadcrumb.tsx +115 -0
  111. package/landing-page/src/components/ui/button.tsx +57 -0
  112. package/landing-page/src/components/ui/card.tsx +79 -0
  113. package/landing-page/src/components/ui/mock-terminal.tsx +68 -0
  114. package/landing-page/src/components/ui/separator.tsx +28 -0
  115. package/landing-page/src/lib/utils.ts +6 -0
  116. package/landing-page/src/main.tsx +10 -0
  117. package/landing-page/src/pages/Dashboard.tsx +133 -0
  118. package/landing-page/src/pages/DocsPage.tsx +79 -0
  119. package/landing-page/src/pages/LandingPage.tsx +31 -0
  120. package/landing-page/src/pages/TerminalView.tsx +106 -0
  121. package/landing-page/src/styles/fonts.css +0 -0
  122. package/landing-page/src/styles/index.css +3 -0
  123. package/landing-page/src/styles/tailwind.css +4 -0
  124. package/landing-page/src/styles/theme.css +181 -0
  125. package/landing-page/vite.config.ts +19 -0
  126. package/npm/darwin-arm64/bin/gssh +0 -0
  127. package/npm/darwin-arm64/package.json +20 -0
  128. package/package.json +74 -0
  129. package/scripts/build.ts +284 -0
  130. package/scripts/release.ts +140 -0
  131. package/src/__tests__/test-utils.ts +298 -0
  132. package/src/commands/__tests__/serve-messages.test.ts +190 -0
  133. package/src/commands/access.ts +298 -0
  134. package/src/commands/add.ts +452 -0
  135. package/src/commands/auth.ts +364 -0
  136. package/src/commands/connect.ts +287 -0
  137. package/src/commands/directory.ts +16 -0
  138. package/src/commands/host.ts +396 -0
  139. package/src/commands/identity.ts +184 -0
  140. package/src/commands/list.ts +200 -0
  141. package/src/commands/relay.ts +315 -0
  142. package/src/commands/remove.ts +241 -0
  143. package/src/commands/serve.ts +1493 -0
  144. package/src/commands/share.ts +456 -0
  145. package/src/commands/status.ts +125 -0
  146. package/src/commands/switch.ts +353 -0
  147. package/src/commands/tmux.ts +317 -0
  148. package/src/core/__tests__/access.test.ts +240 -0
  149. package/src/core/access.ts +277 -0
  150. package/src/core/bundle.ts +342 -0
  151. package/src/core/config.ts +510 -0
  152. package/src/core/git.ts +317 -0
  153. package/src/core/github.ts +151 -0
  154. package/src/core/identity.ts +631 -0
  155. package/src/core/linear.ts +225 -0
  156. package/src/core/shell.ts +161 -0
  157. package/src/core/trusted-relays.ts +315 -0
  158. package/src/index.ts +821 -0
  159. package/src/lib/remote-session/index.ts +7 -0
  160. package/src/lib/remote-session/protocol.ts +267 -0
  161. package/src/lib/remote-session/session-handler.ts +581 -0
  162. package/src/lib/remote-session/workspace-scanner.ts +167 -0
  163. package/src/lib/tmux-lite/README.md +81 -0
  164. package/src/lib/tmux-lite/cli.ts +796 -0
  165. package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +349 -0
  166. package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +291 -0
  167. package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +142 -0
  168. package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +339 -0
  169. package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +477 -0
  170. package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +499 -0
  171. package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +371 -0
  172. package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +573 -0
  173. package/src/lib/tmux-lite/crypto/access-control.test.ts +512 -0
  174. package/src/lib/tmux-lite/crypto/access-control.ts +320 -0
  175. package/src/lib/tmux-lite/crypto/frames.test.ts +262 -0
  176. package/src/lib/tmux-lite/crypto/frames.ts +141 -0
  177. package/src/lib/tmux-lite/crypto/handshake.ts +894 -0
  178. package/src/lib/tmux-lite/crypto/identity.test.ts +220 -0
  179. package/src/lib/tmux-lite/crypto/identity.ts +286 -0
  180. package/src/lib/tmux-lite/crypto/index.ts +51 -0
  181. package/src/lib/tmux-lite/crypto/invites.test.ts +381 -0
  182. package/src/lib/tmux-lite/crypto/invites.ts +215 -0
  183. package/src/lib/tmux-lite/crypto/keyexchange.ts +435 -0
  184. package/src/lib/tmux-lite/crypto/keys.test.ts +58 -0
  185. package/src/lib/tmux-lite/crypto/keys.ts +47 -0
  186. package/src/lib/tmux-lite/crypto/secretbox.test.ts +169 -0
  187. package/src/lib/tmux-lite/crypto/secretbox.ts +124 -0
  188. package/src/lib/tmux-lite/handshake-handler.ts +451 -0
  189. package/src/lib/tmux-lite/protocol.test.ts +307 -0
  190. package/src/lib/tmux-lite/protocol.ts +266 -0
  191. package/src/lib/tmux-lite/relay-client.ts +506 -0
  192. package/src/lib/tmux-lite/server.ts +1250 -0
  193. package/src/lib/tmux-lite/shell-integration.sh +37 -0
  194. package/src/lib/tmux-lite/terminal-queries.test.ts +54 -0
  195. package/src/lib/tmux-lite/terminal-queries.ts +49 -0
  196. package/src/relay/__tests__/e2e-flow.test.ts +1284 -0
  197. package/src/relay/__tests__/helpers/auth.ts +354 -0
  198. package/src/relay/__tests__/helpers/ports.ts +51 -0
  199. package/src/relay/__tests__/protocol-validation.test.ts +265 -0
  200. package/src/relay/authorization.ts +303 -0
  201. package/src/relay/embedded-assets.generated.d.ts +15 -0
  202. package/src/relay/identity.ts +352 -0
  203. package/src/relay/index.ts +57 -0
  204. package/src/relay/pipes.test.ts +427 -0
  205. package/src/relay/pipes.ts +195 -0
  206. package/src/relay/protocol.ts +804 -0
  207. package/src/relay/registries.test.ts +437 -0
  208. package/src/relay/registries.ts +593 -0
  209. package/src/relay/server.test.ts +1323 -0
  210. package/src/relay/server.ts +1092 -0
  211. package/src/relay/signing.ts +238 -0
  212. package/src/relay/types.ts +69 -0
  213. package/src/serve/client-session-manager.ts +622 -0
  214. package/src/serve/daemon.ts +497 -0
  215. package/src/serve/pty-session.ts +236 -0
  216. package/src/serve/types.ts +169 -0
  217. package/src/shared/components/Flow.tsx +453 -0
  218. package/src/shared/components/Flow.tui.tsx +343 -0
  219. package/src/shared/components/Flow.web.tsx +442 -0
  220. package/src/shared/components/Inbox.tsx +446 -0
  221. package/src/shared/components/Inbox.tui.tsx +262 -0
  222. package/src/shared/components/Inbox.web.tsx +329 -0
  223. package/src/shared/components/MachineList.tsx +187 -0
  224. package/src/shared/components/MachineList.tui.tsx +161 -0
  225. package/src/shared/components/MachineList.web.tsx +210 -0
  226. package/src/shared/components/ProjectList.tsx +176 -0
  227. package/src/shared/components/ProjectList.tui.tsx +109 -0
  228. package/src/shared/components/ProjectList.web.tsx +143 -0
  229. package/src/shared/components/SpacesBrowser.tsx +332 -0
  230. package/src/shared/components/SpacesBrowser.tui.tsx +163 -0
  231. package/src/shared/components/SpacesBrowser.web.tsx +221 -0
  232. package/src/shared/components/index.ts +103 -0
  233. package/src/shared/hooks/index.ts +16 -0
  234. package/src/shared/hooks/useNavigation.ts +226 -0
  235. package/src/shared/index.ts +122 -0
  236. package/src/shared/providers/LocalMachineProvider.ts +425 -0
  237. package/src/shared/providers/MachineProvider.ts +165 -0
  238. package/src/shared/providers/RemoteMachineProvider.ts +444 -0
  239. package/src/shared/providers/index.ts +26 -0
  240. package/src/shared/types.ts +145 -0
  241. package/src/tui/adapters.ts +120 -0
  242. package/src/tui/app.tsx +1816 -0
  243. package/src/tui/components/Terminal.tsx +580 -0
  244. package/src/tui/hooks/index.ts +35 -0
  245. package/src/tui/hooks/useAppState.ts +314 -0
  246. package/src/tui/hooks/useDaemonStatus.ts +174 -0
  247. package/src/tui/hooks/useInboxTUI.ts +113 -0
  248. package/src/tui/hooks/useRemoteMachines.ts +209 -0
  249. package/src/tui/index.ts +24 -0
  250. package/src/tui/state.ts +299 -0
  251. package/src/tui/terminal-bracketed-paste.test.ts +45 -0
  252. package/src/tui/terminal-bracketed-paste.ts +47 -0
  253. package/src/types/bundle.ts +112 -0
  254. package/src/types/config.ts +89 -0
  255. package/src/types/errors.ts +206 -0
  256. package/src/types/identity.ts +284 -0
  257. package/src/types/workspace-fuzzy.ts +49 -0
  258. package/src/types/workspace.ts +151 -0
  259. package/src/utils/bun-socket-writer.ts +80 -0
  260. package/src/utils/deps.ts +127 -0
  261. package/src/utils/fuzzy-match.ts +125 -0
  262. package/src/utils/logger.ts +127 -0
  263. package/src/utils/markdown.ts +254 -0
  264. package/src/utils/onboarding.ts +229 -0
  265. package/src/utils/prompts.ts +114 -0
  266. package/src/utils/run-commands.ts +112 -0
  267. package/src/utils/run-scripts.ts +142 -0
  268. package/src/utils/sanitize.ts +98 -0
  269. package/src/utils/secrets.ts +122 -0
  270. package/src/utils/shell-escape.ts +40 -0
  271. package/src/utils/utf8.ts +79 -0
  272. package/src/utils/workspace-state.ts +47 -0
  273. package/src/web/README.md +73 -0
  274. package/src/web/bun.lock +575 -0
  275. package/src/web/eslint.config.js +23 -0
  276. package/src/web/index.html +16 -0
  277. package/src/web/package.json +37 -0
  278. package/src/web/public/vite.svg +1 -0
  279. package/src/web/src/App.tsx +604 -0
  280. package/src/web/src/assets/react.svg +1 -0
  281. package/src/web/src/components/Terminal.tsx +207 -0
  282. package/src/web/src/hooks/useRelayConnection.ts +224 -0
  283. package/src/web/src/hooks/useTerminal.ts +699 -0
  284. package/src/web/src/index.css +55 -0
  285. package/src/web/src/lib/crypto/__tests__/web-terminal.test.ts +1158 -0
  286. package/src/web/src/lib/crypto/frames.ts +205 -0
  287. package/src/web/src/lib/crypto/handshake.ts +396 -0
  288. package/src/web/src/lib/crypto/identity.ts +128 -0
  289. package/src/web/src/lib/crypto/keyexchange.ts +246 -0
  290. package/src/web/src/lib/crypto/relay-signing.ts +53 -0
  291. package/src/web/src/lib/invite.ts +58 -0
  292. package/src/web/src/lib/storage/identity-store.ts +94 -0
  293. package/src/web/src/main.tsx +10 -0
  294. package/src/web/src/types/identity.ts +45 -0
  295. package/src/web/tsconfig.app.json +28 -0
  296. package/src/web/tsconfig.json +7 -0
  297. package/src/web/tsconfig.node.json +26 -0
  298. package/src/web/vite.config.ts +31 -0
  299. package/todo-security.md +92 -0
  300. package/tsconfig.json +23 -0
  301. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite +0 -0
  302. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-shm +0 -0
  303. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-wal +0 -0
  304. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite +0 -0
  305. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-shm +0 -0
  306. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-wal +0 -0
  307. package/worker/bun.lock +237 -0
  308. package/worker/package.json +22 -0
  309. package/worker/schema.sql +96 -0
  310. package/worker/src/handlers/auth.ts +451 -0
  311. package/worker/src/handlers/subdomains.ts +376 -0
  312. package/worker/src/handlers/user.ts +98 -0
  313. package/worker/src/index.ts +70 -0
  314. package/worker/src/middleware/auth.ts +152 -0
  315. package/worker/src/services/cloudflare.ts +609 -0
  316. package/worker/src/types.ts +96 -0
  317. package/worker/tsconfig.json +15 -0
  318. 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
+ }