···7575type Option = {
7676 description: string;
7777 handler: Handler;
7878+ alias?: string;
7879};
79808081type Command = {
···117118 command: string,
118119 option: string,
119120 description: string,
120120- handler: Handler
121121+ handler: Handler,
122122+ alias?: string
121123 ) {
122124 const cmd = this.commands.get(command);
123125 if (!cmd) {
124126 throw new Error(`Command ${command} not found.`);
125127 }
126126- cmd.options.set(option, { description, handler });
128128+ cmd.options.set(option, { description, handler, alias });
127129 return option;
128130 }
129131···233235 toComplete: string,
234236 endsWithSpace: boolean
235237 ): boolean {
236236- return lastPrevArg?.startsWith('--') || toComplete.startsWith('--');
238238+ return (
239239+ lastPrevArg?.startsWith('--') ||
240240+ lastPrevArg?.startsWith('-') ||
241241+ toComplete.startsWith('--') ||
242242+ toComplete.startsWith('-')
243243+ );
237244 }
238245239246 private shouldCompleteCommands(
···255262 let valueToComplete = toComplete;
256263257264 if (toComplete.includes('=')) {
258258- // Handle --flag=value case
265265+ // Handle --flag=value or -f=value case
259266 const parts = toComplete.split('=');
260267 flagName = parts[0];
261268 valueToComplete = parts[1] || '';
262262- } else if (lastPrevArg?.startsWith('--')) {
263263- // Handle --flag value case
269269+ } else if (lastPrevArg?.startsWith('-')) {
270270+ // Handle --flag value or -f value case
264271 flagName = lastPrevArg;
265272 }
266273267274 if (flagName) {
268268- const option = command.options.get(flagName);
275275+ // Try to find the option by long name or alias
276276+ let option = command.options.get(flagName);
277277+ if (!option) {
278278+ // If not found by direct match, try to find by alias
279279+ for (const [name, opt] of command.options) {
280280+ if (opt.alias && `-${opt.alias}` === flagName) {
281281+ option = opt;
282282+ flagName = name; // Use the long name for completion
283283+ break;
284284+ }
285285+ }
286286+ }
287287+269288 if (option) {
270289 const suggestions = await option.handler(
271290 previousArgs,
···286305 }
287306288307 // Handle flag name completion
289289- if (toComplete.startsWith('--')) {
308308+ if (toComplete.startsWith('-')) {
309309+ const isShortFlag =
310310+ toComplete.startsWith('-') && !toComplete.startsWith('--');
311311+290312 for (const [name, option] of command.options) {
291291- if (name.startsWith(toComplete)) {
313313+ // For short flags (-), only show aliases
314314+ if (isShortFlag) {
315315+ if (option.alias && `-${option.alias}`.startsWith(toComplete)) {
316316+ this.completions.push({
317317+ value: `-${option.alias}`,
318318+ description: option.description,
319319+ });
320320+ }
321321+ }
322322+ // For long flags (--), show the full names
323323+ else if (name.startsWith(toComplete)) {
292324 this.completions.push({
293325 value: name,
294326 description: option.description,
+66
tests/__snapshots__/cli.test.ts.snap
···66"
77`;
8899+exports[`cli completion tests for cac > cli option completion tests > should complete option for partial input '{ partial: '-H', expected: '-H' }' 1`] = `
1010+":4
1111+"
1212+`;
1313+1414+exports[`cli completion tests for cac > cli option completion tests > should complete option for partial input '{ partial: '-p', expected: '-p' }' 1`] = `
1515+":4
1616+"
1717+`;
1818+919exports[`cli completion tests for cac > cli option exclusion tests > should not suggest already specified option '{ specified: '--config', shouldNotContain: '--config' }' 1`] = `
1020":4
1121"
···7080exports[`cli completion tests for cac > positional argument completions > should complete single positional argument when ending with space 1`] = `
7181"main.ts Main file
7282index.ts Index file
8383+:4
8484+"
8585+`;
8686+8787+exports[`cli completion tests for cac > short flag handling > should handle global short flags 1`] = `
8888+":4
8989+"
9090+`;
9191+9292+exports[`cli completion tests for cac > short flag handling > should handle short flag value completion 1`] = `
9393+":4
9494+"
9595+`;
9696+9797+exports[`cli completion tests for cac > short flag handling > should handle short flag with equals sign 1`] = `
9898+":4
9999+"
100100+`;
101101+102102+exports[`cli completion tests for cac > short flag handling > should not show duplicate options when short flag is used 1`] = `
103103+"--config Use specified config file
104104+--mode Set env mode
105105+--logLevel info | warn | error | silent
73106:4
74107"
75108`;
···87120"
88121`;
89122123123+exports[`cli completion tests for citty > cli option completion tests > should complete option for partial input '{ partial: '-H', expected: '-H' }' 1`] = `
124124+":4
125125+"
126126+`;
127127+128128+exports[`cli completion tests for citty > cli option completion tests > should complete option for partial input '{ partial: '-p', expected: '-p' }' 1`] = `
129129+":4
130130+"
131131+`;
132132+90133exports[`cli completion tests for citty > cli option exclusion tests > should not suggest already specified option '{ specified: '--config', shouldNotContain: '--config' }' 1`] = `
91134":4
92135"
···131174exports[`cli completion tests for citty > edge case completions for end with space > should suggest port values if user typed \`--port=\` and hasn't typed a space or value yet 1`] = `
132175"--port=3000 Development server port
133176--port=8080 Alternative port
177177+:4
178178+"
179179+`;
180180+181181+exports[`cli completion tests for citty > short flag handling > should handle global short flags 1`] = `
182182+":4
183183+"
184184+`;
185185+186186+exports[`cli completion tests for citty > short flag handling > should handle short flag value completion 1`] = `
187187+":4
188188+"
189189+`;
190190+191191+exports[`cli completion tests for citty > short flag handling > should handle short flag with equals sign 1`] = `
192192+":4
193193+"
194194+`;
195195+196196+exports[`cli completion tests for citty > short flag handling > should not show duplicate options when short flag is used 1`] = `
197197+"--config Use specified config file
198198+--mode Set env mode
199199+--logLevel info | warn | error | silent
134200:4
135201"
136202`;
+31-1
tests/cli.test.ts
···2525 });
26262727 describe('cli option completion tests', () => {
2828- const optionTests = [{ partial: '--p', expected: '--port' }];
2828+ const optionTests = [
2929+ { partial: '--p', expected: '--port' },
3030+ { partial: '-p', expected: '-p' }, // Test short flag completion
3131+ { partial: '-H', expected: '-H' }, // Test another short flag completion
3232+ ];
29333034 test.each(optionTests)(
3135 "should complete option for partial input '%s'",
···959996100 it("should suggest port values if user typed `--port=` and hasn't typed a space or value yet", async () => {
97101 const command = `${commandPrefix} dev --port=`;
102102+ const output = await runCommand(command);
103103+ expect(output).toMatchSnapshot();
104104+ });
105105+ });
106106+107107+ describe('short flag handling', () => {
108108+ it('should handle short flag value completion', async () => {
109109+ const command = `${commandPrefix} dev -p `;
110110+ const output = await runCommand(command);
111111+ expect(output).toMatchSnapshot();
112112+ });
113113+114114+ it('should handle short flag with equals sign', async () => {
115115+ const command = `${commandPrefix} dev -p=3`;
116116+ const output = await runCommand(command);
117117+ expect(output).toMatchSnapshot();
118118+ });
119119+120120+ it('should handle global short flags', async () => {
121121+ const command = `${commandPrefix} -c `;
122122+ const output = await runCommand(command);
123123+ expect(output).toMatchSnapshot();
124124+ });
125125+126126+ it('should not show duplicate options when short flag is used', async () => {
127127+ const command = `${commandPrefix} -c vite.config.js --`;
98128 const output = await runCommand(command);
99129 expect(output).toMatchSnapshot();
100130 });