Skip to main content

tiez-clipboard-manager

TieZ is a Tauri-based cross-platform clipboard manager with history, tags, sync (WebDAV/MQTT), privacy protection, and fast workflows for managing clipboard data across devices.

Jump to install

Source facts

Repository
reason-machines/devtools-skills
Last source activity
May 26, 2026 at 00:55
Detected SKILL.md language
English
Stars
4
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
tiez-clipboard-manager
description
TieZ is a Tauri-based cross-platform clipboard manager with history, tags, sync (WebDAV/MQTT), privacy protection, and fast workflows for managing clipboard data across devices.
triggers
["how do I set up TieZ clipboard manager","configure TieZ clipboard sync with WebDAV","build TieZ clipboard from source","develop features for TieZ clipboard manager","customize TieZ clipboard themes","integrate MQTT sync in TieZ","troubleshoot TieZ clipboard build errors","add tauri commands to TieZ clipboard"]
# TieZ Clipboard Manager Skill > Skill by [ara.so](https://ara.so) — Devtools Skills collection. ## Overview TieZ is a cross-platform clipboard manager built with Tauri 2, React, and Rust. It provides clipboard history tracking, tag organization, privacy masking, cloud sync (WebDAV/MQTT), and multiple UI themes. The application uses a native Rust backend for performance and a TypeScript/React frontend for the UI. **Key Technologies:** - **Tauri 2**: Desktop framework (Rust backend + web frontend) - **React**: Frontend UI framework - **TypeScript**: Frontend language - **Rust**: Backend logic, clipboard monitoring, file operations - **WebDAV/MQTT**: Sync protocols for cross-device data ## Installation & Setup ### Prerequisites ```bash # Install Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Install Node.js (v18+) # macOS brew install node # Windows (use installer from nodejs.org) # Install pnpm npm install -g pnpm ``` ### Clone and Build ```bash # Clone repository git clone https://github.com/jimuzhe/tiez-clipboard.git cd tiez-clipboard # Install frontend dependencies pnpm install # Run in development mode pnpm tauri dev # Build for production pnpm tauri build ``` ### Development Structure ``` tiez-clipboard/ ├── src/ # React frontend (TypeScript) ├── src-tauri/ # Rust backend │ ├── src/ │ │ ├── main.rs │ │ ├── clipboard/ │ │ ├── sync/ │ │ └── commands/ │ ├── Cargo.toml │ └── tauri.conf.json ├── package.json └── vite.config.ts ``` ## Key Concepts ### Tauri Commands TieZ uses Tauri commands to bridge Rust backend and React frontend: **Backend (Rust):** ```rust // src-tauri/src/commands/clipboard.rs use tauri::State; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct ClipboardItem { pub id: String, pub content: String, pub content_type: String, pub timestamp: i64, pub tags: Vec<String>, } #[tauri::command] pub async fn get_clipboard_history( limit: Option<usize>, ) -> Result<Vec<ClipboardItem>, String> { // Query database for clipboard history let items = query_history(limit.unwrap_or(100)) .await .map_err(|e| e.to_string())?; Ok(items) } #[tauri::command] pub async fn add_clipboard_item( content: String, content_type: String, tags: Vec<String>, ) -> Result<ClipboardItem, String> { // Store clipboard item let item = store_clipboard_item(content, content_type, tags) .await .map_err(|e| e.to_string())?; Ok(item) } #[tauri::command] pub async fn delete_clipboard_item(id: String) -> Result<(), String> { remove_item(&id).await.map_err(|e| e.to_string())?; Ok(()) } ``` **Frontend (TypeScript/React):** ```typescript // src/api/clipboard.ts import { invoke } from '@tauri-apps/api/core'; export interface ClipboardItem { id: string; content: string; content_type: string; timestamp: number; tags: string[]; } export async function getClipboardHistory( limit?: number ): Promise<ClipboardItem[]> { return await invoke<ClipboardItem[]>('get_clipboard_history', { limit }); } export async function addClipboardItem( content: string, contentType: string, tags: string[] ): Promise<ClipboardItem> { return await invoke<ClipboardItem>('add_clipboard_item', { content, contentType, tags, }); } export async function deleteClipboardItem(id: string): Promise<void> { await invoke('delete_clipboard_item', { id }); } ``` ### Clipboard Monitoring ```rust // src-tauri/src/clipboard/monitor.rs use clipboard_master::{CallbackResult, ClipboardHandler, Master}; use std::sync::{Arc, Mutex}; use tauri::{AppHandle, Manager}; pub struct ClipboardMonitor { app_handle: AppHandle, } impl ClipboardHandler for ClipboardMonitor { fn on_clipboard_change(&mut self) -> CallbackResult { // Get clipboard content if let Ok(content) = get_clipboard_text() { // Emit event to frontend self.app_handle .emit_all("clipboard-changed", content) .unwrap_or_default(); } CallbackResult::Next } } pub fn start_monitoring(app_handle: AppHandle) { std::thread::spawn(move || { let monitor = ClipboardMonitor { app_handle }; Master::new(monitor).run().unwrap(); }); } ``` ### WebDAV Sync Integration ```rust // src-tauri/src/sync/webdav.rs use reqwest::Client; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct WebDAVConfig { pub url: String, pub username: String, pub password: String, pub path: String, } pub struct WebDAVSync { client: Client, config: WebDAVConfig, } impl WebDAVSync { pub fn new(config: WebDAVConfig) -> Self { Self { client: Client::new(), config, } } pub async fn upload_data(&self, data: &str) -> Result<(), String> { let url = format!("{}/{}", self.config.url, self.config.path); self.client .put(&url) .basic_auth(&self.config.username, Some(&self.config.password)) .body(data.to_string()) .send() .await .map_err(|e| e.to_string())?; Ok(()) } pub async fn download_data(&self) -> Result<String, String> { let url = format!("{}/{}", self.config.url, self.config.path); let response = self.client .get(&url) .basic_auth(&self.config.username, Some(&self.config.password)) .send() .await .map_err(|e| e.to_string())?; response.text().await.map_err(|e| e.to_string()) } } #[tauri::command] pub async fn sync_to_webdav( config: WebDAVConfig, data: String, ) -> Result<(), String> { let sync = WebDAVSync::new(config); sync.upload_data(&data).await } ``` ### MQTT Real-time Sync ```rust // src-tauri/src/sync/mqtt.rs use rumqttc::{AsyncClient, MqttOptions, QoS}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] pub struct MQTTConfig { pub broker: String, pub port: u16, pub client_id: String, pub username: Option<String>, pub password: Option<String>, pub topic: String, } pub async fn publish_clipboard_item( config: &MQTTConfig, item: &ClipboardItem, ) -> Result<(), String> { let mut mqttoptions = MqttOptions::new( &config.client_id, &config.broker, config.port, ); if let (Some(username), Some(password)) = (&config.username, &config.password) { mqttoptions.set_credentials(username, password); } let (client, mut eventloop) = AsyncClient::new(mqttoptions, 10); let payload = serde_json::to_string(item) .map_err(|e| e.to_string())?; client .publish(&config.topic, QoS::AtLeastOnce, false, payload) .await .map_err(|e| e.to_string())?; Ok(()) } #[tauri::command] pub async fn mqtt_sync_item( config: MQTTConfig, item: ClipboardItem, ) -> Result<(), String> { publish_clipboard_item(&config, &item).await } ``` ## Frontend Development ### React Component Example ```typescript // src/components/ClipboardHistory.tsx import React, { useEffect, useState } from 'react'; import { listen } from '@tauri-apps/api/event'; import { getClipboardHistory, ClipboardItem } from '../api/clipboard'; export const ClipboardHistory: React.FC = () => { const [items, setItems] = useState<ClipboardItem[]>([]); const [loading, setLoading] = useState(true); useEffect(() => { // Load initial history loadHistory(); // Listen for clipboard changes const unlisten = listen<string>('clipboard-changed', (event) => { console.log('Clipboard changed:', event.payload); loadHistory(); }); return () => { unlisten.then((fn) => fn()); }; }, []); const loadHistory = async () => { setLoading(true); try { const history = await getClipboardHistory(50); setItems(history); } catch (error) { console.error('Failed to load history:', error); } finally { setLoading(false); } }; return ( <div className="clipboard-history"> {loading ? ( <div>Loading...</div> ) : ( <ul> {items.map((item) => ( <li key={item.id}> <div className="content">{item.content}</div> <div className="tags"> {item.tags.map((tag) => ( <span key={tag} className="tag"> {tag} </span> ))} </div> </li> ))} </ul> )} </div> ); }; ``` ### Settings Management ```typescript // src/api/settings.ts import { invoke } from '@tauri-apps/api/core'; export interface AppSettings { theme: 'frosted' | 'notebook' | 'sticky' | '3d'; darkMode: boolean; autoStart: boolean; maxHistoryItems: number; syncEnabled: boolean; webdavConfig?: { url: string; username: string; path: string; }; mqttConfig?: { broker: string; port: number; clientId: string; topic: string; }; } export async function getSettings(): Promise<AppSettings> { return await invoke<AppSettings>('get_settings'); } export async function updateSettings( settings: Partial<AppSettings> ): Promise<void> { await invoke('update_settings', { settings }); } ``` ```rust // src-tauri/src/commands/settings.rs use tauri::State; use serde::{Deserialize, Serialize}; use std::sync::Mutex; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AppSettings { pub theme: String, pub dark_mode: bool, pub auto_start: bool, pub max_history_items: usize, pub sync_enabled: bool, } pub struct SettingsState(pub Mutex<AppSettings>); #[tauri::command] pub fn get_settings(state: State<SettingsState>) -> Result<AppSettings, String> { let settings = state.0.lock().map_err(|e| e.to_string())?; Ok(settings.clone()) } #[tauri::command] pub fn update_settings( settings: AppSettings, state: State<SettingsState>, ) -> Result<(), String> { let mut current = state.0.lock().map_err(|e| e.to_string())?; *current = settings; // Persist to disk
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub