feat(completion): 自动安装 myagents/myclaude/mykimi shell 补全,移除 eval 旧脚本
This commit is contained in:
@@ -2,15 +2,15 @@ ROOT_DIR := $(shell pwd)
|
|||||||
VENV_BIN_DIR := $(ROOT_DIR)/.venv/bin
|
VENV_BIN_DIR := $(ROOT_DIR)/.venv/bin
|
||||||
USER_BIN_DIR := $(HOME)/.local/bin
|
USER_BIN_DIR := $(HOME)/.local/bin
|
||||||
COMMANDS := myagents myclaude mykimi
|
COMMANDS := myagents myclaude mykimi
|
||||||
COMP_DIR := $(HOME)/.local/bin/completions
|
|
||||||
|
|
||||||
.PHONY: help install uninstall _symlink-commands _install-completions _uninstall-completions
|
.PHONY: help install uninstall _symlink-commands
|
||||||
|
|
||||||
help:
|
help:
|
||||||
@echo "Usage: make [target]"
|
@echo "Usage: make [target]"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo " install Sync venv, symlink bin, install completions"
|
@echo " install Sync venv, symlink bin, install shell completions"
|
||||||
@echo " uninstall Remove bin and completions"
|
@echo " uninstall Remove bin, completions and user symlinks"
|
||||||
|
|
||||||
install:
|
install:
|
||||||
@cd "$(ROOT_DIR)" && \
|
@cd "$(ROOT_DIR)" && \
|
||||||
if command -v uv >/dev/null 2>&1; then \
|
if command -v uv >/dev/null 2>&1; then \
|
||||||
@@ -20,7 +20,8 @@ install:
|
|||||||
PIP_USER=0 .venv/bin/pip install -e .; \
|
PIP_USER=0 .venv/bin/pip install -e .; \
|
||||||
fi
|
fi
|
||||||
@$(MAKE) _symlink-commands
|
@$(MAKE) _symlink-commands
|
||||||
@$(MAKE) _install-completions
|
@echo "Installing shell completions…"
|
||||||
|
@myagents completion install || echo "Warning: completion install failed"
|
||||||
|
|
||||||
_symlink-commands:
|
_symlink-commands:
|
||||||
@mkdir -p "$(USER_BIN_DIR)"
|
@mkdir -p "$(USER_BIN_DIR)"
|
||||||
@@ -32,15 +33,6 @@ _symlink-commands:
|
|||||||
echo "Linked $$dst -> $$src"; \
|
echo "Linked $$dst -> $$src"; \
|
||||||
done
|
done
|
||||||
|
|
||||||
_install-completions:
|
uninstall:
|
||||||
@$(ROOT_DIR)/scripts/install_completion.sh "$(VENV_BIN_DIR)" "$(COMP_DIR)"
|
@myagents completion uninstall || true
|
||||||
|
|
||||||
uninstall: _uninstall-completions
|
|
||||||
@ROOT_DIR="$(ROOT_DIR)" python3 "$(ROOT_DIR)/scripts/rm_user_local_myagents.py"
|
@ROOT_DIR="$(ROOT_DIR)" python3 "$(ROOT_DIR)/scripts/rm_user_local_myagents.py"
|
||||||
|
|
||||||
_uninstall-completions:
|
|
||||||
@rm -f \
|
|
||||||
"$(COMP_DIR)/_myagents" "$(COMP_DIR)/myagents.bash" \
|
|
||||||
"$(COMP_DIR)/_myclaude" "$(COMP_DIR)/myclaude.bash" \
|
|
||||||
"$(COMP_DIR)/_mykimi" "$(COMP_DIR)/mykimi.bash"
|
|
||||||
@echo "Removed completions"
|
|
||||||
|
|||||||
@@ -49,9 +49,24 @@ make install
|
|||||||
`make install` 会:
|
`make install` 会:
|
||||||
1. 同步/创建虚拟环境并做可编辑安装
|
1. 同步/创建虚拟环境并做可编辑安装
|
||||||
2. 在 `~/.local/bin/` 创建 `myagents`、`myclaude`、`mykimi` 符号链接
|
2. 在 `~/.local/bin/` 创建 `myagents`、`myclaude`、`mykimi` 符号链接
|
||||||
3. 安装 shell 补全(zsh/bash)
|
3. 自动安装 shell 补全到 `~/.local/share/zsh/site-functions` 和 `~/.local/share/bash-completion/completions`
|
||||||
|
|
||||||
请确保 `~/.local/bin` 在 `PATH` 中。移除链接:`make uninstall`。
|
请确保 `~/.local/bin` 在 `PATH` 中,并在 `~/.zshrc` 里加上:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
fpath=(~/.local/share/zsh/site-functions $fpath)
|
||||||
|
autoload -Uz compinit && compinit
|
||||||
|
```
|
||||||
|
|
||||||
|
移除链接与补全:`make uninstall`。
|
||||||
|
|
||||||
|
首次运行任意 `myagents` / `myclaude` / `mykimi` 命令时,若补全缺失会自动补齐。也可以手动管理:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
myagents completion doctor # 查看补全状态
|
||||||
|
myagents completion install # 手动重装补全
|
||||||
|
myagents completion zsh # 打印 zsh 补全脚本
|
||||||
|
```
|
||||||
|
|
||||||
> 💡 **提示**:没有 uv 时会创建项目 `.venv` 并用其中的 pip 做可编辑安装。
|
> 💡 **提示**:没有 uv 时会创建项目 `.venv` 并用其中的 pip 做可编辑安装。
|
||||||
|
|
||||||
@@ -68,6 +83,7 @@ myagents kimi # 启动 Kimi Code CLI
|
|||||||
myagents kimi -S <session-id> # 恢复 Kimi session
|
myagents kimi -S <session-id> # 恢复 Kimi session
|
||||||
myagents update # 重装 myagents 并同步 workspace 链接
|
myagents update # 重装 myagents 并同步 workspace 链接
|
||||||
myagents upgrade # update 别名
|
myagents upgrade # update 别名
|
||||||
|
myagents completion # 安装/查看/卸载 shell 补全
|
||||||
|
|
||||||
# 独立入口(与上面完全等价)
|
# 独立入口(与上面完全等价)
|
||||||
myclaude
|
myclaude
|
||||||
@@ -91,18 +107,6 @@ mykimi -S <session-id>
|
|||||||
export MYAGENTS_WORKSPACE_ROOT="/your/custom/workspace"
|
export MYAGENTS_WORKSPACE_ROOT="/your/custom/workspace"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Shell 补全(可选)
|
|
||||||
|
|
||||||
在 `~/.zshrc` 或 `~/.bashrc` 中加入:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
if command -v myagents >/dev/null 2>&1; then
|
|
||||||
eval "$(_MYAGENTS_COMPLETE=zsh_source myagents)"
|
|
||||||
fi
|
|
||||||
```
|
|
||||||
|
|
||||||
Bash 将 `zsh_source` 换成 `bash_source`。保存后 `source` 配置文件。
|
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -5,9 +5,21 @@ from importlib.metadata import PackageNotFoundError, version
|
|||||||
import click
|
import click
|
||||||
|
|
||||||
from myagents.commands import update_cmd, upgrade_cmd
|
from myagents.commands import update_cmd, upgrade_cmd
|
||||||
|
from myagents.commands.completion import build_completion_group
|
||||||
from myagents.launcher import build_cli
|
from myagents.launcher import build_cli
|
||||||
|
|
||||||
|
|
||||||
|
def _progs():
|
||||||
|
"""Return (prog_name, cli_factory) pairs that need shell completions."""
|
||||||
|
from myagents.entrypoints import claude_cli, kimi_cli
|
||||||
|
|
||||||
|
return [
|
||||||
|
("myagents", lambda: cli),
|
||||||
|
("myclaude", lambda: claude_cli),
|
||||||
|
("mykimi", lambda: kimi_cli),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def _package_version() -> str:
|
def _package_version() -> str:
|
||||||
try:
|
try:
|
||||||
return version("myagents")
|
return version("myagents")
|
||||||
@@ -32,9 +44,13 @@ cli.add_command(build_cli("claude"), name="claude")
|
|||||||
cli.add_command(build_cli("kimi"), name="kimi")
|
cli.add_command(build_cli("kimi"), name="kimi")
|
||||||
cli.add_command(update_cmd)
|
cli.add_command(update_cmd)
|
||||||
cli.add_command(upgrade_cmd, name="upgrade")
|
cli.add_command(upgrade_cmd, name="upgrade")
|
||||||
|
cli.add_command(build_completion_group(_progs))
|
||||||
|
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
|
from myagents.commands.completion_install import ensure_completions_installed
|
||||||
|
|
||||||
|
ensure_completions_installed(_progs())
|
||||||
cli()
|
cli()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
"""Shell tab-completion CLI for myagents."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
import click
|
||||||
|
from click.shell_completion import get_completion_class
|
||||||
|
|
||||||
|
from myagents.commands.completion_install import (
|
||||||
|
ProgList,
|
||||||
|
install_completions,
|
||||||
|
run_completion_doctor,
|
||||||
|
uninstall_completions,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ProgListFn(Protocol):
|
||||||
|
def __call__(self) -> ProgList: ...
|
||||||
|
|
||||||
|
|
||||||
|
def _print_script(prog_name: str, shell: str) -> None:
|
||||||
|
"""Print the completion script for one prog/shell pair."""
|
||||||
|
# Import the requested CLI object on demand to avoid circular imports.
|
||||||
|
if prog_name == "myagents":
|
||||||
|
from myagents.cli import cli
|
||||||
|
|
||||||
|
cli_obj = cli
|
||||||
|
elif prog_name == "myclaude":
|
||||||
|
from myagents.entrypoints import claude_cli
|
||||||
|
|
||||||
|
cli_obj = claude_cli
|
||||||
|
elif prog_name == "mykimi":
|
||||||
|
from myagents.entrypoints import kimi_cli
|
||||||
|
|
||||||
|
cli_obj = kimi_cli
|
||||||
|
else:
|
||||||
|
raise click.ClickException(f"Unknown command: {prog_name}")
|
||||||
|
|
||||||
|
shell_class = get_completion_class(shell)
|
||||||
|
if shell_class is None:
|
||||||
|
raise click.ClickException(f"Unsupported shell: {shell}")
|
||||||
|
complete_var = f"_{prog_name.upper()}_COMPLETE"
|
||||||
|
comp = shell_class(cli_obj, {}, prog_name, complete_var)
|
||||||
|
script = comp.source()
|
||||||
|
if not script.endswith("\n"):
|
||||||
|
script += "\n"
|
||||||
|
sys.stdout.write(script)
|
||||||
|
|
||||||
|
|
||||||
|
def build_completion_group(progs_fn: ProgListFn) -> click.Group:
|
||||||
|
@click.group(name="completion", help="Install or inspect shell tab-completion scripts.")
|
||||||
|
def completion() -> None:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@completion.command("install")
|
||||||
|
def install_cmd() -> None:
|
||||||
|
"""Write completion scripts for myagents, myclaude and mykimi."""
|
||||||
|
installed = install_completions(progs_fn())
|
||||||
|
if not installed:
|
||||||
|
raise click.ClickException(
|
||||||
|
"No completion files were written (directory not writable?)."
|
||||||
|
)
|
||||||
|
for path in installed:
|
||||||
|
click.echo(path)
|
||||||
|
|
||||||
|
@completion.command("uninstall")
|
||||||
|
def uninstall_cmd() -> None:
|
||||||
|
"""Remove completion scripts."""
|
||||||
|
removed = uninstall_completions(progs_fn())
|
||||||
|
for path in removed:
|
||||||
|
click.echo(path)
|
||||||
|
if not removed:
|
||||||
|
click.echo("(nothing to remove)")
|
||||||
|
|
||||||
|
@completion.command("doctor")
|
||||||
|
def doctor_cmd() -> None:
|
||||||
|
"""Report completion install state."""
|
||||||
|
run_completion_doctor(progs_fn())
|
||||||
|
|
||||||
|
for shell in ("bash", "zsh"):
|
||||||
|
|
||||||
|
@completion.command(
|
||||||
|
name=shell,
|
||||||
|
help=f"Print the {shell} completion script for a command.",
|
||||||
|
)
|
||||||
|
@click.argument("command", default="myagents")
|
||||||
|
def print_shell(command: str, shell_name: str = shell) -> None:
|
||||||
|
_print_script(command, shell_name)
|
||||||
|
|
||||||
|
return completion
|
||||||
@@ -0,0 +1,213 @@
|
|||||||
|
"""Install shell tab-completion files for myagents entrypoints.
|
||||||
|
|
||||||
|
Completions are written to the user's XDG data directory by default
|
||||||
|
(``~/.local/share``), which keeps them independent of the active venv path.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Callable, Sequence
|
||||||
|
|
||||||
|
import click
|
||||||
|
from click.shell_completion import get_completion_class
|
||||||
|
|
||||||
|
|
||||||
|
ProgList = Sequence[tuple[str, Callable[[], click.Command]]]
|
||||||
|
|
||||||
|
|
||||||
|
MANIFEST_FILENAME = "completion-paths.txt"
|
||||||
|
|
||||||
|
|
||||||
|
def _default_install_root() -> Path:
|
||||||
|
data_home = os.environ.get("XDG_DATA_HOME")
|
||||||
|
if data_home:
|
||||||
|
return Path(data_home)
|
||||||
|
return Path.home() / ".local" / "share"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class CompletionTarget:
|
||||||
|
shell: str
|
||||||
|
prog_name: str
|
||||||
|
path: Path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def installed(self) -> bool:
|
||||||
|
return self.path.is_file()
|
||||||
|
|
||||||
|
|
||||||
|
def completion_targets(
|
||||||
|
prog_name: str,
|
||||||
|
*,
|
||||||
|
install_root: Path | None = None,
|
||||||
|
) -> list[CompletionTarget]:
|
||||||
|
root = install_root or _default_install_root()
|
||||||
|
return [
|
||||||
|
CompletionTarget(
|
||||||
|
"zsh",
|
||||||
|
prog_name,
|
||||||
|
root / "zsh" / "site-functions" / f"_{prog_name}",
|
||||||
|
),
|
||||||
|
CompletionTarget(
|
||||||
|
"bash",
|
||||||
|
prog_name,
|
||||||
|
root / "bash-completion" / "completions" / prog_name,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def _completion_script(cli: click.Command, shell: str, prog_name: str) -> str:
|
||||||
|
shell_class = get_completion_class(shell)
|
||||||
|
if shell_class is None:
|
||||||
|
raise ValueError(f"unsupported shell for completion: {shell}")
|
||||||
|
comp = shell_class(cli, {}, prog_name, f"_{prog_name.upper()}_COMPLETE")
|
||||||
|
script = comp.source()
|
||||||
|
if not script.endswith("\n"):
|
||||||
|
script += "\n"
|
||||||
|
return script
|
||||||
|
|
||||||
|
|
||||||
|
def _all_targets(
|
||||||
|
progs: ProgList,
|
||||||
|
install_root: Path | None,
|
||||||
|
) -> list[CompletionTarget]:
|
||||||
|
targets: list[CompletionTarget] = []
|
||||||
|
for prog_name, _ in progs:
|
||||||
|
targets.extend(completion_targets(prog_name, install_root=install_root))
|
||||||
|
return targets
|
||||||
|
|
||||||
|
|
||||||
|
def install_completions(
|
||||||
|
progs: ProgList,
|
||||||
|
*,
|
||||||
|
install_root: Path | None = None,
|
||||||
|
) -> list[Path]:
|
||||||
|
"""Write completion scripts for all progs and return installed paths."""
|
||||||
|
scripts: dict[tuple[str, str], str] = {}
|
||||||
|
for prog_name, cli_factory in progs:
|
||||||
|
cli = cli_factory()
|
||||||
|
for shell in ("zsh", "bash"):
|
||||||
|
scripts[(prog_name, shell)] = _completion_script(cli, shell, prog_name)
|
||||||
|
|
||||||
|
installed: list[Path] = []
|
||||||
|
for target in _all_targets(progs, install_root):
|
||||||
|
try:
|
||||||
|
target.path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
if not os.access(target.path.parent, os.W_OK):
|
||||||
|
continue
|
||||||
|
target.path.write_text(
|
||||||
|
scripts[(target.prog_name, target.shell)],
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
installed.append(target.path)
|
||||||
|
return installed
|
||||||
|
|
||||||
|
|
||||||
|
def uninstall_completions(
|
||||||
|
progs: ProgList,
|
||||||
|
*,
|
||||||
|
install_root: Path | None = None,
|
||||||
|
) -> list[Path]:
|
||||||
|
"""Remove completion scripts for all progs and return removed paths."""
|
||||||
|
removed: list[Path] = []
|
||||||
|
seen: set[Path] = set()
|
||||||
|
for target in _all_targets(progs, install_root):
|
||||||
|
if target.path in seen:
|
||||||
|
continue
|
||||||
|
seen.add(target.path)
|
||||||
|
if target.path.is_file():
|
||||||
|
target.path.unlink()
|
||||||
|
removed.append(target.path)
|
||||||
|
return removed
|
||||||
|
|
||||||
|
|
||||||
|
def completions_installed(
|
||||||
|
progs: ProgList,
|
||||||
|
*,
|
||||||
|
install_root: Path | None = None,
|
||||||
|
) -> bool:
|
||||||
|
"""Check whether the zsh completion for the first prog is current."""
|
||||||
|
if not progs:
|
||||||
|
return True
|
||||||
|
prog_name, _ = progs[0]
|
||||||
|
for target in completion_targets(prog_name, install_root=install_root):
|
||||||
|
if target.shell == "zsh" and target.installed:
|
||||||
|
marker = f"_{prog_name.upper()}_COMPLETE"
|
||||||
|
if marker in target.path.read_text(encoding="utf-8"):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_completions_installed(
|
||||||
|
progs: ProgList,
|
||||||
|
) -> None:
|
||||||
|
"""Install completions once if they are missing."""
|
||||||
|
if completions_installed(progs):
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
install_completions(progs)
|
||||||
|
except (OSError, ValueError):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def _in_zsh_fpath(zsh_dir: Path) -> bool:
|
||||||
|
"""Check whether ``zsh_dir`` is on the interactive zsh fpath."""
|
||||||
|
try:
|
||||||
|
check = subprocess.run(
|
||||||
|
[
|
||||||
|
"zsh",
|
||||||
|
"-c",
|
||||||
|
f"source ~/.zshrc >/dev/null 2>&1; "
|
||||||
|
f"[[ -n ${{fpath[(r){shlex.quote(str(zsh_dir))}]}} ]] && echo yes",
|
||||||
|
],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
return "yes" in check.stdout
|
||||||
|
except (OSError, subprocess.SubprocessError):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def run_completion_doctor(progs: ProgList) -> None:
|
||||||
|
"""Report completion install state."""
|
||||||
|
install_root = _default_install_root()
|
||||||
|
click.echo(click.style("Install root", bold=True) + f": {install_root}")
|
||||||
|
click.echo()
|
||||||
|
|
||||||
|
click.echo(click.style("Completion scripts", fg="blue", bold=True))
|
||||||
|
for prog_name, _ in progs:
|
||||||
|
for target in completion_targets(prog_name):
|
||||||
|
marker = f"_{prog_name.upper()}_COMPLETE"
|
||||||
|
if target.installed and marker in target.path.read_text(encoding="utf-8"):
|
||||||
|
status = click.style("ok", fg="green")
|
||||||
|
elif target.installed:
|
||||||
|
status = click.style("stale", fg="yellow")
|
||||||
|
else:
|
||||||
|
status = click.style("missing", fg="red")
|
||||||
|
click.echo(f" {target.shell:5} {target.prog_name:10} {target.path} [{status}]")
|
||||||
|
click.echo()
|
||||||
|
|
||||||
|
zsh_dir = install_root / "zsh" / "site-functions"
|
||||||
|
if zsh_dir.is_dir() and not _in_zsh_fpath(zsh_dir):
|
||||||
|
click.echo(
|
||||||
|
click.style("Zsh", fg="yellow", bold=True)
|
||||||
|
+ f": add to fpath if Tab does not work:\n fpath=({zsh_dir} $fpath)"
|
||||||
|
)
|
||||||
|
click.echo()
|
||||||
|
|
||||||
|
if not completions_installed(progs):
|
||||||
|
click.echo(
|
||||||
|
click.style("Fix", fg="yellow", bold=True)
|
||||||
|
+ ": run `myagents completion install` or reinstall the package."
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
click.echo(click.style("Completions are installed.", fg="green", bold=True))
|
||||||
@@ -6,11 +6,27 @@ claude_cli = build_cli("claude", prog_name="myclaude")
|
|||||||
kimi_cli = build_cli("kimi", prog_name="mykimi")
|
kimi_cli = build_cli("kimi", prog_name="mykimi")
|
||||||
|
|
||||||
|
|
||||||
|
def _progs():
|
||||||
|
from myagents.cli import cli
|
||||||
|
|
||||||
|
return [
|
||||||
|
("myagents", lambda: cli),
|
||||||
|
("myclaude", lambda: claude_cli),
|
||||||
|
("mykimi", lambda: kimi_cli),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def claude_main() -> None:
|
def claude_main() -> None:
|
||||||
"""Run ``myclaude``."""
|
"""Run ``myclaude``."""
|
||||||
|
from myagents.commands.completion_install import ensure_completions_installed
|
||||||
|
|
||||||
|
ensure_completions_installed(_progs())
|
||||||
claude_cli()
|
claude_cli()
|
||||||
|
|
||||||
|
|
||||||
def kimi_main() -> None:
|
def kimi_main() -> None:
|
||||||
"""Run ``mykimi``."""
|
"""Run ``mykimi``."""
|
||||||
|
from myagents.commands.completion_install import ensure_completions_installed
|
||||||
|
|
||||||
|
ensure_completions_installed(_progs())
|
||||||
kimi_cli()
|
kimi_cli()
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Install shell completions for myagents, myclaude, mykimi to user directory only
|
|
||||||
# Usage: install_completion.sh <venv_bin_dir> <comp_dir>
|
|
||||||
|
|
||||||
VENV_BIN_DIR="$1"
|
|
||||||
COMP_DIR="$2"
|
|
||||||
|
|
||||||
if [[ -z "$VENV_BIN_DIR" || -z "$COMP_DIR" ]]; then
|
|
||||||
echo "Usage: $0 <venv_bin_dir> <comp_dir>" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
USER_SHELL=$(basename "$SHELL")
|
|
||||||
|
|
||||||
# Portable uppercase helper (macOS bash 3.2 lacks ${var^^}).
|
|
||||||
_upcase() {
|
|
||||||
printf '%s' "$1" | tr '[:lower:]' '[:upper:]'
|
|
||||||
}
|
|
||||||
|
|
||||||
install_zsh_completion() {
|
|
||||||
local cmd="$1"
|
|
||||||
local out="$2"
|
|
||||||
local var="_$(_upcase "$cmd")_COMPLETE"
|
|
||||||
mkdir -p "$COMP_DIR" 2>/dev/null || { echo "Error: cannot create $COMP_DIR" >&2; return 1; }
|
|
||||||
if eval "$var=zsh_source ${VENV_BIN_DIR}/${cmd}" > "$out" 2>/dev/null; then
|
|
||||||
echo "Installed zsh completion: $out"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
echo "Warning: failed to generate zsh completion for $cmd" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
install_bash_completion() {
|
|
||||||
local cmd="$1"
|
|
||||||
local out="$2"
|
|
||||||
local var="_$(_upcase "$cmd")_COMPLETE"
|
|
||||||
mkdir -p "$COMP_DIR" 2>/dev/null || { echo "Error: cannot create $COMP_DIR" >&2; return 1; }
|
|
||||||
if eval "$var=bash_source ${VENV_BIN_DIR}/${cmd}" > "$out" 2>/dev/null; then
|
|
||||||
echo "Installed bash completion: $out"
|
|
||||||
return 0
|
|
||||||
else
|
|
||||||
echo "Warning: failed to generate bash completion for $cmd" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if [[ "$USER_SHELL" == "zsh" ]]; then
|
|
||||||
install_zsh_completion myagents "$COMP_DIR/_myagents"
|
|
||||||
install_zsh_completion myclaude "$COMP_DIR/_myclaude"
|
|
||||||
install_zsh_completion mykimi "$COMP_DIR/_mykimi"
|
|
||||||
elif [[ "$USER_SHELL" == "bash" ]]; then
|
|
||||||
install_bash_completion myagents "$COMP_DIR/myagents.bash"
|
|
||||||
install_bash_completion myclaude "$COMP_DIR/myclaude.bash"
|
|
||||||
install_bash_completion mykimi "$COMP_DIR/mykimi.bash"
|
|
||||||
else
|
|
||||||
echo "Shell '$USER_SHELL' is not supported for automatic completion installation."
|
|
||||||
echo "To install completions manually, run one of the following commands:"
|
|
||||||
for cmd in myagents myclaude mykimi; do
|
|
||||||
var="_$(_upcase "$cmd")_COMPLETE"
|
|
||||||
echo " zsh: $var=zsh_source ${VENV_BIN_DIR}/${cmd} > /path/to/completions/_${cmd}"
|
|
||||||
echo " bash: $var=bash_source ${VENV_BIN_DIR}/${cmd} > /path/to/completions/${cmd}.bash"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
@@ -43,6 +43,16 @@ def main() -> int:
|
|||||||
print("Skip:", link, "is not a symlink, leaving untouched")
|
print("Skip:", link, "is not a symlink, leaving untouched")
|
||||||
skipped += 1
|
skipped += 1
|
||||||
|
|
||||||
|
# Clean up legacy completion files from the old manual install location.
|
||||||
|
legacy_comp_dir = os.path.expanduser("~/.local/bin/completions")
|
||||||
|
for command in _COMMANDS:
|
||||||
|
for filename in (f"_{command}", f"{command}.bash"):
|
||||||
|
path = os.path.join(legacy_comp_dir, filename)
|
||||||
|
if os.path.isfile(path):
|
||||||
|
os.unlink(path)
|
||||||
|
print("Removed legacy completion", path)
|
||||||
|
removed += 1
|
||||||
|
|
||||||
if removed == 0 and skipped == 0:
|
if removed == 0 and skipped == 0:
|
||||||
print("Nothing to remove")
|
print("Nothing to remove")
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
Reference in New Issue
Block a user