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,396 +0,0 @@
1
- /**
2
- * Host commands for gitspace.sh hosting
3
- *
4
- * Handles subdomain management: reserve, release, list, set-primary, status
5
- */
6
-
7
- import { existsSync, writeFileSync, readFileSync } from 'fs';
8
- import { join } from 'path';
9
- import { getSecret, setSecret, deleteSecret } from '../utils/secrets.js';
10
- import { getSpacesDir } from '../core/config.js';
11
- import { getPublicKeyWithoutPassword } from '../core/identity.js';
12
- import { logger } from '../utils/logger.js';
13
- import { SpacesError } from '../types/errors.js';
14
-
15
- // API Configuration
16
- const API_BASE = process.env.GITSPACE_API_URL || 'https://api.gitspace.sh';
17
-
18
- // ============================================================================
19
- // Types
20
- // ============================================================================
21
-
22
- /**
23
- * Host configuration stored in ~/.gitspace/host.json
24
- * Non-sensitive data only - tunnel tokens are in keychain
25
- */
26
- export interface HostConfig {
27
- subdomain: string;
28
- subdomains?: string[];
29
- createdAt: number;
30
- }
31
-
32
- interface SubdomainInfo {
33
- id: string;
34
- subdomain: string;
35
- status: string;
36
- is_primary: number;
37
- created_at: number;
38
- updated_at: number;
39
- }
40
-
41
- interface SubdomainCreateResponse {
42
- id: string;
43
- subdomain: string;
44
- hosts: string[];
45
- isPrimary: boolean;
46
- }
47
-
48
- // ============================================================================
49
- // Host Config Management
50
- // ============================================================================
51
-
52
- /**
53
- * Get the host config file path
54
- */
55
- function getHostConfigPath(): string {
56
- return join(getSpacesDir(), 'host.json');
57
- }
58
-
59
- /**
60
- * Read host config from disk
61
- */
62
- export function readHostConfig(): HostConfig | null {
63
- const configPath = getHostConfigPath();
64
- if (!existsSync(configPath)) {
65
- return null;
66
- }
67
-
68
- try {
69
- const content = readFileSync(configPath, 'utf-8');
70
- return JSON.parse(content) as HostConfig;
71
- } catch {
72
- return null;
73
- }
74
- }
75
-
76
- /**
77
- * Write host config to disk
78
- */
79
- function writeHostConfig(config: HostConfig): void {
80
- const configPath = getHostConfigPath();
81
- writeFileSync(configPath, JSON.stringify(config, null, 2), {
82
- encoding: 'utf-8',
83
- mode: 0o600,
84
- });
85
- }
86
-
87
- /**
88
- * Update host config after subdomain changes
89
- */
90
- async function syncHostConfig(): Promise<void> {
91
- const token = await getSecret('GITSPACE_TOKEN');
92
- if (!token) return;
93
-
94
- try {
95
- const headers = await getAuthHeaders();
96
- const res = await fetch(`${API_BASE}/subdomains`, { headers });
97
-
98
- if (!res.ok) return;
99
-
100
- const subdomains: SubdomainInfo[] = await res.json();
101
- const activeSubdomains = subdomains.filter((s) => s.status === 'active');
102
- const primary = activeSubdomains.find((s) => s.is_primary);
103
-
104
- if (primary) {
105
- writeHostConfig({
106
- subdomain: primary.subdomain,
107
- subdomains: activeSubdomains.map((s) => s.subdomain),
108
- createdAt: primary.created_at,
109
- });
110
- }
111
- } catch {
112
- // Ignore sync errors
113
- }
114
- }
115
-
116
- // ============================================================================
117
- // Helper: Get Auth Token
118
- // ============================================================================
119
-
120
- async function getAuthToken(): Promise<string> {
121
- const token = await getSecret('GITSPACE_TOKEN');
122
- if (!token) {
123
- throw new SpacesError(
124
- 'Not logged in.\n\nRun: gssh auth login',
125
- 'USER_ERROR'
126
- );
127
- }
128
- return token;
129
- }
130
-
131
- async function getAuthHeaders(
132
- extra: Record<string, string> = {}
133
- ): Promise<Record<string, string>> {
134
- const token = await getAuthToken();
135
- const identity = getPublicKeyWithoutPassword();
136
- if (!identity) {
137
- throw new SpacesError(
138
- 'Identity not found.\n\nRun: gssh identity init',
139
- 'USER_ERROR',
140
- 1
141
- );
142
- }
143
- return {
144
- Authorization: `Bearer ${token}`,
145
- 'X-Device-Fingerprint': identity.signingPublicKey,
146
- ...extra,
147
- };
148
- }
149
-
150
- // ============================================================================
151
- // Reserve Subdomain
152
- // ============================================================================
153
-
154
- /**
155
- * Reserve a subdomain on gitspace.sh
156
- */
157
- export async function hostReserve(subdomain: string): Promise<void> {
158
- const headers = await getAuthHeaders();
159
- const jsonHeaders = { ...headers, 'Content-Type': 'application/json' };
160
-
161
- // Normalize subdomain
162
- subdomain = subdomain.toLowerCase().trim();
163
-
164
- // Check availability
165
- logger.info('Checking availability...');
166
- const checkRes = await fetch(
167
- `${API_BASE}/subdomains/check?name=${encodeURIComponent(subdomain)}`,
168
- {
169
- headers,
170
- }
171
- );
172
-
173
- if (!checkRes.ok) {
174
- throw new SpacesError(
175
- `Failed to check availability: ${checkRes.statusText}`,
176
- 'SYSTEM_ERROR'
177
- );
178
- }
179
-
180
- const { available, reason } = await checkRes.json();
181
- if (!available) {
182
- throw new SpacesError(
183
- `Subdomain "${subdomain}" is not available: ${reason}`,
184
- 'USER_ERROR'
185
- );
186
- }
187
-
188
- // Reserve
189
- logger.info('Creating tunnel...');
190
- const res = await fetch(`${API_BASE}/subdomains`, {
191
- method: 'POST',
192
- headers: jsonHeaders,
193
- body: JSON.stringify({ subdomain }),
194
- });
195
-
196
- if (!res.ok) {
197
- const error = await res.json().catch(() => ({ error: res.statusText }));
198
- throw new SpacesError(`Failed to reserve: ${error.error}`, 'USER_ERROR');
199
- }
200
-
201
- const data: SubdomainCreateResponse = await res.json();
202
-
203
- logger.info('Configuring DNS...');
204
-
205
- // Fetch and store tunnel token in keychain
206
- logger.info('Saving credentials...');
207
- const tokenRes = await fetch(
208
- `${API_BASE}/subdomains/${subdomain}/token`,
209
- {
210
- headers,
211
- }
212
- );
213
-
214
- if (!tokenRes.ok) {
215
- throw new SpacesError('Failed to get tunnel token', 'SYSTEM_ERROR');
216
- }
217
-
218
- const { tunnelToken } = await tokenRes.json();
219
- await setSecret(`TUNNEL_TOKEN_${subdomain}`, tunnelToken);
220
-
221
- // Update local host config
222
- await syncHostConfig();
223
-
224
- logger.log('');
225
- logger.success(`Reserved: ${data.subdomain}.gitspace.sh`);
226
- logger.log(` Wildcard: *.${data.subdomain}.gitspace.sh`);
227
- if (data.isPrimary) {
228
- logger.dim(' (set as primary)');
229
- }
230
-
231
- logger.log('');
232
- logger.log("Run 'spaces' to start hosting.");
233
- }
234
-
235
- // ============================================================================
236
- // Release Subdomain
237
- // ============================================================================
238
-
239
- /**
240
- * Release a subdomain
241
- */
242
- export async function hostRelease(subdomain?: string): Promise<void> {
243
- const headers = await getAuthHeaders();
244
-
245
- // If no subdomain specified, show list and exit
246
- if (!subdomain) {
247
- logger.log('Please specify a subdomain to release:');
248
- logger.command(' gssh host release <subdomain>');
249
- logger.log('');
250
- logger.log('To see your subdomains:');
251
- logger.command(' gssh host list');
252
- return;
253
- }
254
-
255
- subdomain = subdomain.toLowerCase().trim();
256
-
257
- const res = await fetch(`${API_BASE}/subdomains/${subdomain}`, {
258
- method: 'DELETE',
259
- headers,
260
- });
261
-
262
- if (!res.ok) {
263
- const error = await res.json().catch(() => ({ error: res.statusText }));
264
- throw new SpacesError(`Failed to release: ${error.error}`, 'USER_ERROR');
265
- }
266
-
267
- // Clear local tunnel token
268
- await deleteSecret(`TUNNEL_TOKEN_${subdomain}`);
269
-
270
- // Update local host config
271
- await syncHostConfig();
272
-
273
- logger.success(`Released: ${subdomain}.gitspace.sh`);
274
- }
275
-
276
- // ============================================================================
277
- // List Subdomains
278
- // ============================================================================
279
-
280
- /**
281
- * List user's subdomains
282
- */
283
- export async function hostList(): Promise<void> {
284
- const headers = await getAuthHeaders();
285
-
286
- const res = await fetch(`${API_BASE}/subdomains`, { headers });
287
-
288
- if (!res.ok) {
289
- throw new SpacesError(
290
- `Failed to list subdomains: ${res.statusText}`,
291
- 'SYSTEM_ERROR'
292
- );
293
- }
294
-
295
- const subdomains: SubdomainInfo[] = await res.json();
296
-
297
- if (subdomains.length === 0) {
298
- logger.log('No subdomains reserved.');
299
- logger.log('');
300
- logger.log('Reserve one with:');
301
- logger.command(' gssh host reserve <name>');
302
- return;
303
- }
304
-
305
- logger.log('Your subdomains:\n');
306
- for (const sub of subdomains) {
307
- const primary = sub.is_primary ? ' (primary)' : '';
308
- const status = sub.status === 'active' ? '\u2713' : '\u2717';
309
- logger.log(` ${status} ${sub.subdomain}.gitspace.sh${primary}`);
310
- logger.dim(` Created: ${new Date(sub.created_at).toLocaleDateString()}`);
311
- }
312
-
313
- logger.log(`\n${subdomains.length}/3 subdomains used (free tier)`);
314
- }
315
-
316
- // ============================================================================
317
- // Set Primary
318
- // ============================================================================
319
-
320
- /**
321
- * Set a subdomain as primary for `gssh serve`
322
- */
323
- export async function hostSetPrimary(subdomain: string): Promise<void> {
324
- const headers = await getAuthHeaders();
325
- subdomain = subdomain.toLowerCase().trim();
326
-
327
- const res = await fetch(`${API_BASE}/subdomains/${subdomain}/set-primary`, {
328
- method: 'POST',
329
- headers,
330
- });
331
-
332
- if (!res.ok) {
333
- const error = await res.json().catch(() => ({ error: res.statusText }));
334
- throw new SpacesError(`Failed: ${error.error}`, 'USER_ERROR');
335
- }
336
-
337
- // Update local host config
338
- await syncHostConfig();
339
-
340
- logger.success(`${subdomain}.gitspace.sh is now your primary subdomain`);
341
- }
342
-
343
- // ============================================================================
344
- // Status
345
- // ============================================================================
346
-
347
- /**
348
- * Show hosting status
349
- */
350
- export async function hostStatus(): Promise<void> {
351
- let headers: Record<string, string>;
352
- try {
353
- headers = await getAuthHeaders();
354
- } catch {
355
- logger.log('Not logged in or identity not found');
356
- logger.dim('Run: gssh auth login');
357
- return;
358
- }
359
-
360
- try {
361
- const res = await fetch(`${API_BASE}/subdomains`, { headers });
362
-
363
- if (!res.ok) {
364
- logger.log('Could not fetch subdomains');
365
- return;
366
- }
367
-
368
- const subdomains: SubdomainInfo[] = await res.json();
369
- const primary = subdomains.find((s) => s.is_primary && s.status === 'active');
370
-
371
- if (!primary) {
372
- logger.log('No primary subdomain set.');
373
- logger.dim('Run: gssh host reserve <name>');
374
- return;
375
- }
376
-
377
- logger.log(`Primary: ${primary.subdomain}.gitspace.sh`);
378
- logger.log(`Status: ${primary.status}`);
379
-
380
- // Check if tunnel token exists locally
381
- const tunnelToken = await getSecret(`TUNNEL_TOKEN_${primary.subdomain}`);
382
- logger.log(`Tunnel token: ${tunnelToken ? 'configured' : 'missing'}`);
383
-
384
- if (!tunnelToken) {
385
- logger.dim('Run: gssh host reserve ' + primary.subdomain + ' (to refresh token)');
386
- }
387
- } catch {
388
- logger.log('Could not verify status (API unreachable)');
389
-
390
- // Show local config
391
- const hostConfig = readHostConfig();
392
- if (hostConfig) {
393
- logger.log(`Local config: ${hostConfig.subdomain}.gitspace.sh`);
394
- }
395
- }
396
- }
@@ -1,184 +0,0 @@
1
- /**
2
- * Identity command implementation
3
- * Handles 'gssh identity init' and 'gssh identity show'
4
- */
5
-
6
- import { createHash } from 'crypto';
7
- import { logger } from '../utils/logger.js';
8
- import { promptPassword, promptInput, promptConfirm } from '../utils/prompts.js';
9
- import {
10
- generateAndSaveKeypair,
11
- loadKeypair,
12
- keypairExists,
13
- getPublicKeyWithoutPassword,
14
- } from '../core/identity.js';
15
- import {
16
- NoIdentityError,
17
- IdentityExistsError,
18
- SpacesError,
19
- } from '../types/errors.js';
20
-
21
- /**
22
- * Initialize a new identity keypair
23
- */
24
- export async function initIdentity(options: { force?: boolean } = {}): Promise<void> {
25
- // Check if keypair already exists
26
- if (keypairExists() && !options.force) {
27
- throw new IdentityExistsError();
28
- }
29
-
30
- // If force flag is set and keypair exists, confirm
31
- if (options.force && keypairExists()) {
32
- const confirmed = await promptConfirm(
33
- 'This will overwrite your existing identity. Are you sure?',
34
- false
35
- );
36
-
37
- if (!confirmed) {
38
- logger.info('Cancelled');
39
- return;
40
- }
41
- }
42
-
43
- // Prompt for password (twice for confirmation)
44
- const password = await promptPassword('Enter password to encrypt your identity:');
45
-
46
- if (!password) {
47
- logger.info('Cancelled');
48
- return;
49
- }
50
-
51
- if (password.length < 8) {
52
- throw new SpacesError(
53
- 'Password must be at least 8 characters long',
54
- 'USER_ERROR',
55
- 1
56
- );
57
- }
58
-
59
- const confirmPassword = await promptPassword('Confirm password:');
60
-
61
- if (!confirmPassword) {
62
- logger.info('Cancelled');
63
- return;
64
- }
65
-
66
- if (password !== confirmPassword) {
67
- throw new SpacesError(
68
- 'Passwords do not match',
69
- 'USER_ERROR',
70
- 1
71
- );
72
- }
73
-
74
- // Prompt for optional label
75
- const label = await promptInput('Enter an optional label for this identity (e.g., "My Laptop"):', {
76
- default: '',
77
- });
78
-
79
- // Generate and save keypair
80
- logger.info('Generating keypair...');
81
- const identity = await generateAndSaveKeypair(
82
- password,
83
- label || undefined,
84
- options.force || false
85
- );
86
-
87
- logger.success('Identity created successfully');
88
-
89
- // Display public key info (identity is PublicIdentity, keys are base64 strings)
90
- const signingKeyBytes = Buffer.from(identity.signingPublicKey, 'base64');
91
- const keyExchangeKeyBytes = Buffer.from(identity.keyExchangePublicKey, 'base64');
92
- const fingerprint = formatFingerprint(signingKeyBytes);
93
- const publicKeyString = formatPublicKey(signingKeyBytes, keyExchangeKeyBytes);
94
-
95
- logger.log('');
96
- logger.bold('Identity Information:');
97
- logger.log(` ID: ${identity.id}`);
98
- logger.log(` Fingerprint: ${fingerprint}`);
99
- if (identity.label) {
100
- logger.log(` Label: ${identity.label}`);
101
- }
102
- logger.log('');
103
- logger.bold('Public Key:');
104
- logger.log(` ${publicKeyString}`);
105
- logger.log('');
106
- logger.dim('Keep your password safe. You will need it to use this identity.');
107
- }
108
-
109
- /**
110
- * Show identity information
111
- */
112
- export async function showIdentity(
113
- options: { fingerprint?: boolean; json?: boolean } = {}
114
- ): Promise<void> {
115
- // Check if keypair exists
116
- if (!keypairExists()) {
117
- throw new NoIdentityError();
118
- }
119
-
120
- // Read public key (no password needed)
121
- const publicIdentity = getPublicKeyWithoutPassword();
122
-
123
- if (!publicIdentity) {
124
- throw new NoIdentityError();
125
- }
126
-
127
- // JSON output
128
- if (options.json) {
129
- console.log(JSON.stringify(publicIdentity, null, 2));
130
- return;
131
- }
132
-
133
- // Fingerprint output
134
- if (options.fingerprint) {
135
- const signingPublicKeyBytes = Buffer.from(publicIdentity.signingPublicKey, 'base64');
136
- const fingerprint = formatFingerprint(signingPublicKeyBytes);
137
- logger.log(fingerprint);
138
- return;
139
- }
140
-
141
- // Default output: full public key
142
- const publicKeyString = formatPublicKey(
143
- Buffer.from(publicIdentity.signingPublicKey, 'base64'),
144
- Buffer.from(publicIdentity.keyExchangePublicKey, 'base64')
145
- );
146
-
147
- logger.bold('Identity Information:');
148
- logger.log(` ID: ${publicIdentity.id}`);
149
- if (publicIdentity.label) {
150
- logger.log(` Label: ${publicIdentity.label}`);
151
- }
152
- logger.log('');
153
- logger.bold('Public Key:');
154
- logger.log(` ${publicKeyString}`);
155
- logger.log('');
156
- logger.bold('Fingerprint:');
157
- logger.log(` ${formatFingerprint(Buffer.from(publicIdentity.signingPublicKey, 'base64'))}`);
158
- }
159
-
160
- /**
161
- * Format fingerprint as first 16 hex chars of SHA-256 hash with colons
162
- */
163
- function formatFingerprint(signingPublicKey: Uint8Array): string {
164
- const hash = createHash('sha256').update(signingPublicKey).digest('hex');
165
- const first16 = hash.substring(0, 16);
166
-
167
- // Add colons every 2 characters
168
- const parts: string[] = [];
169
- for (let i = 0; i < first16.length; i += 2) {
170
- parts.push(first16.substring(i, i + 2));
171
- }
172
-
173
- return parts.join(':');
174
- }
175
-
176
- /**
177
- * Format public key as gssh-pub:BASE64_SIGNING:BASE64_KEYEXCHANGE
178
- */
179
- function formatPublicKey(signingPublicKey: Uint8Array, keyExchangePublicKey: Uint8Array): string {
180
- const signingB64 = Buffer.from(signingPublicKey).toString('base64');
181
- const keyExchangeB64 = Buffer.from(keyExchangePublicKey).toString('base64');
182
-
183
- return `gssh-pub:${signingB64}:${keyExchangeB64}`;
184
- }