completion.ts
1 /** 2 * Shell tab-completion support for opencli. 3 * 4 * Provides: 5 * - Shell script generators for bash, zsh, and fish 6 * - Dynamic completion logic that returns candidates for the current cursor position 7 */ 8 9 import { getRegistry } from './registry.js'; 10 import { CliError } from './errors.js'; 11 import { 12 BUILTIN_COMMANDS, 13 bashCompletionScript, 14 zshCompletionScript, 15 fishCompletionScript, 16 } from './completion-shared.js'; 17 18 // Re-export shell scripts so existing callers (cli.ts) don't break 19 export { bashCompletionScript, zshCompletionScript, fishCompletionScript }; 20 21 // ── Dynamic completion logic ─────────────────────────────────────────────── 22 23 /** 24 * Return completion candidates given the current command-line words and cursor index. 25 * Requires full CLI discovery to have been run (uses getRegistry()). 26 * 27 * @param words - The argv after 'opencli' (words[0] is the first arg, e.g. site name) 28 * @param cursor - 1-based position of the word being completed (1 = first arg) 29 */ 30 export function getCompletions(words: string[], cursor: number): string[] { 31 // cursor === 1 → completing the first argument (site name or built-in command) 32 if (cursor <= 1) { 33 const sites = new Set<string>(); 34 for (const [, cmd] of getRegistry()) { 35 sites.add(cmd.site); 36 } 37 return [...BUILTIN_COMMANDS, ...sites].sort(); 38 } 39 40 const site = words[0]; 41 42 // If the first word is a built-in command, no further completion 43 if (BUILTIN_COMMANDS.includes(site)) { 44 return []; 45 } 46 47 // cursor === 2 → completing the sub-command name under a site 48 if (cursor === 2) { 49 const subcommands: string[] = []; 50 for (const [, cmd] of getRegistry()) { 51 if (cmd.site === site) { 52 subcommands.push(cmd.name); 53 if (cmd.aliases?.length) subcommands.push(...cmd.aliases); 54 } 55 } 56 return [...new Set(subcommands)].sort(); 57 } 58 59 // cursor >= 3 → no further completion 60 return []; 61 } 62 63 // ── Shell script generators ──────────────────────────────────────────────── 64 65 /** 66 * Print the completion script for the requested shell. 67 */ 68 export function printCompletionScript(shell: string): void { 69 switch (shell) { 70 case 'bash': 71 process.stdout.write(bashCompletionScript()); 72 break; 73 case 'zsh': 74 process.stdout.write(zshCompletionScript()); 75 break; 76 case 'fish': 77 process.stdout.write(fishCompletionScript()); 78 break; 79 default: 80 throw new CliError('UNSUPPORTED_SHELL', `Unsupported shell: ${shell}. Supported: bash, zsh, fish`); 81 } 82 }