gitspace 0.2.0-rc.2 → 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.
Files changed (316) hide show
  1. package/README.md +68 -38
  2. package/package.json +36 -25
  3. package/.claude/settings.local.json +0 -21
  4. package/.gitspace/bundle.json +0 -50
  5. package/.gitspace/select/01-status.sh +0 -40
  6. package/.gitspace/setup/01-install-deps.sh +0 -12
  7. package/.gitspace/setup/02-typecheck.sh +0 -16
  8. package/AGENTS.md +0 -439
  9. package/CLAUDE.md +0 -1
  10. package/bun.lock +0 -647
  11. package/docs/CONNECTION.md +0 -623
  12. package/docs/GATEWAY-WORKER.md +0 -319
  13. package/docs/GETTING-STARTED.md +0 -448
  14. package/docs/GITSPACE-PLATFORM.md +0 -1819
  15. package/docs/INFRASTRUCTURE.md +0 -1347
  16. package/docs/PROTOCOL.md +0 -619
  17. package/docs/QUICKSTART.md +0 -174
  18. package/docs/RELAY.md +0 -327
  19. package/docs/REMOTE-DESIGN.md +0 -549
  20. package/docs/ROADMAP.md +0 -564
  21. package/docs/SITE_DOCS_FIGMA_MAKE.md +0 -1167
  22. package/docs/STACK-DESIGN.md +0 -588
  23. package/docs/UNIFIED_ARCHITECTURE.md +0 -292
  24. package/experiments/pty-benchmark.ts +0 -148
  25. package/experiments/pty-latency.ts +0 -100
  26. package/experiments/router/client.ts +0 -199
  27. package/experiments/router/protocol.ts +0 -74
  28. package/experiments/router/router.ts +0 -217
  29. package/experiments/router/session.ts +0 -180
  30. package/experiments/router/test.ts +0 -133
  31. package/experiments/socket-bandwidth.ts +0 -77
  32. package/homebrew/gitspace.rb +0 -45
  33. package/landing-page/ATTRIBUTIONS.md +0 -3
  34. package/landing-page/README.md +0 -11
  35. package/landing-page/bun.lock +0 -801
  36. package/landing-page/guidelines/Guidelines.md +0 -61
  37. package/landing-page/index.html +0 -37
  38. package/landing-page/package.json +0 -90
  39. package/landing-page/postcss.config.mjs +0 -15
  40. package/landing-page/public/_redirects +0 -1
  41. package/landing-page/public/favicon.png +0 -0
  42. package/landing-page/src/app/App.tsx +0 -53
  43. package/landing-page/src/app/components/figma/ImageWithFallback.tsx +0 -27
  44. package/landing-page/src/app/components/ui/accordion.tsx +0 -66
  45. package/landing-page/src/app/components/ui/alert-dialog.tsx +0 -157
  46. package/landing-page/src/app/components/ui/alert.tsx +0 -66
  47. package/landing-page/src/app/components/ui/aspect-ratio.tsx +0 -11
  48. package/landing-page/src/app/components/ui/avatar.tsx +0 -53
  49. package/landing-page/src/app/components/ui/badge.tsx +0 -46
  50. package/landing-page/src/app/components/ui/breadcrumb.tsx +0 -109
  51. package/landing-page/src/app/components/ui/button.tsx +0 -57
  52. package/landing-page/src/app/components/ui/calendar.tsx +0 -75
  53. package/landing-page/src/app/components/ui/card.tsx +0 -92
  54. package/landing-page/src/app/components/ui/carousel.tsx +0 -241
  55. package/landing-page/src/app/components/ui/chart.tsx +0 -353
  56. package/landing-page/src/app/components/ui/checkbox.tsx +0 -32
  57. package/landing-page/src/app/components/ui/collapsible.tsx +0 -33
  58. package/landing-page/src/app/components/ui/command.tsx +0 -177
  59. package/landing-page/src/app/components/ui/context-menu.tsx +0 -252
  60. package/landing-page/src/app/components/ui/dialog.tsx +0 -135
  61. package/landing-page/src/app/components/ui/drawer.tsx +0 -132
  62. package/landing-page/src/app/components/ui/dropdown-menu.tsx +0 -257
  63. package/landing-page/src/app/components/ui/form.tsx +0 -168
  64. package/landing-page/src/app/components/ui/hover-card.tsx +0 -44
  65. package/landing-page/src/app/components/ui/input-otp.tsx +0 -77
  66. package/landing-page/src/app/components/ui/input.tsx +0 -21
  67. package/landing-page/src/app/components/ui/label.tsx +0 -24
  68. package/landing-page/src/app/components/ui/menubar.tsx +0 -276
  69. package/landing-page/src/app/components/ui/navigation-menu.tsx +0 -168
  70. package/landing-page/src/app/components/ui/pagination.tsx +0 -127
  71. package/landing-page/src/app/components/ui/popover.tsx +0 -48
  72. package/landing-page/src/app/components/ui/progress.tsx +0 -31
  73. package/landing-page/src/app/components/ui/radio-group.tsx +0 -45
  74. package/landing-page/src/app/components/ui/resizable.tsx +0 -56
  75. package/landing-page/src/app/components/ui/scroll-area.tsx +0 -58
  76. package/landing-page/src/app/components/ui/select.tsx +0 -189
  77. package/landing-page/src/app/components/ui/separator.tsx +0 -28
  78. package/landing-page/src/app/components/ui/sheet.tsx +0 -139
  79. package/landing-page/src/app/components/ui/sidebar.tsx +0 -726
  80. package/landing-page/src/app/components/ui/skeleton.tsx +0 -13
  81. package/landing-page/src/app/components/ui/slider.tsx +0 -63
  82. package/landing-page/src/app/components/ui/sonner.tsx +0 -25
  83. package/landing-page/src/app/components/ui/switch.tsx +0 -31
  84. package/landing-page/src/app/components/ui/table.tsx +0 -116
  85. package/landing-page/src/app/components/ui/tabs.tsx +0 -66
  86. package/landing-page/src/app/components/ui/textarea.tsx +0 -18
  87. package/landing-page/src/app/components/ui/toggle-group.tsx +0 -73
  88. package/landing-page/src/app/components/ui/toggle.tsx +0 -47
  89. package/landing-page/src/app/components/ui/tooltip.tsx +0 -61
  90. package/landing-page/src/app/components/ui/use-mobile.ts +0 -21
  91. package/landing-page/src/app/components/ui/utils.ts +0 -6
  92. package/landing-page/src/components/docs/DocsContent.tsx +0 -718
  93. package/landing-page/src/components/docs/DocsSidebar.tsx +0 -84
  94. package/landing-page/src/components/landing/CTA.tsx +0 -59
  95. package/landing-page/src/components/landing/Comparison.tsx +0 -84
  96. package/landing-page/src/components/landing/FaultyTerminal.tsx +0 -424
  97. package/landing-page/src/components/landing/Features.tsx +0 -201
  98. package/landing-page/src/components/landing/Hero.tsx +0 -142
  99. package/landing-page/src/components/landing/Pricing.tsx +0 -140
  100. package/landing-page/src/components/landing/Roadmap.tsx +0 -86
  101. package/landing-page/src/components/landing/Security.tsx +0 -81
  102. package/landing-page/src/components/landing/TerminalWindow.tsx +0 -27
  103. package/landing-page/src/components/landing/UseCases.tsx +0 -55
  104. package/landing-page/src/components/landing/Workflow.tsx +0 -101
  105. package/landing-page/src/components/layout/DashboardNavbar.tsx +0 -37
  106. package/landing-page/src/components/layout/Footer.tsx +0 -55
  107. package/landing-page/src/components/layout/LandingNavbar.tsx +0 -82
  108. package/landing-page/src/components/ui/badge.tsx +0 -39
  109. package/landing-page/src/components/ui/breadcrumb.tsx +0 -115
  110. package/landing-page/src/components/ui/button.tsx +0 -57
  111. package/landing-page/src/components/ui/card.tsx +0 -79
  112. package/landing-page/src/components/ui/mock-terminal.tsx +0 -68
  113. package/landing-page/src/components/ui/separator.tsx +0 -28
  114. package/landing-page/src/lib/utils.ts +0 -6
  115. package/landing-page/src/main.tsx +0 -10
  116. package/landing-page/src/pages/Dashboard.tsx +0 -133
  117. package/landing-page/src/pages/DocsPage.tsx +0 -79
  118. package/landing-page/src/pages/LandingPage.tsx +0 -31
  119. package/landing-page/src/pages/TerminalView.tsx +0 -106
  120. package/landing-page/src/styles/fonts.css +0 -0
  121. package/landing-page/src/styles/index.css +0 -3
  122. package/landing-page/src/styles/tailwind.css +0 -4
  123. package/landing-page/src/styles/theme.css +0 -181
  124. package/landing-page/vite.config.ts +0 -19
  125. package/npm/darwin-arm64/bin/gssh +0 -0
  126. package/npm/darwin-arm64/package.json +0 -20
  127. package/scripts/build.ts +0 -298
  128. package/scripts/release.ts +0 -140
  129. package/src/__tests__/test-utils.ts +0 -298
  130. package/src/commands/__tests__/serve-messages.test.ts +0 -190
  131. package/src/commands/access.ts +0 -298
  132. package/src/commands/add.ts +0 -452
  133. package/src/commands/auth.ts +0 -364
  134. package/src/commands/connect.ts +0 -287
  135. package/src/commands/directory.ts +0 -16
  136. package/src/commands/host.ts +0 -396
  137. package/src/commands/identity.ts +0 -184
  138. package/src/commands/list.ts +0 -200
  139. package/src/commands/relay.ts +0 -315
  140. package/src/commands/remove.ts +0 -241
  141. package/src/commands/serve.ts +0 -1493
  142. package/src/commands/share.ts +0 -456
  143. package/src/commands/status.ts +0 -125
  144. package/src/commands/switch.ts +0 -353
  145. package/src/commands/tmux.ts +0 -317
  146. package/src/core/__tests__/access.test.ts +0 -240
  147. package/src/core/access.ts +0 -277
  148. package/src/core/bundle.ts +0 -342
  149. package/src/core/config.ts +0 -510
  150. package/src/core/git.ts +0 -317
  151. package/src/core/github.ts +0 -151
  152. package/src/core/identity.ts +0 -631
  153. package/src/core/linear.ts +0 -225
  154. package/src/core/shell.ts +0 -161
  155. package/src/core/trusted-relays.ts +0 -315
  156. package/src/index.ts +0 -810
  157. package/src/lib/remote-session/index.ts +0 -7
  158. package/src/lib/remote-session/protocol.ts +0 -267
  159. package/src/lib/remote-session/session-handler.ts +0 -581
  160. package/src/lib/remote-session/workspace-scanner.ts +0 -167
  161. package/src/lib/tmux-lite/README.md +0 -81
  162. package/src/lib/tmux-lite/cli.ts +0 -796
  163. package/src/lib/tmux-lite/crypto/__tests__/helpers/handshake-runner.ts +0 -349
  164. package/src/lib/tmux-lite/crypto/__tests__/helpers/mock-relay.ts +0 -291
  165. package/src/lib/tmux-lite/crypto/__tests__/helpers/test-identities.ts +0 -142
  166. package/src/lib/tmux-lite/crypto/__tests__/integration/authorization.integration.test.ts +0 -339
  167. package/src/lib/tmux-lite/crypto/__tests__/integration/e2e-communication.integration.test.ts +0 -477
  168. package/src/lib/tmux-lite/crypto/__tests__/integration/error-handling.integration.test.ts +0 -499
  169. package/src/lib/tmux-lite/crypto/__tests__/integration/handshake.integration.test.ts +0 -371
  170. package/src/lib/tmux-lite/crypto/__tests__/integration/security.integration.test.ts +0 -573
  171. package/src/lib/tmux-lite/crypto/access-control.test.ts +0 -512
  172. package/src/lib/tmux-lite/crypto/access-control.ts +0 -320
  173. package/src/lib/tmux-lite/crypto/frames.test.ts +0 -262
  174. package/src/lib/tmux-lite/crypto/frames.ts +0 -141
  175. package/src/lib/tmux-lite/crypto/handshake.ts +0 -894
  176. package/src/lib/tmux-lite/crypto/identity.test.ts +0 -220
  177. package/src/lib/tmux-lite/crypto/identity.ts +0 -286
  178. package/src/lib/tmux-lite/crypto/index.ts +0 -51
  179. package/src/lib/tmux-lite/crypto/invites.test.ts +0 -381
  180. package/src/lib/tmux-lite/crypto/invites.ts +0 -215
  181. package/src/lib/tmux-lite/crypto/keyexchange.ts +0 -435
  182. package/src/lib/tmux-lite/crypto/keys.test.ts +0 -58
  183. package/src/lib/tmux-lite/crypto/keys.ts +0 -47
  184. package/src/lib/tmux-lite/crypto/secretbox.test.ts +0 -169
  185. package/src/lib/tmux-lite/crypto/secretbox.ts +0 -124
  186. package/src/lib/tmux-lite/handshake-handler.ts +0 -451
  187. package/src/lib/tmux-lite/protocol.test.ts +0 -307
  188. package/src/lib/tmux-lite/protocol.ts +0 -266
  189. package/src/lib/tmux-lite/relay-client.ts +0 -506
  190. package/src/lib/tmux-lite/server.ts +0 -1250
  191. package/src/lib/tmux-lite/shell-integration.sh +0 -37
  192. package/src/lib/tmux-lite/terminal-queries.test.ts +0 -54
  193. package/src/lib/tmux-lite/terminal-queries.ts +0 -49
  194. package/src/relay/__tests__/e2e-flow.test.ts +0 -1284
  195. package/src/relay/__tests__/helpers/auth.ts +0 -354
  196. package/src/relay/__tests__/helpers/ports.ts +0 -51
  197. package/src/relay/__tests__/protocol-validation.test.ts +0 -265
  198. package/src/relay/authorization.ts +0 -303
  199. package/src/relay/embedded-assets.generated.d.ts +0 -15
  200. package/src/relay/identity.ts +0 -352
  201. package/src/relay/index.ts +0 -57
  202. package/src/relay/pipes.test.ts +0 -427
  203. package/src/relay/pipes.ts +0 -195
  204. package/src/relay/protocol.ts +0 -804
  205. package/src/relay/registries.test.ts +0 -437
  206. package/src/relay/registries.ts +0 -593
  207. package/src/relay/server.test.ts +0 -1323
  208. package/src/relay/server.ts +0 -1092
  209. package/src/relay/signing.ts +0 -238
  210. package/src/relay/types.ts +0 -69
  211. package/src/serve/client-session-manager.ts +0 -622
  212. package/src/serve/daemon.ts +0 -497
  213. package/src/serve/pty-session.ts +0 -236
  214. package/src/serve/types.ts +0 -169
  215. package/src/shared/components/Flow.tsx +0 -453
  216. package/src/shared/components/Flow.tui.tsx +0 -343
  217. package/src/shared/components/Flow.web.tsx +0 -442
  218. package/src/shared/components/Inbox.tsx +0 -446
  219. package/src/shared/components/Inbox.tui.tsx +0 -262
  220. package/src/shared/components/Inbox.web.tsx +0 -329
  221. package/src/shared/components/MachineList.tsx +0 -187
  222. package/src/shared/components/MachineList.tui.tsx +0 -161
  223. package/src/shared/components/MachineList.web.tsx +0 -210
  224. package/src/shared/components/ProjectList.tsx +0 -176
  225. package/src/shared/components/ProjectList.tui.tsx +0 -109
  226. package/src/shared/components/ProjectList.web.tsx +0 -143
  227. package/src/shared/components/SpacesBrowser.tsx +0 -332
  228. package/src/shared/components/SpacesBrowser.tui.tsx +0 -163
  229. package/src/shared/components/SpacesBrowser.web.tsx +0 -221
  230. package/src/shared/components/index.ts +0 -103
  231. package/src/shared/hooks/index.ts +0 -16
  232. package/src/shared/hooks/useNavigation.ts +0 -226
  233. package/src/shared/index.ts +0 -122
  234. package/src/shared/providers/LocalMachineProvider.ts +0 -425
  235. package/src/shared/providers/MachineProvider.ts +0 -165
  236. package/src/shared/providers/RemoteMachineProvider.ts +0 -444
  237. package/src/shared/providers/index.ts +0 -26
  238. package/src/shared/types.ts +0 -145
  239. package/src/tui/adapters.ts +0 -120
  240. package/src/tui/app.tsx +0 -1816
  241. package/src/tui/components/Terminal.tsx +0 -580
  242. package/src/tui/hooks/index.ts +0 -35
  243. package/src/tui/hooks/useAppState.ts +0 -314
  244. package/src/tui/hooks/useDaemonStatus.ts +0 -174
  245. package/src/tui/hooks/useInboxTUI.ts +0 -113
  246. package/src/tui/hooks/useRemoteMachines.ts +0 -209
  247. package/src/tui/index.ts +0 -24
  248. package/src/tui/state.ts +0 -299
  249. package/src/tui/terminal-bracketed-paste.test.ts +0 -45
  250. package/src/tui/terminal-bracketed-paste.ts +0 -47
  251. package/src/types/bundle.ts +0 -112
  252. package/src/types/config.ts +0 -89
  253. package/src/types/errors.ts +0 -206
  254. package/src/types/identity.ts +0 -284
  255. package/src/types/workspace-fuzzy.ts +0 -49
  256. package/src/types/workspace.ts +0 -151
  257. package/src/utils/bun-socket-writer.ts +0 -80
  258. package/src/utils/deps.ts +0 -127
  259. package/src/utils/fuzzy-match.ts +0 -125
  260. package/src/utils/logger.ts +0 -127
  261. package/src/utils/markdown.ts +0 -254
  262. package/src/utils/onboarding.ts +0 -229
  263. package/src/utils/prompts.ts +0 -114
  264. package/src/utils/run-commands.ts +0 -112
  265. package/src/utils/run-scripts.ts +0 -142
  266. package/src/utils/sanitize.ts +0 -98
  267. package/src/utils/secrets.ts +0 -122
  268. package/src/utils/shell-escape.ts +0 -40
  269. package/src/utils/utf8.ts +0 -79
  270. package/src/utils/workspace-state.ts +0 -47
  271. package/src/web/README.md +0 -73
  272. package/src/web/bun.lock +0 -575
  273. package/src/web/eslint.config.js +0 -23
  274. package/src/web/index.html +0 -16
  275. package/src/web/package.json +0 -37
  276. package/src/web/public/vite.svg +0 -1
  277. package/src/web/src/App.tsx +0 -604
  278. package/src/web/src/assets/react.svg +0 -1
  279. package/src/web/src/components/Terminal.tsx +0 -207
  280. package/src/web/src/hooks/useRelayConnection.ts +0 -224
  281. package/src/web/src/hooks/useTerminal.ts +0 -699
  282. package/src/web/src/index.css +0 -55
  283. package/src/web/src/lib/crypto/__tests__/web-terminal.test.ts +0 -1158
  284. package/src/web/src/lib/crypto/frames.ts +0 -205
  285. package/src/web/src/lib/crypto/handshake.ts +0 -396
  286. package/src/web/src/lib/crypto/identity.ts +0 -128
  287. package/src/web/src/lib/crypto/keyexchange.ts +0 -246
  288. package/src/web/src/lib/crypto/relay-signing.ts +0 -53
  289. package/src/web/src/lib/invite.ts +0 -58
  290. package/src/web/src/lib/storage/identity-store.ts +0 -94
  291. package/src/web/src/main.tsx +0 -10
  292. package/src/web/src/types/identity.ts +0 -45
  293. package/src/web/tsconfig.app.json +0 -28
  294. package/src/web/tsconfig.json +0 -7
  295. package/src/web/tsconfig.node.json +0 -26
  296. package/src/web/vite.config.ts +0 -31
  297. package/todo-security.md +0 -92
  298. package/tsconfig.json +0 -23
  299. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite +0 -0
  300. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-shm +0 -0
  301. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/12b7107e435bf1b9a8713a7f320472a63e543104d633d89a26f8d21f4e4ef182.sqlite-wal +0 -0
  302. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite +0 -0
  303. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-shm +0 -0
  304. package/worker/.wrangler/state/v3/d1/miniflare-D1DatabaseObject/1a1ac3db1ab86ecf712f90322868a9aabc2c7dc9fe2dfbe94f9b075096276b0f.sqlite-wal +0 -0
  305. package/worker/bun.lock +0 -237
  306. package/worker/package.json +0 -22
  307. package/worker/schema.sql +0 -96
  308. package/worker/src/handlers/auth.ts +0 -451
  309. package/worker/src/handlers/subdomains.ts +0 -376
  310. package/worker/src/handlers/user.ts +0 -98
  311. package/worker/src/index.ts +0 -70
  312. package/worker/src/middleware/auth.ts +0 -152
  313. package/worker/src/services/cloudflare.ts +0 -609
  314. package/worker/src/types.ts +0 -96
  315. package/worker/tsconfig.json +0 -15
  316. package/worker/wrangler.toml +0 -26
@@ -1,199 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Client - connects to router, attaches to session
4
- *
5
- * Usage: bun client.ts [project] [workspace]
6
- */
7
-
8
- import { select } from "@inquirer/prompts";
9
- import {
10
- ROUTER_SOCKET,
11
- type RouterCommand,
12
- type RouterResponse,
13
- type SessionInfo,
14
- type AttachMode,
15
- encodeControl,
16
- isControl,
17
- decodeControl,
18
- type SessionEvent,
19
- } from "./protocol";
20
-
21
- const [project = "test", workspace = "default"] = process.argv.slice(2);
22
-
23
- // Connect to router
24
- async function routerCommand(cmd: RouterCommand): Promise<RouterResponse> {
25
- return new Promise(async (resolve, reject) => {
26
- const socket = await Bun.connect({
27
- unix: ROUTER_SOCKET,
28
- socket: {
29
- data(socket, data) {
30
- resolve(JSON.parse(data.toString()));
31
- socket.end();
32
- },
33
- error(socket, error) {
34
- reject(error);
35
- },
36
- connectError(socket, error) {
37
- reject(new Error("Router not running. Start with: bun router.ts"));
38
- }
39
- }
40
- });
41
-
42
- socket.write(JSON.stringify(cmd));
43
- });
44
- }
45
-
46
- // Get or create session
47
- console.log(`Connecting to ${project}/${workspace}...`);
48
-
49
- let session: SessionInfo;
50
-
51
- try {
52
- let response = await routerCommand({
53
- type: "create",
54
- project,
55
- workspace,
56
- cwd: process.cwd()
57
- });
58
-
59
- // Handle already-attached case
60
- if (response.type === "already-attached") {
61
- console.log(`\nSession "${project}/${workspace}" is already attached.\n`);
62
-
63
- const choice = await select({
64
- message: "What would you like to do?",
65
- choices: [
66
- { value: "take-over", name: "Take over (disconnect other client)" },
67
- { value: "new", name: "Create new session for this workspace" },
68
- { value: "cancel", name: "Cancel" },
69
- ]
70
- }) as AttachMode;
71
-
72
- if (choice === "cancel") {
73
- console.log("Cancelled.");
74
- process.exit(0);
75
- }
76
-
77
- if (choice === "take-over") {
78
- // Attach with take-over mode
79
- response = await routerCommand({
80
- type: "attach",
81
- sessionId: response.session.id,
82
- mode: "take-over"
83
- });
84
- } else if (choice === "new") {
85
- // Force create new session (kill old one first, then create)
86
- await routerCommand({ type: "kill", sessionId: response.session.id });
87
- response = await routerCommand({
88
- type: "create",
89
- project,
90
- workspace,
91
- cwd: process.cwd()
92
- });
93
- }
94
- }
95
-
96
- if (response.type === "error") {
97
- console.error("Error:", response.message);
98
- process.exit(1);
99
- }
100
-
101
- if (response.type !== "created") {
102
- console.error("Unexpected response:", response);
103
- process.exit(1);
104
- }
105
-
106
- session = response.session;
107
- } catch (e: any) {
108
- console.error(e.message);
109
- process.exit(1);
110
- }
111
-
112
- console.log(`Attached to session ${session.id}`);
113
- console.log("Press Ctrl+D to detach\n");
114
-
115
- // Connect to session
116
- const sessionSocket = await Bun.connect({
117
- unix: session.socketPath,
118
- socket: {
119
- data(socket, data) {
120
- const buf = Buffer.from(data);
121
-
122
- if (isControl(buf)) {
123
- const event = decodeControl(buf) as SessionEvent;
124
-
125
- switch (event.type) {
126
- case "attached":
127
- // Replay scrollback
128
- if (event.scrollback) {
129
- const scrollback = Buffer.from(event.scrollback, "base64");
130
- process.stdout.write(scrollback);
131
- }
132
- break;
133
-
134
- case "exited":
135
- process.stdin.setRawMode(false);
136
- console.log(`\nSession exited with code ${event.code}`);
137
- process.exit(event.code);
138
- break;
139
-
140
- case "kicked":
141
- process.stdin.setRawMode(false);
142
- console.log("\n\nAnother client took over this session.");
143
- process.exit(0);
144
- break;
145
-
146
- case "pong":
147
- break;
148
- }
149
- } else {
150
- // PTY output - write to terminal
151
- process.stdout.write(buf);
152
- }
153
- },
154
-
155
- close() {
156
- process.stdin.setRawMode(false);
157
- console.log("\nDisconnected from session");
158
- process.exit(0);
159
- },
160
-
161
- error(socket, error) {
162
- process.stdin.setRawMode(false);
163
- console.error("\nSession error:", error.message);
164
- process.exit(1);
165
- }
166
- }
167
- });
168
-
169
- // Send initial resize
170
- sessionSocket.write(encodeControl({
171
- type: "resize",
172
- cols: process.stdout.columns || 80,
173
- rows: process.stdout.rows || 24
174
- }));
175
-
176
- // Handle terminal resize
177
- process.stdout.on("resize", () => {
178
- sessionSocket.write(encodeControl({
179
- type: "resize",
180
- cols: process.stdout.columns,
181
- rows: process.stdout.rows
182
- }));
183
- });
184
-
185
- // Forward stdin to session
186
- process.stdin.setRawMode(true);
187
- process.stdin.resume();
188
-
189
- for await (const chunk of process.stdin) {
190
- // Check for Ctrl+D (detach)
191
- if (chunk[0] === 4) {
192
- sessionSocket.write(encodeControl({ type: "detach" }));
193
- process.stdin.setRawMode(false);
194
- console.log("\nDetached (session still running)");
195
- process.exit(0);
196
- }
197
-
198
- sessionSocket.write(chunk);
199
- }
@@ -1,74 +0,0 @@
1
- /**
2
- * Shared protocol between router, sessions, and clients
3
- */
4
-
5
- export const ROUTER_SOCKET = "/tmp/spaces-router.sock";
6
- export const SESSION_SOCKET_PREFIX = "/tmp/spaces-session-";
7
-
8
- // Router commands (JSON over socket)
9
- export type RouterCommand =
10
- | { type: "list" }
11
- | { type: "create"; project: string; workspace: string; cwd: string }
12
- | { type: "attach"; sessionId: string; mode?: AttachMode }
13
- | { type: "kill"; sessionId: string }
14
- | { type: "kick"; sessionId: string }; // Disconnect current client
15
-
16
- export type RouterResponse =
17
- | { type: "sessions"; sessions: SessionInfo[] }
18
- | { type: "created"; session: SessionInfo }
19
- | { type: "already-attached"; session: SessionInfo } // Session exists but has a client
20
- | { type: "error"; message: string }
21
- | { type: "ok" };
22
-
23
- export interface SessionInfo {
24
- id: string;
25
- project: string;
26
- workspace: string;
27
- socketPath: string;
28
- pid: number;
29
- attached: boolean; // true if a client is connected
30
- createdAt: number;
31
- }
32
-
33
- // When attaching to an already-attached session
34
- export type AttachMode =
35
- | "take-over" // Disconnect existing client, you take over
36
- | "new" // Create a new session for the same workspace
37
- | "cancel"; // Abort
38
-
39
- // Session protocol (binary + JSON control)
40
- // Control messages start with 0x00, data is raw bytes
41
- export const CONTROL_PREFIX = 0x00;
42
-
43
- export type SessionControl =
44
- | { type: "resize"; cols: number; rows: number }
45
- | { type: "detach" }
46
- | { type: "ping" };
47
-
48
- export type SessionEvent =
49
- | { type: "attached"; scrollback: string }
50
- | { type: "exited"; code: number }
51
- | { type: "kicked" } // Another client took over
52
- | { type: "pong" };
53
-
54
- // Helper to encode control message
55
- export function encodeControl(msg: SessionControl | SessionEvent): Buffer {
56
- const json = JSON.stringify(msg);
57
- const buf = Buffer.alloc(1 + 4 + json.length);
58
- buf[0] = CONTROL_PREFIX;
59
- buf.writeUInt32BE(json.length, 1);
60
- buf.write(json, 5);
61
- return buf;
62
- }
63
-
64
- // Helper to check if data is control message
65
- export function isControl(data: Buffer): boolean {
66
- return data[0] === CONTROL_PREFIX;
67
- }
68
-
69
- // Helper to decode control message
70
- export function decodeControl(data: Buffer): SessionControl | SessionEvent {
71
- const len = data.readUInt32BE(1);
72
- const json = data.subarray(5, 5 + len).toString();
73
- return JSON.parse(json);
74
- }
@@ -1,217 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Router - manages session lifecycle
4
- * Always running, spawns sessions on demand
5
- *
6
- * Usage: bun router.ts
7
- */
8
-
9
- import { unlinkSync, existsSync } from "fs";
10
- import { spawn } from "bun";
11
- import {
12
- ROUTER_SOCKET,
13
- SESSION_SOCKET_PREFIX,
14
- type RouterCommand,
15
- type RouterResponse,
16
- type SessionInfo,
17
- } from "./protocol";
18
-
19
- // Clean up existing socket
20
- try { unlinkSync(ROUTER_SOCKET); } catch {}
21
-
22
- // Active sessions
23
- const sessions = new Map<string, {
24
- info: SessionInfo;
25
- proc: Bun.Subprocess;
26
- stdin: WritableStream<Uint8Array>;
27
- }>();
28
-
29
- // Generate session ID
30
- function genId(): string {
31
- return Math.random().toString(36).substring(2, 10);
32
- }
33
-
34
- // Find session by project/workspace
35
- function findSession(project: string, workspace: string) {
36
- for (const [id, session] of sessions) {
37
- if (session.info.project === project && session.info.workspace === workspace) {
38
- return session;
39
- }
40
- }
41
- return null;
42
- }
43
-
44
- // Spawn a new session
45
- async function createSession(project: string, workspace: string, cwd: string): Promise<SessionInfo> {
46
- const id = genId();
47
- const socketPath = `${SESSION_SOCKET_PREFIX}${id}.sock`;
48
-
49
- const proc = spawn({
50
- cmd: ["bun", "run", `${import.meta.dir}/session.ts`, socketPath, cwd, project, workspace],
51
- stdout: "pipe",
52
- stdin: "pipe",
53
- stderr: "inherit",
54
- });
55
-
56
- // Wait for ready event
57
- const reader = proc.stdout.getReader();
58
- const { value } = await reader.read();
59
- const ready = JSON.parse(new TextDecoder().decode(value));
60
-
61
- if (ready.event !== "ready") {
62
- throw new Error("Session failed to start");
63
- }
64
-
65
- const info: SessionInfo = {
66
- id,
67
- project,
68
- workspace,
69
- socketPath,
70
- pid: ready.pid,
71
- attached: false,
72
- createdAt: Date.now(),
73
- };
74
-
75
- const sessionData = { info, proc, stdin: proc.stdin };
76
- sessions.set(id, sessionData);
77
-
78
- // Monitor session stdout for state updates
79
- (async () => {
80
- while (true) {
81
- const { done, value } = await reader.read();
82
- if (done) break;
83
-
84
- try {
85
- const lines = new TextDecoder().decode(value).trim().split('\n');
86
- for (const line of lines) {
87
- const event = JSON.parse(line);
88
- if (event.attached !== undefined) {
89
- const session = sessions.get(id);
90
- if (session) session.info.attached = event.attached;
91
- }
92
- }
93
- } catch {}
94
- }
95
-
96
- // Session ended
97
- sessions.delete(id);
98
- console.log(`[router] Session ${id} ended`);
99
- })();
100
-
101
- console.log(`[router] Created session ${id} for ${project}/${workspace}`);
102
- return info;
103
- }
104
-
105
- // Kick client from session
106
- async function kickSession(sessionId: string): Promise<boolean> {
107
- const session = sessions.get(sessionId);
108
- if (!session) return false;
109
-
110
- const writer = session.stdin.getWriter();
111
- await writer.write(new TextEncoder().encode("kick\n"));
112
- writer.releaseLock();
113
- return true;
114
- }
115
-
116
- // Start router server
117
- const server = Bun.listen({
118
- unix: ROUTER_SOCKET,
119
- socket: {
120
- async data(socket, data) {
121
- try {
122
- const cmd: RouterCommand = JSON.parse(data.toString());
123
- let response: RouterResponse;
124
-
125
- switch (cmd.type) {
126
- case "list":
127
- response = {
128
- type: "sessions",
129
- sessions: Array.from(sessions.values()).map(s => s.info)
130
- };
131
- break;
132
-
133
- case "create": {
134
- // Check if session already exists for this workspace
135
- const existing = findSession(cmd.project, cmd.workspace);
136
-
137
- if (existing) {
138
- if (existing.info.attached) {
139
- // Session exists and has a client
140
- response = { type: "already-attached", session: existing.info };
141
- } else {
142
- // Session exists but detached - reuse it
143
- response = { type: "created", session: existing.info };
144
- }
145
- } else {
146
- // Create new session
147
- const session = await createSession(cmd.project, cmd.workspace, cmd.cwd);
148
- response = { type: "created", session };
149
- }
150
- break;
151
- }
152
-
153
- case "attach": {
154
- const session = sessions.get(cmd.sessionId);
155
- if (!session) {
156
- response = { type: "error", message: "Session not found" };
157
- } else if (session.info.attached && cmd.mode !== "take-over") {
158
- response = { type: "already-attached", session: session.info };
159
- } else {
160
- if (cmd.mode === "take-over" && session.info.attached) {
161
- await kickSession(cmd.sessionId);
162
- await Bun.sleep(50); // Let kick propagate
163
- }
164
- response = { type: "created", session: session.info };
165
- }
166
- break;
167
- }
168
-
169
- case "kick": {
170
- const kicked = await kickSession(cmd.sessionId);
171
- response = kicked ? { type: "ok" } : { type: "error", message: "Session not found" };
172
- break;
173
- }
174
-
175
- case "kill": {
176
- const toKill = sessions.get(cmd.sessionId);
177
- if (toKill) {
178
- toKill.proc.kill();
179
- sessions.delete(cmd.sessionId);
180
- response = { type: "ok" };
181
- } else {
182
- response = { type: "error", message: "Session not found" };
183
- }
184
- break;
185
- }
186
-
187
- default:
188
- response = { type: "error", message: "Unknown command" };
189
- }
190
-
191
- socket.write(JSON.stringify(response));
192
- } catch (e: any) {
193
- socket.write(JSON.stringify({ type: "error", message: e.message }));
194
- }
195
- },
196
- error(socket, error) {
197
- console.error("[router] Client error:", error.message);
198
- }
199
- }
200
- });
201
-
202
- console.log(`[router] Listening on ${ROUTER_SOCKET}`);
203
- console.log("[router] Ready");
204
-
205
- // Cleanup on exit
206
- process.on("SIGTERM", () => {
207
- for (const [id, session] of sessions) {
208
- session.proc.kill();
209
- }
210
- server.stop();
211
- try { unlinkSync(ROUTER_SOCKET); } catch {}
212
- process.exit(0);
213
- });
214
-
215
- process.on("SIGINT", () => {
216
- process.emit("SIGTERM" as any);
217
- });
@@ -1,180 +0,0 @@
1
- #!/usr/bin/env bun
2
- /**
3
- * Session server - manages a single PTY session
4
- * One client at a time, supports kick for takeover
5
- *
6
- * Usage: bun session.ts <socket-path> <cwd> <project> <workspace>
7
- */
8
-
9
- import { unlinkSync } from "fs";
10
- import {
11
- encodeControl,
12
- isControl,
13
- decodeControl,
14
- type SessionControl,
15
- } from "./protocol";
16
-
17
- const [socketPath, cwd, project, workspace] = process.argv.slice(2);
18
-
19
- if (!socketPath || !cwd) {
20
- console.error("Usage: bun session.ts <socket-path> <cwd> <project> <workspace>");
21
- process.exit(1);
22
- }
23
-
24
- // Clean up existing socket
25
- try { unlinkSync(socketPath); } catch {}
26
-
27
- // Scrollback buffer (keep last 50KB)
28
- const MAX_SCROLLBACK = 50 * 1024;
29
- let scrollback = Buffer.alloc(0);
30
-
31
- // Current attached client (only one allowed)
32
- let client: any = null;
33
-
34
- // Create PTY
35
- const terminal = new Bun.Terminal({
36
- cols: 120,
37
- rows: 40,
38
- data(term, data) {
39
- // Add to scrollback
40
- scrollback = Buffer.concat([scrollback, data]);
41
- if (scrollback.length > MAX_SCROLLBACK) {
42
- scrollback = scrollback.subarray(-MAX_SCROLLBACK);
43
- }
44
-
45
- // Send to attached client
46
- if (client) {
47
- client.write(data);
48
- }
49
- }
50
- });
51
-
52
- // Spawn shell
53
- const proc = Bun.spawn(["bash"], {
54
- terminal,
55
- cwd,
56
- env: {
57
- ...process.env,
58
- SPACES_PROJECT: project,
59
- SPACES_WORKSPACE: workspace,
60
- },
61
- });
62
-
63
- // Handle shell exit
64
- proc.exited.then(code => {
65
- if (client) {
66
- client.write(encodeControl({ type: "exited", code }));
67
- }
68
- // Give client time to receive exit message
69
- setTimeout(() => {
70
- server.stop();
71
- try { unlinkSync(socketPath); } catch {}
72
- process.exit(code);
73
- }, 100);
74
- });
75
-
76
- // Report state to router
77
- function reportState(event: string) {
78
- console.log(JSON.stringify({ event, attached: client !== null }));
79
- }
80
-
81
- // Kick current client
82
- function kickClient() {
83
- if (client) {
84
- client.write(encodeControl({ type: "kicked" }));
85
- client.end();
86
- client = null;
87
- reportState("client_kicked");
88
- }
89
- }
90
-
91
- // Start server
92
- const server = Bun.listen({
93
- unix: socketPath,
94
- socket: {
95
- open(socket) {
96
- if (client) {
97
- // Already have a client - kick them
98
- kickClient();
99
- }
100
-
101
- client = socket;
102
- reportState("client_attached");
103
-
104
- // Send scrollback to new client
105
- const attachMsg = encodeControl({
106
- type: "attached",
107
- scrollback: scrollback.toString("base64")
108
- });
109
- socket.write(attachMsg);
110
- },
111
-
112
- data(socket, data) {
113
- const buf = Buffer.from(data);
114
-
115
- if (isControl(buf)) {
116
- const ctrl = decodeControl(buf) as SessionControl;
117
-
118
- switch (ctrl.type) {
119
- case "resize":
120
- terminal.resize(ctrl.cols, ctrl.rows);
121
- break;
122
- case "detach":
123
- if (socket === client) {
124
- client = null;
125
- reportState("client_detached");
126
- }
127
- socket.end();
128
- break;
129
- case "ping":
130
- socket.write(encodeControl({ type: "pong" }));
131
- break;
132
- }
133
- } else {
134
- // Raw input - write to PTY
135
- terminal.write(buf);
136
- }
137
- },
138
-
139
- close(socket) {
140
- if (socket === client) {
141
- client = null;
142
- reportState("client_disconnected");
143
- }
144
- },
145
-
146
- error(socket, error) {
147
- console.error(JSON.stringify({ event: "error", message: error.message }));
148
- if (socket === client) {
149
- client = null;
150
- }
151
- }
152
- }
153
- });
154
-
155
- console.log(JSON.stringify({
156
- event: "ready",
157
- socketPath,
158
- pid: process.pid,
159
- project,
160
- workspace,
161
- attached: false
162
- }));
163
-
164
- // Handle stdin commands from router (for kick)
165
- const decoder = new TextDecoder();
166
- for await (const chunk of Bun.stdin.stream()) {
167
- const cmd = decoder.decode(chunk).trim();
168
- if (cmd === "kick") {
169
- kickClient();
170
- }
171
- }
172
-
173
- // Handle termination
174
- process.on("SIGTERM", () => {
175
- proc.kill();
176
- terminal.close();
177
- server.stop();
178
- try { unlinkSync(socketPath); } catch {}
179
- process.exit(0);
180
- });