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.
- {copa_cli-0.6.0 → copa_cli-0.7.0}/PKG-INFO +100 -9
- {copa_cli-0.6.0 → copa_cli-0.7.0}/README.md +99 -8
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/cli.py +189 -3
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/config.py +6 -4
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/copa.zsh +26 -17
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa_cli.egg-info/PKG-INFO +100 -9
- {copa_cli-0.6.0 → copa_cli-0.7.0}/pyproject.toml +1 -1
- {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_modal.py +19 -11
- {copa_cli-0.6.0 → copa_cli-0.7.0}/LICENSE +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/__init__.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/__main__.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/cli_common.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/cli_internal.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/cli_llm.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/cli_share.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/db.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/evolve.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/fzf.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/history.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/llm.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/mcp_server.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/models.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/scanner.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/scoring.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa/sharing.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa_cli.egg-info/SOURCES.txt +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa_cli.egg-info/dependency_links.txt +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa_cli.egg-info/entry_points.txt +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa_cli.egg-info/requires.txt +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/copa_cli.egg-info/top_level.txt +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/setup.cfg +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_cli_and_sharing.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_db.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_fzf.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_models.py +0 -0
- {copa_cli-0.6.0 → copa_cli-0.7.0}/tests/test_polish.py +0 -0
- {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.
|
|
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
|
-
###
|
|
90
|
+
### Setup
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
Run the interactive setup wizard:
|
|
91
93
|
|
|
92
94
|
```bash
|
|
93
|
-
|
|
95
|
+
copa setup
|
|
94
96
|
```
|
|
95
97
|
|
|
96
|
-
|
|
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
|
-
|
|
118
|
+
### Manual setup
|
|
119
|
+
|
|
120
|
+
If you prefer to set up manually instead of using `copa setup`:
|
|
103
121
|
|
|
104
122
|
```bash
|
|
105
|
-
|
|
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
|
-
|
|
298
|
+
Copa supports two Tab behaviors when a suggestion is showing:
|
|
245
299
|
|
|
246
|
-
|
|
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
|
-
###
|
|
60
|
+
### Setup
|
|
59
61
|
|
|
60
|
-
|
|
62
|
+
Run the interactive setup wizard:
|
|
61
63
|
|
|
62
64
|
```bash
|
|
63
|
-
|
|
65
|
+
copa setup
|
|
64
66
|
```
|
|
65
67
|
|
|
66
|
-
|
|
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
|
-
|
|
88
|
+
### Manual setup
|
|
89
|
+
|
|
90
|
+
If you prefer to set up manually instead of using `copa setup`:
|
|
73
91
|
|
|
74
92
|
```bash
|
|
75
|
-
|
|
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
|
-
|
|
268
|
+
Copa supports two Tab behaviors when a suggestion is showing:
|
|
215
269
|
|
|
216
|
-
|
|
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
|
-
|
|
45
|
+
@click.pass_context
|
|
46
|
+
def cli(ctx):
|
|
46
47
|
"""Copa — Command Palette. Smart command tracking, ranking, and sharing."""
|
|
47
|
-
|
|
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
|
-
|
|
384
|
-
|
|
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
|
-
#
|
|
407
|
-
#
|
|
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
|
|
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
|
|
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
|
|
463
|
+
local blen=${#BUFFER}
|
|
464
|
+
local remaining="${_COPA_SUGGESTION:$blen}"
|
|
452
465
|
if [[ -n "$remaining" ]]; then
|
|
453
466
|
POSTDISPLAY="$remaining"
|
|
454
|
-
region_highlight=("
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
###
|
|
90
|
+
### Setup
|
|
89
91
|
|
|
90
|
-
|
|
92
|
+
Run the interactive setup wizard:
|
|
91
93
|
|
|
92
94
|
```bash
|
|
93
|
-
|
|
95
|
+
copa setup
|
|
94
96
|
```
|
|
95
97
|
|
|
96
|
-
|
|
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
|
-
|
|
118
|
+
### Manual setup
|
|
119
|
+
|
|
120
|
+
If you prefer to set up manually instead of using `copa setup`:
|
|
103
121
|
|
|
104
122
|
```bash
|
|
105
|
-
|
|
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
|
-
|
|
298
|
+
Copa supports two Tab behaviors when a suggestion is showing:
|
|
245
299
|
|
|
246
|
-
|
|
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
|
|
@@ -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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 +
|
|
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 +
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|