Agent Skills: Tauri Patterns

Tauri path handling, cross-platform file operations, and API usage. Use when working with file paths in Tauri frontend code, accessing filesystem APIs, or handling platform differences in desktop apps.

UncategorizedID: EpicenterHQ/epicenter/tauri

Repository

EpicenterHQLicense: AGPL-3.0
4,013273

Install this agent skill to your local

pnpm dlx add-skill https://github.com/EpicenterHQ/epicenter/tree/HEAD/.agents/skills/tauri

Skill Files

Browse the full folder contents for tauri.

Download Skill

Loading file tree…

.agents/skills/tauri/SKILL.md

Skill Metadata

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.

Tauri Patterns

Reference Repositories

  • 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 ResourceIds. 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)

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:

// OK for logging - consistent cross-platform log output
const logPath = pathSegments.join('/');
console.log(`[Persistence] Loading from ${logPath}`);

Anti-Patterns

Never: Manual String Concatenation

// 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

// 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

// BAD: Windows uses backslashes
const configPath = appDir + '/config.json';

// GOOD: Platform-agnostic
const configPath = await join(appDir, 'config.json');

Never: Assuming Path Format

// 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:

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:

// 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:

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);
}