copa-cli 0.2.0__tar.gz → 0.2.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 (35) hide show
  1. {copa_cli-0.2.0/copa_cli.egg-info → copa_cli-0.2.1}/PKG-INFO +2 -2
  2. {copa_cli-0.2.0 → copa_cli-0.2.1}/README.md +1 -1
  3. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/cli.py +58 -0
  4. copa_cli-0.2.1/copa/copa.zsh +156 -0
  5. {copa_cli-0.2.0 → copa_cli-0.2.1/copa_cli.egg-info}/PKG-INFO +2 -2
  6. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa_cli.egg-info/SOURCES.txt +1 -0
  7. {copa_cli-0.2.0 → copa_cli-0.2.1}/pyproject.toml +4 -1
  8. {copa_cli-0.2.0 → copa_cli-0.2.1}/LICENSE +0 -0
  9. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/__init__.py +0 -0
  10. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/__main__.py +0 -0
  11. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/cli_common.py +0 -0
  12. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/cli_internal.py +0 -0
  13. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/cli_llm.py +0 -0
  14. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/cli_share.py +0 -0
  15. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/config.py +0 -0
  16. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/db.py +0 -0
  17. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/evolve.py +0 -0
  18. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/fzf.py +0 -0
  19. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/history.py +0 -0
  20. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/llm.py +0 -0
  21. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/mcp_server.py +0 -0
  22. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/models.py +0 -0
  23. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/scanner.py +0 -0
  24. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/scoring.py +0 -0
  25. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa/sharing.py +0 -0
  26. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa_cli.egg-info/dependency_links.txt +0 -0
  27. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa_cli.egg-info/entry_points.txt +0 -0
  28. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa_cli.egg-info/requires.txt +0 -0
  29. {copa_cli-0.2.0 → copa_cli-0.2.1}/copa_cli.egg-info/top_level.txt +0 -0
  30. {copa_cli-0.2.0 → copa_cli-0.2.1}/setup.cfg +0 -0
  31. {copa_cli-0.2.0 → copa_cli-0.2.1}/tests/test_cli_and_sharing.py +0 -0
  32. {copa_cli-0.2.0 → copa_cli-0.2.1}/tests/test_db.py +0 -0
  33. {copa_cli-0.2.0 → copa_cli-0.2.1}/tests/test_fzf.py +0 -0
  34. {copa_cli-0.2.0 → copa_cli-0.2.1}/tests/test_models.py +0 -0
  35. {copa_cli-0.2.0 → copa_cli-0.2.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.2.0
3
+ Version: 0.2.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
@@ -87,7 +87,7 @@ pip install copa-cli[ollama]
87
87
  Add this line to your `~/.zshrc`:
88
88
 
89
89
  ```bash
90
- source /path/to/copa/copa.zsh
90
+ eval "$(copa init zsh)"
91
91
  ```
92
92
 
93
93
  Then restart your shell or run `source ~/.zshrc`. This does three things:
@@ -57,7 +57,7 @@ pip install copa-cli[ollama]
57
57
  Add this line to your `~/.zshrc`:
58
58
 
59
59
  ```bash
60
- source /path/to/copa/copa.zsh
60
+ eval "$(copa init zsh)"
61
61
  ```
62
62
 
63
63
  Then restart your shell or run `source ~/.zshrc`. This does three things:
@@ -20,6 +20,64 @@ def cli():
20
20
  pass
21
21
 
22
22
 
23
+ # --- init ---
24
+
25
+
26
+ @cli.command()
27
+ @click.argument("shell", type=click.Choice(["zsh"]))
28
+ def init(shell: str):
29
+ """Print shell integration code. Add to your .zshrc: eval "$(copa init zsh)" """
30
+ from importlib.resources import files
31
+
32
+ zsh_file = files("copa").joinpath("copa.zsh")
33
+ click.echo(zsh_file.read_text())
34
+
35
+
36
+ # --- uninstall ---
37
+
38
+
39
+ @cli.command()
40
+ @click.option("-y", "--yes", is_flag=True, help="Skip confirmation prompt.")
41
+ def uninstall(yes: bool):
42
+ """Remove Copa data and show cleanup instructions."""
43
+ import shutil
44
+
45
+ copa_dir = Path.home() / ".copa"
46
+
47
+ # Inventory what exists
48
+ items: list[tuple[str, Path]] = []
49
+ if copa_dir.is_dir():
50
+ db_path = copa_dir / "copa.db"
51
+ config_path = copa_dir / "config.toml"
52
+ if db_path.is_file():
53
+ items.append(("database", db_path))
54
+ if config_path.is_file():
55
+ items.append(("config", config_path))
56
+ # Count any other files (.copa exports, etc.)
57
+ other = [f for f in copa_dir.iterdir() if f not in (db_path, config_path)]
58
+ for f in other:
59
+ items.append(("file", f))
60
+
61
+ if not items:
62
+ click.echo("No Copa data found (~/.copa/ does not exist or is empty).")
63
+ else:
64
+ click.echo("Copa data directory: ~/.copa/")
65
+ for kind, path in items:
66
+ size = path.stat().st_size if path.is_file() else 0
67
+ label = f"{size:,} bytes" if size else "directory"
68
+ click.echo(f" {path.name} ({kind}, {label})")
69
+
70
+ if not yes:
71
+ click.confirm("\nDelete ~/.copa/ and all its contents?", abort=True)
72
+
73
+ shutil.rmtree(copa_dir)
74
+ click.echo(click.style("Deleted ~/.copa/", fg="green"))
75
+
76
+ click.echo("\nTo finish uninstalling:")
77
+ click.echo(' 1. Remove this line from your ~/.zshrc: eval "$(copa init zsh)"')
78
+ click.echo(" 2. Run: pip uninstall copa-cli")
79
+
80
+
23
81
  # --- add ---
24
82
 
25
83
 
@@ -0,0 +1,156 @@
1
+ # Copa — shell integration for zsh
2
+ # Add to your .zshrc: eval "$(copa init zsh)"
3
+ #
4
+ # What this does:
5
+ # 1. Records every command you run (precmd hook, background, zero latency)
6
+ # 2. Replaces Ctrl+R with an fzf command palette that searches across
7
+ # command text AND descriptions — not just raw history
8
+ # 3. Ctrl+R cycles modes inside fzf: all → frequent → recent → all
9
+ # 4. Composition keys append shell operators (|, &&, &, etc.) to selected commands
10
+
11
+ # --- Load keybinding config (runs once at shell startup) ---
12
+ eval "$(copa _fzf-config 2>/dev/null)" || {
13
+ # Fallback defaults if copa _fzf-config fails
14
+ _COPA_EXPECT='ctrl-v,ctrl-o,ctrl-x,ctrl-t,ctrl-a,ctrl-/'
15
+ _COPA_DESCRIBE_KEY='ctrl-d'
16
+ _COPA_GROUP_KEY='ctrl-g'
17
+ _COPA_FLAGS_KEY='ctrl-f'
18
+ _COPA_FILTER_GROUP_KEY='ctrl-s'
19
+ _COPA_CYCLE_GROUP_KEY='ctrl-n'
20
+ _COPA_COMPLETION_BRANDING='true'
21
+ _COPA_HEADER='Copa | ^R:cycle | ^V:& | ^O:2>&1 | ^X:| | ^T:> | ^A:&& | ^/:quiet | ^G:grp | ^D:desc | ^F:flag | ^S:scope | ^N:↻grp'
22
+ typeset -gA _COPA_SUFFIXES
23
+ _COPA_SUFFIXES[ctrl-v]=' &'
24
+ _COPA_SUFFIXES[ctrl-o]=' 2>&1'
25
+ _COPA_SUFFIXES[ctrl-x]=' | '
26
+ _COPA_SUFFIXES[ctrl-t]=' > '
27
+ _COPA_SUFFIXES[ctrl-a]=' && '
28
+ _COPA_SUFFIXES[ctrl-/]=' 2>/dev/null'
29
+ }
30
+
31
+ # --- precmd hook: record last command ---
32
+ _copa_precmd() {
33
+ local last_cmd
34
+ last_cmd="$(fc -ln -1 2>/dev/null)"
35
+ last_cmd="${last_cmd## }" # strip leading space
36
+ if [[ -n "$last_cmd" && "$last_cmd" != _copa_* ]]; then
37
+ copa _record "$last_cmd" &!
38
+ fi
39
+ }
40
+
41
+ # Add to precmd hooks (avoid duplicates)
42
+ autoload -Uz add-zsh-hook
43
+ add-zsh-hook precmd _copa_precmd
44
+
45
+ # --- Ctrl+R: fzf-powered command palette ---
46
+ # Replaces default zsh reverse-search. fzf searches all visible fields:
47
+ # command text, description, group badges, shared-set names, and frequency.
48
+ # Selected command is placed into the prompt without executing.
49
+ # Composition keys (configured via copa _fzf-config) append shell operators.
50
+ # Requires fzf: brew install fzf
51
+ _copa_fzf_widget() {
52
+ if ! command -v fzf &>/dev/null; then
53
+ zle -M "Copa: fzf not found. Install with: brew install fzf"
54
+ return 1
55
+ fi
56
+
57
+ local mode="all"
58
+ local output
59
+ local copa_bin="${commands[copa]:-copa}"
60
+
61
+ output=$("$copa_bin" fzf-list --mode "$mode" | \
62
+ fzf --ansi \
63
+ --delimiter '┃' \
64
+ --with-nth '2..3' \
65
+ --preview "$copa_bin _preview {1}" \
66
+ --preview-window 'right:40%:wrap' \
67
+ --header "$_COPA_HEADER" \
68
+ --prompt 'copa> ' \
69
+ --height '80%' \
70
+ --layout reverse \
71
+ --expect "$_COPA_EXPECT" \
72
+ --bind "${_COPA_DESCRIBE_KEY}:execute($copa_bin describe {1})+refresh-preview" \
73
+ --bind "${_COPA_GROUP_KEY}:execute($copa_bin _set-group {1})+reload($copa_bin fzf-list)+refresh-preview" \
74
+ --bind "${_COPA_FLAGS_KEY}:execute($copa_bin _set-flags {1})+reload($copa_bin fzf-list)+refresh-preview" \
75
+ --bind "${_COPA_FILTER_GROUP_KEY}:transform:
76
+ group=\$(${copa_bin} _list-groups | fzf --height 40% --layout reverse --prompt 'group> ' --header 'Select group (ESC=cancel)');
77
+ if [[ \$group == '(all)' ]]; then
78
+ echo \"reload(${copa_bin} fzf-list --mode all)+change-prompt(copa> )\"
79
+ elif [[ -n \$group ]]; then
80
+ echo \"reload(${copa_bin} fzf-list --mode group --group \$group)+change-prompt(copa [\$group]> )\"
81
+ fi" \
82
+ --bind "${_COPA_CYCLE_GROUP_KEY}:transform:
83
+ cur_group='(all)';
84
+ if [[ \$FZF_PROMPT =~ 'copa \\[(.+)\\]> ' ]]; then
85
+ cur_group=\"\${match[1]}\";
86
+ fi;
87
+ next=\$(${copa_bin} _next-group \"\$cur_group\");
88
+ if [[ \$next == '(all)' ]]; then
89
+ echo \"reload(${copa_bin} fzf-list --mode all)+change-prompt(copa> )\";
90
+ else
91
+ echo \"reload(${copa_bin} fzf-list --mode group --group \$next)+change-prompt(copa [\$next]> )\";
92
+ fi" \
93
+ --bind 'ctrl-r:transform:
94
+ if [[ $FZF_PROMPT == "copa> " ]]; then
95
+ echo "reload('"$copa_bin"' fzf-list --mode frequent)+change-prompt(frequent> )"
96
+ elif [[ $FZF_PROMPT == "frequent> " ]]; then
97
+ echo "reload('"$copa_bin"' fzf-list --mode recent)+change-prompt(recent> )"
98
+ else
99
+ echo "reload('"$copa_bin"' fzf-list --mode all)+change-prompt(copa> )"
100
+ fi' \
101
+ --bind 'enter:accept' \
102
+ )
103
+
104
+ if [[ -n "$output" ]]; then
105
+ # --expect output: line 1 = key pressed (empty for Enter), line 2+ = selected item
106
+ local key selected cmd suffix
107
+ key=$(echo "$output" | head -1)
108
+ selected=$(echo "$output" | tail -n +2)
109
+
110
+ if [[ -n "$selected" ]]; then
111
+ cmd=$(echo "$selected" | cut -d'┃' -f2 | sed 's/^ *//;s/ *$//')
112
+ suffix="${_COPA_SUFFIXES[$key]}"
113
+ LBUFFER="${cmd}${suffix}"
114
+ fi
115
+ fi
116
+
117
+ zle reset-prompt
118
+ }
119
+
120
+ zle -N _copa_fzf_widget
121
+ bindkey '^R' _copa_fzf_widget
122
+
123
+ # --- Tab completion ---
124
+ # Ensure zsh completion system is available, then register Copa completions.
125
+ if ! (( $+functions[compdef] )); then
126
+ autoload -Uz compinit && compinit -i -C
127
+ fi
128
+ eval "$(copa completion zsh)"
129
+
130
+ # --- Supplemental tab completion from Copa database ---
131
+ # Registers as a fallback completer so that any command (e.g. adb <TAB>)
132
+ # gets completion candidates from Copa's command history.
133
+ _copa_history_complete() {
134
+ # Only provide Copa completions when primary completers found nothing
135
+ (( compstate[nmatches] > 0 )) && return
136
+ # Skip empty tokens (bare <TAB> with no partial input)
137
+ [[ -z "${words[CURRENT]}" ]] && return
138
+ # Skip internal copa commands
139
+ [[ "${words[CURRENT]}" == _copa_* ]] && return
140
+ local -a results
141
+ results=("${(@f)$(copa _complete-word "${(@)words[1,CURRENT]}" 2>/dev/null)}")
142
+ (( ${#results} )) && compadd -V 'copa-history' -o nosort -- "${results[@]}"
143
+ }
144
+
145
+ # Append to existing completers without clobbering user config
146
+ () {
147
+ local -a cur
148
+ zstyle -g cur ':completion:*' completer 2>/dev/null
149
+ if (( ! ${cur[(Ie)_copa_history_complete]} )); then
150
+ zstyle ':completion:*' completer ${cur:-_complete} _copa_history_complete
151
+ fi
152
+ # Copa completion branding: show "Copa history" header when branding is enabled
153
+ if [[ "$_COPA_COMPLETION_BRANDING" != 'false' ]]; then
154
+ zstyle ':completion:*:*:*:copa-history' group-header '%F{magenta}-- Copa history --%f'
155
+ fi
156
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: copa-cli
3
- Version: 0.2.0
3
+ Version: 0.2.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
@@ -87,7 +87,7 @@ pip install copa-cli[ollama]
87
87
  Add this line to your `~/.zshrc`:
88
88
 
89
89
  ```bash
90
- source /path/to/copa/copa.zsh
90
+ eval "$(copa init zsh)"
91
91
  ```
92
92
 
93
93
  Then restart your shell or run `source ~/.zshrc`. This does three things:
@@ -9,6 +9,7 @@ copa/cli_internal.py
9
9
  copa/cli_llm.py
10
10
  copa/cli_share.py
11
11
  copa/config.py
12
+ copa/copa.zsh
12
13
  copa/db.py
13
14
  copa/evolve.py
14
15
  copa/fzf.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "copa-cli"
7
- version = "0.2.0"
7
+ version = "0.2.1"
8
8
  description = "Command Palette — smart command tracking, ranking, and sharing for your shell"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -42,6 +42,9 @@ copa = "copa.cli:cli"
42
42
  [tool.setuptools.packages.find]
43
43
  include = ["copa*"]
44
44
 
45
+ [tool.setuptools.package-data]
46
+ copa = ["copa.zsh"]
47
+
45
48
  [tool.ruff]
46
49
  target-version = "py312"
47
50
  line-length = 120
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