zsh-jev
v0.1.0
Published
Jev-ranked asynchronous zsh history suggestions, powered by Rust
Maintainers
Readme
zsh-jev
输入命令时,从最近的 zsh 历史中选择候选,用 Jev 排序,在光标后显示灰色建议。按 Tab、→、Ctrl-E 或 End 接受。补全核心使用 Rust,zsh 的 ZLE 交互由轻量插件处理。
% git st▌atus [0.970]
% last 5 commits▌ ⇢ git log --oneline -5 [1.000]npm 安装
npm 分发保留现有功能。包内附带 Rust 二进制,安装时无需 Rust,也不运行安装脚本。Node.js 20+ 用于 npm 和初始化命令;补全请求由 shell 启动器直接 exec Rust 程序。
支持 macOS 和 Linux 的 arm64/x64 平台:
npm install -g zsh-jev在 ~/.zshrc 中、其他补全插件之后加入:
# 可以指向你已有的密钥文件,不必复制进 npm 安装目录。
JEV_ENV_FILE="$HOME/.config/zsh-jev/.env"
eval "$(zsh-jev init zsh)"该文件包含 JEV_KEY=your-api-key;也可使用已导出的 TYPESAFE_API_KEY。设置后重开终端即可使用。
zsh-jev plugin-path 可查询插件文件位置,jev-suggest --help 使用原有 CLI。
从源码使用
需要 Rust 工具链(edition 2024)和 zsh 5.9+。
cargo build --release在项目根目录 .env 中设置(已有文件无需修改):
JEV_KEY=your-api-key然后在当前 zsh 中执行,或加入 ~/.zshrc:
source /path/to/zsh-jev/zsh/jev-shell-history.plugin.zsh插件自动将项目 .env 路径传给 Rust 程序,所以切换工作目录后仍可读取密钥。.env 按数据解析,不会作为 shell 脚本执行。密钥也支持 TYPESAFE_API_KEY 和 JEV_API_KEY;环境变量优先于 dotenv,同一来源中按这三个名字的顺序选择:TYPESAFE_API_KEY、JEV_KEY、JEV_API_KEY。.env 已加入 .gitignore。
每次需要排名时,会向 TypeSafe API 发送当前输入和选中的历史候选。验证脚本只使用临时构造的 echo/true 历史,不读取个人历史。
也可以 cargo install --path .,并在加载插件前设置 JEV_BIN=jev-suggest。
行为
- 读取历史文件末尾最多约 512 KiB,支持普通/扩展历史、多行命令、zsh metafied 字节;按最近出现的位置去重,默认取 100 条。
- 排除与输入完全相同的命令。存在字面前缀时,只排名前缀候选;单个前缀候选直接返回,无需 API 或密钥。
- 没有前缀时,对候选做模糊排名。一次请求包含 Choice 排序和 Noul 匹配判断。
- 模糊建议要求
score >= min-score,且hasCompletion >= threshold或score >= strong-score。 - 默认关闭防抖,需要 Jev 排名时立即请求。可设置
JEV_DEBOUNCE_MS=150开启;等待期间新输入会终止旧进程并重新计时。本地单个前缀候选不等待。CLI 的--debounce-ms默认0,HTTP 超时不包含防抖等待,JSON 的elapsedMs包含等待。 - zsh 后台请求不阻塞输入;输入变化会终止旧进程,过期响应不显示。沿已有前缀继续输入不会重复请求。
- Tab 在行尾有建议时接受完整命令,并将光标移到末尾;分数等显示信息不会插入命令。没有建议或光标不在行尾时,调用加载插件前的 Tab 绑定(建议在其他补全插件之后加载本插件)。
- Ctrl-C 会在退出编辑器前擦除灰字并取消后台请求;仅在 ZLE 编辑期间临时处理 SIGINT,执行命令前恢复原有信号处理器。
- 接受建议只更新编辑缓冲区,按 Enter 才执行。支持 emacs 和 vi 键位;vi 模式可用
bindkey '^E' end-of-line绑定 Ctrl-E。 - 空历史、历史文件不存在或输入过短时不请求 API。HTTP 请求使用总超时,不自动重试,避免旧输入持续占用请求。
HTTP 请求结构参照原项目依赖的 官方 TypeSafe JavaScript SDK,Rust 通过 reqwest 直接调用。
最近失败命令的修复建议
插件通过 preexec / precmd 记录最近一次执行的命令、工作目录和退出码。失败后,在同一目录按 ↑ 调回命令,或重新输入同一个工具名,会尝试生成修复候选。例如项目中存在 src/chunk-e2pzjfv8.js:
$ subl $bunfs/root/chunk-e2pzjfv8.js:1178 # 假设命令返回非零退出码
$ subl \$bunfs/root/c ⇢ subl src/chunk-e2pzjfv8.js:1178- 文件路径:从输入路径的父目录和当前工作目录中查找实际存在的文件,匹配文件名、前缀及少量拼写错误;替换最后一个文件参数,保留其他参数。支持
subl、code、常见编辑器、cat、ls、cd等。subl/code的:行号[:列号]会保留,cd只选择目录。 - 转义:生成的路径自动转义
$、空格、引号等字符。文件名只用于构建参数,不会作为 shell 表达式执行。 - 命令名:退出码为
127且输入的命令名不在 PATH 中时,从 PATH 中查找拼写相近的真实可执行文件。 - 排序:有本地修复候选时,优先排名这些候选;Jev 会看到失败命令、退出码、目录和候选,但不会收到文件内容或 stderr。单个字面前缀候选仍直接返回。找不到修复候选时沿用历史补全,并排除这条已知失败的命令。
- 接受:Tab / → 只填入修正后的命令,仍需 Enter 执行。文件存在不保证权限、工具参数或命令其他部分正确。
递归查找最多下探 4 层子目录、检查 4096 个条目,循环检查约 60 ms 的扫描预算;不递归符号链接目录,并跳过隐藏目录、node_modules、target、vendor、.venv。显式输入这些目录中的路径仍可匹配其直接子项。每次最多生成 24 个文件候选。不会全盘扫描,也不会将不存在的路径当作修复结果。
仅在同一工作目录内使用最近一次失败,后续成功执行会清空记录;Ctrl-C 等信号中断不作为修复依据。复杂管道、重定向、命令替换和通配符表达式暂不自动改写。返回成功退出码的程序,即使界面提示了错误,也不会被当作失败。
可在加载插件前设置 JEV_REPAIR_FAILURES=0 关闭。CLI 调试示例:
jev-suggest --buffer 'subl missing/chunk.js:1178' \
--failed-command 'subl missing/chunk.js:1178' \
--failed-exit-code 1 --failed-cwd "$PWD" --jsonJSON 的 repairing 表示本次是否使用了本地生成的修复候选。
CLI
./target/release/jev-suggest --buffer 'git ch' --list
./target/release/jev-suggest --buffer 'last 5 commits' --json
./target/release/jev-suggest --buffer 'gst' --jev-only
./target/release/jev-suggest --buffer 'echo' --history /tmp/example-history
./target/release/jev-suggest --helpCLI 默认读当前目录 .env,可用 --env-file /path/to/.env 或 JEV_ENV_FILE 指定。历史路径优先级为 --history、HISTFILE、~/.zsh_history。
默认输出供插件使用:第一行是 <score> <has_completion> <prefix|replace>,后面是完整命令(可含换行);无建议时无输出。错误写入 stderr 并以非零状态退出。--json 输出完整排名、建议、耗时、模型和 token 用量;--list [n] 默认显示前 10 条。
插件配置
在 source 前设置:
| 变量 | 默认值 | 含义 |
| --- | --- | --- |
| JEV_BIN | npm 包启动器或项目 target/release/jev-suggest | Rust 可执行文件 |
| JEV_ENV_FILE | 项目 .env | 密钥配置文件 |
| JEV_HISTORY_LIMIT | 100 | 最近不同命令数量 |
| JEV_MIN_CHARS | 2 | 最少输入字符数 |
| JEV_THRESHOLD | 0.5 | 模糊匹配 Noul 门槛 |
| JEV_MIN_SCORE | 0.3 | 模糊匹配最低候选分数 |
| JEV_STRONG_SCORE | 0.9 | 可越过 Noul 门槛的分数 |
| JEV_REPAIR_FAILURES | 1 | 是否为最近失败的命令生成修复候选 |
| JEV_DEBOUNCE_MS | 0 | 远程请求前等待的毫秒数,0 关闭防抖 |
| JEV_TIMEOUT | 8000 | HTTP 总超时,毫秒 |
| JEV_HIGHLIGHT | fg=8 | 灰字 ZLE 样式 |
| JEV_SHOW_SCORE | 1 | 是否显示分数 |
| JEV_MODEL | jev-latest | Jev 模型,可回退到 TYPESAFE_DEFAULT_MODEL |
| JEV_DEBUG_LOG | 不启用 | 请求结果和错误日志;包含输入和建议命令 |
可用 TYPESAFE_BASE_URL 替换 API 根地址,默认 https://api.typesafe.ai。自定义接受键:bindkey '^ ' jev-accept-suggestion。
验证
cargo test
cargo clippy --all-targets -- -D warnings
cargo fmt --check
cargo build --release
cargo build # 防抖回归测试使用 debug 二进制
python3 test/debounce.py
python3 test/repair.py
python3 test/tab.py # 无需 API 的 Tab / 光标交互回归测试
zsh test/e2e.zshRust 测试包含历史格式/截断、Unicode、去重、候选选择、门槛边界、HTTP 请求与响应、错误和超时、CLI 输出及 dotenv 错误脱敏。端到端测试使用真实 zsh 伪终端和 Jev API,需要 .env 或环境变量中的有效密钥,并产生少量 API 调用;覆盖前缀、模糊替换、接受键、无关输入和旧请求取消。
npm 打包和发布
npm run pack:local构建当前平台的测试包,输出到dist/。该包标记private: true,仅用于本地安装,不可发布;Linux 本地包使用当前系统的默认链接方式。npm test验证 tarball 文件白名单、无.env、禁用安装脚本后的全局安装、CLI 和 zsh 初始化,并覆盖含空格/单引号的安装路径。.github/workflows/npm-package.yml在手动触发或推送v*标签时构建四种平台:macOS arm64/x64、Linux arm64/x64。Linux 发布构建使用 musl。流程只生成经过检查的 npm tarball artifact,不自动发布。- 通用包需要
vendor/{darwin-arm64,darwin-x64,linux-arm64,linux-x64}/jev-suggest全部存在且可执行;prepack会阻止不完整的包被打包,并检查 Rust/npm 版本一致。 - npm 包使用
files白名单,只包含启动器、二进制、zsh 插件、打包校验脚本和 README/package 元数据。密钥、源码构建目录、日志不会打入包中。
发布步骤:同步修改 Cargo.toml 和 package.json 的版本并更新 Cargo.lock;将代码放入 GitHub 仓库,运行构建流程;下载 npm-package artifact,核对内容后执行:
npm login
npm publish ./zsh-jev-0.1.0.tgz --access public这里应使用 CI 生成的四平台包,不是 dist/ 下标记私有的本地测试包。CI 在四种平台构建并运行二进制,在 Linux x64 验证完整 npm 安装流程。若改用作用域包名,应同时修改 package.json 和安装文档。
参考:npm 打包字段、npm publish、GitHub 构建平台。
终端显示回归测试(覆盖 Ctrl-C 后的真实屏幕内容、中文多行、vi 模式、请求取消和原有 SIGINT 处理器恢复):
python3 -m venv /tmp/zsh-jev-tests
/tmp/zsh-jev-tests/bin/pip install -r test/requirements.txt
/tmp/zsh-jev-tests/bin/python test/interrupt.py