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,49 @@
1
+ /**
2
+ * Type definitions for workspace fuzzy matching
3
+ */
4
+
5
+ /**
6
+ * A workspace candidate for fuzzy matching
7
+ */
8
+ export interface WorkspaceCandidate {
9
+ /** Workspace name */
10
+ name: string;
11
+ /** Absolute path to workspace */
12
+ path: string;
13
+ /** Current git branch */
14
+ branch: string;
15
+ /** Commits ahead of remote */
16
+ ahead: number;
17
+ /** Commits behind remote */
18
+ behind: number;
19
+ /** Number of uncommitted changes */
20
+ uncommittedChanges: number;
21
+ /** Last commit message */
22
+ lastCommit: string;
23
+ }
24
+
25
+ /**
26
+ * Result of fuzzy matching
27
+ */
28
+ export interface FuzzyMatch {
29
+ /** Original candidate item */
30
+ item: WorkspaceCandidate;
31
+ /** Match score (0-100, higher is better) */
32
+ score: number;
33
+ /** Indices of matched characters in item.name */
34
+ matchedIndices: number[];
35
+ }
36
+
37
+ /**
38
+ * Workspace with ranking information
39
+ */
40
+ export interface RankedWorkspace {
41
+ /** Workspace candidate */
42
+ workspace: WorkspaceCandidate;
43
+ /** Fuzzy match score */
44
+ matchScore: number;
45
+ /** Final ranking score (includes bonuses) */
46
+ finalScore: number;
47
+ /** Character indices that matched */
48
+ matchedIndices: number[];
49
+ }
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Type definitions for workspace management
3
+ */
4
+
5
+ /**
6
+ * Information about a git worktree workspace
7
+ */
8
+ export interface WorktreeInfo {
9
+ /** Workspace name (directory name) */
10
+ name: string
11
+ /** Absolute path to the workspace directory */
12
+ path: string
13
+ /** Current git branch */
14
+ branch: string
15
+ /** Number of commits ahead of base branch */
16
+ ahead: number
17
+ /** Number of commits behind base branch */
18
+ behind: number
19
+ /** Number of uncommitted changes */
20
+ uncommittedChanges: number
21
+ /** Last commit message */
22
+ lastCommit: string
23
+ /** Last commit date */
24
+ lastCommitDate: Date
25
+ }
26
+
27
+ /**
28
+ * Project information for listing
29
+ */
30
+ export interface ProjectInfo {
31
+ /** Project name */
32
+ name: string
33
+ /** GitHub repository (owner/repo) */
34
+ repository: string
35
+ /** Absolute path to project directory */
36
+ path: string
37
+ /** Number of workspaces in this project */
38
+ workspaceCount: number
39
+ /** Whether this is the current project */
40
+ isCurrent: boolean
41
+ }
42
+
43
+ /**
44
+ * Dependency information
45
+ */
46
+ export interface Dependency {
47
+ /** Display name of the dependency */
48
+ name: string
49
+ /** Command to check (e.g., "gh", "git") */
50
+ command: string
51
+ /** Arguments to run for version check */
52
+ checkArgs: string[]
53
+ /** URL for installation instructions */
54
+ installUrl: string
55
+ /** Optional custom auth check function */
56
+ authCheck?: () => Promise<boolean>
57
+ }
58
+
59
+ /**
60
+ * Options for creating a workspace
61
+ */
62
+ export interface CreateWorkspaceOptions {
63
+ /** Workspace name */
64
+ name: string
65
+ /** Branch name (defaults to workspace name) */
66
+ branchName?: string
67
+ /** Base branch to create from (defaults to project base branch) */
68
+ fromBranch?: string
69
+ /** Whether to skip opening interactive shell */
70
+ noShell?: boolean
71
+ /** Whether to skip running setup commands */
72
+ noSetup?: boolean
73
+ }
74
+
75
+ /**
76
+ * Label attached to an issue
77
+ */
78
+ export interface Label {
79
+ /** Unique identifier for the label */
80
+ id: string
81
+ /** Display name of the label */
82
+ name: string
83
+ /** Hex color code for the label */
84
+ color: string
85
+ }
86
+
87
+ /**
88
+ * User assigned to an issue
89
+ */
90
+ export interface User {
91
+ /** Unique identifier for the user */
92
+ id: string
93
+ /** Display name of the user */
94
+ name: string
95
+ /** Email address of the user */
96
+ email: string
97
+ }
98
+
99
+ /**
100
+ * Issue state information
101
+ */
102
+ export interface IssueState {
103
+ /** Unique identifier for the state */
104
+ id: string
105
+ /** Display name of the state (e.g., "In Progress", "Done") */
106
+ name: string
107
+ /** State type (e.g., "started", "completed", "canceled") */
108
+ type: string
109
+ }
110
+
111
+ /**
112
+ * Linear issue attachment
113
+ */
114
+ export interface LinearAttachment {
115
+ /** Unique identifier for the attachment */
116
+ id: string
117
+ /** URL to the attachment */
118
+ url: string
119
+ /** Title of the attachment */
120
+ title: string | null
121
+ /** Source type (e.g., "upload", "url") */
122
+ sourceType: string | null
123
+ /** Creation timestamp */
124
+ createdAt: Date
125
+ }
126
+
127
+ /**
128
+ * Linear issue for workspace creation
129
+ */
130
+ export interface LinearIssue {
131
+ /** Issue ID */
132
+ id: string
133
+ /** Issue identifier (e.g., "ENG-123") */
134
+ identifier: string
135
+ /** Issue title */
136
+ title: string
137
+ /** Issue state */
138
+ state: Promise<IssueState> | undefined
139
+ /** Issue description/body (can be null) */
140
+ description: string | null
141
+ /** Web URL to view the issue */
142
+ url: string
143
+ /** User assigned to the issue (null if unassigned) */
144
+ assignee: Promise<User> | undefined
145
+ /** Timestamp when issue was created */
146
+ createdAt: Date
147
+ /** Timestamp when issue was last updated */
148
+ updatedAt: Date
149
+ /** Issue attachments (images, files, etc.) - lazy-loaded function */
150
+ attachments: () => Promise<LinearAttachment[]>
151
+ }
@@ -0,0 +1,80 @@
1
+ import type { Socket } from "bun";
2
+
3
+ type WritableData = Buffer | Uint8Array | ArrayBuffer;
4
+
5
+ /**
6
+ * Bun sockets are unbuffered. `socket.write()` can write fewer bytes than provided
7
+ * under backpressure. For framed protocols, partial writes will desync the reader.
8
+ *
9
+ * This helper buffers pending bytes and flushes them when the socket drains.
10
+ */
11
+ export function createBufferedSocketWriter(socket: Socket<any>) {
12
+ const queue: Buffer[] = [];
13
+ let headOffset = 0;
14
+
15
+ const flush = () => {
16
+ while (queue.length > 0) {
17
+ const head = queue[0]!;
18
+ const remaining = head.length - headOffset;
19
+
20
+ if (remaining <= 0) {
21
+ queue.shift();
22
+ headOffset = 0;
23
+ continue;
24
+ }
25
+
26
+ const written = socket.write(head, headOffset, remaining);
27
+
28
+ // -1: closed/shutting down. 0: backpressure.
29
+ if (written <= 0) return;
30
+
31
+ headOffset += written;
32
+ if (headOffset >= head.length) {
33
+ queue.shift();
34
+ headOffset = 0;
35
+ }
36
+ }
37
+ };
38
+
39
+ const write = (data: WritableData) => {
40
+ // Copy into a stable Buffer (avoids subarray lifetime issues and ensures queue owns bytes)
41
+ const buf = Buffer.from(data as any);
42
+
43
+ // Fast-path if nothing queued.
44
+ if (queue.length === 0) {
45
+ const written = socket.write(buf);
46
+
47
+ if (written <= 0) {
48
+ queue.push(buf);
49
+ headOffset = 0;
50
+ return;
51
+ }
52
+
53
+ if (written >= buf.length) return;
54
+
55
+ queue.push(buf);
56
+ headOffset = written;
57
+ return;
58
+ }
59
+
60
+ queue.push(buf);
61
+ flush();
62
+ };
63
+
64
+ const clear = () => {
65
+ queue.length = 0;
66
+ headOffset = 0;
67
+ };
68
+
69
+ const pendingBytes = () => {
70
+ let total = 0;
71
+ for (let i = 0; i < queue.length; i++) total += queue[i]!.length;
72
+ return Math.max(0, total - headOffset);
73
+ };
74
+
75
+ return { write, flush, clear, pendingBytes };
76
+ }
77
+
78
+
79
+
80
+
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Dependency checking utilities
3
+ */
4
+
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+ import type { Dependency } from '../types/workspace.js';
8
+ import { DependencyError, GitHubAuthError } from '../types/errors.js';
9
+ import { logger } from './logger.js';
10
+
11
+ const execAsync = promisify(exec);
12
+
13
+ /**
14
+ * Required system dependencies
15
+ */
16
+ export const REQUIRED_DEPS: Dependency[] = [
17
+ {
18
+ name: 'GitHub CLI',
19
+ command: 'gh',
20
+ checkArgs: ['--version'],
21
+ installUrl: 'https://cli.github.com/',
22
+ authCheck: async () => {
23
+ try {
24
+ await execAsync('gh auth status');
25
+ return true;
26
+ } catch {
27
+ return false;
28
+ }
29
+ },
30
+ },
31
+ {
32
+ name: 'Git',
33
+ command: 'git',
34
+ checkArgs: ['--version'],
35
+ installUrl: 'https://git-scm.com/',
36
+ },
37
+ {
38
+ name: 'jq',
39
+ command: 'jq',
40
+ checkArgs: ['--version'],
41
+ installUrl: 'https://stedolan.github.io/jq/',
42
+ },
43
+ ];
44
+
45
+ /**
46
+ * Check if a command exists
47
+ */
48
+ async function commandExists(command: string, checkArgs: string[]): Promise<boolean> {
49
+ try {
50
+ await execAsync(`${command} ${checkArgs.join(' ')}`);
51
+ return true;
52
+ } catch {
53
+ return false;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Check all required dependencies
59
+ */
60
+ export async function checkDependencies(deps: Dependency[] = REQUIRED_DEPS): Promise<{
61
+ missing: Dependency[];
62
+ allPresent: boolean;
63
+ }> {
64
+ const missing: Dependency[] = [];
65
+
66
+ for (const dep of deps) {
67
+ const exists = await commandExists(dep.command, dep.checkArgs);
68
+ if (!exists) {
69
+ missing.push(dep);
70
+ }
71
+ }
72
+
73
+ return {
74
+ missing,
75
+ allPresent: missing.length === 0,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Check GitHub CLI authentication
81
+ */
82
+ export async function checkGitHubAuth(): Promise<void> {
83
+ const ghDep = REQUIRED_DEPS.find((d) => d.command === 'gh');
84
+ if (!ghDep?.authCheck) {
85
+ return;
86
+ }
87
+
88
+ const isAuthenticated = await ghDep.authCheck();
89
+ if (!isAuthenticated) {
90
+ throw new GitHubAuthError();
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Display missing dependencies and exit
96
+ */
97
+ export function displayMissingDependencies(missing: Dependency[]): never {
98
+ logger.error('Missing required dependencies:\n');
99
+
100
+ for (const dep of missing) {
101
+ logger.log(` ${dep.name} (${dep.command})`);
102
+ logger.dim(` Install: ${dep.installUrl}\n`);
103
+ }
104
+
105
+ throw new DependencyError(
106
+ 'Please install the missing dependencies and try again.'
107
+ );
108
+ }
109
+
110
+ /**
111
+ * Check dependencies and throw if any are missing
112
+ */
113
+ export async function ensureDependencies(deps?: Dependency[]): Promise<void> {
114
+ const { missing, allPresent } = await checkDependencies(deps);
115
+
116
+ if (!allPresent) {
117
+ displayMissingDependencies(missing);
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Check specific dependencies (subset of all required)
123
+ */
124
+ export async function checkSpecificDeps(commands: string[]): Promise<void> {
125
+ const depsToCheck = REQUIRED_DEPS.filter((d) => commands.includes(d.command));
126
+ await ensureDependencies(depsToCheck);
127
+ }
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Fuzzy matching algorithm
3
+ * Matches query against candidates and returns scored results
4
+ */
5
+
6
+ import type { FuzzyMatch, WorkspaceCandidate } from '../types/workspace-fuzzy.js';
7
+
8
+ /**
9
+ * Perform fuzzy matching on workspace candidates
10
+ *
11
+ * Algorithm:
12
+ * 1. For each candidate, calculate match score
13
+ * 2. Filter out scores below threshold (30)
14
+ * 3. Sort by score descending
15
+ *
16
+ * @param query Search string
17
+ * @param candidates List of workspace candidates
18
+ * @returns Sorted array of matches with scores
19
+ */
20
+ export function fuzzyMatch(
21
+ query: string,
22
+ candidates: WorkspaceCandidate[]
23
+ ): FuzzyMatch[] {
24
+ const matches: FuzzyMatch[] = [];
25
+
26
+ for (const candidate of candidates) {
27
+ const result = calculateMatchScore(query, candidate.name);
28
+
29
+ if (result.score >= 30) { // Minimum threshold
30
+ matches.push({
31
+ item: candidate,
32
+ score: result.score,
33
+ matchedIndices: result.matchedIndices,
34
+ });
35
+ }
36
+ }
37
+
38
+ // Sort by score descending
39
+ matches.sort((a, b) => b.score - a.score);
40
+
41
+ return matches;
42
+ }
43
+
44
+ /**
45
+ * Calculate fuzzy match score between query and candidate
46
+ *
47
+ * Scoring rules:
48
+ * - All query characters must exist in order: otherwise score = 0
49
+ * - Consecutive character matches: +10 points per consecutive char
50
+ * - Word boundary matches: +5 points (e.g., "fb" matches "feature-branch")
51
+ * - Exact case match: +2 points per char
52
+ * - Start of string match: +15 points
53
+ * - Character proximity: -1 point per gap
54
+ * - Base score: 50 points if all chars match
55
+ *
56
+ * @param query Search string
57
+ * @param candidate String to match against
58
+ * @returns Object with score and matched character indices
59
+ */
60
+ function calculateMatchScore(
61
+ query: string,
62
+ candidate: string
63
+ ): { score: number; matchedIndices: number[] } {
64
+ if (!query || !candidate) {
65
+ return { score: 0, matchedIndices: [] };
66
+ }
67
+
68
+ const queryLower = query.toLowerCase();
69
+ const candidateLower = candidate.toLowerCase();
70
+
71
+ // Find all query characters in candidate
72
+ const matchedIndices: number[] = [];
73
+ let candidateIndex = 0;
74
+
75
+ for (let i = 0; i < queryLower.length; i++) {
76
+ const queryChar = queryLower[i];
77
+ const foundIndex = candidateLower.indexOf(queryChar, candidateIndex);
78
+
79
+ if (foundIndex === -1) {
80
+ // Character not found - no match
81
+ return { score: 0, matchedIndices: [] };
82
+ }
83
+
84
+ matchedIndices.push(foundIndex);
85
+ candidateIndex = foundIndex + 1;
86
+ }
87
+
88
+ // All characters matched - start with base score
89
+ let score = 50;
90
+
91
+ // Bonus: Start of string match
92
+ if (matchedIndices[0] === 0) {
93
+ score += 15;
94
+ }
95
+
96
+ // Calculate bonuses for consecutive matches, word boundaries, and case
97
+ for (let i = 0; i < matchedIndices.length; i++) {
98
+ const index = matchedIndices[i];
99
+ const queryChar = query[i];
100
+ const candidateChar = candidate[index];
101
+
102
+ // Consecutive match bonus
103
+ if (i > 0 && matchedIndices[i] === matchedIndices[i - 1] + 1) {
104
+ score += 10;
105
+ }
106
+
107
+ // Word boundary bonus (after -, _, /, or at start)
108
+ if (index === 0 || ['-', '_', '/'].includes(candidate[index - 1])) {
109
+ score += 5;
110
+ }
111
+
112
+ // Exact case match bonus
113
+ if (queryChar === candidateChar) {
114
+ score += 2;
115
+ }
116
+
117
+ // Proximity penalty (gap between matched characters)
118
+ if (i > 0) {
119
+ const gap = matchedIndices[i] - matchedIndices[i - 1] - 1;
120
+ score -= gap; // -1 point per character gap
121
+ }
122
+ }
123
+
124
+ return { score, matchedIndices };
125
+ }
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Logging utilities with chalk for colored output
3
+ */
4
+
5
+ import chalk from 'chalk';
6
+
7
+ /**
8
+ * Log levels
9
+ */
10
+ export type LogLevel = 'info' | 'success' | 'warning' | 'error' | 'debug';
11
+
12
+ /**
13
+ * Logger instance
14
+ */
15
+ class Logger {
16
+ private debugMode: boolean = false;
17
+
18
+ /**
19
+ * Enable or disable debug mode
20
+ */
21
+ setDebugMode(enabled: boolean): void {
22
+ this.debugMode = enabled;
23
+ }
24
+
25
+ /**
26
+ * Log an info message
27
+ */
28
+ info(message: string): void {
29
+ console.log(chalk.blue('ℹ'), message);
30
+ }
31
+
32
+ /**
33
+ * Log a success message
34
+ */
35
+ success(message: string): void {
36
+ console.log(chalk.green('✓'), message);
37
+ }
38
+
39
+ /**
40
+ * Log a warning message
41
+ */
42
+ warning(message: string): void {
43
+ console.log(chalk.yellow('⚠'), message);
44
+ }
45
+
46
+ /**
47
+ * Log an error message
48
+ */
49
+ error(message: string): void {
50
+ console.error(chalk.red('✗'), message);
51
+ }
52
+
53
+ /**
54
+ * Log a debug message (only if debug mode is enabled)
55
+ */
56
+ debug(message: string): void {
57
+ if (this.debugMode) {
58
+ console.log(chalk.gray('DEBUG:'), message);
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Log a message without any prefix
64
+ */
65
+ log(message: string): void {
66
+ console.log(message);
67
+ }
68
+
69
+ /**
70
+ * Log a dim/muted message
71
+ */
72
+ dim(message: string): void {
73
+ console.log(chalk.dim(message));
74
+ }
75
+
76
+ /**
77
+ * Log a bold message
78
+ */
79
+ bold(message: string): void {
80
+ console.log(chalk.bold(message));
81
+ }
82
+
83
+ /**
84
+ * Create a spinner-like loading message
85
+ */
86
+ loading(message: string): () => void {
87
+ process.stdout.write(chalk.blue('⠋') + ' ' + message);
88
+
89
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
90
+ let i = 0;
91
+
92
+ const interval = setInterval(() => {
93
+ process.stdout.write('\r' + chalk.blue(frames[i]) + ' ' + message);
94
+ i = (i + 1) % frames.length;
95
+ }, 80);
96
+
97
+ // Return a function to stop the spinner
98
+ return () => {
99
+ clearInterval(interval);
100
+ process.stdout.write('\r' + ' '.repeat(message.length + 2) + '\r');
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Format a command for display
106
+ */
107
+ command(cmd: string): string {
108
+ return chalk.cyan(cmd);
109
+ }
110
+
111
+ /**
112
+ * Format a path for display
113
+ */
114
+ path(path: string): string {
115
+ return chalk.magenta(path);
116
+ }
117
+
118
+ /**
119
+ * Format a highlight for display
120
+ */
121
+ highlight(text: string): string {
122
+ return chalk.yellow(text);
123
+ }
124
+ }
125
+
126
+ // Export singleton instance
127
+ export const logger = new Logger();