feat: add gateway lock, shell completion, and tailscale serve (Tier 4 features 1-3)
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
import type { Command } from 'commander';
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
import { homedir } from 'os';
|
||||
|
||||
const SUBCOMMANDS = ['start', 'tui', 'send', 'sessions', 'doctor', 'config', 'completion'];
|
||||
|
||||
const SUBCOMMAND_OPTIONS: Record<string, string[]> = {
|
||||
start: ['-c', '--config'],
|
||||
tui: ['-c', '--config', '-f', '--fullscreen'],
|
||||
send: ['-c', '--config', '--no-tools'],
|
||||
config: ['-c', '--config', '--raw'],
|
||||
completion: ['--install'],
|
||||
};
|
||||
|
||||
export function generateBashCompletion(): string {
|
||||
const subcmds = SUBCOMMANDS.join(' ');
|
||||
const cases = Object.entries(SUBCOMMAND_OPTIONS)
|
||||
.map(([cmd, opts]) => ` ${cmd}) COMPREPLY=( $(compgen -W "${opts.join(' ')}" -- "$cur") ) ;;`)
|
||||
.join('\n');
|
||||
|
||||
return `# Flynn bash completion — generated by 'flynn completion bash'
|
||||
_flynn_completions() {
|
||||
local cur prev subcmd
|
||||
cur="\${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
||||
|
||||
if [[ \${COMP_CWORD} -eq 1 ]]; then
|
||||
COMPREPLY=( $(compgen -W "${subcmds} --help --version" -- "$cur") )
|
||||
return
|
||||
fi
|
||||
|
||||
subcmd="\${COMP_WORDS[1]}"
|
||||
case "$subcmd" in
|
||||
${cases}
|
||||
completion) COMPREPLY=( $(compgen -W "bash zsh fish --install" -- "$cur") ) ;;
|
||||
*) COMPREPLY=() ;;
|
||||
esac
|
||||
}
|
||||
complete -F _flynn_completions flynn
|
||||
`;
|
||||
}
|
||||
|
||||
export function generateZshCompletion(): string {
|
||||
const subcmds = SUBCOMMANDS.map(c => `'${c}:${c} subcommand'`).join(' ');
|
||||
const cases = Object.entries(SUBCOMMAND_OPTIONS)
|
||||
.map(([cmd, opts]) => {
|
||||
const flags = opts.filter(o => o.startsWith('--')).map(o => `'${o}'`).join(' ');
|
||||
return ` ${cmd}) compadd ${flags} ;;`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
return `#compdef flynn
|
||||
# Flynn zsh completion — generated by 'flynn completion zsh'
|
||||
_flynn() {
|
||||
local -a subcmds
|
||||
subcmds=(${subcmds})
|
||||
|
||||
if (( CURRENT == 2 )); then
|
||||
_describe 'command' subcmds
|
||||
return
|
||||
fi
|
||||
|
||||
case "\${words[2]}" in
|
||||
${cases}
|
||||
completion) compadd bash zsh fish '--install' ;;
|
||||
*) ;;
|
||||
esac
|
||||
}
|
||||
compdef _flynn flynn
|
||||
`;
|
||||
}
|
||||
|
||||
export function generateFishCompletion(): string {
|
||||
const lines = [
|
||||
`# Flynn fish completion — generated by 'flynn completion fish'`,
|
||||
`# Disable file completions by default`,
|
||||
`complete -c flynn -f`,
|
||||
``,
|
||||
`# Subcommands`,
|
||||
];
|
||||
|
||||
for (const cmd of SUBCOMMANDS) {
|
||||
lines.push(`complete -c flynn -n '__fish_use_subcommand' -a '${cmd}' -d '${cmd} subcommand'`);
|
||||
}
|
||||
lines.push(`complete -c flynn -n '__fish_use_subcommand' -l help -d 'Show help'`);
|
||||
lines.push(`complete -c flynn -n '__fish_use_subcommand' -l version -d 'Show version'`);
|
||||
lines.push('');
|
||||
lines.push('# Subcommand options');
|
||||
|
||||
for (const [cmd, opts] of Object.entries(SUBCOMMAND_OPTIONS)) {
|
||||
for (const opt of opts) {
|
||||
const flag = opt.replace(/^-+/, '');
|
||||
const short = opt.startsWith('--') ? '' : ` -s ${flag}`;
|
||||
const long = opt.startsWith('--') ? ` -l ${flag}` : '';
|
||||
lines.push(`complete -c flynn -n '__fish_seen_subcommand_from ${cmd}'${short}${long}`);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(`complete -c flynn -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish' -d 'Shell type'`);
|
||||
lines.push('');
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function getInstallPath(shell: string): string {
|
||||
const home = homedir();
|
||||
switch (shell) {
|
||||
case 'bash': return resolve(home, '.local/share/bash-completion/completions/flynn');
|
||||
case 'zsh': return resolve(home, '.zfunc/_flynn');
|
||||
case 'fish': return resolve(home, '.config/fish/completions/flynn.fish');
|
||||
default: throw new Error(`Unknown shell: ${shell}`);
|
||||
}
|
||||
}
|
||||
|
||||
export function registerCompletionCommand(program: Command): void {
|
||||
program
|
||||
.command('completion')
|
||||
.description('Generate shell completion script')
|
||||
.argument('<shell>', 'Shell type: bash, zsh, or fish')
|
||||
.option('--install', 'Install to standard location')
|
||||
.action((shell: string, opts: { install?: boolean }) => {
|
||||
const generators: Record<string, () => string> = {
|
||||
bash: generateBashCompletion,
|
||||
zsh: generateZshCompletion,
|
||||
fish: generateFishCompletion,
|
||||
};
|
||||
|
||||
const generator = generators[shell];
|
||||
if (!generator) {
|
||||
console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const script = generator();
|
||||
|
||||
if (opts.install) {
|
||||
const installPath = getInstallPath(shell);
|
||||
mkdirSync(resolve(installPath, '..'), { recursive: true });
|
||||
writeFileSync(installPath, script, 'utf-8');
|
||||
console.log(`Completion script installed to: ${installPath}`);
|
||||
if (shell === 'zsh') {
|
||||
console.log(`Ensure ~/.zfunc is in your fpath: fpath=(~/.zfunc $fpath)`);
|
||||
}
|
||||
} else {
|
||||
process.stdout.write(script);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user