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