teleportation-cli 1.0.0 → 1.0.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.
@@ -207,6 +207,7 @@ function commandHelp() {
207
207
 
208
208
  console.log(c.yellow('Authentication:'));
209
209
  console.log(' ' + c.green('login') + ' Authenticate with API key or token');
210
+ console.log(' ' + c.green('github connect') + ' Save GitHub token for repo access (used by remote sessions)');
210
211
  console.log(' ' + c.green('logout') + ' Clear saved credentials\n');
211
212
 
212
213
  console.log(c.yellow('Setup Commands:'));
@@ -248,6 +249,14 @@ function commandHelp() {
248
249
  console.log(' ' + c.green('config edit') + ' Open config in editor');
249
250
  console.log(' ' + c.green('env') + ' Show environment variables\n');
250
251
 
252
+ console.log(c.yellow('Remote Sessions:'));
253
+ console.log(' ' + c.green('remote start') + ' Start a remote AI agent session');
254
+ console.log(' ' + c.green('remote list') + ' List all remote sessions');
255
+ console.log(' ' + c.green('remote status') + ' Show remote session details');
256
+ console.log(' ' + c.green('remote logs') + ' View logs from remote session');
257
+ console.log(' ' + c.green('remote pull') + ' Pull results from remote session');
258
+ console.log(' ' + c.green('remote help') + ' Show remote commands help\n');
259
+
251
260
  console.log(c.yellow('Session Isolation:'));
252
261
  console.log(' ' + c.green('worktree create') + ' Create isolated worktree for a session');
253
262
  console.log(' ' + c.green('worktree list') + ' List all session worktrees');
@@ -385,7 +394,7 @@ async function commandSetup() {
385
394
  }
386
395
 
387
396
  // Step 1: Authentication
388
- console.log('\n' + c.purple('Step 1 of 4: Authentication\n'));
397
+ console.log('\n' + c.purple('Step 1 of 5: Authentication\n'));
389
398
  console.log(' You\'ll need to sign in to get an API key.\n');
390
399
 
391
400
  // Try to open browser
@@ -437,7 +446,7 @@ async function commandSetup() {
437
446
  console.log(c.green(' ✅ API key validated successfully!'));
438
447
 
439
448
  // Step 2: Configuration
440
- console.log('\n' + c.purple('Step 2 of 4: Configuration\n'));
449
+ console.log('\n' + c.purple('Step 2 of 5: Configuration\n'));
441
450
  console.log(` Relay URL: ${c.cyan(relayUrl)}`);
442
451
 
443
452
  // Test relay connectivity
@@ -484,7 +493,7 @@ async function commandSetup() {
484
493
  }
485
494
 
486
495
  // Step 3: Install Hooks
487
- console.log('\n' + c.purple('Step 3 of 4: Installing Hooks\n'));
496
+ console.log('\n' + c.purple('Step 3 of 5: Installing Hooks\n'));
488
497
  console.log(' Installing Claude Code hooks to ~/.claude/hooks/');
489
498
 
490
499
  try {
@@ -510,12 +519,37 @@ async function commandSetup() {
510
519
  return;
511
520
  }
512
521
 
513
- // Step 4: Verification
514
- console.log('\n' + c.purple('Step 4 of 4: Verification\n'));
522
+ // Step 4: Start Daemon
523
+ console.log('\n' + c.purple('Step 4 of 5: Starting Daemon\n'));
524
+ console.log(' Starting the teleportation daemon for remote commands...');
525
+
526
+ try {
527
+ const lifecyclePath = path.join(TELEPORTATION_DIR, 'lib', 'daemon', 'lifecycle.js');
528
+ const { access } = await import('fs/promises');
529
+ try {
530
+ await access(lifecyclePath);
531
+ } catch {
532
+ throw new Error('Daemon module not found. Please reinstall: npm install -g teleportation-cli');
533
+ }
534
+ const { startDaemon } = await import('file://' + lifecyclePath);
535
+ const result = await startDaemon({ detached: true, silent: true });
536
+ console.log(c.green(` ✅ Daemon started (PID: ${result.pid})`));
537
+ } catch (e) {
538
+ if (e.message && e.message.includes('already running')) {
539
+ console.log(c.green(' ✅ Daemon already running'));
540
+ } else {
541
+ console.log(c.yellow(` ⚠️ Could not start daemon: ${e.message}`));
542
+ console.log(c.cyan(' You can start it manually with: teleportation daemon start'));
543
+ }
544
+ }
545
+
546
+ // Step 5: Verification
547
+ console.log('\n' + c.purple('Step 5 of 5: Verification\n'));
515
548
  console.log(c.green(' ✅ Credentials saved'));
516
549
  console.log(c.green(' ✅ Configuration saved'));
517
550
  console.log(c.green(' ✅ Hooks installed'));
518
551
  console.log(c.green(' ✅ Relay connectivity confirmed'));
552
+ console.log(c.green(' ✅ Daemon running'));
519
553
 
520
554
  // Success!
521
555
  console.log('\n' + c.cyan('╭─────────────────────────────────────────────────────╮'));
@@ -1545,7 +1579,8 @@ async function commandLogin(args) {
1545
1579
 
1546
1580
  // Check for existing credentials
1547
1581
  const existing = await manager.load();
1548
- if (existing) {
1582
+ const hasRelayCreds = !!(existing && (existing.relayApiKey || existing.apiKey || existing.accessToken));
1583
+ if (hasRelayCreds) {
1549
1584
  console.log(c.yellow('⚠️ You are already logged in.'));
1550
1585
  console.log(c.cyan(' Run "teleportation logout" to clear existing credentials.\n'));
1551
1586
 
@@ -1608,7 +1643,9 @@ async function performLogin(manager, flags, positional) {
1608
1643
  }
1609
1644
 
1610
1645
  // Save credentials
1646
+ const existingCredsForMerge = await manager.load().catch(() => null);
1611
1647
  const credentials = {
1648
+ ...(existingCredsForMerge && typeof existingCredsForMerge === 'object' ? existingCredsForMerge : {}),
1612
1649
  apiKey: apiKey,
1613
1650
  relayApiUrl: relayApiUrl,
1614
1651
  authenticatedAt: Date.now(),
@@ -1629,6 +1666,29 @@ async function performLogin(manager, flags, positional) {
1629
1666
 
1630
1667
  console.log(c.green('✅ Successfully authenticated with API key!\n'));
1631
1668
  console.log(c.cyan('Credentials saved to ~/.teleportation/credentials\n'));
1669
+
1670
+ // Auto-start daemon
1671
+ console.log(c.cyan('Starting daemon for remote commands...'));
1672
+ try {
1673
+ const lifecyclePath = path.join(TELEPORTATION_DIR, 'lib', 'daemon', 'lifecycle.js');
1674
+ const { access } = await import('fs/promises');
1675
+ try {
1676
+ await access(lifecyclePath);
1677
+ } catch {
1678
+ throw new Error('Daemon module not found. Please reinstall: npm install -g teleportation-cli');
1679
+ }
1680
+ const { startDaemon } = await import('file://' + lifecyclePath);
1681
+ const result = await startDaemon({ detached: true, silent: true });
1682
+ console.log(c.green(`✅ Daemon started (PID: ${result.pid})\n`));
1683
+ } catch (e) {
1684
+ if (e.message && e.message.includes('already running')) {
1685
+ console.log(c.green('✅ Daemon already running\n'));
1686
+ } else {
1687
+ console.log(c.yellow(`⚠️ Could not start daemon: ${e.message}`));
1688
+ console.log(c.cyan('You can start it manually with: teleportation daemon start\n'));
1689
+ }
1690
+ }
1691
+
1632
1692
  console.log(c.yellow('⚠️ Restart Claude Code to apply changes to current session.\n'));
1633
1693
  return;
1634
1694
  } catch (error) {
@@ -1643,7 +1703,9 @@ async function performLogin(manager, flags, positional) {
1643
1703
 
1644
1704
  try {
1645
1705
  // Save credentials with token
1706
+ const existingCredsForMerge = await manager.load().catch(() => null);
1646
1707
  const credentials = {
1708
+ ...(existingCredsForMerge && typeof existingCredsForMerge === 'object' ? existingCredsForMerge : {}),
1647
1709
  accessToken: token,
1648
1710
  relayApiUrl: relayApiUrl,
1649
1711
  authenticatedAt: Date.now(),
@@ -1664,6 +1726,29 @@ async function performLogin(manager, flags, positional) {
1664
1726
 
1665
1727
  console.log(c.green('✅ Successfully authenticated with token!\n'));
1666
1728
  console.log(c.cyan('Credentials saved to ~/.teleportation/credentials\n'));
1729
+
1730
+ // Auto-start daemon
1731
+ console.log(c.cyan('Starting daemon for remote commands...'));
1732
+ try {
1733
+ const lifecyclePath = path.join(TELEPORTATION_DIR, 'lib', 'daemon', 'lifecycle.js');
1734
+ const { access } = await import('fs/promises');
1735
+ try {
1736
+ await access(lifecyclePath);
1737
+ } catch {
1738
+ throw new Error('Daemon module not found. Please reinstall: npm install -g teleportation-cli');
1739
+ }
1740
+ const { startDaemon } = await import('file://' + lifecyclePath);
1741
+ const result = await startDaemon({ detached: true, silent: true });
1742
+ console.log(c.green(`✅ Daemon started (PID: ${result.pid})\n`));
1743
+ } catch (e) {
1744
+ if (e.message && e.message.includes('already running')) {
1745
+ console.log(c.green('✅ Daemon already running\n'));
1746
+ } else {
1747
+ console.log(c.yellow(`⚠️ Could not start daemon: ${e.message}`));
1748
+ console.log(c.cyan('You can start it manually with: teleportation daemon start\n'));
1749
+ }
1750
+ }
1751
+
1667
1752
  console.log(c.yellow('⚠️ Restart Claude Code to apply changes to current session.\n'));
1668
1753
  return;
1669
1754
  } catch (error) {
@@ -1709,7 +1794,9 @@ async function performLogin(manager, flags, positional) {
1709
1794
  }
1710
1795
 
1711
1796
  // Save credentials
1797
+ const existingCredsForMerge = await manager.load().catch(() => null);
1712
1798
  const credentials = {
1799
+ ...(existingCredsForMerge && typeof existingCredsForMerge === 'object' ? existingCredsForMerge : {}),
1713
1800
  apiKey: apiKey,
1714
1801
  relayApiUrl: relayApiUrl,
1715
1802
  authenticatedAt: Date.now(),
@@ -1719,6 +1806,29 @@ async function performLogin(manager, flags, positional) {
1719
1806
  await manager.save(credentials);
1720
1807
  console.log(c.green('✅ Successfully authenticated!\n'));
1721
1808
  console.log(c.cyan('Credentials saved to ~/.teleportation/credentials\n'));
1809
+
1810
+ // Auto-start daemon
1811
+ console.log(c.cyan('Starting daemon for remote commands...'));
1812
+ try {
1813
+ const lifecyclePath = path.join(TELEPORTATION_DIR, 'lib', 'daemon', 'lifecycle.js');
1814
+ const { access } = await import('fs/promises');
1815
+ try {
1816
+ await access(lifecyclePath);
1817
+ } catch {
1818
+ throw new Error('Daemon module not found. Please reinstall: npm install -g teleportation-cli');
1819
+ }
1820
+ const { startDaemon } = await import('file://' + lifecyclePath);
1821
+ const result = await startDaemon({ detached: true, silent: true });
1822
+ console.log(c.green(`✅ Daemon started (PID: ${result.pid})\n`));
1823
+ } catch (e) {
1824
+ if (e.message && e.message.includes('already running')) {
1825
+ console.log(c.green('✅ Daemon already running\n'));
1826
+ } else {
1827
+ console.log(c.yellow(`⚠️ Could not start daemon: ${e.message}`));
1828
+ console.log(c.cyan('You can start it manually with: teleportation daemon start\n'));
1829
+ }
1830
+ }
1831
+
1722
1832
  resolve();
1723
1833
  } catch (error) {
1724
1834
  console.log(c.red(`❌ Error: ${error.message}\n`));
@@ -1880,6 +1990,128 @@ async function commandLogout() {
1880
1990
  });
1881
1991
  }
1882
1992
 
1993
+ async function validateGitHubToken(token) {
1994
+ const response = await fetch('https://api.github.com/user', {
1995
+ headers: {
1996
+ 'Authorization': `Bearer ${token}`,
1997
+ 'User-Agent': 'teleportation-cli'
1998
+ }
1999
+ });
2000
+
2001
+ if (!response.ok) {
2002
+ let message = response.statusText;
2003
+ try {
2004
+ const body = await response.json();
2005
+ message = body?.message || message;
2006
+ } catch {
2007
+ // ignore
2008
+ }
2009
+ throw new Error(`GitHub token validation failed (${response.status}): ${message}`);
2010
+ }
2011
+
2012
+ return response.json();
2013
+ }
2014
+
2015
+ async function commandGithub(args) {
2016
+ const subCommand = args[0] || 'help';
2017
+ const { flags } = parseFlags(args.slice(1));
2018
+
2019
+ const manager = await loadCredentialManager();
2020
+ if (!manager) {
2021
+ console.log(c.red('❌ Failed to load credential manager'));
2022
+ process.exit(1);
2023
+ }
2024
+
2025
+ if (subCommand === 'connect') {
2026
+ let token = flags.token || flags.t;
2027
+
2028
+ if (!token) {
2029
+ const readline = require('readline').createInterface({
2030
+ input: process.stdin,
2031
+ output: process.stdout
2032
+ });
2033
+
2034
+ token = await new Promise((resolve) => {
2035
+ readline.question('Enter your GitHub token (fine-grained PAT recommended): ', (answer) => {
2036
+ readline.close();
2037
+ resolve(answer?.trim() || '');
2038
+ });
2039
+ });
2040
+ }
2041
+
2042
+ if (!token) {
2043
+ console.log(c.yellow('Connect cancelled.\n'));
2044
+ return;
2045
+ }
2046
+
2047
+ try {
2048
+ console.log(c.yellow('Validating GitHub token...\n'));
2049
+ const user = await validateGitHubToken(token);
2050
+
2051
+ const existing = await manager.load().catch(() => null);
2052
+ const next = {
2053
+ ...(existing && typeof existing === 'object' ? existing : {}),
2054
+ githubToken: token,
2055
+ githubUser: user?.login || '',
2056
+ githubConnectedAt: Date.now()
2057
+ };
2058
+
2059
+ await manager.save(next);
2060
+ loadedCredentials = next;
2061
+
2062
+ console.log(c.green('✅ GitHub connected!\n'));
2063
+ if (user?.login) {
2064
+ console.log(c.cyan(`GitHub user: ${user.login}`));
2065
+ }
2066
+ console.log(c.cyan('GitHub token saved to ~/.teleportation/credentials\n'));
2067
+ return;
2068
+ } catch (error) {
2069
+ console.log(c.red(`❌ Error: ${error.message}\n`));
2070
+ process.exit(1);
2071
+ }
2072
+ }
2073
+
2074
+ if (subCommand === 'status') {
2075
+ const existing = await manager.load().catch(() => null);
2076
+ const token = existing?.githubToken;
2077
+ console.log(c.yellow('GitHub Credentials:\n'));
2078
+ console.log(' Connected:', token ? c.green('yes') : c.red('no'));
2079
+ if (token) {
2080
+ console.log(' Token:', '***' + token.slice(-4));
2081
+ }
2082
+ if (existing?.githubUser) {
2083
+ console.log(' User:', existing.githubUser);
2084
+ }
2085
+ console.log();
2086
+ return;
2087
+ }
2088
+
2089
+ if (subCommand === 'disconnect') {
2090
+ const existing = await manager.load().catch(() => null);
2091
+ if (!existing || !existing.githubToken) {
2092
+ console.log(c.yellow('⚠️ No GitHub token saved.\n'));
2093
+ return;
2094
+ }
2095
+
2096
+ const next = { ...(existing && typeof existing === 'object' ? existing : {}) };
2097
+ delete next.githubToken;
2098
+ delete next.githubUser;
2099
+ delete next.githubConnectedAt;
2100
+
2101
+ await manager.save(next);
2102
+ loadedCredentials = next;
2103
+
2104
+ console.log(c.green('✅ GitHub disconnected.\n'));
2105
+ return;
2106
+ }
2107
+
2108
+ console.log(c.purple('Teleportation GitHub\n'));
2109
+ console.log(c.yellow('Usage:'));
2110
+ console.log(' teleportation github connect --token <token>');
2111
+ console.log(' teleportation github status');
2112
+ console.log(' teleportation github disconnect\n');
2113
+ }
2114
+
1883
2115
  // Worktree/Snapshot/Session command handlers
1884
2116
  async function commandWorktree(args) {
1885
2117
  const cliPath = path.join(TELEPORTATION_DIR, 'lib', 'cli', 'index.js');
@@ -1920,6 +2152,170 @@ async function commandSession(args) {
1920
2152
  await routeCommand(parsed);
1921
2153
  }
1922
2154
 
2155
+ /**
2156
+ * Handle remote session commands
2157
+ */
2158
+ async function commandRemote(args) {
2159
+ const subCommand = args[0] || 'help';
2160
+
2161
+ // Parse flags
2162
+ const { flags, positional } = parseFlags(args.slice(1));
2163
+
2164
+ try {
2165
+ // Dynamically import remote commands
2166
+ const remoteCommandsPath = path.join(TELEPORTATION_DIR, 'lib', 'cli', 'remote-commands.js');
2167
+ const {
2168
+ commandRemoteStart,
2169
+ commandRemoteList,
2170
+ commandRemoteStatus,
2171
+ commandRemoteLogs,
2172
+ commandRemotePull,
2173
+ commandRemoteStop,
2174
+ commandRemoteResume,
2175
+ commandRemoteDestroy
2176
+ } = await import('file://' + remoteCommandsPath);
2177
+
2178
+ switch (subCommand) {
2179
+ case 'start':
2180
+ // Parse command: teleportation remote start --task "description" --branch "branch-name" [--provider fly|daytona]
2181
+ const task = flags.task;
2182
+ const branch = flags.branch;
2183
+ const provider = flags.provider;
2184
+ const noPr = flags['no-pr'] || false;
2185
+
2186
+ await commandRemoteStart({
2187
+ task,
2188
+ branch,
2189
+ provider,
2190
+ noPr
2191
+ });
2192
+ break;
2193
+
2194
+ case 'list':
2195
+ case 'ls':
2196
+ // Parse command: teleportation remote list [--status running|paused|completed|failed] [--provider fly|daytona]
2197
+ await commandRemoteList({
2198
+ status: flags.status,
2199
+ provider: flags.provider
2200
+ });
2201
+ break;
2202
+
2203
+ case 'status':
2204
+ // Parse command: teleportation remote status <session-id>
2205
+ const statusSessionId = positional[0] || flags['session-id'] || flags.id;
2206
+ if (!statusSessionId) {
2207
+ console.log(c.red('❌ Error: Session ID is required\n'));
2208
+ console.log(c.cyan('Usage: teleportation remote status <session-id>\n'));
2209
+ process.exit(1);
2210
+ }
2211
+ await commandRemoteStatus(statusSessionId);
2212
+ break;
2213
+
2214
+ case 'logs':
2215
+ // Parse command: teleportation remote logs <session-id> [--follow] [--tail N]
2216
+ const logsSessionId = positional[0] || flags['session-id'] || flags.id;
2217
+ if (!logsSessionId) {
2218
+ console.log(c.red('❌ Error: Session ID is required\n'));
2219
+ console.log(c.cyan('Usage: teleportation remote logs <session-id> [--follow] [--tail N]\n'));
2220
+ process.exit(1);
2221
+ }
2222
+ await commandRemoteLogs(logsSessionId, {
2223
+ follow: flags.follow || false,
2224
+ tail: flags.tail ? parseInt(flags.tail, 10) : undefined
2225
+ });
2226
+ break;
2227
+
2228
+ case 'pull':
2229
+ // Parse command: teleportation remote pull <session-id>
2230
+ const pullSessionId = positional[0] || flags['session-id'] || flags.id;
2231
+ if (!pullSessionId) {
2232
+ console.log(c.red('❌ Error: Session ID is required\n'));
2233
+ console.log(c.cyan('Usage: teleportation remote pull <session-id>\n'));
2234
+ process.exit(1);
2235
+ }
2236
+ await commandRemotePull(pullSessionId);
2237
+ break;
2238
+
2239
+ case 'stop':
2240
+ // Parse command: teleportation remote stop <session-id>
2241
+ const stopSessionId = positional[0] || flags['session-id'] || flags.id;
2242
+ if (!stopSessionId) {
2243
+ console.log(c.red('❌ Error: Session ID is required\n'));
2244
+ console.log(c.cyan('Usage: teleportation remote stop <session-id>\n'));
2245
+ process.exit(1);
2246
+ }
2247
+ await commandRemoteStop(stopSessionId);
2248
+ break;
2249
+
2250
+ case 'resume':
2251
+ // Parse command: teleportation remote resume <session-id>
2252
+ const resumeSessionId = positional[0] || flags['session-id'] || flags.id;
2253
+ if (!resumeSessionId) {
2254
+ console.log(c.red('❌ Error: Session ID is required\n'));
2255
+ console.log(c.cyan('Usage: teleportation remote resume <session-id>\n'));
2256
+ process.exit(1);
2257
+ }
2258
+ await commandRemoteResume(resumeSessionId);
2259
+ break;
2260
+
2261
+ case 'destroy':
2262
+ // Parse command: teleportation remote destroy <session-id> --confirm
2263
+ const destroySessionId = positional[0] || flags['session-id'] || flags.id;
2264
+ if (!destroySessionId) {
2265
+ console.log(c.red('❌ Error: Session ID is required\n'));
2266
+ console.log(c.cyan('Usage: teleportation remote destroy <session-id> --confirm\n'));
2267
+ process.exit(1);
2268
+ }
2269
+ await commandRemoteDestroy(destroySessionId, {
2270
+ confirm: flags.confirm || false
2271
+ });
2272
+ break;
2273
+
2274
+ case 'help':
2275
+ case '--help':
2276
+ case '-h':
2277
+ console.log(c.purple('Remote Session Commands\n'));
2278
+ console.log(c.cyan('Manage remote AI agent sessions running on cloud infrastructure\n'));
2279
+ console.log(c.yellow('Session Management:'));
2280
+ console.log(' ' + c.green('remote start --task "description"') + ' Start a new remote session');
2281
+ console.log(' ' + c.green('remote list') + ' List all remote sessions');
2282
+ console.log(' ' + c.green('remote status <session-id>') + ' Show session details');
2283
+ console.log(' ' + c.green('remote logs <session-id>') + ' View session logs');
2284
+ console.log(' ' + c.green('remote pull <session-id>') + ' Pull results from remote\n');
2285
+ console.log(c.yellow('Session Control:'));
2286
+ console.log(' ' + c.green('remote stop <session-id>') + ' Pause a running session');
2287
+ console.log(' ' + c.green('remote resume <session-id>') + ' Resume a paused session');
2288
+ console.log(' ' + c.green('remote destroy <session-id> --confirm') + ' Destroy session and cleanup\n');
2289
+ console.log(c.yellow('Options:'));
2290
+ console.log(' ' + c.green('--task "description"') + ' Task for the AI agent to work on');
2291
+ console.log(' ' + c.green('--branch "name"') + ' Git branch to use (optional)');
2292
+ console.log(' ' + c.green('--provider fly|daytona') + ' Force specific provider');
2293
+ console.log(' ' + c.green('--no-pr') + ' Skip PR creation on completion');
2294
+ console.log(' ' + c.green('--status running|paused') + ' Filter by status');
2295
+ console.log(' ' + c.green('--follow') + ' Stream logs in real-time');
2296
+ console.log(' ' + c.green('--tail N') + ' Show last N log lines\n');
2297
+ console.log(c.purple('Examples:'));
2298
+ console.log(' teleportation remote start --task "Implement feature X" --branch feature/x');
2299
+ console.log(' teleportation remote list --status running');
2300
+ console.log(' teleportation remote logs remote-abc123 --follow');
2301
+ console.log(' teleportation remote pull remote-abc123');
2302
+ console.log(' teleportation remote destroy remote-abc123 --confirm\n');
2303
+ break;
2304
+
2305
+ default:
2306
+ console.log(c.red(`Unknown remote command: ${subCommand}\n`));
2307
+ console.log(c.cyan('Run "teleportation remote help" for available commands\n'));
2308
+ process.exit(1);
2309
+ }
2310
+ } catch (error) {
2311
+ console.log(c.red(`❌ Remote command failed: ${error.message}\n`));
2312
+ if (process.env.DEBUG) {
2313
+ console.error(error.stack);
2314
+ }
2315
+ process.exit(1);
2316
+ }
2317
+ }
2318
+
1923
2319
  async function commandDaemon(args) {
1924
2320
  const subCommand = args[0] || 'status';
1925
2321
 
@@ -2807,7 +3203,9 @@ const command = process.argv[2] || 'help';
2807
3203
  const args = process.argv.slice(3);
2808
3204
 
2809
3205
  // Handle async commands that need to complete before exit
2810
- const asyncCommands = ['login', 'logout', 'status', 'test', 'env', 'config', 'daemon', 'away', 'back', 'daemon-status', 'command', 'inbox', 'inbox-ack', 'install-hooks', 'update'];
3206
+ const asyncCommands = ['login', 'logout', 'status', 'test', 'env', 'config', 'daemon', 'away', 'back', 'daemon-status', 'command', 'inbox', 'inbox-ack', 'install-hooks', 'update', 'remote'];
3207
+ // Keep this list in sync with switch cases below
3208
+ asyncCommands.push('github');
2811
3209
  if (asyncCommands.includes(command)) {
2812
3210
  // These commands handle their own async execution
2813
3211
  }
@@ -2898,6 +3296,12 @@ try {
2898
3296
  process.exit(1);
2899
3297
  });
2900
3298
  break;
3299
+ case 'github':
3300
+ commandGithub(args).catch(err => {
3301
+ console.error(c.red('❌ Error:'), err.message);
3302
+ process.exit(1);
3303
+ });
3304
+ break;
2901
3305
  case 'daemon':
2902
3306
  commandDaemon(args).catch(err => {
2903
3307
  console.error(c.red('❌ Error:'), err.message);
@@ -2964,6 +3368,12 @@ try {
2964
3368
  process.exit(1);
2965
3369
  });
2966
3370
  break;
3371
+ case 'remote':
3372
+ commandRemote(args).catch(err => {
3373
+ console.error(c.red('❌ Error:'), err.message);
3374
+ process.exit(1);
3375
+ });
3376
+ break;
2967
3377
  case 'version':
2968
3378
  case '--version':
2969
3379
  case '-v':