Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e89f79e
added CLI completions support for: bash, fish and powershell
noameron Dec 15, 2025
ad53abc
Add bash/fish/powershell completions
noameron Dec 23, 2025
07cad96
Archive extend-shell-completions
noameron Dec 23, 2025
0bac2e3
Archive extend-shell-completions
noameron Dec 23, 2025
5524026
Merge branch 'main' into feature/bash_fish_power_shells_completions
noameron Dec 23, 2025
9dd9962
Fix canWriteFile control flow and add tests
noameron Dec 24, 2025
f69feab
Merge branch 'main' into feature/bash_fish_power_shells_completions
noameron Dec 27, 2025
d9b4ae6
Fix bash completion fallback and security escaping
noameron Dec 27, 2025
71f434b
Merge remote-tracking branch 'origin/feature/bash_fish_power_shells_c…
noameron Dec 27, 2025
aaf65a2
Merge branch 'main' into feature/bash_fish_power_shells_completions
noameron Jan 1, 2026
f624d3f
refactor: extract completion templates and standardize naming
noameron Jan 4, 2026
fadb83b
docs: update spec to reflect multi-shell support
noameron Jan 4, 2026
311b99d
fix: add all shells to zsh completion suggestions
noameron Jan 4, 2026
a597dc9
feat: add --yes flag to completion uninstall
noameron Jan 4, 2026
b9255cf
fix: remove bash-completion dependency from fallback
noameron Jan 9, 2026
f47632a
fix: use printf instead of echo for Fish tab output
noameron Jan 9, 2026
291d0ee
fix: make UX messages shell-aware
noameron Jan 9, 2026
f192da5
fix: add Homebrew paths for bash-completion detection
noameron Jan 9, 2026
28f47db
fix: support both PowerShell Core and Windows PS 5.1
noameron Jan 9, 2026
94123dd
fix: preserve colon handling in bash completion
noameron Jan 9, 2026
6e6903a
fix: update completion tests to match implementation changes
noameron Jan 9, 2026
3322198
Merge branch 'main' into feature/bash_fish_power_shells_completions
noameron Jan 9, 2026
21641b5
Merge branch 'main' into feature/bash_fish_power_shells_completions
TabishB Jan 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Change Proposal: Extend Shell Completions

## Why

Zsh completions provide an excellent developer experience, but many developers use bash, fish, or PowerShell. Extending completion support to these shells removes friction for the majority of developers who don't use Zsh.

## What Changes

This change adds bash, fish, and PowerShell completion support following the same architectural patterns, documentation methodology, and testing rigor established for Zsh completions.

## Deltas

- **Spec:** `cli-completion`
- **Operation:** MODIFIED
- **Description:** Extend completion generation, installation, and testing requirements to support bash, fish, and PowerShell while maintaining the existing Zsh implementation and architectural patterns

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Implementation Tasks

## Phase 1: Foundation and Bash Support

- [x] Update `SupportedShell` type in `src/utils/shell-detection.ts` to include `'bash' | 'fish' | 'powershell'`
- [x] Extend shell detection logic to recognize bash, fish, and PowerShell from environment variables
- [x] Create `src/core/completions/generators/bash-generator.ts` implementing `CompletionGenerator` interface
- [x] Create `src/core/completions/installers/bash-installer.ts` implementing `CompletionInstaller` interface
- [x] Update `CompletionFactory.createGenerator()` to support bash
- [x] Update `CompletionFactory.createInstaller()` to support bash
- [x] Create test file `test/core/completions/generators/bash-generator.test.ts` mirroring zsh test structure
- [x] Create test file `test/core/completions/installers/bash-installer.test.ts` mirroring zsh test structure
- [x] Verify bash completions work manually: `openspec completion install bash && exec bash`

## Phase 2: Fish Support

- [x] Create `src/core/completions/generators/fish-generator.ts` implementing `CompletionGenerator` interface
- [x] Create `src/core/completions/installers/fish-installer.ts` implementing `CompletionInstaller` interface
- [x] Update `CompletionFactory.createGenerator()` to support fish
- [x] Update `CompletionFactory.createInstaller()` to support fish
- [x] Create test file `test/core/completions/generators/fish-generator.test.ts`
- [x] Create test file `test/core/completions/installers/fish-installer.test.ts`
- [x] Verify fish completions work manually: `openspec completion install fish`

## Phase 3: PowerShell Support

- [x] Create `src/core/completions/generators/powershell-generator.ts` implementing `CompletionGenerator` interface
- [x] Create `src/core/completions/installers/powershell-installer.ts` implementing `CompletionInstaller` interface
- [x] Update `CompletionFactory.createGenerator()` to support powershell
- [x] Update `CompletionFactory.createInstaller()` to support powershell
- [x] Create test file `test/core/completions/generators/powershell-generator.test.ts`
- [x] Create test file `test/core/completions/installers/powershell-installer.test.ts`
- [x] Verify PowerShell completions work manually on Windows or macOS PowerShell

## Phase 4: Documentation and Testing

- [x] Update `CLAUDE.md` or relevant documentation to mention all four supported shells
- [x] Add cross-shell consistency test verifying all shells support same commands
- [x] Run `pnpm test` to ensure all tests pass
- [x] Run `pnpm run build` to verify TypeScript compilation
- [x] Test all shells on different platforms (Linux for bash/fish/zsh, Windows/macOS for PowerShell)

## Phase 5: Validation and Cleanup

- [x] Run `openspec validate extend-shell-completions --strict` and resolve all issues
- [x] Update error messages to list all four supported shells
- [x] Verify `openspec completion --help` documentation is current
- [x] Test auto-detection works for all shells
- [x] Ensure uninstall works cleanly for all shells
229 changes: 187 additions & 42 deletions openspec/specs/cli-completion/spec.md

Large diffs are not rendered by default.

56 changes: 50 additions & 6 deletions src/commands/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,27 @@ export class CompletionCommand {
if (result.backupPath) {
console.log(` Backup created: ${result.backupPath}`);
}
if (result.zshrcConfigured) {
console.log(` ~/.zshrc configured automatically`);

// Check if any shell config was updated
const configWasUpdated = result.zshrcConfigured || result.bashrcConfigured || result.profileConfigured;

if (configWasUpdated) {
const configPaths: Record<string, string> = {
zsh: '~/.zshrc',
bash: '~/.bashrc',
fish: '~/.config/fish/config.fish',
powershell: '$PROFILE',
};
const configPath = configPaths[shell] || 'config file';
console.log(` ${configPath} configured automatically`);
}
}

// Display warnings if present
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a bunch of zshrcConfigured checks in this file, do we need to extend for other shells here too?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly I think there's a few places where we focus on zsh heavily. For example there's a restart hint for zsh but not for other shells.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think even when uninstalling we mention zshrc

if (result.warnings && result.warnings.length > 0) {
console.log('');
for (const warning of result.warnings) {
console.log(warning);
}
}

Expand All @@ -155,9 +174,24 @@ export class CompletionCommand {
for (const instruction of result.instructions) {
console.log(instruction);
}
} else if (result.zshrcConfigured) {
console.log('');
console.log('Restart your shell or run: exec zsh');
} else {
// Check if any shell config was updated (InstallationResult has: zshrcConfigured, bashrcConfigured, profileConfigured)
const configWasUpdated = result.zshrcConfigured || result.bashrcConfigured || result.profileConfigured;

if (configWasUpdated) {
console.log('');

// Shell-specific reload instructions
const reloadCommands: Record<string, string> = {
zsh: 'exec zsh',
bash: 'exec bash',
fish: 'exec fish',
powershell: '. $PROFILE',
};
const reloadCmd = reloadCommands[shell] || `restart your ${shell} shell`;

console.log(`Restart your shell or run: ${reloadCmd}`);
}
}
} else {
console.error(`✗ ${result.message}`);
Expand All @@ -179,8 +213,18 @@ export class CompletionCommand {
// Prompt for confirmation unless --yes flag is provided
if (!skipConfirmation) {
const { confirm } = await import('@inquirer/prompts');

// Get shell-specific config file path
const configPaths: Record<string, string> = {
zsh: '~/.zshrc',
bash: '~/.bashrc',
fish: 'Fish configuration', // Fish doesn't modify profile, just removes script file
powershell: '$PROFILE',
};
const configPath = configPaths[shell] || `${shell} configuration`;

const confirmed = await confirm({
message: 'Remove OpenSpec configuration from ~/.zshrc?',
message: `Remove OpenSpec configuration from ${configPath}?`,
default: false,
});

Expand Down
8 changes: 7 additions & 1 deletion src/core/completions/command-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,13 @@ export const COMMAND_REGISTRY: CommandDefinition[] = [
description: 'Uninstall completion script for a shell',
acceptsPositional: true,
positionalType: 'shell',
flags: [],
flags: [
{
name: 'yes',
short: 'y',
description: 'Skip confirmation prompts',
},
],
},
],
},
Expand Down
42 changes: 37 additions & 5 deletions src/core/completions/factory.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,31 @@
import { CompletionGenerator } from './types.js';
import { ZshGenerator } from './generators/zsh-generator.js';
import { ZshInstaller, InstallationResult } from './installers/zsh-installer.js';
import { BashGenerator } from './generators/bash-generator.js';
import { FishGenerator } from './generators/fish-generator.js';
import { PowerShellGenerator } from './generators/powershell-generator.js';
import { ZshInstaller } from './installers/zsh-installer.js';
import { BashInstaller } from './installers/bash-installer.js';
import { FishInstaller } from './installers/fish-installer.js';
import { PowerShellInstaller } from './installers/powershell-installer.js';
import { SupportedShell } from '../../utils/shell-detection.js';

/**
* Common installation result interface
*/
export interface InstallationResult {
success: boolean;
installedPath?: string;
backupPath?: string;
message: string;
instructions?: string[];
warnings?: string[];
// Shell-specific optional fields
isOhMyZsh?: boolean;
zshrcConfigured?: boolean;
bashrcConfigured?: boolean;
profileConfigured?: boolean;
}

/**
* Interface for completion installers
*/
Expand All @@ -11,15 +34,12 @@ export interface CompletionInstaller {
uninstall(): Promise<{ success: boolean; message: string }>;
}

// Re-export InstallationResult for convenience
export type { InstallationResult };

/**
* Factory for creating completion generators and installers
* This design makes it easy to add support for additional shells
*/
export class CompletionFactory {
private static readonly SUPPORTED_SHELLS: SupportedShell[] = ['zsh'];
private static readonly SUPPORTED_SHELLS: SupportedShell[] = ['zsh', 'bash', 'fish', 'powershell'];

/**
* Create a completion generator for the specified shell
Expand All @@ -32,6 +52,12 @@ export class CompletionFactory {
switch (shell) {
case 'zsh':
return new ZshGenerator();
case 'bash':
return new BashGenerator();
case 'fish':
return new FishGenerator();
case 'powershell':
return new PowerShellGenerator();
default:
throw new Error(`Unsupported shell: ${shell}`);
}
Expand All @@ -48,6 +74,12 @@ export class CompletionFactory {
switch (shell) {
case 'zsh':
return new ZshInstaller();
case 'bash':
return new BashInstaller();
case 'fish':
return new FishInstaller();
case 'powershell':
return new PowerShellInstaller();
default:
throw new Error(`Unsupported shell: ${shell}`);
}
Expand Down
Loading
Loading