diff --git a/src/cac.ts b/src/cac.ts index a0d66f5..1bf032a 100644 --- a/src/cac.ts +++ b/src/cac.ts @@ -15,6 +15,9 @@ const quotedProcessExecArgs = process.execArgv.map(quoteIfNeeded); const x = `${quotedExecPath} ${quotedProcessExecArgs.join(' ')} ${quotedProcessArgs[0]}`; +// Regex to detect if an option takes a value (has or [optional] parameters) +const VALUE_OPTION_RE = /<[^>]+>|\[[^\]]+\]/; + function quoteIfNeeded(path: string): string { return path.includes(' ') ? `'${path}'` : path; } @@ -78,8 +81,12 @@ export default async function tab( const targetCommand = isRootCommand ? t : command; if (targetCommand) { const handler = commandCompletionConfig?.options?.[argName]; + + // Check if option takes a value (has <> or [] in rawName, or is marked as required) + const takesValue = + option.required || VALUE_OPTION_RE.test(option.rawName); + if (handler) { - // Has custom handler → value option if (shortFlag) { targetCommand.option( argName, @@ -90,8 +97,24 @@ export default async function tab( } else { targetCommand.option(argName, option.description || '', handler); } + } else if (takesValue) { + // Takes value but no custom handler = value option with no completions + if (shortFlag) { + targetCommand.option( + argName, + option.description || '', + async () => [], // Empty completions + shortFlag + ); + } else { + targetCommand.option( + argName, + option.description || '', + async () => [] + ); + } } else { - // No custom handler → boolean flag + // No custom handler and doesn't take value = boolean flag if (shortFlag) { targetCommand.option(argName, option.description || '', shortFlag); } else {