ai-cli-toolkit 0.2.0__py3-none-any.whl
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.
- ai_cli/__init__.py +3 -0
- ai_cli/__main__.py +6 -0
- ai_cli/bin/ai-mux-linux-x86_64 +0 -0
- ai_cli/bin/remote-tty-wrapper +153 -0
- ai_cli/ca.py +175 -0
- ai_cli/completion_gen.py +680 -0
- ai_cli/config.py +185 -0
- ai_cli/credentials.py +341 -0
- ai_cli/detached_cleanup.py +135 -0
- ai_cli/housekeeping.py +50 -0
- ai_cli/instructions.py +308 -0
- ai_cli/log.py +53 -0
- ai_cli/main.py +1516 -0
- ai_cli/main_helpers.py +553 -0
- ai_cli/prompt_editor_launcher.py +324 -0
- ai_cli/proxy.py +627 -0
- ai_cli/remote.py +669 -0
- ai_cli/remote_package.py +1111 -0
- ai_cli/session.py +1344 -0
- ai_cli/session_store.py +236 -0
- ai_cli/traffic.py +1510 -0
- ai_cli/traffic_db.py +118 -0
- ai_cli/tui.py +525 -0
- ai_cli/update.py +200 -0
- ai_cli_toolkit-0.2.0.dist-info/METADATA +17 -0
- ai_cli_toolkit-0.2.0.dist-info/RECORD +30 -0
- ai_cli_toolkit-0.2.0.dist-info/WHEEL +5 -0
- ai_cli_toolkit-0.2.0.dist-info/entry_points.txt +2 -0
- ai_cli_toolkit-0.2.0.dist-info/licenses/LICENSE +21 -0
- ai_cli_toolkit-0.2.0.dist-info/top_level.txt +1 -0
ai_cli/completion_gen.py
ADDED
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
"""Generate concise shell completions for ai-cli and wrapped tools.
|
|
2
|
+
|
|
3
|
+
Design goals:
|
|
4
|
+
- Keep completion scripts human-readable and fast (no huge autogenerated blobs)
|
|
5
|
+
- Distinguish wrapped aliases (~/.ai-cli/bin/<tool>) from native binaries
|
|
6
|
+
- Offer wrapper-specific flags only when command is wrapped
|
|
7
|
+
- Capture baseline completion scripts from tools that expose built-in generators
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import re
|
|
14
|
+
import subprocess
|
|
15
|
+
import sys
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
from ai_cli.config import ensure_config, get_tool_config
|
|
21
|
+
from ai_cli.tools import load_registry
|
|
22
|
+
|
|
23
|
+
WRAPPER_FLAGS = [
|
|
24
|
+
"--ai-cli-system-instructions-file",
|
|
25
|
+
"--ai-cli-system-instructions-text",
|
|
26
|
+
"--ai-cli-canary-rule",
|
|
27
|
+
"--ai-cli-passthrough",
|
|
28
|
+
"--ai-cli-debug-requests",
|
|
29
|
+
"--ai-cli-developer-instructions-mode",
|
|
30
|
+
"--ai-cli-no-startup-context",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class ToolCompletionData:
|
|
36
|
+
name: str
|
|
37
|
+
native_flags: list[str]
|
|
38
|
+
native_subcommands: list[str]
|
|
39
|
+
file_flags: list[str]
|
|
40
|
+
dir_flags: list[str]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _run(cmd: list[str], timeout: int = 5) -> tuple[int, str]:
|
|
44
|
+
try:
|
|
45
|
+
proc = subprocess.run(
|
|
46
|
+
cmd, check=False, capture_output=True, text=True,
|
|
47
|
+
timeout=timeout, stdin=subprocess.DEVNULL,
|
|
48
|
+
)
|
|
49
|
+
except subprocess.TimeoutExpired:
|
|
50
|
+
return 1, ""
|
|
51
|
+
except OSError:
|
|
52
|
+
return 1, ""
|
|
53
|
+
output = (proc.stdout or "") + (proc.stderr or "")
|
|
54
|
+
return proc.returncode, output
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _looks_like_script(text: str, shell: str) -> bool:
|
|
58
|
+
if not text.strip():
|
|
59
|
+
return False
|
|
60
|
+
if shell == "bash":
|
|
61
|
+
return "complete " in text or "compgen" in text
|
|
62
|
+
return "compdef" in text or "_arguments" in text
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _baseline_completion_command(tool: str, binary: str, shell: str) -> Optional[list[str]]:
|
|
66
|
+
if tool == "codex":
|
|
67
|
+
return [binary, "completion", shell]
|
|
68
|
+
if tool in {"copilot", "gemini", "claude"}:
|
|
69
|
+
return [binary, "completion", shell]
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _extract_flags(help_text: str) -> list[tuple[Optional[str], str, bool]]:
|
|
74
|
+
flags: list[tuple[Optional[str], str, bool]] = []
|
|
75
|
+
seen: set[str] = set()
|
|
76
|
+
regex = re.compile(r"^\s*(?:(-[A-Za-z0-9]),\s*)?(--[A-Za-z0-9][A-Za-z0-9-]*)(.*)$")
|
|
77
|
+
|
|
78
|
+
for raw in help_text.splitlines():
|
|
79
|
+
line = raw.rstrip("\n")
|
|
80
|
+
m = regex.match(line)
|
|
81
|
+
if not m:
|
|
82
|
+
continue
|
|
83
|
+
short = m.group(1)
|
|
84
|
+
long_opt = m.group(2)
|
|
85
|
+
tail = m.group(3) or ""
|
|
86
|
+
if long_opt in seen:
|
|
87
|
+
continue
|
|
88
|
+
seen.add(long_opt)
|
|
89
|
+
expects_value = any(tok in tail for tok in ("<", ":", "="))
|
|
90
|
+
flags.append((short, long_opt, expects_value))
|
|
91
|
+
|
|
92
|
+
return flags
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _extract_commands(help_text: str) -> list[str]:
|
|
96
|
+
commands: list[str] = []
|
|
97
|
+
seen: set[str] = set()
|
|
98
|
+
in_block = False
|
|
99
|
+
cmd_line = re.compile(r"^ ([A-Za-z][A-Za-z0-9-]*)\s{2,}.*$")
|
|
100
|
+
|
|
101
|
+
for raw in help_text.splitlines():
|
|
102
|
+
line = raw.rstrip("\n")
|
|
103
|
+
low = line.strip().lower()
|
|
104
|
+
|
|
105
|
+
if not in_block and (
|
|
106
|
+
low.startswith("commands:") or low.startswith("available commands:")
|
|
107
|
+
):
|
|
108
|
+
in_block = True
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
if not in_block:
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
if not line.strip():
|
|
115
|
+
continue
|
|
116
|
+
|
|
117
|
+
if low.startswith(("options:", "flags:", "arguments:", "positionals:", "examples:")):
|
|
118
|
+
break
|
|
119
|
+
|
|
120
|
+
m = cmd_line.match(raw)
|
|
121
|
+
if not m:
|
|
122
|
+
continue
|
|
123
|
+
|
|
124
|
+
token = m.group(1).strip("[]")
|
|
125
|
+
if not token or token.startswith("-") or token in {"[query..]", "[command]"}:
|
|
126
|
+
continue
|
|
127
|
+
if token in seen:
|
|
128
|
+
continue
|
|
129
|
+
seen.add(token)
|
|
130
|
+
commands.append(token)
|
|
131
|
+
|
|
132
|
+
return commands
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _classify_flags(flags: list[tuple[Optional[str], str, bool]]) -> tuple[list[str], list[str]]:
|
|
136
|
+
file_flags: list[str] = []
|
|
137
|
+
dir_flags: list[str] = []
|
|
138
|
+
for _, long_opt, expects in flags:
|
|
139
|
+
if not expects:
|
|
140
|
+
continue
|
|
141
|
+
low = long_opt.lower()
|
|
142
|
+
if any(k in low for k in ("dir", "directory", "worktree")):
|
|
143
|
+
dir_flags.append(long_opt)
|
|
144
|
+
elif any(k in low for k in ("file", "path", "config", "schema", "settings", "ca")):
|
|
145
|
+
file_flags.append(long_opt)
|
|
146
|
+
return sorted(set(file_flags)), sorted(set(dir_flags))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
_responsive_tools: set[str] = set()
|
|
150
|
+
"""Tools whose --help responded within timeout (skip slow ones in baseline)."""
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _tool_data() -> list[ToolCompletionData]:
|
|
154
|
+
registry = load_registry()
|
|
155
|
+
cfg = ensure_config()
|
|
156
|
+
result: list[ToolCompletionData] = []
|
|
157
|
+
|
|
158
|
+
for name, spec in registry.items():
|
|
159
|
+
tool_cfg = get_tool_config(cfg, name)
|
|
160
|
+
binary = spec.resolve_binary(tool_cfg.get("binary", ""))
|
|
161
|
+
code, help_text = _run([binary, "--help"])
|
|
162
|
+
if code != 0 and not help_text.strip():
|
|
163
|
+
help_text = ""
|
|
164
|
+
else:
|
|
165
|
+
_responsive_tools.add(name)
|
|
166
|
+
|
|
167
|
+
parsed_flags = _extract_flags(help_text)
|
|
168
|
+
commands = _extract_commands(help_text)
|
|
169
|
+
file_flags, dir_flags = _classify_flags(parsed_flags)
|
|
170
|
+
|
|
171
|
+
native_flags: list[str] = []
|
|
172
|
+
for short, long_opt, _ in parsed_flags:
|
|
173
|
+
if short:
|
|
174
|
+
native_flags.append(short)
|
|
175
|
+
native_flags.append(long_opt)
|
|
176
|
+
|
|
177
|
+
result.append(
|
|
178
|
+
ToolCompletionData(
|
|
179
|
+
name=name,
|
|
180
|
+
native_flags=sorted(set(native_flags)),
|
|
181
|
+
native_subcommands=commands,
|
|
182
|
+
file_flags=file_flags,
|
|
183
|
+
dir_flags=dir_flags,
|
|
184
|
+
)
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
return result
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _capture_baselines(root: Path, tools: list[ToolCompletionData], shell: str) -> None:
|
|
191
|
+
registry = load_registry()
|
|
192
|
+
cfg = ensure_config()
|
|
193
|
+
dest = root / "completions" / "generated" / "baseline"
|
|
194
|
+
dest.mkdir(parents=True, exist_ok=True)
|
|
195
|
+
|
|
196
|
+
for tool in tools:
|
|
197
|
+
if tool.name not in _responsive_tools:
|
|
198
|
+
continue
|
|
199
|
+
spec = registry[tool.name]
|
|
200
|
+
tool_cfg = get_tool_config(cfg, tool.name)
|
|
201
|
+
binary = spec.resolve_binary(tool_cfg.get("binary", ""))
|
|
202
|
+
cmd = _baseline_completion_command(tool.name, binary, shell)
|
|
203
|
+
if not cmd:
|
|
204
|
+
continue
|
|
205
|
+
rc, out = _run(cmd)
|
|
206
|
+
if rc == 0 and _looks_like_script(out, shell):
|
|
207
|
+
ext = "bash" if shell == "bash" else "zsh"
|
|
208
|
+
(dest / f"{tool.name}.{ext}").write_text(out, encoding="utf-8")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _join(values: list[str]) -> str:
|
|
212
|
+
return " ".join(values)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _render_bash(tools: list[ToolCompletionData]) -> str:
|
|
216
|
+
case_blocks = []
|
|
217
|
+
for t in tools:
|
|
218
|
+
case_blocks.append(
|
|
219
|
+
f""" {t.name})
|
|
220
|
+
native_flags=\"{_join(t.native_flags)}\"
|
|
221
|
+
native_subcommands=\"{_join(t.native_subcommands)}\"
|
|
222
|
+
file_flags=\"{_join(t.file_flags)}\"
|
|
223
|
+
dir_flags=\"{_join(t.dir_flags)}\"
|
|
224
|
+
;;
|
|
225
|
+
"""
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
ai_subcommands = "claude codex copilot gemini menu status system prompt-edit session traffic update completions help"
|
|
229
|
+
tool_names = "claude codex copilot gemini"
|
|
230
|
+
wrapper_flags = _join(WRAPPER_FLAGS)
|
|
231
|
+
traffic_callers = "claude copilot codex gemini"
|
|
232
|
+
traffic_sort_opts = "date address"
|
|
233
|
+
|
|
234
|
+
return f'''#!/usr/bin/env bash
|
|
235
|
+
# Bash completion for ai-cli and wrapped/native tool aliases.
|
|
236
|
+
# Generated by ai_cli.completion_gen
|
|
237
|
+
|
|
238
|
+
_ai_cli_is_wrapped_cmd() {{
|
|
239
|
+
local cmd="$1"
|
|
240
|
+
local resolved
|
|
241
|
+
resolved="$(command -v "$cmd" 2>/dev/null || true)"
|
|
242
|
+
[[ "$resolved" == "$HOME/.ai-cli/bin/$cmd" ]]
|
|
243
|
+
}}
|
|
244
|
+
|
|
245
|
+
_ai_cli_fill_tool_data() {{
|
|
246
|
+
local tool="$1"
|
|
247
|
+
native_flags=""
|
|
248
|
+
native_subcommands=""
|
|
249
|
+
file_flags=""
|
|
250
|
+
dir_flags=""
|
|
251
|
+
case "$tool" in
|
|
252
|
+
{''.join(case_blocks)} *) ;;
|
|
253
|
+
esac
|
|
254
|
+
}}
|
|
255
|
+
|
|
256
|
+
_ai_cli_tool_completion_impl() {{
|
|
257
|
+
local tool="$1"
|
|
258
|
+
local cur prev words cword
|
|
259
|
+
_init_completion 2>/dev/null || {{
|
|
260
|
+
COMPREPLY=()
|
|
261
|
+
cur="${{COMP_WORDS[COMP_CWORD]}}"
|
|
262
|
+
prev="${{COMP_WORDS[COMP_CWORD-1]}}"
|
|
263
|
+
words=("${{COMP_WORDS[@]}}")
|
|
264
|
+
cword=$COMP_CWORD
|
|
265
|
+
}}
|
|
266
|
+
|
|
267
|
+
_ai_cli_fill_tool_data "$tool"
|
|
268
|
+
|
|
269
|
+
local flags="$native_flags"
|
|
270
|
+
if _ai_cli_is_wrapped_cmd "$tool"; then
|
|
271
|
+
flags="$flags {wrapper_flags}"
|
|
272
|
+
fi
|
|
273
|
+
|
|
274
|
+
case " $file_flags " in
|
|
275
|
+
*" $prev "*) COMPREPLY=($(compgen -f -- "$cur")); return ;;
|
|
276
|
+
esac
|
|
277
|
+
case " $dir_flags " in
|
|
278
|
+
*" $prev "*) COMPREPLY=($(compgen -d -- "$cur")); return ;;
|
|
279
|
+
esac
|
|
280
|
+
|
|
281
|
+
if [[ "$cur" == -* ]]; then
|
|
282
|
+
COMPREPLY=($(compgen -W "$flags" -- "$cur"))
|
|
283
|
+
return
|
|
284
|
+
fi
|
|
285
|
+
|
|
286
|
+
local i subcommand=""
|
|
287
|
+
for (( i=1; i < cword; i++ )); do
|
|
288
|
+
if [[ "${{words[i]}}" != -* ]]; then
|
|
289
|
+
subcommand="${{words[i]}}"
|
|
290
|
+
break
|
|
291
|
+
fi
|
|
292
|
+
done
|
|
293
|
+
|
|
294
|
+
if [[ -z "$subcommand" ]]; then
|
|
295
|
+
COMPREPLY=($(compgen -W "$native_subcommands" -- "$cur"))
|
|
296
|
+
COMPREPLY+=($(compgen -d -- "$cur"))
|
|
297
|
+
fi
|
|
298
|
+
}}
|
|
299
|
+
|
|
300
|
+
_ai_cli_completion() {{
|
|
301
|
+
local cur prev words cword
|
|
302
|
+
_init_completion 2>/dev/null || {{
|
|
303
|
+
COMPREPLY=()
|
|
304
|
+
cur="${{COMP_WORDS[COMP_CWORD]}}"
|
|
305
|
+
prev="${{COMP_WORDS[COMP_CWORD-1]}}"
|
|
306
|
+
words=("${{COMP_WORDS[@]}}")
|
|
307
|
+
cword=$COMP_CWORD
|
|
308
|
+
}}
|
|
309
|
+
|
|
310
|
+
# Top-level subcommand completions with descriptions
|
|
311
|
+
if [[ $cword -eq 1 ]]; then
|
|
312
|
+
COMPREPLY=($(compgen -W "{ai_subcommands}" -- "$cur"))
|
|
313
|
+
return
|
|
314
|
+
fi
|
|
315
|
+
|
|
316
|
+
local cmd="${{words[1]}}"
|
|
317
|
+
case "$cmd" in
|
|
318
|
+
claude|codex|copilot|gemini)
|
|
319
|
+
_ai_cli_tool_completion_impl "$cmd"
|
|
320
|
+
return ;;
|
|
321
|
+
system)
|
|
322
|
+
if [[ $cword -eq 2 ]]; then
|
|
323
|
+
COMPREPLY=($(compgen -W "prompt {tool_names}" -- "$cur"))
|
|
324
|
+
else
|
|
325
|
+
COMPREPLY=($(compgen -W "{tool_names}" -- "$cur"))
|
|
326
|
+
fi
|
|
327
|
+
return ;;
|
|
328
|
+
session)
|
|
329
|
+
case "$prev" in
|
|
330
|
+
--agent) COMPREPLY=($(compgen -W "all {tool_names}" -- "$cur")); return ;;
|
|
331
|
+
--tail) return ;;
|
|
332
|
+
esac
|
|
333
|
+
if [[ "$cur" == -* ]]; then
|
|
334
|
+
COMPREPLY=($(compgen -W "--agent --all --list --grep --tail --tools --raw" -- "$cur"))
|
|
335
|
+
else
|
|
336
|
+
COMPREPLY=($(compgen -f -- "$cur"))
|
|
337
|
+
fi
|
|
338
|
+
return ;;
|
|
339
|
+
traffic)
|
|
340
|
+
case "$prev" in
|
|
341
|
+
--caller|-c) COMPREPLY=($(compgen -W "{traffic_callers}" -- "$cur")); return ;;
|
|
342
|
+
--sort) COMPREPLY=($(compgen -W "{traffic_sort_opts}" -- "$cur")); return ;;
|
|
343
|
+
--db) COMPREPLY=($(compgen -f -- "$cur")); return ;;
|
|
344
|
+
--host|--search|-s|--limit|-n|--detail|-d) return ;;
|
|
345
|
+
esac
|
|
346
|
+
if [[ "$cur" == -* ]]; then
|
|
347
|
+
COMPREPLY=($(compgen -W "--caller --host --search --api --sort --limit --db --plain --detail" -- "$cur"))
|
|
348
|
+
fi
|
|
349
|
+
return ;;
|
|
350
|
+
update)
|
|
351
|
+
if [[ "$cur" == -* ]]; then
|
|
352
|
+
COMPREPLY=($(compgen -W "--all --dry-run --list --method --list-methods" -- "$cur"))
|
|
353
|
+
else
|
|
354
|
+
COMPREPLY=($(compgen -W "{tool_names}" -- "$cur"))
|
|
355
|
+
fi
|
|
356
|
+
return ;;
|
|
357
|
+
completions)
|
|
358
|
+
if [[ $cword -eq 2 ]]; then
|
|
359
|
+
COMPREPLY=($(compgen -W "generate" -- "$cur"))
|
|
360
|
+
elif [[ "$cur" == -* ]]; then
|
|
361
|
+
COMPREPLY=($(compgen -W "--shell" -- "$cur"))
|
|
362
|
+
fi
|
|
363
|
+
return ;;
|
|
364
|
+
esac
|
|
365
|
+
}}
|
|
366
|
+
|
|
367
|
+
_ai_cli_claude_completion() {{ _ai_cli_tool_completion_impl claude; }}
|
|
368
|
+
_ai_cli_codex_completion() {{ _ai_cli_tool_completion_impl codex; }}
|
|
369
|
+
_ai_cli_copilot_completion() {{ _ai_cli_tool_completion_impl copilot; }}
|
|
370
|
+
_ai_cli_gemini_completion() {{ _ai_cli_tool_completion_impl gemini; }}
|
|
371
|
+
|
|
372
|
+
complete -F _ai_cli_completion ai-cli
|
|
373
|
+
complete -F _ai_cli_claude_completion claude
|
|
374
|
+
complete -F _ai_cli_codex_completion codex
|
|
375
|
+
complete -F _ai_cli_copilot_completion copilot
|
|
376
|
+
complete -F _ai_cli_gemini_completion gemini
|
|
377
|
+
'''
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
def _render_zsh(tools: list[ToolCompletionData]) -> str:
|
|
381
|
+
case_blocks = []
|
|
382
|
+
for t in tools:
|
|
383
|
+
case_blocks.append(
|
|
384
|
+
f""" {t.name})
|
|
385
|
+
native_flags=({_join(t.native_flags)})
|
|
386
|
+
native_subcommands=({_join(t.native_subcommands)})
|
|
387
|
+
file_flags=({_join(t.file_flags)})
|
|
388
|
+
dir_flags=({_join(t.dir_flags)})
|
|
389
|
+
;;
|
|
390
|
+
"""
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
ai_subcommands = "claude codex copilot gemini menu status system prompt-edit session traffic update completions help"
|
|
394
|
+
tool_names = "claude codex copilot gemini"
|
|
395
|
+
wrapper_flags = _join(WRAPPER_FLAGS)
|
|
396
|
+
traffic_callers = "claude copilot codex gemini"
|
|
397
|
+
traffic_sort_opts = "date address"
|
|
398
|
+
|
|
399
|
+
return f'''#compdef ai-cli claude codex copilot gemini
|
|
400
|
+
# Zsh completion for ai-cli and wrapped/native tool aliases.
|
|
401
|
+
# Generated by ai_cli.completion_gen
|
|
402
|
+
|
|
403
|
+
autoload -U is-at-least
|
|
404
|
+
|
|
405
|
+
_ai_cli_is_wrapped_cmd() {{
|
|
406
|
+
local cmd="$1"
|
|
407
|
+
local resolved
|
|
408
|
+
resolved="$(whence -p "$cmd" 2>/dev/null)"
|
|
409
|
+
[[ "$resolved" == "$HOME/.ai-cli/bin/$cmd" ]]
|
|
410
|
+
}}
|
|
411
|
+
|
|
412
|
+
_ai_cli_fill_tool_data() {{
|
|
413
|
+
local tool="$1"
|
|
414
|
+
native_flags=()
|
|
415
|
+
native_subcommands=()
|
|
416
|
+
file_flags=()
|
|
417
|
+
dir_flags=()
|
|
418
|
+
case "$tool" in
|
|
419
|
+
{''.join(case_blocks)} *) ;;
|
|
420
|
+
esac
|
|
421
|
+
}}
|
|
422
|
+
|
|
423
|
+
_ai_cli_tool_completion_impl() {{
|
|
424
|
+
local tool="$1"
|
|
425
|
+
_ai_cli_fill_tool_data "$tool"
|
|
426
|
+
|
|
427
|
+
local -a flags
|
|
428
|
+
flags=(${{native_flags[@]}})
|
|
429
|
+
if _ai_cli_is_wrapped_cmd "$tool"; then
|
|
430
|
+
flags+=({wrapper_flags})
|
|
431
|
+
fi
|
|
432
|
+
|
|
433
|
+
local prev="${{words[CURRENT-1]}}"
|
|
434
|
+
if (( ${{file_flags[(Ie)$prev]}} )); then
|
|
435
|
+
_files
|
|
436
|
+
return
|
|
437
|
+
fi
|
|
438
|
+
if (( ${{dir_flags[(Ie)$prev]}} )); then
|
|
439
|
+
_directories
|
|
440
|
+
return
|
|
441
|
+
fi
|
|
442
|
+
|
|
443
|
+
if [[ "${{words[CURRENT]}}" == -* ]]; then
|
|
444
|
+
_describe 'option' flags
|
|
445
|
+
return
|
|
446
|
+
fi
|
|
447
|
+
|
|
448
|
+
local i subcommand=""
|
|
449
|
+
for ((i=2; i<CURRENT; i++)); do
|
|
450
|
+
if [[ "${{words[i]}}" != -* ]]; then
|
|
451
|
+
subcommand="${{words[i]}}"
|
|
452
|
+
break
|
|
453
|
+
fi
|
|
454
|
+
done
|
|
455
|
+
|
|
456
|
+
if [[ -z "$subcommand" ]]; then
|
|
457
|
+
_describe 'command' native_subcommands
|
|
458
|
+
_directories
|
|
459
|
+
fi
|
|
460
|
+
}}
|
|
461
|
+
|
|
462
|
+
_ai_cli_main_completion() {{
|
|
463
|
+
local cur="${{words[CURRENT]}}"
|
|
464
|
+
local cmd="${{words[2]}}"
|
|
465
|
+
|
|
466
|
+
if (( CURRENT == 2 )); then
|
|
467
|
+
local -a sc
|
|
468
|
+
sc=(
|
|
469
|
+
"claude:Launch Claude Code agent"
|
|
470
|
+
"codex:Launch OpenAI Codex agent"
|
|
471
|
+
"copilot:Launch GitHub Copilot CLI"
|
|
472
|
+
"gemini:Launch Google Gemini CLI"
|
|
473
|
+
"menu:Interactive tool manager (TUI)"
|
|
474
|
+
"status:Show installed tools and versions"
|
|
475
|
+
"system:Edit or view system instructions"
|
|
476
|
+
"prompt-edit:Edit global or tool prompt file"
|
|
477
|
+
"session:Browse agent conversation history"
|
|
478
|
+
"traffic:Browse and search proxied API traffic"
|
|
479
|
+
"update:Install or update wrapped tools"
|
|
480
|
+
"completions:Generate shell completion scripts"
|
|
481
|
+
"help:Show usage information"
|
|
482
|
+
)
|
|
483
|
+
_describe 'command' sc
|
|
484
|
+
return
|
|
485
|
+
fi
|
|
486
|
+
|
|
487
|
+
case "$cmd" in
|
|
488
|
+
claude|codex|copilot|gemini)
|
|
489
|
+
_ai_cli_tool_completion_impl "$cmd"
|
|
490
|
+
return ;;
|
|
491
|
+
system)
|
|
492
|
+
if (( CURRENT == 3 )); then
|
|
493
|
+
local -a sa
|
|
494
|
+
sa=(
|
|
495
|
+
"prompt:Show captured system prompt for a model"
|
|
496
|
+
"claude:Edit Claude instructions"
|
|
497
|
+
"codex:Edit Codex instructions"
|
|
498
|
+
"copilot:Edit Copilot instructions"
|
|
499
|
+
"gemini:Edit Gemini instructions"
|
|
500
|
+
)
|
|
501
|
+
_describe 'subcommand' sa
|
|
502
|
+
else
|
|
503
|
+
local -a tn
|
|
504
|
+
tn=({tool_names})
|
|
505
|
+
_describe 'tool' tn
|
|
506
|
+
fi
|
|
507
|
+
return ;;
|
|
508
|
+
prompt-edit)
|
|
509
|
+
if (( CURRENT == 3 )); then
|
|
510
|
+
local -a sa
|
|
511
|
+
sa=(
|
|
512
|
+
"global:Edit global prompt file"
|
|
513
|
+
"tool:Edit tool-specific prompt file"
|
|
514
|
+
)
|
|
515
|
+
_describe 'scope' sa
|
|
516
|
+
elif (( CURRENT == 4 )); then
|
|
517
|
+
local -a tn
|
|
518
|
+
tn=({tool_names})
|
|
519
|
+
_describe 'tool' tn
|
|
520
|
+
fi
|
|
521
|
+
return ;;
|
|
522
|
+
session)
|
|
523
|
+
case "${{words[CURRENT-1]}}" in
|
|
524
|
+
--agent)
|
|
525
|
+
local -a a
|
|
526
|
+
a=(
|
|
527
|
+
"all:All tools"
|
|
528
|
+
"claude:Claude sessions"
|
|
529
|
+
"codex:Codex sessions"
|
|
530
|
+
"copilot:Copilot sessions"
|
|
531
|
+
"gemini:Gemini sessions"
|
|
532
|
+
)
|
|
533
|
+
_describe 'agent' a
|
|
534
|
+
return ;;
|
|
535
|
+
--tail)
|
|
536
|
+
_message 'number'
|
|
537
|
+
return ;;
|
|
538
|
+
esac
|
|
539
|
+
if [[ "$cur" == -* ]]; then
|
|
540
|
+
local -a sf
|
|
541
|
+
sf=(
|
|
542
|
+
"--agent:Filter by tool name"
|
|
543
|
+
"--all:Show all sessions"
|
|
544
|
+
"--list:List sessions"
|
|
545
|
+
"--grep:Search session content"
|
|
546
|
+
"--tail:Show last N entries"
|
|
547
|
+
"--tools:Show tool metadata"
|
|
548
|
+
"--raw:Raw output format"
|
|
549
|
+
)
|
|
550
|
+
_describe 'flag' sf
|
|
551
|
+
else
|
|
552
|
+
_files
|
|
553
|
+
fi
|
|
554
|
+
return ;;
|
|
555
|
+
traffic)
|
|
556
|
+
case "${{words[CURRENT-1]}}" in
|
|
557
|
+
--caller|-c)
|
|
558
|
+
local -a tc
|
|
559
|
+
tc=(
|
|
560
|
+
"claude:Show Claude traffic"
|
|
561
|
+
"copilot:Show Copilot traffic"
|
|
562
|
+
"codex:Show Codex traffic"
|
|
563
|
+
"gemini:Show Gemini traffic"
|
|
564
|
+
)
|
|
565
|
+
_describe 'caller' tc
|
|
566
|
+
return ;;
|
|
567
|
+
--sort)
|
|
568
|
+
local -a so
|
|
569
|
+
so=(
|
|
570
|
+
"date:Sort by timestamp (newest first)"
|
|
571
|
+
"address:Sort by host and path"
|
|
572
|
+
)
|
|
573
|
+
_describe 'sort order' so
|
|
574
|
+
return ;;
|
|
575
|
+
--db)
|
|
576
|
+
_files
|
|
577
|
+
return ;;
|
|
578
|
+
--host|--search|-s|--limit|-n|--detail|-d)
|
|
579
|
+
return ;;
|
|
580
|
+
esac
|
|
581
|
+
if [[ "$cur" == -* ]]; then
|
|
582
|
+
local -a tf
|
|
583
|
+
tf=(
|
|
584
|
+
"--caller:Filter by caller tool"
|
|
585
|
+
"--host:Filter by host substring"
|
|
586
|
+
"--search:Search request/response bodies"
|
|
587
|
+
"--api:Show only confirmed API calls"
|
|
588
|
+
"--sort:Sort order (date or address)"
|
|
589
|
+
"--limit:Maximum rows to show"
|
|
590
|
+
"--db:Path to traffic database"
|
|
591
|
+
"--plain:Plain text output (no curses)"
|
|
592
|
+
"--detail:Show detail for a specific row ID"
|
|
593
|
+
)
|
|
594
|
+
_describe 'flag' tf
|
|
595
|
+
fi
|
|
596
|
+
return ;;
|
|
597
|
+
update)
|
|
598
|
+
if [[ "$cur" == -* ]]; then
|
|
599
|
+
local -a uf
|
|
600
|
+
uf=(
|
|
601
|
+
"--all:Update all tools"
|
|
602
|
+
"--dry-run:Show what would be done"
|
|
603
|
+
"--list:List available tools"
|
|
604
|
+
"--method:Installation method"
|
|
605
|
+
"--list-methods:List install methods"
|
|
606
|
+
)
|
|
607
|
+
_describe 'flag' uf
|
|
608
|
+
else
|
|
609
|
+
local -a tn
|
|
610
|
+
tn=({tool_names})
|
|
611
|
+
_describe 'tool' tn
|
|
612
|
+
fi
|
|
613
|
+
return ;;
|
|
614
|
+
completions)
|
|
615
|
+
if (( CURRENT == 3 )); then
|
|
616
|
+
local -a acts
|
|
617
|
+
acts=("generate:Generate completion scripts")
|
|
618
|
+
_describe 'action' acts
|
|
619
|
+
elif [[ "$cur" == -* ]]; then
|
|
620
|
+
local -a cf
|
|
621
|
+
cf=("--shell:Shell type (bash, zsh, all)")
|
|
622
|
+
_describe 'flag' cf
|
|
623
|
+
fi
|
|
624
|
+
return ;;
|
|
625
|
+
esac
|
|
626
|
+
}}
|
|
627
|
+
|
|
628
|
+
_ai_cli_command_completion() {{ _ai_cli_main_completion "$@"; }}
|
|
629
|
+
_ai_cli_claude_completion() {{ _ai_cli_tool_completion_impl claude; }}
|
|
630
|
+
_ai_cli_codex_completion() {{ _ai_cli_tool_completion_impl codex; }}
|
|
631
|
+
_ai_cli_copilot_completion() {{ _ai_cli_tool_completion_impl copilot; }}
|
|
632
|
+
_ai_cli_gemini_completion() {{ _ai_cli_tool_completion_impl gemini; }}
|
|
633
|
+
|
|
634
|
+
compdef _ai_cli_command_completion ai-cli
|
|
635
|
+
compdef _ai_cli_claude_completion claude
|
|
636
|
+
compdef _ai_cli_codex_completion codex
|
|
637
|
+
compdef _ai_cli_copilot_completion copilot
|
|
638
|
+
compdef _ai_cli_gemini_completion gemini
|
|
639
|
+
'''
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def generate(shell: str = "all") -> int:
|
|
643
|
+
root = Path(__file__).resolve().parent.parent
|
|
644
|
+
comp_dir = root / "completions"
|
|
645
|
+
comp_dir.mkdir(parents=True, exist_ok=True)
|
|
646
|
+
|
|
647
|
+
tools = _tool_data()
|
|
648
|
+
|
|
649
|
+
if shell in ("all", "bash"):
|
|
650
|
+
_capture_baselines(root, tools, "bash")
|
|
651
|
+
(comp_dir / "ai-cli.bash").write_text(_render_bash(tools), encoding="utf-8")
|
|
652
|
+
|
|
653
|
+
if shell in ("all", "zsh"):
|
|
654
|
+
_capture_baselines(root, tools, "zsh")
|
|
655
|
+
(comp_dir / "_ai-cli").write_text(_render_zsh(tools), encoding="utf-8")
|
|
656
|
+
|
|
657
|
+
print("Generated completions:")
|
|
658
|
+
print(f"- {comp_dir / '_ai-cli'}")
|
|
659
|
+
print(f"- {comp_dir / 'ai-cli.bash'}")
|
|
660
|
+
print(f"- {comp_dir / 'generated' / 'baseline'}")
|
|
661
|
+
return 0
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
def main(argv: Optional[list[str]] = None) -> int:
|
|
665
|
+
parser = argparse.ArgumentParser(prog="ai-cli completions")
|
|
666
|
+
sub = parser.add_subparsers(dest="action")
|
|
667
|
+
gen = sub.add_parser("generate")
|
|
668
|
+
gen.add_argument("--shell", choices=["bash", "zsh", "all"], default="all")
|
|
669
|
+
args = parser.parse_args(argv)
|
|
670
|
+
|
|
671
|
+
action = args.action or "generate"
|
|
672
|
+
if action == "generate":
|
|
673
|
+
return generate(shell=getattr(args, "shell", "all"))
|
|
674
|
+
|
|
675
|
+
parser.print_help()
|
|
676
|
+
return 1
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
if __name__ == "__main__":
|
|
680
|
+
raise SystemExit(main())
|