copa-cli 0.6.0__tar.gz → 0.7.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. {copa_cli-0.6.0 → copa_cli-0.7.0}/PKG-INFO +100 -9
  2. {copa_cli-0.6.0 → copa_cli-0.7.0}/README.md +99 -8
  3. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/cli.py +189 -3
  4. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/config.py +6 -4
  5. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/copa.zsh +26 -17
  6. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa_cli.egg-info/PKG-INFO +100 -9
  7. {copa_cli-0.6.0 → copa_cli-0.7.0}/pyproject.toml +1 -1
  8. {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_modal.py +19 -11
  9. {copa_cli-0.6.0 → copa_cli-0.7.0}/LICENSE +0 -0
  10. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/__init__.py +0 -0
  11. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/__main__.py +0 -0
  12. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/cli_common.py +0 -0
  13. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/cli_internal.py +0 -0
  14. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/cli_llm.py +0 -0
  15. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/cli_share.py +0 -0
  16. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/db.py +0 -0
  17. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/evolve.py +0 -0
  18. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/fzf.py +0 -0
  19. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/history.py +0 -0
  20. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/llm.py +0 -0
  21. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/mcp_server.py +0 -0
  22. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/models.py +0 -0
  23. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/scanner.py +0 -0
  24. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/scoring.py +0 -0
  25. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/sharing.py +0 -0
  26. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa_cli.egg-info/SOURCES.txt +0 -0
  27. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa_cli.egg-info/dependency_links.txt +0 -0
  28. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa_cli.egg-info/entry_points.txt +0 -0
  29. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa_cli.egg-info/requires.txt +0 -0
  30. {copa_cli-0.6.0 → copa_cli-0.7.0}/copa_cli.egg-info/top_level.txt +0 -0
  31. {copa_cli-0.6.0 → copa_cli-0.7.0}/setup.cfg +0 -0
  32. {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_cli_and_sharing.py +0 -0
  33. {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_db.py +0 -0
  34. {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_fzf.py +0 -0
  35. {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_models.py +0 -0
  36. {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_polish.py +0 -0
  37. {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_scanner.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copa-cli
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Command Palette — smart command tracking, ranking, and sharing for your shell
5
5
  Author: Mark Stanford
6
6
  License-Expression: MIT
@@ -46,7 +46,9 @@ Copa tracks the commands you run, ranks them by frequency and recency, and gives
46
46
  - **LLM descriptions** — `copa fix --auto` uses Claude or ollama to generate descriptions for undescribed commands
47
47
  - **Script protocol** — `#@ Description:` / `#@ Usage:` / `#@ Purpose:` / `#@ Flag:` headers in your scripts are auto-detected by `copa scan` across all `$PATH` directories
48
48
  - **Flag documentation** — document command flags with descriptions; flags are searchable, visible in the preview pane, and preserved in `.copa` exports
49
+ - **Inline suggestions** — ghost text appears as you type; Tab accepts or opens a completion menu with the suggestion highlighted
49
50
  - **Groups & Ctrl+G** — organize commands by project, device, or workflow; assign groups inline from the fzf palette with Ctrl+G
51
+ - **Bulk operations** — Ctrl+B enters select mode for batch group assignment, batch deletion, or batch LLM description
50
52
  - **Sharing & `copa create`** — export/import command sets as `.copa` JSON files; `copa create` scaffolds a `.copa` file from an existing group
51
53
  - **Set filtering** — scope list, search, and fzf to a specific shared set with `--set`
52
54
  - **MCP server** — expose your commands to Claude Code (or any MCP client)
@@ -85,24 +87,47 @@ pip install -e .
85
87
  pip install copa-cli[ollama]
86
88
  ```
87
89
 
88
- ### Shell integration (required)
90
+ ### Setup
89
91
 
90
- Add this line to your `~/.zshrc`:
92
+ Run the interactive setup wizard:
91
93
 
92
94
  ```bash
93
- eval "$(copa init zsh)"
95
+ copa setup
94
96
  ```
95
97
 
96
- Then restart your shell or run `source ~/.zshrc`. This does three things:
98
+ This walks you through everything:
99
+ 1. Checks that **fzf** is installed (tells you how to install if missing)
100
+ 2. Creates the Copa database (`~/.copa/copa.db`)
101
+ 3. Adds shell integration to your `~/.zshrc` (prompts for confirmation)
102
+ 4. Optionally imports your zsh history
103
+
104
+ Then activate Copa in your current terminal:
105
+
106
+ ```bash
107
+ source ~/.zshrc
108
+ ```
109
+
110
+ ### What shell integration does
111
+
112
+ The `eval "$(copa init zsh)"` line added to your `.zshrc` does three things:
97
113
 
98
114
  1. **Records every command you run** — a `precmd` hook silently calls `copa _record` in the background after each command, building up frequency and recency data with zero latency impact.
99
115
  2. **Replaces Ctrl+R** — the default zsh reverse-history-search is replaced with Copa's fzf-powered command palette (see below).
100
116
  3. **Supplements tab completion** — Copa registers as a completer so that any command gets completion candidates from your Copa database. The behavior is configurable (`fallback`, `hybrid`, `always`, or `never`) — see [Tab Completion](#tab-completion).
101
117
 
102
- Initialize the database:
118
+ ### Manual setup
119
+
120
+ If you prefer to set up manually instead of using `copa setup`:
103
121
 
104
122
  ```bash
105
- copa _init
123
+ # Add shell integration to ~/.zshrc
124
+ echo 'eval "$(copa init zsh)"' >> ~/.zshrc
125
+
126
+ # Activate in current terminal
127
+ source ~/.zshrc
128
+
129
+ # Import your history (optional)
130
+ copa sync
106
131
  ```
107
132
 
108
133
  ## Ctrl+R — fzf Command Palette
@@ -151,11 +176,40 @@ While the fzf palette is open, these keys are available:
151
176
  | **Ctrl+N** | Cycle group | Cycles through groups: (all) → group1 → group2 → ... → (all) |
152
177
  | **Ctrl+D** | Describe | Generate/edit a description using LLM (with tty-aware input) |
153
178
  | **Ctrl+F** | Edit flags | Add flag documentation to the highlighted command |
179
+ | **Ctrl+B** | Select mode | Enter multi-select for bulk operations (see below) |
154
180
  | **Ctrl+H** | Toggle header | Show/hide the key hints for more screen space |
155
181
  | **ESC** | Cancel/back | In scope/group mode: returns to command list. Otherwise: closes fzf |
156
182
 
157
183
  Keybindings are configurable via `~/.copa/config.toml`. See [Configuration](#configuration).
158
184
 
185
+ ### Select mode (bulk operations)
186
+
187
+ Press **Ctrl+B** from the Ctrl+R palette to enter **select mode**. This opens a new fzf view with multi-select enabled:
188
+
189
+ - **Tab** toggles selection on individual commands
190
+ - **Ctrl+R** cycles modes (all → frequent → recent) just like the main palette
191
+ - **Enter** confirms your selection and shows the batch action menu
192
+ - **ESC** cancels and returns to your prompt
193
+
194
+ After selecting commands, Copa shows a batch action menu:
195
+
196
+ ```
197
+ Selected 5 command(s).
198
+ g = assign group
199
+ d = delete
200
+ a = auto-describe (LLM)
201
+ q = cancel
202
+ Action:
203
+ ```
204
+
205
+ | Action | What it does |
206
+ |--------|-------------|
207
+ | **g** | Assign all selected commands to a group (or clear their group) |
208
+ | **d** | Delete all selected commands (with confirmation) |
209
+ | **a** | Auto-generate descriptions for all selected commands using your configured LLM backend |
210
+
211
+ This is useful for organizing large command sets — select 20 undescribed commands and batch-describe them, or move a set of related commands into a group in one step.
212
+
159
213
  ### Preview pane
160
214
 
161
215
  The right side shows a detail card for the highlighted command: full description, usage, purpose, flag documentation, score breakdown, frequency, last used, source, group, shared set, and tags.
@@ -241,9 +295,29 @@ $ git pu█sh origin main ← grey ghost text
241
295
 
242
296
  ### Tab accept mode
243
297
 
244
- By default (`tab_accept = 2`), pressing Tab when a suggestion is showing highlights the ghost text (changes from dim grey to cyan/bold) to indicate it's ready to be accepted. Pressing Tab again accepts it into the buffer. Pressing Esc reverts to dim grey without accepting. This two-step flow gives you a visual confirmation before committing.
298
+ Copa supports two Tab behaviors when a suggestion is showing:
245
299
 
246
- Set `tab_accept = 1` to restore the old behavior where a single Tab directly accepts the suggestion.
300
+ **Menu select** (`tab_accept = 2`, default):
301
+ 1. Press **Tab** — ghost text clears, a completion menu opens with the Copa suggestion highlighted at the top, alongside native completions below
302
+ 2. Press **Tab** or **Space** — accepts the highlighted item
303
+ 3. Press **Escape** — cancels the menu, restores your original text
304
+ 4. Use **arrow keys** to navigate if you want a different completion
305
+
306
+ This gives you a chance to see alternatives before committing. The Copa suggestion is always the first item in the menu.
307
+
308
+ **Inline accept** (`tab_accept = 1`):
309
+ - Press **Tab** — the suggestion is accepted directly into your command line. One keystroke, done.
310
+
311
+ ### Completion menu navigation
312
+
313
+ When the completion menu is open (from Tab in `tab_accept = 2` mode or from normal tab completion):
314
+
315
+ | Key | Action |
316
+ |-----|--------|
317
+ | **Tab** | Accept the highlighted completion |
318
+ | **Space** | Accept the highlighted completion |
319
+ | **Escape** | Cancel — dismiss menu, restore original text |
320
+ | **Arrow keys** | Navigate between completions |
247
321
 
248
322
  ### Backspace latch
249
323
 
@@ -259,8 +333,11 @@ Press **Tab** to unlatch and re-enable suggestions. The next new prompt (Enter)
259
333
  enabled = true # set to false to disable inline suggestions
260
334
  min_length = 2 # minimum characters before querying (default: 2)
261
335
  tab_accept = 2 # 1 = Tab accepts directly, 2 = Tab opens menu first (default)
336
+ color = 242 # ghost text color (256-color palette, default: 242 mid-grey)
262
337
  ```
263
338
 
339
+ The `color` value is a [256-color palette](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) number. Some useful values: `240` (darker grey), `242` (default mid-grey), `245` (lighter grey), `8` (bright black), `244` (light grey).
340
+
264
341
  Inline suggestions are enabled by default. Set `enabled = false` to disable them entirely (zero performance overhead when disabled).
265
342
 
266
343
  ## Quick Start
@@ -438,10 +515,20 @@ Copa ships with ready-to-use `.copa` files in the [`examples/`](examples/) direc
438
515
  | [`git.copa`](examples/git.copa) | Essential Git commands |
439
516
  | [`docker.copa`](examples/docker.copa) | Docker container management |
440
517
  | [`python-dev.copa`](examples/python-dev.copa) | Python development workflow |
518
+ | [`npm.copa`](examples/npm.copa) | Node.js package management |
441
519
  | [`network.copa`](examples/network.copa) | Network diagnostics |
520
+ | [`curl-http.copa`](examples/curl-http.copa) | HTTP requests and API testing |
521
+ | [`ssh-remote.copa`](examples/ssh-remote.copa) | SSH, SCP, and remote server management |
442
522
  | [`adb.copa`](examples/adb.copa) | Android Debug Bridge |
523
+ | [`aws.copa`](examples/aws.copa) | AWS CLI cloud infrastructure |
524
+ | [`terraform.copa`](examples/terraform.copa) | Terraform infrastructure-as-code |
443
525
  | [`k8s.copa`](examples/k8s.copa) | Kubernetes cluster management |
526
+ | [`systemd.copa`](examples/systemd.copa) | Linux service management |
444
527
  | [`sysadmin.copa`](examples/sysadmin.copa) | System administration |
528
+ | [`process-monitoring.copa`](examples/process-monitoring.copa) | Process management and debugging |
529
+ | [`disk-files.copa`](examples/disk-files.copa) | Disk usage and file management |
530
+ | [`tmux.copa`](examples/tmux.copa) | Terminal multiplexer sessions |
531
+ | [`homebrew.copa`](examples/homebrew.copa) | Homebrew package manager (macOS) |
445
532
 
446
533
  Load any of them:
447
534
 
@@ -560,6 +647,7 @@ group = "ctrl-g" # assign group (inline modal)
560
647
  flags = "ctrl-f" # edit flags
561
648
  filter_group = "ctrl-s" # scope by group (inline modal)
562
649
  cycle_group = "ctrl-n" # cycle through groups
650
+ select = "ctrl-b" # enter select mode (bulk operations)
563
651
  toggle_header = "ctrl-h" # show/hide key hints
564
652
 
565
653
  # Tab completion behavior
@@ -572,6 +660,7 @@ branding = true # show "Copa history" group header
572
660
  enabled = true # set to false to disable
573
661
  min_length = 2 # minimum chars before querying
574
662
  tab_accept = 2 # 1 = accept directly, 2 = open menu first
663
+ color = 242 # ghost text color (256-color palette)
575
664
 
576
665
  # Composition key behavior (continue vs close)
577
666
  # "continue" keys re-open fzf so you can chain another command
@@ -607,6 +696,7 @@ continue = []
607
696
 
608
697
  | Command | Purpose |
609
698
  |---------|---------|
699
+ | `copa setup` | Interactive setup wizard |
610
700
  | `copa add "cmd" -d "desc" -g group -f "flag: desc"` | Save a command (with optional flags) |
611
701
  | `copa edit ID [-d desc] [-g group] [-f flags] [--pin]` | Edit a command's metadata |
612
702
  | `copa remove ID` | Remove a command |
@@ -628,6 +718,7 @@ continue = []
628
718
  | `copa share list` | List shared sets |
629
719
  | `copa share sync DIR` | Sync .copa files from dir |
630
720
  | `copa import FILE [-g group]` | Import commands from markdown |
721
+ | `copa reset` | Wipe database and start fresh (keeps config) |
631
722
  | `copa uninstall` | Remove Copa data and show cleanup steps |
632
723
 
633
724
  ## How Scoring Works
@@ -16,7 +16,9 @@ Copa tracks the commands you run, ranks them by frequency and recency, and gives
16
16
  - **LLM descriptions** — `copa fix --auto` uses Claude or ollama to generate descriptions for undescribed commands
17
17
  - **Script protocol** — `#@ Description:` / `#@ Usage:` / `#@ Purpose:` / `#@ Flag:` headers in your scripts are auto-detected by `copa scan` across all `$PATH` directories
18
18
  - **Flag documentation** — document command flags with descriptions; flags are searchable, visible in the preview pane, and preserved in `.copa` exports
19
+ - **Inline suggestions** — ghost text appears as you type; Tab accepts or opens a completion menu with the suggestion highlighted
19
20
  - **Groups & Ctrl+G** — organize commands by project, device, or workflow; assign groups inline from the fzf palette with Ctrl+G
21
+ - **Bulk operations** — Ctrl+B enters select mode for batch group assignment, batch deletion, or batch LLM description
20
22
  - **Sharing & `copa create`** — export/import command sets as `.copa` JSON files; `copa create` scaffolds a `.copa` file from an existing group
21
23
  - **Set filtering** — scope list, search, and fzf to a specific shared set with `--set`
22
24
  - **MCP server** — expose your commands to Claude Code (or any MCP client)
@@ -55,24 +57,47 @@ pip install -e .
55
57
  pip install copa-cli[ollama]
56
58
  ```
57
59
 
58
- ### Shell integration (required)
60
+ ### Setup
59
61
 
60
- Add this line to your `~/.zshrc`:
62
+ Run the interactive setup wizard:
61
63
 
62
64
  ```bash
63
- eval "$(copa init zsh)"
65
+ copa setup
64
66
  ```
65
67
 
66
- Then restart your shell or run `source ~/.zshrc`. This does three things:
68
+ This walks you through everything:
69
+ 1. Checks that **fzf** is installed (tells you how to install if missing)
70
+ 2. Creates the Copa database (`~/.copa/copa.db`)
71
+ 3. Adds shell integration to your `~/.zshrc` (prompts for confirmation)
72
+ 4. Optionally imports your zsh history
73
+
74
+ Then activate Copa in your current terminal:
75
+
76
+ ```bash
77
+ source ~/.zshrc
78
+ ```
79
+
80
+ ### What shell integration does
81
+
82
+ The `eval "$(copa init zsh)"` line added to your `.zshrc` does three things:
67
83
 
68
84
  1. **Records every command you run** — a `precmd` hook silently calls `copa _record` in the background after each command, building up frequency and recency data with zero latency impact.
69
85
  2. **Replaces Ctrl+R** — the default zsh reverse-history-search is replaced with Copa's fzf-powered command palette (see below).
70
86
  3. **Supplements tab completion** — Copa registers as a completer so that any command gets completion candidates from your Copa database. The behavior is configurable (`fallback`, `hybrid`, `always`, or `never`) — see [Tab Completion](#tab-completion).
71
87
 
72
- Initialize the database:
88
+ ### Manual setup
89
+
90
+ If you prefer to set up manually instead of using `copa setup`:
73
91
 
74
92
  ```bash
75
- copa _init
93
+ # Add shell integration to ~/.zshrc
94
+ echo 'eval "$(copa init zsh)"' >> ~/.zshrc
95
+
96
+ # Activate in current terminal
97
+ source ~/.zshrc
98
+
99
+ # Import your history (optional)
100
+ copa sync
76
101
  ```
77
102
 
78
103
  ## Ctrl+R — fzf Command Palette
@@ -121,11 +146,40 @@ While the fzf palette is open, these keys are available:
121
146
  | **Ctrl+N** | Cycle group | Cycles through groups: (all) → group1 → group2 → ... → (all) |
122
147
  | **Ctrl+D** | Describe | Generate/edit a description using LLM (with tty-aware input) |
123
148
  | **Ctrl+F** | Edit flags | Add flag documentation to the highlighted command |
149
+ | **Ctrl+B** | Select mode | Enter multi-select for bulk operations (see below) |
124
150
  | **Ctrl+H** | Toggle header | Show/hide the key hints for more screen space |
125
151
  | **ESC** | Cancel/back | In scope/group mode: returns to command list. Otherwise: closes fzf |
126
152
 
127
153
  Keybindings are configurable via `~/.copa/config.toml`. See [Configuration](#configuration).
128
154
 
155
+ ### Select mode (bulk operations)
156
+
157
+ Press **Ctrl+B** from the Ctrl+R palette to enter **select mode**. This opens a new fzf view with multi-select enabled:
158
+
159
+ - **Tab** toggles selection on individual commands
160
+ - **Ctrl+R** cycles modes (all → frequent → recent) just like the main palette
161
+ - **Enter** confirms your selection and shows the batch action menu
162
+ - **ESC** cancels and returns to your prompt
163
+
164
+ After selecting commands, Copa shows a batch action menu:
165
+
166
+ ```
167
+ Selected 5 command(s).
168
+ g = assign group
169
+ d = delete
170
+ a = auto-describe (LLM)
171
+ q = cancel
172
+ Action:
173
+ ```
174
+
175
+ | Action | What it does |
176
+ |--------|-------------|
177
+ | **g** | Assign all selected commands to a group (or clear their group) |
178
+ | **d** | Delete all selected commands (with confirmation) |
179
+ | **a** | Auto-generate descriptions for all selected commands using your configured LLM backend |
180
+
181
+ This is useful for organizing large command sets — select 20 undescribed commands and batch-describe them, or move a set of related commands into a group in one step.
182
+
129
183
  ### Preview pane
130
184
 
131
185
  The right side shows a detail card for the highlighted command: full description, usage, purpose, flag documentation, score breakdown, frequency, last used, source, group, shared set, and tags.
@@ -211,9 +265,29 @@ $ git pu█sh origin main ← grey ghost text
211
265
 
212
266
  ### Tab accept mode
213
267
 
214
- By default (`tab_accept = 2`), pressing Tab when a suggestion is showing highlights the ghost text (changes from dim grey to cyan/bold) to indicate it's ready to be accepted. Pressing Tab again accepts it into the buffer. Pressing Esc reverts to dim grey without accepting. This two-step flow gives you a visual confirmation before committing.
268
+ Copa supports two Tab behaviors when a suggestion is showing:
215
269
 
216
- Set `tab_accept = 1` to restore the old behavior where a single Tab directly accepts the suggestion.
270
+ **Menu select** (`tab_accept = 2`, default):
271
+ 1. Press **Tab** — ghost text clears, a completion menu opens with the Copa suggestion highlighted at the top, alongside native completions below
272
+ 2. Press **Tab** or **Space** — accepts the highlighted item
273
+ 3. Press **Escape** — cancels the menu, restores your original text
274
+ 4. Use **arrow keys** to navigate if you want a different completion
275
+
276
+ This gives you a chance to see alternatives before committing. The Copa suggestion is always the first item in the menu.
277
+
278
+ **Inline accept** (`tab_accept = 1`):
279
+ - Press **Tab** — the suggestion is accepted directly into your command line. One keystroke, done.
280
+
281
+ ### Completion menu navigation
282
+
283
+ When the completion menu is open (from Tab in `tab_accept = 2` mode or from normal tab completion):
284
+
285
+ | Key | Action |
286
+ |-----|--------|
287
+ | **Tab** | Accept the highlighted completion |
288
+ | **Space** | Accept the highlighted completion |
289
+ | **Escape** | Cancel — dismiss menu, restore original text |
290
+ | **Arrow keys** | Navigate between completions |
217
291
 
218
292
  ### Backspace latch
219
293
 
@@ -229,8 +303,11 @@ Press **Tab** to unlatch and re-enable suggestions. The next new prompt (Enter)
229
303
  enabled = true # set to false to disable inline suggestions
230
304
  min_length = 2 # minimum characters before querying (default: 2)
231
305
  tab_accept = 2 # 1 = Tab accepts directly, 2 = Tab opens menu first (default)
306
+ color = 242 # ghost text color (256-color palette, default: 242 mid-grey)
232
307
  ```
233
308
 
309
+ The `color` value is a [256-color palette](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) number. Some useful values: `240` (darker grey), `242` (default mid-grey), `245` (lighter grey), `8` (bright black), `244` (light grey).
310
+
234
311
  Inline suggestions are enabled by default. Set `enabled = false` to disable them entirely (zero performance overhead when disabled).
235
312
 
236
313
  ## Quick Start
@@ -408,10 +485,20 @@ Copa ships with ready-to-use `.copa` files in the [`examples/`](examples/) direc
408
485
  | [`git.copa`](examples/git.copa) | Essential Git commands |
409
486
  | [`docker.copa`](examples/docker.copa) | Docker container management |
410
487
  | [`python-dev.copa`](examples/python-dev.copa) | Python development workflow |
488
+ | [`npm.copa`](examples/npm.copa) | Node.js package management |
411
489
  | [`network.copa`](examples/network.copa) | Network diagnostics |
490
+ | [`curl-http.copa`](examples/curl-http.copa) | HTTP requests and API testing |
491
+ | [`ssh-remote.copa`](examples/ssh-remote.copa) | SSH, SCP, and remote server management |
412
492
  | [`adb.copa`](examples/adb.copa) | Android Debug Bridge |
493
+ | [`aws.copa`](examples/aws.copa) | AWS CLI cloud infrastructure |
494
+ | [`terraform.copa`](examples/terraform.copa) | Terraform infrastructure-as-code |
413
495
  | [`k8s.copa`](examples/k8s.copa) | Kubernetes cluster management |
496
+ | [`systemd.copa`](examples/systemd.copa) | Linux service management |
414
497
  | [`sysadmin.copa`](examples/sysadmin.copa) | System administration |
498
+ | [`process-monitoring.copa`](examples/process-monitoring.copa) | Process management and debugging |
499
+ | [`disk-files.copa`](examples/disk-files.copa) | Disk usage and file management |
500
+ | [`tmux.copa`](examples/tmux.copa) | Terminal multiplexer sessions |
501
+ | [`homebrew.copa`](examples/homebrew.copa) | Homebrew package manager (macOS) |
415
502
 
416
503
  Load any of them:
417
504
 
@@ -530,6 +617,7 @@ group = "ctrl-g" # assign group (inline modal)
530
617
  flags = "ctrl-f" # edit flags
531
618
  filter_group = "ctrl-s" # scope by group (inline modal)
532
619
  cycle_group = "ctrl-n" # cycle through groups
620
+ select = "ctrl-b" # enter select mode (bulk operations)
533
621
  toggle_header = "ctrl-h" # show/hide key hints
534
622
 
535
623
  # Tab completion behavior
@@ -542,6 +630,7 @@ branding = true # show "Copa history" group header
542
630
  enabled = true # set to false to disable
543
631
  min_length = 2 # minimum chars before querying
544
632
  tab_accept = 2 # 1 = accept directly, 2 = open menu first
633
+ color = 242 # ghost text color (256-color palette)
545
634
 
546
635
  # Composition key behavior (continue vs close)
547
636
  # "continue" keys re-open fzf so you can chain another command
@@ -577,6 +666,7 @@ continue = []
577
666
 
578
667
  | Command | Purpose |
579
668
  |---------|---------|
669
+ | `copa setup` | Interactive setup wizard |
580
670
  | `copa add "cmd" -d "desc" -g group -f "flag: desc"` | Save a command (with optional flags) |
581
671
  | `copa edit ID [-d desc] [-g group] [-f flags] [--pin]` | Edit a command's metadata |
582
672
  | `copa remove ID` | Remove a command |
@@ -598,6 +688,7 @@ continue = []
598
688
  | `copa share list` | List shared sets |
599
689
  | `copa share sync DIR` | Sync .copa files from dir |
600
690
  | `copa import FILE [-g group]` | Import commands from markdown |
691
+ | `copa reset` | Wipe database and start fresh (keeps config) |
601
692
  | `copa uninstall` | Remove Copa data and show cleanup steps |
602
693
 
603
694
  ## How Scoring Works
@@ -40,11 +40,30 @@ def _cmd_to_json(cmd: Command) -> dict:
40
40
  # --- Main group ---
41
41
 
42
42
 
43
- @click.group()
43
+ @click.group(invoke_without_command=True)
44
44
  @click.version_option(package_name="copa-cli")
45
- def cli():
45
+ @click.pass_context
46
+ def cli(ctx):
46
47
  """Copa — Command Palette. Smart command tracking, ranking, and sharing."""
47
- pass
48
+ if ctx.invoked_subcommand is None:
49
+ # Show help, then a setup hint if needed
50
+ click.echo(ctx.get_help())
51
+ _maybe_show_setup_hint()
52
+
53
+
54
+ def _maybe_show_setup_hint():
55
+ """Show a setup hint if Copa is not fully configured."""
56
+ db_path = Path.home() / ".copa" / "copa.db"
57
+ zshrc = Path.home() / ".zshrc"
58
+ has_db = db_path.is_file()
59
+ has_shell = zshrc.is_file() and "copa init zsh" in zshrc.read_text()
60
+ has_fzf = shutil.which("fzf") is not None
61
+
62
+ if not has_db or not has_shell or not has_fzf:
63
+ click.echo()
64
+ hint = click.style(" Tip: ", fg="cyan", bold=True)
65
+ hint += "run " + click.style("copa setup", bold=True) + " to get started"
66
+ click.echo(hint)
48
67
 
49
68
 
50
69
  # --- init ---
@@ -60,6 +79,173 @@ def init(shell: str):
60
79
  click.echo(zsh_file.read_text())
61
80
 
62
81
 
82
+ # --- setup ---
83
+
84
+ _SHELL_INTEGRATION_LINE = 'eval "$(copa init zsh)"'
85
+
86
+
87
+ def _write_config_tab_accept(tab_accept: int) -> None:
88
+ """Write or update tab_accept in ~/.copa/config.toml."""
89
+ config_path = Path.home() / ".copa" / "config.toml"
90
+ config_path.parent.mkdir(parents=True, exist_ok=True)
91
+
92
+ if config_path.is_file():
93
+ content = config_path.read_text()
94
+ else:
95
+ content = ""
96
+
97
+ import re
98
+
99
+ if re.search(r"^\[suggest\]", content, re.MULTILINE):
100
+ # [suggest] section exists — update or add tab_accept
101
+ if re.search(r"^tab_accept\s*=", content, re.MULTILINE):
102
+ content = re.sub(
103
+ r"^tab_accept\s*=\s*\d+",
104
+ f"tab_accept = {tab_accept}",
105
+ content,
106
+ flags=re.MULTILINE,
107
+ )
108
+ else:
109
+ content = re.sub(
110
+ r"(\[suggest\]\n)",
111
+ f"\\1tab_accept = {tab_accept}\n",
112
+ content,
113
+ )
114
+ else:
115
+ # No [suggest] section — append it
116
+ if content and not content.endswith("\n"):
117
+ content += "\n"
118
+ content += f"\n[suggest]\ntab_accept = {tab_accept}\n"
119
+
120
+ config_path.write_text(content)
121
+
122
+
123
+ @cli.command()
124
+ def setup():
125
+ """Interactive setup wizard — checks prerequisites and configures Copa."""
126
+ ok = click.style("OK", fg="green")
127
+ fixed = click.style("FIXED", fg="green")
128
+ skip = click.style("SKIP", fg="yellow")
129
+ fail = click.style("!!", fg="yellow")
130
+
131
+ click.echo(click.style("Copa Setup", bold=True))
132
+ click.echo()
133
+
134
+ # 1. Check fzf
135
+ if shutil.which("fzf"):
136
+ click.echo(f" [{ok}] fzf is installed")
137
+ else:
138
+ click.echo(f" [{fail}] fzf is not installed")
139
+ click.echo(" Copa's Ctrl+R palette requires fzf.")
140
+ click.echo(" Install: " + click.style("brew install fzf", bold=True) + " (macOS)")
141
+ click.echo(" " + click.style("sudo apt install fzf", bold=True) + " (Linux)")
142
+ click.echo()
143
+
144
+ # 2. Initialize database
145
+ db_path = Path.home() / ".copa" / "copa.db"
146
+ if db_path.is_file():
147
+ click.echo(f" [{ok}] Database exists ({db_path})")
148
+ else:
149
+ click.echo(f" [{fixed}] Database created ({db_path})")
150
+ # Always call get_db to ensure db + tables exist
151
+ from .cli_common import get_db
152
+
153
+ get_db()
154
+
155
+ # 3. Shell integration
156
+ zshrc = Path.home() / ".zshrc"
157
+ if zshrc.is_file() and _SHELL_INTEGRATION_LINE in zshrc.read_text():
158
+ click.echo(f" [{ok}] Shell integration in ~/.zshrc")
159
+ else:
160
+ click.echo(f" [{fail}] Shell integration not found in ~/.zshrc")
161
+ if click.confirm(" Add it now?", default=True):
162
+ with open(zshrc, "a") as f:
163
+ f.write(f"\n# Copa — Command Palette\n{_SHELL_INTEGRATION_LINE}\n")
164
+ click.echo(f" [{fixed}] Added to ~/.zshrc")
165
+ else:
166
+ click.echo(f" [{skip}] Skipped — add manually:")
167
+ click.echo(f" {_SHELL_INTEGRATION_LINE}")
168
+
169
+ # 4. Tab completion style
170
+ click.echo()
171
+ click.echo(" Copa shows inline suggestions as you type (ghost text).")
172
+ click.echo(" How should " + click.style("Tab", bold=True) + " handle suggestions?")
173
+ click.echo()
174
+ click.echo(" " + click.style("1", bold=True) + " " + click.style("Inline accept", fg="cyan"))
175
+ click.echo(" Tab accepts the suggestion directly into your command line.")
176
+ click.echo()
177
+ opt2 = click.style("Menu select", fg="cyan") + click.style(" (default)", dim=True)
178
+ click.echo(" " + click.style("2", bold=True) + " " + opt2)
179
+ click.echo(" Tab opens a completion menu with the suggestion highlighted.")
180
+ click.echo(" Tab again accepts it. See alternatives before committing.")
181
+ click.echo()
182
+ tab_choice = click.prompt(
183
+ " Choose tab style",
184
+ type=click.Choice(["1", "2"]),
185
+ default="2",
186
+ show_choices=False,
187
+ )
188
+ tab_accept = int(tab_choice)
189
+ _write_config_tab_accept(tab_accept)
190
+ label = "Inline accept" if tab_accept == 1 else "Menu select"
191
+ click.echo(f" [{fixed}] Tab style: {label}")
192
+
193
+ # 5. Sync history
194
+ click.echo()
195
+ if click.confirm(" Import commands from your zsh history?", default=True):
196
+ from .history import sync_history
197
+
198
+ db = get_db()
199
+ added = sync_history(db)
200
+ click.echo(f" [{fixed}] Synced {added} commands from history")
201
+ else:
202
+ click.echo(f" [{skip}] Skipped — run " + click.style("copa sync", bold=True) + " later")
203
+
204
+ # 6. Done
205
+ click.echo()
206
+ click.echo(click.style(" Setup complete!", fg="green", bold=True))
207
+ click.echo()
208
+ click.echo(" Next steps:")
209
+ click.echo(" " + click.style("source ~/.zshrc", bold=True) + " Activate Copa in this terminal")
210
+ click.echo(" " + click.style("copa doctor", bold=True) + " Verify everything is working")
211
+ click.echo(" Press " + click.style("Ctrl+R", bold=True) + " Open the command palette")
212
+ click.echo(" Type a command + " + click.style("Tab", bold=True) + " See inline suggestions")
213
+ click.echo()
214
+
215
+
216
+ # --- reset ---
217
+
218
+
219
+ @cli.command()
220
+ @click.option("-y", "--yes", is_flag=True, help="Skip confirmation prompt.")
221
+ def reset(yes: bool):
222
+ """Wipe the command database and start fresh. Keeps your config."""
223
+ db_path = Path.home() / ".copa" / "copa.db"
224
+
225
+ if not db_path.is_file():
226
+ click.echo("No database found — nothing to reset.")
227
+ return
228
+
229
+ from .cli_common import get_db
230
+
231
+ db = get_db()
232
+ stats = db.get_stats()
233
+ click.echo(f"Database: {db_path}")
234
+ cmds, grps, sets = stats["total_commands"], stats["total_groups"], stats["shared_sets"]
235
+ click.echo(f" {cmds} commands, {grps} groups, {sets} shared sets")
236
+ click.echo()
237
+
238
+ if not yes:
239
+ click.confirm(click.style("Delete all commands and start fresh?", fg="red"), abort=True)
240
+
241
+ db.conn.close()
242
+ db_path.unlink()
243
+ # Re-create empty database
244
+ get_db()
245
+ click.echo(click.style("Database reset.", fg="green") + " Config preserved.")
246
+ click.echo("Run " + click.style("copa sync", bold=True) + " to re-import your shell history.")
247
+
248
+
63
249
  # --- uninstall ---
64
250
 
65
251
 
@@ -89,6 +89,7 @@ def load_config(path: Path | None = None) -> dict:
89
89
  config["_suggest_enabled"] = True # default: inline suggestions on
90
90
  config["_suggest_min_length"] = 2 # minimum chars before querying
91
91
  config["_suggest_tab_accept"] = 2 # 1=direct accept, 2=open menu first
92
+ config["_suggest_color"] = "242" # ghost text color (256-color palette)
92
93
 
93
94
  if path is None:
94
95
  path = Path.home() / ".copa" / "config.toml"
@@ -129,10 +130,7 @@ def load_config(path: Path | None = None) -> dict:
129
130
  continue_list = composition_section.get("continue")
130
131
  if isinstance(continue_list, list):
131
132
  # Only keep valid composition action names (those that have suffixes)
132
- config["_continue_actions"] = {
133
- name for name in continue_list
134
- if isinstance(name, str) and name in SUFFIXES
135
- }
133
+ config["_continue_actions"] = {name for name in continue_list if isinstance(name, str) and name in SUFFIXES}
136
134
 
137
135
  # [suggest] section — inline suggestion settings
138
136
  suggest_section = data.get("suggest")
@@ -146,6 +144,9 @@ def load_config(path: Path | None = None) -> dict:
146
144
  tab_accept = suggest_section.get("tab_accept")
147
145
  if isinstance(tab_accept, int) and tab_accept in (1, 2):
148
146
  config["_suggest_tab_accept"] = tab_accept
147
+ color = suggest_section.get("color")
148
+ if isinstance(color, (int, str)):
149
+ config["_suggest_color"] = str(color)
149
150
 
150
151
  keys_section = data.get("keys")
151
152
  if not isinstance(keys_section, dict):
@@ -249,6 +250,7 @@ def emit_zsh_config(config: dict[str, str]) -> str:
249
250
  lines.append(f"_COPA_SUGGEST_ENABLED='{'true' if suggest_enabled else 'false'}'")
250
251
  lines.append(f"_COPA_SUGGEST_MIN_LENGTH='{config.get('_suggest_min_length', 2)}'")
251
252
  lines.append(f"_COPA_SUGGEST_TAB_ACCEPT='{config.get('_suggest_tab_accept', 2)}'")
253
+ lines.append(f"_COPA_SUGGEST_COLOR='{config.get('_suggest_color', '242')}'")
252
254
 
253
255
  # Split suffixes into close (fzf exits) and continue (fzf re-opens)
254
256
  lines.append("typeset -gA _COPA_CLOSE_SUFFIXES")
@@ -31,6 +31,7 @@ eval "$(copa _fzf-config 2>/dev/null)" || {
31
31
  _COPA_SUGGEST_ENABLED='true'
32
32
  _COPA_SUGGEST_MIN_LENGTH='2'
33
33
  _COPA_SUGGEST_TAB_ACCEPT='2'
34
+ _COPA_SUGGEST_COLOR='242'
34
35
  _COPA_HEADER=$'Copa | ^R:cycle | ^V:& | ^O:2>&1 | ^X:| | ^T:> | ^A:&& | ^/:quiet | ^H:keys\n^G:grp | ^D:desc | ^F:flag | ^S:scope | ^N:↻grp'
35
36
  typeset -gA _COPA_CLOSE_SUFFIXES
36
37
  _COPA_CLOSE_SUFFIXES[ctrl-v]=' &'
@@ -341,6 +342,10 @@ _copa_history_complete() {
341
342
  zstyle ':completion:*' menu select
342
343
  zmodload zsh/complist 2>/dev/null
343
344
  bindkey -M menuselect '^I' .accept-line
345
+ # Escape cancels the menu and restores the original buffer
346
+ bindkey -M menuselect '^[' send-break
347
+ # Space accepts the focused completion and closes the menu
348
+ bindkey -M menuselect ' ' .accept-line
344
349
  # Raise threshold before "show all N?" prompt
345
350
  LISTMAX=200
346
351
  # Copa completion branding: show group description headers
@@ -380,15 +385,19 @@ _copa_suggest_fetch() {
380
385
  result=$(copa _suggest "$BUFFER" 2>/dev/null)
381
386
  if [[ -n "$result" && "$result" != "$BUFFER" ]]; then
382
387
  _COPA_SUGGESTION="$result"
383
- POSTDISPLAY="${result:${#BUFFER}}"
384
- region_highlight=("P0 ${#POSTDISPLAY} fg=8")
388
+ local blen=${#BUFFER}
389
+ local ghost="${result:$blen}"
390
+ POSTDISPLAY="$ghost"
391
+ # Use buffer-relative offsets that extend into POSTDISPLAY
392
+ region_highlight=("$blen $(( blen + ${#ghost} )) fg=${_COPA_SUGGEST_COLOR:-242}")
385
393
  fi
386
394
  }
387
395
 
388
396
  # --- Widget wrappers ---
389
397
 
390
- # self-insert: type a character, then fetch suggestion
398
+ # self-insert: type a character, unlatch, then fetch suggestion
391
399
  _copa_suggest_self_insert() {
400
+ _COPA_SUGGEST_LATCHED=0
392
401
  zle .self-insert
393
402
  _copa_suggest_fetch
394
403
  }
@@ -403,8 +412,10 @@ _copa_suggest_backward_delete_char() {
403
412
  zle -N backward-delete-char _copa_suggest_backward_delete_char
404
413
 
405
414
  # Tab: accept suggestion or open completion menu.
406
- # Uses menu-complete (not expand-or-complete) to enter menu-select
407
- # mode immediately on the first Tab press.
415
+ # Tab: accept suggestion or open completion menu.
416
+ # Don't call _copa_suggest_fetch after expand-or-complete menu-select
417
+ # runs asynchronously and fetch would overwrite the display with
418
+ # ghost text. The self-insert wrapper re-fetches on the next keystroke.
408
419
  _copa_suggest_expand_or_complete() {
409
420
  if [[ -n "$_COPA_SUGGESTION" ]]; then
410
421
  if [[ "$_COPA_SUGGEST_TAB_ACCEPT" == '1' ]]; then
@@ -413,12 +424,14 @@ _copa_suggest_expand_or_complete() {
413
424
  CURSOR=${#BUFFER}
414
425
  _copa_suggest_clear
415
426
  else
416
- # tab_accept=2: open completion menu with suggestion hoisted to top
427
+ # tab_accept=2: open completion menu with suggestion highlighted
428
+ # Use .expand-or-complete (not menu-complete) so menu-select
429
+ # highlights the first item in group-order (copa-suggestion),
430
+ # rather than the first match from the first completer.
417
431
  local pending="$_COPA_SUGGESTION"
418
432
  _copa_suggest_clear
419
433
  _COPA_SUGGEST_PENDING="$pending"
420
- zle menu-complete
421
- _copa_suggest_fetch # re-suggest after menu closes
434
+ zle .expand-or-complete
422
435
  fi
423
436
  return
424
437
  fi
@@ -428,7 +441,6 @@ _copa_suggest_expand_or_complete() {
428
441
  return
429
442
  fi
430
443
  zle menu-complete
431
- _copa_suggest_fetch # re-suggest after menu closes
432
444
  }
433
445
  zle -N _copa_suggest_expand_or_complete
434
446
  bindkey '^I' _copa_suggest_expand_or_complete
@@ -448,10 +460,11 @@ _copa_suggest_forward_char() {
448
460
  BUFFER="${BUFFER}${word}"
449
461
  CURSOR=${#BUFFER}
450
462
  # Update ghost text from existing suggestion (no re-query, no flicker)
451
- local remaining="${_COPA_SUGGESTION:${#BUFFER}}"
463
+ local blen=${#BUFFER}
464
+ local remaining="${_COPA_SUGGESTION:$blen}"
452
465
  if [[ -n "$remaining" ]]; then
453
466
  POSTDISPLAY="$remaining"
454
- region_highlight=("P0 ${#POSTDISPLAY} fg=8")
467
+ region_highlight=("$blen $(( blen + ${#remaining} )) fg=${_COPA_SUGGEST_COLOR:-242}")
455
468
  else
456
469
  # Fully accepted; clear and re-fetch for extended suggestions
457
470
  _copa_suggest_clear
@@ -503,12 +516,10 @@ zle -N up-line-or-history _copa_suggest_up_line_or_history
503
516
 
504
517
  _copa_suggest_down_line_or_history() {
505
518
  if [[ -n "$_COPA_SUGGESTION" ]]; then
506
- # Suggestion showing: open completion menu with suggestion at top
507
519
  local pending="$_COPA_SUGGESTION"
508
520
  _copa_suggest_clear
509
521
  _COPA_SUGGEST_PENDING="$pending"
510
- zle menu-complete
511
- _copa_suggest_fetch
522
+ zle .expand-or-complete
512
523
  else
513
524
  zle .down-line-or-history
514
525
  fi
@@ -523,12 +534,10 @@ zle -N up-line-or-search _copa_suggest_up_line_or_search
523
534
 
524
535
  _copa_suggest_down_line_or_search() {
525
536
  if [[ -n "$_COPA_SUGGESTION" ]]; then
526
- # Suggestion showing: open completion menu with suggestion at top
527
537
  local pending="$_COPA_SUGGESTION"
528
538
  _copa_suggest_clear
529
539
  _COPA_SUGGEST_PENDING="$pending"
530
- zle menu-complete
531
- _copa_suggest_fetch
540
+ zle .expand-or-complete
532
541
  else
533
542
  zle .down-line-or-search
534
543
  fi
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copa-cli
3
- Version: 0.6.0
3
+ Version: 0.7.0
4
4
  Summary: Command Palette — smart command tracking, ranking, and sharing for your shell
5
5
  Author: Mark Stanford
6
6
  License-Expression: MIT
@@ -46,7 +46,9 @@ Copa tracks the commands you run, ranks them by frequency and recency, and gives
46
46
  - **LLM descriptions** — `copa fix --auto` uses Claude or ollama to generate descriptions for undescribed commands
47
47
  - **Script protocol** — `#@ Description:` / `#@ Usage:` / `#@ Purpose:` / `#@ Flag:` headers in your scripts are auto-detected by `copa scan` across all `$PATH` directories
48
48
  - **Flag documentation** — document command flags with descriptions; flags are searchable, visible in the preview pane, and preserved in `.copa` exports
49
+ - **Inline suggestions** — ghost text appears as you type; Tab accepts or opens a completion menu with the suggestion highlighted
49
50
  - **Groups & Ctrl+G** — organize commands by project, device, or workflow; assign groups inline from the fzf palette with Ctrl+G
51
+ - **Bulk operations** — Ctrl+B enters select mode for batch group assignment, batch deletion, or batch LLM description
50
52
  - **Sharing & `copa create`** — export/import command sets as `.copa` JSON files; `copa create` scaffolds a `.copa` file from an existing group
51
53
  - **Set filtering** — scope list, search, and fzf to a specific shared set with `--set`
52
54
  - **MCP server** — expose your commands to Claude Code (or any MCP client)
@@ -85,24 +87,47 @@ pip install -e .
85
87
  pip install copa-cli[ollama]
86
88
  ```
87
89
 
88
- ### Shell integration (required)
90
+ ### Setup
89
91
 
90
- Add this line to your `~/.zshrc`:
92
+ Run the interactive setup wizard:
91
93
 
92
94
  ```bash
93
- eval "$(copa init zsh)"
95
+ copa setup
94
96
  ```
95
97
 
96
- Then restart your shell or run `source ~/.zshrc`. This does three things:
98
+ This walks you through everything:
99
+ 1. Checks that **fzf** is installed (tells you how to install if missing)
100
+ 2. Creates the Copa database (`~/.copa/copa.db`)
101
+ 3. Adds shell integration to your `~/.zshrc` (prompts for confirmation)
102
+ 4. Optionally imports your zsh history
103
+
104
+ Then activate Copa in your current terminal:
105
+
106
+ ```bash
107
+ source ~/.zshrc
108
+ ```
109
+
110
+ ### What shell integration does
111
+
112
+ The `eval "$(copa init zsh)"` line added to your `.zshrc` does three things:
97
113
 
98
114
  1. **Records every command you run** — a `precmd` hook silently calls `copa _record` in the background after each command, building up frequency and recency data with zero latency impact.
99
115
  2. **Replaces Ctrl+R** — the default zsh reverse-history-search is replaced with Copa's fzf-powered command palette (see below).
100
116
  3. **Supplements tab completion** — Copa registers as a completer so that any command gets completion candidates from your Copa database. The behavior is configurable (`fallback`, `hybrid`, `always`, or `never`) — see [Tab Completion](#tab-completion).
101
117
 
102
- Initialize the database:
118
+ ### Manual setup
119
+
120
+ If you prefer to set up manually instead of using `copa setup`:
103
121
 
104
122
  ```bash
105
- copa _init
123
+ # Add shell integration to ~/.zshrc
124
+ echo 'eval "$(copa init zsh)"' >> ~/.zshrc
125
+
126
+ # Activate in current terminal
127
+ source ~/.zshrc
128
+
129
+ # Import your history (optional)
130
+ copa sync
106
131
  ```
107
132
 
108
133
  ## Ctrl+R — fzf Command Palette
@@ -151,11 +176,40 @@ While the fzf palette is open, these keys are available:
151
176
  | **Ctrl+N** | Cycle group | Cycles through groups: (all) → group1 → group2 → ... → (all) |
152
177
  | **Ctrl+D** | Describe | Generate/edit a description using LLM (with tty-aware input) |
153
178
  | **Ctrl+F** | Edit flags | Add flag documentation to the highlighted command |
179
+ | **Ctrl+B** | Select mode | Enter multi-select for bulk operations (see below) |
154
180
  | **Ctrl+H** | Toggle header | Show/hide the key hints for more screen space |
155
181
  | **ESC** | Cancel/back | In scope/group mode: returns to command list. Otherwise: closes fzf |
156
182
 
157
183
  Keybindings are configurable via `~/.copa/config.toml`. See [Configuration](#configuration).
158
184
 
185
+ ### Select mode (bulk operations)
186
+
187
+ Press **Ctrl+B** from the Ctrl+R palette to enter **select mode**. This opens a new fzf view with multi-select enabled:
188
+
189
+ - **Tab** toggles selection on individual commands
190
+ - **Ctrl+R** cycles modes (all → frequent → recent) just like the main palette
191
+ - **Enter** confirms your selection and shows the batch action menu
192
+ - **ESC** cancels and returns to your prompt
193
+
194
+ After selecting commands, Copa shows a batch action menu:
195
+
196
+ ```
197
+ Selected 5 command(s).
198
+ g = assign group
199
+ d = delete
200
+ a = auto-describe (LLM)
201
+ q = cancel
202
+ Action:
203
+ ```
204
+
205
+ | Action | What it does |
206
+ |--------|-------------|
207
+ | **g** | Assign all selected commands to a group (or clear their group) |
208
+ | **d** | Delete all selected commands (with confirmation) |
209
+ | **a** | Auto-generate descriptions for all selected commands using your configured LLM backend |
210
+
211
+ This is useful for organizing large command sets — select 20 undescribed commands and batch-describe them, or move a set of related commands into a group in one step.
212
+
159
213
  ### Preview pane
160
214
 
161
215
  The right side shows a detail card for the highlighted command: full description, usage, purpose, flag documentation, score breakdown, frequency, last used, source, group, shared set, and tags.
@@ -241,9 +295,29 @@ $ git pu█sh origin main ← grey ghost text
241
295
 
242
296
  ### Tab accept mode
243
297
 
244
- By default (`tab_accept = 2`), pressing Tab when a suggestion is showing highlights the ghost text (changes from dim grey to cyan/bold) to indicate it's ready to be accepted. Pressing Tab again accepts it into the buffer. Pressing Esc reverts to dim grey without accepting. This two-step flow gives you a visual confirmation before committing.
298
+ Copa supports two Tab behaviors when a suggestion is showing:
245
299
 
246
- Set `tab_accept = 1` to restore the old behavior where a single Tab directly accepts the suggestion.
300
+ **Menu select** (`tab_accept = 2`, default):
301
+ 1. Press **Tab** — ghost text clears, a completion menu opens with the Copa suggestion highlighted at the top, alongside native completions below
302
+ 2. Press **Tab** or **Space** — accepts the highlighted item
303
+ 3. Press **Escape** — cancels the menu, restores your original text
304
+ 4. Use **arrow keys** to navigate if you want a different completion
305
+
306
+ This gives you a chance to see alternatives before committing. The Copa suggestion is always the first item in the menu.
307
+
308
+ **Inline accept** (`tab_accept = 1`):
309
+ - Press **Tab** — the suggestion is accepted directly into your command line. One keystroke, done.
310
+
311
+ ### Completion menu navigation
312
+
313
+ When the completion menu is open (from Tab in `tab_accept = 2` mode or from normal tab completion):
314
+
315
+ | Key | Action |
316
+ |-----|--------|
317
+ | **Tab** | Accept the highlighted completion |
318
+ | **Space** | Accept the highlighted completion |
319
+ | **Escape** | Cancel — dismiss menu, restore original text |
320
+ | **Arrow keys** | Navigate between completions |
247
321
 
248
322
  ### Backspace latch
249
323
 
@@ -259,8 +333,11 @@ Press **Tab** to unlatch and re-enable suggestions. The next new prompt (Enter)
259
333
  enabled = true # set to false to disable inline suggestions
260
334
  min_length = 2 # minimum characters before querying (default: 2)
261
335
  tab_accept = 2 # 1 = Tab accepts directly, 2 = Tab opens menu first (default)
336
+ color = 242 # ghost text color (256-color palette, default: 242 mid-grey)
262
337
  ```
263
338
 
339
+ The `color` value is a [256-color palette](https://en.wikipedia.org/wiki/ANSI_escape_code#8-bit) number. Some useful values: `240` (darker grey), `242` (default mid-grey), `245` (lighter grey), `8` (bright black), `244` (light grey).
340
+
264
341
  Inline suggestions are enabled by default. Set `enabled = false` to disable them entirely (zero performance overhead when disabled).
265
342
 
266
343
  ## Quick Start
@@ -438,10 +515,20 @@ Copa ships with ready-to-use `.copa` files in the [`examples/`](examples/) direc
438
515
  | [`git.copa`](examples/git.copa) | Essential Git commands |
439
516
  | [`docker.copa`](examples/docker.copa) | Docker container management |
440
517
  | [`python-dev.copa`](examples/python-dev.copa) | Python development workflow |
518
+ | [`npm.copa`](examples/npm.copa) | Node.js package management |
441
519
  | [`network.copa`](examples/network.copa) | Network diagnostics |
520
+ | [`curl-http.copa`](examples/curl-http.copa) | HTTP requests and API testing |
521
+ | [`ssh-remote.copa`](examples/ssh-remote.copa) | SSH, SCP, and remote server management |
442
522
  | [`adb.copa`](examples/adb.copa) | Android Debug Bridge |
523
+ | [`aws.copa`](examples/aws.copa) | AWS CLI cloud infrastructure |
524
+ | [`terraform.copa`](examples/terraform.copa) | Terraform infrastructure-as-code |
443
525
  | [`k8s.copa`](examples/k8s.copa) | Kubernetes cluster management |
526
+ | [`systemd.copa`](examples/systemd.copa) | Linux service management |
444
527
  | [`sysadmin.copa`](examples/sysadmin.copa) | System administration |
528
+ | [`process-monitoring.copa`](examples/process-monitoring.copa) | Process management and debugging |
529
+ | [`disk-files.copa`](examples/disk-files.copa) | Disk usage and file management |
530
+ | [`tmux.copa`](examples/tmux.copa) | Terminal multiplexer sessions |
531
+ | [`homebrew.copa`](examples/homebrew.copa) | Homebrew package manager (macOS) |
445
532
 
446
533
  Load any of them:
447
534
 
@@ -560,6 +647,7 @@ group = "ctrl-g" # assign group (inline modal)
560
647
  flags = "ctrl-f" # edit flags
561
648
  filter_group = "ctrl-s" # scope by group (inline modal)
562
649
  cycle_group = "ctrl-n" # cycle through groups
650
+ select = "ctrl-b" # enter select mode (bulk operations)
563
651
  toggle_header = "ctrl-h" # show/hide key hints
564
652
 
565
653
  # Tab completion behavior
@@ -572,6 +660,7 @@ branding = true # show "Copa history" group header
572
660
  enabled = true # set to false to disable
573
661
  min_length = 2 # minimum chars before querying
574
662
  tab_accept = 2 # 1 = accept directly, 2 = open menu first
663
+ color = 242 # ghost text color (256-color palette)
575
664
 
576
665
  # Composition key behavior (continue vs close)
577
666
  # "continue" keys re-open fzf so you can chain another command
@@ -607,6 +696,7 @@ continue = []
607
696
 
608
697
  | Command | Purpose |
609
698
  |---------|---------|
699
+ | `copa setup` | Interactive setup wizard |
610
700
  | `copa add "cmd" -d "desc" -g group -f "flag: desc"` | Save a command (with optional flags) |
611
701
  | `copa edit ID [-d desc] [-g group] [-f flags] [--pin]` | Edit a command's metadata |
612
702
  | `copa remove ID` | Remove a command |
@@ -628,6 +718,7 @@ continue = []
628
718
  | `copa share list` | List shared sets |
629
719
  | `copa share sync DIR` | Sync .copa files from dir |
630
720
  | `copa import FILE [-g group]` | Import commands from markdown |
721
+ | `copa reset` | Wipe database and start fresh (keeps config) |
631
722
  | `copa uninstall` | Remove Copa data and show cleanup steps |
632
723
 
633
724
  ## How Scoring Works
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "copa-cli"
7
- version = "0.6.0"
7
+ version = "0.7.0"
8
8
  description = "Command Palette — smart command tracking, ranking, and sharing for your shell"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -242,7 +242,7 @@ class TestCompositionConfig:
242
242
 
243
243
  def test_load_config_empty_continue_list(self, tmp_path):
244
244
  config_file = tmp_path / "config.toml"
245
- config_file.write_text('[composition]\ncontinue = []\n')
245
+ config_file.write_text("[composition]\ncontinue = []\n")
246
246
  config = load_config(config_file)
247
247
  assert config["_continue_actions"] == set()
248
248
 
@@ -292,7 +292,7 @@ class TestCompositionConfig:
292
292
 
293
293
  def test_empty_continue_all_keys_close(self, tmp_path):
294
294
  config_file = tmp_path / "config.toml"
295
- config_file.write_text('[composition]\ncontinue = []\n')
295
+ config_file.write_text("[composition]\ncontinue = []\n")
296
296
  config = load_config(config_file)
297
297
  output = emit_zsh_config(config)
298
298
  # All 6 keys should be in _COPA_EXPECT
@@ -395,7 +395,7 @@ class TestLayoutConfig:
395
395
 
396
396
  def test_load_config_integer_height(self, tmp_path):
397
397
  config_file = tmp_path / "config.toml"
398
- config_file.write_text('[layout]\nheight = 100\n')
398
+ config_file.write_text("[layout]\nheight = 100\n")
399
399
  config = load_config(config_file)
400
400
  assert config["_height"] == "100"
401
401
 
@@ -838,12 +838,16 @@ class TestSuggestBackspaceLatch:
838
838
  class TestTabAcceptConfig:
839
839
  """Test [suggest] tab_accept config option."""
840
840
 
841
- def test_default_tab_accept_is_2(self):
842
- config = load_config()
841
+ def test_default_tab_accept_is_2(self, tmp_path):
842
+ config_file = tmp_path / "config.toml"
843
+ config_file.write_text("")
844
+ config = load_config(config_file)
843
845
  assert config["_suggest_tab_accept"] == 2
844
846
 
845
- def test_emit_zsh_config_has_tab_accept(self):
846
- config = load_config()
847
+ def test_emit_zsh_config_has_tab_accept(self, tmp_path):
848
+ config_file = tmp_path / "config.toml"
849
+ config_file.write_text("")
850
+ config = load_config(config_file)
847
851
  output = emit_zsh_config(config)
848
852
  assert "_COPA_SUGGEST_TAB_ACCEPT='2'" in output
849
853
 
@@ -912,10 +916,13 @@ class TestTabAcceptZsh:
912
916
  def test_packaged_zsh_tab_checks_accept_mode(self):
913
917
  content = self._read_zsh("copa/copa.zsh")
914
918
  start = content.index("_copa_suggest_expand_or_complete()")
915
- func_block = content[start : start + 800]
919
+ func_block = content[start : start + 1000]
916
920
  # Tab widget checks tab_accept internally for mode 1 vs 2
917
921
  assert "_COPA_SUGGEST_TAB_ACCEPT" in func_block
918
922
  assert "_copa_suggest_clear" in func_block
923
+ # Mode 2 uses expand-or-complete so menu-select highlights by group-order
924
+ assert "zle .expand-or-complete" in func_block
925
+ # Non-suggestion fallback still uses menu-complete
919
926
  assert "zle menu-complete" in func_block
920
927
  # Mode 2 sets pending and opens completion menu
921
928
  assert "_COPA_SUGGEST_PENDING" in func_block
@@ -925,9 +932,10 @@ class TestTabAcceptZsh:
925
932
  def test_root_zsh_tab_checks_accept_mode(self):
926
933
  content = self._read_zsh("copa.zsh")
927
934
  start = content.index("_copa_suggest_expand_or_complete()")
928
- func_block = content[start : start + 800]
935
+ func_block = content[start : start + 1000]
929
936
  assert "_COPA_SUGGEST_TAB_ACCEPT" in func_block
930
937
  assert "_copa_suggest_clear" in func_block
938
+ assert "zle .expand-or-complete" in func_block
931
939
  assert "zle menu-complete" in func_block
932
940
  assert "_COPA_SUGGEST_PENDING" in func_block
933
941
  assert "bindkey '^I' _copa_suggest_expand_or_complete" in content
@@ -940,7 +948,7 @@ class TestTabAcceptZsh:
940
948
  assert ".down-line-or-history" in func_block
941
949
  # Down arrow with suggestion: hoists to completion menu
942
950
  assert "_COPA_SUGGEST_PENDING" in func_block
943
- assert "zle menu-complete" in func_block
951
+ assert "zle .expand-or-complete" in func_block
944
952
 
945
953
  def test_root_zsh_down_opens_menu_or_history(self):
946
954
  content = self._read_zsh("copa.zsh")
@@ -949,7 +957,7 @@ class TestTabAcceptZsh:
949
957
  assert "_copa_suggest_clear" in func_block
950
958
  assert ".down-line-or-history" in func_block
951
959
  assert "_COPA_SUGGEST_PENDING" in func_block
952
- assert "zle menu-complete" in func_block
960
+ assert "zle .expand-or-complete" in func_block
953
961
 
954
962
  def test_packaged_zsh_clear_resets_pending(self):
955
963
  content = self._read_zsh("copa/copa.zsh")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes