feat: add gateway lock, shell completion, and tailscale serve (Tier 4 features 1-3)

This commit is contained in:
William Valentin
2026-02-09 13:29:59 -08:00
parent 9be8f76bc7
commit 4413c4dc7c
12 changed files with 535 additions and 5 deletions
+149
View File
@@ -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);
}
});
}