Skip to content
Web apps

Tauri 2 guard for Tauri 1 imports, config and Rust APIs

Stop agents writing @tauri-apps/api/tauri, appWindow, the allowlist, get_window and emit_all into Tauri 2 apps, and catch the snake_case invoke arguments Tauri 2 rejects.

Verified install Not agent-tested v1.0.0 MIT updated tauritypescriptrust
Equips
  • 2 2 Rules
  • 1 1 Skills
  • – 0 MCP

Tauri 2 has been out since October 2024, but much of what agents learnt is Tauri 1. Tauri 2 moved invoke to @tauri-apps/api/core, replaced appWindow, split fs, dialog, shell and http into plugins, replaced the allowlist with capabilities, renamed tauri.conf.json keys (tauri to app, distDir to frontendDist, devPath to devUrl) and renamed core Rust APIs (get_window, emit_all, SystemTray, WindowBuilder). It also expects command arguments in camelCase from JavaScript, which catches out new Tauri 2 code too.

This loadout pairs a Cursor rule and AGENTS.md (a never/always table covering the front end, the config and Rust) with a skill that checks the work. A standard-library Python scanner reports Tauri 1 code as errors across .ts/.js, .rs, tauri.conf.json, Cargo.toml and package.json. Then tsc checks the front end, a Node test mocks the IPC to pin the argument names, and the config is validated against the schema shipped in the Tauri CLI. The verifier runs all of this on a Tauri 2 notes app and a Tauri 1 sample, and checks the Rust names against the published tauri 2.12.5 crate source. For least-privilege capabilities, pair it with the Tauri 2 desktop app loadout.

What's in it

  • Never/always table for the front end, tauri.conf.json and Rust
  • Scanner flags Tauri 1 imports, config keys and Rust APIs as errors
  • Warns on snake_case invoke arguments, which Tauri 2 doesn't map
  • A Node test with @tauri-apps/api/mocks pins the camelCase argument names
  • Validates tauri.conf.json against the schema shipped in the Tauri CLI

You'll need

  • Tools: node, python3
Level 1 · automated

Verification

Verified install

Checked at commit ba77c20 · 9 passed, 0 failed, 1 not applicable

CheckResultWhat we found
Listinglisting.yaml matches the schema and declares every file in the folder. Pass
Schema valid; 11 files declared, none missing or extra
  • tested.status: untested (agent tests are separate from Level 1)
Files parseJSON parses; SKILL.md and .mdc front matter is valid YAML with the fields each agent needs; code fences are closed. Pass All 11 files parse; front matter valid
AdaptersCLAUDE.md imports @AGENTS.md, GEMINI.md imports @./AGENTS.md, and both match the generator. Pass
CLAUDE.md and GEMINI.md import AGENTS.md and are up to date
  • CLAUDE.md: matches the generator
  • GEMINI.md: matches the generator
Install pathsEvery file lands where each agent documents it, with no collisions, and the laid-out project is re-discovered correctly. Pass
Correct paths for all 6 agents; layouts re-discovered
  • Claude Code: 9 files, 1 skill in .claude/skills/
  • Codex CLI: 8 files, 1 skill in .agents/skills/
  • Cursor: 9 files, 1 skill in .agents/skills/, 1 rule
  • Gemini CLI: 9 files, 1 skill in .agents/skills/
  • Antigravity CLI: 8 files, 1 skill in .agents/skills/
  • GitHub Copilot: 8 files, 1 skill in .agents/skills/
MCP configmcp.json is structurally valid, converts to each agent's format, declares no literal secrets, and referenced packages exist on npm/PyPI. Not applicable No MCP servers in this loadout
Safety scanThe intake scanner finds no credentials, risky shell, hidden Unicode, instructions hidden in HTML comments or after long blank padding, or prompt-injection and covert wording (such as telling the agent to keep something from you), and the listing's network flag is honest. Pass
Clean: no credentials, risky shell, hidden text or covert instructions
  • Scanner intake-precheck/2: verdict clean; network no (listing says yes)
ReferencesLinks and file paths between the loadout's files resolve, including for each agent's installed layout. Pass 3 references between files resolve for every agent
Size limitsFile count and sizes are within the submission limits; SKILL.md bodies stay under 500 lines. Pass
11 files, 22.6 KB total; largest skills/tauri-2-guard/scripts/tauri2_guard.py 8.4 KB
  • Limits: 30 files, 32 KB per file, 128 KB total, 500 lines per skill or rule
LicenceA recognised SPDX licence, with the licence text available. Pass MIT, by SetupBay; text in the repository LICENSE
Starter buildA minimal fixture project with the loadout applied installs and builds or type-checks, and the loadout's own code examples compile. Pass
tauri2_guard.py passes a Tauri 2 notes app (front end, lib.rs, main.rs, tauri.conf.json, Cargo.toml) and flags every Tauri 1 line in the skill's four sample files: 25 errors and 3 warnings, including the snake_case invoke argument. With the pinned @tauri-apps packages, tsc passes the front end, a no
  • Applied 18 loadout files at every agent's paths
  • ✓ scanner: the Tauri 2 app is clean: bash -c python3 .agents/skills/tauri-2-guard/scripts/tauri2_guard.py src src-tauri package.json; echo "exit=$?"
  • ✓ scanner: the Tauri 1 sample is flagged line by line: bash -c R=.claude/skills/tauri-2-guard/references/tauri1-sample; mkdir -p tauri1/src tauri1/src-tauri/src && cp $R/main.ts.txt tauri1/src/main.ts && cp $R/main.rs.txt tauri1/src-tauri/src/main.rs && cp $R/tauri.conf.json.txt tauri1/src-tauri/tauri.conf.json && cp $R/Cargo.toml.txt tauri1/src-tauri/Cargo.toml && python3 .claude/skills/tauri-2-guard/scripts/tauri2_guard.py tauri1; echo "exit=$?"
  • ✓ install @tauri-apps/api 2.12.4, the plugins, the CLI, Ajv and TypeScript (exact pins): npm ci --no-audit --no-fund
  • ✓ tsc type-checks the front end against the Tauri 2 packages: bash -c node_modules/.bin/tsc -p tsconfig.json; echo "tsc=$?"
  • ✓ node test with mockIPC: camelCase arguments reach the command: node --test test/notes.test.js
  • ✓ the CLI's config schema accepts the Tauri 2 config and rejects the Tauri 1 keys: bash -c node validate_conf.mjs src-tauri/tauri.conf.json; echo "v2=$?"; node validate_conf.mjs tauri1/src-tauri/tauri.conf.json; echo "v1=$?"
  • ✓ the published tauri 2.12.5 crate: the Tauri 1 Rust APIs are gone: bash -c curl -fsSL https://static.crates.io/crates/tauri/tauri-2.12.5.crate -o tauri.crate && python3 crate_probe.py tauri.crate
  • Covers: The scanner's front-end, config, Rust and manifest rules against a real Tauri 1 app, and a clean Tauri 2 app
  • Covers: The front end type-checks against @tauri-apps/api 2.12.4 and the plugin packages, and a mocked-IPC test pins the camelCase argument names
  • Covers: tauri.conf.json against the schema shipped in @tauri-apps/cli 2.12.4
  • Covers: Not covered: compiling the Rust side (that needs the platform WebView libraries); the crate source check confirms the Rust names instead

What this means: a script checked the files themselves: they parse, they install where each agent looks, any MCP config is sound, the safety scan is clean, references between files resolve, and, where a free and repeatable fixture exists, a small starter project builds with the loadout applied. No AI agent was run. Per-agent results from agent runs are separate and stay untested until an agent actually runs.

Install

Choose your agent. Run the commands from your project root, or download a zip already laid out in the right folders.

Claude Code reads CLAUDE.md (imports AGENTS.md with @AGENTS.md) and skills from .claude/skills/.

Download zip
curl -fsSL --create-dirs -o AGENTS.md https://setupbay.com/raw/tauri-2-api-guard/1.0.0/AGENTS.md
curl -fsSL --create-dirs -o CLAUDE.md https://setupbay.com/raw/tauri-2-api-guard/1.0.0/CLAUDE.md
curl -fsSL --create-dirs -o .claude/skills/tauri-2-guard/SKILL.md https://setupbay.com/raw/tauri-2-api-guard/1.0.0/skills/tauri-2-guard/SKILL.md
curl -fsSL --create-dirs -o .claude/skills/tauri-2-guard/scripts/tauri2_guard.py https://setupbay.com/raw/tauri-2-api-guard/1.0.0/skills/tauri-2-guard/scripts/tauri2_guard.py
curl -fsSL --create-dirs -o .claude/skills/tauri-2-guard/references/v1-to-v2.md https://setupbay.com/raw/tauri-2-api-guard/1.0.0/skills/tauri-2-guard/references/v1-to-v2.md
curl -fsSL --create-dirs -o .claude/skills/tauri-2-guard/references/tauri1-sample/main.ts.txt https://setupbay.com/raw/tauri-2-api-guard/1.0.0/skills/tauri-2-guard/references/tauri1-sample/main.ts.txt
curl -fsSL --create-dirs -o .claude/skills/tauri-2-guard/references/tauri1-sample/main.rs.txt https://setupbay.com/raw/tauri-2-api-guard/1.0.0/skills/tauri-2-guard/references/tauri1-sample/main.rs.txt
curl -fsSL --create-dirs -o .claude/skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt https://setupbay.com/raw/tauri-2-api-guard/1.0.0/skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt
curl -fsSL --create-dirs -o .claude/skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt https://setupbay.com/raw/tauri-2-api-guard/1.0.0/skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt
FileGoes to
AGENTS.mdAGENTS.md
CLAUDE.mdCLAUDE.md
skills/tauri-2-guard/SKILL.md.claude/skills/tauri-2-guard/SKILL.md
skills/tauri-2-guard/scripts/tauri2_guard.py.claude/skills/tauri-2-guard/scripts/tauri2_guard.py
skills/tauri-2-guard/references/v1-to-v2.md.claude/skills/tauri-2-guard/references/v1-to-v2.md
skills/tauri-2-guard/references/tauri1-sample/main.ts.txt.claude/skills/tauri-2-guard/references/tauri1-sample/main.ts.txt
skills/tauri-2-guard/references/tauri1-sample/main.rs.txt.claude/skills/tauri-2-guard/references/tauri1-sample/main.rs.txt
skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt.claude/skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt
skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt.claude/skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt

Already have an AGENTS.md or CLAUDE.md? The commands above overwrite it — download the file instead and merge the sections you want. Which file wins when several are present: what Claude Code reads.

Or as a Claude Code plugin

Adds the skill to Claude Code for all your projects, without copying files. Plugins can't carry project rules, so add AGENTS.md and CLAUDE.md with the commands above. About the plugin marketplace.

/plugin marketplace add https://setupbay.com/marketplace.json
/plugin install tauri-2-api-guard@setupbay

Files

Rules AGENTS.md Tauri 2 version facts, rules and the done checklist. 40 lines
Download Raw
# AGENTS.md: Tauri 2 app (Tauri 1 API guard)

This desktop app runs **Tauri 2** (`tauri` crate 2.12, `@tauri-apps/api` 2.12). Tauri 2 came out in October 2024,
but much of what you learnt is Tauri 1: `@tauri-apps/api/tauri`, `appWindow`, `@tauri-apps/api/fs`, the
`allowlist`, `get_window`, `emit_all` and `SystemTray`. None of those work in Tauri 2.

## Version facts
- Check the pins first: `src-tauri/Cargo.toml` (`tauri = "2..."`) and `package.json` (`@tauri-apps/api` 2.x).
  If either says 1.x, stop and ask; don't mix the two.
- Front end: `invoke` comes from `@tauri-apps/api/core`. The current window is `getCurrentWebviewWindow()` from
  `@tauri-apps/api/webviewWindow`. fs, dialog, shell, http, clipboard, notification, os, process and the updater are
  plugins: a JS package `@tauri-apps/plugin-<name>`, a Rust crate `tauri-plugin-<name>` registered with
  `.plugin(...)`, and a permission in `src-tauri/capabilities/`.
- Opening URLs and files is `openUrl` / `openPath` from `@tauri-apps/plugin-opener`, not shell `open`.
- **Command arguments are camelCase from JS.** A Rust `fn save_note(note_title: String)` is called with
  `invoke("save_note", { noteTitle })`. Sending `note_title` fails with a missing-argument error. (Return values and
  struct fields keep their serde names.)
- Config: there's no `tauri` or `package` key. `productName`, `version` and `identifier` are top level, the window
  list and security settings live under `app`, `build.devPath` is `build.devUrl` and `build.distDir` is
  `build.frontendDist`. Validate against the schema in `node_modules/@tauri-apps/cli/config.schema.json`.
- Rust: `get_webview_window`, `emit` (the `Emitter` trait), `app.path()`, `tauri::tray::TrayIconBuilder`,
  `tauri::menu::MenuBuilder`, `WebviewWindowBuilder`. The builder goes in `lib.rs` as `pub fn run()` with
  `#[cfg_attr(mobile, tauri::mobile_entry_point)]`, and `main.rs` only calls it.
- Docs for this version: https://v2.tauri.app/ (the migration guide is
  https://v2.tauri.app/start/migrate/from-tauri-1/). Ignore tauri.app/v1 pages.

## Rules
- Write Tauri 2 code only. The `tauri-2-guard` skill has the full table.
- Every plugin needs all three parts: the npm package, the crate plus `.plugin(...)`, and the narrowest permission
  that works. Never add `"fs:default"`-style wide grants to make an error go away; ask.
- Keep `app.security.csp`. Don't turn on `withGlobalTauri` to use `window.__TAURI__`; import from `@tauri-apps/api`.
- Don't change the Tauri version without asking.

## Before you say "done"
1. Run the `tauri-2-guard` scanner on `src`, `src-tauri` and `package.json`. It must report `0 error(s)`.
2. Type-check the front end (`npx tsc --noEmit`) and run its tests, mocking the IPC with `@tauri-apps/api/mocks`.
3. Validate `src-tauri/tauri.conf.json` against the CLI's config schema.
4. Run `cargo check --manifest-path src-tauri/Cargo.toml` where the system has the WebView libraries, and
   `npm run tauri dev` if you changed windows, menus or the tray.
Adapter CLAUDE.md Imports AGENTS.md for Claude Code. 11 lines
Download Raw
# CLAUDE.md

@AGENTS.md

## Claude Code notes

- Skills from this loadout live in `.claude/skills/`: `tauri-2-guard`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From SetupBay "Tauri 2 guard for Tauri 1 imports, config and Rust APIs" v1.0.0 — https://setupbay.com/loadouts/tauri-2-api-guard/ -->
Adapter GEMINI.md Imports AGENTS.md for Gemini CLI. 11 lines
Download Raw
# GEMINI.md

@./AGENTS.md

## Gemini CLI notes

- Skills from this loadout live in `.agents/skills/`: `tauri-2-guard`. Use them when their description matches the task.
- Follow AGENTS.md above; where this file and AGENTS.md disagree, AGENTS.md wins.

<!-- From SetupBay "Tauri 2 guard for Tauri 1 imports, config and Rust APIs" v1.0.0 — https://setupbay.com/loadouts/tauri-2-api-guard/ -->
Cursor rule .cursor/rules/tauri-2-only.mdc Never/always table for the front end, Rust and tauri.conf.json. 25 lines
Download Raw
---
description: Tauri 2 only; no Tauri 1 imports, Rust APIs or config keys
globs: "src/**/*.ts,src/**/*.js,src/**/*.tsx,src/**/*.vue,src/**/*.svelte,src-tauri/**/*.rs,src-tauri/tauri.conf.json"
alwaysApply: false
---

This app uses Tauri 2. Never write Tauri 1 code.

| Never (Tauri 1) | Always (Tauri 2) |
|---|---|
| `import { invoke } from "@tauri-apps/api/tauri"` | `import { invoke } from "@tauri-apps/api/core"` |
| `appWindow.setTitle(...)` | `getCurrentWebviewWindow().setTitle(...)` (`@tauri-apps/api/webviewWindow`) |
| `@tauri-apps/api/fs`, `/dialog`, `/shell`, `/http` | `@tauri-apps/plugin-fs`, `-dialog`, `-shell`, `-http` (+ crate + permission) |
| shell `open(url)` | `openUrl(url)` from `@tauri-apps/plugin-opener` |
| `invoke("save_note", { note_title })` | `invoke("save_note", { noteTitle })` (camelCase) |
| `"allowlist"` | `src-tauri/capabilities/*.json` permissions |
| `"tauri": {...}`, `"package": {...}` | `"app": {...}`; top-level `productName`, `version`, `identifier` |
| `build.devPath`, `build.distDir` | `build.devUrl`, `build.frontendDist` |
| `app.get_window("main")` | `app.get_webview_window("main")` |
| `window.emit_all(...)` | `window.emit(...)` (`use tauri::Emitter`) |
| `SystemTray`, `CustomMenuItem`, `WindowBuilder` | `TrayIconBuilder`, `MenuItemBuilder`, `WebviewWindowBuilder` |
| `app.path_resolver()`, `tauri::api::*` | `app.path()`, the plugin crates |

- After editing, run the `tauri-2-guard` skill's scanner, the type-check and the config schema check.
Skill skills/tauri-2-guard/SKILL.md Scan for Tauri 1 code, then type-check, test with mocked IPC and validate the config schema. 53 lines
Download Raw
---
name: tauri-2-guard
description: Check a Tauri 2 app for Tauri 1 code in the front end, the Rust side and tauri.conf.json (api/tauri imports, appWindow, the allowlist, get_window, emit_all, SystemTray) and for snake_case invoke arguments, then type-check, test with mocked IPC and validate the config schema. Use after writing or editing Tauri code, when invoke reports a missing argument, or when porting a Tauri 1 example.
---

# Tauri 2 guard

Most Tauri examples online are Tauri 1, and Tauri 2 changed the import paths, moved fs/dialog/shell/http into
plugins, replaced the allowlist with capabilities, renamed config keys and renamed core Rust APIs. One change bites
even in pure Tauri 2 code: JS must send command arguments in camelCase.

## 1. Confirm the version
- `src-tauri/Cargo.toml` must say `tauri = "2..."` and `package.json` `@tauri-apps/api` 2.x. If not, stop and ask.

## 2. Scan
```bash
python3 .agents/skills/tauri-2-guard/scripts/tauri2_guard.py src src-tauri package.json
```
(Use the path the skill was installed to: `.claude/skills/...` for Claude Code, `.agents/skills/...` for the others.)

- Each `error` is Tauri 1 code. Fix it with the replacement on the line, or look it up in `references/v1-to-v2.md`.
- `warn [invoke-snake-args]`: rename the keys to camelCase, unless the Rust command has
  `#[tauri::command(rename_all = "snake_case")]`. Then add `// guard: ok rename_all` to the line.
- `warn [global-tauri]`: import from `@tauri-apps/api` instead of using `window.__TAURI__`.
- When a plugin import is reported, add all three parts: the npm package, the crate plus `.plugin(...)`, and a
  narrow permission in `src-tauri/capabilities/`.

## 3. Check the front end
```bash
npx tsc --noEmit
```
Test calls to Rust without running the app, using the mocks that ship with `@tauri-apps/api`:
```js
globalThis.window ??= globalThis; // only when the test runs in Node
const { mockIPC, mockWindows } = await import("@tauri-apps/api/mocks");
mockWindows("main");
mockIPC((cmd, args) => (cmd === "save_note" ? `saved ${args.noteTitle}` : null));
```
Assert on the command name and the argument keys, so a snake_case key fails the test.

## 4. Validate the config
`node_modules/@tauri-apps/cli/config.schema.json` is the schema for this exact CLI version, and it rejects unknown
keys. Validate `src-tauri/tauri.conf.json` against it with any JSON Schema (draft-07) validator. With Ajv, pass
`unicodeRegExp: false`, because the schema has a pattern that isn't valid as a Unicode regex.

## 5. Check the Rust side
`cargo check --manifest-path src-tauri/Cargo.toml` needs the platform WebView libraries (on Linux, webkit2gtk-4.1).
Where they aren't installed, say so in your report rather than claiming the Rust side compiles.

## 6. Report
Say which Tauri 1 calls and keys you replaced, which plugins and permissions you added, and which checks ran
(scanner, tsc, tests, schema, cargo check).
Skill file skills/tauri-2-guard/scripts/tauri2_guard.py Read-only scanner for Tauri 1 imports, config keys, Rust APIs and snake_case invoke arguments. 160 lines
Download Raw
#!/usr/bin/env python3
"""Find Tauri 1 APIs, imports and config keys in a Tauri 2 app (front end, src-tauri Rust, tauri.conf.json, manifests).

Usage: python3 tauri2_guard.py [PATH ...]        (default: the current folder)
       python3 tauri2_guard.py --list            (print every rule)

A "guard: ok" comment on a line silences its warnings (never its errors).
Prints one line per finding:  path:line: level [rule] what is wrong -> what to use
Exit 0 when nothing at "error" level is found, 1 when something is, 2 on bad usage.
Standard library only. Read-only: it never edits a file and makes no network calls.
"""
import fnmatch
import os
import re
import sys

# (rule id, level, regex, message, file patterns). Patterns run against each line with comments removed.
RULES = [
    ('api-tauri-module', 'error', '[\\"\']@tauri-apps/api/tauri[\\"\']',
     '@tauri-apps/api/tauri was renamed -> @tauri-apps/api/core (invoke, convertFileSrc)', ['*.ts', '*.tsx', '*.js', '*.mjs', '*.jsx', '*.vue', '*.svelte']),
    ('api-plugin-module', 'error', '[\\"\']@tauri-apps/api/(fs|dialog|shell|http|clipboard|notification|globalShortcut|os|process|updater|cli)[\\"\']',
     'this module moved to a plugin in Tauri 2 -> @tauri-apps/plugin-<name> plus the Rust crate tauri-plugin-<name> and a capability permission', ['*.ts', '*.tsx', '*.js', '*.mjs', '*.jsx', '*.vue', '*.svelte']),
    ('app-window', 'error', '\\bappWindow\\b',
     'appWindow was removed -> getCurrentWebviewWindow() from @tauri-apps/api/webviewWindow (or getCurrentWindow() from /window)', ['*.ts', '*.tsx', '*.js', '*.mjs', '*.jsx', '*.vue', '*.svelte']),
    ('webview-window-import', 'error', 'import\\s*\\{[^}]*\\bWebviewWindow\\b[^}]*\\}\\s*from\\s*[\\"\']@tauri-apps/api/window[\\"\']',
     'WebviewWindow moved -> import it from @tauri-apps/api/webviewWindow', ['*.ts', '*.tsx', '*.js', '*.mjs', '*.jsx', '*.vue', '*.svelte']),
    ('invoke-snake-args', 'warn', 'invoke\\(\\s*[\\"\'][^\\"\']+[\\"\']\\s*,\\s*\\{[^}]*\\b[a-z0-9]+_[a-z0-9_]+\\s*:',
     'Tauri command arguments are camelCase on the JS side by default (Rust note_title -> JS noteTitle), unless the command uses rename_all = "snake_case"', ['*.ts', '*.tsx', '*.js', '*.mjs', '*.jsx', '*.vue', '*.svelte']),
    ('global-tauri', 'warn', 'window\\.__TAURI__\\b',
     'window.__TAURI__ only exists with app.withGlobalTauri: true -> import from @tauri-apps/api instead', ['*.ts', '*.tsx', '*.js', '*.mjs', '*.jsx', '*.vue', '*.svelte']),
    ('shell-open-url', 'warn', 'import\\s*\\{[^}]*\\bopen\\b[^}]*\\}\\s*from\\s*[\\"\']@tauri-apps/plugin-shell[\\"\']',
     'shell open() is deprecated for URLs and files -> openUrl / openPath from @tauri-apps/plugin-opener', ['*.ts', '*.tsx', '*.js', '*.mjs', '*.jsx', '*.vue', '*.svelte']),
    ('conf-allowlist', 'error', '\\"allowlist\\"\\s*:',
     'the allowlist was replaced by capabilities -> src-tauri/capabilities/*.json with plugin permissions', ['tauri.conf.json']),
    ('conf-tauri-key', 'error', '^\\s{0,4}\\"tauri\\"\\s*:\\s*\\{',
     'the top-level "tauri" object was renamed -> "app" (and bundle moves to the top level)', ['tauri.conf.json']),
    ('conf-dist-dir', 'error', '\\"distDir\\"\\s*:',
     'build.distDir was renamed -> build.frontendDist', ['tauri.conf.json']),
    ('conf-dev-path', 'error', '\\"devPath\\"\\s*:',
     'build.devPath was renamed -> build.devUrl', ['tauri.conf.json']),
    ('conf-package', 'error', '^\\s{0,4}\\"package\\"\\s*:\\s*\\{',
     'the "package" object was removed -> top-level productName and version', ['tauri.conf.json']),
    ('conf-system-tray', 'error', '\\"systemTray\\"\\s*:',
     'systemTray was renamed -> app.trayIcon (or build it in Rust with TrayIconBuilder)', ['tauri.conf.json']),
    ('conf-updater', 'error', '\\"updater\\"\\s*:\\s*\\{\\s*\\"active\\"',
     'the built-in updater moved to a plugin -> plugins.updater and tauri-plugin-updater', ['tauri.conf.json']),
    ('rs-get-window', 'error', '\\.get_window\\(',
     'get_window() only exists behind the unstable feature in Tauri 2 -> get_webview_window()', ['*.rs']),
    ('rs-emit-all', 'error', '\\.emit_all\\(',
     'emit_all() was removed -> emit() (to every target), or emit_to()', ['*.rs']),
    ('rs-system-tray', 'error', '\\bSystemTray(Event|Menu|MenuItem)?\\b',
     'SystemTray was replaced -> tauri::tray::TrayIconBuilder (feature tray-icon)', ['*.rs']),
    ('rs-window-builder', 'error', '\\bWindowBuilder::new\\(',
     'WindowBuilder::new was replaced -> WebviewWindowBuilder::new(app, label, WebviewUrl::App(...))', ['*.rs']),
    ('rs-custom-menu', 'error', '\\bCustomMenuItem\\b|\\bMenu::new\\(\\s*\\)',
     'the Tauri 1 menu API was replaced -> tauri::menu::{MenuBuilder, MenuItemBuilder}', ['*.rs']),
    ('rs-api-module', 'error', '\\btauri::api::',
     'tauri::api was removed -> the matching plugin crate (tauri-plugin-shell, -dialog, -fs, -http, -os, -process)', ['*.rs']),
    ('rs-path-resolver', 'error', '\\.path_resolver\\(\\)',
     'path_resolver() was removed -> app.path() (the PathResolver from the Manager trait)', ['*.rs']),
    ('rs-window-param', 'warn', 'fn\\s+\\w+\\s*\\([^)]*:\\s*tauri::Window\\b',
     'commands get a WebviewWindow in Tauri 2 -> window: tauri::WebviewWindow', ['*.rs']),
    ('cargo-tauri-1', 'error', '^\\s*tauri(-build)?\\s*=\\s*(\\{[^}]*version\\s*=\\s*)?\\"[~^=]?1\\.',
     'Cargo.toml pins Tauri 1 -> tauri = "2" and tauri-build = "2"', ['Cargo.toml']),
    ('npm-tauri-1', 'error', '\\"@tauri-apps/(api|cli)\\"\\s*:\\s*\\"[~^]?1\\.',
     'package.json pins Tauri 1 -> @tauri-apps/api and @tauri-apps/cli 2.x', ['package.json']),
]
FILES = ['*.ts', '*.tsx', '*.js', '*.mjs', '*.jsx', '*.vue', '*.svelte', '*.rs', 'tauri.conf.json', 'Cargo.toml', 'package.json']
COMMENT = '//'
SKIP_DIRS = {".git", ".venv", "venv", "node_modules", "__pycache__", ".agents", ".claude", ".cursor", "dist", "build", "target", ".godot"}
COMPILED = [(rid, lvl, re.compile(rx), msg, globs) for rid, lvl, rx, msg, globs in RULES]


def strip_comment(line):
    """Drop a trailing comment, leaving string literals alone."""
    q = None
    i = 0
    while i < len(line):
        c = line[i]
        if q:
            if c == "\\":
                i += 2
                continue
            if c == q:
                q = None
        elif c in "\"'`":
            q = c
        elif line.startswith(COMMENT, i):
            return line[:i]
        i += 1
    return line


def wanted(name):
    return any(fnmatch.fnmatch(name, g) for g in FILES)


def walk(paths):
    for p in paths:
        if os.path.isfile(p):
            yield p
            continue
        for root, dirs, files in os.walk(p):
            dirs[:] = sorted(d for d in dirs if d not in SKIP_DIRS)
            for f in sorted(files):
                if wanted(f):
                    yield os.path.join(root, f)


def scan(path):
    out = []
    name = os.path.basename(path)
    try:
        with open(path, encoding="utf-8", errors="replace") as fh:
            lines = fh.read().splitlines()
    except OSError as e:
        return [(path, 0, "error", "unreadable", str(e))]
    for n, raw in enumerate(lines, 1):
        code = strip_comment(raw)
        if not code.strip():
            continue
        accepted = "guard: ok" in raw  # a person accepted the warnings on this line
        for rid, lvl, rx, msg, globs in COMPILED:
            if accepted and lvl == "warn":
                continue
            if globs and not any(fnmatch.fnmatch(name, g) for g in globs):
                continue
            if rx.search(code):
                out.append((path, n, lvl, rid, msg))
    return out


def main(argv):
    if "--list" in argv:
        for rid, lvl, _, msg, globs in RULES:
            print(f"{lvl:5} [{rid}] {msg}" + (f"  (only {', '.join(globs)})" if globs else ""))
        return 0
    if any(a.startswith("-") for a in argv):
        print(__doc__)
        return 2
    paths = argv or ["."]
    for p in paths:
        if not os.path.exists(p):
            print(f"no such path: {p}", file=sys.stderr)
            return 2
    files = list(walk(paths))
    findings = []
    for f in files:
        findings.extend(scan(f))
    for path, n, lvl, rid, msg in findings:
        print(f"{os.path.relpath(path)}:{n}: {lvl} [{rid}] {msg}")
    errors = sum(1 for x in findings if x[2] == "error")
    warns = sum(1 for x in findings if x[2] == "warn")
    print(f"tauri2_guard: {len(files)} file(s), {errors} error(s), {warns} warning(s)")
    return 1 if errors else 0


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
Skill file skills/tauri-2-guard/references/v1-to-v2.md Tauri 1 to Tauri 2 tables for the front end, the config and Rust. 45 lines
Download Raw
# Tauri 1 to Tauri 2, the code agents get wrong

From the official guide (https://v2.tauri.app/start/migrate/from-tauri-1/). Checked against `@tauri-apps/api` 2.12.4,
`@tauri-apps/cli` 2.12.4 (config schema 2.12.5) and the `tauri` 2.12.5 crate source.

## Front end
| Tauri 1 | Tauri 2 |
|---|---|
| `import { invoke, convertFileSrc } from "@tauri-apps/api/tauri"` | `from "@tauri-apps/api/core"` |
| `import { appWindow } from "@tauri-apps/api/window"` | `getCurrentWebviewWindow()` from `@tauri-apps/api/webviewWindow` (or `getCurrentWindow()` from `/window`) |
| `WebviewWindow` from `@tauri-apps/api/window` | from `@tauri-apps/api/webviewWindow` |
| `@tauri-apps/api/fs` (`readTextFile(p, { dir })`) | `@tauri-apps/plugin-fs` (`readTextFile(p, { baseDir })`) |
| `@tauri-apps/api/dialog` | `@tauri-apps/plugin-dialog` |
| `@tauri-apps/api/shell` `open(url)` | `openUrl(url)` from `@tauri-apps/plugin-opener`; `Command` from `@tauri-apps/plugin-shell` |
| `@tauri-apps/api/http` | `@tauri-apps/plugin-http` (`fetch`) |
| `clipboard`, `notification`, `globalShortcut`, `os`, `process`, `updater`, `cli` | `@tauri-apps/plugin-clipboard-manager`, `-notification`, `-global-shortcut`, `-os`, `-process`, `-updater`, `-cli` |
| `invoke("cmd", { note_title })` | `invoke("cmd", { noteTitle })`: arguments are camelCase by default |
| `window.__TAURI__.*` | imports from `@tauri-apps/api` (the global needs `app.withGlobalTauri: true`) |

## tauri.conf.json
| Tauri 1 | Tauri 2 |
|---|---|
| `package.productName`, `package.version` | top-level `productName`, `version` |
| `tauri.bundle.identifier` | top-level `identifier` |
| `tauri` | `app` (windows, security, trayIcon); `bundle` moves to the top level |
| `tauri.allowlist` | `src-tauri/capabilities/*.json` with plugin permissions |
| `build.devPath` / `build.distDir` | `build.devUrl` / `build.frontendDist` |
| `build.withGlobalTauri` | `app.withGlobalTauri` |
| `tauri.systemTray` | `app.trayIcon` |
| `tauri.updater` | `plugins.updater` and `tauri-plugin-updater` |

## Rust
| Tauri 1 | Tauri 2 |
|---|---|
| `app.get_window("main")` | `app.get_webview_window("main")` (`get_window` needs the `unstable` feature) |
| `window.emit_all(event, payload)` | `window.emit(event, payload)` with `use tauri::Emitter` |
| `window: tauri::Window` in a command | `window: tauri::WebviewWindow` |
| `app.path_resolver().app_data_dir()` (Option) | `app.path().app_data_dir()` (Result) |
| `SystemTray::new().with_menu(...)` | `tauri::tray::TrayIconBuilder::new().menu(&menu).build(app)?` (feature `tray-icon`) |
| `Menu::new().add_item(CustomMenuItem::new(id, text))` | `MenuBuilder::new(app).item(&MenuItemBuilder::with_id(id, text).build(app)?).build()?` |
| `WindowBuilder::new(app, label, WindowUrl::App(..))` | `WebviewWindowBuilder::new(app, label, WebviewUrl::App(..))` |
| `tauri::api::shell::open`, `tauri::api::dialog` | the plugin crates |
| builder in `main.rs` | `pub fn run()` in `lib.rs` with `#[cfg_attr(mobile, tauri::mobile_entry_point)]` |
| `tauri = { version = "1", features = ["shell-open", ...] }` | `tauri = "2"` plus plugin crates; allowlist features are gone |
Skill file skills/tauri-2-guard/references/tauri1-sample/main.ts.txt Tauri 1 front end for checking the guard. 24 lines
Download Raw
// Tauri 1 front end, used to check the guard. Every marked import is gone in Tauri 2.
import { invoke } from "@tauri-apps/api/tauri";
import { appWindow, WebviewWindow } from "@tauri-apps/api/window";
import { readTextFile, BaseDirectory } from "@tauri-apps/api/fs";
import { open } from "@tauri-apps/api/dialog";
import { open as openLink } from "@tauri-apps/api/shell";

export async function saveNote(title: string, body: string) {
  await invoke("save_note", { note_title: title, body });
  await appWindow.setTitle(`Notes: ${title}`);
}

export async function loadNote() {
  const path = await open({ filters: [{ name: "Notes", extensions: ["md"] }] });
  return readTextFile(path as string, { dir: BaseDirectory.AppData });
}

export function openHelp() {
  new WebviewWindow("help", { url: "help.html" });
  return openLink("https://tauri.app");
}

export const version = window.__TAURI__.app.getVersion();
Skill file skills/tauri-2-guard/references/tauri1-sample/main.rs.txt Tauri 1 main.rs for checking the guard. 27 lines
Download Raw
// Tauri 1 src-tauri/src/main.rs, used to check the guard.
use tauri::{CustomMenuItem, Manager, Menu, SystemTray, SystemTrayMenu, WindowBuilder, WindowUrl};

#[tauri::command]
fn save_note(window: tauri::Window, note_title: String, body: String) -> Result<(), String> {
    let dir = window.app_handle().path_resolver().app_data_dir().ok_or("no app data dir")?;
    std::fs::write(dir.join(format!("{note_title}.md")), body).map_err(|e| e.to_string())?;
    window.emit_all("note-saved", note_title).map_err(|e| e.to_string())
}

fn main() {
    let tray = SystemTray::new().with_menu(SystemTrayMenu::new());
    let menu = Menu::new().add_item(CustomMenuItem::new("quit", "Quit"));
    tauri::Builder::default()
        .menu(menu)
        .system_tray(tray)
        .setup(|app| {
            let main = app.get_window("main").unwrap();
            WindowBuilder::new(app, "help", WindowUrl::App("help.html".into())).build()?;
            tauri::api::shell::open(&main.shell_scope(), "https://tauri.app", None)?;
            Ok(())
        })
        .invoke_handler(tauri::generate_handler![save_note])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}
Skill file skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt Tauri 1 tauri.conf.json for checking the guard. 26 lines
Download Raw
{
  "build": {
    "beforeDevCommand": "npm run dev",
    "beforeBuildCommand": "npm run build",
    "devPath": "http://localhost:1420",
    "distDir": "../dist",
    "withGlobalTauri": true
  },
  "package": {
    "productName": "notes",
    "version": "0.1.0"
  },
  "tauri": {
    "allowlist": {
      "all": false,
      "fs": { "readFile": true, "scope": ["$APPDATA/*"] },
      "dialog": { "open": true },
      "shell": { "open": true }
    },
    "bundle": { "active": true, "identifier": "com.example.notes", "targets": "all" },
    "systemTray": { "iconPath": "icons/icon.png" },
    "updater": { "active": false },
    "windows": [{ "title": "Notes", "width": 800, "height": 600 }]
  }
}
Skill file skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt Tauri 1 Cargo.toml for checking the guard. 13 lines
Download Raw
# Tauri 1 src-tauri/Cargo.toml, used to check the guard.
[package]
name = "notes"
version = "0.1.0"
edition = "2021"

[build-dependencies]
tauri-build = { version = "1.5", features = [] }

[dependencies]
tauri = { version = "1.8", features = ["shell-open", "dialog-open", "fs-read-file", "system-tray"] }
serde = { version = "1", features = ["derive"] }

All files: source zip · Licence MIT · By SetupBay · First listed

Installing and checks

How do I install Tauri 2 guard for Tauri 1 imports, config and Rust APIs in Claude Code?

From your project root, put AGENTS.md, CLAUDE.md, skills/tauri-2-guard/SKILL.md at .claude/skills/tauri-2-guard/SKILL.md, skills/tauri-2-guard/scripts/tauri2_guard.py at .claude/skills/tauri-2-guard/scripts/tauri2_guard.py, skills/tauri-2-guard/references/v1-to-v2.md at .claude/skills/tauri-2-guard/references/v1-to-v2.md, skills/tauri-2-guard/references/tauri1-sample/main.ts.txt at .claude/skills/tauri-2-guard/references/tauri1-sample/main.ts.txt, skills/tauri-2-guard/references/tauri1-sample/main.rs.txt at .claude/skills/tauri-2-guard/references/tauri1-sample/main.rs.txt, skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt at .claude/skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt, skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt at .claude/skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt. The "Claude Code" tab on this page has the curl commands, or download https://setupbay.com/download/tauri-2-api-guard/claude-code.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Tauri 2 guard for Tauri 1 imports, config and Rust APIs in Codex CLI?

From your project root, put AGENTS.md, skills/tauri-2-guard/SKILL.md at .agents/skills/tauri-2-guard/SKILL.md, skills/tauri-2-guard/scripts/tauri2_guard.py at .agents/skills/tauri-2-guard/scripts/tauri2_guard.py, skills/tauri-2-guard/references/v1-to-v2.md at .agents/skills/tauri-2-guard/references/v1-to-v2.md, skills/tauri-2-guard/references/tauri1-sample/main.ts.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/main.ts.txt, skills/tauri-2-guard/references/tauri1-sample/main.rs.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/main.rs.txt, skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt, skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt. The "Codex CLI" tab on this page has the curl commands, or download https://setupbay.com/download/tauri-2-api-guard/codex.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Tauri 2 guard for Tauri 1 imports, config and Rust APIs in Cursor?

From your project root, put AGENTS.md, .cursor/rules/tauri-2-only.mdc, skills/tauri-2-guard/SKILL.md at .agents/skills/tauri-2-guard/SKILL.md, skills/tauri-2-guard/scripts/tauri2_guard.py at .agents/skills/tauri-2-guard/scripts/tauri2_guard.py, skills/tauri-2-guard/references/v1-to-v2.md at .agents/skills/tauri-2-guard/references/v1-to-v2.md, skills/tauri-2-guard/references/tauri1-sample/main.ts.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/main.ts.txt, skills/tauri-2-guard/references/tauri1-sample/main.rs.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/main.rs.txt, skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt, skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt. The "Cursor" tab on this page has the curl commands, or download https://setupbay.com/download/tauri-2-api-guard/cursor.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Tauri 2 guard for Tauri 1 imports, config and Rust APIs in Gemini CLI?

From your project root, put AGENTS.md, GEMINI.md, skills/tauri-2-guard/SKILL.md at .agents/skills/tauri-2-guard/SKILL.md, skills/tauri-2-guard/scripts/tauri2_guard.py at .agents/skills/tauri-2-guard/scripts/tauri2_guard.py, skills/tauri-2-guard/references/v1-to-v2.md at .agents/skills/tauri-2-guard/references/v1-to-v2.md, skills/tauri-2-guard/references/tauri1-sample/main.ts.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/main.ts.txt, skills/tauri-2-guard/references/tauri1-sample/main.rs.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/main.rs.txt, skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt, skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt. The "Gemini CLI" tab on this page has the curl commands, or download https://setupbay.com/download/tauri-2-api-guard/gemini-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Tauri 2 guard for Tauri 1 imports, config and Rust APIs in Antigravity CLI?

From your project root, put AGENTS.md, skills/tauri-2-guard/SKILL.md at .agents/skills/tauri-2-guard/SKILL.md, skills/tauri-2-guard/scripts/tauri2_guard.py at .agents/skills/tauri-2-guard/scripts/tauri2_guard.py, skills/tauri-2-guard/references/v1-to-v2.md at .agents/skills/tauri-2-guard/references/v1-to-v2.md, skills/tauri-2-guard/references/tauri1-sample/main.ts.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/main.ts.txt, skills/tauri-2-guard/references/tauri1-sample/main.rs.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/main.rs.txt, skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt, skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt. The "Antigravity CLI" tab on this page has the curl commands, or download https://setupbay.com/download/tauri-2-api-guard/antigravity-cli.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

How do I install Tauri 2 guard for Tauri 1 imports, config and Rust APIs in GitHub Copilot?

From your project root, put AGENTS.md, skills/tauri-2-guard/SKILL.md at .agents/skills/tauri-2-guard/SKILL.md, skills/tauri-2-guard/scripts/tauri2_guard.py at .agents/skills/tauri-2-guard/scripts/tauri2_guard.py, skills/tauri-2-guard/references/v1-to-v2.md at .agents/skills/tauri-2-guard/references/v1-to-v2.md, skills/tauri-2-guard/references/tauri1-sample/main.ts.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/main.ts.txt, skills/tauri-2-guard/references/tauri1-sample/main.rs.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/main.rs.txt, skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/tauri.conf.json.txt, skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt at .agents/skills/tauri-2-guard/references/tauri1-sample/Cargo.toml.txt. The "GitHub Copilot" tab on this page has the curl commands, or download https://setupbay.com/download/tauri-2-api-guard/copilot.zip, which is already laid out. Do not overwrite an existing AGENTS.md or CLAUDE.md; merge it.

What has been checked in Tauri 2 guard for Tauri 1 imports, config and Rust APIs?

It passed the Level 1 automated install checks on 2026-10-29: the files parse, install to each agent's documented paths, and pass the safety scan. No AI agent has run it yet, so no agent pass or fail is claimed.

More for Claude Code, Codex, Cursor, Gemini CLI, Antigravity CLI and GitHub Copilot.