@vertu-tech/vps-cli
v0.2.0
Published
Vertu CLI - bridge Odoo ai_cli + ai_skills to a local Node.js command and one-click install skills into Cursor / Claude Code / Trae.
Maintainers
Readme
@vertu-tech/vps-cli
把 Odoo ai_cli + ai_skills 两个模块的 HTTP 网关包装成一个本地 CLI(vertu),并支持把服务端定义的 Skill 一键安装到 Cursor / Claude Code / Trae / OpenClaw / Hermes 这些 AI Agent 的用户级目录。
- CLI 是运行时:调用真实的
/ai/cli/v1/execute、/ai/skills/v1/execute等接口 - Skill 是知识层:
SKILL.md告诉 AI Agent 在什么场景下、用什么参数去调vertu
设计理念、接口契约的更深背景见 vertu_addons/ai_cli/README.md 与 vertu_addons/ai_skills/README.md。
特性
- 单二进制
vertu,子命令分组:login/switch/logout/whoami/caps/update/<program> <leaf...>/skill ... - 多服务、多库、多账号登录项:每次成功登录按标准化的
baseUrl + db + login独立保存;vertu switch本地秒切已登录项,vertu switch --cwd可让不同 Git worktree 固定使用不同端口、数据库和账号 - 统一且隔离的认证:用户登录走
/ai/cli/v1/login,每个登录项及其关联端点有独立的 session、caps 和 Skill 缓存;session_id 写 OS 钥匙串(keytar),不可用时降级到~/.vertu/config.json(POSIX 0600);CI 走VERTU_BOT_INBOUND_KEY环境变量 - 端点与命令树自动收敛:主服务下发的
endpoint_aliases是关联端点真源;alias 被删除时 CLI 清理该登录项的旧端点/凭据/缓存,关联端点未成功登录时不物化依赖它的动态 leaf - 动态命令:每次启动按需读取
/ai/cli/v1/capabilities,根据leaves[].argv_prefix还原嵌套命令、按parameters_schema渲染--<flag>,并按invoke_kind自动选择payload.arguments(ai_tool)或payload.json_params(http_json) - Skill 跨 Agent 同步:
vertu skill update默认扫描全部 Adapter 的.vertu-manifest.json,按(agent, skill_key) → sha256与远端比对:内容漂移会重写、远端新增的 skill 会自动安装到「已经在用此 Agent」的本机、远端已删除的 skill 会本地物理 prune;--dry-run仅打印对齐表,--no-include-new/--no-prune可分别关闭新增/删除同步 - AI Agent 低频自维护:动态业务命令最多每 24 小时基于已加载 capabilities 做一次本地检查,只向 stderr 提醒且从不自动写入;
vertu update只报告,只有显式vertu update --apply才按 CLI → capabilities → Skills 更新 - 支持的 Agent 安装目标(仅用户级):
- Cursor →
~/.cursor/skills/<skill_key>/ - Claude Code →
~/.claude/skills/<skill_key>/ - Trae 国际版 →
~/.trae/skills/<skill_key>/ - Trae 国内版 →
~/.trae-cn/skills/<skill_key>/(与国际版完全分离) - OpenClaw →
~/.openclaw/skills/<skill_key>/(OpenClaw 的 Managed/local skills 根目录,可被~/.openclaw/openclaw.json的 allowlist 进一步过滤) - Hermes →
~/.hermes/skills/<skill_key>/(Nous Research Hermes Agent 的唯一 source-of-truth 目录) - 任意目录(v0.1.5+)→ 通过
--dir <path>直接安装到自定义路径,用于尚未在内置列表里的「未知 Agent」;支持~与相对路径,可与--agent混用
- Cursor →
- AI-friendly 输入纠错(v0.1.3+):
- 单
required: string字段的 leaf 自动暴露 positional shorthand:vertu odoo knowledge business "罗来"等价于... business --query "罗来",无需先--help一次再写 - 智能 unknown-flag 提示:
--model→(Did you mean one of --model-name, --model-name-file?)、--field→(Did you mean --fields?),覆盖 commander 默认maxDistance=3算法漏掉的「短前缀 vs 长 kebab 名」常见 AI 误用 - Sandbox 脚本里检测到
sql_read(时会在 stderr 提醒:Odoo 模型数据优先用 ACL-aware ORM(env["model"].search/search_read/_read_group),sql_read只用于非 ORM PostgreSQL 表/视图或必要 SQL 报表口径
- 单
一句话自举(推荐,AI Agent 引导安装)
把「装 vertu + 登录 + 把业务 skills 同步到当前 agent」交给 AI agent 完成。bootstrap skill 由 Odoo ai_skills 静态托管,无需登录即可访问:
https://admin.vertu.cn/ai_skills/static/skills/vps-cli/SKILL.md
仓库内真源:vertu-erp/vertu_addons/ai_skills/static/skills/vps-cli/SKILL.md
在 Cursor / Claude Code / Trae / OpenClaw / Hermes 里跟 agent 说(或等价表述):
安装 https://admin.vertu.cn/ai_skills/static/skills/vps-cli/SKILL.md 这个 skill,并按 skill 初始化 vertu CLI、把业务 skill 装到当前 agent
agent 会自行 wget/curl 该文件到本机 agent 的 skills/vps-cli/,再按 SKILL.md 走完:检测 Node 20+ → npm i -g @vertu-tech/vps-cli@latest → 引导你 vertu login(密码须本人填写)→ vertu skill update(或 vertu skill install --all --agent all -y)→ vertu whoami / vertu caps list 自检。
Hermes 用户对照:与「先装 hermes-agent skill 再自举」类似;区别是 bootstrap skill 由 Odoo 静态托管,agent 按 URL 拉取即可。
快速开始(手动)
公网安装(推荐,使用方)
npm install -g @vertu-tech/vps-cli
vertu --version
vertu login # 默认登录 https://admin.vertu.cn,无需 --base-url / --db
vertu --help最低 Node 版本 20+(包内已声明 engines.node)。
从源码安装(贡献方)
git clone https://gitee.com/he-linyan/vps-cli.git
cd vps-cli
npm install
npm run build
node dist/vertu.cjs --help
# 或者把工作区链成全局 `vertu`,改 `src/` 后只需 `npm run build` 即生效:
npm link
vertu --help完整流程示例见 examples/README.md。
升级 / 卸载
vertu update # 只读检查,稳定 JSON 用 --json
vertu update --apply # 明确授权后执行完整维护链路
npm update -g @vertu-tech/vps-cli # 人工 fallback(link/tarball/来源不明时使用)
npm uninstall -g @vertu-tech/vps-cli # 卸载普通 vertu odoo ... 调用不会升级 CLI、刷新 caps 或改写 Skills。被动检查复用该次已经加载的 capabilities,不增加 RPC;正常周期内只读取一份按 base URL 隔离的状态文件。相同问题每天最多提醒一次,服务端 minimum/recommended 或 revision 改变时可提前检查。
命令一览
会话与缓存
| 命令 | 说明 |
|------|------|
| vertu login [--base-url --db --login --password] | 调用 /ai/cli/v1/login,新增或刷新一个 baseUrl + db + login 登录项并设为全局当前项;不会覆盖同 URL/DB 下的其他 login |
| vertu switch [--base-url --db --login] [--cwd] | 在已成功登录的登录项中按 AND 条件筛选并切换;不重新认证、不访问网络。--cwd 绑定当前 Git worktree |
| vertu switch --list [--json] | 查看全部登录项、全局/worktree 当前项、端点数、caps 状态及更新时间 |
| vertu switch --clear-cwd | 清除当前 worktree/目录绑定,恢复全局当前登录项 |
| vertu logout [--all] [--forget] | 清除当前登录项的 session;--all 仅覆盖该登录项的关联端点,--forget 只删除该登录项 |
| vertu reauth [--all] [--endpoint ALIAS] | 刷新当前登录项的 session;默认及 --all 都只处理当前登录项的关联端点 |
| vertu whoami | 调用 /ai/cli/v1/whoami,附带打印凭据来源、缓存路径 |
| vertu caps refresh | 强刷 /ai/cli/v1/capabilities 缓存 |
| vertu caps list [--program --json] | 渲染当前缓存里的命令树 |
| vertu update [--json] | 只读检查 CLI / capabilities / Skills,输出有序 actions |
| vertu update --apply [--json] | 显式执行 CLI → capabilities → Skills;JSON 模式仅最终报告走 stdout |
动态命令
vertu <program_slug> <segment...> 会从 capabilities 里找到对应 leaf,根据 JSON Schema 渲染 --<flag>。flag 名是 schema key 的 kebab-case(model_name → --model-name,knowledge_types → --knowledge-types);snake_case 也接受,会在入口被归一化:
# 标准 ai_tool leaf:每个字段都是 --kebab-flag
vertu odoo data search --model-name res.partner --domain '[]' --fields 'id,name' --limit 5
# 单 required string 字段的 leaf 支持 positional shorthand(v0.1.3+)
vertu odoo knowledge business "罗来"
# 等价于:vertu odoo knowledge business --query "罗来"
# http_json leaf
vertu odoo skills execute --skill-key sample --params '{"foo":1}'
# sandbox leaf:复杂只读逻辑优先 ORM;sql_read 仅作为非 ORM 表/视图的例外路径
vertu odoo data sandbox --code "ai['result'] = env['sale.order'].search_count([])"
# 兜底:直接传整个 payload
vertu odoo data search --payload-json '{"arguments":{"model_name":"res.partner","limit":3}}'写错 flag 名时(--model 这种 AI 高频肌肉记忆)会得到上下文相关的提示:
$ vertu odoo data search --model res.users --domain "..."
error: unknown option '--model'
(Did you mean one of --model-name, --model-name-file?)
(use -h or --help for usage)Skill 子命令
| 命令 | 说明 |
|------|------|
| vertu skill list [--source remote\|caps] [--json] | 列出所有可用 skill |
| vertu skill pull <skill_key\|all> [--out DIR] | 仅下载 SKILL.md + assets,不写入 Agent |
| vertu skill run <skill_key> [--script-key --params --validate-only] | 调用 /ai/skills/v1/execute |
| vertu skill install [skill_keys...] [--all] [--agent ...] [--dir ...] [-y] | 交互式多选向导(不传任何参数时进入),亦可显式列 key 或用 --all;TTY 缺省会同时让你多选 Agent,并在末尾要求确认 |
| vertu skill update [--agent --dir --skill-key --dry-run --no-include-new --no-prune] | 跨 Agent 同步:默认扫描所有 builtin agent + ~/.vertu/known-dirs.json 中所有自定义目录,比对远端 sha256,自动重写漂移、安装远端新增、prune 远端已删除 |
| vertu skill uninstall <skill_key> [--agent ...] [--dir ...] | 从一个/若干/全部目标移除(默认与 update 同源:含全部 builtin agent + 全部已登记的自定义目录) |
| vertu skill known-dirs list / add <path> / forget <path> / scrub | 维护 ~/.vertu/known-dirs.json 注册表(普通流程不需要直接调用,install/uninstall 会自动维护) |
文件下载与大数据落盘
| 命令 | 说明 |
|------|------|
| vertu attachment download <id> [--out PATH] | 下载任意 Odoo ir.attachment(复用 CLI session/Bearer,权限与 /web/content 一致),--out 缺省用服务器文件名落到当前目录 |
| vertu export get <id> [--out PATH] | 取回沙箱 ai.export(...) 生成的临时导出文件(TTL 24h、默认 20MB 上限、仅创建人可取);服务端会返回 X-Content-Sha256,CLI 自动校验 |
| vertu odoo data sandbox ... --out PATH | 把沙箱完整 JSON 结果直接写盘(自动带上 output=file,跳过内联 10 万字符截断),适合不生成附件的大结果快照 |
沙箱内生成 Excel/Word/CSV/Markdown:脚本调用 ai.export(rows, filename='report.xlsx')(支持 json/csv/xlsx/docx/markdown/txt),execution.export 返回 {id, name, format, size, sha256, expires_at} 小句柄,再用 vertu export get --id <id> --out ./report.xlsx 落盘。详细契约见 Odoo 侧 ai_data_retrieval 技能文档。
--agent 接受逗号或空格分隔的 id:cursor / claude / claude-code / trae(国际版) / trae-cn(国内版) / openclaw(别名 open-claw / claw) / hermes(别名 hermes-agent / nous),或 all。
--dir <path...>(v0.1.5+)支持把同一份 skill 安装/更新/卸载到任意自定义目录,专门用于尚未在 builtin agent 列表里的「未知 Agent」。每个 <path> 会被展开成 <path>/<skill_key>/SKILL.md 并写一份 .vertu-manifest.json,所以后续 vertu skill update 可以同步该目录。要点:
- 支持
~展开(~/Library/AgentX/skills)与相对路径(按当前 shell 的 cwd 解析) - 多路径用空格或逗号分隔:
--dir ./skills ./agents/foo/skills或--dir "./a,./b" - 与
--agent可混用;同一物理路径只会写一次(按绝对路径去重) - 与 builtin adapter 不同的是:dir adapter 不会改写 frontmatter(不加
disable-model-invocation、不加allowed-tools),仅保证name字段存在 —— 让目标 Agent 自己解释 frontmatter install --dir <path>会自动把绝对路径登记到~/.vertu/known-dirs.json,让后续不带任何参数的vertu skill update与vertu skill uninstall也会自动覆盖这些目录(详见下一节)
# 安装到自定义目录(首次会自动建目录 + 登记到 known-dirs)
vertu skill install hello-world --dir ~/work/my-agent/skills
# 同时装到 cursor + 一个本地原型 agent
vertu skill install --all --agent cursor --dir ./prototype-agent/skills
# 不带任何参数即可同步全部目标(含 known-dirs 中的自定义目录)
vertu skill update
# 仅同步指定目录(跳过 known-dirs 自动发现)
vertu skill update --dir ~/work/my-agent/skills
# 仅同步特定 builtin agent(跳过 known-dirs 自动发现)
vertu skill update --agent cursor
# 移除(不带参数同样会扫到 known-dirs)
vertu skill uninstall hello-world自动发现自定义目录:~/.vertu/known-dirs.json
为了让用户无需在每次 update 都重复 --dir <path>,CLI 维护一份全局注册表 ~/.vertu/known-dirs.json:
{
"version": 1,
"dirs": [
{
"path": "C:\\work\\my-agent\\skills",
"registeredAt": "2026-05-08T...",
"lastUsedAt": "2026-05-08T..."
}
]
}生命周期完全自动:
install --dir <path>成功后把绝对路径写入注册表(已存在则刷新lastUsedAt)update/uninstall在「未传--agent也未传--dir」时,把注册表内全部还活着的目录展开为dir:<abs>adapter 一并参与同步;扫描前会自动 scrub 掉两类过期条目:- 目录已被手动删掉(
stat返回 ENOENT) - 目录还在,但
.vertu-manifest.json已空(最后一条 skill 已被 uninstall)
- 目录已被手动删掉(
uninstall <skill>把某 dir 的最后一条 skill 移除后,会主动从注册表中删除该路径
「显式优先」:一旦传了 --agent 或 --dir,不会再去展开 known-dirs,遵循 commander 一贯的"用户给了就尊重"语义。
如果需要手动管理注册表,使用 vertu skill known-dirs 子命令簇:
vertu skill known-dirs list # 看现在 update 会自动覆盖哪些目录
vertu skill known-dirs add ~/foo/skills # 把已经手动拷过 skill 文件的目录纳入自动同步
vertu skill known-dirs forget ~/foo/skills # 从注册表移除(不删盘上文件)
vertu skill known-dirs scrub # 主动 scrub 一遍vertu skill install 的交互规则(v0.1.4+):
- TTY 下「skill 维度」与「agent 维度」中任意一个没有显式给出就会进入对应的 inquirer 多选面板(
<space>切换、<a>全选、<i>反选、<enter>确认)。 - 多选 Agent 默认会勾选「该 Agent 的 manifest 已经至少装过一个 skill」的本机已有 Agent;首次使用则全部预选。
- 进入过任一交互面板后,末尾默认还会有一次预览 +
Confirm?,加-y/--yes跳过。 - CI 或纯命令行场景:完整给出
<skill_keys>/--all与--agent即可绕过所有交互;想强制非交互(让缺参数直接报错而不是 prompt)可加--no-interactive。
跨 Agent 同步是怎么工作的
vertu skill install 时会在每个目标 Agent 的根目录写一份 .vertu-manifest.json,结构:
{
"version": 1,
"entries": {
"hello-world": {
"skillKey": "hello-world",
"baseUrl": "https://erp.example.com",
"installedAt": "2026-...",
"skillMdSha256": "ab12...",
"files": [
{ "path": "SKILL.md", "sha256": "ab12..." },
{ "path": "reference.md", "sha256": "cd34..." }
],
"sourceRoute": "/ai/skills/v1/skill_file"
}
}
}vertu skill update 流程:
- 扫描各 Adapter 的
.vertu-manifest.json(Cursor / Claude Code / Trae 国际版 / Trae 国内版 / OpenClaw / Hermes)→ 汇总(agent, skill_key) → installed_sha256,并记下「该 Agent 是否已经至少装过任意 skill」 - 使用当前登录项一次性
POST /ai/skills/v1/catalog取该账号可见的content_sha256与全量skill_key列表;catalog 和所需 bundle 全部成功获取后才修改 Agent 目录 - 计算并打印对齐表:
AGENT SKILL_KEY LOCAL_SHA REMOTE_SHA ACTION(skip|update|install|prune|orphan)install:远端有但本地未装;默认自动补装到「已经在用此 Agent」(manifest 非空)的目录,避免给从未碰过的 Agent 创建文件夹。--no-include-new关掉这一类prune:本地装过但远端已不存在;默认物理删除目录 + 移除 manifest 条目。--no-prune关掉后会以orphan仅展示不动手
- 非
--dry-run时,对update/install复用当前登录项专属缓存~/.vertu/skills-cache/<login_id>/<endpoint>/<skill_key>/后分发到各 Agent;对prune只清理.vertu-manifest.json管理的目录,绝不删除未受管目录。切换账号本身不会改写 Agent 技能目录,需显式执行vertu skill update
凭据与缓存路径
| 类型 | 默认位置 |
|------|----------|
| 钥匙串条目 | service=vertu-cli,account=session:v2:<login_id>:<endpoint> / bearer:v2:... / password:v2:... |
| 配置文件(钥匙串不可用时) | %USERPROFILE%\.vertu\config.json(Windows) / ~/.vertu/config.json(macOS/Linux),mode 0600 |
| Capabilities 缓存 | ~/.vertu/capabilities.<login_id>.<endpoint>.json,按登录项隔离,TTL 1h |
| Skill 文件缓存 | ~/.vertu/skills-cache/<login_id>/<endpoint>/<skill_key>/ |
| Cursor Skill 安装位置 | ~/.cursor/skills/<skill_key>/ |
| Claude Code Skill 安装位置 | ~/.claude/skills/<skill_key>/ |
| Trae 国际版 Skill 安装位置 | ~/.trae/skills/<skill_key>/ |
| Trae 国内版 Skill 安装位置 | ~/.trae-cn/skills/<skill_key>/ |
| OpenClaw Skill 安装位置 | ~/.openclaw/skills/<skill_key>/ |
| Hermes Skill 安装位置 | ~/.hermes/skills/<skill_key>/ |
| 自定义目录(--dir) | <--dir 给的绝对路径>/<skill_key>/,每个目录一份独立 .vertu-manifest.json |
| 已注册自定义目录的全局注册表 | ~/.vertu/known-dirs.json(v0.1.5+,install --dir 自动写入,update / uninstall 默认扫描) |
环境变量
| 变量 | 说明 |
|------|------|
| VERTU_BASE_URL | 已保存登录项的 URL 筛选器。运行上下文优先级:显式 --base-url > VERTU_BASE_URL > 最近的 worktree/目录绑定 > 全局当前登录项;同 URL 有多个 DB/login 且无法由当前项消歧时会要求先 vertu switch |
| VERTU_BOT_INBOUND_KEY | Bot Bearer token;设了就走 Bearer 模式(CI 推荐) |
| VERTU_DISABLE_KEYTAR=1 | 强制走文件存储(CI/容器/裸机调试用) |
| VERTU_DEBUG=1 | 打印 dynamic capabilities 加载失败的原因 |
常见问题
1. npm install 报 keytar 编译失败?
keytar 是可选依赖。设 VERTU_DISABLE_KEYTAR=1 或 npm install --no-optional,CLI 会自动降级到文件存储。Windows 上若需要 keytar,先装 windows-build-tools(或者直接用文件模式,零依赖)。
2. 我能用 user session 调 /ai/skills/v1/* 吗?
本 CLI 始终用同一个 session/Bearer 头,对 CLI 网关与 Skills 网关无差别处理。前提是 Odoo 侧的 skills_gateway.py 已改造成统一认证(你已确认会做此改造)。
3. 同一台机器同时装了 Trae 国际版和国内版会冲突吗?
不会,但两版互不共享 SKILL 目录:国际版读写 ~/.trae/skills/,国内版读写 ~/.trae-cn/skills/。vertu skill install --all 默认会同时写入两份;如果只装了其中一版,可用 --agent trae 或 --agent trae-cn 限定。
4. 怎么把 SKILL.md 提供给项目级的 .cursor/rules/*.mdc 或 AGENTS.md?
当前版本只覆盖用户级目录。如需项目级或者 AGENTS.md 整合,请提 issue 说明使用场景,会作为后续 adapter 类型加入。
开发
npm install # 含 keytar(可选)
npm run dev -- --help
npm run typecheck
npm test
npm run build测试用 vitest,覆盖:JSON-RPC 客户端(用 undici MockAgent)、Schema → Commander Option、Frontmatter 解析/改写、Adapter 写入、跨 Adapter manifest 扫描。
