Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
c1b9ba0
Fix bash substitution error in fish shell
jellydn Aug 25, 2025
8da7c0d
Merge upstream changes and restore -s flag functionality
jellydn Aug 25, 2025
805f064
Fix unbound variable error when no arguments passed
jellydn Aug 25, 2025
60dc9a8
Add comprehensive editor configuration documentation
jellydn Aug 25, 2025
b42f4e1
Add toggle functionality for gitignored files
jellydn Aug 26, 2025
1dec5c2
Fix shellcheck issues in toggle functionality
jellydn Aug 26, 2025
2297955
Remove header text for cleaner interface
jellydn Aug 26, 2025
f6e76d5
Remove toggle message for completely clean interface
jellydn Aug 26, 2025
677451d
Fix shellcheck errors in cmdk.sh
jellydn Aug 26, 2025
8388578
Fix -s flag to exclude omnipresent items
jellydn Aug 26, 2025
7630fba
Fix -o flag to exclude HOME but keep .. for navigation
jellydn Aug 26, 2025
05a5464
feat(toggle): add git filter toggle with Ctrl+G
jellydn Feb 5, 2026
d11f6a6
refactor(toggle): reorganize toggle scripts into actions directory
jellydn Feb 5, 2026
82e06ab
feat(core): implement unified file loading system
jellydn Feb 5, 2026
7a095fb
docs: update README with new Ctrl+G feature and formatting
jellydn Feb 5, 2026
0940364
chore: remove .claude directory from git tracking
jellydn Feb 5, 2026
8794cbb
chore: add .claude to .gitignore
jellydn Feb 5, 2026
452ba00
fix: cmdk-core exit code bug, trap cleanup, flag validation, dep checks
jellydn Feb 6, 2026
a548e86
fix: list-files shebang sh→bash, apply home excludes to fd call
jellydn Feb 6, 2026
2957774
fix: preview shebang sh→bash, add tool fallbacks
jellydn Feb 6, 2026
82b1a63
fix: replace A&&B||C with proper if/then/else in reload-files
jellydn Feb 6, 2026
f63e151
test: add BATS test suite (21 tests)
jellydn Feb 6, 2026
4818f9d
docs: update CONCERNS.md with resolved issues
jellydn Feb 6, 2026
73d7b88
docs: add testing section to README
jellydn Feb 6, 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
75 changes: 75 additions & 0 deletions .TOGGLES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Toggle Features Documentation

## Overview

cmdk now supports two independent toggle features that work seamlessly together:

### **Ctrl+T: Environment File Visibility**
- **State File**: `/tmp/cmdk_env_toggle_${USER}`
- **Default**: OFF (hides `.env`, `.gitignore`, etc.)
- **When ON**: Shows files that would normally be gitignored
- **Use Case**: Quick access to configuration files when needed

### **Ctrl+G: Git Filter Toggle**
- **State File**: `/tmp/cmdk_git_toggle_${USER}`
- **Default**: OFF (shows all files)
- **When ON**: Shows only git-changed files (modified, staged, untracked)
- **Use Case**: Focus on work-in-progress files
- **Fallback**: Returns to normal file list if not in a git repository

## How They Work Together

The toggles are **independent** and can be combined:

| Ctrl+T | Ctrl+G | Result |
|--------|--------|--------|
| OFF | OFF | All files (excluding gitignored by default) |
| ON | OFF | All files + gitignored files |
| OFF | ON | Git-changed files only |
| ON | ON | Git-changed files + any .env/.gitignored among them |

## Implementation Details

### State Management
- Each toggle maintains its own persistent state file in `/tmp/`
- State persists across multiple cmdk invocations during a session
- Automatically cleaned up when cmdk exits

### Script Architecture
```
cmdk-core.sh
└── Uses: reload-files.sh
├── Checks: actions/toggle-state.sh (Ctrl+T)
├── Checks: actions/git-toggle-state.sh (Ctrl+G)
├── If Ctrl+G ON → runs: git-files.sh
└── If Ctrl+G OFF → runs: reload-with-toggle.sh
└── Respects: actions/toggle-state.sh (Ctrl+T)
```

### Error Handling
- If not in a git repository, Ctrl+G gracefully falls back to normal file list
- Both toggles fail silently and continue normal operation
- No breaking errors or messages to disrupt user experience

## Testing the Toggles

```bash
# Test Ctrl+T (env visibility)
bash actions/toggle-state.sh init off
bash reload-files.sh -o | grep -i env # Should NOT show .env

bash actions/toggle-state.sh init on
bash reload-files.sh -o | grep -i env # Should show .env

# Test Ctrl+G (git filter)
bash actions/git-toggle-state.sh init off
bash reload-files.sh -o | head -10 # Shows all files

bash actions/git-toggle-state.sh init on
bash reload-files.sh -o # Shows only git-changed files
```

## Known Limitations
- Ctrl+G only works in git repositories (gracefully falls back)
- State files are per-user, not per-repository
- Toggles reset at end of each cmdk invocation
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,6 @@ go.work.sum

# env file
.env

# IDE/Editor config
.claude
155 changes: 155 additions & 0 deletions .planning/codebase/CONCERNS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Codebase Concerns

**Analysis Date:** 2026-02-06
**Last Updated:** 2026-02-06

## Tech Debt

**~~No automated testing framework:~~** ✅ RESOLVED
- Fix: Added BATS test suite in `test/` with 21 tests covering list-files, git-files, toggle-state, and preview scripts

**Shell compatibility concerns:**
- Issue: Scripts need to work across Bash and Fish shells
- Files: `cmdk-core.sh`, `cmdk.sh`, `cmdk.fish`
- Impact: Portability issues, different feature sets between shells
- Fix approach: Define minimum shell version requirements, add CI tests for both shells
- Status: Shebang mismatches fixed (`list-files.sh` and `preview.sh` now correctly use `#!/usr/bin/env bash`)

**~~No error handling standardization:~~** ✅ PARTIALLY RESOLVED
- Fix: Added dependency checks for required tools (`fzf`, `fd`, `file`) in `cmdk-core.sh`
- Fix: Added flag validation with clear error messages for unknown flags
- Fix: Added `trap` cleanup in `cmdk-core.sh` for guaranteed state cleanup on exit/error
- Fix: Fixed exit code capture bug (was capturing cleanup exit code, not fzf)
- Fix: Fixed `return` in non-sourced script → `exit 1`
- Remaining: Could add a shared error handling library if more scripts are added

**~~Missing ShellCheck integration:~~** ✅ ALREADY RESOLVED
- ShellCheck CI already exists in `.github/workflows/shellcheck.yml`
- All scripts now pass ShellCheck cleanly

## Known Bugs

**~~Exit code capture bug in cmdk-core.sh:~~** ✅ FIXED
- Was: `exit_code=$?` captured cleanup exit code, not fzf's exit code
- Fix: Exit code now captured immediately after fzf with `|| exit_code=$?`

**~~`return` used in non-sourced script:~~** ✅ FIXED
- Was: `return` in `cmdk-core.sh` which is invoked via `bash`, not sourced
- Fix: Changed to `exit 1`

**~~Home excludes not applied in list-files.sh:~~** ✅ FIXED
- Was: `home_exclude_args` was computed but never passed to the `fd` call when in HOME
- Fix: Now conditionally included in fd invocation when `PWD == HOME`

**~~Incorrect if-then-else in reload-files.sh:~~** ✅ FIXED
- Was: `A && B || C` pattern which is not equivalent to if-then-else
- Fix: Replaced with proper `if/then/else/fi` structure

## Security Considerations

**Unquoted variables in scripts:**
- Risk: Word splitting and glob expansion vulnerabilities
- Files: `list-files.sh` (intentional word-splitting for fd args, documented with shellcheck directives)
- Current mitigation: All intentional word-splitting annotated with `# shellcheck disable=SC2086`
- Status: Reviewed and acceptable for controlled internal values

**~~No input validation:~~** ✅ RESOLVED
- Fix: Added flag validation in `cmdk-core.sh` — only `-o`, `-s`, `-e` accepted
- Fix: Unknown flags now produce clear error and exit 1
- Fix: Validated flags used instead of raw `$*` in fzf commands

**Environment variable parsing:**
- Risk: Uncontrolled environment variables could affect behavior
- Files: `.env` parsing in core
- Current mitigation: .env file is local only
- Recommendations: Validate/whitelist environment variables

## Performance Bottlenecks

**File discovery inefficiency:**
- Problem: `list-files.sh` may scan entire directory trees repeatedly
- Files: `list-files.sh`, `cmdk-core.sh`
- Cause: No caching of file listings
- Improvement path: Add incremental file caching, use git for tracking changes

**Script sourcing overhead:**
- Problem: Each invocation sources multiple files
- Files: All shell entry points
- Cause: No pre-compilation, full parsing on each run
- Improvement path: Consider compiled shell (shc) for production, profile hot paths

## Fragile Areas

**Shell compatibility layer:**
- Files: `cmdk.sh`, `cmdk.fish`, `cmdk-core.sh`
- Why fragile: Different shells have different semantics, behavior divergence
- Safe modification: Create comprehensive tests before changing core
- Test coverage: BATS tests now cover core scripts; cross-shell integration tests still needed

**Action routing system:**
- Files: `cmdk-core.sh` (action dispatch logic)
- Why fragile: Central routing point, affects all commands
- Safe modification: Write integration tests first, test all actions after changes
- Test coverage: Toggle state tests added; full dispatch testing requires fzf stubbing

**File discovery utilities:**
- Files: `list-files.sh`, `git-files.sh`
- Why fragile: Depends on specific Unix tools (find, git)
- Safe modification: Dependency checks now added in `cmdk-core.sh`
- Test coverage: BATS tests cover basic scenarios, edge cases (spaces, special chars)

## Scaling Limits

**Script interpretation overhead:**
- Current capacity: Suitable for small to medium CLI usage
- Limit: May become slow with very large file sets or deep nesting
- Scaling path: Profile hot paths, consider compiled versions or faster language

**Memory usage in large operations:**
- Current capacity: Should be fine for typical usage
- Limit: Loading entire file lists into memory could be issue with huge projects
- Scaling path: Implement streaming/incremental processing

## Dependencies at Risk

**~~Git dependency (silent failure):~~** ✅ PARTIALLY RESOLVED
- Fix: `git-files.sh` already exits non-zero when not in git repo
- Fix: `reload-files.sh` now uses proper if/then/else for git fallback
- Remaining: Could add `git` to dependency checks if git features are required

**Unix utilities:**
- Risk: Depends on find, grep, sed, etc.
- Impact: Breaks on systems without standard Unix tools (minimal containers, Windows WSL)
- Migration plan: `cmdk-core.sh` now checks for `fzf`, `fd`, `file` at startup
- Status: `preview.sh` now has fallbacks for optional tools (`bat`→`cat`, `tiv`, `pdftotext`, `unzip`)

## Missing Critical Features

None identified at this scope level.

## Test Coverage Gaps

**~~Core dispatcher logic:~~** ✅ PARTIALLY RESOLVED
- Added: Toggle state tests, list-files tests, git-files tests, preview tests
- Remaining: Full fzf interaction testing would require fzf stubbing

**Shell-specific integration:**
- What's not tested: Bash-specific vs Fish-specific behavior
- Files: `cmdk.sh`, `cmdk.fish`
- Risk: Commands might work in one shell but not the other
- Priority: High

**~~Error conditions:~~** ✅ PARTIALLY RESOLVED
- Added: Tests for invalid toggle commands, non-git directory handling
- Remaining: Missing file, permission error, missing tool scenarios
- Priority: Medium

**~~Edge cases in file operations:~~** ✅ RESOLVED
- Added: Tests for files with spaces and special characters
- Files: `test/list-files.bats`
- Priority: Medium

---

*Concerns audit: 2026-02-06*
*Last fix pass: 2026-02-06*
85 changes: 69 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,21 @@ Installation
source ~/.cmdk/cmdk.fish
```
4. (Optional) Bind the `⌘-k` hotkey (or any other if you prefer) to send the text `cmdk\n` in your terminal:
<details>
<summary>💻 iTerm</summary>
`Settings → Profiles → Keys → Keybindings → + → Send Text`, then binding `⌘-k` to send the text `cmdk\n`
</details>
<details>
<summary>👻 Ghostty</summary>
```
# ~/.config/ghostty/config (or $XDG_CONFIG_HOME/ghostty/config)
keybind = cmd+k=text:cmdk\r
```
</details>
<details>
<summary>💻 iTerm</summary>

`Settings → Profiles → Keys → Keybindings → + → Send Text`, then binding `⌘-k` to send the text `cmdk\n`

</details>
<details>
<summary>👻 Ghostty</summary>

```
# ~/.config/ghostty/config (or $XDG_CONFIG_HOME/ghostty/config)
keybind = cmd+k=text:cmdk\r
```

</details>
5. Open a new shell and press your hotkey (⌘-K if you bound it) or enter `cmdk` (if you don't have a hotkey)
6. (Optional) If you'd like to use `cmdk`'s functionality with `fzf`'s <Ctrl-T>, add the following to your `.bashrc` or `.zshrc`:
```
Expand All @@ -74,6 +74,8 @@ Press ⌘-k (or type `cmdk`) and...
- `ENTER` to select the result
- `TAB` to select multiple items before `ENTER`
- `Ctrl-u` to clear the selection
- `Ctrl-t` to toggle visibility of gitignored files (like `.env`)
- `Ctrl-g` to toggle between all files and git-changed files only (modified, staged, untracked)

> ⚠️ Some directories like `Library`, `/`, and `.git` are full of stuff users don't need to access, so their contents are excluded. To get to their contents, first ⌘-k to them and then ⌘-k again to see their contents.

Expand All @@ -83,13 +85,64 @@ Press ⌘-k (or type `cmdk`) and...

- `-o` - Only list the contents of the current directory at depth 1 (original behavior)
- `-s` - List all contents of the current directory recursively, including subdirectories
- `-e` - Show hidden files that are typically excluded by `.gitignore` (including `.env` files)

### Editor Configuration

By default, cmdk opens text files using your `$EDITOR` environment variable, or falls back to `vim -O` if unset. You can configure any editor:

**Neovim:**
```bash
export EDITOR=nvim
```

**Cursor:**
```bash
export EDITOR=cursor
```

**VS Code:**
```bash
export EDITOR="code -w"
```

**Neovim with vertical splits for multiple files:**
```bash
export EDITOR="nvim -O"
```

**For Fish shell users**, add to `~/.config/fish/config.fish`:
```fish
set -gx EDITOR nvim
```

**For Bash/Zsh users**, add to `~/.bashrc` or `~/.zshrc`:
```bash
export EDITOR=nvim
```

Feedback
--------
Hi HN! I'd love to hear how you're using cmdk, and making it your own.

Testing
-------
cmdk uses [BATS](https://github.com/bats-core/bats-core) (Bash Automated Testing System) for automated tests.

```sh
brew install bats-core # if not already installed
bats test/
```

Test files:
- `test/list-files.bats` — file discovery, depth modes, spaces/special chars, exclude dirs
- `test/git-files.bats` — git-changed file detection, deduplication, non-git fallback
- `test/toggle-state.bats` — toggle init, flip, get, cleanup
- `test/preview.bats` — text/directory/HOME preview

For manual testing across shells (bash, zsh, fish), see [testing-checklist.md](testing-checklist.md).

TODO
----
- [Allow customizing the program used to open files](https://github.com/mieubrisse/cmdk/issues/4)
- [Allow for favoriting files that pop to the top of the search](https://github.com/mieubrisse/cmdk/issues/5)
- [Store the results of a selection in the history](https://github.com/mieubrisse/cmdk/issues/1)
44 changes: 44 additions & 0 deletions actions/git-toggle-state.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash

# Manage git filter toggle state (same pattern as toggle-state.sh)

set -euo pipefail

STATE_FILE="/tmp/cmdk_git_toggle_${USER}"

case "${1:-}" in
"get")
if [ -f "$STATE_FILE" ]; then
cat "$STATE_FILE"
else
echo "off"
fi
;;
"toggle")
if [ -f "$STATE_FILE" ]; then
current_state="$(cat "$STATE_FILE")"
else
current_state="off"
fi
if [ "$current_state" = "on" ]; then
echo "off" > "$STATE_FILE"
echo "off"
else
echo "on" > "$STATE_FILE"
echo "on"
fi
;;
"init")
# Initialize with given state or default to off
state="${2:-off}"
echo "$state" > "$STATE_FILE"
echo "$state"
;;
"cleanup")
rm -f "$STATE_FILE"
;;
*)
echo "Usage: $0 {get|toggle|init [on|off]|cleanup}" >&2
exit 1
;;
esac
Loading