diff --git a/src/cli/completion-cli.ts b/src/cli/completion-cli.ts index 01cd02c018c..afa881b2fbd 100644 --- a/src/cli/completion-cli.ts +++ b/src/cli/completion-cli.ts @@ -442,17 +442,17 @@ function generateZshSubcmdList(cmd: Command): string { } function generateZshSubcommands(program: Command, prefix: string): string { - let script = ""; + const segments: string[] = []; for (const cmd of program.commands) { const cmdName = cmd.name(); const funcName = `_${prefix}_${cmdName.replace(/-/g, "_")}`; // Recurse first - script += generateZshSubcommands(cmd, `${prefix}_${cmdName.replace(/-/g, "_")}`); + segments.push(generateZshSubcommands(cmd, `${prefix}_${cmdName.replace(/-/g, "_")}`)); const subCommands = cmd.commands; if (subCommands.length > 0) { - script += ` + segments.push(` ${funcName}() { local -a commands local -a options @@ -470,17 +470,17 @@ ${funcName}() { ;; esac } -`; +`); } else { - script += ` + segments.push(` ${funcName}() { _arguments -C \\ ${generateZshArgs(cmd)} } -`; +`); } } - return script; + return segments.join(""); } function generateBashCompletion(program: Command): string { @@ -529,11 +529,11 @@ function generateBashSubcommand(cmd: Command): string { function generatePowerShellCompletion(program: Command): string { const rootCmd = program.name(); - const visit = (cmd: Command, parents: string[]): string => { + const visit = (cmd: Command, parents: string[]): string[] => { const cmdName = cmd.name(); const fullPath = [...parents, cmdName].join(" "); - let script = ""; + const segments: string[] = []; // Command completion for this level const subCommands = cmd.commands.map((c) => c.name()); @@ -541,25 +541,25 @@ function generatePowerShellCompletion(program: Command): string { const allCompletions = [...subCommands, ...options].map((s) => `'${s}'`).join(","); if (allCompletions.length > 0) { - script += ` + segments.push(` if ($commandPath -eq '${fullPath}') { $completions = @(${allCompletions}) $completions | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object { [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterName', $_) } } -`; +`); } // Recurse for (const sub of cmd.commands) { - script += visit(sub, [...parents, cmdName]); + segments.push(...visit(sub, [...parents, cmdName])); } - return script; + return segments; }; - const rootBody = visit(program, []); + const rootBody = visit(program, []).join(""); return ` Register-ArgumentCompleter -Native -CommandName ${rootCmd} -ScriptBlock { @@ -593,65 +593,57 @@ Register-ArgumentCompleter -Native -CommandName ${rootCmd} -ScriptBlock { function generateFishCompletion(program: Command): string { const rootCmd = program.name(); - let script = ""; + const segments: string[] = []; const visit = (cmd: Command, parents: string[]) => { const cmdName = cmd.name(); - const fullPath = [...parents]; - if (parents.length > 0) { - fullPath.push(cmdName); - } // Only push if not root, or consistent root handling - - // Fish uses 'seen_subcommand_from' to determine context. - // For root: complete -c openclaw -n "__fish_use_subcommand" -a "subcmd" -d "desc" // Root logic if (parents.length === 0) { // Subcommands of root for (const sub of cmd.commands) { - script += buildFishSubcommandCompletionLine({ - rootCmd, - condition: "__fish_use_subcommand", - name: sub.name(), - description: sub.description(), - }); + segments.push( + buildFishSubcommandCompletionLine({ + rootCmd, + condition: "__fish_use_subcommand", + name: sub.name(), + description: sub.description(), + }), + ); } // Options of root for (const opt of cmd.options) { - script += buildFishOptionCompletionLine({ - rootCmd, - condition: "__fish_use_subcommand", - flags: opt.flags, - description: opt.description, - }); + segments.push( + buildFishOptionCompletionLine({ + rootCmd, + condition: "__fish_use_subcommand", + flags: opt.flags, + description: opt.description, + }), + ); } } else { - // Nested commands - // Logic: if seen subcommand matches parents... - // But fish completion logic is simpler if we just say "if we haven't seen THIS command yet but seen parent" - // Actually, a robust fish completion often requires defining a function to check current line. - // For simplicity, we'll assume standard fish helper __fish_seen_subcommand_from. - - // To properly scope to 'openclaw gateway' and not 'openclaw other gateway', we need to check the sequence. - // A simplified approach: - // Subcommands for (const sub of cmd.commands) { - script += buildFishSubcommandCompletionLine({ - rootCmd, - condition: `__fish_seen_subcommand_from ${cmdName}`, - name: sub.name(), - description: sub.description(), - }); + segments.push( + buildFishSubcommandCompletionLine({ + rootCmd, + condition: `__fish_seen_subcommand_from ${cmdName}`, + name: sub.name(), + description: sub.description(), + }), + ); } // Options for (const opt of cmd.options) { - script += buildFishOptionCompletionLine({ - rootCmd, - condition: `__fish_seen_subcommand_from ${cmdName}`, - flags: opt.flags, - description: opt.description, - }); + segments.push( + buildFishOptionCompletionLine({ + rootCmd, + condition: `__fish_seen_subcommand_from ${cmdName}`, + flags: opt.flags, + description: opt.description, + }), + ); } } @@ -661,5 +653,5 @@ function generateFishCompletion(program: Command): string { }; visit(program, []); - return script; + return segments.join(""); }