add-command · git:20260817.d00a470 · 2026-08-17 · sha256 2441f8e8cd9f2076
add-command git:20260817.d00a470A
Immutable. This exact content is served forever at /api/v1/blob/2441f8e8cd9f2076.
---
name: add-command
description: 给前端加一个 Tauri 命令时用。要改五处,漏了其中三处只表现为运行时「command not found」;权限文件还有个先后顺序的坑会让 build.rs 直接 panic。
---
# 加一个 Tauri 命令
一个命令要在**五处**同时存在。`src-tauri/tests/acl.rs` 只守得住两处,
另外三处漏了不报编译错,表现是前端调用时 `<name> not allowed. Command not found`。
## 五处清单
**1. 命令本体** —— `src-tauri/src/lib.rs`
```rust
#[tauri::command]
async fn term_share(
terms: tauri::State<'_, term::Terminals>,
id: u32,
shared: bool,
) -> HostResult<()> {
terms.set_shared(id, shared);
Ok(())
}
```
返回 `HostResult<T>`,错误用 `HostError` 的现成变体(`HostError::Term`、
`HostError::Provider`、`HostError::Hook`…)。参数名用 snake_case,前端那边
传 camelCase,Tauri 自己转。
**2. 注册** —— `lib.rs` 的 `invoke_handler` 列表
`[约束]` `invoke_handler` 只能调用一次,调多次只有最后一次生效。所以是往
那个已有的列表里加一行,不是再写一个 `.invoke_handler(...)`。
**3. 声明存在** —— `src-tauri/build.rs` 的 `COMMANDS`
不加这里,自定义命令**默认对所有 window/webview 开放**,不受 capability
约束。那意味着将来加一个 OAuth window 或 devtools window,它自动拥有全部
命令权限。
**4. 授予可用** —— `src-tauri/capabilities/default.json` 的 `permissions`
形式是 `allow-<kebab-case>`,例:`term_share` → `"allow-term-share"`。
声明「存在」和授予「可用」是两件事,所以要改两处。
**5. 前端入口** —— `src/bridge/index.ts`
`[约束]` 这是**唯一**允许调 Tauri API 的地方。别处直接 `import
@tauri-apps/api` 会让前端无法脱离 Tauri 运行,组件测试全部失效。
## 那个会让 build.rs panic 的坑
capability 里写了 `allow-term-share`,但 `src-tauri/permissions/autogenerated/`
下还没有对应的 `term_share.toml` 时,`tauri-build` 会直接 panic:
```
Permission allow-term-share not found, expected one of allow-add-project, ...
```
那些 toml 是 tauri-build 生成的,但**生成和校验在同一次构建里**,所以第一次
加命令会撞上这个先后顺序。照现有文件的格式手写一个即可:
```toml
# Automatically generated - DO NOT EDIT!
[[permission]]
identifier = "allow-term-share"
description = "Enables the term_share command without any pre-configured scope."
commands.allow = ["term_share"]
[[permission]]
identifier = "deny-term-share"
description = "Denies the term_share command without any pre-configured scope."
commands.deny = ["term_share"]
```
## 一条边界
面板里的操作是**用户自己**在敲,不过权限链 —— 权限管的是「模型能不能做」。
但反过来要小心:如果这个命令会放宽模型的能力(例 `term_share` 让模型能读
一个终端),那么**模型侧不能有对应的接口**。它不能给自己开权限,这要靠
trait 上没有那个方法来保证,不是靠提示词劝。
## 验证
```bash
cargo test -p riot-host --test acl
```
两个用例:`注册的命令都在_build_rs_里声明了`、`放行的权限都对应真实存在的命令`。
再跑 `pnpm typecheck` 确认 bridge 那侧的类型对得上。