teleportation-cli 1.1.5 ā 1.2.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.
- package/.claude/hooks/permission_request.mjs +326 -59
- package/.claude/hooks/post_tool_use.mjs +90 -0
- package/.claude/hooks/pre_tool_use.mjs +212 -293
- package/.claude/hooks/session-register.mjs +89 -104
- package/.claude/hooks/session_end.mjs +41 -42
- package/.claude/hooks/session_start.mjs +45 -60
- package/.claude/hooks/stop.mjs +752 -99
- package/.claude/hooks/user_prompt_submit.mjs +26 -3
- package/lib/cli/daemon-commands.js +1 -1
- package/lib/cli/teleport-commands.js +469 -0
- package/lib/daemon/daemon-v2.js +104 -0
- package/lib/daemon/lifecycle.js +56 -171
- package/lib/daemon/services/index.js +3 -0
- package/lib/daemon/services/polling-service.js +173 -0
- package/lib/daemon/services/queue-service.js +318 -0
- package/lib/daemon/services/session-service.js +115 -0
- package/lib/daemon/state.js +35 -0
- package/lib/daemon/task-executor-v2.js +413 -0
- package/lib/daemon/task-executor.js +270 -96
- package/lib/daemon/teleportation-daemon.js +709 -126
- package/lib/daemon/timeline-analyzer.js +215 -0
- package/lib/daemon/transcript-ingestion.js +696 -0
- package/lib/daemon/utils.js +91 -0
- package/lib/install/installer.js +184 -20
- package/lib/install/uhr-installer.js +136 -0
- package/lib/remote/providers/base-provider.js +46 -0
- package/lib/remote/providers/daytona-provider.js +58 -0
- package/lib/remote/providers/provider-factory.js +90 -19
- package/lib/remote/providers/sprites-provider.js +711 -0
- package/lib/teleport/exporters/claude-exporter.js +302 -0
- package/lib/teleport/exporters/gemini-exporter.js +307 -0
- package/lib/teleport/exporters/index.js +93 -0
- package/lib/teleport/exporters/interface.js +153 -0
- package/lib/teleport/fork-tracker.js +415 -0
- package/lib/teleport/git-committer.js +337 -0
- package/lib/teleport/index.js +48 -0
- package/lib/teleport/manager.js +620 -0
- package/lib/teleport/session-capture.js +282 -0
- package/package.json +6 -2
- package/teleportation-cli.cjs +488 -453
- package/.claude/hooks/heartbeat.mjs +0 -396
- package/lib/daemon/pid-manager.js +0 -183
package/teleportation-cli.cjs
CHANGED
|
@@ -268,6 +268,13 @@ function commandHelp() {
|
|
|
268
268
|
console.log(' ' + c.green('remote pull') + ' Pull results from remote session');
|
|
269
269
|
console.log(' ' + c.green('remote help') + ' Show remote commands help\n');
|
|
270
270
|
|
|
271
|
+
console.log(c.yellow('Session Teleportation:'));
|
|
272
|
+
console.log(' ' + c.green('teleport start') + ' Teleport current session to cloud');
|
|
273
|
+
console.log(' ' + c.green('teleport list') + ' List active teleports');
|
|
274
|
+
console.log(' ' + c.green('teleport status') + ' Show teleport status');
|
|
275
|
+
console.log(' ' + c.green('teleport stop') + ' Stop a teleport and create PR');
|
|
276
|
+
console.log(' ' + c.green('teleport help') + ' Show teleport commands help\n');
|
|
277
|
+
|
|
271
278
|
console.log(c.yellow('Session Isolation:'));
|
|
272
279
|
console.log(' ' + c.green('worktree create') + ' Create isolated worktree for a session');
|
|
273
280
|
console.log(' ' + c.green('worktree list') + ' List all session worktrees');
|
|
@@ -296,55 +303,65 @@ function commandHelp() {
|
|
|
296
303
|
|
|
297
304
|
async function commandOn() {
|
|
298
305
|
console.log(c.yellow('š Enabling Teleportation Remote Control...\n'));
|
|
299
|
-
|
|
306
|
+
|
|
300
307
|
try {
|
|
301
|
-
//
|
|
302
|
-
const
|
|
303
|
-
const {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
308
|
+
// Try UHR first, fall back to legacy installer
|
|
309
|
+
const uhrInstallerPath = path.join(TELEPORTATION_DIR, 'lib', 'install', 'uhr-installer.js');
|
|
310
|
+
const { isUhrAvailable, installViaUhr } = await import('file://' + uhrInstallerPath);
|
|
311
|
+
|
|
312
|
+
if (await isUhrAvailable()) {
|
|
313
|
+
console.log(c.dim(' Using UHR (Universal Hook Registry)...'));
|
|
314
|
+
const manifestPath = path.join(TELEPORTATION_DIR, 'teleportation.uhr.json');
|
|
315
|
+
const hooksDir = path.join(TELEPORTATION_DIR, '.claude', 'hooks');
|
|
316
|
+
const uhrResult = await installViaUhr(manifestPath, hooksDir);
|
|
317
|
+
|
|
318
|
+
if (uhrResult.success) {
|
|
319
|
+
console.log(c.green('\nš Teleportation Remote Control ENABLED!'));
|
|
320
|
+
console.log(c.cyan('\nInstalled via UHR (Universal Hook Registry)'));
|
|
321
|
+
if (uhrResult.warnings.length > 0) {
|
|
322
|
+
uhrResult.warnings.forEach(w => console.log(c.yellow(` ā ļø ${w}`)));
|
|
323
|
+
}
|
|
324
|
+
} else {
|
|
325
|
+
console.log(c.yellow(` ā ļø UHR install failed: ${uhrResult.reason}`));
|
|
326
|
+
console.log(c.dim(' Falling back to legacy installer...'));
|
|
327
|
+
await _legacyInstall();
|
|
328
|
+
}
|
|
317
329
|
} else {
|
|
318
|
-
|
|
330
|
+
await _legacyInstall();
|
|
319
331
|
}
|
|
320
|
-
|
|
321
|
-
// Install hooks
|
|
322
|
-
const sourceHooksDir = path.join(TELEPORTATION_DIR, '.claude', 'hooks');
|
|
323
|
-
if (!fs.existsSync(sourceHooksDir)) {
|
|
324
|
-
console.log(c.red(`ā Hooks not found at ${sourceHooksDir}\n`));
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const result = await install(sourceHooksDir);
|
|
329
|
-
|
|
330
|
-
console.log(c.green('\nš Teleportation Remote Control ENABLED!'));
|
|
331
|
-
console.log(c.cyan('\nInstallation Summary:'));
|
|
332
|
-
console.log(` Hooks verified: ${c.green(result.hooksVerified)}`);
|
|
333
|
-
console.log(` Daemon installed: ${c.green(result.daemonInstalled + ' files')}`);
|
|
334
|
-
console.log(` Settings file: ${c.green(result.settingsFile)}`);
|
|
335
|
-
console.log(` Hooks directory: ${c.green(result.hooksDir)}`);
|
|
336
|
-
console.log(` Daemon directory: ${c.green(result.daemonDir)}`);
|
|
332
|
+
|
|
337
333
|
console.log(c.cyan('\nNext steps:'));
|
|
338
334
|
console.log(' 1. Login: teleportation login');
|
|
339
335
|
console.log(' 2. Check status: teleportation status');
|
|
340
336
|
console.log(' 3. Run diagnostics: teleportation doctor\n');
|
|
341
|
-
|
|
337
|
+
|
|
342
338
|
} catch (error) {
|
|
343
339
|
console.log(c.red(`ā Installation failed: ${error.message}\n`));
|
|
344
340
|
process.exit(1);
|
|
345
341
|
}
|
|
346
342
|
}
|
|
347
343
|
|
|
344
|
+
async function _legacyInstall(options = {}) {
|
|
345
|
+
const installerPath = path.join(TELEPORTATION_DIR, 'lib', 'install', 'installer.js');
|
|
346
|
+
const { install } = await import('file://' + installerPath);
|
|
347
|
+
const sourceHooksDir = path.join(TELEPORTATION_DIR, '.claude', 'hooks');
|
|
348
|
+
|
|
349
|
+
const result = await install(sourceHooksDir, options);
|
|
350
|
+
|
|
351
|
+
console.log(c.green('\nš Teleportation Remote Control ENABLED!'));
|
|
352
|
+
console.log(c.cyan('\nInstallation Summary:'));
|
|
353
|
+
if (result.hooksInstalled > 0) {
|
|
354
|
+
console.log(` Claude Code hooks: ${c.green(result.hooksInstalled)} installed`);
|
|
355
|
+
}
|
|
356
|
+
if (result.geminiHooksInstalled > 0) {
|
|
357
|
+
console.log(` Gemini CLI hooks: ${c.green(result.geminiHooksInstalled)} installed`);
|
|
358
|
+
}
|
|
359
|
+
console.log(` Daemon installed: ${c.green(result.daemonInstalled + ' files')}`);
|
|
360
|
+
console.log(` Settings file: ${c.green(result.settingsFile)}`);
|
|
361
|
+
|
|
362
|
+
return result;
|
|
363
|
+
}
|
|
364
|
+
|
|
348
365
|
/**
|
|
349
366
|
* Setup wizard - guided onboarding for new users
|
|
350
367
|
* Creates backup before making changes, validates API key, installs hooks
|
|
@@ -458,7 +475,18 @@ async function commandSetup() {
|
|
|
458
475
|
|
|
459
476
|
// Step 2: Configuration
|
|
460
477
|
console.log('\n' + c.purple('Step 2 of 5: Configuration\n'));
|
|
461
|
-
|
|
478
|
+
|
|
479
|
+
// Choose assistants (PRD-0024 Parity)
|
|
480
|
+
console.log(' Which AI coding assistants do you use?');
|
|
481
|
+
console.log(' 1. Claude Code only');
|
|
482
|
+
console.log(' 2. Gemini CLI only');
|
|
483
|
+
console.log(' 3. Both');
|
|
484
|
+
|
|
485
|
+
const assistantChoice = await question('\n Select option (1-3, default: Both): ');
|
|
486
|
+
const includeClaude = assistantChoice === '1' || assistantChoice === '3' || !assistantChoice;
|
|
487
|
+
const includeGemini = assistantChoice === '2' || assistantChoice === '3' || !assistantChoice;
|
|
488
|
+
|
|
489
|
+
console.log(`\n Relay URL: ${c.cyan(relayUrl)}`);
|
|
462
490
|
|
|
463
491
|
// Test relay connectivity
|
|
464
492
|
console.log(c.cyan(' Testing connectivity...'));
|
|
@@ -505,14 +533,50 @@ async function commandSetup() {
|
|
|
505
533
|
|
|
506
534
|
// Step 3: Install Hooks
|
|
507
535
|
console.log('\n' + c.purple('Step 3 of 5: Installing Hooks\n'));
|
|
508
|
-
console.log(' Installing
|
|
536
|
+
console.log(' Installing Teleportation hooks...');
|
|
509
537
|
|
|
510
538
|
try {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
539
|
+
// Try UHR first for Claude Code hooks
|
|
540
|
+
let uhrInstalled = false;
|
|
541
|
+
if (includeClaude) {
|
|
542
|
+
try {
|
|
543
|
+
const uhrInstallerPath = path.join(TELEPORTATION_DIR, 'lib', 'install', 'uhr-installer.js');
|
|
544
|
+
const { isUhrAvailable, installViaUhr } = await import('file://' + uhrInstallerPath);
|
|
545
|
+
|
|
546
|
+
if (await isUhrAvailable()) {
|
|
547
|
+
console.log(c.dim(' Using UHR (Universal Hook Registry)...'));
|
|
548
|
+
const manifestPath = path.join(TELEPORTATION_DIR, 'teleportation.uhr.json');
|
|
549
|
+
const hooksDir = path.join(TELEPORTATION_DIR, '.claude', 'hooks');
|
|
550
|
+
const uhrResult = await installViaUhr(manifestPath, hooksDir);
|
|
551
|
+
|
|
552
|
+
if (uhrResult.success) {
|
|
553
|
+
console.log(c.green(' ā
Claude Code hooks installed via UHR'));
|
|
554
|
+
if (uhrResult.warnings.length > 0) {
|
|
555
|
+
uhrResult.warnings.forEach(w => console.log(c.yellow(` ā ļø ${w}`)));
|
|
556
|
+
}
|
|
557
|
+
uhrInstalled = true;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
} catch (e) {
|
|
561
|
+
// UHR not available, fall through to legacy
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
if (!uhrInstalled) {
|
|
566
|
+
// Legacy installer fallback
|
|
567
|
+
const installerPath = path.join(TELEPORTATION_DIR, 'lib', 'install', 'installer.js');
|
|
568
|
+
const { install } = await import('file://' + installerPath);
|
|
569
|
+
const sourceHooksDir = path.join(TELEPORTATION_DIR, '.claude', 'hooks');
|
|
570
|
+
|
|
571
|
+
const result = await install(sourceHooksDir, { includeClaude, includeGemini });
|
|
572
|
+
|
|
573
|
+
if (result.hooksInstalled > 0) {
|
|
574
|
+
console.log(c.green(` ā
${result.hooksInstalled} Claude Code hooks installed`));
|
|
575
|
+
}
|
|
576
|
+
if (result.geminiHooksInstalled > 0) {
|
|
577
|
+
console.log(c.green(` ā
${result.geminiHooksInstalled} Gemini CLI hooks installed`));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
516
580
|
} catch (e) {
|
|
517
581
|
console.log(c.red(` ā Failed to install hooks: ${e.message}`));
|
|
518
582
|
console.log(c.yellow('\n Would you like to restore your previous configuration?'));
|
|
@@ -697,24 +761,42 @@ async function commandBackup(args) {
|
|
|
697
761
|
}
|
|
698
762
|
}
|
|
699
763
|
|
|
700
|
-
function commandOff() {
|
|
764
|
+
async function commandOff() {
|
|
701
765
|
console.log(c.yellow('š Disabling Teleportation Remote Control...\n'));
|
|
702
|
-
|
|
703
|
-
//
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
766
|
+
|
|
767
|
+
// Try UHR uninstall first
|
|
768
|
+
let uhrUninstalled = false;
|
|
769
|
+
try {
|
|
770
|
+
const uhrInstallerPath = path.join(TELEPORTATION_DIR, 'lib', 'install', 'uhr-installer.js');
|
|
771
|
+
const { isUhrAvailable, uninstallViaUhr } = await import('file://' + uhrInstallerPath);
|
|
772
|
+
|
|
773
|
+
if (await isUhrAvailable()) {
|
|
774
|
+
const result = await uninstallViaUhr('teleportation');
|
|
775
|
+
if (result.success) {
|
|
776
|
+
console.log(c.green('ā
Uninstalled via UHR'));
|
|
777
|
+
uhrUninstalled = true;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
} catch (e) {
|
|
781
|
+
// UHR not available or failed, fall through to manual removal
|
|
707
782
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
}
|
|
715
|
-
|
|
783
|
+
|
|
784
|
+
if (!uhrUninstalled) {
|
|
785
|
+
// Manual removal fallback
|
|
786
|
+
if (fs.existsSync(config.globalSettings)) {
|
|
787
|
+
fs.unlinkSync(config.globalSettings);
|
|
788
|
+
console.log(c.green('ā
Removed ~/.claude/settings.json'));
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
if (fs.existsSync(config.globalHooks)) {
|
|
792
|
+
const hooks = fs.readdirSync(config.globalHooks).filter(f => f.endsWith('.mjs'));
|
|
793
|
+
hooks.forEach(hook => {
|
|
794
|
+
fs.unlinkSync(path.join(config.globalHooks, hook));
|
|
795
|
+
});
|
|
796
|
+
console.log(c.green(`ā
Removed ${hooks.length} hooks`));
|
|
797
|
+
}
|
|
716
798
|
}
|
|
717
|
-
|
|
799
|
+
|
|
718
800
|
console.log(c.yellow('\nš Teleportation Remote Control DISABLED'));
|
|
719
801
|
console.log(c.cyan('Services are still running. Stop with: ./teleportation stop\n'));
|
|
720
802
|
}
|
|
@@ -814,13 +896,32 @@ async function commandStatus() {
|
|
|
814
896
|
const hooksConfigured = config.isConfigured();
|
|
815
897
|
|
|
816
898
|
if (hooksConfigured) {
|
|
817
|
-
console.log(' ' + c.green('ā
') + '
|
|
899
|
+
console.log(' ' + c.green('ā
') + ' Claude Code hooks installed');
|
|
818
900
|
const hookFiles = fs.readdirSync(config.globalHooks).filter(f => f.endsWith('.mjs'));
|
|
819
|
-
console.log('
|
|
820
|
-
|
|
901
|
+
console.log(' ' + c.green('ā¢') + ` ${hookFiles.length} files in ~/.claude/hooks/`);
|
|
902
|
+
} else {
|
|
903
|
+
console.log(' ' + c.red('ā') + ' Claude Code hooks not installed');
|
|
904
|
+
issues.push('Run `teleportation setup` to install Claude hooks');
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Gemini Hooks Status (Parity)
|
|
908
|
+
const globalGeminiHooks = path.join(HOME_DIR, '.gemini', 'hooks');
|
|
909
|
+
const globalGeminiSettings = path.join(HOME_DIR, '.gemini', 'settings.json');
|
|
910
|
+
const geminiHooksConfigured = fs.existsSync(globalGeminiSettings) && fs.existsSync(globalGeminiHooks);
|
|
911
|
+
|
|
912
|
+
if (geminiHooksConfigured) {
|
|
913
|
+
console.log(' ' + c.green('ā
') + ' Gemini CLI hooks installed');
|
|
914
|
+
const geminiFiles = fs.readdirSync(globalGeminiHooks).filter(f => f.endsWith('.mjs'));
|
|
915
|
+
console.log(' ' + c.green('ā¢') + ` ${geminiFiles.length} files in ~/.gemini/hooks/`);
|
|
821
916
|
} else {
|
|
822
|
-
|
|
823
|
-
|
|
917
|
+
try {
|
|
918
|
+
const { execSync } = require('child_process');
|
|
919
|
+
execSync('which gemini', { stdio: 'ignore' });
|
|
920
|
+
console.log(' ' + c.yellow('ā ļø') + ' Gemini CLI found but hooks not installed');
|
|
921
|
+
warnings.push('Run `teleportation setup` to install Gemini hooks');
|
|
922
|
+
} catch (e) {
|
|
923
|
+
// Gemini not installed, don't nag about hooks
|
|
924
|
+
}
|
|
824
925
|
}
|
|
825
926
|
|
|
826
927
|
// Config/credentials sync check
|
|
@@ -1117,22 +1218,43 @@ async function commandTest() {
|
|
|
1117
1218
|
// Test 3: Relay service
|
|
1118
1219
|
console.log(c.yellow('Test 3: Relay API Service'));
|
|
1119
1220
|
const relayUrl = creds.RELAY_API_URL || 'https://api.teleportation.dev';
|
|
1120
|
-
|
|
1221
|
+
const isLocalRelay = relayUrl.includes('localhost') || relayUrl.includes('127.0.0.1');
|
|
1222
|
+
|
|
1223
|
+
const relayHealthy = checkServiceHealth(relayUrl);
|
|
1224
|
+
// Only check local port if we expect a local relay
|
|
1225
|
+
const relayPortOpen = isLocalRelay ? checkService('relay', 3030) : true;
|
|
1226
|
+
|
|
1227
|
+
if (relayPortOpen && relayHealthy) {
|
|
1121
1228
|
console.log(c.green(' ā
PASS - Relay API running and healthy\n'));
|
|
1122
1229
|
passed++;
|
|
1123
1230
|
} else {
|
|
1124
|
-
|
|
1231
|
+
if (isLocalRelay && !relayPortOpen) {
|
|
1232
|
+
console.log(c.red(' ā FAIL - Local relay process not running (port 3030)\n'));
|
|
1233
|
+
} else if (!relayHealthy) {
|
|
1234
|
+
console.log(c.red(` ā FAIL - Relay API unreachable at ${relayUrl}\n`));
|
|
1235
|
+
} else {
|
|
1236
|
+
console.log(c.red(' ā FAIL - Relay API issue\n'));
|
|
1237
|
+
}
|
|
1125
1238
|
failed++;
|
|
1126
1239
|
}
|
|
1127
1240
|
|
|
1128
1241
|
// Test 4: Storage service
|
|
1129
1242
|
console.log(c.yellow('Test 4: Storage API Service'));
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1243
|
+
// Storage is usually local for dev, but might be remote in future.
|
|
1244
|
+
// For now, only check if we are in local dev mode (implied by local relay) or if explicitly checking local ports.
|
|
1245
|
+
// Actually, CLI users might not need Storage API directly running if using production relay (which handles storage).
|
|
1246
|
+
|
|
1247
|
+
if (isLocalRelay) {
|
|
1248
|
+
if (checkService('storage', 3040) && checkServiceHealth('http://localhost:3040')) {
|
|
1249
|
+
console.log(c.green(' ā
PASS - Local Storage API running and healthy\n'));
|
|
1250
|
+
passed++;
|
|
1251
|
+
} else {
|
|
1252
|
+
console.log(c.red(' ā FAIL - Local Storage API not running or unhealthy\n'));
|
|
1253
|
+
failed++;
|
|
1254
|
+
}
|
|
1133
1255
|
} else {
|
|
1134
|
-
console.log(c.
|
|
1135
|
-
|
|
1256
|
+
console.log(c.green(' ā
PASS - Using Remote Relay (Storage managed by Relay)\n'));
|
|
1257
|
+
passed++;
|
|
1136
1258
|
}
|
|
1137
1259
|
|
|
1138
1260
|
// Test 5: Hook execution
|
|
@@ -1140,8 +1262,28 @@ async function commandTest() {
|
|
|
1140
1262
|
try {
|
|
1141
1263
|
const testHook = path.join(config.globalHooks, 'pre_tool_use.mjs');
|
|
1142
1264
|
if (fs.existsSync(testHook)) {
|
|
1143
|
-
const testInput = '{"session_id":"test","tool_name":"Read","tool_input":{}}';
|
|
1144
|
-
const
|
|
1265
|
+
const testInput = '{"session_id":"test","tool_name":"Read","tool_input":{"file_path":"test.txt"}}';
|
|
1266
|
+
const relayUrl = creds.RELAY_API_URL || 'http://localhost:3030';
|
|
1267
|
+
const relayKey = creds.RELAY_API_KEY || 'dev-key-123';
|
|
1268
|
+
const envVars = `RELAY_API_URL="${relayUrl}" RELAY_API_KEY="${relayKey}"`;
|
|
1269
|
+
|
|
1270
|
+
// Register test session first so it appears correctly in UI
|
|
1271
|
+
try {
|
|
1272
|
+
await fetch(`${relayUrl}/api/sessions/register`, {
|
|
1273
|
+
method: 'POST',
|
|
1274
|
+
headers: {
|
|
1275
|
+
'Content-Type': 'application/json',
|
|
1276
|
+
'Authorization': `Bearer ${relayKey}`
|
|
1277
|
+
},
|
|
1278
|
+
body: JSON.stringify({
|
|
1279
|
+
session_id: 'test',
|
|
1280
|
+
meta: { project_name: 'teleportation-test', hostname: os.hostname() }
|
|
1281
|
+
})
|
|
1282
|
+
});
|
|
1283
|
+
} catch (e) {
|
|
1284
|
+
// Ignore registration errors for the hook test
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1145
1287
|
execSync(`echo '${testInput}' | ${envVars} node ${testHook}`, { stdio: 'ignore' });
|
|
1146
1288
|
console.log(c.green(' ā
PASS - Hook executes successfully\n'));
|
|
1147
1289
|
passed++;
|
|
@@ -1174,50 +1316,74 @@ async function commandDoctor() {
|
|
|
1174
1316
|
let checksPassed = 0;
|
|
1175
1317
|
let checksFailed = 0;
|
|
1176
1318
|
|
|
1177
|
-
// Check 1:
|
|
1178
|
-
console.log(c.yellow('1.
|
|
1319
|
+
// Check 1: Assistant installations
|
|
1320
|
+
console.log(c.yellow('1. Assistant Installations'));
|
|
1321
|
+
let anyAssistantFound = false;
|
|
1322
|
+
|
|
1323
|
+
// 1.1 Claude Code
|
|
1179
1324
|
try {
|
|
1180
1325
|
const claudeCodePath = execSync('which claude', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
1181
1326
|
if (claudeCodePath) {
|
|
1182
|
-
console.log(c.green(` ā
|
|
1183
|
-
|
|
1184
|
-
} else {
|
|
1185
|
-
console.log(c.yellow(' ā ļø Claude Code not found in PATH\n'));
|
|
1186
|
-
issues.push('Claude Code not found');
|
|
1187
|
-
recommendations.push('Install Claude Code or add it to your PATH');
|
|
1188
|
-
checksFailed++;
|
|
1327
|
+
console.log(c.green(` ā
Claude Code: ${claudeCodePath}`));
|
|
1328
|
+
anyAssistantFound = true;
|
|
1189
1329
|
}
|
|
1190
|
-
} catch (e) {
|
|
1191
|
-
|
|
1192
|
-
|
|
1330
|
+
} catch (e) {}
|
|
1331
|
+
|
|
1332
|
+
// 1.2 Gemini CLI
|
|
1333
|
+
try {
|
|
1334
|
+
const geminiPath = execSync('which gemini', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
1335
|
+
if (geminiPath) {
|
|
1336
|
+
console.log(c.green(` ā
Gemini CLI: ${geminiPath}`));
|
|
1337
|
+
anyAssistantFound = true;
|
|
1338
|
+
}
|
|
1339
|
+
} catch (e) {}
|
|
1340
|
+
|
|
1341
|
+
if (anyAssistantFound) {
|
|
1342
|
+
console.log('');
|
|
1343
|
+
checksPassed++;
|
|
1344
|
+
} else {
|
|
1345
|
+
console.log(c.red(' ā No supported AI coding assistants found (Claude/Gemini)\n'));
|
|
1346
|
+
issues.push('No AI assistants found');
|
|
1347
|
+
recommendations.push('Install Claude Code or Gemini CLI');
|
|
1193
1348
|
checksFailed++;
|
|
1194
1349
|
}
|
|
1195
1350
|
|
|
1196
1351
|
// Check 2: Hooks installation
|
|
1197
1352
|
console.log(c.yellow('2. Hooks Installation'));
|
|
1353
|
+
|
|
1354
|
+
// 2.1 Claude Hooks
|
|
1198
1355
|
const hooksConfigured = config.isConfigured();
|
|
1199
1356
|
if (hooksConfigured) {
|
|
1200
1357
|
const hookFiles = fs.readdirSync(config.globalHooks).filter(f => f.endsWith('.mjs'));
|
|
1201
|
-
console.log(c.green(` ā
${hookFiles.length} hooks installed
|
|
1202
|
-
hookFiles.forEach(f => {
|
|
1203
|
-
const hookPath = path.join(config.globalHooks, f);
|
|
1204
|
-
const stats = fs.statSync(hookPath);
|
|
1205
|
-
const isExecutable = (stats.mode & parseInt('111', 8)) !== 0;
|
|
1206
|
-
if (isExecutable) {
|
|
1207
|
-
console.log(c.green(` ⢠${f} (executable)\n`));
|
|
1208
|
-
} else {
|
|
1209
|
-
console.log(c.yellow(` ⢠${f} (not executable)\n`));
|
|
1210
|
-
issues.push(`Hook ${f} is not executable`);
|
|
1211
|
-
recommendations.push(`Run: chmod +x ${hookPath}`);
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
checksPassed++;
|
|
1358
|
+
console.log(c.green(` ā
Claude: ${hookFiles.length} hooks installed`));
|
|
1215
1359
|
} else {
|
|
1216
|
-
console.log(c.
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1360
|
+
console.log(c.dim(' - Claude: hooks not installed'));
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
// 2.2 Gemini Hooks
|
|
1364
|
+
const globalGeminiHooks = path.join(HOME_DIR, '.gemini', 'hooks');
|
|
1365
|
+
const globalGeminiSettings = path.join(HOME_DIR, '.gemini', 'settings.json');
|
|
1366
|
+
if (fs.existsSync(globalGeminiSettings) && fs.existsSync(globalGeminiHooks)) {
|
|
1367
|
+
const geminiFiles = fs.readdirSync(globalGeminiHooks).filter(f => f.endsWith('.mjs'));
|
|
1368
|
+
console.log(c.green(` ā
Gemini: ${geminiFiles.length} hooks installed`));
|
|
1369
|
+
} else {
|
|
1370
|
+
console.log(c.dim(' - Gemini: hooks not installed'));
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// 2.3 UHR availability
|
|
1374
|
+
try {
|
|
1375
|
+
const uhrInstallerPath = path.join(TELEPORTATION_DIR, 'lib', 'install', 'uhr-installer.js');
|
|
1376
|
+
const { isUhrAvailable } = await import('file://' + uhrInstallerPath);
|
|
1377
|
+
if (await isUhrAvailable()) {
|
|
1378
|
+
console.log(c.green(' ā
UHR (Universal Hook Registry) available'));
|
|
1379
|
+
} else {
|
|
1380
|
+
console.log(c.dim(' - UHR not installed (using legacy installer)'));
|
|
1381
|
+
}
|
|
1382
|
+
} catch (e) {
|
|
1383
|
+
console.log(c.dim(' - UHR check skipped'));
|
|
1220
1384
|
}
|
|
1385
|
+
console.log('');
|
|
1386
|
+
checksPassed++;
|
|
1221
1387
|
|
|
1222
1388
|
// Check 3: Credentials
|
|
1223
1389
|
console.log(c.yellow('3. Credentials'));
|
|
@@ -1702,8 +1868,23 @@ async function performLogin(manager, flags, positional) {
|
|
|
1702
1868
|
console.log(c.green('ā
Successfully authenticated with API key!\n'));
|
|
1703
1869
|
console.log(c.cyan('Credentials saved to ~/.teleportation/credentials\n'));
|
|
1704
1870
|
|
|
1705
|
-
//
|
|
1706
|
-
|
|
1871
|
+
// Kill any orphaned Claude processes spawned by the old daemon
|
|
1872
|
+
// These would be running tasks with old credentials
|
|
1873
|
+
try {
|
|
1874
|
+
const { execSync } = require('child_process');
|
|
1875
|
+
// Kill Claude processes spawned in task mode (have TELEPORTATION_TASK_MODE in their environment)
|
|
1876
|
+
// pkill returns exit code 1 if no processes found, so we ignore errors
|
|
1877
|
+
execSync('pkill -f "claude.*-p.*--output-format" 2>/dev/null || true', { stdio: 'ignore' });
|
|
1878
|
+
if (process.env.DEBUG) {
|
|
1879
|
+
console.log(c.dim('Cleaned up any orphaned Claude task processes.'));
|
|
1880
|
+
}
|
|
1881
|
+
} catch (e) {
|
|
1882
|
+
// Ignore errors - cleanup is best-effort
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
// Restart daemon to pick up new credentials
|
|
1886
|
+
// (If daemon was running with old credentials, restart ensures it uses new ones)
|
|
1887
|
+
console.log(c.cyan('Restarting daemon with new credentials...'));
|
|
1707
1888
|
try {
|
|
1708
1889
|
const lifecyclePath = path.join(TELEPORTATION_DIR, 'lib', 'daemon', 'lifecycle.js');
|
|
1709
1890
|
const { access } = await import('fs/promises');
|
|
@@ -1712,16 +1893,16 @@ async function performLogin(manager, flags, positional) {
|
|
|
1712
1893
|
} catch {
|
|
1713
1894
|
throw new Error('Daemon module not found. Please reinstall: npm install -g teleportation-cli');
|
|
1714
1895
|
}
|
|
1715
|
-
const {
|
|
1716
|
-
const result = await
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
if (e.message && e.message.includes('already running')) {
|
|
1720
|
-
console.log(c.green('ā
Daemon already running\n'));
|
|
1896
|
+
const { restartDaemon } = await import('file://' + lifecyclePath);
|
|
1897
|
+
const result = await restartDaemon({ stopTimeout: 5000, force: true });
|
|
1898
|
+
if (result.wasRunning) {
|
|
1899
|
+
console.log(c.green(`ā
Daemon restarted with new credentials (PID: ${result.pid})\n`));
|
|
1721
1900
|
} else {
|
|
1722
|
-
console.log(c.
|
|
1723
|
-
console.log(c.cyan('You can start it manually with: teleportation daemon start\n'));
|
|
1901
|
+
console.log(c.green(`ā
Daemon started (PID: ${result.pid})\n`));
|
|
1724
1902
|
}
|
|
1903
|
+
} catch (e) {
|
|
1904
|
+
console.log(c.yellow(`ā ļø Could not start daemon: ${e.message}`));
|
|
1905
|
+
console.log(c.cyan('You can start it manually with: teleportation daemon start\n'));
|
|
1725
1906
|
}
|
|
1726
1907
|
|
|
1727
1908
|
console.log(c.yellow('ā ļø Restart Claude Code to apply changes to current session.\n'));
|
|
@@ -1799,8 +1980,15 @@ async function performLogin(manager, flags, positional) {
|
|
|
1799
1980
|
console.log(c.cyan('Credentials saved to ~/.teleportation/credentials'));
|
|
1800
1981
|
console.log(c.dim(`Access token expires in ${Math.floor(tokenData.expiresIn / 60)} minutes (auto-refreshes)\n`));
|
|
1801
1982
|
|
|
1802
|
-
//
|
|
1803
|
-
|
|
1983
|
+
// Kill any orphaned Claude processes spawned by the old daemon
|
|
1984
|
+
try {
|
|
1985
|
+
const { execSync } = require('child_process');
|
|
1986
|
+
execSync('pkill -f "claude.*-p.*--output-format" 2>/dev/null || true', { stdio: 'ignore' });
|
|
1987
|
+
} catch (e) { /* Ignore */ }
|
|
1988
|
+
|
|
1989
|
+
// Restart daemon to pick up new credentials
|
|
1990
|
+
// (If daemon was running with old credentials, restart ensures it uses new ones)
|
|
1991
|
+
console.log(c.cyan('Restarting daemon with new credentials...'));
|
|
1804
1992
|
try {
|
|
1805
1993
|
const lifecyclePath = path.join(TELEPORTATION_DIR, 'lib', 'daemon', 'lifecycle.js');
|
|
1806
1994
|
const { access } = await import('fs/promises');
|
|
@@ -1809,16 +1997,16 @@ async function performLogin(manager, flags, positional) {
|
|
|
1809
1997
|
} catch {
|
|
1810
1998
|
throw new Error('Daemon module not found. Please reinstall: npm install -g teleportation-cli');
|
|
1811
1999
|
}
|
|
1812
|
-
const {
|
|
1813
|
-
const result = await
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
if (e.message && e.message.includes('already running')) {
|
|
1817
|
-
console.log(c.green('ā
Daemon already running\n'));
|
|
2000
|
+
const { restartDaemon } = await import('file://' + lifecyclePath);
|
|
2001
|
+
const result = await restartDaemon({ stopTimeout: 5000, force: true });
|
|
2002
|
+
if (result.wasRunning) {
|
|
2003
|
+
console.log(c.green(`ā
Daemon restarted with new credentials (PID: ${result.pid})\n`));
|
|
1818
2004
|
} else {
|
|
1819
|
-
console.log(c.
|
|
1820
|
-
console.log(c.cyan('You can start it manually with: teleportation daemon start\n'));
|
|
2005
|
+
console.log(c.green(`ā
Daemon started (PID: ${result.pid})\n`));
|
|
1821
2006
|
}
|
|
2007
|
+
} catch (e) {
|
|
2008
|
+
console.log(c.yellow(`ā ļø Could not start daemon: ${e.message}`));
|
|
2009
|
+
console.log(c.cyan('You can start it manually with: teleportation daemon start\n'));
|
|
1822
2010
|
}
|
|
1823
2011
|
|
|
1824
2012
|
console.log(c.yellow('ā ļø Restart Claude Code to apply changes to current session.\n'));
|
|
@@ -1859,8 +2047,15 @@ async function performLogin(manager, flags, positional) {
|
|
|
1859
2047
|
console.log(c.green('ā
Successfully authenticated with token!\n'));
|
|
1860
2048
|
console.log(c.cyan('Credentials saved to ~/.teleportation/credentials\n'));
|
|
1861
2049
|
|
|
1862
|
-
//
|
|
1863
|
-
|
|
2050
|
+
// Kill any orphaned Claude processes spawned by the old daemon
|
|
2051
|
+
try {
|
|
2052
|
+
const { execSync } = require('child_process');
|
|
2053
|
+
execSync('pkill -f "claude.*-p.*--output-format" 2>/dev/null || true', { stdio: 'ignore' });
|
|
2054
|
+
} catch (e) { /* Ignore */ }
|
|
2055
|
+
|
|
2056
|
+
// Restart daemon to pick up new credentials
|
|
2057
|
+
// (If daemon was running with old credentials, restart ensures it uses new ones)
|
|
2058
|
+
console.log(c.cyan('Restarting daemon with new credentials...'));
|
|
1864
2059
|
try {
|
|
1865
2060
|
const lifecyclePath = path.join(TELEPORTATION_DIR, 'lib', 'daemon', 'lifecycle.js');
|
|
1866
2061
|
const { access } = await import('fs/promises');
|
|
@@ -1869,16 +2064,16 @@ async function performLogin(manager, flags, positional) {
|
|
|
1869
2064
|
} catch {
|
|
1870
2065
|
throw new Error('Daemon module not found. Please reinstall: npm install -g teleportation-cli');
|
|
1871
2066
|
}
|
|
1872
|
-
const {
|
|
1873
|
-
const result = await
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
if (e.message && e.message.includes('already running')) {
|
|
1877
|
-
console.log(c.green('ā
Daemon already running\n'));
|
|
2067
|
+
const { restartDaemon } = await import('file://' + lifecyclePath);
|
|
2068
|
+
const result = await restartDaemon({ stopTimeout: 5000, force: true });
|
|
2069
|
+
if (result.wasRunning) {
|
|
2070
|
+
console.log(c.green(`ā
Daemon restarted with new credentials (PID: ${result.pid})\n`));
|
|
1878
2071
|
} else {
|
|
1879
|
-
console.log(c.
|
|
1880
|
-
console.log(c.cyan('You can start it manually with: teleportation daemon start\n'));
|
|
2072
|
+
console.log(c.green(`ā
Daemon started (PID: ${result.pid})\n`));
|
|
1881
2073
|
}
|
|
2074
|
+
} catch (e) {
|
|
2075
|
+
console.log(c.yellow(`ā ļø Could not start daemon: ${e.message}`));
|
|
2076
|
+
console.log(c.cyan('You can start it manually with: teleportation daemon start\n'));
|
|
1882
2077
|
}
|
|
1883
2078
|
|
|
1884
2079
|
console.log(c.yellow('ā ļø Restart Claude Code to apply changes to current session.\n'));
|
|
@@ -1941,8 +2136,15 @@ async function performLogin(manager, flags, positional) {
|
|
|
1941
2136
|
console.log(c.green('ā
Successfully authenticated!\n'));
|
|
1942
2137
|
console.log(c.cyan('Credentials saved to ~/.teleportation/credentials\n'));
|
|
1943
2138
|
|
|
1944
|
-
//
|
|
1945
|
-
|
|
2139
|
+
// Kill any orphaned Claude processes spawned by the old daemon
|
|
2140
|
+
try {
|
|
2141
|
+
const { execSync } = require('child_process');
|
|
2142
|
+
execSync('pkill -f "claude.*-p.*--output-format" 2>/dev/null || true', { stdio: 'ignore' });
|
|
2143
|
+
} catch (e) { /* Ignore */ }
|
|
2144
|
+
|
|
2145
|
+
// Restart daemon to pick up new credentials
|
|
2146
|
+
// (If daemon was running with old credentials, restart ensures it uses new ones)
|
|
2147
|
+
console.log(c.cyan('Restarting daemon with new credentials...'));
|
|
1946
2148
|
try {
|
|
1947
2149
|
const lifecyclePath = path.join(TELEPORTATION_DIR, 'lib', 'daemon', 'lifecycle.js');
|
|
1948
2150
|
const { access } = await import('fs/promises');
|
|
@@ -1951,16 +2153,16 @@ async function performLogin(manager, flags, positional) {
|
|
|
1951
2153
|
} catch {
|
|
1952
2154
|
throw new Error('Daemon module not found. Please reinstall: npm install -g teleportation-cli');
|
|
1953
2155
|
}
|
|
1954
|
-
const {
|
|
1955
|
-
const result = await
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
if (e.message && e.message.includes('already running')) {
|
|
1959
|
-
console.log(c.green('ā
Daemon already running\n'));
|
|
2156
|
+
const { restartDaemon } = await import('file://' + lifecyclePath);
|
|
2157
|
+
const result = await restartDaemon({ stopTimeout: 5000, force: true });
|
|
2158
|
+
if (result.wasRunning) {
|
|
2159
|
+
console.log(c.green(`ā
Daemon restarted with new credentials (PID: ${result.pid})\n`));
|
|
1960
2160
|
} else {
|
|
1961
|
-
console.log(c.
|
|
1962
|
-
console.log(c.cyan('You can start it manually with: teleportation daemon start\n'));
|
|
2161
|
+
console.log(c.green(`ā
Daemon started (PID: ${result.pid})\n`));
|
|
1963
2162
|
}
|
|
2163
|
+
} catch (e) {
|
|
2164
|
+
console.log(c.yellow(`ā ļø Could not start daemon: ${e.message}`));
|
|
2165
|
+
console.log(c.cyan('You can start it manually with: teleportation daemon start\n'));
|
|
1964
2166
|
}
|
|
1965
2167
|
|
|
1966
2168
|
resolve();
|
|
@@ -2480,6 +2682,89 @@ async function commandRemote(args) {
|
|
|
2480
2682
|
}
|
|
2481
2683
|
}
|
|
2482
2684
|
|
|
2685
|
+
async function commandTeleport(args) {
|
|
2686
|
+
const subCommand = args[0] || 'help';
|
|
2687
|
+
|
|
2688
|
+
// Parse flags
|
|
2689
|
+
const { flags, positional } = parseFlags(args.slice(1));
|
|
2690
|
+
|
|
2691
|
+
try {
|
|
2692
|
+
// Dynamically import teleport commands
|
|
2693
|
+
const teleportCommandsPath = path.join(TELEPORTATION_DIR, 'lib', 'cli', 'teleport-commands.js');
|
|
2694
|
+
const {
|
|
2695
|
+
commandTeleportStart,
|
|
2696
|
+
commandTeleportStatus,
|
|
2697
|
+
commandTeleportList,
|
|
2698
|
+
commandTeleportStop,
|
|
2699
|
+
commandTeleportHelp
|
|
2700
|
+
} = await import('file://' + teleportCommandsPath);
|
|
2701
|
+
|
|
2702
|
+
switch (subCommand) {
|
|
2703
|
+
case 'start':
|
|
2704
|
+
// Parse: teleportation teleport start --task "description" [--provider sprites|daytona] [--target-coder claude-code|gemini-cli] [--mode pause|fork]
|
|
2705
|
+
await commandTeleportStart({
|
|
2706
|
+
sessionId: flags['session-id'] || flags.id,
|
|
2707
|
+
task: flags.task,
|
|
2708
|
+
cwd: flags.cwd || process.cwd(),
|
|
2709
|
+
provider: flags.provider,
|
|
2710
|
+
targetCoder: flags['target-coder'] || flags.coder || 'claude-code',
|
|
2711
|
+
mode: flags.mode || 'pause',
|
|
2712
|
+
});
|
|
2713
|
+
break;
|
|
2714
|
+
|
|
2715
|
+
case 'status':
|
|
2716
|
+
// Parse: teleportation teleport status <teleport-id>
|
|
2717
|
+
const statusTeleportId = positional[0] || flags['teleport-id'] || flags.id;
|
|
2718
|
+
if (!statusTeleportId) {
|
|
2719
|
+
console.log(c.red('ā Error: Teleport ID is required\n'));
|
|
2720
|
+
console.log(c.cyan('Usage: teleportation teleport status <teleport-id>\n'));
|
|
2721
|
+
process.exit(1);
|
|
2722
|
+
}
|
|
2723
|
+
await commandTeleportStatus(statusTeleportId);
|
|
2724
|
+
break;
|
|
2725
|
+
|
|
2726
|
+
case 'list':
|
|
2727
|
+
case 'ls':
|
|
2728
|
+
// Parse: teleportation teleport list [--status running|completed|failed]
|
|
2729
|
+
await commandTeleportList({
|
|
2730
|
+
status: flags.status,
|
|
2731
|
+
});
|
|
2732
|
+
break;
|
|
2733
|
+
|
|
2734
|
+
case 'stop':
|
|
2735
|
+
// Parse: teleportation teleport stop <teleport-id> [--create-pr] [--base-branch main]
|
|
2736
|
+
const stopTeleportId = positional[0] || flags['teleport-id'] || flags.id;
|
|
2737
|
+
if (!stopTeleportId) {
|
|
2738
|
+
console.log(c.red('ā Error: Teleport ID is required\n'));
|
|
2739
|
+
console.log(c.cyan('Usage: teleportation teleport stop <teleport-id> [--create-pr]\n'));
|
|
2740
|
+
process.exit(1);
|
|
2741
|
+
}
|
|
2742
|
+
await commandTeleportStop(stopTeleportId, {
|
|
2743
|
+
createPR: flags['create-pr'] || false,
|
|
2744
|
+
baseBranch: flags['base-branch'],
|
|
2745
|
+
});
|
|
2746
|
+
break;
|
|
2747
|
+
|
|
2748
|
+
case 'help':
|
|
2749
|
+
case '--help':
|
|
2750
|
+
case '-h':
|
|
2751
|
+
commandTeleportHelp();
|
|
2752
|
+
break;
|
|
2753
|
+
|
|
2754
|
+
default:
|
|
2755
|
+
console.log(c.red(`Unknown teleport command: ${subCommand}\n`));
|
|
2756
|
+
console.log(c.cyan('Run "teleportation teleport help" for available commands\n'));
|
|
2757
|
+
process.exit(1);
|
|
2758
|
+
}
|
|
2759
|
+
} catch (error) {
|
|
2760
|
+
console.log(c.red(`ā Teleport command failed: ${error.message}\n`));
|
|
2761
|
+
if (process.env.DEBUG) {
|
|
2762
|
+
console.error(error.stack);
|
|
2763
|
+
}
|
|
2764
|
+
process.exit(1);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2483
2768
|
async function commandDaemon(args) {
|
|
2484
2769
|
const subCommand = args[0] || 'status';
|
|
2485
2770
|
|
|
@@ -2815,334 +3100,75 @@ async function commandInboxAck(id) {
|
|
|
2815
3100
|
}
|
|
2816
3101
|
|
|
2817
3102
|
/**
|
|
2818
|
-
* Install hooks globally to ~/.claude/hooks/
|
|
2819
|
-
* This command
|
|
2820
|
-
* and merges settings into ~/.claude/settings.json
|
|
3103
|
+
* Install hooks globally to ~/.claude/hooks/ and ~/.gemini/hooks/
|
|
3104
|
+
* This command uses the unified installer logic.
|
|
2821
3105
|
*/
|
|
2822
3106
|
async function commandInstallHooks() {
|
|
2823
3107
|
console.log(c.purple('š§ Installing Teleportation Hooks Globally\n'));
|
|
2824
3108
|
|
|
2825
|
-
const globalHooksDir = path.join(HOME_DIR, '.claude', 'hooks');
|
|
2826
|
-
const globalSettings = path.join(HOME_DIR, '.claude', 'settings.json');
|
|
2827
|
-
const sourceHooksDir = path.join(TELEPORTATION_DIR, '.claude', 'hooks');
|
|
2828
|
-
|
|
2829
|
-
// List of hooks to install
|
|
2830
|
-
const hooks = [
|
|
2831
|
-
'pre_tool_use.mjs',
|
|
2832
|
-
'post_tool_use.mjs',
|
|
2833
|
-
'permission_request.mjs',
|
|
2834
|
-
'stop.mjs',
|
|
2835
|
-
'session_start.mjs',
|
|
2836
|
-
'session_end.mjs',
|
|
2837
|
-
'notification.mjs',
|
|
2838
|
-
'user_prompt_submit.mjs',
|
|
2839
|
-
'config-loader.mjs',
|
|
2840
|
-
'session-register.mjs',
|
|
2841
|
-
'heartbeat.mjs' // Spawned by session-register.mjs, needs to be in hooks directory
|
|
2842
|
-
];
|
|
2843
|
-
|
|
2844
|
-
// Step 1: Ensure directories exist
|
|
2845
|
-
console.log(c.yellow('Step 1: Creating directories...\n'));
|
|
2846
3109
|
try {
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
}
|
|
2850
|
-
if (!fs.existsSync(globalHooksDir)) {
|
|
2851
|
-
fs.mkdirSync(globalHooksDir, { recursive: true });
|
|
2852
|
-
}
|
|
2853
|
-
console.log(c.green(' ā
~/.claude/hooks/ directory ready\n'));
|
|
2854
|
-
} catch (e) {
|
|
2855
|
-
console.log(c.red(` ā Failed to create directories: ${e.message}\n`));
|
|
2856
|
-
process.exit(1);
|
|
2857
|
-
}
|
|
2858
|
-
|
|
2859
|
-
// Step 2: Copy hook files
|
|
2860
|
-
console.log(c.yellow('Step 2: Copying hooks...\n'));
|
|
2861
|
-
let installed = 0;
|
|
2862
|
-
let failed = 0;
|
|
2863
|
-
|
|
2864
|
-
for (const hook of hooks) {
|
|
2865
|
-
const src = path.join(sourceHooksDir, hook);
|
|
2866
|
-
const dest = path.join(globalHooksDir, hook);
|
|
2867
|
-
|
|
3110
|
+
// Try UHR first
|
|
3111
|
+
let installed = false;
|
|
2868
3112
|
try {
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
3113
|
+
const uhrInstallerPath = path.join(TELEPORTATION_DIR, 'lib', 'install', 'uhr-installer.js');
|
|
3114
|
+
const { isUhrAvailable, installViaUhr } = await import('file://' + uhrInstallerPath);
|
|
3115
|
+
|
|
3116
|
+
if (await isUhrAvailable()) {
|
|
3117
|
+
console.log(c.dim(' Using UHR (Universal Hook Registry)...'));
|
|
3118
|
+
const manifestPath = path.join(TELEPORTATION_DIR, 'teleportation.uhr.json');
|
|
3119
|
+
const hooksDir = path.join(TELEPORTATION_DIR, '.claude', 'hooks');
|
|
3120
|
+
const uhrResult = await installViaUhr(manifestPath, hooksDir);
|
|
3121
|
+
|
|
3122
|
+
if (uhrResult.success) {
|
|
3123
|
+
console.log(c.green(' ā
Hooks installed via UHR'));
|
|
3124
|
+
if (uhrResult.warnings.length > 0) {
|
|
3125
|
+
uhrResult.warnings.forEach(w => console.log(c.yellow(` ā ļø ${w}`)));
|
|
3126
|
+
}
|
|
3127
|
+
installed = true;
|
|
3128
|
+
} else {
|
|
3129
|
+
console.log(c.yellow(` ā ļø UHR install failed: ${uhrResult.reason}`));
|
|
3130
|
+
console.log(c.dim(' Falling back to legacy installer...'));
|
|
3131
|
+
}
|
|
2872
3132
|
}
|
|
2873
|
-
|
|
2874
|
-
fs.copyFileSync(src, dest);
|
|
2875
|
-
fs.chmodSync(dest, 0o755); // Make executable
|
|
2876
|
-
console.log(c.green(` ā
${hook}`));
|
|
2877
|
-
installed++;
|
|
2878
3133
|
} catch (e) {
|
|
2879
|
-
|
|
2880
|
-
failed++;
|
|
2881
|
-
}
|
|
2882
|
-
}
|
|
2883
|
-
|
|
2884
|
-
console.log(`\n Installed: ${c.green(installed)}, Failed: ${failed > 0 ? c.red(failed) : '0'}\n`);
|
|
2885
|
-
|
|
2886
|
-
// Step 2.5: Install lib files that hooks depend on (metadata.js, etc.)
|
|
2887
|
-
console.log(c.yellow('Step 2.5: Installing lib files...\n'));
|
|
2888
|
-
try {
|
|
2889
|
-
const installerPath = path.join(TELEPORTATION_DIR, 'lib', 'install', 'installer.js');
|
|
2890
|
-
if (fs.existsSync(installerPath)) {
|
|
2891
|
-
const installer = await import('file://' + installerPath);
|
|
2892
|
-
const libResult = await installer.installLibFiles();
|
|
2893
|
-
if (libResult.installed.length > 0) {
|
|
2894
|
-
libResult.installed.forEach(file => {
|
|
2895
|
-
console.log(c.green(` ā
${file}`));
|
|
2896
|
-
});
|
|
2897
|
-
}
|
|
2898
|
-
if (libResult.failed.length > 0) {
|
|
2899
|
-
libResult.failed.forEach(({ file, error }) => {
|
|
2900
|
-
console.log(c.yellow(` ā ļø ${file}: ${error}`));
|
|
2901
|
-
});
|
|
2902
|
-
}
|
|
2903
|
-
console.log(`\n Lib files installed: ${c.green(libResult.installed.length)}, Failed: ${libResult.failed.length > 0 ? c.yellow(libResult.failed.length) : '0'}\n`);
|
|
2904
|
-
} else {
|
|
2905
|
-
console.log(c.yellow(' ā ļø Installer module not found, skipping lib files\n'));
|
|
3134
|
+
// UHR not available
|
|
2906
3135
|
}
|
|
2907
|
-
} catch (e) {
|
|
2908
|
-
console.log(c.yellow(` ā ļø Failed to install lib files: ${e.message}\n`));
|
|
2909
|
-
}
|
|
2910
|
-
|
|
2911
|
-
// Step 3: Update settings.json
|
|
2912
|
-
console.log(c.yellow('Step 3: Updating Claude Code settings...\n'));
|
|
2913
3136
|
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
// Remove ALL existing Teleportation hooks (regardless of path)
|
|
2921
|
-
// This prevents accumulation of hooks from different installation locations
|
|
2922
|
-
const removeResult = await settingsManager.removeTeleportationHooks();
|
|
2923
|
-
if (removeResult.hooksRemoved > 0) {
|
|
2924
|
-
console.log(c.cyan(` Removed ${removeResult.hooksRemoved} existing Teleportation hook(s)\n`));
|
|
2925
|
-
}
|
|
2926
|
-
|
|
2927
|
-
// Add new hooks pointing to global hooks directory
|
|
2928
|
-
const addResult = await settingsManager.addHooks(globalHooksDir);
|
|
2929
|
-
console.log(c.green(` ā
Added ${addResult.hooksAdded} hook(s) to settings\n`));
|
|
2930
|
-
|
|
2931
|
-
// Deduplicate in case there are any remaining duplicates
|
|
2932
|
-
const dedupeResult = await settingsManager.deduplicate();
|
|
2933
|
-
if (dedupeResult.duplicatesRemoved > 0) {
|
|
2934
|
-
console.log(c.cyan(` Removed ${dedupeResult.duplicatesRemoved} duplicate hook(s)\n`));
|
|
2935
|
-
}
|
|
2936
|
-
} catch (e) {
|
|
2937
|
-
console.log(c.red(` ā Failed to update settings: ${e.message}\n`));
|
|
2938
|
-
|
|
2939
|
-
try {
|
|
2940
|
-
// Fallback to manual merge if SettingsManager fails
|
|
2941
|
-
console.log(c.yellow(' Attempting fallback method...\n'));
|
|
2942
|
-
|
|
2943
|
-
const quotePath = (p) => JSON.stringify(p);
|
|
2944
|
-
|
|
2945
|
-
const hooksConfig = {
|
|
2946
|
-
PreToolUse: [{
|
|
2947
|
-
matcher: ".*",
|
|
2948
|
-
hooks: [{
|
|
2949
|
-
type: "command",
|
|
2950
|
-
command: `node ${quotePath(path.join(globalHooksDir, 'pre_tool_use.mjs'))}`
|
|
2951
|
-
}]
|
|
2952
|
-
}],
|
|
2953
|
-
PostToolUse: [{
|
|
2954
|
-
matcher: ".*",
|
|
2955
|
-
hooks: [{
|
|
2956
|
-
type: "command",
|
|
2957
|
-
command: `node ${quotePath(path.join(globalHooksDir, 'post_tool_use.mjs'))}`
|
|
2958
|
-
}]
|
|
2959
|
-
}],
|
|
2960
|
-
PermissionRequest: [{
|
|
2961
|
-
matcher: ".*",
|
|
2962
|
-
hooks: [{
|
|
2963
|
-
type: "command",
|
|
2964
|
-
command: `node ${quotePath(path.join(globalHooksDir, 'permission_request.mjs'))}`
|
|
2965
|
-
}]
|
|
2966
|
-
}],
|
|
2967
|
-
Stop: [{
|
|
2968
|
-
matcher: ".*",
|
|
2969
|
-
hooks: [{
|
|
2970
|
-
type: "command",
|
|
2971
|
-
command: `node ${quotePath(path.join(globalHooksDir, 'stop.mjs'))}`
|
|
2972
|
-
}]
|
|
2973
|
-
}],
|
|
2974
|
-
SessionStart: [{
|
|
2975
|
-
matcher: ".*",
|
|
2976
|
-
hooks: [{
|
|
2977
|
-
type: "command",
|
|
2978
|
-
command: `node ${quotePath(path.join(globalHooksDir, 'session_start.mjs'))}`
|
|
2979
|
-
}]
|
|
2980
|
-
}],
|
|
2981
|
-
SessionEnd: [{
|
|
2982
|
-
matcher: ".*",
|
|
2983
|
-
hooks: [{
|
|
2984
|
-
type: "command",
|
|
2985
|
-
command: `node ${quotePath(path.join(globalHooksDir, 'session_end.mjs'))}`
|
|
2986
|
-
}]
|
|
2987
|
-
}],
|
|
2988
|
-
Notification: [{
|
|
2989
|
-
matcher: ".*",
|
|
2990
|
-
hooks: [{
|
|
2991
|
-
type: "command",
|
|
2992
|
-
command: `node ${quotePath(path.join(globalHooksDir, 'notification.mjs'))}`
|
|
2993
|
-
}]
|
|
2994
|
-
}],
|
|
2995
|
-
UserPromptSubmit: [{
|
|
2996
|
-
matcher: ".*",
|
|
2997
|
-
hooks: [{
|
|
2998
|
-
type: "command",
|
|
2999
|
-
command: `node ${quotePath(path.join(globalHooksDir, 'user_prompt_submit.mjs'))}`
|
|
3000
|
-
}]
|
|
3001
|
-
}]
|
|
3002
|
-
};
|
|
3137
|
+
if (!installed) {
|
|
3138
|
+
// Legacy installer fallback
|
|
3139
|
+
const installerPath = path.join(TELEPORTATION_DIR, 'lib', 'install', 'installer.js');
|
|
3140
|
+
const { install } = await import('file://' + installerPath);
|
|
3141
|
+
const sourceHooksDir = path.join(TELEPORTATION_DIR, '.claude', 'hooks');
|
|
3003
3142
|
|
|
3004
|
-
|
|
3143
|
+
const result = await install(sourceHooksDir);
|
|
3005
3144
|
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
const content = fs.readFileSync(globalSettings, 'utf8');
|
|
3010
|
-
existingSettings = JSON.parse(content);
|
|
3011
|
-
console.log(c.cyan(' Found existing settings, merging...\n'));
|
|
3012
|
-
} catch (err) {
|
|
3013
|
-
console.log(c.yellow(` ā ļø Could not parse existing settings, creating new...\n`));
|
|
3145
|
+
if (result.hooksInstalled > 0) {
|
|
3146
|
+
console.log(c.green(` ā
${result.hooksInstalled} Claude Code hooks installed`));
|
|
3147
|
+
console.log(c.dim(' Directory: ~/.claude/hooks/'));
|
|
3014
3148
|
}
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
let isTeleportationHook;
|
|
3019
|
-
try {
|
|
3020
|
-
const settingsManagerPath = path.join(TELEPORTATION_DIR, 'lib', 'settings', 'manager.js');
|
|
3021
|
-
const settingsModule = await import('file://' + settingsManagerPath);
|
|
3022
|
-
isTeleportationHook = settingsModule.isTeleportationHook;
|
|
3023
|
-
} catch (err) {
|
|
3024
|
-
// Log the specific error for debugging
|
|
3025
|
-
console.log(c.yellow(` ā ļø Could not load SettingsManager: ${err.message}`));
|
|
3026
|
-
console.log(c.yellow(' Using simplified hook detection pattern\n'));
|
|
3027
|
-
|
|
3028
|
-
// More robust fallback pattern matching the regex in SettingsManager
|
|
3029
|
-
// Matches: .claude/hooks/(pre_tool_use|post_tool_use|permission_request|stop|session_start|session_end|notification|user_prompt_submit).mjs
|
|
3030
|
-
isTeleportationHook = (cmd) => {
|
|
3031
|
-
if (!cmd || typeof cmd !== 'string') return false;
|
|
3032
|
-
return /\.claude\/hooks\/(pre_tool_use|post_tool_use|permission_request|stop|session_start|session_end|notification|user_prompt_submit)\.mjs/.test(cmd);
|
|
3033
|
-
};
|
|
3034
|
-
}
|
|
3035
|
-
|
|
3036
|
-
// Merge hooks - preserve NON-teleportation user hooks, remove ALL teleportation hooks
|
|
3037
|
-
const mergeHookArrays = (existing, incoming) => {
|
|
3038
|
-
if (!existing || !Array.isArray(existing)) return incoming;
|
|
3039
|
-
if (!incoming || !Array.isArray(incoming)) return existing;
|
|
3040
|
-
|
|
3041
|
-
// Filter out ALL teleportation hooks (regardless of path)
|
|
3042
|
-
const nonTeleportationHooks = existing.filter(matcher => {
|
|
3043
|
-
if (!matcher.hooks || !Array.isArray(matcher.hooks)) return true;
|
|
3044
|
-
// Keep matcher only if it has non-teleportation hooks
|
|
3045
|
-
return !matcher.hooks.every(h => h.command && isTeleportationHook(h.command));
|
|
3046
|
-
});
|
|
3047
|
-
|
|
3048
|
-
// Combine: existing (non-teleportation) + incoming (new teleportation hooks)
|
|
3049
|
-
return [...nonTeleportationHooks, ...incoming];
|
|
3050
|
-
};
|
|
3051
|
-
|
|
3052
|
-
// Merge all hook types with warnings about user hooks
|
|
3053
|
-
const mergedHooks = { ...(existingSettings.hooks || {}) };
|
|
3054
|
-
let hasUserHooks = false;
|
|
3055
|
-
|
|
3056
|
-
for (const [hookType, hookConfig] of Object.entries(hooksConfig)) {
|
|
3057
|
-
const existingHooksForType = existingSettings.hooks?.[hookType] || [];
|
|
3058
|
-
|
|
3059
|
-
// Find user-defined hooks (not from teleportation)
|
|
3060
|
-
const userHooks = existingHooksForType.filter(h => {
|
|
3061
|
-
if (!h.hooks || !Array.isArray(h.hooks)) return true;
|
|
3062
|
-
// Keep only hooks that are NOT teleportation hooks
|
|
3063
|
-
return !h.hooks.every(hh => hh.command && isTeleportationHook(hh.command));
|
|
3064
|
-
});
|
|
3065
|
-
|
|
3066
|
-
if (userHooks.length > 0) {
|
|
3067
|
-
hasUserHooks = true;
|
|
3068
|
-
console.log(c.yellow(` ā ļø Preserving ${userHooks.length} custom ${hookType} hook(s):`));
|
|
3069
|
-
userHooks.forEach(h => {
|
|
3070
|
-
const cmds = (h.hooks || []).map(hh => hh.command || 'unknown');
|
|
3071
|
-
cmds.forEach(cmd => console.log(c.dim(` ⢠${cmd}`)));
|
|
3072
|
-
});
|
|
3073
|
-
console.log('');
|
|
3149
|
+
if (result.geminiHooksInstalled > 0) {
|
|
3150
|
+
console.log(c.green(` ā
${result.geminiHooksInstalled} Gemini CLI hooks installed`));
|
|
3151
|
+
console.log(c.dim(' Directory: ~/.gemini/hooks/'));
|
|
3074
3152
|
}
|
|
3075
|
-
|
|
3076
|
-
mergedHooks[hookType] = mergeHookArrays(existingHooksForType, hookConfig);
|
|
3077
|
-
}
|
|
3078
|
-
|
|
3079
|
-
if (hasUserHooks) {
|
|
3080
|
-
console.log(c.cyan(' Your custom hooks will continue to work alongside Teleportation hooks.\n'));
|
|
3081
|
-
}
|
|
3082
|
-
|
|
3083
|
-
const mergedSettings = {
|
|
3084
|
-
...existingSettings,
|
|
3085
|
-
hooks: mergedHooks
|
|
3086
|
-
};
|
|
3087
|
-
|
|
3088
|
-
// Write settings
|
|
3089
|
-
fs.writeFileSync(globalSettings, JSON.stringify(mergedSettings, null, 2));
|
|
3090
|
-
console.log(c.green(' ā
~/.claude/settings.json updated\n'));
|
|
3091
|
-
|
|
3092
|
-
// Deduplicate in fallback path (Gap #2)
|
|
3093
|
-
console.log(c.yellow(' Running deduplication check...\n'));
|
|
3094
|
-
let duplicatesRemoved = 0;
|
|
3095
3153
|
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
const seenCommands = new Set();
|
|
3100
|
-
const uniqueMatchers = [];
|
|
3101
|
-
|
|
3102
|
-
for (const matcher of matchers) {
|
|
3103
|
-
const commands = (matcher.hooks || []).map(h => h.command).filter(Boolean);
|
|
3104
|
-
const isUnique = !commands.some(cmd => seenCommands.has(cmd));
|
|
3105
|
-
|
|
3106
|
-
if (isUnique) {
|
|
3107
|
-
uniqueMatchers.push(matcher);
|
|
3108
|
-
commands.forEach(cmd => seenCommands.add(cmd));
|
|
3109
|
-
} else {
|
|
3110
|
-
duplicatesRemoved++;
|
|
3111
|
-
}
|
|
3154
|
+
if (result.libFilesInstalled > 0) {
|
|
3155
|
+
console.log(c.green(` ā
${result.libFilesInstalled} shared library files installed`));
|
|
3112
3156
|
}
|
|
3113
|
-
|
|
3114
|
-
mergedSettings.hooks[hookType] = uniqueMatchers;
|
|
3115
3157
|
}
|
|
3116
3158
|
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
console.log(c.red(' Please report this issue at: https://github.com/dundas/teleportation-private/issues\n'));
|
|
3126
|
-
process.exit(1);
|
|
3127
|
-
}
|
|
3128
|
-
}
|
|
3129
|
-
|
|
3130
|
-
// Summary
|
|
3131
|
-
console.log(c.cyan('āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®'));
|
|
3132
|
-
console.log(c.cyan('ā ā'));
|
|
3133
|
-
console.log(c.cyan('ā š ') + c.green('Hooks Installed Successfully!') + c.cyan(' ā'));
|
|
3134
|
-
console.log(c.cyan('ā ā'));
|
|
3135
|
-
console.log(c.cyan('ā Hooks location: ~/.claude/hooks/ ā'));
|
|
3136
|
-
console.log(c.cyan('ā Settings: ~/.claude/settings.json ā'));
|
|
3137
|
-
console.log(c.cyan('ā ā'));
|
|
3138
|
-
console.log(c.cyan('ā ') + c.yellow('ā ļø Restart Claude Code') + c.cyan(' to activate hooks. ā'));
|
|
3139
|
-
console.log(c.cyan('ā ā'));
|
|
3140
|
-
console.log(c.cyan('ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ\n'));
|
|
3159
|
+
console.log(c.cyan('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®'));
|
|
3160
|
+
console.log(c.cyan('ā ā'));
|
|
3161
|
+
console.log(c.cyan('ā š ') + c.green('Hooks Installed Successfully!') + c.cyan(' ā'));
|
|
3162
|
+
console.log(c.cyan('ā ā'));
|
|
3163
|
+
console.log(c.cyan('ā ') + c.yellow('ā ļø Important:') + c.cyan(' Restart your AI assistants to ā'));
|
|
3164
|
+
console.log(c.cyan('ā activate the new hooks. ā'));
|
|
3165
|
+
console.log(c.cyan('ā ā'));
|
|
3166
|
+
console.log(c.cyan('ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ\n'));
|
|
3141
3167
|
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3168
|
+
} catch (error) {
|
|
3169
|
+
console.log(c.red(`\nā Installation failed: ${error.message}\n`));
|
|
3170
|
+
process.exit(1);
|
|
3171
|
+
}
|
|
3146
3172
|
}
|
|
3147
3173
|
|
|
3148
3174
|
async function commandCommand() {
|
|
@@ -3441,7 +3467,7 @@ const command = process.argv[2] || 'help';
|
|
|
3441
3467
|
const args = process.argv.slice(3);
|
|
3442
3468
|
|
|
3443
3469
|
// Handle async commands that need to complete before exit
|
|
3444
|
-
const asyncCommands = ['login', 'logout', 'status', 'test', 'env', 'config', 'daemon', 'away', 'back', 'daemon-status', 'command', 'inbox', 'inbox-ack', 'install-hooks', 'update', 'remote'];
|
|
3470
|
+
const asyncCommands = ['login', 'logout', 'status', 'test', 'env', 'config', 'daemon', 'away', 'back', 'daemon-status', 'command', 'inbox', 'inbox-ack', 'install-hooks', 'update', 'remote', 'off'];
|
|
3445
3471
|
// Keep this list in sync with switch cases below
|
|
3446
3472
|
asyncCommands.push('github');
|
|
3447
3473
|
if (asyncCommands.includes(command)) {
|
|
@@ -3475,7 +3501,10 @@ try {
|
|
|
3475
3501
|
});
|
|
3476
3502
|
break;
|
|
3477
3503
|
case 'off':
|
|
3478
|
-
commandOff()
|
|
3504
|
+
commandOff().catch(err => {
|
|
3505
|
+
console.log(c.red('Error: ' + err.message));
|
|
3506
|
+
process.exit(1);
|
|
3507
|
+
});
|
|
3479
3508
|
break;
|
|
3480
3509
|
case 'status':
|
|
3481
3510
|
commandStatus().catch(err => {
|
|
@@ -3618,6 +3647,12 @@ try {
|
|
|
3618
3647
|
process.exit(1);
|
|
3619
3648
|
});
|
|
3620
3649
|
break;
|
|
3650
|
+
case 'teleport':
|
|
3651
|
+
commandTeleport(args).catch(err => {
|
|
3652
|
+
console.error(c.red('ā Error:'), err.message);
|
|
3653
|
+
process.exit(1);
|
|
3654
|
+
});
|
|
3655
|
+
break;
|
|
3621
3656
|
case 'version':
|
|
3622
3657
|
case '--version':
|
|
3623
3658
|
case '-v':
|