[READ-ONLY] Mirror of https://github.com/bombshell-dev/tab. shell autocompletions for javascript CLIs
4

Configure Feed

Select the types of activity you want to include in your feed.

fix: package manager completion support (#78)

* fix: npm exec

* prettier

* changeset

authored by

AmirHossein Sakhravi and committed by
GitHub
(Oct 19, 2025, 9:57 PM +0330) 8defa6f5 71782881

+59 -32
+5
.changeset/cuddly-clubs-count.md
··· 1 + --- 2 + '@bomb.sh/tab': patch 3 + --- 4 + 5 + npm exec support
+54 -32
bin/package-manager-completion.ts
··· 12 12 packageManager: string 13 13 ): Promise<boolean> { 14 14 try { 15 - debugLog(`Checking if ${cliName} has completions via ${packageManager}`); 16 - const command = `${packageManager} ${cliName} complete --`; 17 - const result = execSync(command, { 15 + const result = execSync(`${cliName} complete --`, { 18 16 encoding: 'utf8', 19 17 stdio: ['pipe', 'pipe', 'ignore'], 20 18 timeout: 1000, 21 19 }); 22 - const hasCompletions = !!result.trim(); 23 - debugLog(`${cliName} supports completions: ${hasCompletions}`); 24 - return hasCompletions; 25 - } catch (error) { 26 - debugLog(`Error checking completions for ${cliName}:`, error); 20 + if (result.trim()) return true; 21 + } catch {} 22 + 23 + try { 24 + const result = execSync(`${packageManager} ${cliName} complete --`, { 25 + encoding: 'utf8', 26 + stdio: ['pipe', 'pipe', 'ignore'], 27 + timeout: 1000, 28 + }); 29 + return !!result.trim(); 30 + } catch { 27 31 return false; 28 32 } 29 33 } ··· 33 37 packageManager: string, 34 38 args: string[] 35 39 ): Promise<string[]> { 40 + const completeArgs = args.map((arg) => 41 + arg.includes(' ') ? `"${arg}"` : arg 42 + ); 43 + 36 44 try { 37 - const completeArgs = args.map((arg) => 38 - arg.includes(' ') ? `"${arg}"` : arg 45 + const result = execSync( 46 + `${cliName} complete -- ${completeArgs.join(' ')}`, 47 + { 48 + encoding: 'utf8', 49 + stdio: ['pipe', 'pipe', 'ignore'], 50 + timeout: 1000, 51 + } 39 52 ); 40 - const completeCommand = `${packageManager} ${cliName} complete -- ${completeArgs.join(' ')}`; 41 - debugLog(`Getting completions with command: ${completeCommand}`); 42 - 43 - const result = execSync(completeCommand, { 44 - encoding: 'utf8', 45 - stdio: ['pipe', 'pipe', 'ignore'], 46 - timeout: 1000, 47 - }); 53 + if (result.trim()) { 54 + return result.trim().split('\n').filter(Boolean); 55 + } 56 + } catch {} 48 57 49 - const completions = result.trim().split('\n').filter(Boolean); 50 - debugLog(`Got ${completions.length} completions from ${cliName}`); 51 - return completions; 52 - } catch (error) { 53 - debugLog(`Error getting completions from ${cliName}:`, error); 58 + try { 59 + const result = execSync( 60 + `${packageManager} ${cliName} complete -- ${completeArgs.join(' ')}`, 61 + { 62 + encoding: 'utf8', 63 + stdio: ['pipe', 'pipe', 'ignore'], 64 + timeout: 1000, 65 + } 66 + ); 67 + return result.trim().split('\n').filter(Boolean); 68 + } catch { 54 69 return []; 55 70 } 56 71 } ··· 69 84 this.packageManager = packageManager; 70 85 } 71 86 72 - // Enhanced parse method with package manager logic 87 + private stripPackageManagerCommands(args: string[]): string[] { 88 + if (args.length === 0) return args; 89 + const execCommands = ['exec', 'x', 'run', 'dlx']; 90 + if (execCommands.includes(args[0])) return args.slice(1); 91 + return args; 92 + } 93 + 73 94 async parse(args: string[]) { 74 - // Handle package manager completions first 75 - if (args.length >= 1 && args[0].trim() !== '') { 76 - const potentialCliName = args[0]; 95 + const normalizedArgs = this.stripPackageManagerCommands(args); 96 + 97 + if (normalizedArgs.length >= 1 && normalizedArgs[0].trim() !== '') { 98 + const potentialCliName = normalizedArgs[0]; 77 99 const knownCommands = [...this.commands.keys()]; 78 100 79 101 if (!knownCommands.includes(potentialCliName)) { ··· 83 105 ); 84 106 85 107 if (hasCompletions) { 86 - const cliArgs = args.slice(1); 108 + const cliArgs = normalizedArgs.slice(1); 87 109 const suggestions = await getCliCompletions( 88 110 potentialCliName, 89 111 this.packageManager, ··· 91 113 ); 92 114 93 115 if (suggestions.length > 0) { 94 - // Print completions directly in the same format as the core library 116 + debugLog( 117 + `Returning ${suggestions.length} completions for ${potentialCliName}` 118 + ); 95 119 for (const suggestion of suggestions) { 96 120 if (suggestion.startsWith(':')) continue; 97 - 98 121 if (suggestion.includes('\t')) { 99 122 const [value, description] = suggestion.split('\t'); 100 123 console.log(`${value}\t${description}`); ··· 102 125 console.log(suggestion); 103 126 } 104 127 } 105 - console.log(':4'); // Shell completion directive (NoFileComp) 128 + console.log(':4'); 106 129 return; 107 130 } 108 131 } 109 132 } 110 133 } 111 134 112 - // Fall back to regular completion logic (shows basic package manager commands) 113 135 return super.parse(args); 114 136 } 115 137 }