---
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 那侧的类型对得上。
