copa-cli 0.6.0__tar.gz → 0.7.1__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.1}/PKG-INFO +102 -11
  2. {copa_cli-0.6.0 → copa_cli-0.7.1}/README.md +101 -10
  3. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/cli.py +189 -3
  4. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/config.py +6 -4
  5. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/copa.zsh +45 -28
  6. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa_cli.egg-info/PKG-INFO +102 -11
  7. {copa_cli-0.6.0 → copa_cli-0.7.1}/pyproject.toml +1 -1
  8. {copa_cli-0.6.0 → copa_cli-0.7.1}/tests/test_modal.py +29 -26
  9. {copa_cli-0.6.0 → copa_cli-0.7.1}/LICENSE +0 -0
  10. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/__init__.py +0 -0
  11. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/__main__.py +0 -0
  12. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/cli_common.py +0 -0
  13. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/cli_internal.py +0 -0
  14. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/cli_llm.py +0 -0
  15. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/cli_share.py +0 -0
  16. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/db.py +0 -0
  17. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/evolve.py +0 -0
  18. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/fzf.py +0 -0
  19. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/history.py +0 -0
  20. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/llm.py +0 -0
  21. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/mcp_server.py +0 -0
  22. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/models.py +0 -0
  23. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/scanner.py +0 -0
  24. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/scoring.py +0 -0
  25. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa/sharing.py +0 -0
  26. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa_cli.egg-info/SOURCES.txt +0 -0
  27. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa_cli.egg-info/dependency_links.txt +0 -0
  28. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa_cli.egg-info/entry_points.txt +0 -0
  29. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa_cli.egg-info/requires.txt +0 -0
  30. {copa_cli-0.6.0 → copa_cli-0.7.1}/copa_cli.egg-info/top_level.txt +0 -0
  31. {copa_cli-0.6.0 → copa_cli-0.7.1}/setup.cfg +0 -0
  32. {copa_cli-0.6.0 → copa_cli-0.7.1}/tests/test_cli_and_sharing.py +0 -0
  33. {copa_cli-0.6.0 → copa_cli-0.7.1}/tests/test_db.py +0 -0
  34. {copa_cli-0.6.0 → copa_cli-0.7.1}/tests/test_fzf.py +0 -0
  35. {copa_cli-0.6.0 → copa_cli-0.7.1}/tests/test_models.py +0 -0
  36. {copa_cli-0.6.0 → copa_cli-0.7.1}/tests/test_polish.py +0 -0
  37. {copa_cli-0.6.0 → copa_cli-0.7.1}/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.1
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.
@@ -231,8 +285,8 @@ $ git pu█sh origin main ← grey ghost text
231
285
  |-----|-------------------|---------------|
232
286
  | Type chars | Insert char, re-fetch suggestion | Insert char, fetch suggestion |
233
287
  | Backspace | Delete char, **latch** (suppress suggestions) | Delete char normally |
234
- | **Tab** | `tab_accept=1`: accept full suggestion. `tab_accept=2` (default): first Tab highlights suggestion (cyan), second Tab accepts | If latched: unlatch + re-fetch suggestion. Else: normal tab completion |
235
- | **Down** | Highlight suggestion (enter confirming state) | History navigation |
288
+ | **Tab** | `tab_accept=1`: accept full suggestion. `tab_accept=2` (default): open completion menu with suggestion highlighted at top, native completions below | If latched: unlatch + re-fetch suggestion. Else: normal tab completion |
289
+ | **Down** | Open completion menu with suggestion highlighted at top | History navigation |
236
290
  | **Right arrow** | Accept one word, re-fetch | Move cursor right |
237
291
  | Enter | Clear suggestion, execute | Execute |
238
292
  | Esc | Dismiss suggestion | Normal Esc |
@@ -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.
@@ -201,8 +255,8 @@ $ git pu█sh origin main ← grey ghost text
201
255
  |-----|-------------------|---------------|
202
256
  | Type chars | Insert char, re-fetch suggestion | Insert char, fetch suggestion |
203
257
  | Backspace | Delete char, **latch** (suppress suggestions) | Delete char normally |
204
- | **Tab** | `tab_accept=1`: accept full suggestion. `tab_accept=2` (default): first Tab highlights suggestion (cyan), second Tab accepts | If latched: unlatch + re-fetch suggestion. Else: normal tab completion |
205
- | **Down** | Highlight suggestion (enter confirming state) | History navigation |
258
+ | **Tab** | `tab_accept=1`: accept full suggestion. `tab_accept=2` (default): open completion menu with suggestion highlighted at top, native completions below | If latched: unlatch + re-fetch suggestion. Else: normal tab completion |
259
+ | **Down** | Open completion menu with suggestion highlighted at top | History navigation |
206
260
  | **Right arrow** | Accept one word, re-fetch | Move cursor right |
207
261
  | Enter | Clear suggestion, execute | Execute |
208
262
  | Esc | Dismiss suggestion | Normal Esc |
@@ -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")