gitspace 0.2.0-rc.3 → 0.2.0-rc.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +3 -1
- package/AGENTS.md +28 -16
- package/package.json +5 -5
- package/src/commands/auth.ts +5 -0
- package/src/commands/host.ts +77 -5
- package/src/commands/serve.ts +59 -16
- package/src/commands/share.ts +0 -5
- package/src/index.ts +16 -0
- package/src/lib/tmux-lite/cli.ts +16 -3
- package/src/relay/server.ts +6 -4
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"WebFetch(domain:developers.cloudflare.com)",
|
|
18
18
|
"WebFetch(domain:gitspace.sh)",
|
|
19
19
|
"Bash(/Users/bradleat/spaces/spaces/workspaces/remote-space/dist/gssh-darwin-arm64:*)",
|
|
20
|
-
"Bash(npx wrangler pages deploy:*)"
|
|
20
|
+
"Bash(npx wrangler pages deploy:*)",
|
|
21
|
+
"Bash(git add:*)",
|
|
22
|
+
"Bash(git commit:*)"
|
|
21
23
|
]
|
|
22
24
|
}
|
|
23
25
|
}
|
package/AGENTS.md
CHANGED
|
@@ -215,10 +215,11 @@ src/
|
|
|
215
215
|
| Command | Description |
|
|
216
216
|
|---------|-------------|
|
|
217
217
|
| `gssh relay start` | Start relay server |
|
|
218
|
-
| `gssh relay
|
|
219
|
-
| `gssh relay
|
|
220
|
-
| `gssh relay revoke <id>` | Revoke client authorization |
|
|
218
|
+
| `gssh relay authorize <pubkey>` | Authorize a machine on relay |
|
|
219
|
+
| `gssh relay revoke <id>` | Revoke machine authorization |
|
|
221
220
|
| `gssh relay machines` | List authorized machines |
|
|
221
|
+
| `gssh relay trusted` | List trusted relays |
|
|
222
|
+
| `gssh relay untrust <url>` | Remove relay from trusted list |
|
|
222
223
|
|
|
223
224
|
### tmux-lite Daemon
|
|
224
225
|
| Command | Description |
|
|
@@ -263,10 +264,19 @@ src/
|
|
|
263
264
|
└─────────────────┘ └─────────────────┘ └─────────────────┘
|
|
264
265
|
```
|
|
265
266
|
|
|
266
|
-
### Connection Flow
|
|
267
|
+
### Connection Flow (gitspace.sh Hosting)
|
|
267
268
|
|
|
268
|
-
1.
|
|
269
|
-
2.
|
|
269
|
+
1. User logs in: `gssh auth login` (GitHub OAuth)
|
|
270
|
+
2. User reserves subdomain: `gssh host reserve <name>`
|
|
271
|
+
3. Machine runs `gssh serve start` (auto-starts local relay + cloudflared tunnel)
|
|
272
|
+
4. Client connects via web: `https://<name>.gitspace.sh`
|
|
273
|
+
5. X3DH handshake establishes session keys
|
|
274
|
+
6. All terminal I/O is E2E encrypted
|
|
275
|
+
|
|
276
|
+
### Connection Flow (Self-Hosted Relay)
|
|
277
|
+
|
|
278
|
+
1. Machine runs `gssh serve start --relay ws://relay:4480/ws`
|
|
279
|
+
2. Machine registers with relay (Ed25519 challenge-response auth)
|
|
270
280
|
3. Machine creates invite: `gssh share create`
|
|
271
281
|
4. Client connects with invite: `gssh connect <token>`
|
|
272
282
|
5. X3DH handshake establishes session keys
|
|
@@ -280,7 +290,7 @@ src/
|
|
|
280
290
|
| Key exchange | X25519 |
|
|
281
291
|
| Symmetric encryption | AES-256-GCM |
|
|
282
292
|
| Key derivation | HKDF-SHA256 |
|
|
283
|
-
|
|
|
293
|
+
| Relay authentication | Ed25519 challenge-response |
|
|
284
294
|
|
|
285
295
|
## TUI/Web Shared Component Pattern
|
|
286
296
|
|
|
@@ -322,11 +332,14 @@ bun run build
|
|
|
322
332
|
# Run TUI
|
|
323
333
|
bun src/index.ts
|
|
324
334
|
|
|
325
|
-
# Run relay
|
|
326
|
-
|
|
335
|
+
# Run relay (uses Ed25519 identity from keychain)
|
|
336
|
+
bun src/index.ts relay start
|
|
337
|
+
|
|
338
|
+
# Run serve with gitspace.sh hosting (requires auth login + host reserve)
|
|
339
|
+
bun src/index.ts serve start
|
|
327
340
|
|
|
328
|
-
# Run serve
|
|
329
|
-
bun src/index.ts serve --relay ws://localhost:
|
|
341
|
+
# Run serve with self-hosted relay
|
|
342
|
+
bun src/index.ts serve start --relay ws://localhost:4480/ws
|
|
330
343
|
```
|
|
331
344
|
|
|
332
345
|
### Code Style
|
|
@@ -409,6 +422,7 @@ bun src/index.ts serve --relay ws://localhost:8080/ws --token <jwt>
|
|
|
409
422
|
| Access list | `~/gitspace/.access.json` | Authorized client public keys |
|
|
410
423
|
| Machine info | `~/gitspace/.machine.json` | Machine registration |
|
|
411
424
|
| Relay config | `~/gitspace/.relay.json` | Relay configuration cache |
|
|
425
|
+
| Host config | `~/gitspace/host.json` | gitspace.sh subdomain config |
|
|
412
426
|
| Daemon state | `~/gitspace/.serve/` | PID files, status sockets |
|
|
413
427
|
| tmux-lite state | `/tmp/` | Session data (configurable via `TMUX_LITE_SESSION_DIR`) |
|
|
414
428
|
|
|
@@ -418,9 +432,8 @@ bun src/index.ts serve --relay ws://localhost:8080/ws --token <jwt>
|
|
|
418
432
|
|----------|-------------|---------|
|
|
419
433
|
| `RELAY_PORT` | Relay server port | `4480` |
|
|
420
434
|
| `RELAY_BIND` | Relay bind address | `0.0.0.0` |
|
|
421
|
-
| `RELAY_SIGNING_SECRET` | Secret for HMAC JWT signing | Required |
|
|
422
435
|
| `RELAY_PRIVATE_KEY` | Base64 Ed25519 private key | Uses keychain |
|
|
423
|
-
| `
|
|
436
|
+
| `RELAY_LABEL` | Label for relay identity | None |
|
|
424
437
|
| `GITSPACE_API_URL` | gitspace.sh API URL | `https://api.gitspace.sh` |
|
|
425
438
|
|
|
426
439
|
### Default Values
|
|
@@ -431,9 +444,8 @@ bun src/index.ts serve --relay ws://localhost:8080/ws --token <jwt>
|
|
|
431
444
|
| Base branch | `main` | Global/project config |
|
|
432
445
|
| Stale workspace days | `30` | Global config |
|
|
433
446
|
| Relay port | `4480` | CLI/env |
|
|
434
|
-
| Default relay URL | `wss://relay.gitspace.sh` | CLI |
|
|
435
447
|
|
|
436
448
|
---
|
|
437
449
|
|
|
438
|
-
**Last Updated**:
|
|
439
|
-
**Runtime**: Bun
|
|
450
|
+
**Last Updated**: 2026-01
|
|
451
|
+
**Runtime**: Bun 1.3+
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitspace",
|
|
3
|
-
"version": "0.2.0-rc.
|
|
3
|
+
"version": "0.2.0-rc.4",
|
|
4
4
|
"description": "CLI for managing GitHub workspaces with git worktrees and secure remote terminal access",
|
|
5
5
|
"bin": {
|
|
6
6
|
"gssh": "./bin/gssh"
|
|
@@ -17,10 +17,10 @@
|
|
|
17
17
|
"relay": "bun src/relay/index.ts"
|
|
18
18
|
},
|
|
19
19
|
"optionalDependencies": {
|
|
20
|
-
"@gitspace/darwin-arm64": "0.2.0-rc.
|
|
21
|
-
"@gitspace/darwin-x64": "0.2.0-rc.
|
|
22
|
-
"@gitspace/linux-x64": "0.2.0-rc.
|
|
23
|
-
"@gitspace/linux-arm64": "0.2.0-rc.
|
|
20
|
+
"@gitspace/darwin-arm64": "0.2.0-rc.4",
|
|
21
|
+
"@gitspace/darwin-x64": "0.2.0-rc.4",
|
|
22
|
+
"@gitspace/linux-x64": "0.2.0-rc.4",
|
|
23
|
+
"@gitspace/linux-arm64": "0.2.0-rc.4"
|
|
24
24
|
},
|
|
25
25
|
"keywords": [
|
|
26
26
|
"cli",
|
package/src/commands/auth.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { sign, serializeIdentity } from '../lib/tmux-lite/crypto/identity.js';
|
|
|
12
12
|
import { promptPassword } from '../utils/prompts.js';
|
|
13
13
|
import { logger } from '../utils/logger.js';
|
|
14
14
|
import { NoIdentityError, SpacesError } from '../types/errors.js';
|
|
15
|
+
import { syncHostConfig } from './host.js';
|
|
15
16
|
|
|
16
17
|
// API Configuration
|
|
17
18
|
const API_BASE = process.env.GITSPACE_API_URL || 'https://api.gitspace.sh';
|
|
@@ -194,6 +195,10 @@ export async function authLogin(): Promise<void> {
|
|
|
194
195
|
logger.success('Authentication complete');
|
|
195
196
|
logger.success(`Logged in as ${user.github_username}`);
|
|
196
197
|
logger.success('Token saved to keychain');
|
|
198
|
+
|
|
199
|
+
// Step 6: Sync host config (fetches existing subdomains from API)
|
|
200
|
+
// Interactive mode will prompt user to select primary or reserve a subdomain
|
|
201
|
+
await syncHostConfig(true);
|
|
197
202
|
}
|
|
198
203
|
|
|
199
204
|
// ============================================================================
|
package/src/commands/host.ts
CHANGED
|
@@ -85,9 +85,11 @@ function writeHostConfig(config: HostConfig): void {
|
|
|
85
85
|
}
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
*
|
|
88
|
+
* Sync host config from gitspace.sh API
|
|
89
|
+
* Called after login and subdomain changes to keep local config in sync
|
|
90
|
+
* @param interactive - If true, prompt user to select primary if needed
|
|
89
91
|
*/
|
|
90
|
-
async function syncHostConfig(): Promise<void> {
|
|
92
|
+
export async function syncHostConfig(interactive: boolean = false): Promise<void> {
|
|
91
93
|
const token = await getSecret('GITSPACE_TOKEN');
|
|
92
94
|
if (!token) return;
|
|
93
95
|
|
|
@@ -99,7 +101,40 @@ async function syncHostConfig(): Promise<void> {
|
|
|
99
101
|
|
|
100
102
|
const subdomains: SubdomainInfo[] = await res.json();
|
|
101
103
|
const activeSubdomains = subdomains.filter((s) => s.status === 'active');
|
|
102
|
-
|
|
104
|
+
|
|
105
|
+
// No subdomains - tell user to reserve one
|
|
106
|
+
if (activeSubdomains.length === 0) {
|
|
107
|
+
if (interactive) {
|
|
108
|
+
logger.log('');
|
|
109
|
+
logger.dim('No subdomains reserved yet.');
|
|
110
|
+
logger.dim('To enable remote access, reserve a subdomain:');
|
|
111
|
+
logger.command(' gssh host reserve <name>');
|
|
112
|
+
}
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check for primary
|
|
117
|
+
let primary = activeSubdomains.find((s) => s.is_primary);
|
|
118
|
+
|
|
119
|
+
// If no primary set and interactive, ask user to pick one
|
|
120
|
+
if (!primary && interactive && activeSubdomains.length > 0) {
|
|
121
|
+
logger.log('');
|
|
122
|
+
logger.log('Your subdomains:');
|
|
123
|
+
activeSubdomains.forEach((s, i) => {
|
|
124
|
+
logger.log(` ${i + 1}. ${s.subdomain}.gitspace.sh`);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (activeSubdomains.length === 1) {
|
|
128
|
+
// Auto-set the only one as primary
|
|
129
|
+
primary = activeSubdomains[0];
|
|
130
|
+
logger.dim(`Setting ${primary.subdomain}.gitspace.sh as primary...`);
|
|
131
|
+
await hostSetPrimary(primary.subdomain);
|
|
132
|
+
} else {
|
|
133
|
+
logger.log('');
|
|
134
|
+
logger.dim('Select a primary subdomain for this machine:');
|
|
135
|
+
logger.command(' gssh host set-primary <name>');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
103
138
|
|
|
104
139
|
if (primary) {
|
|
105
140
|
writeHostConfig({
|
|
@@ -107,6 +142,26 @@ async function syncHostConfig(): Promise<void> {
|
|
|
107
142
|
subdomains: activeSubdomains.map((s) => s.subdomain),
|
|
108
143
|
createdAt: primary.created_at,
|
|
109
144
|
});
|
|
145
|
+
|
|
146
|
+
// Sync tunnel token if not present (e.g., new machine with existing account)
|
|
147
|
+
const existingToken = await getSecret(`TUNNEL_TOKEN_${primary.subdomain}`);
|
|
148
|
+
if (!existingToken) {
|
|
149
|
+
if (interactive) {
|
|
150
|
+
logger.dim(`Fetching tunnel credentials for ${primary.subdomain}.gitspace.sh...`);
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const tokenRes = await fetch(`${API_BASE}/subdomains/${primary.subdomain}/token`, { headers });
|
|
154
|
+
if (tokenRes.ok) {
|
|
155
|
+
const { tunnelToken } = await tokenRes.json();
|
|
156
|
+
await setSecret(`TUNNEL_TOKEN_${primary.subdomain}`, tunnelToken);
|
|
157
|
+
if (interactive) {
|
|
158
|
+
logger.success('Tunnel credentials saved');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
} catch {
|
|
162
|
+
// Ignore token fetch errors
|
|
163
|
+
}
|
|
164
|
+
}
|
|
110
165
|
}
|
|
111
166
|
} catch {
|
|
112
167
|
// Ignore sync errors
|
|
@@ -378,11 +433,28 @@ export async function hostStatus(): Promise<void> {
|
|
|
378
433
|
logger.log(`Status: ${primary.status}`);
|
|
379
434
|
|
|
380
435
|
// Check if tunnel token exists locally
|
|
381
|
-
|
|
436
|
+
let tunnelToken = await getSecret(`TUNNEL_TOKEN_${primary.subdomain}`);
|
|
437
|
+
|
|
438
|
+
// Auto-fetch token if missing
|
|
439
|
+
if (!tunnelToken) {
|
|
440
|
+
logger.dim('Tunnel credentials missing, fetching...');
|
|
441
|
+
try {
|
|
442
|
+
const tokenRes = await fetch(`${API_BASE}/subdomains/${primary.subdomain}/token`, { headers });
|
|
443
|
+
if (tokenRes.ok) {
|
|
444
|
+
const { tunnelToken: newToken } = await tokenRes.json();
|
|
445
|
+
await setSecret(`TUNNEL_TOKEN_${primary.subdomain}`, newToken);
|
|
446
|
+
tunnelToken = newToken;
|
|
447
|
+
logger.success('Tunnel credentials synced');
|
|
448
|
+
}
|
|
449
|
+
} catch {
|
|
450
|
+
// Ignore
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
382
454
|
logger.log(`Tunnel token: ${tunnelToken ? 'configured' : 'missing'}`);
|
|
383
455
|
|
|
384
456
|
if (!tunnelToken) {
|
|
385
|
-
logger.
|
|
457
|
+
logger.warning('Could not fetch tunnel credentials');
|
|
386
458
|
}
|
|
387
459
|
} catch {
|
|
388
460
|
logger.log('Could not verify status (API unreachable)');
|
package/src/commands/serve.ts
CHANGED
|
@@ -66,7 +66,7 @@ import {
|
|
|
66
66
|
const PACKAGE_VERSION = '1.0.0';
|
|
67
67
|
|
|
68
68
|
/** Default relay URL */
|
|
69
|
-
|
|
69
|
+
// No default relay - must use hosting or explicit --relay
|
|
70
70
|
|
|
71
71
|
/** Local relay port for gitspace.sh hosting */
|
|
72
72
|
const LOCAL_RELAY_PORT = 4480;
|
|
@@ -503,21 +503,36 @@ export async function serve(options: {
|
|
|
503
503
|
const entries = readAccessList();
|
|
504
504
|
accessList.import(entries);
|
|
505
505
|
|
|
506
|
-
// Step 3:
|
|
506
|
+
// Step 3: Check for gitspace.sh hosting or explicit relay
|
|
507
|
+
const hostConfig = readHostConfig();
|
|
508
|
+
const relayUrl = options.relay; // No default - must use hosting or explicit --relay
|
|
509
|
+
|
|
510
|
+
// If no hosting config and no explicit relay, error out
|
|
511
|
+
if (!hostConfig?.subdomain && !relayUrl) {
|
|
512
|
+
throw new SpacesError(
|
|
513
|
+
'No relay configured.\n\n' +
|
|
514
|
+
'Either set up gitspace.sh hosting:\n' +
|
|
515
|
+
' gssh auth login\n' +
|
|
516
|
+
' gssh host reserve <subdomain>\n\n' +
|
|
517
|
+
'Or specify a relay explicitly:\n' +
|
|
518
|
+
' gssh serve start --relay ws://localhost:4480/ws',
|
|
519
|
+
'USER_ERROR'
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Display info
|
|
507
524
|
const machineIdentity = readMachineIdentity();
|
|
508
525
|
const machineId = machineIdentity?.machineId ?? identity.id;
|
|
509
|
-
const relayUrl = options.relay ?? DEFAULT_RELAY_URL;
|
|
510
526
|
|
|
511
527
|
logger.log('');
|
|
512
528
|
logger.bold('Machine Identity:');
|
|
513
529
|
logger.log(` ID: ${machineId}`);
|
|
514
|
-
|
|
530
|
+
if (relayUrl) {
|
|
531
|
+
logger.log(` Relay: ${relayUrl}`);
|
|
532
|
+
}
|
|
515
533
|
logger.log('');
|
|
516
534
|
logger.dim(`Access list: ${entries.length} authorized ${entries.length === 1 ? 'client' : 'clients'}`);
|
|
517
535
|
logger.log('');
|
|
518
|
-
|
|
519
|
-
// Step 3b: Check for gitspace.sh hosting
|
|
520
|
-
const hostConfig = readHostConfig();
|
|
521
536
|
let localRelayServer: ReturnType<typeof createRelayServer> | null = null;
|
|
522
537
|
let localRelayIdentity: ReturnType<typeof generateRelayIdentity> | null = null;
|
|
523
538
|
|
|
@@ -608,6 +623,11 @@ export async function serve(options: {
|
|
|
608
623
|
return;
|
|
609
624
|
}
|
|
610
625
|
|
|
626
|
+
// If we get here, relayUrl must be defined (checked earlier)
|
|
627
|
+
if (!relayUrl) {
|
|
628
|
+
throw new SpacesError('No relay URL configured', 'USER_ERROR');
|
|
629
|
+
}
|
|
630
|
+
|
|
611
631
|
// Step 4: Create session manager
|
|
612
632
|
const sessionManager = new ClientSessionManager({
|
|
613
633
|
relay: relayUrl,
|
|
@@ -1208,10 +1228,18 @@ export async function serveStart(options: {
|
|
|
1208
1228
|
logger.log('Starting serve daemon...');
|
|
1209
1229
|
|
|
1210
1230
|
// Build args for background process
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1231
|
+
// Detect if we're running as a compiled binary vs dev mode
|
|
1232
|
+
const isCompiled = !process.execPath.endsWith('bun');
|
|
1233
|
+
|
|
1234
|
+
const serveArgs = ['serve', 'start', '--foreground'];
|
|
1235
|
+
if (options.relay) serveArgs.push('--relay', options.relay);
|
|
1236
|
+
if (options.relayPubkey) serveArgs.push('--relay-pubkey', options.relayPubkey);
|
|
1237
|
+
serveArgs.push('--password-stdin');
|
|
1238
|
+
|
|
1239
|
+
// Build command: compiled binary runs directly, dev mode uses bun
|
|
1240
|
+
const cmd = isCompiled
|
|
1241
|
+
? [process.execPath, ...serveArgs]
|
|
1242
|
+
: ['bun', process.argv[1], ...serveArgs];
|
|
1215
1243
|
|
|
1216
1244
|
// Write output to log file for debugging
|
|
1217
1245
|
const logFile = getServeLogFile();
|
|
@@ -1221,7 +1249,7 @@ export async function serveStart(options: {
|
|
|
1221
1249
|
await Bun.write(logFile, `[${new Date().toISOString()}] Starting serve daemon...\n`);
|
|
1222
1250
|
|
|
1223
1251
|
const child = spawn({
|
|
1224
|
-
cmd
|
|
1252
|
+
cmd,
|
|
1225
1253
|
stdin: 'pipe',
|
|
1226
1254
|
stdout: Bun.file(logFile),
|
|
1227
1255
|
stderr: Bun.file(logFile),
|
|
@@ -1269,20 +1297,35 @@ export async function serveStart(options: {
|
|
|
1269
1297
|
// Get config
|
|
1270
1298
|
const machineIdentity = readMachineIdentity();
|
|
1271
1299
|
const machineId = machineIdentity?.machineId ?? identity.id;
|
|
1272
|
-
const relayUrl = options.relay ?? DEFAULT_RELAY_URL;
|
|
1273
1300
|
|
|
1274
1301
|
// Check for gitspace.sh hosting
|
|
1275
1302
|
const hostConfig = readHostConfig();
|
|
1303
|
+
const relayUrl = options.relay; // No default - must use hosting or explicit --relay
|
|
1304
|
+
|
|
1305
|
+
// If no hosting config and no explicit relay, error out
|
|
1306
|
+
if (!hostConfig?.subdomain && !relayUrl) {
|
|
1307
|
+
cleanupServeFiles();
|
|
1308
|
+
throw new SpacesError(
|
|
1309
|
+
'No relay configured.\n\n' +
|
|
1310
|
+
'Either set up gitspace.sh hosting:\n' +
|
|
1311
|
+
' gssh auth login\n' +
|
|
1312
|
+
' gssh host reserve <subdomain>\n\n' +
|
|
1313
|
+
'Or specify a relay explicitly:\n' +
|
|
1314
|
+
' gssh serve start --relay ws://localhost:4480/ws',
|
|
1315
|
+
'USER_ERROR'
|
|
1316
|
+
);
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1276
1319
|
let localRelayServer: ReturnType<typeof createRelayServer> | null = null;
|
|
1277
1320
|
let localRelayIdentity: ReturnType<typeof generateRelayIdentity> | null = null;
|
|
1278
|
-
let effectiveRelayUrl = relayUrl;
|
|
1321
|
+
let effectiveRelayUrl = relayUrl || '';
|
|
1279
1322
|
|
|
1280
1323
|
// Initialize daemon state
|
|
1281
1324
|
setDaemonState({
|
|
1282
1325
|
version: PACKAGE_VERSION,
|
|
1283
1326
|
startTime: Date.now(),
|
|
1284
1327
|
relay: {
|
|
1285
|
-
url:
|
|
1328
|
+
url: effectiveRelayUrl,
|
|
1286
1329
|
status: 'connecting',
|
|
1287
1330
|
},
|
|
1288
1331
|
clients: 0,
|
|
@@ -1373,7 +1416,7 @@ export async function serveStart(options: {
|
|
|
1373
1416
|
|
|
1374
1417
|
// Save relay config for share/access commands
|
|
1375
1418
|
writeRelayConfig({
|
|
1376
|
-
relayUrl,
|
|
1419
|
+
relayUrl: effectiveRelayUrl,
|
|
1377
1420
|
machineId,
|
|
1378
1421
|
savedAt: Date.now(),
|
|
1379
1422
|
});
|
package/src/commands/share.ts
CHANGED
|
@@ -27,11 +27,6 @@ import { SpacesError, NoIdentityError, InvalidPasswordError } from '../types/err
|
|
|
27
27
|
import chalk from 'chalk';
|
|
28
28
|
import { createHash } from 'crypto';
|
|
29
29
|
|
|
30
|
-
/**
|
|
31
|
-
* Default relay URL
|
|
32
|
-
*/
|
|
33
|
-
const DEFAULT_RELAY_URL = 'wss://relay.gitspace.sh';
|
|
34
|
-
|
|
35
30
|
/**
|
|
36
31
|
* Duration string formats supported:
|
|
37
32
|
* - "1h" -> 1 hour
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,22 @@
|
|
|
5
5
|
* Manages GitHub workspaces with git worktrees and secure remote terminal access
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
// Internal command: run tmux-lite server directly (for compiled binary)
|
|
9
|
+
// This must be checked before any other imports to avoid loading unnecessary modules
|
|
10
|
+
if (process.argv.includes('--internal-tmux-server')) {
|
|
11
|
+
// Pass through --test flag if present
|
|
12
|
+
if (process.argv.includes('--test')) {
|
|
13
|
+
process.env.TMUX_LITE_SOCKET = '/tmp/tmux-lite-test.sock';
|
|
14
|
+
process.env.TMUX_LITE_SESSION_DIR = '/tmp/tmux-lite-test';
|
|
15
|
+
process.env.TMUX_LITE_PID_FILE = '/tmp/tmux-lite-test.pid';
|
|
16
|
+
}
|
|
17
|
+
// Import and run server (module auto-starts on import)
|
|
18
|
+
await import('./lib/tmux-lite/server.js');
|
|
19
|
+
// Keep process alive - server runs via Bun.listen() which is async
|
|
20
|
+
// We need to prevent the rest of this file from executing
|
|
21
|
+
await new Promise(() => {}); // Block forever
|
|
22
|
+
}
|
|
23
|
+
|
|
8
24
|
import { Command } from 'commander'
|
|
9
25
|
import { readFileSync } from 'fs'
|
|
10
26
|
import { join } from 'path'
|
package/src/lib/tmux-lite/cli.ts
CHANGED
|
@@ -67,9 +67,22 @@ if (isTestMode) {
|
|
|
67
67
|
process.env.TMUX_LITE_SESSION_DIR = "/tmp/tmux-lite-test";
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const getServerCommand = (): string[] =>
|
|
71
|
-
|
|
72
|
-
);
|
|
70
|
+
const getServerCommand = (): string[] => {
|
|
71
|
+
// Detect if we're running as a compiled binary (not bun)
|
|
72
|
+
const isCompiled = !process.execPath.endsWith('bun');
|
|
73
|
+
|
|
74
|
+
if (isCompiled) {
|
|
75
|
+
// Use the binary with internal flag
|
|
76
|
+
return isTestMode
|
|
77
|
+
? [process.execPath, '--internal-tmux-server', '--test']
|
|
78
|
+
: [process.execPath, '--internal-tmux-server'];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Dev mode: use bun run
|
|
82
|
+
return isTestMode
|
|
83
|
+
? ['bun', 'run', SERVER_SCRIPT, '--test']
|
|
84
|
+
: ['bun', 'run', SERVER_SCRIPT];
|
|
85
|
+
};
|
|
73
86
|
|
|
74
87
|
// Check if we're already inside a tmux-lite session
|
|
75
88
|
export function isNested(): boolean {
|
package/src/relay/server.ts
CHANGED
|
@@ -68,25 +68,27 @@ function getContentType(pathname: string): string {
|
|
|
68
68
|
* Serve a static file - tries embedded assets first, falls back to filesystem
|
|
69
69
|
*/
|
|
70
70
|
async function serveStaticFile(pathname: string): Promise<Response | null> {
|
|
71
|
+
// Normalize path for content type (/ -> /index.html)
|
|
72
|
+
const normalizedPath = pathname === "/" ? "/index.html" : pathname;
|
|
73
|
+
|
|
71
74
|
// Try embedded assets first (compiled binary)
|
|
72
75
|
if (hasEmbeddedAssets() && embeddedAssets) {
|
|
73
76
|
const blob = embeddedAssets.getEmbeddedFile(pathname);
|
|
74
77
|
if (blob) {
|
|
75
78
|
return new Response(blob, {
|
|
76
|
-
headers: { "Content-Type": getContentType(
|
|
79
|
+
headers: { "Content-Type": getContentType(normalizedPath) },
|
|
77
80
|
});
|
|
78
81
|
}
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
// Fall back to filesystem (development mode)
|
|
82
|
-
const
|
|
83
|
-
const resolvedPath = resolveAssetPath(filePath);
|
|
85
|
+
const resolvedPath = resolveAssetPath(normalizedPath);
|
|
84
86
|
if (!resolvedPath) return null;
|
|
85
87
|
|
|
86
88
|
const file = Bun.file(resolvedPath);
|
|
87
89
|
if (await file.exists()) {
|
|
88
90
|
return new Response(file, {
|
|
89
|
-
headers: { "Content-Type": getContentType(
|
|
91
|
+
headers: { "Content-Type": getContentType(normalizedPath) },
|
|
90
92
|
});
|
|
91
93
|
}
|
|
92
94
|
|