tauri
$
npx mdskill add EpicenterHQ/epicenter/tauriHandles Tauri commands, file paths, and cross-platform desktop app security
- Solves Tauri command setup, permissions, and filesystem path issues in desktop apps
- Uses Rust commands, @tauri-apps/api, and native filesystem APIs
- Analyzes security config, capabilities, and platform-specific file operations
- Provides precise code patterns for Tauri app development workflows
SKILL.md
.github/skills/tauriView on GitHub ↗
---
name: tauri
description: Tauri commands, permissions, capabilities, security config, path handling, cross-platform file ops, and native filesystem APIs. Use when mentioning Tauri, desktop apps, Rust commands, invoke, capabilities, permissions, ResourceId, file paths, or platform differences.
metadata:
author: epicenter
version: '1.0'
---
# Tauri Patterns
## Reference Repositories
- [Tauri](https://github.com/tauri-apps/tauri) — Desktop app framework with Rust backend and web frontend
## When to Apply This Skill
Use this pattern when you need to:
- Add or change Tauri commands, permissions, capabilities, or security config.
- Build file paths in Tauri frontend code running in the webview.
- Choose correctly between `@tauri-apps/api/path` and Node/Bun `path` APIs.
- Replace manual slash concatenation with `join()`, `dirname()`, and related helpers.
- Handle cross-platform filesystem behavior for desktop apps.
- Combine Tauri path APIs with `@tauri-apps/plugin-fs` operations.
## Commands, Permissions, And Security
- Expose focused Rust APIs with `#[tauri::command]`, register them with `generate_handler!`, and return `Result<T, E>` for fallible work.
- Validate command inputs on the Rust side. TypeScript callers are not the trust boundary.
- Keep capabilities least-privilege in `app.security.capabilities`, scoped to the windows or webviews that need them. Avoid broad permission wildcards.
- Treat CSP, `devCsp`, asset protocol configuration, `convertFileSrc`, `freezePrototype`, and remote IPC as security-sensitive config.
- Long-lived Rust objects should be Tauri resources with frontend `ResourceId`s. Do not serialize complex long-lived objects through command responses.
## Context Detection
Before choosing a path API, determine your execution context:
| Context | Location | Correct API |
| ----------------------- | ---------------------------------------------- | ---------------------- |
| **Tauri frontend** | `apps/*/src/**/*.ts`, `apps/*/src/**/*.svelte` | `@tauri-apps/api/path` |
| **Node.js/Bun backend** | `packages/**/*.ts`, CLI tools | Node.js `path` module |
**Rule**: If the code runs in the browser (Tauri webview), use Tauri's path APIs. If it runs in Node.js/Bun, use the Node.js `path` module.
## Available Functions from `@tauri-apps/api/path`
### Path Manipulation
| Function | Purpose | Example |
| ---------------------- | ------------------------------------------ | ------------------------------------------------- |
| `join(...paths)` | Join path segments with platform separator | `await join(baseDir, 'workspaces', id)` |
| `dirname(path)` | Get parent directory | `await dirname('/foo/bar/file.txt')` → `/foo/bar` |
| `basename(path, ext?)` | Get filename, optionally strip extension | `await basename('/foo/bar.txt', '.txt')` → `bar` |
| `extname(path)` | Get file extension | `await extname('file.txt')` → `.txt` |
| `normalize(path)` | Resolve `..` and `.` segments | `await normalize('/foo/bar/../baz')` → `/foo/baz` |
| `resolve(...paths)` | Resolve to absolute path | `await resolve('relative', 'path')` |
| `isAbsolute(path)` | Check if path is absolute | `await isAbsolute('/foo')` → `true` |
### Platform Constants
| Function | Purpose | Returns |
| ------------- | ----------------------- | ---------------------------- |
| `sep()` | Platform path separator | `\` on Windows, `/` on POSIX |
| `delimiter()` | Platform path delimiter | `;` on Windows, `:` on POSIX |
### Base Directories
| Function | Purpose |
| ----------------------- | ---------------------------------- |
| `appLocalDataDir()` | App's local data directory |
| `appDataDir()` | App's roaming data directory |
| `appConfigDir()` | App's config directory |
| `appCacheDir()` | App's cache directory |
| `appLogDir()` | App's log directory |
| `tempDir()` | System temp directory |
| `resourceDir()` | App's resource directory |
| `resolveResource(path)` | Resolve path relative to resources |
## Patterns
### Constructing Paths (Correct)
```typescript
import { appLocalDataDir, dirname, join } from '@tauri-apps/api/path';
// Join path segments - handles platform separators automatically
const baseDir = await appLocalDataDir();
const filePath = await join(baseDir, 'workspaces', workspaceId, 'data.json');
// Get parent directory - cleaner than manual slicing
const parentDir = await dirname(filePath);
await mkdir(parentDir, { recursive: true });
```
### Logging Paths (Exception)
For human-readable log output, hardcoded `/` is acceptable since it's not used for filesystem operations:
```typescript
// OK for logging - consistent cross-platform log output
const logPath = pathSegments.join('/');
console.log(`[Persistence] Loading from ${logPath}`);
```
## Anti-Patterns
### Never: Manual String Concatenation
```typescript
// BAD: Hardcoded separator breaks on Windows
const filePath = baseDir + '/' + 'workspaces' + '/' + id;
// BAD: Template literal with hardcoded separator
const filePath = `${baseDir}/workspaces/${id}`;
// GOOD: Use join()
const filePath = await join(baseDir, 'workspaces', id);
```
### Never: Manual Parent Directory Extraction
```typescript
// BAD: Manual slicing is error-prone
const parentSegments = pathSegments.slice(0, -1);
const parentDir = await join(baseDir, ...parentSegments);
// GOOD: Use dirname()
const parentDir = await dirname(filePath);
```
### Never: Hardcoded Separators in Filesystem Operations
```typescript
// BAD: Windows uses backslashes
const configPath = appDir + '/config.json';
// GOOD: Platform-agnostic
const configPath = await join(appDir, 'config.json');
```
### Never: Assuming Path Format
```typescript
// BAD: Splitting on '/' fails on Windows paths
const parts = filePath.split('/');
// GOOD: Use dirname/basename for extraction
const dir = await dirname(filePath);
const file = await basename(filePath);
```
## Import Pattern
Always import from `@tauri-apps/api/path`:
```typescript
import {
appLocalDataDir,
dirname,
join,
basename,
extname,
normalize,
resolve,
sep,
} from '@tauri-apps/api/path';
```
## Note on Async
All Tauri path functions are **async** because they communicate with the Rust backend via IPC. Always `await` them:
```typescript
// All path operations return Promises
const baseDir = await appLocalDataDir();
const filePath = await join(baseDir, 'file.txt');
const parent = await dirname(filePath);
const separator = await sep();
```
## Filesystem Operations
Use `@tauri-apps/plugin-fs` for file operations, combined with Tauri path APIs:
```typescript
import { appLocalDataDir, dirname, join } from '@tauri-apps/api/path';
import { mkdir, readFile, writeFile } from '@tauri-apps/plugin-fs';
async function saveData(segments: string[], data: Uint8Array) {
const baseDir = await appLocalDataDir();
const filePath = await join(baseDir, ...segments);
// Ensure parent directory exists
const parentDir = await dirname(filePath);
await mkdir(parentDir, { recursive: true });
await writeFile(filePath, data);
}
```
More from EpicenterHQ/epicenter
- agent-goalWrite `/goal` prompts for long-running agent work in Codex or Claude Code. Use for slash goal, agent goal, durable objective, autonomous coding run.
- approachability-auditReview code as a new TypeScript developer. Use when code feels indirect, clever, hard to follow, or needs a pass on abstractions, names, first-read clarity.
- arktypeArktype: runtime validation, discriminated unions with .merge()/.or(), spread keys. Use when mentioning arktype, type(), union types, command/event schemas.
- attach-primitiveContract and invariants for `attach*` composition primitives in `packages/workspace` (side-effectful building blocks like attachIndexedDb, attachSqlite, attachBroadcastChannel, attachEncryption, attachTable, openCollaboration), and when to use `create*` (pure construction) instead. Use when writing or reviewing an `attach*` or `create*` function, naming a new workspace primitive, composing inside a workspace builder, or deciding whether a primitive registers listeners at call time.
- authEpicenter auth packages: `@epicenter/auth`, `@epicenter/auth-svelte`, OAuth sessions, identity state, auth-owned fetch/WebSocket, and workspace lifecycle binding. Use when editing Epicenter auth clients, session state, hosted sign-in, or auth/workspace integration.
- autumnAutumn billing in Epicenter: `autumn.config.ts`, `autumn-js` credit checks, `atmn` CLI, plan gates, and metered AI usage. Use when changing billing, pricing, credits, plan access, refunds, or usage events.
- better-auth-best-practicesBetter Auth server/client setup: `auth.ts`, generated schema, DB adapters, sessions, cookies, env vars, and plugins. Use when mentioning Better Auth, betterauth, auth handlers, OAuth, email/password, or session configuration.
- better-auth-security-best-practicesBetter Auth security hardening: rate limits, secrets, CSRF, trusted origins, cookies, sessions, OAuth tokens, and audit logging. Use when reviewing auth security, brute-force protection, token handling, or deployment safety.
- change-proposalPresent proposed code changes visually before implementing. Use when: "show me options", "compare approaches", "what should we do", or when changes need before/after comparison.
- claude-code-consultUse this skill when the user asks to consult Claude, ask Claude Code, get another model's take, run a taste check, find cleaner options, or prepare a Claude prompt. Create a bounded second-opinion prompt or run a read-only Claude Code consult, then verify Claude's claims against local files.